Go語(yǔ)言編寫一個(gè)小型區(qū)塊鏈

本文是為了后面講解 區(qū)塊鏈的分布式系統(tǒng)以及共識(shí)機(jī)制作為鋪墊的。有了實(shí)際的開(kāi)發(fā)基礎(chǔ),理解理論的知識(shí)就會(huì)容易很多。本文分為三個(gè)部分,第一部分用Go語(yǔ)言編寫一個(gè)小型的區(qū)塊鏈,第二部分在第一部分的基礎(chǔ)上加入Proof of Work(工作量共識(shí)機(jī)制),最后一部分在第一部分的基礎(chǔ)上添加可以加入節(jié)點(diǎn)的功能。

Part 1 Go語(yǔ)言編寫一個(gè)小型區(qū)塊鏈


1.1 開(kāi)發(fā)環(huán)境準(zhǔn)備

首先安裝Go語(yǔ)言
安裝完成后加入一下的packages:
go get github.com/davecgh/go-spew/spew
Spew 可以格式化 structs 和 slices,以便我們?cè)赾onsole能清晰明了的看這些數(shù)據(jù)。

go get github.com/gorilla/mux
Gorilla/mux 是一個(gè)很流行的處理http請(qǐng)求的包

go get github.com/joho/godotenv
Godotenv 可以讓我們讀取 .env 文件里面的環(huán)境變量。

在根目錄創(chuàng)建一個(gè) .env 文件,寫入下面內(nèi)容:
PORT=8080
創(chuàng)建一個(gè) main.go 文件,然后就可以開(kāi)始編寫我們的小型區(qū)塊鏈了。

1.2 小型區(qū)塊鏈

1. Imports(引用包)

首先,引用我們會(huì)用到的包以及前面安裝的包。

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 來(lái)定義一個(gè) block

type Block struct {
    Index           int
    Timestamp       string
    Data            int
    Hash            string
    PrevHash        string
}
  • Index 是標(biāo)識(shí)區(qū)塊在區(qū)塊鏈里的位置
  • Timestamp 是生成區(qū)塊的時(shí)間戳
  • Data 是要寫入?yún)^(qū)塊的數(shù)據(jù)
  • Hash 整個(gè)區(qū)塊數(shù)據(jù) SHA256 的哈希值
  • PrevHash 是一個(gè)區(qū)塊的哈希值

var Blockchain []Block
創(chuàng)建一個(gè) Block 的 slice。

為什么會(huì)用到哈希?

  1. 節(jié)省空間。區(qū)塊數(shù)據(jù)的哈希值在區(qū)塊鏈里可以代表這個(gè)區(qū)塊的數(shù)據(jù),當(dāng)需要驗(yàn)證區(qū)塊的完整以及真實(shí)性時(shí)候,儲(chǔ)存哈希值比起儲(chǔ)存所有數(shù)據(jù)方便很多。
  2. 區(qū)塊鏈的完整性。每一個(gè)區(qū)塊都保存上一個(gè)區(qū)塊的哈希值,確保區(qū)塊鏈的每個(gè)區(qū)塊的數(shù)據(jù)都是真實(shí)且完整的,也防止不誠(chéng)實(shí)節(jié)點(diǎn)篡改數(shù)據(jù)。

接下來(lái),寫一個(gè)計(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 這個(gè)方法把區(qū)塊里的必要數(shù)據(jù)以字符串拼接起來(lái),并計(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 哈希值要等于 上一個(gè)區(qū)塊的哈希值。

接下來(lái),驗(yàn)證新區(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
}

通過(guò)比對(duì)新舊區(qū)塊的 Index, 哈希值來(lái)決定新生成的區(qū)塊是否 Valid。

區(qū)塊鏈會(huì)發(fā)生分叉,我們?nèi)「L(zhǎng)的鏈作為正確的鏈:

func replaceChain(newBlocks []Block) {
    if len(newBlocks) > len(Blockchain) {
        Blockchain = newBlocks
    }
}
區(qū)塊鏈分叉

到這一步,整個(gè)區(qū)塊鏈的基本功能就編寫完成了。下一步,我們開(kāi)始web server的編寫。

首先,寫一個(gè) run 方法來(lái)跑我們的server, 后面會(huì)引用。

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 文件的端口號(hào)。

接著,寫一個(gè)處理http請(qǐng)求的方法:

func makeMuxRouter() http.Handler {
    muxRouter := mux.NewRouter()
    muxRouter.HandleFunc("/", handleGetBlockchain).Methods("GET")
    muxRouter.HandleFunc("/", handleWriteBlock).Methods("POST")
    return muxRouter
}

一個(gè)GET請(qǐng)求,一個(gè)POST請(qǐng)求,都是到"/"這個(gè)路由。

我們的GET請(qǐng)求方法:

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))
}

