比特币P2P网络协议深度解析:从节点发现到区块广播

Posted by 链汇情报站 on September 9, 2025

比特币网络协议使得全节点(对等点)能够协作维护一个用于区块和交易交换的点对点网络。全节点在将区块和交易转发给其他节点之前,会下载并验证每一个区块和交易。归档节点是存储了整个区块链并能向其他节点提供历史区块的全节点。修剪节点则是不存储整个区块链的全节点。许多SPV(简化支付验证)客户端也使用比特币网络协议连接到全节点。

共识规则并不涵盖网络层面,因此比特币程序可以使用替代网络和协议,例如某些矿工使用的高速区块中继网络,以及某些提供SPV级别安全性的钱包使用的专用交易信息服务器。

节点发现机制

当程序首次启动时,并不知道任何活跃全节点的IP地址。为了发现这些IP地址,它们会查询内置于比特币核心客户端和BitcoinJ中的一个或多个DNS名称(称为DNS种子)。查询的响应应包含一个或多个DNS A记录,其中列出了可能接受新传入连接的全节点的IP地址。

DNS种子由比特币社区成员维护:一些提供动态DNS种子服务器,通过扫描网络自动获取活跃节点的IP地址;另一些则提供静态DNS种子,这些种子是手动更新的,更可能包含非活跃节点的IP地址。无论是哪种情况,节点只有在运行于比特币默认端口(主网为8333,测试网为18333)上时,才会被添加到DNS种子中。

需要注意的是,DNS种子的结果并未经过认证,恶意的种子操作员或网络中间人攻击者可以只返回由攻击者控制的节点的IP地址,从而将程序隔离在攻击者自己的网络中,并向其灌输伪造的交易和区块。因此,程序不应完全依赖DNS种子。

一旦程序连接到网络,其邻居节点就可以开始向它发送包含网络上其他对等点IP地址和端口号的addr(地址)消息,这提供了一种完全去中心化的节点发现方法。比特币核心客户端会在一个持久的磁盘数据库中保存已知节点的记录,这通常使得它在后续启动时能够直接连接到这些节点,而无需使用DNS种子。

然而,节点经常离开网络或更改IP地址,因此程序在启动时可能需要多次尝试不同的连接才能成功。这可能会显著增加连接到网络所需的时间,迫使用户在发送交易或检查支付状态前等待。

连接到对等节点

通过向远程节点发送“version”消息来建立连接,该消息包含你的版本号、区块和当前时间。远程节点会以其自身的“version”消息作为响应。随后,双方节点各发送一条“verack”消息给对方,以表明连接已建立。

连接一旦建立,客户端就可以向远程节点发送getaddr“addr”消息来收集更多的对等节点信息。

为了维持与对等节点的连接,节点默认会在30分钟不活动之前向对等节点发送一条消息。如果一个对等节点在90分钟内未收到任何消息,客户端将假定该连接已关闭。

初始区块下载

在全节点可以验证未确认交易和最新挖掘出的区块之前,它必须下载并验证从区块1(硬编码的创世区块之后的一个区块)到最佳区块链当前顶端的所有区块。这个过程被称为初始区块下载(IBD)或初始同步。

尽管“初始”一词暗示此方法仅使用一次,但它也可在任何需要下载大量区块的时候使用,例如当一个之前已同步的节点长时间离线后。此时,节点可以使用IBD方法来下载自其上次在线以来产生的所有区块。

区块优先模式

比特币核心客户端(直至0.9.3版本)使用一种简单的初始区块下载方法,我们称之为区块优先。其目标是按顺序下载最佳区块链中的区块。

该方法中,节点选择一个远程对等节点(称为同步节点)并向其发送“getblocks”消息。同步节点收到后,会回复一个包含最多500个区块库存的“inv”消息。IBD节点使用收到的库存,通过“getdata”消息向同步节点请求最多128个区块。

区块优先节点要求区块按顺序请求和发送至关重要,因为每个区块头都包含前一个区块头的哈希值。这意味着IBD节点只有在收到父区块后才能完全验证一个区块。因其父区块尚未收到而无法验证的区块被称为孤块

区块优先的优缺点

