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

Starrier發表於2018-04-20

如果這不是您第一次讀本文,請閱讀第 2 部分 —— 這裡

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

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

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

還有什麼比通過建立自己的區塊鏈來了解區塊鏈更好的方法呢?

您將能夠做什麼

  • 建立您自己的區塊鏈
  • 瞭解 hash 如何維護區塊鏈的完整性
  • 瞭解如何新增新塊
  • 瞭解當多個節點生成塊時,tiebreakers 如何解決
  • 在 web 瀏覽器中檢視區塊鏈
  • 寫新的塊
  • 獲取區塊鏈的基礎知識,以便您可以決定您的旅程將從這裡走向何處!

您不能做的事

為了保持本文的簡單性,我們不會處理更高階的共識概念,比如工作證明和利害關係證明。為了讓您檢視您的區塊鏈和區塊的新增,我們將模擬網路互動,但網路廣播作為文章的深度將被保留。

讓我們開始吧!

準備工作

既然我們決定用 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 相同,我們知道組成鏈的塊的正確順序。

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

雜湊和生成新塊

那我們為什麼需要雜湊呢?我們雜湊資料的兩個主要原因:

  • 為了節省空間。雜湊從塊上的所有資料派生。在我們的示例中,我們只有幾個資料點,但是假設我們有來自數百、數千或者數百萬以前的資料塊的資料。將資料雜湊到單個 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
}
複製程式碼

如果我們遇到這樣一個問題,即區塊鏈生態系統的兩個節點都向它們的鏈新增了區塊,並且我們都收到了它們。我們選擇哪一個作為真理的來源?我們選擇最長的鏈條。這是一個經典的區塊鏈問題,與邪惡的演員沒有任何關係。

兩個有意義的節點可能只是具有不同的鏈長,因此很自然地,較長的節點將是最新的,並且擁有最新的塊。因此,讓我們確保我們正在接受的新鏈要比我們現有的鏈長。如果是,我們可以用具有新塊的新鏈覆蓋我們的鏈。

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

為了實現這一點,我們簡單地比較了鏈片的長度:

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 伺服器已經啟動並執行,我們得到了我們的初始塊的列印輸出。

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

現在使用您的埠號來訪問 localhost,對我們來說是 8080。不出所料,我們看到了相同的初始塊。

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

現在,讓我們傳送一些 POST 請求來新增塊。使用 Postman,我們將新增一些具有不同 BPM 的新塊。

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

讓我們重新整理一下瀏覽器。瞧,我們現在看到鏈中的所有新塊都帶有新塊的 PrevHash與舊塊的 Hash 相匹配,正如我們預期的那樣!

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

下一步

恭喜!您只是用適當的雜湊和塊驗證來編寫自己的塊鏈。現在您應該能夠控制自己的區塊鏈之旅,並探索更復雜的主題,如工作證明、利益證明、智慧合約、Dapp、側鏈等等。

本教程沒有討論的是如何使用工作證明來挖掘新的塊。這將是一個單純的教程,但大量的區塊鏈存在,沒有證明工作機制。此外,目前通過在 Web 伺服器中寫入和檢視區塊鏈來模擬廣播。本教程中沒有 P2P 元件。

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

通過大眾需求,我們增加了本教程的後續內容!看看它們!

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


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

相關文章