r语言error in match.fun(fun) :_Go语言200行写区块链源代码分析
Github上有一個Repo,是一個使用Go語言(golang),不到200行代碼寫的區(qū)塊鏈源代碼,準(zhǔn)確的說是174行。原作者起了個名字是 Code your own blockchain in less than 200 lines of Go! 而且作者也為此寫了一篇文章。https://medium.com/@mycoralhealth/code-your-own-blockchain-in-less-than-200-lines-of-go-e296282bcffc
這篇文章是一個大概的思路和代碼的實現(xiàn),當(dāng)然還有很多代碼的邏輯沒有涉及,所以我就針對這不到200行的代碼進(jìn)行一個分析,包含原文章里沒有涉及到的知識點,對Go語言,區(qū)塊鏈都會有一個更深的認(rèn)識。
所有的源代碼都在這里:https://github.com/nosequeldeebee/blockchain-tutorial/blob/master/main.go
import?(????"crypto/sha256"
????"encoding/hex"
????"encoding/json"
????"io"
????"log"
????"net/http"
????"os"
????"strconv"
????"sync"
????"time"
????"github.com/davecgh/go-spew/spew"
????"github.com/gorilla/mux"
????"github.com/joho/godotenv"
)
在源代碼的開頭,是作者引入的一些包,有標(biāo)準(zhǔn)的,也有第三方的。像sha256,hex這些標(biāo)準(zhǔn)包是為了sha-256編碼用的,其他還有啟動http服務(wù),打印日志的log,并發(fā)控制的sync,時間戳的time。
第三方包有三個,其中兩個我都詳細(xì)介紹過,相信大家不會陌生。
go-spew是一個變量結(jié)構(gòu)體的調(diào)試?yán)?#xff0c;可以打印出變量結(jié)構(gòu)體對應(yīng)的數(shù)據(jù)和結(jié)構(gòu),調(diào)試非常方便
gorilla/mux是一個web路由服務(wù),可以很簡單的幫我們構(gòu)建web服務(wù)。
不過目前用gin的比較多,也推薦使用gin https://github.com/gin-gonic/gin。
godotenv是一個讀取配置文章的庫,可以讓我們讀取.env格式的配置文件,比如從配置文件里讀取IP、PORT等。不過目前配置文件還是推薦YAML和TOML,對應(yīng)的第三方庫是:
gopkg.in/yaml.v21https://github.com/BurntSushi/toml
既然要寫一個區(qū)塊鏈,那么肯定的有一個區(qū)塊的實體,我們通過golang的struct來實現(xiàn)。
//?Block?represents?each?'item'?in?the?blockchaintype?Block?struct?{
????Index?????int
????Timestamp?string
????BPM???????int
????Hash??????string
????PrevHash??string
}
Block里包含幾個字段:
Index 就是Block的順序索引
Timestamp是生成Block的時間戳
BPM,作者說代表心率,每分鐘心跳數(shù)
Hash是通過sha256生成的散列值,對整個Block數(shù)據(jù)的Hash
PrevHash 上一個Block的Hash,這樣區(qū)塊才能連在一起構(gòu)成區(qū)塊鏈
有了區(qū)塊Block了,那么區(qū)塊鏈就非常好辦了。
//?Blockchain?is?a?series?of?validated?Blocksvar?Blockchain?[]Block
就是這么簡單,一個Block數(shù)組就是一個區(qū)塊鏈。區(qū)塊鏈的構(gòu)成關(guān)鍵在于Hash和PrevHash,通過他們一個個串聯(lián)起來,就是一串Block,也就是區(qū)塊鏈。因為相互之間通過Hash和PrevHash進(jìn)行關(guān)聯(lián),所以整個鏈很難被篡改,鏈越長被篡改的成本越大,因為要把整個鏈全部改掉才能完成篡改的目的,這樣的話,其他節(jié)點驗證這次篡改肯定是不能通過的。
既然關(guān)鍵點在于Hash,所以我們要先算出來一個Block的數(shù)據(jù)的Hash,也就是對Block里的字段進(jìn)行Hash,計算出一個唯一的Hash值。
//?SHA256?hasingfunc?calculateHash(block?Block)?string?{
????record?:=?strconv.Itoa(block.Index)?+?block.Timestamp?+?strconv.Itoa(block.BPM)?+?block.PrevHash
????h?:=?sha256.New()
????h.Write([]byte(record))
????hashed?:=?h.Sum(nil)
????return?hex.EncodeToString(hashed)
}
sha256是golang內(nèi)置的sha256的散列標(biāo)準(zhǔn)庫,可以讓我們很容易的生成對應(yīng)數(shù)據(jù)的散列值。從源代碼看,是把Block的所有字段進(jìn)行字符串拼接,然后通過sha256進(jìn)行散列,散列的數(shù)據(jù)再通過hex.EncodeToString轉(zhuǎn)換為16進(jìn)制的字符串,這樣就得到了我們常見的sha256散列值,類似這樣的字符串8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92。
Block的散列值被我們計算出來了,Block的Hash和PrevHash這兩個字段搞定了,那么我們現(xiàn)在就可以生成一個區(qū)塊了,因為其他幾個字段都是可以自動生成的。
//?create?a?new?block?using?previous?block's?hashfunc?generateBlock(oldBlock?Block,?BPM?int)?Block?{
????var?newBlock?Block
????t?:=?time.Now()
????newBlock.Index?=?oldBlock.Index?+?1
????newBlock.Timestamp?=?t.String()
????newBlock.BPM?=?BPM
????newBlock.PrevHash?=?oldBlock.Hash
????newBlock.Hash?=?calculateHash(newBlock)
????return?newBlock
}
因為區(qū)塊鏈?zhǔn)琼樞蛳噙B的,所以我們在生成一個新的區(qū)塊的時候,必須知道上一個區(qū)塊,也就是源代碼里的oldBlock。另外一個參數(shù)BPM就是我們需要在區(qū)塊里存儲的數(shù)據(jù)信息了,這里作者演示的例子是心率,我們可以換成其他業(yè)務(wù)中想要的數(shù)據(jù)。
Index是上一個區(qū)塊的Index+1,保持順序;Timestamp通過time.Now()可以得到;Hash通過calculateHash方法計算出來。這樣我們就生成了一個新的區(qū)塊。
在這里作者并沒有使用POW(工作量證明)這類算法來生成區(qū)塊,而是沒有任何條件的,這里主要是為了模擬區(qū)塊的生成,演示方便。
區(qū)塊可以生成了,但是生成的區(qū)塊是否可信,我們還得對他進(jìn)行校驗,不能隨便生成一個區(qū)塊。在比特幣(BitCoin)中校驗比較復(fù)雜,這里作者采用了簡單模擬的方式。
//?make?sure?block?is?valid?by?checking?index,?and?comparing?the?hash?of?the?previous?blockfunc?isBlockValid(newBlock,?oldBlock?Block)?bool?{
????if?oldBlock.Index+1?!=?newBlock.Index?{
????????return?false
????}
????if?oldBlock.Hash?!=?newBlock.PrevHash?{
????????return?false
????}
????if?calculateHash(newBlock)?!=?newBlock.Hash?{
????????return?false
????}
????return?true
}
簡單的對比Index,Hash是否是正確的,并且重新計算了一遍Hash,防止被篡改。
到了這里,關(guān)于區(qū)塊鏈的代碼已經(jīng)全部完成了,剩下的就是把區(qū)塊鏈的生成、查看等包裝成一個Web服務(wù),可以通過API、瀏覽器訪問查看。因為作者這里沒有實現(xiàn)P2P網(wǎng)絡(luò),所以采用的是WEB服務(wù)的方式。
//?create?handlersfunc?makeMuxRouter()?http.Handler?{
????muxRouter?:=?mux.NewRouter()
????muxRouter.HandleFunc("/",?handleGetBlockchain).Methods("GET")
????muxRouter.HandleFunc("/",?handleWriteBlock).Methods("POST")
????return?muxRouter
}
通過mux定義了兩個Handler,URL都是/,但是對應(yīng)的Method是不一樣的。
GET方法通過handleGetBlockchain函數(shù)實現(xiàn),用于獲取區(qū)塊鏈的信息。
func?handleGetBlockchain(w?http.ResponseWriter,?r?*http.Request)?{????bytes,?err?:=?json.MarshalIndent(Blockchain,?"",?"??")
????if?err?!=?nil?{
????????http.Error(w,?err.Error(),?http.StatusInternalServerError)
????????return
????}
????io.WriteString(w,?string(bytes))
}
Blockchain是一個[]Block,handleGetBlockchain函數(shù)的作用是把Blockchain格式化為JSON字符串,然后顯示出來。io.WriteString是一個很好用的函數(shù),可以往Writer里寫入字符串。更多參考?Go語言實戰(zhàn)筆記(十九)| Go Writer 和 Reader
'POST'方法通過handleWriteBlock函數(shù)實現(xiàn),用于模擬區(qū)塊的生成。
func?handleWriteBlock(w?http.ResponseWriter,?r?*http.Request)?{????w.Header().Set("Content-Type",?"application/json")
????//使用了一個Mesage結(jié)構(gòu)體,更方便的存儲BPM
????var?msg?Message
????//接收請求的數(shù)據(jù)信息,類似{"BPM":60}這樣的格式
????decoder?:=?json.NewDecoder(r.Body)
????if?err?:=?decoder.Decode(&msg);?err?!=?nil?{
????????respondWithJSON(w,?r,?http.StatusBadRequest,?r.Body)
????????return
????}
????defer?r.Body.Close()
????//控制并發(fā),生成區(qū)塊鏈,并且校驗
????mutex.Lock()
????prevBlock?:=?Blockchain[len(Blockchain)-1]
????newBlock?:=?generateBlock(prevBlock,?msg.BPM)
????//校驗區(qū)塊鏈
????if?isBlockValid(newBlock,?prevBlock)?{
????????Blockchain?=?append(Blockchain,?newBlock)
????????spew.Dump(Blockchain)
????}
????mutex.Unlock()
????//返回新的區(qū)塊信息
????respondWithJSON(w,?r,?http.StatusCreated,?newBlock)
}
以上代碼我進(jìn)行了注釋,便于理解。主要是通過POST發(fā)送一個{"BPM":60}格式的BODY來添加區(qū)塊,如果格式正確,那么就生成區(qū)塊進(jìn)行校驗,合格了就加入到區(qū)塊里;如果格式不對,那么返回錯誤信息。
用于控制并發(fā)的鎖可以參考Go語言實戰(zhàn)筆記(十七)| Go 讀寫鎖
這個方法里有個Message結(jié)構(gòu)體,主要是為了便于操作方便。
//?Message?takes?incoming?JSON?payload?for?writing?heart?ratetype?Message?struct?{
????BPM?int
}
返回的JSON信息,也被抽取成了一個函數(shù)respondWithJSON,便于公用。
func?respondWithJSON(w?http.ResponseWriter,?r?*http.Request,?code?int,?payload?interface{})?{????response,?err?:=?json.MarshalIndent(payload,?"",?"??")
????if?err?!=?nil?{
????????w.WriteHeader(http.StatusInternalServerError)
????????w.Write([]byte("HTTP?500:?Internal?Server?Error"))
????????return
????}
????w.WriteHeader(code)
????w.Write(response)
}
好了,快完成了,以上Web的Handler已經(jīng)好了,現(xiàn)在我們要啟動我們的Web服務(wù)了。
//?web?serverfunc?run()?error?{
????mux?:=?makeMuxRouter()
????//從配置文件里讀取監(jiān)聽的端口
????httpPort?:=?os.Getenv("PORT")
????log.Println("HTTP?Server?Listening?on?port?:",?httpPort)
????s?:=?&http.Server{
????????Addr:???????????":"?+?httpPort,
????????Handler:????????mux,
????????ReadTimeout:????10?*?time.Second,
????????WriteTimeout:???10?*?time.Second,
????????MaxHeaderBytes:?1?<20,
????}
????if?err?:=?s.ListenAndServe();?err?!=?nil?{
????????return?err
????}
????return?nil
}
和原生的http.Server基本一樣,應(yīng)該比較好理解。mux其實也是一個Handler,這就是整個Handler處理鏈。現(xiàn)在我們就差一個main主函數(shù)來啟動我們整個程序了。
//控制并發(fā)的鎖var?mutex?=?&sync.Mutex{}
func?main()?{
????//加載env配置文件
????err?:=?godotenv.Load()
????if?err?!=?nil?{
????????log.Fatal(err)
????}
????//開啟一個goroutine生成一個創(chuàng)世區(qū)塊
????go?func()?{
????????t?:=?time.Now()
????????genesisBlock?:=?Block{}
????????genesisBlock?=?Block{0,?t.String(),?0,?calculateHash(genesisBlock),?""}
????????spew.Dump(genesisBlock)
????????mutex.Lock()
????????Blockchain?=?append(Blockchain,?genesisBlock)
????????mutex.Unlock()
????}()
????log.Fatal(run())
}
整個main函數(shù)并不太復(fù)雜,主要就是加載env配置文件,開啟一個go協(xié)程生成一個創(chuàng)世區(qū)塊并且添加到區(qū)塊鏈的第一個位置,然后就是通過run函數(shù)啟動Web服務(wù)。
一個區(qū)塊鏈都有一個創(chuàng)世區(qū)塊,也就是第一個區(qū)塊。有了第一個區(qū)塊我們才能添加第二個,第三個,第N個區(qū)塊。創(chuàng)世區(qū)塊因為是第一個區(qū)塊,所以它是沒有PrevHash的。
終于可以運(yùn)行了,假設(shè)我們設(shè)置的PORT是8080,現(xiàn)在我們通過go run main.go啟動這個簡易的區(qū)塊鏈程序,就可以看到控制臺輸出的創(chuàng)世區(qū)塊信息。然后我們通過瀏覽器打開http://localhost:8080也可以看到這個區(qū)塊鏈的信息,里面只有一個創(chuàng)世區(qū)塊。
如果我們要新增一個區(qū)塊,通過curl或者postman,向http://localhost:8080?發(fā)送body格式為{"BPM":60}的POST的信息即可。然后在通過瀏覽器訪問http://localhost:8080查看區(qū)塊鏈信息,驗證是否已經(jīng)添加成功。
到這里,整個源代碼的分析已經(jīng)完了,我們看下這個簡易的區(qū)塊鏈涉及到多少知識:
sha256散列
字節(jié)到16進(jìn)制轉(zhuǎn)換
并發(fā)同步鎖
Web服務(wù)
配置文件
后向式鏈表
結(jié)構(gòu)體
JSON
……
等等,上面的很多知識,我已經(jīng)在文章中講解或者通過以前些的文章說明,大家可以看一下,詳細(xì)了解。
推薦閱讀
go語言的開源區(qū)塊鏈代碼都有哪些?歡迎留言補(bǔ)充
站長 polarisxu
自己的原創(chuàng)文章
不限于 Go 技術(shù)
職場和創(chuàng)業(yè)經(jīng)驗
Go語言中文網(wǎng)
每天為你
分享 Go 知識
Go愛好者值得關(guān)注
總結(jié)
以上是生活随笔為你收集整理的r语言error in match.fun(fun) :_Go语言200行写区块链源代码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Apache Cassandra和Apa
- 下一篇: 唐纳德 高德纳给年轻人的建议 Donal