《精通比特币》解读 第八章 - 比特币网络

8.1 P2P网络架构
比特币采用了基于国际互联网(Internet)的P2P(peer-to-peer)网络架构。P2P是指位于同一网络中的每台计算机都彼此对等,各个节点共同提供网络服务,不存在任何“特殊”节点。每个网络节点在具有“扁平”拓扑结构的mesh network网络中相互连接。 在P2P网络中不存在任何服务端(server)、中央化的服务、以及层级结构。P2P网络的节点之间交互运作、协同处理,每个节点在对外提供服务的同时也使用网络中其他节点所提供的服务,这也是作为参与进网络的一种激励。P2P网络也因此天生具有网络弹性(resilient)、去中心化,以 及开放性。早期的国际互联网就是P2P网络架构的一个典型用例:IP网络中的各个节点完全平等。当今的互联网架构具有分层架构,但是IP协议仍然保留了扁平拓扑的本质。除了比特币,规模最大也最成功的P2P技术应用是在文件分享领域:Napster是该领域的先驱者,BitTorrent是其架构的最新演变。

比特币所采用的P2P网络架构不仅仅是选择拓扑结构这样简单。比特币被设计为一种点对点的数字现金系统,它的网络架构既是这种核心特性的反映,也是该特性的基石。去中心化控制是一个核心设计原则,它只能通过一种扁平化的、 去中心化的P2P共识网络来实现和维持。

术语“比特币网络”指的是按照比特币P2P协议运行的一系列节点的集合。除了比特币P2P协议之外,比特币网络中也包含其他协议,例如Stratum协议就被应用于挖矿、以及轻量级或移动端比特币钱包之中。这些额外的协议是由网关路由服务器(gateway routing servers)提供支持的,它使用比特币P2P协议接入比特币网络,并把网络拓展到运行其他协议的节点。例如,Stratum服务器通过Stratum协议将所有的Stratum挖矿节点连接至比特币主网络、并将Stratum协议桥接(bridge)至比特币P2P协议。我们使用“扩展比特币网络(extended bitcoin network)”指代所有包含比特币P2P协议、矿池挖矿协议、Stratum协议以及其他任何连接比特币系统组件的相关协议的整体网络。

8.2 节点类型及角色
尽管比特币P2P网络中的各个节点相互对等,但是根据所提供的功能不同,各节点可能具有不同的角色。一个比特币节点是路由、区块链数据库、挖矿、钱包服务的功能集合。一个具有全部四个功能的全节点(full node)如图8-1所示:
这里写图片描述
Figure 8-1. A bitcoin network node with all four functions: wallet, miner, full blockchain database, and network routing

所有节点都有路由功能,以参与到网络中,同时也可能包含其他功能。所有节点都参与验证并传播交易和区块,发现并维持与对等节点的连接。在图8-1所示的全节点例子中,路由功能用一个名为“网络路由节点”或带有字母‘N’ 的橙色圆圈表示。

一些节点,被称为全节点,它保持有一份完整的、最新的区块链拷贝。全节点能够独立自主地可信地校验任何交易,而不需要任何外部参照。另外还有一些节点只保持了区块链的一个子集,它们通过一种名为“简易支付验证(SPV)”的方法来验证交易。这样的节点被称为“SPV节点”或者“轻量级节点”。在如上图所示的全节点例子中,全节点区块链数据库功能用一个名为“Full Blockchain”或带有字母’B’的蓝色圆圈表示。在图8-3中,SPV节点没有画蓝色圆圈,表示它们没有区块链的完整拷贝。

挖矿节点通过运行在特殊硬件设备上,解决工作量证明(proof-of-work)算法问题,以相互竞争的方式创建新的区块。一些挖矿节点同时也是全节点,维持着一份区块链的完整拷贝,还有一些参与矿池挖矿的节点是轻量级节点,它们必须依赖矿池服务器维护的全节点进行工作。在全节点例子中,挖矿功能用一个名为“Miner” 或者带有字母“M”的黑色圆圈表示。

用户钱包也可以作为全节点的一部分,这在桌面比特币客户端中比较常见。当前,越来越多的用户钱包都是SPV节点, 尤其是运行于诸如智能手机等资源受限设备上的比特币钱包应用。在图8-1中,钱包功能用一个名为“Wallet”或带有字母“W”的绿色圆圈表示。

