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

Posted Bigben

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了200行Go代码实现自己的区块链——区块生成与网络通信相关的知识,希望对你有一定的参考价值。

英文原版:https://medium.com/@mycoralhealth/code-your-own-blockchain-in-less-than-200-lines-of-go-e296282bcffc

 

 

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

 

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

 

流程

 

技术分享图片

 

  • 第一个节点创建“创始区块”,同时启动 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 的值来创建块,这里会用到前面的函数:generateBlockisBlockValid,和 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

 

 

技术分享图片

 

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

 

接着我们打开一个新的终端,连接到那个端口。(我们用不同颜色来区分)
nc localhost 9000

技术分享图片

技术分享图片

技术分享图片

 

接下来我们输入一个BPM值:

 

技术分享图片

技术分享图片

 

接着我们从第一个终端(节点)中能看到(依据输入的BPM)创建了新的块。

技术分享图片

 

我们等待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,由魏佳翻译。转载译文请注明出处,技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。

 

 

高可用架构

改变互联网的构建方式

技术分享图片

长按二维码 关注「高可用架构」公众号

Code your own blockchain in less than 200 lines of Go!

 
技术分享图片

If this isn’t your first time reading this post, check out Part 2: Networkinghere!

This tutorial is adapted from this excellent post about writing a basic blockchain using javascript. We’ve ported it over to Go and added some extra goodies like viewing your blockchain in a web browser. If you have any questions about the following tutorial, make sure to join our Telegramchat. Ask us anything!

The data examples in this tutorial will be based on your resting heartbeat. We are a healthcare company after all :-) For fun, count your pulse for a minute (beats per minute) and keep that number in mind throughout the tutorial.

Almost every developer in the world has heard of the blockchain but most still don’t know how it works. They might only know about it because of Bitcoin and because they’ve heard of things like smart contracts. This post is an attempt to demystify the blockchain by helping you write your own simple blockchain in Go, with less than 200 lines of code! By the end of this tutorial, you’ll be able to run and write to a blockchain locally and view it in a web browser.

What better way to learn about the blockchain than to create your own?

What you will be able to do

  • Create your own blockchain
  • Understand how hashing works in maintaining integrity of the blockchain
  • See how new blocks get added
  • See how tiebreakers get resolved when multiple nodes generate blocks
  • View your blockchain in a web browser
  • Write new blocks
  • Get a foundational understanding of the blockchain so you can decide where your journey takes you from here!

What you won’t be able to do

To keep this post simple, we won’t be dealing with more advanced consensus concepts like proof of work vs. proof of stake. Network interactions will be simulated so you can view your blockchain and see blocks added, but network broadcasting will be reserved for a future post.

Let’s get started!

Setup

Since we’re going to write our code in Go, we assume you have had some experience with Go. After installing and configuring Go, we’ll also want to grab the following packages:

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

Spew allows us to view structs and slices cleanly formatted in our console. This is nice to have.

go get github.com/gorilla/mux

Gorilla/mux is a popular package for writing web handlers. We’ll need this.

go get github.com/joho/godotenv

Gotdotenv lets us read from a .env file that we keep in the root of our directory so we don’t have to hardcode things like our http ports. We’ll need this too.

Let’s also create a .env file in the root of our directory defining the port that will serve http requests. Just add one line to this file:

ADDR=8080

Create a main.go file. Everything from now on will be written to this file and will be less than 200 lines of code. Let’s get coding!

Imports

Here are the imports we’ll need, along with our package declaration. Let’s write these to main.go

 

Data model

Let’s define the struct of each of our blocks that will make up the blockchain. Don’t worry, we’ll explain what all of these fields mean in a minute.

 

Each Block contains data that will be written to the blockchain, and represents each case when you took your pulse rate (remember you did that at the beginning of the article?).

  • Index is the position of the data record in the blockchain
  • Timestamp is automatically determined and is the time the data is written
  • BPM or beats per minute, is your pulse rate
  • Hash is a SHA256 identifier representing this data record
  • PrevHash is the SHA256 identifier of the previous record in the chain

