go開發一個自己的區塊鏈只需180行程式碼
區塊鏈開發用什麼語言?通過本文你將使用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 main
import (
"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 值,這樣就以正確的塊順序構建出鏈:
雜湊和生成新塊
我們為什麼需要雜湊?主要是兩個原因:
- 在節省空間的前提下去唯一標識資料。雜湊是用整個塊的資料計算得出,在我們的例子中,將整個塊的資料通過 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 函式計算得出,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
}
除了校驗塊以外,我們還會遇到一個問題:兩個節點都生成塊並新增到各自的鏈上,那我們應該以誰為準?這裡的細節我們留到下一篇文章, 這裡先讓我們記住一個原則:始終選擇最長的鏈:
通常來說,更長的連結串列示它的資料(狀態)是更新的,所以我們需要一個函式能幫我們將本地的過期的鏈切換成最新的鏈:
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
讓我們來啟動它:
~$ go run main.go
在終端中,我們可以看到 web 伺服器啟動的日誌資訊,並且列印出了創世塊的資訊:
接著我們開啟瀏覽器,訪問 localhost:8080 這個地址,我們可以看到頁面中展示了當前整個區塊鏈的資訊(當然,目前只有一個創世塊):
接著,我們再通過 POSTMAN 來傳送一些 POST 請求:
重新整理剛才的頁面,現在的鏈中多了一些塊,正是我們剛才生成的,同時你們可以看到,塊的順序和雜湊值都正確。
總結
剛剛我們完成了一個自己的區塊鏈,雖然很簡單(陋),但它具備塊生成、雜湊計算、塊校驗等基本能力。接下來你就可以繼續深入的學習 區塊鏈的其他重要知識,比如工作量證明、權益證明這樣的共識演算法,或者是智慧合約、Dapp、側鏈等等。
目前這個實現中不包括任何 P2P 網路的內容,我們會在下一篇文章中補充這部分內容,當然,我們鼓勵你在這個基礎上自己實踐一遍!
如果你希望馬上開始學習以太坊DApp開發,可以訪問匯智網提供的出色的線上互動教程:
相關文章
- Go(golang)開發區塊鏈只需180行程式碼Golang區塊鏈行程
- JavaScript開發區塊鏈只需200行程式碼JavaScript區塊鏈行程
- 50行ruby程式碼開發一個區塊鏈區塊鏈
- 一個簡單的區塊鏈程式碼實現區塊鏈
- 區塊鏈投票系統開發方案,區塊鏈投票系統開發原始碼區塊鏈原始碼
- 區塊鏈開發之Go語言—IO操作區塊鏈Go
- FISCO BCOS | 開發第一個區塊鏈應用區塊鏈
- 區塊鏈程式設計go(四)-交易區塊鏈程式設計Go
- 區塊鏈技術開發主鏈區塊鏈的應用分析區塊鏈
- 初識區塊鏈 - 用JS構建你自己的區塊鏈區塊鏈JS
- 區塊鏈開發_建立區塊鏈公鏈,聯盟鏈,私有鏈區塊鏈
- 世鏈財經|區塊鏈專案開發指南,如何開發一款區塊鏈專案區塊鏈
- 利用Hyperledger Fabric開發你的第一個區塊鏈應用區塊鏈
- 一個簡單的區塊鏈區塊鏈
- 區塊鏈開發公司談區塊鏈的應用場景區塊鏈
- 區塊鏈商城開發正式版丨區塊鏈商城系統開發技術原理丨區塊鏈商城原始碼平臺區塊鏈原始碼
- 區塊鏈技術開發 區塊鏈錢包交易所開發區塊鏈
- 區塊鏈主鏈開發規則及原始碼示例區塊鏈原始碼
- 300行ABAP程式碼實現一個最簡單的區塊鏈原型區塊鏈原型
- 區塊鏈DAPP去中心繫統開發技術程式碼流程區塊鏈APP
- 上海區塊鏈系統開發/區塊鏈交易所繫統開發區塊鏈
- 打造一個最小區塊鏈區塊鏈
- 區塊鏈開發平臺_區塊鏈技術服務區塊鏈
- 區塊鏈開發公司區塊鏈與產業變革區塊鏈產業
- 區塊鏈技術開發公司談區塊鏈如何良性發展區塊鏈
- 區塊鏈技術開發公司 聊區塊鏈“主鏈”價值區塊鏈
- [譯] 用 Go 編寫你自己的區塊鏈挖礦演算法!Go區塊鏈演算法
- 區塊鏈技術開發區塊鏈
- 區塊鏈錢包開發區塊鏈
- 區塊鏈開發:公鏈開發那些事兒區塊鏈
- 26個區塊鏈行業常用名詞解釋-區塊鏈交易所開發區塊鏈行業
- 區塊鏈技術開發主鏈 區塊鏈的企業級應用剖析區塊鏈
- 區塊鏈搭建開發公司談銀行使用區塊鏈的好處區塊鏈
- 區塊鏈技術開發公司談區塊鏈保險的特點區塊鏈
- 區塊鏈落地應用開發,區塊鏈幣幣撮合交易系統開發區塊鏈
- 區塊鏈錢包系統開發:區塊鏈支付平臺系統開發區塊鏈
- 企業如何開發自己的聯盟鏈,區塊鏈實體運用落地服務區塊鏈
- 區塊鏈HASH爆點逃跑模式系統開發功能(程式碼方案)區塊鏈模式