比特币的P2P网络协议
转载请注明出处:https://www.jzgwind.com/?p=1001 by joey
一、五层网络模型
网络的五层模型可以分为:物理层、数据链路层、网络层、传输层、应用层。
物理层:
网络的实际物理介质,同轴电缆、双绞线、光纤等。
数据链路层:
可以理解为局域网内的通信,基于MAC地址。其中以太网是局域网最通用的通信协议标准。
网络层:
也叫IP层,负责不同网络之间的通信,各个网络之间由一个称之为“网关”的设备负责在网络之间转发数据。
传输层:
在网络层的基础上,增加了端口号,用于区分不同的应用程序,它是建立“端口”到“端口”的通信。Unix系统把主机+端口,叫“套接字(Socket)”。典型的传输层协议包括TCP协议和UDP协议。
点击查看《TCP的三次握手和四次挥手》——握手和挥手分别对应于“建立连接”和“断开连接”。
应用层:
应用层的作用,可以理解为规定应用程序所采用的数据格式。例如http协议、ftp协议、p2p协议等。比特币所采用的应用层网络协议就是一种p2p协议。
二、P2P点对点通信需要克服的障碍
P2P通信协议属于应用层协议,它的实现方式有多种,比特币的网络协议即是一种P2P协议,它是建立在传输层的TCP协议之上,并采用8333端口进行通信。
由于历史原因,想实现点对点通信,需要解决NAT地址转换所引发的问题。STUN算法即是解决该问题的算法之一,具体原理可参见我写的另一篇文章《P2P网络节点间如何互访——详解STUN方式NAT穿透》。
解决了点对点通信问题,再来看一下比特币的P2P网络协议的协议细节:
三、比特币的P2P网络协议
以下内容主要翻译自“比特币维基百科——网络” https://en.bitcoin.it/wiki/Network
1、消息类型:
● version
有关程序版本号和区块数量等信息。用于节点之间初次建立连接时交换信息
● verack
当有节点接收到version消息后,如果该节点愿意进行连接,则可以通过回复verack消息进行确认。
● addr
列出一个或多个IP地址和对应的端口号。
● inv
库存清单,取”inventory”的前3个字母。“我有这些区块/交易:……”,通常在一个新的区块或交易被转发的时候发送inv消息。它只是一个清单,并不包括实际的交易数据。
● getdata
通过hash值请求一个单独的区块或交易。 节点收到邻居节点的getdata消息后,向邻居节点返回tx消息或block消息。
● getblocks
请求inv库存清单中的所有区块。
● getheaders
请求获取指定范围内的区块的区块头信息。
● tx
发送一笔交易。它只用于响应getdata请求消息。
● block
发送一个区块。同上,它只用于响应getdata请示消息。
● headers
发送区块头消息,最多一次能发送2000个区块头。非“全节点”(例如SPV节点)可以用它下载区块头信息,而不用下载完整的区块信息。
● getaddr
包含一些已知的活跃节点,用于请求一个addr消息从而获取活跃节点的IP和端口号,通常在新节点刚加入进行网络初始化启动时(bootstrapping)调用。
● submitorder, checkorder, and reply
这些消息只在“基于IP交易”的比特币版本中使用,因为它容易受到“中间人攻击”,因此“基于IP交易”的功能在比特币core的v0.8.0版本中被删除了。
● alert
发送网络警告。
● ping
不做任何事情。它只是用来检测网络连接是否仍然有效。如果连接失效会报一个TCP异常。
2、网络初始化启动——节点发现(Bootstrapping)
节点有一个可以连接的节点地址簿,这个地址簿是依据节点最近一次获取它的时间进行排序,并加入一些随机处理。节点在加入比特币网络建立连接之前,会依照地址簿的地址次序进行选择。
比特币有3种发现网络节点的方法:
(1) IRC
v0.6.x开始,比特币客户端默认不再支持IRC方式的节点发现,且从v0.8.2开始IRC方式的节点发现机制被彻底删除。以下信息仅对老版本的比特币客户端适用:
比特币加入IRC网络的随机频道(频道号在#bitcoin00 和 #bitcoin99之间)。节点的昵称根据IP地址进行编码。通过将这些频道中的昵称进行解码,节点可以获取当前连接到比特币网络中的节点地址列表。
由于IRC的标准端口是6777,如果节点主机没有该端口的出口通讯,Ifnet服务器也同时在监听7777端口。
(2) addr
本文提到的addr消息广播,它所达到的网络节点发现的效果和IRC方式类似。当一个新节点加入到比特币网络中时,其他节点可以可以相当快速地发现它,仅管当有节点离开时,其他节点可能并不知道并且持续一段时间。
比特币有一些被称为“种子节点”的地址。如果无法通过IRC方式连接,且之前从未连接过比特币网络,客户端通过连接种子节点列表中的节点来更新网络节点地址簿。
可以通过addnode命令行参数手工添加一个节点。addnode命令行参数强制比特币网络与一个指定节点进行连接。
(3) DNS
比特币通过查询服务器域名解析得到的IP地址并将其添加到潜在地址列表中。这是默认的种子节点发现机制,在v0.6.x及之后的比特币版本中均适用。
3、连接
比特币节点建立连接过程
如上图所示,节点B在比特币网络中,节点A通过节点B建立连接从而完成加入比特币网络的第一步,通信过程如下:
节点A通过发送一个version消息和比特币网络中的一个节点B建立连接,这个version消息包含了“版本号”、“区块数”、“当前时间”。
如果远程节点B收到并且同意根据节点A的version信息建立连接,节点B会给节点A返回一个verack确认消息和另外一条它自己的version消息。
节点A如果同意根据远程节点B发过来的version消息建立连接,节点A同样需要给远程节点B回复一个verack确认消息,节点B收到确认消息,到此,节点A已通过与节点B建立起了连接。
节点A连接的所有远程节点的时间数据会被收集,所有用到时间参数的网络任务,都使用上述收到到的时间的中位数作为其时间参数(除非是不同version的消息)。
获取网络节点地址
接着,你通过交换getaddr和addr消息,存储所有你所不知道的地址信息。addr消息一般只包含一个地址,但有时会包含最多1000个地址,这种情况通常出现在信息交换的初始阶段。
4、消息转发
交易发送过程
如果比特币网络中有节点要发送交易,它首先会发送一条包含该交易的inv消息给他所有的邻居节点。这些邻居节点会通过发送getdata消息请求inv里所有交易的完整信息。当这些邻居节点接收到交易的完整信息并且验证都是有效交易后,邻居节点又会各自通过发送inv消息给他们自己的邻居节点,以此类推。
节点收到交易信息后,只有确认他们自己之前没有接收过这些交易时,节点才会转发收到的交易。节点永远不会转发一个之前已经接收过的交易,仅管过一段时间后这些交易可能因为没有被记录到区块里而最后被“遗忘”。然而,交易的发送节点和接收节点会重新广播这些被遗忘的交易。
任何收到有效交易的节点会试图将他们装进“区块”(挖矿)。当有节点找到一个区块(控到矿)后,他们会发送一个包含该区块信息的inv消息给它的邻居节点,通信过程和上面“交易”的转发流程一样。
addr消息广播
所有节点每隔24小时会广播包含他们自身IP地址的addr消息。接收到消息的节点会转发这些addr消息到它的邻居节点,如果addr中的地址是新地址的话,节点会存储addr消息中的地址信息。通过这样的系统,所有节点都会有一张相当清晰的视图:当前时刻有哪些IP地址是在比特币网络中保持连接状态的。当新节点连接到比特币网络后,通过发送addr消息广播,它几乎是会立刻被添加到其他所有节点的地址簿中。
比特币中的网络警告是通过发送alert消息完成的。与交易和区块信息先通过发送inv简要信息不同,alert消息会包含所有要警告的内容。警告消息是由网络中的一个节点用它的私钥签名后发送的,如果其他节点接收到这条警告消息并且验证通过的话,它会被转发到所有的其他节点。只要警告仍然是有效的,每当比特币网络中有新的节点加入,这个警告消息都会立即被广播。
5、初始区块下载
从邻居节点获取区块
当你通过一个节点建立连接并加入到比特币网络后,你需要把你所知道的最新区块的hash值放到getblocks消息体里并发送给你的邻居节点。如果邻居节点会收到getblocks消息并且识别出你给的hash对应的区块不是最新区块时,邻居节点会给你发送一个inv消息,这个inv消息里包含了你发过来的hash对应区块的前序区块信息(最多500个区块)。接着当你收到这个env消息后,你再根据env所列的区块信息,给该邻居节点发送getdata消息来获取对应的每一个区块,邻居节点收到getdata消息后,会通过发送block消息来给你返回区块的完整信息。当你下载和处理好所有这些区块后,你可以再次发送getblocks消息,以此类推,直到你获得了所有的网络中所有的区块。
6、瘦(轻)SPV客户端
SPV轻(瘦)客户端获取headers区块
中本聪的论文中提到了SPV(简单支付验证),在比特币最开始的版本里并没有添加这一功能。BIP 0037 介绍了通过SPV方式实现的比特币瘦(轻)客户端。当需要验证区块链中的交易是否真实存在和被确认过,通过SPV客户端可以不需要下载完整的区块内容,而是依赖区块头所组成的区块链(通过发送getheaders消息和返回headers消息获取)以及bloom过滤器从其他节点获取相应的验证数据就可以完成上述验证。这种SPV轻客户端,可以和全节点之间进行安全级别校低的通讯,但代价是会牺牲一部分隐私性,因为其他节点可以据此推断出SPV节点所寻找的信息对应的地址。
PS:Bloom过滤器可以让SPV节点指定交易的搜索模式,该搜索模式可以基于准确性或私密性的考虑被调节。一个非常具体的Bloom过滤器会生成更准确的结果,但也会显示该用户钱包里的使用的地址;反之,如果过滤器只包含简单的关键词,更多相应的交易会被搜索出来,在包含若干无关交易的同时有着更高的私密性。
MultiBit 和 Bitcoin Wallet 是基于bitcoinj实现的两个比特币钱包,它们实现了上述SPV客户的特性。
7、心跳
当节点大于等于30分钟没有发送新的消息时,它会发送一个消息来保持和其邻居节点之间的连接。
当节点大于等于90分钟没有发送新的消息时,邻居节点会认为它已经关闭了连接。