[譯] 用不到 200 行的 GO 語言編寫您本身的區塊鏈

若是這不是您第一次讀本文,請閱讀第 2 部分 —— 這裏前端

本教程改編自這篇關於使用 JavaScript 編寫基礎區塊鏈的優秀文章。咱們已經將其移植到 Go 並添加了一些額外的好處 -- 好比在 Web 瀏覽器上查看您的區塊鏈。若是您對下面的教程有任何疑問,請務必加入咱們的  Telegram。可向咱們諮詢任何問題!android

本教程中的數據示例將基於您的休息心跳。畢竟咱們是一家醫療保健公司 :-) 爲了有趣,記錄您一分鐘的脈搏數(每分鐘的節拍)並記住這個數值。ios

世界上幾乎每一個開發者都據說過區塊鏈,但大多數仍然不知道它的工做原理。他們可能僅僅是由於比特幣才知道它,又或者是由於他們據說過智能合約之類的東西。這篇文章試圖經過幫助您用 Go 編寫本身的簡單區塊鏈,使用少於 200 行代碼來揭開區塊鏈的神祕面紗!到本教程結束時,您將可以編寫並在本地運行您本身的區塊鏈,以及在 Web 瀏覽器中查看它。git

還有什麼比經過建立本身的區塊鏈來了解區塊鏈更好的方法呢?github

您將可以作什麼golang

  • 建立您本身的區塊鏈
  • 瞭解 hash 如何維護區塊鏈的完整性
  • 瞭解如何添加新塊
  • 瞭解當多個節點生成塊時,tiebreakers 如何解決
  • 在 web 瀏覽器中查看區塊鏈
  • 寫新的塊
  • 獲取區塊鏈的基礎知識,以便您能夠決定您的旅程將從這裏走向何處!

您不能作的事web

爲了保持本文的簡單性,咱們不會處理更高級的共識概念,好比工做證實和利害關係證實。爲了讓您查看您的區塊鏈和區塊的添加,咱們將模擬網絡交互,但網絡廣播做爲文章的深度將被保留。算法

讓咱們開始吧!

準備工做json

既然咱們決定用 Go 編寫代碼,咱們假設您已經有了一些 Go 方面的經驗,在安裝並配置 Go 以後,咱們還須要獲取如下軟件包:後端

go get github.com/davecgh/go-spew/spew

Spew 容許咱們在控制檯中查看格式清晰的 structsslices,您值得擁有。

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 相同,咱們知道組成鏈的塊的正確順序。

散列和生成新塊

那咱們爲何須要散列呢?咱們散列數據的兩個主要緣由:

  • 爲了節省空間。散列從塊上的全部數據派生。在咱們的示例中,咱們只有幾個數據點,可是假設咱們有來自數百、數千或者數百萬之前的數據塊的數據。將數據散列到單個 SHA256 字符串或散列這些散列表中要比一遍又一遍地複製前面塊中的全部數據高效得多。
  • 保護區塊鏈的完整性。經過存儲前面的散列,就像咱們在上面的圖中所作的那樣,咱們可以確保區塊鏈中的塊是按正確的順序排列的。若是惡意的一方試圖操縱數據(例如,改變咱們的心率來肯定人壽保險的價格),散列將迅速改變,鏈將「斷裂」,每一個人都會知道也再也不信任這個惡意鏈。

讓咱們編寫一個函數,該函數接受 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 函數將 BlockIndexTimestampBPM,咱們提供塊的 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 函數是被調用的。從上一個塊的散列複製到 PrevHashIndex 從上一個塊的索引中遞增。

塊校驗

咱們須要編寫一些函數來確保這些塊沒有被篡改。咱們還經過檢查 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_ 是一個方便的函數,它能夠將咱們的結構打印到控制檯上。這對調試頗有用。
  • 對於測試 POST 請求,咱們喜歡使用 Postmancurl 效果也很好,若是您不能離開終端的話。

當咱們的 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 文件中讀取像端口號這樣的變量,這樣咱們就沒必要在整個應用程序中對它們進行硬編碼(噁心!)。
  • genesisBlockmain 函數中最重要的部分。咱們須要爲咱們的區塊鏈提供一個初始區塊,不然新區塊將沒法將其先前的散列與任何東西比較,由於先前的散列並不存在。
  • 咱們將初始塊隔離到它本身的 Go 例程中,這樣咱們就能夠將關注點從咱們的區塊鏈邏輯和 Web 服務器邏輯中分離出來。但它只是以這種沒有 Go 例程狀況下做更優雅的方式工做。

太好了!咱們完成了!

如下是所有代碼:

如今來討論下有趣的事情。讓咱們試一下 :-)

使用 go run main.go 從終端啓動應用程序

在終端中,咱們看到 Web 服務器已經啓動並運行,咱們獲得了咱們的初始塊的打印輸出。

如今使用您的端口號來訪問 localhost,對咱們來講是 8080。不出所料,咱們看到了相同的初始塊。

如今,讓咱們發送一些 POST 請求來添加塊。使用 Postman,咱們將添加一些具備不一樣 BPM 的新塊。

讓咱們刷新一下瀏覽器。瞧,咱們如今看到鏈中的全部新塊都帶有新塊的 PrevHash與舊塊的 Hash 相匹配,正如咱們預期的那樣!

下一步

恭喜!您只是用適當的散列和塊驗證來編寫本身的塊鏈。如今您應該可以控制本身的區塊鏈之旅,並探索更復雜的主題,如工做證實、利益證實、智能合約、Dapp、側鏈等等。

本教程沒有討論的是如何使用工做證實來挖掘新的塊。這將是一個單純的教程,但大量的區塊鏈存在,沒有證實工做機制。此外,目前經過在 Web 服務器中寫入和查看區塊鏈來模擬廣播。本教程中沒有 P2P 組件。

若是您想咱們添加諸如工做證實和人際關係之類的內容,請務必在咱們的 Telegram 中告訴咱們,並關注咱們的 Twitter!這是和咱們溝通的最好的方式。問咱們問題,給出反饋,並建議新教程。咱們很想聽聽您的意見。

經過大衆需求,咱們增長了本教程的後續內容!看看它們!

想了解更多關於珊瑚健康的信息,以及咱們如何使用區塊鏈來推動個性化用藥/治療研究,請訪問咱們的網站


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索