区块链技术实现只需180行go代码!
區(qū)塊鏈技術(shù)實(shí)現(xiàn)只需180行g(shù)o代碼!
通過本文,你將可以使用Go創(chuàng)建自己的區(qū)塊鏈、理解哈希函數(shù)是如何保持區(qū)塊鏈的完整性、掌握如何創(chuàng)造并添加新的塊、實(shí)現(xiàn)多個(gè)節(jié)點(diǎn)通過競(jìng)爭(zhēng)生成塊、通過瀏覽器來查看整個(gè)鏈、了解所有其他關(guān)于區(qū)塊鏈的基礎(chǔ)知識(shí)。
如果你希望馬上開始學(xué)習(xí)以太坊DApp開發(fā),可以訪問匯智網(wǎng)提供的出色的在線互動(dòng)教程:
- 以太坊DApp實(shí)戰(zhàn)開發(fā)入門
- 去中心化電商DApp實(shí)戰(zhàn)開發(fā)
但是,文章中將不會(huì)涉及工作量證明算法(PoW)以及權(quán)益證明算法(PoS)這類的共識(shí)算法,同時(shí)為了讓你更清楚得查看區(qū)塊鏈以及塊的添加,我們將網(wǎng)絡(luò)交互的過程簡(jiǎn)化了,關(guān)于 P2P 網(wǎng)絡(luò)比如“全網(wǎng)廣播”這個(gè)過程等內(nèi)容將在后續(xù)文章中補(bǔ)上。
開發(fā)環(huán)境
我們假設(shè)你已經(jīng)具備一點(diǎn) Go 語言的開發(fā)經(jīng)驗(yàn)。在安裝和配置 Go 開發(fā)環(huán)境后之后,我們還要獲取以下一些依賴:
~$ go get github.com/davecgh/go-spew/spew- 1
spew可以幫助我們?cè)诮K端中中直接查看 struct 和 slice 這兩種數(shù)據(jù)結(jié)構(gòu)。
~$ go get github.com/gorilla/mux- 1
Gorilla 的?mux?包非常流行, 我們用它來寫 web handler。
~$ go get github.com/joho/godotenv- 1
godotenv可以幫助我們讀取項(xiàng)目根目錄中的.env?配置文件,這樣就不用將 http端口之類的配置硬編碼進(jìn)代碼中了。比如像這樣:
ADDR=8080- 1
接下來,我們創(chuàng)建一個(gè)?main.go?文件。之后的大部分工作都圍繞這個(gè)文件,開始寫代碼吧!
導(dǎo)入依賴包
我們將所有的依賴包以聲明的方式導(dǎo)入進(jìn)去:
package mainimport ("crypto/sha256""encoding/hex""encoding/json""io""log""net/http""os""time""github.com/davecgh/go-spew/spew""github.com/gorilla/mux""github.com/joho/godotenv" )- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
數(shù)據(jù)模型
接著我們來定義一個(gè)結(jié)構(gòu)體,它代表組成區(qū)塊鏈的每一個(gè)塊的數(shù)據(jù)模型:
type Block struct {Index intTimestamp stringBPM intHash stringPrevHash string }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- Index 是這個(gè)塊在整個(gè)鏈中的位置
- Timestamp 顯而易見就是塊生成時(shí)的時(shí)間戳
- Hash 是這個(gè)塊通過 SHA256 算法生成的散列值
- PrevHash 代表前一個(gè)塊的 SHA256 散列值
- BPM 每分鐘心跳數(shù),也就是心率
接著,我們?cè)俣x一個(gè)結(jié)構(gòu)表示整個(gè)鏈,最簡(jiǎn)單的表示形式就是一個(gè) Block 的 slice:
var Blockchain []Block- 1
我們使用散列算法(SHA256)來確定和維護(hù)鏈中塊和塊正確的順序,確保每一個(gè)塊的 PrevHash 值等于前一個(gè)塊中的 Hash 值,這樣就以正確的塊順序構(gòu)建出鏈:
散列和生成新塊
我們?yōu)槭裁葱枰⒘?#xff1f;主要是兩個(gè)原因:
- 在節(jié)省空間的前提下去唯一標(biāo)識(shí)數(shù)據(jù)。散列是用整個(gè)塊的數(shù)據(jù)計(jì)算得出,在我們的例子中,將整個(gè)塊的數(shù)據(jù)通過 SHA256 計(jì)算成一個(gè)定長(zhǎng)不可偽造的字符串。
- 維持鏈的完整性。通過存儲(chǔ)前一個(gè)塊的散列值,我們就能夠確保每個(gè)塊在鏈中的正確順序。任何對(duì)數(shù)據(jù)的篡改都將改變散列值,同時(shí)也就破壞了鏈。以我們從事的醫(yī)療健康領(lǐng)域?yàn)槔?#xff0c;比如有一個(gè)惡意的第三方為了調(diào)整“人壽險(xiǎn)”的價(jià)格,而修改了一個(gè)或若干個(gè)塊中的代表不健康的 BPM 值,那么整個(gè)鏈都變得不可信了。
我們接著寫一個(gè)函數(shù),用來計(jì)算給定的數(shù)據(jù)的 SHA256 散列值:
func calculateHash(block Block) string {record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHashh := sha256.New()h.Write([]byte(record))hashed := h.Sum(nil)return hex.EncodeToString(hashed) }- 1
- 2
- 3
- 4
- 5
- 6
- 7
這個(gè) calculateHash 函數(shù)接受一個(gè)塊,通過塊中的 Index,Timestamp,BPM,以及 PrevHash 值來計(jì)算出 SHA256 散列值。接下來我們就能編寫一個(gè)生成塊的函數(shù):
func generateBlock(oldBlock Block, BPM int) (Block, error) {var newBlock Blockt := time.Now()newBlock.Index = oldBlock.Index + 1newBlock.Timestamp = t.String()newBlock.BPM = BPMnewBlock.PrevHash = oldBlock.HashnewBlock.Hash = calculateHash(newBlock)return newBlock, nil }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
其中,Index 是從給定的前一塊的 Index 遞增得出,時(shí)間戳是直接通過 time.Now() 函數(shù)來獲得的,Hash 值通過前面的 calculateHash 函數(shù)計(jì)算得出,PrevHash 則是給定的前一個(gè)塊的 Hash 值。
校驗(yàn)塊
搞定了塊的生成,接下來我們需要有函數(shù)幫我們判斷一個(gè)塊是否有被篡改。檢查 Index 來看這個(gè)塊是否正確得遞增,檢查 PrevHash 與前一個(gè)塊的 Hash 是否一致,再來通過 calculateHash 檢查當(dāng)前塊的 Hash 值是否正確。通過這幾步我們就能寫出一個(gè)校驗(yàn)函數(shù):
func 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 }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
除了校驗(yàn)塊以外,我們還會(huì)遇到一個(gè)問題:兩個(gè)節(jié)點(diǎn)都生成塊并添加到各自的鏈上,那我們應(yīng)該以誰為準(zhǔn)?這里的細(xì)節(jié)我們留到下一篇文章,?
這里先讓我們記住一個(gè)原則:始終選擇最長(zhǎng)的鏈:
通常來說,更長(zhǎng)的鏈表示它的數(shù)據(jù)(狀態(tài))是更新的,所以我們需要一個(gè)函數(shù)能幫我們將本地的過期的鏈切換成最新的鏈:
func replaceChain(newBlocks []Block) {if len(newBlocks) > len(Blockchain) {Blockchain = newBlocks} }- 1
- 2
- 3
- 4
- 5
到這一步,我們基本就把所有重要的函數(shù)完成了。接下來,我們需要一個(gè)方便直觀的方式來查看我們的鏈,包括數(shù)據(jù)及狀態(tài)。通過瀏覽器查看 web 頁面可能是最合適的方式!
Web 服務(wù)
我猜你一定對(duì)傳統(tǒng)的 web 服務(wù)及開發(fā)非常熟悉,所以這部分你肯定一看就會(huì)。
借助 Gorilla/mux 包,我們先寫一個(gè)函數(shù)來初始化我們的 web 服務(wù):
func run() error {mux := makeMuxRouter()httpAddr := os.Getenv("ADDR")log.Println("Listening on ", os.Getenv("ADDR"))s := &http.Server{Addr: ":" + httpAddr,Handler: mux,ReadTimeout: 10 * time.Second,WriteTimeout: 10 * time.Second,MaxHeaderBytes: 1 << 20,}if err := s.ListenAndServe(); err != nil {return err}return nil }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
其中的端口號(hào)是通過前面提到的 .env 來獲得,再添加一些基本的配置參數(shù),這個(gè) web 服務(wù)就已經(jīng)可以 listen and serve 了!
接下來我們?cè)賮矶x不同 endpoint 以及對(duì)應(yīng)的 handler。例如,對(duì)“/”的 GET 請(qǐng)求我們可以查看整個(gè)鏈,“/”的 POST 請(qǐng)求可以創(chuàng)建塊。
func makeMuxRouter() http.Handler {muxRouter := mux.NewRouter()muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")return muxRouter }- 1
- 2
- 3
- 4
- 5
- 6
GET 請(qǐng)求的 handler:
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)) }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
為了簡(jiǎn)化,我們直接以 JSON 格式返回整個(gè)鏈,你可以在瀏覽器中訪問 localhost:8080 或者 127.0.0.1:8080 來查看(這里的8080就是你在 .env 中定義的端口號(hào) ADDR)。
POST 請(qǐng)求的 handler 稍微有些復(fù)雜,我們先來定義一下 POST 請(qǐng)求的 payload:
type Message struct {BPM int }- 1
- 2
- 3
再看看 handler 的實(shí)現(xiàn):
func handleWriteBlock(w http.ResponseWriter, r *http.Request) {var m Messagedecoder := json.NewDecoder(r.Body)if err := decoder.Decode(&m); err != nil {respondWithJSON(w, r, http.StatusBadRequest, r.Body)return}defer r.Body.Close()newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], m.BPM)if err != nil {respondWithJSON(w, r, http.StatusInternalServerError, m)return}if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {newBlockchain := append(Blockchain, newBlock)replaceChain(newBlockchain)spew.Dump(Blockchain)}respondWithJSON(w, r, http.StatusCreated, newBlock)}- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
我們的 POST 請(qǐng)求體中可以使用上面定義的 payload,比如:
{"BPM":75}- 1
還記得前面我們寫的 generateBlock 這個(gè)函數(shù)嗎?它接受一個(gè)“前一個(gè)塊”參數(shù),和一個(gè) BPM 值。POST handler 接受請(qǐng)求后就能獲得請(qǐng)求體中的 BPM 值,接著借助生成塊的函數(shù)以及校驗(yàn)塊的函數(shù)就能生成一個(gè)新的塊了!
除此之外,你也可以:
- 使用spew.Dump 這個(gè)函數(shù)可以以非常美觀和方便閱讀的方式將 struct、slice 等數(shù)據(jù)打印在控制臺(tái)里,方便我們調(diào)試。
- 測(cè)試 POST 請(qǐng)求時(shí),可以使用 POSTMAN 這個(gè) chrome 插件,相比 curl它更直觀和方便。
POST 請(qǐng)求處理完之后,無論創(chuàng)建塊成功與否,我們需要返回客戶端一個(gè)響應(yīng):
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) }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
快要大功告成了。
接下來,我們把這些關(guān)于區(qū)塊鏈的函數(shù),web 服務(wù)的函數(shù)“組裝”起來:
func main() {err := godotenv.Load()if err != nil {log.Fatal(err)}go func() {t := time.Now()genesisBlock := Block{0, t.String(), 0, "", ""}spew.Dump(genesisBlock)Blockchain = append(Blockchain, genesisBlock)}()log.Fatal(run()) }- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
這里的 genesisBlock (創(chuàng)世塊)是 main 函數(shù)中最重要的部分,通過它來初始化區(qū)塊鏈,畢竟第一個(gè)塊的 PrevHash 是空的。
哦耶!完成了
可以從這里獲得完整的代碼:Github repo
讓我們來啟動(dòng)它:
~$ go run main.go- 1
在終端中,我們可以看到 web 服務(wù)器啟動(dòng)的日志信息,并且打印出了創(chuàng)世塊的信息:
接著我們打開瀏覽器,訪問 localhost:8080 這個(gè)地址,我們可以看到頁面中展示了當(dāng)前整個(gè)區(qū)塊鏈的信息(當(dāng)然,目前只有一個(gè)創(chuàng)世塊):
接著,我們?cè)偻ㄟ^ POSTMAN 來發(fā)送一些 POST 請(qǐng)求:
?
刷新剛才的頁面,現(xiàn)在的鏈中多了一些塊,正是我們剛才生成的,同時(shí)你們可以看到,塊的順序和散列值都正確。
總結(jié)
剛剛我們完成了一個(gè)自己的區(qū)塊鏈,雖然很簡(jiǎn)單(陋),但它具備塊生成、散列計(jì)算、塊校驗(yàn)等基本能力。接下來你就可以繼續(xù)深入的學(xué)習(xí)?
區(qū)塊鏈的其他重要知識(shí),比如工作量證明、權(quán)益證明這樣的共識(shí)算法,或者是智能合約、Dapp、側(cè)鏈等等。
目前這個(gè)實(shí)現(xiàn)中不包括任何 P2P 網(wǎng)絡(luò)的內(nèi)容,我們會(huì)在下一篇文章中補(bǔ)充這部分內(nèi)容,當(dāng)然,我們鼓勵(lì)你在這個(gè)基礎(chǔ)上自己實(shí)踐一遍!
原文:180行g(shù)o代碼讓你徹底理解區(qū)塊鏈?zhǔn)鞘裁?/p> 《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀
總結(jié)
以上是生活随笔為你收集整理的区块链技术实现只需180行go代码!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 谷歌提出新型卷积网络EfficientN
- 下一篇: AIADATA 独家深度解密:百度Xup