- 原文地址:Code your own blockchain in less than 200 lines of Go!
- 原文做者:Coral Health
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:Starrier
- 校對者:ALVINYEH、github.com/SergeyChang
若是這不是您第一次讀本文,請閱讀第 2 部分 —— 這裏!前端
本教程改編自這篇關於使用 JavaScript 編寫基礎區塊鏈的優秀文章。咱們已經將其移植到 Go 並添加了一些額外的好處 -- 好比在 Web 瀏覽器上查看您的區塊鏈。若是您對下面的教程有任何疑問,請務必加入咱們的 Telegram。可向咱們諮詢任何問題!android
本教程中的數據示例將基於您的休息心跳。畢竟咱們是一家醫療保健公司 :-) 爲了有趣,記錄您一分鐘的脈搏數(每分鐘的節拍)並記住這個數值。ios
世界上幾乎每一個開發者都據說過區塊鏈,但大多數仍然不知道它的工做原理。他們可能僅僅是由於比特幣才知道它,又或者是由於他們據說過智能合約之類的東西。這篇文章試圖經過幫助您用 Go 編寫本身的簡單區塊鏈,使用少於 200 行代碼來揭開區塊鏈的神祕面紗!到本教程結束時,您將可以編寫並在本地運行您本身的區塊鏈,以及在 Web 瀏覽器中查看它。git
還有什麼比經過建立本身的區塊鏈來了解區塊鏈更好的方法呢?github
您將可以作什麼golang
您不能作的事web
爲了保持本文的簡單性,咱們不會處理更高級的共識概念,好比工做證實和利害關係證實。爲了讓您查看您的區塊鏈和區塊的添加,咱們將模擬網絡交互,但網絡廣播做爲文章的深度將被保留。算法
準備工做json
既然咱們決定用 Go 編寫代碼,咱們假設您已經有了一些 Go 方面的經驗,在安裝並配置 Go 以後,咱們還須要獲取如下軟件包:後端
go get github.com/davecgh/go-spew/spew
Spew 容許咱們在控制檯中查看格式清晰的 structs
和 slices
,您值得擁有。
go get github.com/gorilla/mux
Gorilla/mux 是編寫 Web 程序處理的經常使用包。咱們將會使用它。
go get github.com/joho/godotenv
Gotdotenv 容許咱們從根目錄中讀取 .env
文件,這樣就沒必要對 HTTP 端口之類的內容進行硬編碼。咱們也須要這個。
咱們在根目錄中建立 .env
文件,定義爲 http 請求提供服務的端口。只須要該文件添加一行:
ADDR=8080
建立 main.go
文件。從如今開始,全部的內容都會寫進這個文件中,而且將用少於 200 代碼進行編碼。
導入
這是咱們須要導入的以及包聲明,咱們把它們寫入 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"
)
複製代碼
數據模型
咱們將定義組成區塊鏈的每一個塊的 struct
。別擔憂,咱們會在一分鐘內結束全部這些字段的含義。
type Block struct {
Index int
Timestamp string
BPM int
Hash string
PrevHash string
}
複製代碼
每一個 Block
都包含將被寫入區塊鏈的數據,並表示當您獲取脈搏率時的每一種狀況(還記得您在文章的開頭就這樣作了麼?)。
Index
是數據記錄在區塊鏈中的位置Timestamp
是自動肯定寫入數據的時間BPM
每分鐘節拍數,是您的脈搏率Hash
是表示此數據記錄的 SHA256 標識符PrevHash
是鏈中上一條記錄的 SHA256 標識符咱們也對區塊鏈自己建模,它只是 Block
中的 slice
:
var Blockchain []Block
複製代碼
那麼散列如何適合於塊和區塊鏈呢?咱們使用散列表來識別和保持塊的正確順序。經過確保每一個 Block
中的 PrevHash
與前面 Block
塊中的 Hash
相同,咱們知道組成鏈的塊的正確順序。
散列和生成新塊
那咱們爲何須要散列呢?咱們散列數據的兩個主要緣由:
讓咱們編寫一個函數,該函數接受 Block
數據並建立 i 的 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
函數將 Block
的 Index
、Timestamp
、BPM
,咱們提供塊的 PrevHash
連接爲一個參數,並以字符串的形式返回 SHA256 散列。如今咱們能夠用一個新的 generateBlock
函數來生成一個包含咱們所需的全部元素的新塊。咱們須要提供它前面的塊,以便咱們能夠獲得它的散列以及在 BPM 中的脈搏率。不要擔憂傳入 BPM int
參數。咱們稍後再討論這個問題。
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
}
複製代碼
注意當前時間使用 time.Now()
自動寫入塊中的。還請注意,咱們以前的 calculateHash
函數是被調用的。從上一個塊的散列複製到 PrevHash
。Index
從上一個塊的索引中遞增。
塊校驗
咱們須要編寫一些函數來確保這些塊沒有被篡改。咱們還經過檢查 Index
來實現這一點,以確保它們按預期的速度遞增。咱們還將檢查以確保咱們的 PrevHash
與前一個塊的 Hash
相同。最後,咱們但願經過在當前塊上再次運行 calculateHash
函數來從新檢查當前塊的散列。讓咱們編寫一個 isBlockValid
函數,它執行全部這些操做並返回一個 bool
。若是它經過了咱們全部的檢查,它就會返回 true
:
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 服務器的工做方式,並有一些將它們鏈接到 Go 中的經驗。咱們如今就帶你走一遍這個流程。
咱們將使用您以前下載的 Gorilla/mux 包來爲咱們完成繁重的任務。
咱們在稍後調用的 run
函數中建立服務器。
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
文件。咱們使用 log.Println
爲本身提供一個實時的控制檯消息來讓咱們的服務器啓動並運行。咱們對武器進行了一些配置,而後對 ListenAndServe
進行配置。很標準的 Go。
如今咱們須要編寫 makeMuxRouter
函數,該函數將定義全部的處理程序。要在瀏覽器中查看並寫入咱們的區塊鏈,咱們只須要兩個路由,咱們將保持它們的簡單性。若是咱們發送一個 GET
請求到 localhost
,咱們將查看到區塊鏈。若是咱們發送一 POST
請求,咱們能夠進行寫入。
func makeMuxRouter() http.Handler {
muxRouter := mux.NewRouter()
muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
return muxRouter
}
複製代碼
這是咱們的 GET
處理器。
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
在任意瀏覽器中可以查看。咱們在 .env
文件中將 ADDR
變量設置爲 8080,若是您更改它,請確保訪問您的正確端口。
咱們的 POST
請求有些複雜(複雜狀況並很少)。首先,咱們須要一個新的 Message
struct
。稍後咱們會解釋爲何咱們須要它。
type Message struct {
BPM int
}
複製代碼
下面是編寫新塊的處理程序的代碼。您看完後咱們會帶您再看一遍。
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)
}
複製代碼
使用獨立 Message
結構的緣由是接收 JSON POST 請求的請求體,咱們將使用它來編寫新的塊。這容許咱們簡單地發送帶有如下主體的 POST 請求,咱們的處理程序將爲咱們填充該塊的其他部分:
{"BPM":50}
50
是一個以每分鐘爲單位的脈搏頻率的例子。用一個整數值來改變您的脈搏率。
在將請求體解碼成 var m Message
結構後,經過傳入前一個塊並將新的脈衝率傳遞到前面編寫的 generateBlock
函數中來建立一個新塊。這就是函數建立新塊所需的所有內容。咱們使用以前建立的 isBlockValid
函數,快速檢查以確保新塊是正常的。
一些筆記
_spew.Dump_
是一個方便的函數,它能夠將咱們的結構打印到控制檯上。這對調試頗有用。curl
效果也很好,若是您不能離開終端的話。當咱們的 POST 請求成功或者失敗時,咱們但願獲得相應的通知。咱們使用了一個小包裝器函數 respondWithJSON
來讓咱們知道發生了什麼。記住,在 Go 中,千萬不要忽略它們。要優雅地處理它們。
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 處理程序和 Web 服務器連接在一個簡短、乾淨的 main
函數中:
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())
}
複製代碼
這是怎麼回事?
godotenv.Load()
容許咱們從根目錄中的 .env
文件中讀取像端口號這樣的變量,這樣咱們就沒必要在整個應用程序中對它們進行硬編碼(噁心!)。genesisBlock
是 main
函數中最重要的部分。咱們須要爲咱們的區塊鏈提供一個初始區塊,不然新區塊將沒法將其先前的散列與任何東西比較,由於先前的散列並不存在。如下是所有代碼:
如今來討論下有趣的事情。讓咱們試一下 :-)
使用 go run main.go
從終端啓動應用程序
在終端中,咱們看到 Web 服務器已經啓動並運行,咱們獲得了咱們的初始塊的打印輸出。
如今使用您的端口號來訪問 localhost
,對咱們來講是 8080。不出所料,咱們看到了相同的初始塊。
如今,讓咱們發送一些 POST 請求來添加塊。使用 Postman,咱們將添加一些具備不一樣 BPM 的新塊。
讓咱們刷新一下瀏覽器。瞧,咱們如今看到鏈中的全部新塊都帶有新塊的 PrevHash
與舊塊的 Hash
相匹配,正如咱們預期的那樣!
下一步
恭喜!您只是用適當的散列和塊驗證來編寫本身的塊鏈。如今您應該可以控制本身的區塊鏈之旅,並探索更復雜的主題,如工做證實、利益證實、智能合約、Dapp、側鏈等等。
本教程沒有討論的是如何使用工做證實來挖掘新的塊。這將是一個單純的教程,但大量的區塊鏈存在,沒有證實工做機制。此外,目前經過在 Web 服務器中寫入和查看區塊鏈來模擬廣播。本教程中沒有 P2P 組件。
若是您想咱們添加諸如工做證實和人際關係之類的內容,請務必在咱們的 Telegram 中告訴咱們,並關注咱們的 Twitter!這是和咱們溝通的最好的方式。問咱們問題,給出反饋,並建議新教程。咱們很想聽聽您的意見。
想了解更多關於珊瑚健康的信息,以及咱們如何使用區塊鏈來推動個性化用藥/治療研究,請訪問咱們的網站。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。