除了这些在比特币P2P协议上的主要节点类型之外,还有一些服务器和节点运行着其他协议,例如特殊矿池挖矿协议、轻量级客户端访问协议等。

图8-2描述了扩展比特币网络中最为常见的节点类型。
这里写图片描述
Figure 8-2. Different types of nodes on the extended bitcoin network

8.3 扩展比特币网络
运行着比特币P2P协议的比特币主网络由大约5000-8000个运行着各种不同版本比特币核心客户端(Bitcoin Core)的监听节点以及几百个运行着其他各种实现了比特币P2P协议的应用(例如Bitcoin Classic, Bitcoin Unlimited, BitcoinJ, Libbitcoin, btcd, and bcoin等)的节点组成。比特币P2P网络中的一小部分节点也是挖矿节点,它们竞争挖矿,验证交易,并创建新的区块。许多连接到比特币网络的大型公司运行着基于Bitcoin核心客户端的全节点客户端,它们具有区块链的完整拷贝及网络节点,但不具备挖矿及钱包功能。这些节点是网络中的边缘路由器(edge routers),它们允许各种其他服务,例如交易所、钱包、区块浏览器、商用支付处理(merchant payment processing)等,在其上面搭建。

如前文所述,扩展比特币网络既包括了运行比特币P2P协议的网络,又包括运行特殊协议的网络节点。连接到比特币P2P主网络的是许多矿池服务器以及连接着运行着其他协议的节点的协议网关。这些运行着其他协议的节点通常都是矿池挖矿节点(参见Chapter 10)以及轻量级钱包客户端,它们通常不保持区块链的完整备份。

图8-3描述了具有各种类型节点的扩展比特币网络,网关服务器、边缘路由器、钱包客户端以及它们相互连接所使用的各种协议。

这里写图片描述
Figure 8-3. The extended bitcoin network showing various node types, gateways, and protocols

8.4比特币中继网络(Bitcoin Relay Networks)
虽然比特币P2P网络服务于各种节点类型的普遍需求,但是对于比特币挖掘节点的专门需求,它显示出太高的网络延迟。

比特币矿工进行着(are engaged in )时间敏感的竞争,以解决Proof-of-Work难题,并扩展延伸区块链(参见Chapter 10)。在参加竞争时,比特币矿工必须尽可能地缩短获胜块的传播与下一轮比赛开始之间的时间。在采矿方面,网络延迟直接关系到利润率。

比特币中继网络是一种尝试最小化矿工之间传输区块的延迟的网络。最初的比特币中继网络(Bitcoin Relay Network)是由核心开发者Matt Corallo于2015年创建的,以便能够以非常低的延迟在矿工之间快速同步区块。该网络由世界各地的亚马逊Web服务基础架构上托管的几个专门的节点组成,并且服务于连接大多数矿工和矿池。

初始的比特币中继网络在2016年被 Fast Internet Bitcoin Relay Engine(FIBRE) 取代了,同样是由核心开发者Matt Corallo创建的。 FIBER是一种基于UDP的中继网络,可以在节点网络里中继区块。 FIBER实现了致密区块(compact block)的优化策略,以进一步减少传输的数据数量和网络延迟。

另一个中继网络(仍在提案阶段)是 Falcon,这是基于康奈尔大学的研究。 Falcon使用“直通路由(cut-through-routing)”而不是“存储转发(store-and-forward)”来减少延迟,即接收到区块的某些部分时就传播,而不是等待直到接收到一个完整的区块才传播。

中继网络不是比特币P2P网络的替代品。相反,它们是覆盖网络(overlay networks),在具有特殊需求的节点之间提供额外的连接。就像高速公路不是农村道路的替代品,而是交通繁忙的两点之间的捷径,您仍然需要小路连接到高速公路。

8.5 网络发现

当新的网络节点启动后,它必须发现网络中的其他比特币节点,以参与到其中。为了开始这个过程,新的节点必须发现网络中至少一个已存在的其它节点并与它建立连接,其它节点的地理位置是无关紧要的。由于比特币网络的拓扑结构并不基于节点间的地理位置,因此,在新节点建立连接时,可以随机选择网络中已存在的任何比特币节点。

