原文:《Part 2: Networking — Code your own blockchain in less than 200 lines of Go!》
如果你还没有读《200行go代码实现区块链》可以先读一下,以下内容以其为基础。
本文仅是模拟节点广播区块链数据到其他节点,可以在此基础上进行一些修改使其真正地成为节点互联。
第一个终端负责产生创世块,并建立TCP服务接受其他节点的连接请求。
(原文还有一些关于HTTP 和TCP的区别,这里就不翻译了。)
.env
文件内容ADDR=9000
新建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"
)
我们将《200行go代码实现区块链》部分可用代码复制到该文件中。
// 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
}
}
现在进入本文的关键,首先我们定义一个全局的chan
var bcServer chan []Block
接下来我们需要定义main方法
首先我们有如下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)
}
我们需要在其中添加启动tcp服务、以及处理连接的相关方法。
// 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)
}
最终 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)
}
}
到现在为止我们还差一个方法就是handleConn
用于处理TCP连接
func handleConn(conn net.Conn) {
defer conn.Close()
}
首先,终端连接到服务后,会提示终端输入其心跳数据(PS:本区块链是用于记录自己的心跳数据)
服务器接收到心跳数据后,生成一个新块,并将该块加入到区块链中,最后将新生成的区块链写入chan中。代码如下:
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:")
}
}()
广播新生成的区块链(原文代码中并没有使用chan ,而是直接使用Blockchain进行多线程之间的数据共享,这没有体现chan的使用,我将在以后的代码中进行改进)
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)
}
Ok 至此所有的代码就完成了。可以开始测试了。
我们开启三个终端,第一个终端我们启动我们的服务go run main.go
另外两个终端使用nc命令连接该服务nc localhost 5000
然后我们在第2个终端中输入50
我们可以立即在第一个终端中看到新生成的区块数据,等待30秒后(模拟节点挖矿生成新块需要一定时间)可以在第三个终端看到新的区块链数据
# 英文原文
英文原文的内容比我说的丰富详细多了,建议大伙儿阅读
《Part 2_ Networking — Code your own blockchain in less than 200 lines of Go!》