比特币开发者指南(7)--P2P网络

P2P网络

Bitcoin网络协议允许全节点(peers) 共同维护一个进行区块和交易数据交换的点对点的网络。完整节点下载并验证每个块和事务,然后再将它们中继到其他节点。存档节点是满节点,其存储整个块链,并且可以将历史块提供给其他节点。修剪节点是不存储整个块链的满节点。许多SPV客户端也使用比特币网络协议连接到完整的节点。

共识规则不涵盖网络,所以Bitcoin程序可能使用替代网络和协议,例如一些矿工使用的高速块中继网络和专用交易信息服务器使用的钱包提供SPV级安全性。

为了提供Bitcoin 对等网络的实际示例,本节使用比特币核心作为代表性的完整节点和BitcoinJ作为代表SPV客户端。这两个程序都是灵活的,所以只描述默认行为。另外,为了隐私,下面的示例输出中的实际IP地址已被替换为RFC5737保留的IP地址。

同伴发现

当第一次启动时,程序不知道任何活动的完整节点的IP地址。为了发现一些IP地址,他们查询一个或多个硬编码到Bitcoin Core和BitcoinJ的DNS名称(称为DNS种子)。对查找的响应应包括可能接受新的传入连接的一个或多个具有完整节点的IP地址的一个或多个DNS A记录。例如,使用Unix dig命令:


Bitotin社区成员维护DNS种子:其中一些提供动态DNS种子服务器,通过扫描,自动获取活动的节点的IP地址 网络;其他人提供手动更新的静态DNS种子,更有可能为非活动的节点提供IP地址。在任何一种情况下,如果节点被添加到DNS种子,如果它们运行在默认Bitcoin端口为8333,对于mainnet或18333为testnet 。

DNS种子结果未通过身份验证,恶意种子运营商或网络 中间人攻击者只能返回,隔离攻击者自己的网络上的程序,并允许攻击者提供虚假事务和块。因此,程序不应仅仅依靠DNS种子。

一旦程序连接到网络,它的对等体可以开始发送addr(地址)消息,IP地址和端口号为网络上的其他对等体,提供了一个完全分散的对等体发现方法。Bitcoin Core在持久的磁盘数据库中保留已知的对等体的记录,通常允许它在后续启动时直接连接到对等体,而不必使用DNS种子。

然而,对等体通常离开网络或更改IP地址,因此程序可能需要在启动成功连接之前启动几个不同的连接尝试。这可能会增加连接到网络所需的时间量,从而迫使用户在发送交易或检查付款状态之前等待。

为了避免这种可能的延迟,BitcoinJ总是使用动态DNS种子来获取被认为是当前活动的节点的的IP地址。Bitcoin Core还尝试在最小化延迟之间取得平衡,并避免不必要的DNS种子使用:如果Bitcoin Core在其对等体数据库中具有条目,则尝试最多11秒连接至少一个,然后倒回种子;如果在该时间内建立连接,则不会查询任何种子。

Bitcoin Core和BitcoinJ也包括一个硬编码的IP地址和端口号的列表,到几十个节点,这些节点在特定版本的软件首次发布的时候是活跃的。如果没有一个DNS种子服务器在60秒内响应查询,Bitcoin Core将开始尝试连接到这些节点,提供自动后备选项。

作为手动后备选项,Bitcoin Core还提供了几种命令行连接选项,包括通过IP地址从特定的节点获取对等体的列表的功能,或者通过IP地址持久连接到特定的节点。有关详细信息,请参阅-help文本。BitcoinJ可以编程做同样的事情。

资源: 比特币播种器,程序由Bitcoin Core和BitcoinJ使用的几个种子运行。Bitcoin Core DNS种子策略。Bitcoin Core和BitcoinJ使用的IP地址的硬编码列表使用makeseeds脚本生成。

连接到同伴

通过发送包含你的版本号,区块和当前时间的version消息给远处节点来连接同伴。远程节点使用自己的版本消息进行响应。然后,节点向另一个节点发送verack消息以指示连接已建立。

一旦连接,客户端可以发送到远程节点 getaddr和addr消息来收集其他对等体 T4>。

为了保持与同伴的连接,默认情况下,节点将在30分钟不活动之前向对等体发送消息。如果90分钟没有通过对等体接收到消息,客户端将假定该连接已关闭。

初始块下载

在完整节点可以验证未确认的事务和最近开始的块之前,必须下载并验证所有块 块 1(在硬编码生成块之后的块)到最佳块链的当前尖端。这是初始块下载(IBD)或初始同步。

