blockchain_go节点间命令交互流程分析
1.背景
blockchain_go是一个go语言实现的简化的区块链项目,麻雀虽小五脏俱全,区块链涉及的各个方面都会有所体现,是入门区块链的一个好选择。作为分布式应用的区块链,新节点的加入需要同步已有节点的区块信息,使得自己保持最新,从而才能正常加入区块链中工作。加入到区块链网络中后,也会涉及到各个节点之间的信息和命令交互,本篇对节点间的交互流程进行分析整理。
2.通信协议机制
blockchain_go节点间的通信协议规则采用如下规则:
固定长度command + payload
command固定长度12字节,payload是每个命令的消息体的gob打包二进制序列。
底层通信机制方面,blockchain_go采用了简化处理方式,并没有保存和维护每个邻居节点的链接信息,而是采用了“短连接”的方式处理每次消息的发送。
通信协议可以指定tcp或者udp,都兼容。每次发送数据都建立一个socket套接字(无论tcp还是udp),发送完毕后释放链接信息。这样的机制对于数据接收方有另外一个好处,不用处理黏包问题,每次接收数据时将数据全部读完(读到EOF)即可。如果采用场链接tcp协议,对于流式数据是必须要处理半包(协议包数据未收完)和黏包(多个协议包黏在一起,需要拆包),处理逻辑会更复杂。
3.节点间交互命令
节点间的交互命令command如下:
version : 发送字节的版本信息
getblocks : 获取区块链中所有块的hash列表
inv : 返回区块hash列表目录
block : 新区块到来通知
addr : 通知其他节点自己的地址信息
getdata : 获取数据请求(获取block区块信息或者tx交易信息)
tx : 交易信息到来通知
3.1 version命令
version命令发送给对方,告知对方自己节点的版本信息和区块高度,命令消息体定义:
type verzion struct {
Version int
BestHeight int
AddrFrom string
}
Version: 发送方自己节点区块链的版本号
BestHeight: 发送方区块链的最大高度(区块链中高度越高,表示自己越新),即区块最大个数
AddrFrom: 发送方消息接收地址(并没有让对方通过socket获取,而是发送方灵活指定自己的接收地址)
新节点启动时,发送自己的version信息给配置的知名节点,从而加入到网络中。
3.2 getblocks命令
getblocks命令请求,请求对方发送自己的所有区块的hash列表给自己,消息体定义:
type getblocks struct {
AddrFrom string
}
只需要填充自己的接受地址即可,对方收到请求后,生成hash列表返回。
3.3 inv命令
inv命令是inventory的简写,表示节点得到了目录列表的请求(区块hash列表或者tx交易hash列表)后,发送给请求方的目录响应,消息定义:
type inv struct {
AddrFrom string
Type string
Items [][]byte
}
AddrFrom: 节点自己的信息接收地址
Type: 目录类型(block/tx)
Items: 目录具体值(区块hash列表,交易hash列表)
3.4 addr命令
把自己的接收地址和自己知道的知名节点的接收地址发送给对方,消息定义:
type addr struct {
AddrList []string
}
AddrList: 地址列表
发送给了对方后,对方可以从这些节点获取信息。
3.5 getdata命令
请求命令,请求对方给自己发送数据,消息定义:
type getdata struct {
AddrFrom string
Type string
ID []byte
}
AddrFrom: 自己的接收地址
Type: 请求的数据类型(block or tx)
ID: 请求的数据参数(block类型–>block hash;tx类型–>tx hash)
前面的getblocks只是得到了block的hash列表,并没有得到区块的详细信息,getdata就是提供获取区块或者交易的详细信息。
3.6 block命令
接收到getdata命令,且类型为block时,返回block命令,类型定义:
type block struct {
AddrFrom string
Block []byte
}
AddrFrom: 自己的接收地址
Block: 请求区块的详细信息
3.7 tx命令
接收到getdata命令,且类型为tx时,返回tx命令,类型定义:
type tx struct {
AddrFrom string
Transaction []byte
}
AddrFrom: 自己的接收地址
Transaction: 交易信息
4.交互流程
这里主要分析新节点启动加入到区块链的底层交互流程,以及加入成功后,产生一个新的区块后的节点间的交互。
4.1 新节点启动交互流程
新节点的启动交互流程如下图所示:
说明如下:
新节点A启动前配置好邻居节点B的地址,启动后向邻居节点B发送version命令,告知对方自己的区块链版本号,区块高度以及自己的信息接收地址。
节点B接收到version命令后,和自己的区块高度做比较。
如果A的区块高度更高(表示B的区块比A更旧),那么B向A发起getblocks命令,请求区块链的全部区块hash列表。
如果B发现自己的区块高度比A高,那么回应一个version命令给A,让A知道自己的基本信息。
A收到B的version信息,进行区块高度比较。
A发现B比自己的区块更高,自己落后了,需要获取最新区块,于是发送getblocks命令。
B收到getblocks后,将自己的完整区块链的区块hash列表返回给A,发送inv。
A计算收到的区块列表,将没有的所有区块都一次请求获取。
A向B发送getdata(类型为block),获取指定的一个区块详细信息。
B根据给定的查询区块hash,返回区块的详细给A,响应一个block命令。
依次循环,处理完所有区块后,A把自己的区块链更新成了最新状态。
4.2 新区块产生交互流程
新区开产生后的节点交互如下图:
说明如下:
节点A收到了一个新的交易信息,处理交易。
节点A在交易池中有了新信息,触发挖矿逻辑,产出一个block。
节点A把新的block循环依次发送给所有邻居。
通过inv发送新节点的hash信息给邻居。
邻居B收到hash列表后,通过block命令获取区块详细信息。
A返回新区块详细信息给B。
节点B存储新收到的区块,并更新本地的utxo信息。
5.总结
blockchain_go是一个简化版的区块链实现,相关功能都进行了精简,并不具备线上生成能力,不过对于初学区块链,了解区块链相关原理和实现逻辑还是很有意义。