在梳理代码逻辑之前,首先介绍几个比较重要的结构:
CMessageHeader
消息头包含的内容:
1 | class CMessageHeader { |
Message Headers
网络传输中,所有消息的消息头格式是一样的,下面解释一下每一个消息头具体包含哪些内容:
bytes | name | 数据类型 | 描述 |
---|---|---|---|
4 | start string | char[4] | 告诉发送端的节点,现在可以开始发送 Magic 的消息;用于在流状态未知时寻求下一条消息。 |
12 | command name | char[12] | 标识有效载荷中包含的消息类型的ASCII字符串。随后是空值(0x00)来填充字节数; 例如:version\0\0\0\0\0 |
4 | payload size | uint32_t | 有效 payload 中的字节数。Bitcoin Core 在有效的 payload 中允许的当前最大字节数(MAX_SIZE)为32个有效载荷大小超过此值的消息将被丢弃或拒绝。 |
4 | checksum | char[4] | SHA256的前4个字节(SHA256(payload))以内部字节顺序排列。 如果有效payload为空,如verack和getaddr消息,则校验和始终为0x5df6e0e2(SHA256(SHA256( |
GetDataMsg
getdata / inv消息类型。这些号码由协议定义。
1 | enum GetDataMsg { |
消息类型大致分为:
- MSG_TX 交易信息
- MSG_BLOCK 区块
- MSG_FILTERED_BLOCK 过滤的区块
- MSG_CMPCT_BLOCK 紧凑区块 //bip152
NetMsgType
1 | namespace NetMsgType { |
Inv
当需要获取inventory
时,发送此命令,发送时,需要指定范围。接收到此命令后,按指定范围获取inventory数据(PushGetBlocks)。
bytes | name | 数据类型 | 描述 |
---|---|---|---|
Varies | count | compactSize uint | inventory 条目的数量 |
Varies | inventory | inventory | 一个或多个库存条目,最多50,000个条目 |
Inv 消息的示例:
1 | 02 ................................. Count: 2 |
getdata:
getdata消息请求来自另一个节点的一个或多个数据对象。这些对象由一个 inventory 请求,请求节点通常通过inv消息预先接收这些对象。
对 getdata 消息的响应可以是 tx 消息,阻塞消息,merkleblock 消息或未找到的消息。
getdata 不能用于请求任意数据,例如不再存在于内存池中的一些历史交易。如果全节点已经从其block数据库中打包了先前的交易,这时全节点可能无法提供这些block。 出于这个原因,getdata消息通常只能通过发送inv消息向先前通告它的节点请求数据。
getdata消息的格式和最大大小限制与inv消息相同,但是消息标题不同。
merkleblock:
如BIP37所述,在协议版本70001中添加。
merkleblock 消息是对使用 inventory 类型 MSG_MERKLEBLOCK 请求块的 getdata 消息的回复。这只是答复的一部分:如果找到任何匹配的交易,它们将作为tx消息单独发送。
如果之前已经使用过滤器加载消息设置了过滤器,则merkleblock消息将包含所请求块中与过滤器匹配的所有事务的TXID以及将这些事务连接到块头的必要块所需的块merkle树的任何部分 merkle根。 该消息还包含块头的完整副本,以允许客户端对其进行 hash 并确认其工作证明。
merkleblock消息示例:
1 | 01000000 ........................... Block version: 1 |
getblocks
getblocks消息请求一个inv消息,该消息提供从块链中的特定点开始的块头hash。区块同步时,发送此命令,发送时需要指定区块范围(PushGetBlocks)。接收到此命令后,根据区块范围,获取相应的区块,反馈回去。接收的数据中包含区块范围的开始区块的定位信息(CBlockLocator)、结束区块的索引,从开始区块的下一个区块开始。每次最多获取500个区块信息。满500个时,记录获取的最后一个区块的hahs值,保存到源节点的hashContinue中。
bytes | name | 数据类型 | 描述 |
---|---|---|---|
4 | version | uint32_t | 协议版本号;与版本信息中发送的一样。 |
Varies | hash count | compactSize uint | 提供的header数量不包括stop哈希。除了整个消息的字节大小必须低于MAX_SIZE限制外,没有限制;通常发送1到200次hash。 |
Varies | block header hashes | char[32] | 一个或多个块头hash(每个32字节)以内部字节顺序排列。hash应该以块高度相反的顺序提供,因此最高高度 hash 指向第一个,最低高度哈希指向最后一个。 |
32 | stop hash | char[32] | 请求最后一个header hash 的头部hash值; 设置为全零以请求包含所有后续头部hash的inv消息(最多500个将作为对此消息的回复发送;如果您需要超过500个,则需要发送另一个具有更高高度头部的getblocks消息 hash 作为块头 hash 字段中的第一个条目)。 |
示例如下:
1 | 71110100 ........................... Protocol version: 70001 |
getheaders
getheaders消息请求 headers 消息,该消息提供从块链中的特定点开始的块 header。接收到此命令后,获取指定的范围的区块的头,将 headers消息发送给源节点。
getheaders消息几乎与getblocks消息相同,只有一点区别:对getblocks消息的inv回复将包含不超过500个块头hash; headers 回复 getheaders 消息将包含多达2000个块 headers。
tx
tx 消息以原始交易格式传输单个交易。它可以在各种情况下发送;
- 交易响应:Bitcoin Core 和 BitcoinJ 将发送它以响应getdata消息,该消息请求 inventory 类型为MSG_TX的交易。
- MerkleBlock响应:Bitcoin Core 将发送它以响应getdata消息,该消息请求inventory类型为MSG_MERKLEBLOCK的merkle块。 (这是发送merkleblock消息的补充。)在这种情况下,每个tx消息提供该块的匹配交易。
- Unsolicited:BitcoinJ会发送一个tx消息来主动发起它的交易。
headers
headers 消息将 block message 发送到先前用getheaders消息请求特定 headers 的节点。headers 消息可以是空的。
bytes | name | 数据类型 | 描述 |
---|---|---|---|
Varies | count | compactSize uint | block headers 的数量最多可达2000个。注意:headers-first sync 假定发送节点将尽可能发送最大数量的headers |
Varies | headers | block_header | block headers:每个80字节block headers采用 block headers section中描述的格式,并附加一个0x00后缀。 这个0x00被称为交易计数器,但由于头部消息不包含任何事务,因此事务计数始终为零。 |
示例如下:
1 | 01 ................................. Header count: 1 |
block
block message以 serialized blocks section 描述的格式发送单个 serialized block。
- 获取数据响应:节点将始终发送它以响应一个getdata消息,该消息请求存储类型为MSG_BLOCK的块(假设该节点具有可用于发送的该块)。
- 主动提供:一些矿工会发送未经请求的block信息,将他们新挖掘的块块广播给他们的所有同行。 许多矿池做同样的事情,虽然有些可能被错误地配置为从多个节点发送块,可能不止一次地将同一块发送给别的节点。
notfound
notfound 的消息是对getdata消息的回复,该消息请求接收节点没有可用于发送的对象。 (预计节点不会传递不再存在于内存池或发送集中的历史事务,节点也可能从较旧的块中删除已用完的事务,使它们无法发送这些块。)
notfound消息的格式和最大大小限制与inv消息相同,只有消息的headers不同。
mempool
mempool消息请求接收节点已验证为有效但尚未出现在块中的交易的TXID。 也就是说,在接收节点的内存池中的交易。 对mempool消息的响应是一个或多个包含 inventory 格式的TXID的inv消息。
当程序首次连接到网络时,发送mempool消息非常有用。 全节点可以使用它来快速收集网络上可用的大部分或全部未确认的交易; 这对试图收取交易费用的矿工尤其有用。 SPV客户端可以在发送mempool之前设置过滤器,以仅接收与该过滤器匹配的交易; 这允许最近开始的客户获得与其钱包有关的大部分或全部未确认的交易。
对mempool消息的inv响应充其量只是一个节点的网络视图 - 而不是网络上未经确认的交易的完整列表。以下是列表可能不完整的一些其他原因:
- 在Bitcoin Core 0.9.0之前,对mempool消息的响应只有一个inv消息。 inv消息被限制为50,000个库存,所以具有大于50,000个条目的内存池的节点不会发送所有内容。 Bitcoin Core 的更新版本根据需要发送尽可能多的inv消息以引用其完整的内存池。
- mempool消息当前不与filterload消息的BLOOM_UPDATE_ALL和BLOOM_UPDATE_P2PUBKEY_ONLY标志完全兼容。 Mempool交易不像块内交易那样排序,因此一个消耗输出的交易(tx2)可以出现在包含该输出的交易(tx1)之前,这意味着自动过滤器更新机制将不会运行,直到第二次出现的交易 tx1) - 缺少首次出现的交易(tx2)。 在Bitcoin Core issue #2381中已经提出,交易在被过滤器处理之前应该被排序。
以上是对 Data Messages 的描述,其关系图如下:
version:
version 消息在连接开始时向接收节点提供关于发送节点的信息。在这两个节点交换 version 消息之前,不会接受其他消息。
当接收节点收到version消息之后,会回复给发送节点一个verack消息,同时把所有警告也反馈回去。但是在version消息的初始化未完成之前,是不会发送verack消息的。
发送端只能发送一次获取版本的命令,重复发送时,回应拒绝命令 (reject)。发送命令后,会把发送节点的地址信息添加到节点的地址管理器中。
示例如下:
1 | 72110100 ........................... Protocol version: 70002 |
verack:
verack消息确认先前收到的版本消息,通知连接节点它可以开始发送其他消息。 接收到版本回应命令后,设置节点的接收版本。与此同时,设置接收版本号,节点的接收版本(nRecvVersion)、接收消息的报头流的版本号、接收消息数据流的版本号(nVersion)都要设置。
adddr:
addr(IP地址)消息用来表示网络上节点的连接信息。 每个想要接受传入连接的节点创建一个addr消息,提供其连接信息,然后将该消息发送给未经请求的节点,当接收端收到此命令后把接收到的地址添加到节点的地址管理器中,发送、接收的地址数量最多1000个。
bytes | name | 数据类型 | 描述 |
---|---|---|---|
4 | time | uint32 | 在协议版本31402中添加。采用Unix纪元格式的时间。通告自己IP地址的节点会将其设置为当前时间。通告他们连接的IP地址的节点将其设置为最后一次连接到该节点的时间。发送IP地址的节点不会改变时间。节点可以使用时间字段来避免传播旧的地址信息。恶意节点可能会改变时间,甚至可能会在未来进行设置。 |
8 | services | uint64_t | 节点在其版本消息中广播的服务信息。 |
16 | IP address | char | IPv6地址采用大端字节顺序。 IPv4地址可以作为IPv4映射的IPv6地址提供 |
2 | port | uint16_t | 端口号以大端字节顺序排列。 请注意,为了寻找自己的伙伴,Bitcoin Core只会连接到具有非标准端口号的节点。 这是为了防止其他人尝试使用网络来破坏在其他端口上运行的非比特币服务。 |
Ping
ping消息有助于确认接收方仍处于连接状态(判断网络是否连通)。 如果在发送ping消息时遇到TCP / IP错误(例如连接超时),则发送节点可以假设接收节点已断开连接。 对ping消息的响应是pong消息。
在协议版本60000之前,ping消息没有 payload。从协议版本60001及所有更高版本开始,消息包含一个字段即nonce。
bytes | name | 数据类型 | 描述 |
---|---|---|---|
8 | nonce | uint64_t | 如BIP31所述,在协议版本60001中添加。将随机数分配给ping消息。响应的pong消息将包括这个随机数以识别它正在回复的ping消息。 |
ping消息的nonce字段,示例如下:
1 | 0094102111e2af4d ... Nonce |
pong
pong消息回复ping消息,向pinging节点证明ponging节点仍然存在。默认情况下,Bitcoin Core将在20分钟内断开任何未响应ping消息的客户端。接收到pong命令后,更新节点的ping花费时间(nPingUsecTime),所花费的时间为当前时间与节点的ping开始时间(nPingUsecStart)的差。
为了允许节点跟踪等待时间,pong的回复消息中所包含的nonce字段与ping消息的nonce是相同的。
pong消息的格式与ping消息相同;只有消息头不同。
reject
发生特殊情况时,reject 消息通知接收节点其先前消息之一已被拒绝。
特殊情况如下:
- 重复发送获取版本信息的命令(”version”)。
- 发送端的版本号大于最大版本号(MIN_PEER_PROTO_VERSION = 209)。
- 接收到DDoS攻击。
- 处理消息时发生异常(ProcessMessage)。
发送拒绝命令时,带上参数,表示拒绝的原因。
Code | In Reply To | Description |
---|---|---|
0x01 | REJECT_MALFORMED | 处理消息时发生异常(ProcessMessage) |
0x10 | REJECT_INVALID | 块、交易信息无效 |
0x11 | REJECT_OBSOLETE | 块版本、发送端版本过期 |
0x12 | REJECT_DUPLICATE | 重复发送获取版本命令、交易信息 |
0x40 | REJECT_NONSTANDARD | 交易信息不标准 |
0x41 | REJECT_DUST | 无 |
0x42 | REJECT_INSUFFICIENTFEE | 交易费不足 |
0x43 | REJECT_CHECKPOINT | 与校验点有关的错误 |
SendHeaders
sendheaders消息告诉接收方使用 headers 消息而不是inv消息发送新的块通告,具体可参照 bip130 。
GetAddr
getaddr消息请求来自接收节点的addr消息,最好是具有大量其他接收节点的IP地址的消息。 发送节点可以使用这些IP地址来快速更新其可用节点的数据库,而不是等待未经请求的addr消息随时间到达。
接收到getaddr命令后把节点的地址管理器中的地址返回给发送端。先清空源节点的发送地址数组(vAddrToSend)。再把节点的IP地址管理器 (addrman)中的地址(CAddress)发送给源节点。
FeeFilter
FeeFilter消息是对接收方的请求,不将任何交易inv消息转发给发送方,其中交易费率低于feefilter消息中指定的费率。
在Bitcoin Core 0.12.0引入mempool限制之后,feefilter在Bitcoin Core 0.13.0中引入。 Mempool限制功能可以防止费用较低的交易的攻击,并且不会将其纳入开采块中。 feefilter消息告诉其他节点,如果你的费率低于我预先设置的费率,那你的这个交易是不允许进入我的mempool的,同时,这些节点就没必要继续把低于该费率的交易的inv消息转达给该节点。
bytes | name | 数据类型 | 描述 |
---|---|---|---|
8 | feerate | uint64_t | 费用率(以每千字节为satoshis)低于该费率,交易不应传递给其它节点。 |
接收方可以选择不过滤这笔交易,直接忽略该消息。
FeeFilter与bloom过滤器相加。如果SPV客户端加载bloom过滤器并发送FeeFilter消息,则只有通过两个过滤器才能转发交易。
但请注意,feefilter对块传播或对getdata消息的响应没有影响。 例如,如果一个节点通过发送一个包含inv类型MSG_FILTERED_BLOCK的getdata消息来请求一个merkleblock,并且它先前已经向该节点发送了一个feefilter,那么即使他们低于feefilter的费率,该节点也应该响应一个包含所有匹配bloom过滤器的交易的merkleblock。
示例如下:
1 | 7cbd000000000000 ... satoshis per kilobyte: 48,508 |
FilterAdd
filteradd消息告诉接收方将单个元素添加到先前设置的布隆过滤器,例如新的公共hash。 该元素直接发送给接收方; 然后其它节点使用在过滤器加载消息中设置的参数来将该元素添加到布隆过滤器。
由于该元素直接发送到接收方,因此elem不会产生歧义,也不会出现布隆过滤器提供的似是而非的隐私。希望保持更高隐私性的客户端应自行重新计算布隆过滤器,并使用重新计算的布隆过滤器发送新的过滤器负载消息。
bytes | name | 数据类型 | 描述 |
---|---|---|---|
Varies | element bytes | compactSize uint | 下列元素字段中的字节数。 |
Varies | element | uint8_t[] | 要添加到当前过滤器的元素。最大为520个字节,这是在pubkey或签名脚本中可以压入堆栈的元素的最大大小。元素必须以它们在原始交易中出现时使用的字节顺序发送; 例如,hash应以内部字节顺序发送。 |
注意:除非先前使用的filterload消息设置了过滤器,否则节点将不接受filteradd消息。
示例如下:
1 | 20 ................................. Element bytes: 32 |
FilterClear
filterclear消息告诉接收方删除先前设置的bloom过滤器。这也消除了将版本消息中的转发字段设置为0的效果,允许未经过滤的访问广播新交易的inv消息。
Bitcoin Core在替换过滤器加载filterload之前不需要filterclear消息。它也不需要filterclear消息之前的filterload消息。
FilterLoad
filterload消息告诉接收方需要过滤所有转发的交易,并通过提供的过滤器请求merkle块。 这允许客户接收与其钱包相关的交易。
bytes | name | 数据类型 | 描述 |
---|---|---|---|
Varies | nFilterBytes | compactSize uint | 以下过滤器位字段中的字节数。 |
Varies | filter | uint8_t[] | 任意字节对齐大小的位字段。最大大小是36,000字节。 |
4 | nHashFuncs | uint32_t | 在此过滤器中使用的hash函数的数量。该字段中允许的最大值为50。 |
4 | nTweak | uint32_t | 一个任意值,用于添加到布隆过滤器使用的哈希函数中的种子值。 |
1 | nFlags | uint8_t | 一组控制与匹配的pubkey脚本相对应的outpoint的标志被添加到过滤器中。请参阅下面的更新布隆过滤器小节中的表格。 |
示例如下:
1 | 02 ......... Filter bytes: 2 |
sendcmpct and cmpctblock、getblocktxn、blocktxn参照BIP152.
以上是对Control Messages的描述,其关系如下:
下表列出了一些值得注意的P2P网络协议版本,其中最新版本列在第一位:
version | Initial Release | Major Changes |
---|---|---|
70015 | Bitcoin Core 0.13.2 (Jan 2017) | v0.14.0中对无效压缩 blocks 的新禁用行为#9026, 在#9048中回退到v0.13.2 . |
70014 | Bitcoin Core 0.13.0 (Aug 2016) | BIP152: 增加了 sendcmpct , cmpctblock , getblocktxn , blocktxn 消息类型;给 getdata message.增加了MSG_CMPCT_BLOCK inventory 类型 |
70013 | Bitcoin Core 0.13.0 (Aug 2016) | BIP133: 增加了 feefilter message.移除了 alert message 系统. 具体参照 Alert System Retirement |
70012 | Bitcoin Core 0.12.0 (Feb 2016) | BIP130: 增加了 sendheaders message. |
70011 | Bitcoin Core 0.12.0 (Feb 2016) | BIP111: 在包含此版本后,filter* 消息在没有NODE_BLOOM的情况下被禁用。 |
70002 | Bitcoin Core 0.7.0 (Sep 2012) | BIP35: 增加了 mempool message. 扩展了 getdata message 允许下载内存池中的交易 |
60001 | Bitcoin Core 0.6.1 (May 2012) | BIP31: 增加nonce字段为 ping message 、增加了 pong message |
31800 | Bitcoin Core 0.3.18 (Dec 2010) | 增加了 getheaders message 和 headers message. |
31402 | Bitcoin Core 0.3.15 (Oct 2010) | 给 addr message添加时间字段 |
311 | Bitcoin Core 0.3.11 (Aug 2010) | 增加了 alert message. |
209 | Bitcoin Core 0.2.9 (May 2010) | 给 message headers增加了时间字段, 增加了 verack message, 并且增加了开始的 height 给 version message. |
106 | Bitcoin Core 0.1.6 (Oct 2009) | 给发送端的version message添加了IP地址字段,随机数和用户代理(subVer) |
本文由 copernicus 团队 冉小龙
分析编写,转载无需授权!