虽然“初始”一词意味着该方法只使用一次,但也可以在需要下载大量的任何时候使用,例如,当先前追加节点已经离线了很长时间。在这种情况下,节点可以使用IBD方法下载自上次上线以来生成的所有块。

Bitcoin Core使用IBD方法,只要其本地最佳块链上的最后一个块有块头时间过去24小时以上。比特币核心0.10.0还将执行?? IBD,如果其本地最佳块链大于144 块低于它的本地最佳头链(即,本地块链过去超过约24小时)。

块优先

比特币核心(直到版本0.9.3)使用简单的初始块下载(IBD)方法,我们称之为blocks-first目的是从最佳块链顺序下载块。


Overview Of Blocks-First Method

第一次启动节点时,它的本地最佳块链中仅具有单个块 - 硬编码的创世块(块0)。节点选择一个称为同步节点的远程对等体,并发送getblocks消息如下所示。


First GetBlocks Message Sent During IBD

在getblocks消息的头散列字段中,这个新的节点发送头 块的hash(内部字节顺序中的6fe2 ... 0000)。它还将停止哈希字段设置为全零,以请求最大大小的响应。

在getblocks消息的收据之后,同步节点取第一个(且仅)哈希,并使用标题哈希查找其块的本地最佳块链。它发现块0匹配,所以它回复500 块 库存(对getblocks的最大响应 message)从块 1开始。它将这些库存发送到下面所示的inv消息。


First Inv Message Sent During IBD

库存是网络上的信息的唯一标识符。每个库存包含一个类型字段和对象实例的唯一标识符。对于块,唯一标识符是块的 头的散列。

块库存出现在inv消息中,与块链,所以第一个inv消息包含块 1到501的库存。(例如,如上图所示,块1的散列为4860 ... 0000)

IBD 节点使用接收的库存从sync 节点请求128 块在下面说明的getdata消息中。


First GetData Message Sent During IBD

块首先 节点重要的是请求并发送块,因为每个块头引用头。这意味着IBD 节点无法完全验证块,直到其父块已被接收为止。阻止由于父节点未被接收而无法验证,称为孤儿块;下面的小节更详细地描述它们。

在getdata消息的收据之后,同步节点回复每个块请求。每个块被放入串行化块格式,并以单独的块消息发送。发送的第一个块消息(对于块1)如下所示。


First Block Message Sent During IBD

IBD 节点下载每个块,验证它,然后请求尚未请求的下一个块 ,保持高达128 块的队列下载。当它请求了它具有库存的每个块时,它向另一个getblocks消息发送到同步节点请求最多500个块的库存。第二个getblocks消息包含多个头哈希,如下所示:


Second GetBlocks Message Sent During IBD

在收到第二个getblocks消息之后,同步节点搜索其本地的最佳块链对于块,匹配消息中的头散列之一,按照接收的顺序尝试每个哈希。如果找到匹配的散列,则从那个点开始,以块开头的500 块 库存进行回复。但是如果没有匹配的散列(除了停止散列)之外,它假设只有块,两个节点有共同的是块0,所以它从块 1(相同的inv消息开始 t>发送inv,见上面的几个插图)。

即使IBD 节点的本地块链,该重复搜索也允许sync 节点发送有用的库存 从节点的本地块链分支。IBD 节点越来越接近块链的尖端,这种fork检测变得越来越有用。

当IBD 节点收到第二个inv消息时,它将请求那些块使用getdata消息。同步节点将响应块消息。然后,IBD 节点将使用另一个getblocks消息请求更多的库存循环将重复,直到IBD 节点同步到块链的尖端。此时,节点将接受通过稍后小节中描述的常规块广播发送的块。


块优先的优点和缺点

block-first IBD的主要优点是其简单性。主要缺点是IBD 节点依赖于单个同步节点进行下载。这有几个含义:
  • 速度限制:所有请求都发送到同步节点,所以如果同步节点的上传带宽有限,则 节点将具有较慢的下载速度。注意:如果同步节点脱机,Bitcoin Core将继续从另一个节点下载,但它仍然只能从单个同步节点一次。
  • 下载重新启动:同步节点可以向IBD发送非最佳(但有效的)块链 节点。IBD 节点将无法将其识别为非最佳状态,直到初始块下载接近完成,迫使IBD 节点从不同的节点再次重新启动块链下载。Bitcoin Core在开发人员选择的各种块高度中附带了多个块链检查点,以帮助IBD 节点检测到它正在馈送一个替代的块链历史,允许IBD 节点在该过程的前面重新启动它的下载。
  • 磁盘填充攻击:与下载重新启动密切相关,如果同步节点发送非最佳(但有效)的块链,则链将被存储在磁盘上,浪费空间,并可能用无用数据填充磁盘驱动器。
  • 高内存使用:无论是恶意还是意外,同步节点可以发送块,从而创建孤儿块,在父母被接受和验证之前无法验证。孤立块存储在内存中,同时等待验证,这可能导致高内存使用。
