Go语言编写一个小型区块链

本文是为了后面讲解 区块链的分布式系统以及共识机制作为铺垫的。有了实际的开发基础,理解理论的知识就会容易很多。本文分为三个部分,第一部分用Go语言编写一个小型的区块链,第二部分在第一部分的基础上加入Proof of Work(工作量共识机制),最后一部分在第一部分的基础上添加可以加入节点的功能。

Part 1 Go语言编写一个小型区块链


1.1 开发环境准备

首先安装Go语言
安装完成后加入一下的packages:
go get github.com/davecgh/go-spew/spew
Spew 可以格式化 structs 和 slices,以便我们在console能清晰明了的看这些数据。

go get github.com/gorilla/mux
Gorilla/mux 是一个很流行的处理http请求的包

go get github.com/joho/godotenv
Godotenv 可以让我们读取 .env 文件里面的环境变量。

在根目录创建一个 .env 文件,写入下面内容:
PORT=8080
创建一个 main.go 文件,然后就可以开始编写我们的小型区块链了。

1.2 小型区块链

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. 数据原型

用 struct 来定义一个 block

type Block struct {
    Index           int
    Timestamp       string
    Data            int
    Hash            string
    PrevHash        string
}
  • Index 是标识区块在区块链里的位置
  • Timestamp 是生成区块的时间戳
  • Data 是要写入区块的数据
  • Hash 整个区块数据 SHA256 的哈希值
  • PrevHash 是一个区块的哈希值

var Blockchain []Block
创建一个 Block 的 slice。

为什么会用到哈希?

  1. 节省空间。区块数据的哈希值在区块链里可以代表这个区块的数据,当需要验证区块的完整以及真实性时候,储存哈希值比起储存所有数据方便很多。
  2. 区块链的完整性。每一个区块都保存上一个区块的哈希值,确保区块链的每个区块的数据都是真实且完整的,也防止不诚实节点篡改数据。

接下来,写一个计算区块哈希值的函数:

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 这个方法把区块里的必要数据以字符串拼接起来,并计算他们的哈希值。

然后,生成新区块的方法:

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
}

每次生成新的区块 Index 增加1,新区块的 PrevHash 哈希值要等于 上一个区块的哈希值。

接下来,验证新区块是否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
}

通过比对新旧区块的 Index, 哈希值来决定新生成的区块是否 Valid。

区块链会发生分叉,我们取更长的链作为正确的链:

func replaceChain(newBlocks []Block) {
    if len(newBlocks) > len(Blockchain) {
        Blockchain = newBlocks
    }
}
区块链分叉

到这一步,整个区块链的基本功能就编写完成了。下一步,我们开始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时候,我们把整个区块链数据以JSON的格式返回并显示在页面上。

在写 POST 请求之前,先加入一个新的 struct:

type Message struct {
    Data int
}

方面我们后面POST请求写入Data数据。

我们的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请求写入新的数据。

然后,写一个统一的 JSON数据 返回方法:

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数据的方法来更好的告知所有的请求的发生详细过程。

最后,我们的 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 环境变量。
genesisBlock 是创世区块,也就是区块链的第一个区块。

现在可以在命令行用 go run main.go 来跑一下试试看。


初始化,创建创世区块

可以试着往 localhost:8080 传入 {"Data": 55} 来写入新的区块:

写入新区块

新区块的PrevHash的哈希值与上一个区块的Hash 哈希值是相同的。
Index 也对应的增加1。

localhost:8080

访问 localhost:8080 显示了当前区块链的所有区块数据。

这样我们就完成了一个小型的区块链,下一部分我们会加入Proof of Work(区块链的工作量证明机制)。

Part2 工作量共识机制(Proof of Work)


1.1 基础设置以及基础概念

首先,先复习一下我们在第一部分完成的代码,第二部分是基于第一部分的基础上再继续开发的。

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

熟悉完第一部分的代码之后,我们来慢慢加入一些功能来实现Proof of Work。

在进行开发之前,先讲解一下什么是挖矿,Proof of Work。

1.2 加密货币的挖矿

挖扩其实是通过解答数学难题,让矿工在区块链中获得生成一个新区块的权利,并且因此而得到对应的奖励(一般都是对应的货币奖励,比如比特币,以太币)。
那上面所说的解答数学难题的过程,其实就是PoW。要理解PoW前,先了解什么是哈希函数 请翻看我之前写的密码学系统介绍的第5部分,哈希函数加密
PoW原理上就是要找出一个 Nonce 的值,当这个 Nonce 值加上区块的其他数据的SHA256的哈希值拥有我们所指定的难度的带有多少个以0为开头的哈希值,那么这个数学难题就算是成功解答了。

1.3 PoW开发

这部分比第一部分新增加了一些包

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方法来转换成字符串,以及加入了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定义的个数的0开头。

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

generateBlock方法我们加入for loop 来处理如何生成一个新的block。以nonce转换成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的处理。创世区块的创建也加入了对应的新属性。

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

}

到这里,我们就在第一部分的区块链上面实现了PoW的功能了。
以防有某些地方没有看懂,下面是第二部分的完整代码。以供参考。

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

在命令行执行 go run main.go 来试试看。
我们试试往 localhost:8080 写入新的数据 {"Data": 99}, 会发现不像第一部分那样可以马上生成新的区块,而是要先进行挖扩,当找到了我们在 Difficulty 常量所定义的数字开头的多少个0开头的哈希值后,然后才允许往区块链中写入新的区块。

生成新区块前的哈希计算

这就是带有PoW机制的区块链。现实中,比如比特币或者以太坊区块链,所要求的难度会高很多,比如需要找出几十个0开头的哈希值,这需要非常大的计算量。而且,他们还会根据生成新区块的速度(间隔时间)来相应的调整难度。

Part 3 为区块链添加节点


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 来实现添加新节点的连接:


不同节点的连接以及同步过程

首先,定义一个channel来处理连接过来的区块:
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, 创造创世区块, 以及用 tcp server 来监听端口, 最后用一个 for loop 以及 handleConn的方法来处理想要进行连接的节点。

然后,我们来实现 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)
    }
}

这个方法里我们实现了:

  1. defer conn.Close() 是当完成我们需要执行的功能时,就把端口关闭。
  2. 连接的端口可以传一个Data数据过来,写成新的区块,包括验证区块等,最后把我们的整个区块链发送给bcServer这个channel。
  3. 每隔30秒会同步第一个终端的最新的区块链数据给所有连接的节点。

运行 go run main.go


在第一个终端运行

开启任何新的终端 输入 nc localhost 8080 会连接到第一个终端,输入Data值后会生成新的区块,并且每隔30秒会给新的所有连接的节点同步最新的区块链数据信息。

每隔30秒给所有连接的节点同步最新的区块链数据

至此,我们就完成了加入一个模拟p2p网络功能的区块链。当然,这个不是真的实现了p2p网络,这是在本地机器上的一个不同终端之间的模拟,真实的p2p网络是复杂很多的。Go语言有一个 libp2p 的库,大家如果有兴趣可以去看看学习,这个库是真正的实现了p2p网络的功能,可以尝试在这个库的基础上开发一个真正的p2p区块链。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341