Go語言開發區塊鏈只需180行代碼

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。區塊鏈開發用什麼語言?通過本文你將使用**Go**語言開發自己的區塊鏈(或者說用go語言搭建區塊鏈)、理解雜湊函數是如何保持區塊鏈的完整性、掌握如何用Go語言編程創造並添加新的塊、實現多個節點通過競爭產生塊、通過瀏覽器來查看整個鏈、瞭解所有其他關於區塊鏈的基礎知識。但是,文章中將不會涉及工作量證明演算法(PoW)以及權益證明演算法(PoS)這類的共識演算法,同時為了讓你更清楚得查看區塊鏈以及塊的添加,我們將網路互動的過程簡化了,關於 P2P 網路比如“全網廣播”這個過程等內容將在後續文章中補上。## 開發環境我們假設你已經具備一點 Go 語言的開發經驗。在安裝和配置 Go 開發環境後之後,我們還要擷取以下一些依賴:```~$ go get github.com/davecgh/go-spew/spew````spew`可以協助我們在終端中中直接查看 struct 和 slice 這兩種資料結構。```~$ go get github.com/gorilla/mux```Gorilla 的 `mux` 包非常流行, 我們用它來寫 web handler。```~$ go get github.com/joho/godotenv````godotenv `可以協助我們讀取項目根目錄中的`.env` 設定檔,這樣就不用將 http連接埠之類的配置寫入程式碼進代碼中了。比如像這樣:```ADDR=8080```接下來,我們建立一個 `main.go` 檔案。之後的大部分工作都圍繞這個檔案,開始寫代碼吧!## 匯入依賴包我們將所有的依賴包以聲明的方式匯入進去:```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")```## 資料模型接著我們來定義一個結構體,它代表組成區塊鏈的每一個塊的資料模型:```type Block struct { Index int Timestamp string BPM int Hash string PrevHash string}```- Index 是這個塊在整個鏈中的位置- Timestamp 顯而易見就是塊產生時的時間戳記- Hash 是這個塊通過 SHA256 演算法產生的散列值- PrevHash 代表前一個塊的 SHA256 散列值- BPM 每分鐘心跳數,也就是心率接著,我們再定義一個結構表示整個鏈,最簡單的表示形式就是一個 Block 的 slice:```var Blockchain []Block```我們使用散列演算法(SHA256)來確定和維護鏈中塊和塊正確的順序,確保每一個塊的 PrevHash 值等於前一個塊中的 Hash 值,這樣就以正確的塊順序構建出鏈:![Go語言開發區塊鏈](http://blog.hubwiz.com/2018/02/04/blockchain-diy-go/chain-model.png)## 散列和產生新塊我們為什麼需要散列?主要是兩個原因:- 在節省空間的前提下去唯一標識資料。散列是用整個塊的資料計算得出,在我們的例子中,將整個塊的資料通過 SHA256 計算成一個定長不可偽造的字串。- 維持鏈的完整性。通過儲存前一個塊的散列值,我們就能夠確保每個塊在鏈中的正確順序。任何對資料的篡改都將改變散列值,同時也就破壞了鏈。以我們從事的醫學健康領域為例,比如有一個惡意的第三方為了調整“人壽險”的價格,而修改了一個或若干個塊中的代表不健康的 BPM 值,那麼整個鏈都變得不可信了。我們接著寫一個函數,用來計算給定的資料的 SHA256 散列值:```func calculateHash(block Block) string { record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash h := sha256.New() h.Write([]byte(record)) hashed := h.Sum(nil) return hex.EncodeToString(hashed)}```這個 calculateHash 函數接受一個塊,通過塊中的 Index,Timestamp,BPM,以及 PrevHash 值來計算出 SHA256 散列值。接下來我們就能編寫一個產生塊的函數:```func generateBlock(oldBlock Block, BPM int) (Block, error) { 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, nil}```其中,Index 是從給定的前一塊的 Index 遞增得出,時間戳記是直接通過 time.Now() 函數來獲得的,Hash 值通過前面的 calculateHash Function Compute得出,PrevHash 則是給定的前一個塊的 Hash 值。## 校正塊搞定了塊的產生,接下來我們需要有函數幫我們判斷一個塊是否有被篡改。檢查 Index 來看這個塊是否正確得遞增,檢查 PrevHash 與前一個塊的 Hash 是否一致,再來通過 calculateHash 檢查當前塊的 Hash 值是否正確。通過這幾步我們就能寫出一個校正函數:```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}```除了校正塊以外,我們還會遇到一個問題:兩個節點都產生塊並添加到各自的鏈上,那我們應該以誰為準?這裡的細節我們留到下一篇文章,這裡先讓我們記住一個原則:始終選擇最長的鏈:![go語言開發區塊鏈](http://blog.hubwiz.com/2018/02/04/blockchain-diy-go/longer.png)通常來說,更長的鏈表示它的資料(狀態)是更新的,所以我們需要一個函數能幫我們將本地的到期的鏈切換成最新的鏈:```func replaceChain(newBlocks []Block) { if len(newBlocks) > len(Blockchain) { Blockchain = newBlocks }}```到這一步,我們基本就把所有重要的函數完成了。接下來,我們需要一個方便直觀的方式來查看我們的鏈,包括資料及狀態。通過瀏覽器查看 web 頁面可能是最合適的方式!## Web 服務我猜你一定對傳統的 web 服務及開發非常熟悉,所以這部分你肯定一看就會。藉助 Gorilla/mux 包,我們先寫一個函數來初始化我們的 web 服務:```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}```其中的連接埠號碼是通過前面提到的 .env 來獲得,再添加一些基本的配置參數,這個 web 服務就已經可以 listen and serve 了!接下來我們再來定義不同 endpoint 以及對應的 handler。例如,對“/”的 GET 請求我們可以查看整個鏈,“/”的 POST 請求可以建立塊。```func makeMuxRouter() http.Handler { muxRouter := mux.NewRouter() muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET") muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST") return muxRouter}```GET 請求的 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))}```為了簡化,我們直接以 JSON 格式返回整個鏈,你可以在瀏覽器中訪問 localhost:8080 或者 127.0.0.1:8080 來查看(這裡的8080就是你在 .env 中定義的連接埠號碼 ADDR)。POST 請求的 handler 稍微有些複雜,我們先來定義一下 POST 請求的 payload:```type Message struct { BPM int}```再看看 handler 的實現:```func handleWriteBlock(w http.ResponseWriter, r *http.Request) { var m Message decoder := 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)}```我們的 POST 請求體中可以使用上面定義的 payload,比如:```{"BPM":75}```還記得前面我們寫的 generateBlock 這個函數嗎?它接受一個“前一個塊”參數,和一個 BPM 值。POST handler 接受請求後就能獲得請求體中的 BPM 值,接著藉助產生塊的函數以及校正塊的函數就能產生一個新的塊了!除此之外,你也可以:- 使用spew.Dump 這個函數可以以非常美觀和方便閱讀的方式將 struct、slice 等資料列印在控制台裡,方便我們調試。- 測試 POST 請求時,可以使用 POSTMAN 這個 chrome 外掛程式,相比 curl它更直觀和方便。POST 請求處理完之後,無論建立塊成功與否,我們需要返回用戶端一個響應:```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 服務的函數“組裝”起來:```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())}```這裡的 genesisBlock (創世塊)是 main 函數中最重要的部分,通過它來初始化區塊鏈,畢竟第一個塊的 PrevHash 是空的。## 哦耶!完成了可以從這裡獲得完整的代碼:[Github repo](https://github.com/mycoralhealth/blockchain-tutorial/blob/master/main.go)讓我們來啟動它:```~$ go run main.go```在終端中,我們可以看到 網頁伺服器啟動的日誌資訊,並且列印出了創世塊的資訊:![go語言開發區塊鏈](http://blog.hubwiz.com/2018/02/04/blockchain-diy-go/run.png)接著我們開啟瀏覽器,訪問 localhost:8080 這個地址,我們可以看到頁面中展示了當前整個區塊鏈的資訊(當然,目前只有一個創世塊):![go語言開發區塊鏈](http://blog.hubwiz.com/2018/02/04/blockchain-diy-go/browser.png)接著,我們再通過 POSTMAN 來發送一些 POST 請求:![go語言開發區塊鏈](http://blog.hubwiz.com/2018/02/04/blockchain-diy-go/post.png)重新整理剛才的頁面,現在的鏈中多了一些塊,正是我們剛才產生的,同時你們可以看到,塊的順序和散列值都正確。![go語言開發區塊鏈](http://blog.hubwiz.com/2018/02/04/blockchain-diy-go/browser-2.png)## 總結剛剛我們完成了一個自己的區塊鏈,雖然很簡單(陋),但它具備塊產生、散列計算、塊校正等基本能力。接下來你就可以繼續深入的學習區塊鏈的其他重要知識,比如工作量證明、權益證明這樣的共識演算法,或者是智能合約、Dapp、側鏈等等。目前這個實現中不包括任何 P2P 網路的內容,我們會在下一篇文章中補充這部分內容,當然,我們鼓勵你在這個基礎上自己實踐一遍!> 如果你希望**高效的**學習以太坊DApp開發,可以訪問匯智網提供的**最熱門**線上互動教程:> >- 適合區塊鏈新手的以太坊DApp實戰入門教程 :http://xc.hubwiz.com/course/5a952991adb3847553d205d1?affid=0508studygolangw>- 區塊鏈+IPFS+Node.js+MongoDB+Express去中心化以太坊電商應用開發實戰:http://xc.hubwiz.com/course/5abbb7acc02e6b6a59171dd6?affid=20180508studygolangw原文也可以訪問[這個以太坊部落格](http://blog.hubwiz.com/2018/02/04/blockchain-diy-go/)。1217 次點擊  
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.