节点通常建立一条TCP连接以连接到已知的对等节点,一般是使用对方的8333端口(该端口号通常是比特币所使用的)或者对方提供的其它替代端口。在建立连接时,节点会通过发送一个包含基本认证信息的version消息来开始“握手”通信过程(见图8-4)。基本认证信息包括:

nVersion
定义了客户端所“说”的比特币P2P协议的版本(例如:70002)。

nLocalServices
一组该节点支持的本地服务列表,当前仅支持NODE_NETWORK

nTime
当前时间

addrYou
当前节点所见到的远程节点的IP地址

addrMe
本地节点所发现的本机IP地址

subver
一个子版本号,表示当前节点运行的软件的类型(例如:/Satoshi:0.9.2.1/)

BaseHeight
当前节点的区块链的区块高度

(version网络消息的具体用例请参见GitHub

version消息始终是任何对等节点发送给另一个对等节点的第一条消息。 接收到version消息的本地对等节点将检查远程对等节点报告的nVersion,并确定远程对等节点是否兼容。 如果远程对等节点是兼容的,则本地对等节点将确认version消息,并通过发送一个verack应答消息来建立连接。

新节点如何找到对等节点? 第一种方法是使用若干“DNS种子”来查询DNS,这些“DNS种子”就是DNS服务器,他们提供比特币节点的IP地址列表。 其中一些DNS种子提供了稳定的比特币侦听节点的静态IP地址列表。 一些DNS种子是BIND(Berkeley Internet Name Daemon)的自定义实现,它从爬虫或长时间运行的比特币节点收集的比特币节点地址列表中返回一个随机子集。 Bitcoin Core客户端包含五个不同DNS种子的名称。不同DNS种子的所有权的多样性和不同DNS种子的实现的多样性为初始自举(bootstrapping)过程提供了高可靠性。 在Bitcoin Core客户端中,使用DNS种子的选择权由选择开关 -dnsseed 控制(默认设置为1,以使用DNS种子)。

或者,不知道网络任何信息的自举节点必须被给予至少一个比特币节点的IP地址,之后可以通过进一步引进来建立连接。 命令行参数 -seednode 可用于连接到一个节点,该节点仅作为一个种子用于引进其他节点。 在使用初始种子节点完成引进其他节点后,客户端将与初始种子节点断开连接并使用新发现的对等节点。
这里写图片描述
Figure 8-4. The initial handshake between peers

当建立一个或多个连接后,新节点将一条包含自身IP地址的addr消息发送给其相邻节点。相邻节点再将此条addr消息依次转发给它们各自的相邻节点,从而保证新节点被广为所知并保证连接更稳定。另外,新接入的节点可以向它的相邻节点发送getaddr消息,要求它们返回其已知对等节点的IP地址列表。通过这种方式,节点可以找到能连接到的对等节点,并向网络宣告它的存在,以便其他节点能够找到它。图8-5描述了这种地址发现协议( address discovery protoco)。
这里写图片描述
Figure 8-5. Address propagation and discovery

节点必须连接到若干不同的对等节点才能建立通向比特币网络的多样化的路径。由于节点可以随时加入和离开,通讯路径是不可靠的。因此,节点在失去已有连接时必须继续发现新节点,并在其他节点启动时为其提供帮助。节点启动时只需要一个连接,因为第一个节点可以将它引荐给自己的对等节点,而这些节点又会进一步提供引荐。一个节点连接到大量的其他对等节点是没有必要的,也是对网络资源的浪费。在启动完成后,节点会记住它最近的成功连接的对等节点,因此,当重新启动后它可以迅速与先前的对等节点网络重新建立连接。如果没有先前的对等节点对它的连接请求做出响应,该节点可以使用种子节点进行重启动。

在运行比特币核心客户端的节点上,您可以使用 getpeerinfo 命令列出对等节点连接信息:

$ bitcoin-cli getpeerinfo
[
{
"addr" : "85.213.199.39:8333",
"services" : "00000001",
"lastsend" : 1405634126,
"lastrecv" : 1405634127,
"bytessent" : 23487651,
"bytesrecv" : 138679099,
"conntime" : 1405021768,
"pingtime" : 0.00000000,
"version" : 70002,
"subver" : "/Satoshi:0.9.2.1/",
"inbound" : false,
"startingheight" : 310131,
"banscore" : 0,
"syncnode" : true
},
{
"addr" : "58.23.244.20:8333",
"services" : "00000001",
"lastsend" : 1405634127,
"lastrecv" : 1405634124,
"bytessent" : 4460918,
"bytesrecv" : 8903575,
"conntime" : 1405559628,
"pingtime" : 0.00000000,
"version" : 70001,
"subver" : "/Satoshi:0.8.6/",
"inbound" : false,
"startingheight" : 311074,
"banscore" : 0,
"syncnode" : false
}
]

用户可以通过提供 -connect=<IPAddress> 选项来指定一个或多个IP地址,从而覆盖节点的自动管理并指定IP地址列表。如果采用此选项,节点将只会连接到这些选定的IP地址,而不会自动发现并维护与对等节点的连接。

如果已建立的连接上没有数据通信,节点会定期发送信息以维持连接。如果节点在一条连接上超过90分钟都没有任何通信,它会被认为已经断开连接,并将寻找新的对等节点。因此,比特币网络会随时根据变化的节点(transient
nodes)及网络问题进行动态调整,可以根据需要有机地扩张和收缩而不需要中心化的控制。

8.6 全节点

全节点是指维持有包含全部交易信息的完整区块链的节点。更加准确地说,这样的节点应当被称为“full blockchain nodes”。在比特币发展的早期,所有节点都是全节点,当前的比特币核心客户端(Bitcoin Core client)也是完整区块链节点。但在过去的两年中出现了许多新型客户端,它们不需要维持完整的区块链,而是作为轻量级客户端运行。在下面的章节里我们会对这些轻量级客户端进行详细介绍。

完整区块链节点保持有完整的、最新的包含全部交易信息的比特币区块链拷贝,这样的节点可以独立地进行建立并校验区块链,从第一个区块(创世区块)一直建立到网络中的最新区块。完整区块链节点可以独立自主地并且可信地校验任何交易信息,而不需要其他资源或借助任何其他节点或其他信息来源。完整区块链节点通过比特币网络接收包含交易信息的新区块更新,在验证无误后将此更新合并至本地的区块链拷贝之中。

运行完整区块链节点可以给您一种纯粹的比特币体验:不需借助或信任其他系统即可独立地对所有交易信息进行验证。 辨别您是否在运行全节点是十分容易的,因为它需要超过20GB的永久性存储设备(如硬盘)用来存储完整区块链。如果您需要很大的磁盘空间,并且同步比特币网络耗时2至3天,那么您使用的正是全节点。这就是获得完全的独立并且摆脱中心化管理所要付出的代价。

尽管目前还有一些使用不同编程语言及软件架构实现的其他的完整区块链比特币客户端存在,但是最常用的仍然是标准客户端Bitcoin Core,它也被称为“Satoshi client”。比特币网络中超过75%的节点运行着各个版本的比特币核心客户端。它可以使用version消息中的子版本字符串中的”Satoshi”所识别,如前文所述,可以用getpeerinfo命令展现,例如,/Satoshi: 0.8.6/ 。

8.7 交换“库存清单”(Exchanging “Inventory”)
一个全节点连接到对等节点之后,第一件要做的事情就是构建完整的区块链。如果该节点是一个全新的(brand-new)节点,不包含任何区块链信息,它只知道一个区块,即创世区块,它是静态嵌入在客户端软件中的。新节点需要下载从0号区块(创世区块)开始的数十万区块的全部内容,才能跟网络同步,并重建全区块链。

同步区块链的过程从发送version消息开始,这是因为该消息中含有的BestHeight字段表示了节点当前的区块链高度(区块数量)。节点将查看从它的对等节点中得到的version消息,了解各个对等节点各自有多少区块,从而可以与其自身区块链所拥 有的区块数量进行比较。对等节点们会交换一个getblocks消息,其中包含他们本地区块链的顶端区块的哈希值(指纹)。如果某个对等节点识别出它接收到的哈希值是非顶端区块的,而是属于一个旧区块的,那么它就能推断出其自身的本地区块链比它的对等节点的区块链更长。

拥有更长区块链的对等节点比其他节点拥有更多的区块,可以识别出哪些区块是其他节点需要以便能“赶上”的。它会识别出第一批500个区块,通过使用inv(inventory)消息把这些区块的哈希值分享并传播出去。缺少这些区块的节点便可以通过发送一系列的getdata消息来请求全区块数据,并用inv消息中的哈希值来确认请求到的区块,从而获取这些缺失的区块。

在下例中,我们假设某节点只含有创世区块。它收到了来自对等节点的inv消息,其中包含了区块链中接下去500个区块的哈希值。于是它开始向所有与之相连的对等节点请求区块,并通过分摊负载的方式防止单一对等节点被批量请求所压垮。该节点会追踪记录其每个对等节点连接上的“正在传输”(指那些它已经发出了请求但还没有接收到)的区块数量,并且检查该数量没有超过上限( MAX_BLOCKS_IN_TRANSIT_PER_PEER )。用这种办法,如果一个节点需要更新大量区块,它会在上一请求完成后才发送对新区块的请求,从而允许对等节点控制更新速度,不至于压垮网络。每一个区块在被接收后就会被添加至区块链中,这一过程详见Chapter 9。随着本地区块链的逐步建立,越来越多的区块被请求和接收,整个过程将一直持续到该节点追赶上网络中所有剩余的区块为止。

每当一个节点离线,不管离线时间有多长,这个与对等节点比较本地区块链并恢复任何缺失区块的过程就会发生。节点无论是只离线几分钟导致缺失了几个区块,或者离线长达一个月导致缺失了上千个区块,它启动时,都会发送 getblocks 消息,收到一个inv响应,接着开始下载缺失的区块。库存和区块传播协议如图8-6所示。
这里写图片描述
Figure 8-6. Node synchronizing the blockchain by retrieving blocks from a peer

8.8 简易支付验证 (Simplified Payment Verification (SPV) )节点

并非所有的节点都有能力储存完整的区块链。许多比特币客户端被设计成运行在空间和功率受限的设备上,如智能电话、平板电脑、嵌入式系统等。对于这样的设备,通过简单支付验证(SPV)的方式可以使它们在不存储完整区块链的情况下进行工作。这种类型的客端被称为SPV客户端或轻量级客户端。随着比特币的使用热潮,SPV节点逐渐变成最常见形式的比特币节点,尤其是比特币钱包。

SPV节点只需下载区块头,而不用下载包含在每个区块中的交易信息。由此产生的不含交易信息的区块链,大小只有完整区块链的1/1000。SPV节点不能构建所有可用于消费的UTXO的全貌,这是由于它们并不知道网络上所有交易数据。SPV节点验证交易时所使用的方法略有不同,该方法需依赖对等节点按需提供区块链相关部分的局部视图。

打个比方来说,全节点就像是一个在陌生城市里的游客,他带着一张包含每条街道、每个地址的详细地图。相比之下,SPV节点就像是陌生城市里的这名游客只知道一条主干道的名字,通过随机询问该城市的陌生人来获取建议路线规划指示。虽然两种游客都可以通过实地访问来验证一条街是否存在,但没有地图的游客不知道每个小巷中有哪些街道,也不知道附近还有什么其他街道。没有地图的游客站在“教堂街23号”前面时,并不知道这个城市里是否还有其他若干“教堂街 23号”地址,也不知道面前的这个是否就是要找的那个。对于没有地图的游客来说,最好的选择就是向足够多的人问路,并且希望其中一部分人不要试图抢劫他。

简易支付验证是通过参考交易在区块链中的深度,而不是高度,来验证它们。一个拥有完整区块链的节点会构造一条验证链,这条链是由沿着区块链按时间倒序一直追溯到创世区块的数千区块及交易组成,而一个SPV节点会验证所有区块构成的链(但不是所有的交易),并把该链和感兴趣的交易连接起来。

例如,一个全节点要检查第300,000号区块中的某个交易,它会把从该区块开始一直回溯到创世区块的300,000个区块全部都链接起来,并建立一个完整的UTXO数据库,通过确认该UTXO是否还未被花费来证实交易的有效性。SPV节点则不能验证UTXO是否还未被花费。相反地,SPV节点会在该交易和它所在区块之间用merkle路径(见“ Merkle 树”章节)建立一条链接。然后SPV节点一直等待,直到序号从300,001到300,006的六个区块堆叠在该交易所在的区块之上,并通过确立交易的深度是在第300,006区块~第300,001区块来验证交易的有效性。事实上,如果网络中的其他节点都接受了第300,000区块,并通过足够的工作在该块之上又生成了六个区块,就可以证明该交易不是双重支付。

如果一个交易实际上不存在,SPV节点不会误认为该交易存在于某区块中。SPV节点会通过请求merkle路径证明以及验证区块链中的工作量证明,来证实交易的存在性。可是,一个交易的存在是可能对SPV节点“隐藏”的。SPV节点可以明确证实某个交易确实存在,但它不能验证某个交易(比如同一个UTXO的双重支付)不存在,这是因为SPV节点没有一个所有交易的记录。这个漏洞会被针对SPV节点的拒绝服务攻击或双重花费攻击所利用。为了防御这些攻击,SPV节点需要随机连接到多个节点,以增加至少与一个可靠节点相连接的概率。这种随机连接的需求意味着SPV节点也容易受到网络分区攻击或女巫攻击(Sybil attacks),即SPV节点被连接到虚假节点或虚假网络中,没有连接到可靠节点或真正的比特币网络。

在绝大多数的实际情况中,具有良好连接的SPV节点是足够安全的,它在资源需求、实用性和安全性之间维持恰当的平衡。当然,如果要保证绝对的安全性,没有什么比运行完整区块链的节点更安全。

提示
完整的区块链节点是通过检查整个链中在该交易之下的数千个区块来保证这个UTXO没有被支付,从而验证交易。而 SPV节点是通过检查在其上面的数个区块将它压在下面的深度来验证交易。

SPV节点使用的是一条getheaders消息,而不是getblocks消息来获得区块头。响应的对等节点将用一条 headers 消息发送多达2000个区块头。这一过程和全节点获取所有区块的过程没什么区别。SPV节点还在与对等节点的连接上设置了过滤器,用以过滤从对等节点发来的未来区块和交易数据流。任何感兴趣的交易都是通过一条getdata的请求来读取的。对等节点生成一条包含交易信息的tx消息作为响应。区块头的同步过程如图8-7所示。

由于SPV节点需要读取特定交易从而选择性地验证它们,这样就产生了隐私风险。与全区块链节点收集每一个区块内的全部交易所不同的是,SPV节点对特定数据的请求可能无意中透露了钱包里的地址信息。例如,监控网络的第三方可以跟踪某个SPV节点上的钱包所请求的全部交易信息,并且利用这些交易信息把比特币地址和钱包的用户关联起来,从而损害了用户的隐私。

这里写图片描述
Figure 8-7. SPV node synchronizing the block headers

在引入SPV节点/轻量级节点后不久,比特币开发人员就添加了一个新功能叫做Bloom过滤器,用以解决SPV节点的隐私风险问题。Bloom过滤器通过一个采用概率而不是固定模式的过滤机制,允许SPV节点只接收交易的子集,这样不会精确泄露哪些地址是它们感兴趣的。

8.9 Bloom过滤器
Bloom过滤器是一个允许用户描述期望的模式而不必精确表述的基于概率的搜索过滤器。Bloom过滤器提供了一种既能表达搜索模式同时也能保护隐私的有效的方法。这一方法被用在SPV节点中向对等节点请求符合指定模式的交易,同时交易地址不会被暴露。

用我们之前的例子,一位手中没有地图的游客需要询问去特定地方的路线。如果他向陌生人询问“教堂街23号在哪里”, 不经意之间,他就暴露了自己的目的地。Bloom过滤器则会这样问,附近有带‘堂’字的街道吗?”这样的问法包含了比之前略少的关键词。这位游客可以自己选择包含信息的多少,比如“以‘堂街’结尾”或者“‘教’字开头的街道”。如果他问得越少,得到了更多可能的地址,隐私得到了保护,但这些地址里面不乏无关的结果;如果他问得非常具体,他在得到较准 确的结果的同时也暴露了自己的隐私。

Bloom过滤器可以让SPV节点指定交易的搜索模式,该搜索模式可以基于准确性或私密性的考虑被调节。一个非常具体 的Bloom过滤器会生成更准确的结果,但也会显示该用户钱包里的使用的地址;反之,如果过滤器只包含简单的关键 词,更多相应的交易会被搜索出来,在包含若干无关交易的同时有着更高的私密性。

阅读更多

更多精彩内容