200行Go代码实现自己的区块链——区块生成与网络通信

高可用架构 · · 374 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

200行Go代码实现自己的区块链——区块生成与网络通信

在第一篇文章[1]中,我们向大家展示了如何通过精炼的Go代码实现一个简单的区块链。如何计算每个块的 Hash 值,如何验证块数据,如何让块链接起来等等,但是所有这些都是跑在一个节点上的。文章发布后,读者反响热烈,纷纷留言让我快点填坑(网络部分),于是就诞生了这第二篇文章。

这篇文章在之前的基础上,解决多个节点网络内,如何生成块、如何通信、如何广播消息等。

流程

200行Go代码实现自己的区块链——区块生成与网络通信

  • 第一个节点创建“创始区块”,同时启动 TCP server并监听一个端口,等待其他节点连接。

Step 1

  • 启动其他节点,并与第一个节点建立TCP连接(这里我们通过不同的终端来模拟其他节点)
  • 创建新的块

* Step 2

  • 第一个节点验证新生成块
  • 验证之后广播(链的新状态)给其他节点

Step 3

  • 所有的节点都同步了最新的链的状态

之后你可以重复上面的步骤,使得每个节点都创建TCP server并监听(不同的)端口以便其他节点来连接。通过这样的流程你将建立一个简化的模拟的(本地的)P2P网络,当然你也可以将节点的代码编译后,将二进制程序部署到云端。

开始coding吧

设置与导入依赖

参考之前第一篇文章,我们使用相同的计算 hash 的函数、验证块数据的函数等。

设置

在工程的根目录创建一个 .env 文件,并添加配置:

ADDR=9000

通过 go-spew 包将链数据输出到控制台,方便我们阅读:

go get github.com/davecgh/go-spew/spew

通过 godotenv 包来加载配置文件:

go get github.com/joho/godotenv

之后创建 main.go 文件。

导入

接着我们导入所有的依赖:

package main

import (
"bufio"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"log"
"net"
"os"
"strconv"
"time"

"github.com/davecgh/go-spew/spew"
"github.com/joho/godotenv"

)

回顾

让我们再快速回顾下之前的重点,我们创建一个 Block 结构体,并声明一个Block 类型的 slice,Blockchain:

// Block represents each 'item' in the blockchain
type Block struct {
Index int
Timestamp string
BPM int
Hash string
PrevHash string
}

// Blockchain is a series of validated Blocks
var Blockchain []Block

创建块时计算hash值的函数:

// SHA256 hashing
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)
}

创建块的函数:

// create a new block using previous block's hash
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

}

验证块数据的函数:

// make sure block is valid by checking index,
// and comparing the hash of the previous block
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

}

确保各个节点都以最长的链为准:

// make sure the chain we're checking is longer than
// the current blockchain
func replaceChain(newBlocks []Block) {
if len(newBlocks) > len(Blockchain) {
Blockchain = newBlocks
}
}

网络通信

接着我们来建立各个节点间的网络,用来传递块、同步链状态等。

我们先来声明一个全局变量 bcServer ,以 channel(译者注:channel 类似其他语言中的 Queue,代码中声明的是一个 Block 数组的 channel)的形式来接受块。

// bcServer handles incoming concurrent Blocks
var bcServer chan []Block

注:Channel 是 Go 语言中很重要的特性之一,它使得我们以流的方式读写数据,特别是用于并发编程。通过这里[2]可以更深入地学习 Channel。

接下来我们声明 main 函数,从 .env 加载配置,也就是端口号,然后实例化 bcServer

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)

}

接着创建 TCP server 并监听端口:

// start TCP and serve TCP server
server, err := net.Listen("tcp", ":"+os.Getenv("ADDR"))
if err != nil {
log.Fatal(err)
}
defer server.Close()

需要注意这里的 defer server.Close(),它用来之后关闭链接,可以从这里[3]了解更多 defer 的用法。

for {
conn, err := server.Accept()
if err != nil {
log.Fatal(err)
}
go handleConn(conn)
}

通过这个无限循环,我们可以接受其他节点的 TCP 链接,同时通过 go handleConn(conn) 启动一个新的 go routine(译者注:Rob Pike 不认为go routine 是协程,因此没有译为协程)来处理请求。