Let’s also model out the blockchain itself, which is simply a slice ofBlock:

 

So how does hashing fit into blocks and the blockchain? We use hashes to identify and keep the blocks in the right order. By ensuring the PrevHash in each Block is identical to Hash in the previous Block we know the proper order of the blocks that make up the chain.

 
技术分享图片

Hashing and Generating New Blocks

So why do we need hashing? We hash data for 2 main reasons:

  • To save space. Hashes are derived from all the data that is on the block. In our case, we only have a few data points but imagine we have data from hundreds, thousands or millions of previous blocks. It’s much more efficient to hash that data into a single SHA256 string or hash the hashes than to copy all the data in preceding blocks over and over again.
  • Preserve integrity of the blockchain. By storing previous hashes like we do in the diagram above, we’re able to ensure the blocks in the blockchain are in the right order. If a malicious party were to come in and try to manipulate the data (for example, to change our heart rate to fix life insurance prices), the hashes would change quickly and the chain would “break”, and everyone would know to not trust that malicious chain.

Let’s write a function that takes our Block data and creates a SHA256 hash of it.

 

This calculateHash function concatenates IndexTimestampBPM,PrevHash of the Block we provide as an argument and returns the SHA256 hash as a string. Now we can generate a new Block with all the elements we need with a new generateBlock function. We’ll need to supply it the previous block so we can get its hash and our pulse rate in BPM. Don’t worry about the BPM int argument that’s passed in. We’ll address that later.

 

Notice that the current time is automatically written in the block withtime.Now(). Also notice that our prior calculateHash function was called.PrevHash is copied over from the hash of the previous block. Index is incremented from the Index of the previous block.

Block Validation

Now we need to write some functions to make sure the blocks haven’t been tampered with. We do this by checking Index to make sure they’ve incremented as expected. We also check to make sure our PrevHash is indeed the same as the Hash of the previous block. Lastly, we want to double check the hash of the current block by running the calculateHashfunction again on the current block. Let’s write a isBlockValid function that does all these things and returns a bool. It’ll return true if it passes all our checks:

 

What if we run into an issue where two nodes of our blockchain ecosystem both added blocks to their chains and we received them both. Which one do we pick as the source of truth? We choose the longest chain. This is a classic blockchain issue and has nothing to do with nefarious actors.

Two well meaning nodes may simply have different chain lengths, so naturally the longer one will be the most up to date and have the latest blocks. So let’s make sure the new chain we’re taking in is longer than the current chain we have. If it is, we can overwrite our chain with the new one that has the new block(s).

 
技术分享图片

We simply compare the length of the slices of the chains to accomplish this:

 

If you’ve made it this far, pat yourself on the back! We’ve basically written up the guts of our blockchain with all the various functions we need.

Now we want a convenient way to view our blockchain and write to it, ideally in a web browser so we can show our friends!

Web Server

We assume you’re already familiar with how web servers work and have a bit of experience wiring them up in Go. We’ll walk you through the process now.

We’ll be using the Gorilla/mux package that you downloaded earlier to do the heavy lifting for us.

Let’s create our server in a run function that we’ll call later.

 

Note that port we choose comes from our .env file that we created earlier. We give ourselves a quick console message with log.Println to let us know the server is up and running. We configure the server a bit and thenListenAndServe. Pretty standard Go stuff.

Now we need to write the makeMuxRouter function that will define all our handlers. To view and write to our blockchain in a browser, we only need 2 routes and we’ll keep them simple. If we send a GET request to localhostwe’ll view our blockchain. If we send a POST request to it, we can write to it.

 

Here’s our GET handler:

 

We simply write back the full blockchain, in JSON format, that we can view in any browser by visiting localhost:8080. We have ourADDR variable set as 8080 in our `.env` file so if you change it, make sure to visit your correct port.