當(dāng)瀏覽 localhost:8080時(shí)候,我們把整個(gè)區(qū)塊鏈數(shù)據(jù)以JSON的格式返回并顯示在頁(yè)面上。

在寫 POST 請(qǐng)求之前,先加入一個(gè)新的 struct:

type Message struct {
    Data int
}

方面我們后面POST請(qǐng)求寫入Data數(shù)據(jù)。

我們的POST請(qǐng)求方法:

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} 這樣的方式通過(guò)POST請(qǐng)求寫入新的數(shù)據(jù)。

然后,寫一個(gè)統(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)
}

我們封裝了一個(gè) 返回 JSON數(shù)據(jù)的方法來(lái)更好的告知所有的請(qǐng)求的發(fā)生詳細(xì)過(guò)程。

最后,我們的 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ū)塊鏈的第一個(gè)區(qū)塊。

現(xiàn)在可以在命令行用 go run main.go 來(lái)跑一下試試看。


初始化,創(chuàng)建創(chuàng)世區(qū)塊

可以試著往 localhost:8080 傳入 {"Data": 55} 來(lái)寫入新的區(qū)塊:

寫入新區(qū)塊

新區(qū)塊的PrevHash的哈希值與上一個(gè)區(qū)塊的Hash 哈希值是相同的。
Index 也對(duì)應(yīng)的增加1。

localhost:8080

訪問(wèn) localhost:8080 顯示了當(dāng)前區(qū)塊鏈的所有區(qū)塊數(shù)據(jù)。

這樣我們就完成了一個(gè)小型的區(qū)塊鏈,下一部分我們會(huì)加入Proof of Work(區(qū)塊鏈的工作量證明機(jī)制)。

Part2 工作量共識(shí)機(jī)制(Proof of Work)


1.1 基礎(chǔ)設(shè)置以及基礎(chǔ)概念

首先,先復(fù)習(xí)一下我們?cè)诘谝徊糠滞瓿傻拇a,第二部分是基于第一部分的基礎(chǔ)上再繼續(xù)開(kāi)發(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())
}

熟悉完第一部分的代碼之后,我們來(lái)慢慢加入一些功能來(lái)實(shí)現(xiàn)Proof of Work。

在進(jìn)行開(kāi)發(fā)之前,先講解一下什么是挖礦,Proof of Work。

1.2 加密貨幣的挖礦

挖擴(kuò)其實(shí)是通過(guò)解答數(shù)學(xué)難題,讓礦工在區(qū)塊鏈中獲得生成一個(gè)新區(qū)塊的權(quán)利,并且因此而得到對(duì)應(yīng)的獎(jiǎng)勵(lì)(一般都是對(duì)應(yīng)的貨幣獎(jiǎng)勵(lì),比如比特幣,以太幣)。
那上面所說(shuō)的解答數(shù)學(xué)難題的過(guò)程,其實(shí)就是PoW。要理解PoW前,先了解什么是哈希函數(shù) 請(qǐng)翻看我之前寫的密碼學(xué)系統(tǒng)介紹的第5部分,哈希函數(shù)加密
PoW原理上就是要找出一個(gè) Nonce 的值,當(dāng)這個(gè) Nonce 值加上區(qū)塊的其他數(shù)據(jù)的SHA256的哈希值擁有我們所指定的難度的帶有多少個(gè)以0為開(kāi)頭的哈希值,那么這個(gè)數(shù)學(xué)難題就算是成功解答了。

1.3 PoW開(kāi)發(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"
)

增加一個(gè)difficulty 常量,定義PoW的解答難度,即哈希值要有多少個(gè)0開(kāi)頭才能算是解答成功。

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, 用來(lái)計(jì)算哈希值。
mutex 在后面用來(lái),防止有同時(shí)的寫入請(qǐng)求造成的錯(cuò)誤。

新的 calculateHash 用 strconv.Itoa方法來(lái)轉(zhuǎn)換成字符串,以及加入了Nonce值來(lái)計(jì)算哈希值

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方法用來(lái)驗(yàn)證哈希值是否以difficulty定義的個(gè)數(shù)的0開(kāi)頭。