接下来是“处理请求”这个重要函数,其他节点可以创建新的块并通过 TCP 连接发送出来。在这里我们依然像第一篇文章一样,以 BPM 来作为示例数据。

  • 客户端通过 stdin 输入 BPM
  • 以 BPM 的值来创建块,这里会用到前面的函数:generateBlock,isBlockValid,和 replaceChain
  • 将新的链放在 channel 中,并广播到整个网络

func handleConn(conn net.Conn) {
io.WriteString(conn, "Enter a new BPM:")

scanner := bufio.NewScanner(conn)

// take in BPM from stdin and add it to blockchain after 
// conducting necessary validation
go func() {
    for scanner.Scan() {
        bpm, 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], bpm)
        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, "\nEnter a new BPM:")
    }
}()

defer conn.Close()

}

我们创建一个 scanner,并通过 for scanner.Scan() 来持续接收连接中发来的数据。为了简化,我们把 BPM 数值转化成字符串。bcServer <- Blockchain 是表示我们将新的链写入 channel 中。

通过 TCP 链接将最新的链广播出去时,我们需要:

  • 将数据序列化成 JSON 格式
  • 通过 timer 来定时广播
  • 在控制台中打印出来,方便我们查看链的最新状态

// simulate receiving broadcast
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)
}

整个 handleConn 函数差不多就完成了,通过这里[4]可以获得完整的代码。

有意思的地方

现在让我们来启动整个程序,
go run main.go
200行Go代码实现自己的区块链——区块生成与网络通信

就像我们预期的,首先创建了“创世块”,接着启动了 TCP server 并监听9000端口。

接着我们打开一个新的终端,连接到那个端口。(我们用不同颜色来区分)
nc localhost 9000
200行Go代码实现自己的区块链——区块生成与网络通信
200行Go代码实现自己的区块链——区块生成与网络通信
200行Go代码实现自己的区块链——区块生成与网络通信
接下来我们输入一个BPM值:
200行Go代码实现自己的区块链——区块生成与网络通信
200行Go代码实现自己的区块链——区块生成与网络通信

接着我们从第一个终端(节点)中能看到(依据输入的BPM)创建了新的块。
200行Go代码实现自己的区块链——区块生成与网络通信

我们等待30秒后,可以从其他终端(节点)看到广播过来的最新的链。

下一步

到目前为止,我们为这个例子添加了简单的、本地模拟的网络能力。当然,肯定有读者觉得这不够有说服力。但本质上来说,这就是区块链的网络层。它能接受外部数据并改变内在数据的状态,又能将内在数据的最新状态广播出去。

接下来你需要学习的是一些主流的共识算法,比如 PoW (Proof-of-Work) 和 PoS (Proof-of-Stake) 等。当然,我们会继续在后续的文章中将共识算法添加到这个例子中。

下一篇文章再见!

参考链接

[1] https://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=2653549361&idx=1&sn=019f54713891cf33ef3bef3b24773a96&chksm=813a62a9b64debbfdd24a8507bb974048a4456e5b0a2d5f685fb3bdf40366a25764c5df8afec&scene=21
[2] https://golangbot.com/channels/
[3] https://blog.golang.org/defer-panic-and-recover
[4] https://github.com/mycoralhealth/blockchain-tutorial/blob/master/networking/main.go


相关阅读:

只用200行Go代码写一个自己的区块链!

特别推荐:

比特币、以太坊、ERC20、PoW、PoS、智能合约、闪电网络……
想深入了解及讨论这些话题?高可用架构在知识星球(小密圈)创建了区块链学习小组,共同学习区块链包括数字货币前沿技术,欢迎点击链接加入。

区块链学习小组

本文作者 Coral Health,由魏佳翻译。转载译文请注明出处,技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。

高可用架构

改变互联网的构建方式

200行Go代码实现自己的区块链——区块生成与网络通信


有疑问加站长微信联系(非本文作者)

本文来自:51CTO博客

感谢作者:高可用架构

查看原文:200行Go代码实现自己的区块链——区块生成与网络通信

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

374 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传