Our POST request is a little bit more complicated, but not by much. To start, we’ll need a new Message struct. We’ll explain in a second why we need it.

 

Here’s the code for the handler that writes new blocks. We’ll walk you through it after you’ve read it.

 

The reason we used a separate Message struct is to take in the request body of the JSON POST request we’ll use to write new blocks. This allows us to simply send a POST request with the following body and our handler will fill in the rest of the block for us:

{"BPM":50}

The 50 is an example pulse rate in beats per minute. Use your own by changing that integer to your own pulse rate.

After we’re done decoding the request body into our var m Message struct, we create a new block by passing in the previous block and our new pulse rate into the generateBlock function we wrote earlier . This is everything the function needs to create a new block. We do a quick check to make sure the new block is kosher using the isBlockValid function we created earlier.

A couple notes

  • spew.Dump is a convenient function that pretty prints our structs into the console. It’s useful for debugging.
  • for testing POST requests, we like to use Postmancurl works well too, if you just can’t get away from the terminal.

When our POST requests are successful or unsuccessful, we want to be alerted accordingly. We use a little wrapper function respondWithJSON to let us know what happened. Remember, in Go, never ignore errors. Handle them gracefully.

 

Almost done!

Let’s wire all these different blockchain functions, web handlers and the web server in a short, clean main function:

 

So what’s going on here?

  • godotenv.Load() allows us to read in variables like our port number from the.env file we placed at the root of our directory so we don’t have to hardcode them (gross!) throughout our app.
  • genesisBlock is the most important part of the main function. We need to supply our blockchain with an initial block, or else a new block will not be able to compare its previous hash to anything, since a previous hash doesn’t exist.
  • We isolate the genesis block into its own go routine so we can have a separation of concerns from our blockchain logic and our web server logic. This will work without the go routine but it’s just cleaner this way.

Tada! We’re done!

Here’s the full code:

Now for the fun stuff. Let’s try it out :-)

Fire up your application from terminal using go run main.go

In terminal, we see that the web server is up and running and we get a printout of our genesis block.

 
技术分享图片

Now, visit localhost with your port number, which for us is 8080. As expected, we see the same genesis block.

 
技术分享图片

Now, let’s send some POST requests to add blocks. Using Postman, we’re going to add a some new blocks with various BPMs.

 
技术分享图片

Let’s refresh our browser. Lo and behold, we now see all our new blocks in our chain with the PrevHash of our new ones matching the Hash of the old ones, just as we expected!

 
技术分享图片

Next Steps

Congrats!! You just wrote up your own blockchain with proper hashing and block validation. You should now be able to control your own blockchain journey and explore more complicated topics like Proof of Work, Proof of Stake, Smart Contracts, Dapps, Side Chains and more.

What this tutorial doesn’t address is how new blocks get mined using Proof of Work. This would be a separate tutorial but plenty of blockchains exist without Proof of Work mechanisms. In addition, the network broadcasting is currently simulated by writing and viewing the blockchain in a web server. There is no P2P component in this tutorial.

If you’d like us to add things like Proof of Work and Networking, make sure to tell us in our Telegram chat and follow us on Twitter! That’s the best way to communicate with us. Ask us questions, give feedback, and suggest new tutorials. We’d love to hear from you!

By popular demand we’ve added Part 2: Networking to this tutorial! Check it out!

We also released a tutorial on how to store data through the blockchain using IPFS. Take a look here!

To learn more about Coral Health and how we’re using the blockchain to advance personalized medicine research, visit our website.

 

以上是关于200行Go代码实现自己的区块链——区块生成与网络通信的主要内容,如果未能解决你的问题,请参考以下文章

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

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

200行go语言代码自建一个区块链 体验挖矿乐趣

Go语言200行写区块链源代码分析

自己动手实现区块链

自己动手实现区块链