func isHashValid(hash string, difficulty int) bool {
    prefix := strings.Repeat("0", difficulty)
    return strings.HasPrefix(hash, prefix)
}

generateBlock方法我們加入for loop 來(lái)處理如何生成一個(gè)新的block。以nonce轉(zhuǎn)換成hex值來(lái)加入哈希值的計(jì)算,從0開(kāi)始,一旦滿足了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)建也加入了對(duì)應(yī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ū)塊鏈上面實(shí)現(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 來(lái)試試看。
我們?cè)囋囃?localhost:8080 寫入新的數(shù)據(jù) {"Data": 99}, 會(huì)發(fā)現(xiàn)不像第一部分那樣可以馬上生成新的區(qū)塊,而是要先進(jìn)行挖擴(kuò),當(dāng)找到了我們?cè)?Difficulty 常量所定義的數(shù)字開(kāi)頭的多少個(gè)0開(kāi)頭的哈希值后,然后才允許往區(qū)塊鏈中寫入新的區(qū)塊。

生成新區(qū)塊前的哈希計(jì)算

這就是帶有PoW機(jī)制的區(qū)塊鏈?,F(xiàn)實(shí)中,比如比特幣或者以太坊區(qū)塊鏈,所要求的難度會(huì)高很多,比如需要找出幾十個(gè)0開(kāi)頭的哈希值,這需要非常大的計(jì)算量。而且,他們還會(huì)根據(jù)生成新區(qū)塊的速度(間隔時(shí)間)來(lái)相應(yīng)的調(diào)整難度。

Part 3 為區(qū)塊鏈添加節(jié)點(diǎn)


1.1 前期準(zhǔn)備

這是目前為part3準(zhǔn)備的基本代碼:

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 來(lái)實(shí)現(xiàn)添加新節(jié)點(diǎn)的連接:


不同節(jié)點(diǎn)的連接以及同步過(guò)程

首先,定義一個(gè)channel來(lái)處理連接過(guò)來(lái)的區(qū)塊:
var bcServer chan []Block
GoRoutine 和 channel 是Go語(yǔ)言很重要的兩大功能 詳細(xì)可以看這里學(xué)習(xí)

接下來(lái),開(kāi)始寫我們的 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 來(lái)監(jiān)聽(tīng)端口, 最后用一個(gè) for loop 以及 handleConn的方法來(lái)處理想要進(jìn)行連接的節(jié)點(diǎn)。

然后,我們來(lái)實(shí)現(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)
    }
}

這個(gè)方法里我們實(shí)現(xiàn)了:

  1. defer conn.Close() 是當(dāng)完成我們需要執(zhí)行的功能時(shí),就把端口關(guān)閉。
  2. 連接的端口可以傳一個(gè)Data數(shù)據(jù)過(guò)來(lái),寫成新的區(qū)塊,包括驗(yàn)證區(qū)塊等,最后把我們的整個(gè)區(qū)塊鏈發(fā)送給bcServer這個(gè)channel。
  3. 每隔30秒會(huì)同步第一個(gè)終端的最新的區(qū)塊鏈數(shù)據(jù)給所有連接的節(jié)點(diǎn)。

運(yùn)行 go run main.go


在第一個(gè)終端運(yùn)行

開(kāi)啟任何新的終端 輸入 nc localhost 8080 會(huì)連接到第一個(gè)終端,輸入Data值后會(huì)生成新的區(qū)塊,并且每隔30秒會(huì)給新的所有連接的節(jié)點(diǎn)同步最新的區(qū)塊鏈數(shù)據(jù)信息。

每隔30秒給所有連接的節(jié)點(diǎn)同步最新的區(qū)塊鏈數(shù)據(jù)

至此,我們就完成了加入一個(gè)模擬p2p網(wǎng)絡(luò)功能的區(qū)塊鏈。當(dāng)然,這個(gè)不是真的實(shí)現(xiàn)了p2p網(wǎng)絡(luò),這是在本地機(jī)器上的一個(gè)不同終端之間的模擬,真實(shí)的p2p網(wǎng)絡(luò)是復(fù)雜很多的。Go語(yǔ)言有一個(gè) libp2p 的庫(kù),大家如果有興趣可以去看看學(xué)習(xí),這個(gè)庫(kù)是真正的實(shí)現(xiàn)了p2p網(wǎng)絡(luò)的功能,可以嘗試在這個(gè)庫(kù)的基礎(chǔ)上開(kāi)發(fā)一個(gè)真正的p2p區(qū)塊鏈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容