所有这些问题部分或全部由Bitcoin Core 0.10.0中使用的头 - 第一个IBD方法解决。

资源:下表总结了本小节提及的消息。消息字段中的链接将带您进入该消息的参考页面。

块头优先

Bitcoin Core 0.10.0使用名为headers-first的初始块下载(IBD)方法。目标是下载最佳头链的标头,尽可能地最大限度地验证它们,然后并行下载相应的块 。这解决了旧的block-first IBD方法的几个问题。



Overview Of Headers-First Method

第一次启动节点时,它的本地最佳块链中仅具有单个块 - 硬编码的创世块(块0)。节点选择一个远程对等体,我们称之为节点,并发送getheaders 消息如下所示。


First getheaders message

在getheaders消息的头散列字段中,新的节点发送头 块的创世块(内部字节顺序中的6fe2 ... 0000)。它还将停止哈希字段设置为全零,以请求最大大小的响应。

在getheaders消息的收据之后,同步节点取第一个(且仅)哈希,并使用标题哈希查找其块的本地最佳块链。它发现块0匹配,所以它从块 1开始的2000 头(最大响应)回复。它将这些标题散列在下面所示的标题消息中。


First headers message

IBD 节点可以通过确保所有字段遵循共识规则来部分地验证这些块标题,并且根据nBits字段,头低于目标阈值。(完全验证仍然需要相应的块的所有事务。)

