go启动后,可以用telnet登录访问。
注意端口配置写在.env里面。
源码:https://github.com/mycoralhealth/blockchain-tutorial/tree/master/networking
英文原版:https://medium.com/@mycoralhealth/code-your-own-blockchain-in-less-than-200-lines-of-go-e296282bcffc
Part 2: Networking — Code your own blockchain in less than 200 lines of Go!
Have you checked out Part 1 of this series yet? If not, go take a look. Don’t worry, we’ll wait…
Welcome back!
We were stunned at the reception we received in our first “Code your own blockchain in less than 200 lines of Go!” post. What was meant to be a little tutorial for beginning blockchain developers has taken on a life of its own. We’ve been bombarded with requests to do an add-on post where we integrate networking.
Before we get started, make sure to join our Telegram chat! This is the best place to ask us questions, give feedback and request new tutorials. If you need some help with your code, this is the perfect place to ask!
The last post showed you how to code up your own blockchain, complete with hashing and validation of each new block. But it all ran in a single terminal (node). How do we get another node to connect to our main instance and have them contribute new blocks, and how do we broadcast the updated blockchain to all the other nodes?
We’ll show you here.
Workflow
- The first terminal creates a genesis block and a TCP server to which new nodes can connect
Step 1
- Additional terminals are opened and TCP connections are made to the first terminal
- New terminals write a block to the first terminal
Step 2
- The first terminal validates the block
- The first terminal broadcasts the new blockchain to each of the new nodes
Step 3
- All terminals now have synced blockchains!
After the tutorial, try this yourself: have each of the new terminals also act as “First” terminals with different TCP ports and have the other terminals connect to it for a true network!
What you will be able to do
- Run a terminal that provides a genesis block
- Fire up as many additional terminals as you like and have them write blocks to the first terminal
- Have the first terminal broadcast updated blocks to the new terminals
What you won’t be able to do
Like the last post, the aim of this tutorial is to get a basic network of nodes working so you can decide on where your blockchain journey goes from here. You won’t be able to have computers in other networks writing to your first terminal, but this can be readily achieved by putting your binary in the cloud. Also, blockchain broadcasting will be simulated to each of the nodes. Don’t worry, we’ll explain all of this shortly.
Let’s get coding!
Some of this will be a review of the last post. We’ll be using many of the same block generation, hashing and validation functions. But we won’t be using any of the HTTP functionality since we’ll be viewing results in the console and we’ll be using TCP for networking.
What’s the difference between TCP and HTTP?
We won’t get into much detail here but all you need to know is that TCP is a base protocol that transmits data. HTTP is built on top of TCP to utilize this data transmission on the web and in browsers. When you view a website, you’re using HTTP, which is supported by an underlying data transmission protocol, called TCP.
For this tutorial, we’ll be working with TCP as we won’t be needing to view anything in a browser. Go has a nice net
package that provides all the TCP connection functions we need.
Setup, Imports and Review
Some of this will be a review from Part 1. For blockchain generation and validation, we’ll be using the same functions from the last post. Bear with us while we do a quick review. Don’t worry, we’ll get to the new stuff soon!
Setup
Create a .env
file in your root directory and add the following line:
ADDR=9000
We store the TCP port number we want to use (in this case 9000) in an environment variable called ADDR
.
If you haven’t done it already, grab the following packages:
go get github.com/davecgh/go-spew/spew
to pretty print our blockchain to the console
go get github.com/joho/godotenv
to load the variables in our .env
file
Create an empty main.go
file. We’ll be putting all our code here.
Imports
Let’s do our standard package declaration and write up the imports we’ll need.
Review
The following code snippets are explained in much greater detail in Part 1. Refer to it if you need a refresher. We’ll be moving fast through this.
Let’s create our Block
struct and declare our Blockchain
as a slice ofBlock
Now create our hashing function that we need when generating new blocks.
Our block generation function:
Make sure our new block is valid by checking its PrevHash
against the Hash
of the previous block.
Now we ensure we take the longest chain as the true blockchain:
Nice! We’ve basically got all the blockchain related functions we need with all the HTTP related stuff from Part 1 stripped out. We can now proceed to networking.
Networking
Finally! We’re at the good stuff. Let’s set up a network that can pass around new blocks, integrate them into our blockchain, and broadcast new blockchains back to the network.
Let’s start with our main function, as that is a nice abstraction that will help us understand the overall flow.
Right before we do that, let’s declare a global variable called bcServer
(short for blockchain server) under our other struct declarations that is a channel that takes in incoming blocks.
Side note: Channels are one of the most popular features of Go and allow for elegant streamlining of data reading/writing and are most often used to prevent data races. They become particularly powerful when used concurrently, as multiple Go routines can write to the same channel. Traditionally in Java and other C-like language, you’d have to open and close the “gates” for incoming data through mutexes. Channels in Go make this much easier, although there are still places for mutexes in Go. Learn more about channels here.
Now let’s declare our main
function and load up the environment variable from our .env
file that sits in our root directory. Remember, the only environment variable is ADDR
which is our TCP port number 9000 which we’ll use in a second. Also, let’s instantiate our bcServer
in the main
function.
Now we need to write up our TCP server. Remember, you can think of TCP servers as being similar to HTTP servers but there is no browser component. All data transmission will be done in the console through our terminal. We will be handling multiple connections to our TCP port. Add this to your main
function below the last line.
This fires up our TCP server at port 9000. It’s important to defer server.Close()
so the connection closes cleanly when we no longer need it. Learn more about defer
here.
Now we need to create a new connection each time we receive a connection request, and we need to serve it. Add this below the last line.
We just made an infinite loop where we accept new connections. We want to concurrently deal with each connection through a separate handler in a Go routine go handleConn(conn)
, so we don’t clog up our for
loop. This is how we can serve multiple connections concurrently.
The perceptive reader will jump up and say, “hey wait! We don’t have ahandleConn
function!”. You’re right. But let’s take a quick breather. Nice job, we just wrote up our entire main function. It looks like this:
Let’s write up that handleConn
function now. It only takes in one argument, a nicely packaged net.Conn
interface. Interfaces in Go are amazing and in our opinion, what separates Go from every other C-based language. Concurrency and Go routines get all the hype but interfaces and the fact that they’re satisfied implicitly are the language’s most powerful feature. If you don’t use interfaces in Go yet, get familiar with them as quickly as you can. Interfaces are your next step in your journey to becoming a 10x Go developer!
Put in the skeleton of the function and start it off with a clean defer
statement to close each connection when we’re done with it.
Now we need to allow the client to add new blocks for us to integrate into the blockchain. We’re going to use the same pulse rate from Part 1. Take your own pulse over a minute and keep that number in your head. That will be our BPM (beats per minute).
To achieve the above we need to:
- prompt the client to enter their BPM
- scan the client’s input from
stdin
- create a new block with this data, using the
generateBlock
,isBlockValid
, andreplaceChain
functions we created previously - put the new blockchain in the channel we created to be broadcast to the network
- allow the client to enter a new BPM
Here is the code to do that, in the exact order described above.
We create a new scanner. Thefor scanner.Scan()
loop is tucked away in its own Go routine so it can run concurrently and separately from other connections. We do a quick string conversion of our BPM (which will always be an integer, so we check for that). We do our standard block generation and validation checks and create a new blockchain with the new block.
The syntax bcServer <- Blockchain
just means we’re throwing our new blockchain into the channel we created. Then we prompt the client to enter a new BPM to create the next block.
Broadcasting
We need to broadcast the new blockchain to all the connections being served by our TCP server. Since we’re coding this up on one computer, we’ll be simulating how data gets transmitted to all clients. Under the last line of code, in the same handleConn
function, we need to
- Marshall our new blockchain into JSON so we can read it nicely
- write the new blockchain to the consoles of each of our connections
- set a timer to do this periodically so we’re not getting inundated with blockchain data. This is also what you see in live blockchain networks, where new blockchains are broadcast every X minutes. We’ll use 30 seconds
- Pretty print the main blockchain to the first terminal, just so we can see what’s going on and ensure blocks being added by different nodes are indeed being integrated into the main blockchain
Here is the code, in this exact order.
Nice! Our handleConn
function is done. In fact, the whole program is done and we’ve kept it to under 200 lines. That wasn’t bad at all was it?
Check out the completed code here!
The Fun Stuff
Let’s try this baby out! Let’s fire up our application by going to our directory and go run main.go
As expected, we see our genesis block. What’s happening at the same time is that we’ve started a TCP server at port 9000 that can take in multiple connections. So let’s do that.
Open a new terminal window and connect to our TCP server with nc localhost 9000
. We’ll be using different color terminals to make it clear these are different clients. Do this a couple times with a couple terminal sessions to activate multiple clients.
Now type in a BPM to any of the clients. And KAPOW! We see the new block added to the first terminal! Networking in action!
Here’s where it gets REALLY cool. Wait 30 seconds. Go to one of the other clients and you’ll see the new blockchain broadcast to all the clients, even if those clients never inputted a BPM!
Next steps
Congratulations! Not only did you create your own blockchain from the last tutorial, you’ve now added networking ability to it. There are several places you can go from here:
- To have a bigger network running on your local environment, create multiple directories that hold a copy of the application each with different TCP ports. For each terminal session, serve a TCP port and connect to a different one so you can receive and send data.
- Connect data being streamed from multiple ports. This is a topic for another tutorial but is done readily.
This is all a blockchain network is. It needs to take in incoming data and also broadcast data externally. Doing both in a single terminal session is effectively what a mining setup is.
- If you want to try this with you friends, set up the server in the cloud using your favorite hosting provider. Have your friends connect to it and send in data. There are some additional security considerations here. If there is demand we’ll write up a tutorial for this too.
You are well on your way to intimately understanding multiple facets of the blockchain. From here, we’d recommend reading up on consensus algorithms like Proof of Work or Proof of Stake.
Or…you can just wait for us to write a new blog post about it :-)
Again, the best place to tell us what you want to see is to join our Telegramchat! Don’t miss out on some cool sneak peeks on the tutorials we’re going to be releasing next. Maybe if you get to us early enough we’ll change our minds and write a tutorial on what you want to see. Make sure to follow us on Twitter too!
We released a follow-up 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.
Until next time!
在第一篇文章[1]中,我们向大家展示了如何通过精炼的Go代码实现一个简单的区块链。如何计算每个块的 Hash 值,如何验证块数据,如何让块链接起来等等,但是所有这些都是跑在一个节点上的。文章发布后,读者反响热烈,纷纷留言让我快点填坑(网络部分),于是就诞生了这第二篇文章。
https://mp.weixin.qq.com/s?__biz=MzAwMDU1MTE1OQ==&mid=2653549384&idx=1&sn=fce9e6fa059c044a6abfcf2cc3241ba5&chksm=813a62d0b64debc657e09718d6c851ee1cc7c37d3cb5b0a4213732a331dcd4bd5aae38a5fdf4&scene=21#wechat_redirect
这篇文章在之前的基础上,解决多个节点网络内,如何生成块、如何通信、如何广播消息等。
流程
-
第一个节点创建“创始区块”,同时启动 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
就像我们预期的,首先创建了“创世块”,接着启动了 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
相关阅读:
特别推荐:
比特币、以太坊、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!
全部评论
请发表评论