区块优先IBD的主要优势在于其简单性。主要缺点则是IBD节点依赖单个同步节点进行所有下载,这带来了几个问题:

  • 速度限制:所有请求都发往同一个同步节点,如果该节点的上传带宽有限,IBD节点的下载速度就会很慢。
  • 下载重启:同步节点可能向IBD节点发送一条非最佳(但其他方面有效)的区块链。IBD节点可能直到初始区块下载接近完成时才能识别出它是非最佳的,从而迫使IBD节点从另一个节点重新开始区块链下载。
  • 磁盘填充攻击:与下载重启紧密相关,如果同步节点发送一条非最佳(但其他方面有效)的区块链,该链会被存储在磁盘上,浪费空间并可能用无用数据填满磁盘驱动器。
  • 高内存使用:无论恶意还是无意,同步节点可能乱序发送区块,产生孤块,这些孤块在等待其父区块被接收和验证期间会存储在内存中,可能导致高内存使用率。

👉 探索更高效的区块同步策略

区块广播

当矿工发现一个新区块时,它会使用以下方法之一将新区块广播给其邻居节点:

  • 未经请求的区块推送:矿工直接向它的每个全节点邻居发送一条包含新区块的“block”消息
  • 标准区块中继:矿工作为标准中继节点,向它的所有邻居(包括全节点和SPV客户端)发送一条包含指向新区块的库存的“inv”消息。收到消息的节点会根据其类型(如区块优先节点、头部优先节点或SPV客户端)回复“getdata”“getheaders”等消息来请求完整区块或所需数据。
  • 直接头部公告:中继节点可以跳过“inv”消息getheaders的往返开销,而是立即发送一条包含新区块完整头部的“headers”消息。收到此消息的头部优先节点会部分验证区块头,并在头部有效时用“getdata”消息请求完整的区块内容。

全节点验证收到的区块后,会使用上述标准区块中继方法向其邻居节点广播该区块。

孤块处理

区块优先节点可能会下载到孤块——即这些块的前一个区块头哈希字段指向一个该节点尚未见过的区块。换句话说,孤块没有已知的父区块(与陈旧区块不同,陈旧区块有已知的父区块,但不属于最佳区块链)。

当区块优先节点下载到一个孤块时,它不会验证该区块。相反,它会向发送该孤块的节点发送一条“getblocks”消息;广播节点将回复一条“inv”消息,其中包含下载节点缺失的(最多500个)区块的库存;下载节点随后用“getdata”消息请求这些区块;广播节点则用“block”消息发送这些区块。下载节点会验证这些区块,一旦 former orphan block 的父区块被验证,它就会验证这个曾经的孤块。

头部优先节点通过总是在使用“getdata”消息请求区块之前,先使用“getheaders”消息请求区块头部,来避免部分复杂性。

交易广播与内存池

为了向对等节点发送交易,会发送一条“inv”消息。如果收到getdata响应消息,则使用tx消息发送交易。接收到此交易的对等节点如果验证其为有效交易,也会以同样的方式转发该交易。

内存池的作用

全节点可能会追踪有资格被包含在下一个区块中的未确认交易。这对于将要挖掘这些部分或全部交易的矿工来说至关重要,对于任何想要跟踪未确认交易的节点也很有用,例如为SPV客户端提供未确认交易信息的节点。

由于未确认交易在比特币中没有永久状态,比特币核心将它们存储在非持久性内存中,称之为内存池或mempool。当一个节点关闭时,其内存池会丢失,只有其钱包存储的交易除外。这意味着,从未被挖掘的未确认交易会随着节点重启或为了给其他交易腾出空间而清除某些交易,从而慢慢从网络中消失。

已被挖掘到区块中但该区块后来变成陈旧区块的交易,可能会被重新添加回内存池。这些重新添加的交易如果被替换区块包含,则可能几乎立即被再次移除。

常见问题

什么是比特币P2P网络?

比特币P2P网络是一个去中心化的对等网络,节点之间直接通信,共同维护区块链账本,并广播新的交易和区块,无需依赖中心服务器。

全节点和SPV客户端有什么区别?

全节点会下载并验证整个区块链上的每一个区块和交易,独立验证所有规则,具备最高的安全性和独立性。SPV客户端则只下载区块头,并依赖全节点来验证交易的存在性,牺牲部分独立性以换取更低的资源消耗。

初始区块下载(IBD)的主要挑战是什么?

IBD的主要挑战包括下载大量数据耗时较长、需要验证所有历史交易的工作量证明、可能从不可靠节点接收到无效或非主链的区块,以及对网络带宽和节点存储空间的较高要求。

孤块是如何产生的?

孤块通常是因为网络延迟,两个矿工几乎同时挖出新区块,导致区块链出现临时分叉。当其中一个区块成为更长链的一部分后,另一个区块就成了孤块(或陈旧区块),不再属于主链。

内存池有什么作用?

内存池是节点用来临时存储那些已经被网络接收但尚未被确认(即打包进区块)的有效交易的缓存区。矿工从内存池中选择交易来构建新的区块。