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

本文是為了后面講解 區(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拓瞪。

為什么會用到哈希?

  1. 節(jié)省空間。區(qū)塊數(shù)據(jù)的哈希值在區(qū)塊鏈里可以代表這個區(qū)塊的數(shù)據(jù)趾娃,當需要驗證區(qū)塊的完整以及真實性時候,儲存哈希值比起儲存所有數(shù)據(jù)方便很多培遵。
  2. 區(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ū)塊鏈分叉

到這一步,整個區(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 來跑一下試試看。


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

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

寫入新區(qū)塊

新區(qū)塊的PrevHash的哈希值與上一個區(qū)塊的Hash 哈希值是相同的熙宇。
Index 也對應的增加1鳖擒。

localhost:8080

訪問 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ū)塊。

生成新區(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é)點的連接:


不同節(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)了:

  1. defer conn.Close() 是當完成我們需要執(zhí)行的功能時艳吠,就把端口關(guān)閉麦备。
  2. 連接的端口可以傳一個Data數(shù)據(jù)過來,寫成新的區(qū)塊昭娩,包括驗證區(qū)塊等凛篙,最后把我們的整個區(qū)塊鏈發(fā)送給bcServer這個channel。
  3. 每隔30秒會同步第一個終端的最新的區(qū)塊鏈數(shù)據(jù)給所有連接的節(jié)點栏渺。

運行 go run main.go


在第一個終端運行

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

每隔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ū)塊鏈沛励。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末责语,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子目派,更是在濱河造成了極大的恐慌坤候,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件企蹭,死亡現(xiàn)場離奇詭異白筹,居然都是意外死亡,警方通過查閱死者的電腦和手機谅摄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門徒河,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人送漠,你說我怎么就攤上這事顽照。” “怎么了闽寡?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵代兵,是天一觀的道長。 經(jīng)常有香客問我下隧,道長奢人,這世上最難降的妖魔是什么谓媒? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任淆院,我火速辦了婚禮,結(jié)果婚禮上句惯,老公的妹妹穿的比我還像新娘土辩。我一直安慰自己,他們只是感情好抢野,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布拷淘。 她就那樣靜靜地躺著,像睡著了一般指孤。 火紅的嫁衣襯著肌膚如雪启涯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天恃轩,我揣著相機與錄音结洼,去河邊找鬼。 笑死叉跛,一個胖子當著我的面吹牛松忍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播筷厘,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鸣峭,長吁一口氣:“原來是場噩夢啊……” “哼宏所!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起摊溶,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤爬骤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后莫换,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盖腕,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年浓镜,在試婚紗的時候發(fā)現(xiàn)自己被綠了溃列。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡膛薛,死狀恐怖听隐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哄啄,我是刑警寧澤雅任,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站咨跌,受9級特大地震影響沪么,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜锌半,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一禽车、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刊殉,春花似錦殉摔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至遍膜,卻和暖如春碗硬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瓢颅。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工恩尾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惜索。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓特笋,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子猎物,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355