本文是為了后面講解 區(qū)塊鏈的分布式系統(tǒng)以及共識機制作為鋪墊的。有了實際的開發(fā)基礎(chǔ)恤溶,理解理論的知識就會容易很多。本文分為三個部分帐姻,第一部分用Go語言編寫一個小型的區(qū)塊鏈饥瓷,第二部分在第一部分的基礎(chǔ)上加入Proof of Work(工作量共識機制),最后一部分在第一部分的基礎(chǔ)上添加可以加入節(jié)點的功能痹籍。
Part 1 Go語言編寫一個小型區(qū)塊鏈
1.1 開發(fā)環(huán)境準備
首先安裝Go語言
安裝完成后加入一下的packages:
go get github.com/davecgh/go-spew/spew
Spew 可以格式化 structs 和 slices呢铆,以便我們在console能清晰明了的看這些數(shù)據(jù)。
go get github.com/gorilla/mux
Gorilla/mux 是一個很流行的處理http請求的包
go get github.com/joho/godotenv
Godotenv 可以讓我們讀取 .env 文件里面的環(huán)境變量蹲缠。
在根目錄創(chuàng)建一個 .env 文件棺克,寫入下面內(nèi)容:
PORT=8080
創(chuàng)建一個 main.go
文件,然后就可以開始編寫我們的小型區(qū)塊鏈了线定。
1.2 小型區(qū)塊鏈
1. Imports(引用包)
首先因俐,引用我們會用到的包以及前面安裝的包。
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"
)
2. 數(shù)據(jù)原型
用 struct 來定義一個 block
type Block struct {
Index int
Timestamp string
Data int
Hash string
PrevHash string
}
- Index 是標識區(qū)塊在區(qū)塊鏈里的位置
- Timestamp 是生成區(qū)塊的時間戳
- Data 是要寫入?yún)^(qū)塊的數(shù)據(jù)
- Hash 整個區(qū)塊數(shù)據(jù) SHA256 的哈希值
- PrevHash 是一個區(qū)塊的哈希值
var Blockchain []Block
創(chuàng)建一個 Block 的 slice拓瞪。
為什么會用到哈希?
- 節(jié)省空間。區(qū)塊數(shù)據(jù)的哈希值在區(qū)塊鏈里可以代表這個區(qū)塊的數(shù)據(jù)趾娃,當需要驗證區(qū)塊的完整以及真實性時候,儲存哈希值比起儲存所有數(shù)據(jù)方便很多培遵。
- 區(qū)塊鏈的完整性。每一個區(qū)塊都保存上一個區(qū)塊的哈希值呜袁,確保區(qū)塊鏈的每個區(qū)塊的數(shù)據(jù)都是真實且完整的荐操,也防止不誠實節(jié)點篡改數(shù)據(jù)疗绣。
接下來料仗,寫一個計算區(qū)塊哈希值的函數(shù):
func calculateHash(block Block) string {
record := string(block.Index) + block.Timestamp + string(block.Data) + block.PrevHash
h := sha256.New()
h.Write([]byte(record))
hashed := h.Sum(nil)
return hex.EncodeToString(hashed)
}
calculateHash 這個方法把區(qū)塊里的必要數(shù)據(jù)以字符串拼接起來吓肋,并計算他們的哈希值。
然后椎麦,生成新區(qū)塊的方法:
func generateBlock(oldBlock Block, Data int) (Block, error) {
var newBlock Block
t := time.Now()
newBlock.Index = oldBlock.Index + 1
newBlock.Timestamp = t.String()
newBlock.Data = Data
newBlock.PrevHash = oldBlock.Hash
newBlock.Hash = calculateHash(newBlock)
return newBlock, nil
}
每次生成新的區(qū)塊 Index 增加1,新區(qū)塊的 PrevHash 哈希值要等于 上一個區(qū)塊的哈希值坐桩。
接下來,驗證新區(qū)塊是否valid的方法:
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
}
通過比對新舊區(qū)塊的 Index, 哈希值來決定新生成的區(qū)塊是否 Valid僧诚。
區(qū)塊鏈會發(fā)生分叉脊串,我們?nèi)「L的鏈作為正確的鏈:
func replaceChain(newBlocks []Block) {
if len(newBlocks) > len(Blockchain) {
Blockchain = newBlocks
}
}
到這一步,整個區(qū)塊鏈的基本功能就編寫完成了。下一步,我們開始web server的編寫推汽。
首先咬清,寫一個 run 方法來跑我們的server, 后面會引用奈虾。
func run() error {
mux := makeMuxRouter()
httpAddr := os.Getenv("PORT")
log.Println("Listening on ", os.Getenv("PORT"))
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
}
os.Getenv("PORT") 就是我們前面用.env 文件的端口號夺谁。
接著,寫一個處理http請求的方法:
func makeMuxRouter() http.Handler {
muxRouter := mux.NewRouter()
muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
return muxRouter
}
一個GET請求肉微,一個POST請求匾鸥,都是到"/"這個路由。
我們的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))
}
當瀏覽 localhost:8080
時候碉纳,我們把整個區(qū)塊鏈數(shù)據(jù)以JSON的格式返回并顯示在頁面上勿负。
在寫 POST 請求之前,先加入一個新的 struct:
type Message struct {
Data int
}
方面我們后面POST請求寫入Data數(shù)據(jù)村象。
我們的POST請求方法:
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.Data)
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)
}
這樣我們就可以用 {"Data": 77}
這樣的方式通過POST請求寫入新的數(shù)據(jù)笆环。
然后,寫一個統(tǒng)一的 JSON數(shù)據(jù) 返回方法:
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)
}
我們封裝了一個 返回 JSON數(shù)據(jù)的方法來更好的告知所有的請求的發(fā)生詳細過程厚者。
最后躁劣,我們的 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 文件定義的port 環(huán)境變量。
genesisBlock 是創(chuàng)世區(qū)塊库菲,也就是區(qū)塊鏈的第一個區(qū)塊账忘。
現(xiàn)在可以在命令行用 go run main.go 來跑一下試試看。
可以試著往 localhost:8080
傳入 {"Data": 55}
來寫入新的區(qū)塊:
新區(qū)塊的PrevHash的哈希值與上一個區(qū)塊的Hash 哈希值是相同的熙宇。
Index 也對應的增加1鳖擒。
訪問
localhost:8080
顯示了當前區(qū)塊鏈的所有區(qū)塊數(shù)據(jù)。
這樣我們就完成了一個小型的區(qū)塊鏈烫止,下一部分我們會加入Proof of Work(區(qū)塊鏈的工作量證明機制)蒋荚。
Part2 工作量共識機制(Proof of Work)
1.1 基礎(chǔ)設(shè)置以及基礎(chǔ)概念
首先,先復習一下我們在第一部分完成的代碼馆蠕,第二部分是基于第一部分的基礎(chǔ)上再繼續(xù)開發(fā)的期升。
package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"log"
"net/http"
"os"
"sync"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/gorilla/mux"
"github.com/joho/godotenv"
)
type Block struct {
Index int
Timestamp string
Data int
Hash string
PrevHash string
}
type Message struct {
Data int
}
var mutex = &sync.Mutex{}
var Blockchain []Block
func calculateHash(block Block) string {
record := string(block.Index) + block.Timestamp + string(block.Data) + block.PrevHash
h := sha256.New()
h.Write([]byte(record))
hashed := h.Sum(nil)
return hex.EncodeToString(hashed)
}
func generateBlock(oldBlock Block, Data int) (Block, error) {
var newBlock Block
t := time.Now()
newBlock.Index = oldBlock.Index + 1
newBlock.Timestamp = t.String()
newBlock.Data = Data
newBlock.PrevHash = oldBlock.Hash
newBlock.Hash = calculateHash(newBlock)
return newBlock, nil
}
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
}
}
func run() error {
mux := makeMuxRouter()
httpAddr := os.Getenv("PORT")
log.Println("Listening on ", os.Getenv("PORT"))
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
}
func makeMuxRouter() http.Handler {
muxRouter := mux.NewRouter()
muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
return muxRouter
}
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))
}
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()
mutex.Lock()
newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], m.Data)
mutex.Unlock()
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)
}
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)
}
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
go func() {
t := time.Now()
genesisBlock := Block{}
genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), ""}
spew.Dump(generateBlock)
mutex.Lock()
Blockchain = append(Blockchain, genesisBlock)
mutex.Unlock()
}()
log.Fatal(run())
}
熟悉完第一部分的代碼之后惊奇,我們來慢慢加入一些功能來實現(xiàn)Proof of Work。
在進行開發(fā)之前播赁,先講解一下什么是挖礦颂郎,Proof of Work。
1.2 加密貨幣的挖礦
挖擴其實是通過解答數(shù)學難題容为,讓礦工在區(qū)塊鏈中獲得生成一個新區(qū)塊的權(quán)利乓序,并且因此而得到對應的獎勵(一般都是對應的貨幣獎勵,比如比特幣坎背,以太幣)替劈。
那上面所說的解答數(shù)學難題的過程,其實就是PoW沼瘫。要理解PoW前抬纸,先了解什么是哈希函數(shù) 請翻看我之前寫的密碼學系統(tǒng)介紹的第5部分咙俩,哈希函數(shù)加密
PoW原理上就是要找出一個 Nonce 的值耿戚,當這個 Nonce 值加上區(qū)塊的其他數(shù)據(jù)的SHA256的哈希值擁有我們所指定的難度的帶有多少個以0為開頭的哈希值,那么這個數(shù)學難題就算是成功解答了阿趁。
1.3 PoW開發(fā)
這部分比第一部分新增加了一些包
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
"strings"
"sync"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/gorilla/mux"
"github.com/joho/godotenv"
)
增加一個difficulty 常量膜蛔,定義PoW的解答難度,即哈希值要有多少個0開頭才能算是解答成功脖阵。
const difficulty = 1
type Block struct {
Index int
Timestamp string
Data int
Hash string
PrevHash string
Difficulty int
Nonce string
}
var mutex = &sync.Mutex{}
Block的struct 加入 Difficulty皂股,以及 Nonce, 用來計算哈希值。
mutex 在后面用來命黔,防止有同時的寫入請求造成的錯誤呜呐。
新的 calculateHash 用 strconv.Itoa方法來轉(zhuǎn)換成字符串,以及加入了Nonce值來計算哈希值
func calculateHash(block Block) string {
record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.Data) + block.PrevHash + block.Nonce
h := sha256.New()
h.Write([]byte(record))
hashed := h.Sum(nil)
return hex.EncodeToString(hashed)
}
isHashValid方法用來驗證哈希值是否以difficulty定義的個數(shù)的0開頭悍募。
func isHashValid(hash string, difficulty int) bool {
prefix := strings.Repeat("0", difficulty)
return strings.HasPrefix(hash, prefix)
}
generateBlock方法我們加入for loop 來處理如何生成一個新的block蘑辑。以nonce轉(zhuǎn)換成hex值來加入哈希值的計算,從0開始坠宴,一旦滿足了isHashValid方法的要求洋魂,就可以視為解題成功。
func generateBlock(oldBlock Block, Data int) (Block, error) {
var newBlock Block
t := time.Now()
newBlock.Index = oldBlock.Index + 1
newBlock.Timestamp = t.String()
newBlock.Data = Data
newBlock.PrevHash = oldBlock.Hash
newBlock.Difficulty = difficulty
for i := 0; ; i++ {
hex := fmt.Sprintf("%x", i)
newBlock.Nonce = hex
if !isHashValid(calculateHash(newBlock), newBlock.Difficulty) {
fmt.Println(calculateHash(newBlock), " do more work!!")
time.Sleep(time.Second)
continue
} else {
fmt.Println(calculateHash(newBlock), " work done!!")
newBlock.Hash = calculateHash(newBlock)
break
}
}
return newBlock, nil
}
最后喜鼓,我們 main 方法也加入了mutex的處理副砍。創(chuàng)世區(qū)塊的創(chuàng)建也加入了對應的新屬性。
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
go func() {
t := time.Now()
genesisBlock := Block{}
genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), "", difficulty, ""}
spew.Dump(genesisBlock)
mutex.Lock()
Blockchain = append(Blockchain, genesisBlock)
mutex.Unlock()
}()
log.Fatal(run())
}
到這里庄岖,我們就在第一部分的區(qū)塊鏈上面實現(xiàn)了PoW的功能了豁翎。
以防有某些地方?jīng)]有看懂,下面是第二部分的完整代碼隅忿。以供參考心剥。
package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
"strings"
"sync"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/gorilla/mux"
"github.com/joho/godotenv"
)
const difficulty = 1
type Block struct {
Index int
Timestamp string
Data int
Hash string
PrevHash string
Difficulty int
Nonce string
}
var mutex = &sync.Mutex{}
type Message struct {
Data int
}
var Blockchain []Block
func calculateHash(block Block) string {
record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.Data) + block.PrevHash + block.Nonce
h := sha256.New()
h.Write([]byte(record))
hashed := h.Sum(nil)
return hex.EncodeToString(hashed)
}
func generateBlock(oldBlock Block, Data int) (Block, error) {
var newBlock Block
t := time.Now()
newBlock.Index = oldBlock.Index + 1
newBlock.Timestamp = t.String()
newBlock.Data = Data
newBlock.PrevHash = oldBlock.Hash
newBlock.Difficulty = difficulty
for i := 0; ; i++ {
hex := fmt.Sprintf("%x", i)
newBlock.Nonce = hex
if !isHashValid(calculateHash(newBlock), newBlock.Difficulty) {
fmt.Println(calculateHash(newBlock), " do more work!!")
time.Sleep(time.Second)
continue
} else {
fmt.Println(calculateHash(newBlock), " work done!!")
newBlock.Hash = calculateHash(newBlock)
break
}
}
return newBlock, nil
}
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
}
}
func run() error {
mux := makeMuxRouter()
httpAddr := os.Getenv("PORT")
log.Println("Listening on ", os.Getenv("PORT"))
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
}
func makeMuxRouter() http.Handler {
muxRouter := mux.NewRouter()
muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
return muxRouter
}
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))
}
func handleWriteBlock(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
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()
mutex.Lock()
newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], m.Data)
mutex.Unlock()
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)
}
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)
}
func isHashValid(hash string, difficulty int) bool {
prefix := strings.Repeat("0", difficulty)
return strings.HasPrefix(hash, prefix)
}
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
go func() {
t := time.Now()
genesisBlock := Block{}
genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), "", difficulty, ""}
spew.Dump(generateBlock)
mutex.Lock()
Blockchain = append(Blockchain, genesisBlock)
mutex.Unlock()
}()
log.Fatal(run())
}
在命令行執(zhí)行 go run main.go 來試試看启搂。
我們試試往 localhost:8080
寫入新的數(shù)據(jù) {"Data": 99}
, 會發(fā)現(xiàn)不像第一部分那樣可以馬上生成新的區(qū)塊,而是要先進行挖擴刘陶,當找到了我們在 Difficulty 常量所定義的數(shù)字開頭的多少個0開頭的哈希值后胳赌,然后才允許往區(qū)塊鏈中寫入新的區(qū)塊。
這就是帶有PoW機制的區(qū)塊鏈〕赘簦現(xiàn)實中疑苫,比如比特幣或者以太坊區(qū)塊鏈,所要求的難度會高很多纷责,比如需要找出幾十個0開頭的哈希值捍掺,這需要非常大的計算量。而且再膳,他們還會根據(jù)生成新區(qū)塊的速度(間隔時間)來相應的調(diào)整難度挺勿。
Part 3 為區(qū)塊鏈添加節(jié)點
1.1 前期準備
這是目前為part3準備的基本代碼:
package main
import (
"bufio"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"log"
"net"
"os"
"strconv"
"sync"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/joho/godotenv"
)
type Block struct {
Index int
Timestamp string
Data int
Hash string
PrevHash string
}
type Message struct {
Data int
}
var Blockchain []Block
func calculateHash(block Block) string {
record := string(block.Index) + block.Timestamp + string(block.Data) + block.PrevHash
h := sha256.New()
h.Write([]byte(record))
hashed := h.Sum(nil)
return hex.EncodeToString(hashed)
}
func generateBlock(oldBlock Block, Data int) (Block, error) {
var newBlock Block
t := time.Now()
newBlock.Index = oldBlock.Index + 1
newBlock.Timestamp = t.String()
newBlock.Data = Data
newBlock.PrevHash = oldBlock.Hash
newBlock.Hash = calculateHash(newBlock)
return newBlock, nil
}
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
}
}
我們用 tcp 來實現(xiàn)添加新節(jié)點的連接:
首先,定義一個channel來處理連接過來的區(qū)塊:
var bcServer chan []Block
GoRoutine 和 channel 是Go語言很重要的兩大功能 詳細可以看這里學習
接下來喂柒,開始寫我們的 main() 方法:
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal(err)
}
bcServer = make(chan []Block)
// create genesis block
t := time.Now()
genesisBlock := Block{0, t.String(), 0, "", ""}
spew.Dump(genesisBlock)
Blockchain = append(Blockchain, genesisBlock)
// start TCP and serve TCP server
server, err := net.Listen("tcp", ":"+os.Getenv("ADDR"))
if err != nil {
log.Fatal(err)
}
defer server.Close()
for {
conn, err := server.Accept()
if err != nil {
log.Fatal(err)
}
go handleConn(conn)
}
}
在這里不瓶,我們初始化了 bcServer, 創(chuàng)造創(chuàng)世區(qū)塊, 以及用 tcp server 來監(jiān)聽端口灾杰, 最后用一個 for loop 以及 handleConn的方法來處理想要進行連接的節(jié)點蚊丐。
然后,我們來實現(xiàn) handleConn 方法:
func handleConn(conn net.Conn) {
defer conn.Close()
io.WriteString(conn, "Enter a new Data:")
scanner := bufio.NewScanner(conn)
go func() {
for scanner.Scan() {
data, err := strconv.Atoi(scanner.Text())
if err != nil {
log.Printf("%v not a number: %v", scanner.Text(), err)
continue
}
newBlock, err := generateBlock(Blockchain[len(Blockchain)-1], data)
if err != nil {
log.Println(err)
continue
}
if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) {
newBlockchain := append(Blockchain, newBlock)
replaceChain(newBlockchain)
}
bcServer <- Blockchain
io.WriteString(conn, "\n Enter a new Data:")
}
}()
go func() {
for {
time.Sleep(30 * time.Second)
output, err := json.Marshal(Blockchain)
if err != nil {
log.Fatal(err)
}
io.WriteString(conn, string(output))
}
}()
for _ = range bcServer {
spew.Dump(Blockchain)
}
}
這個方法里我們實現(xiàn)了:
- defer conn.Close() 是當完成我們需要執(zhí)行的功能時艳吠,就把端口關(guān)閉麦备。
- 連接的端口可以傳一個Data數(shù)據(jù)過來,寫成新的區(qū)塊昭娩,包括驗證區(qū)塊等凛篙,最后把我們的整個區(qū)塊鏈發(fā)送給bcServer這個channel。
- 每隔30秒會同步第一個終端的最新的區(qū)塊鏈數(shù)據(jù)給所有連接的節(jié)點栏渺。
運行 go run main.go
開啟任何新的終端 輸入 nc localhost 8080
會連接到第一個終端呛梆,輸入Data值后會生成新的區(qū)塊,并且每隔30秒會給新的所有連接的節(jié)點同步最新的區(qū)塊鏈數(shù)據(jù)信息迈嘹。
至此削彬,我們就完成了加入一個模擬p2p網(wǎng)絡(luò)功能的區(qū)塊鏈。當然秀仲,這個不是真的實現(xiàn)了p2p網(wǎng)絡(luò)融痛,這是在本地機器上的一個不同終端之間的模擬,真實的p2p網(wǎng)絡(luò)是復雜很多的神僵。Go語言有一個 libp2p 的庫雁刷,大家如果有興趣可以去看看學習,這個庫是真正的實現(xiàn)了p2p網(wǎng)絡(luò)的功能保礼,可以嘗試在這個庫的基礎(chǔ)上開發(fā)一個真正的p2p區(qū)塊鏈沛励。