在IBD 节点部分验证了块头之后,它可以并行执行两件事:
  1. 下载更多标头: IBD 节点可以发送另一个getheaders消息到同步节点,以请求最佳头链上的下一个2,000 头。那些头可以立即验证,另一个批次重复请求,直到从同步节点收到头消息少于2,000个头,表示它不再提供头。在撰写本文时,标题同步可以在少于200次往返,或约32 MB的下载数据中完成。一旦IBD 节点收到一个头消息,小于2,000 头同步节点,它向每个出站对等体发送getheaders消息,以获得最佳头链。通过比较响应,可以很容易地确定它已经下载的头是否属于其任何出站对等体报告的最佳头链。这意味着即使不使用检查点,也将很快发现不诚实的同步节点(只要IBD 节点连接至少一个诚实的对等体;比特币核心将继续提供检查点,以防无法找到诚实的对等体。
  2. 下载块:当IBD 节点继续下载头 头完成下载,IBD 节点将请求并下载每个块。IBD 节点可以使用从头链计算出的块头来创建消息请求块需要库存。它不需要从同步节点请求它们 - 可以从任何完整节点 对等体请求它们。(尽管不是全部节点可能存储所有块。)这允许它并行获取块,并避免将其下载速度限制为单个同步节点的上传速度。为了在多个对等体之间传播负载,Bitcoin Core将一次只能从单个对等体请求最多16个块。结合最多8个出站连接,这意味着headers-first BitCol Core将在IBD期间同时请求最多128个块(相同比特币核心从其同步节点请求的最大数字块首先。

Simulated Headers-First Download Window

Bitcoin Core的头 - 第一个模式使用1,024- 块移动下载窗口来最大限度地提高下载速度。窗口中最低的高度 块是待验证的下一个块如果在比特币核心准备好验证它之后,块尚未到达,Bitcoin Core将等待至少两秒以上,停止节点发送块。如果块尚未到达,Bitcoin Core将与停止的节点断开连接,并尝试连接到另一个节点。例如,在上述图示中,如果Node A在至少两秒钟内不发送块 3将断开连接。

一旦将IBD 节点同步到块链的末尾,它将接受通过常规发送的块 block在后面的小节中描述的广播。

资源:下表总结了本小节提及的消息。消息字段中的链接将带您进入该消息的参考页面。

块广播

当矿工发现新的块时,使用以下方法之一向对等体广播新的块 :
  • 主动阻止推送: 矿工将块消息发送到其每个全部节点 对等体与新的块。miner可以以这种方式合理地绕过标准中继方法,因为它知道其对等体中没有一个刚刚发现的块。
  • 标准块中继器:作为标准中继器节点的矿工发送inv消息到对等体(全节点和SPV), 参考新的块。最常见的答案是:
  • 希望块的块优先(BF)对等体使用getdata消息请求完整的块。
  • 希望块的首标(HF)对等体回复getheaders消息包含最佳头链中最高高度 头的头哈希,可能还有一些标头进一步回到最佳标题链,以允许fork检测。那个消息紧随其后是getdata消息请求完整的块。首先请求头,如下面的小节所述,headers-first 对等体可以拒绝孤儿块。
  • 希望块的简化付款验证(SPV)客户端使用getdata消息 t3>通常请求merkle块。
矿工通过在块消息中发送块来相应地回复每个请求,一个或多个头消息或merkle块中的头以及相对于SPV客户端 bloom filter在merkleblock消息后跟零个或多个tx消息。

默认情况下,Bitcoin Core使用标准块中继广播块,但它将接受使用上述方法发送的块。

完整节点使用标准块中继方法验证所接收的块,然后将其通告给它们的对等体。下面的汇总表突出显示了上述消息的操作(Relay,BF,HF和SPV是指继电器节点,一个节点,首标 节点和SPV客户端; 任何使用任何块检索方法节点)

孤儿块

块优先节点可以下载 - 块,其前一个块头散列字段是指块头 节点尚未看到。换句话说,孤儿块没有已知的父(不同于陈旧块,它们已知父,但不属于最佳块链)。


Difference Between Orphan And Stale Blocks

当block-first 节点下载孤儿块时,它不会对其进行验证。而是将getblocks消息发送到发送孤儿块的节点;广播节点将使用包含任何块的库存的inv消息缺少节点(最多500个);下载节点将使用getdata消息请求块;并且广播节点将使用块消息发送那些块。下载节点将验证那些块,一旦前一个孤岛块的父节点被验证,它将验证前一个孤儿块。

首标 节点通过总是使用getheaders消息请求块头,然后使用getdata消息请求块。广播节点将发送包含所有块头(最多2,000个)的头消息,认为下载节点需要到达最佳头链的尖端;每个头将指向其父节点,因此当下载节点收到块消息时,块不应该是孤儿块 - 所有的父节点应该是已知的(即使它们尚未被验证)。尽管如此,在块消息中收到的块是孤立块,一个头-first 节点将立即丢弃。

然而,孤儿丢弃确实意味着首部 节点将忽略中的矿工发送的孤儿块 未经请求的块推。

交易广播

为了将事务发送到对等体,发送inv消息。如果接收到getdata响应消息,则使用tx发送事务。接收此事务的对等体也以相同的方式转发该事务,因为它是一个有效的事务。

内存池

完整的对等体可以跟踪有可能被包含在下一个块中的未确认事务。这对于矿工谁将真正我的某些或所有这些事务至关重要,但对于希望跟踪的任何对等体也是有用的未确认的事务,例如对等体将未确认的事务信息提供给SPV客户端。

因为未确认的事务在Bitcoin中没有永久状态,Bitcoin Core将它们存储在非持久性内存中,称为内存池或记忆库。当对等体关闭时,除了钱包存储的任何事务外,其内存池将丢失。这意味着从未开始的未确认的事务趋向于从网络缓慢消失,因为对等体重新启动,或者当它们清除一些事务以在内存中腾出空间为他人。

开始进入块的事务可能会添加到内存池中。如果替换块包含它们,这些重新添加的事务可能几乎立即从池中重新删除。在Bitcoin Core中是这样,它从链中逐个删除陈旧块,从尖端(最高块)开始。当每个块被删除时,其事务将被添加回内存池。在删除所有过期块之后,替换块逐个添加到链中,以新提示结尾。当添加每个块时,它确认的任何事务都将从内存池中删除。

SPV客户端没有内存池,因为它们不会中继事务。他们无法独立验证交易尚未包含在块中,并且仅支持UTXO,因此他们无法知道哪些交易有资格被包含在下一个块中。

行为不端的节点

请注意,对于这两种类型的广播,都有机制来惩罚通过发送虚假信息占用带宽和计算资源的行为不正确的对方。如果对等体在-banscore =< n>阈值以上获得一个banscore,他将被禁止由-bantime = n>,默认值为86,400(24小时)。

警报

在Bitcoin Core 0.13.0中删除。

Bitcoin Core的早期版本允许开发人员和值得信赖的社区成员发布比特币警报以通知用户关键的网络范围的问题。此消息系统已在Bitcoin Core v0.13.0中退役但是,内部警报,分区检测警告和-alertnotify选项功能仍然存在。

猜你喜欢

转载自blog.csdn.net/nuptuser/article/details/79078622
P2P
今日推荐