来源:https://github.com/tianmingyun/MasterBitcoin2CN/blob/master/ch08.md
第8章 比特币网络
8.1 P2P网络架构
比特币采用了基于互联网(Internet)的点对点(peer-to-peer)网络架构。点对点,或者叫P2P是指位于同一网络中的每台计算机都彼此平等,身份对等,各个节点共同提供网络服务,不存在任何“特殊”节点。网络节点以“扁平”的拓扑结构相互连通。 网络中不存在任何服务器端、中央化的服务、以及层级结构。点对点网络中的节点同时提供和消费服务,互惠互利。点对点网络也因此具有可靠性、去中心化,以及开放性。早期的互联网就是点对点网络架构的一个典型用例:IP网络中的各个节点完全平等。当今的互联网架构具有分层架构,但是IP协议仍然保留了扁平拓扑的结构。除了比特币,规模最大也最成功的点对点技术应用是在文件分享领域,Napster是该领域的先锋,BitTorrent是其架构的最新演变。
比特币所采用的点对点网络架构不仅仅是拓扑结构选择。比特币被设计为一种点对点的数字现金系统,它的网络架构既是这种核心特性的反映,也是该特性的基石。去中心化控制是核心设计原则,它只能通过维持一种扁平化、 去中心化的点对点共识网络来实现和维护。
“比特币网络”是运行比特币点对点协议的一系列节点的集合。除了比特币点对点协议之外,比特币网络中也包含其他协议,例如Stratum协议就被应用于挖矿、以及轻量级或移动端比特币钱包。网关路由服务器提供这些协议,使用比特币点对点协议接入比特币网络,并把网络拓展到运行其他协议的各个节点。例如,Stratum服务器通过 Stratum协议将所有的Stratum挖矿节点连接至比特币主网络、并将Stratum协议桥接至比特币点对点协议。我们使用“扩展比特币网络(extended bitcoin network)”指代所有包含比特币点对点协议、矿池挖矿协议、Stratum 协议以及其他连接比特币系统组件相关协议的整体网络结构。
8.2 节点类型及角色
尽管比特币点对点网络中的各个节点地位对等,但是根据所提供的功能不同,各节点可能具有不同的角色。比特币节点是路由、区块链数据库、挖矿、钱包服务这些功能的集合。全节点(full node)包括如图8-1所示的四个功能:
图8-1 具有所有四个功能:钱包,矿工,完整的区块链数据库和网络路由的比特币网络节点
所有节点都包含参与网络的路由功能,并且还可能包含其他功能。每个节点都参与验证并传播交易及区块信息,发现并维持与对等节点的连接。在图8-1中,路由功能由名为“网络路由节点”的圆圈或字母“N”表示。
一些节点保有一份完整的、最新的区块链副本,这样的节点被称为“全节点”。全节点能够自主权威验证所有交易,而不需借由任何外部参照。另外还有一些节点只维护了区块链的子集,并使用一种名为*“简易支付验证simplified payment verification,SPV”*的方式来验证交易,这样的节点被称为“SPV节点”,又叫“轻量级节点”。在上图的全节点示例中,全节点区块链数据库功能由一个称为“完整区块链”的圆圈或字母“B”表示。在图8-3中,SPV节点没有“B”圆圈,以示它们没有区块链的完整副本。
挖矿节点通过运行在专用硬件设备上的工作量证明(proof-of-work)算法,相互竞争创建新的区块。一些挖矿节点同时也是全节点,保有区块链的完整副本;还有一些参与矿池挖矿的节点是轻量级节点,它们必须依赖矿池服务器维护的全节点进行工作。上图中,挖矿功能如图中名为“矿工”的圆圈,用字母“M”表示。
用户钱包也可以作为全节点的一部分,这在比特币PC客户端中比较常见。越来越多的用户钱包,特别是那些运行在智能手机等资源受限设备上的用户钱包,都是SPV节点。在图8-1中,名为“钱包”的圆圈字母“W”代表钱包功能。
在比特币P2P协议中,除了这些主要的节点类型之外,还有一些服务器及节点也在运行着其他协议,例如专用矿池挖矿协议、轻量级客户端访问协议等。
图8-2描述了扩展比特币网络中最为常见的节点类型。
8.3 扩展比特币网络
运行比特币协议的比特币主网由大约5000-8000个运行着不同版本比特币参考客户端(Bitcoin Core)的监听节点、以及几百个运行着各类比特币点对点协议的其他实现例如Bitcoin Classic, Bitcoin Unlimited, BitcoinJ, Libbitcoin, btcd, and bcoin等的节点组成。比特币点对点网络中的一小部分节点也是挖矿节点,它们竞争挖矿、验证交易、并创建新的区块。许多连接到比特币网络的大型公司运行着基于Bitcoin Core客户端的全节点客户端,它们具有区块链的完整副本及网络节点,但不具备挖矿及钱包功能。这些节点是网络中的边缘路由器,允许在上面构建各种其他服务,例如交易所、钱包、区块浏览器、商业支付处理等。
如前文所述,扩展比特币网络既包括了运行比特币点对点协议的网络,又包含运行专用协议的网络节点。连接到比特币点对点主网上,还有许多矿池服务器以及协议网关,它们把运行其他协议的节点连接起来。这些节点通常都是矿池挖矿节点(参见【第八章 挖矿】)以及轻量级钱包客户端,后者通常不具备区块链的完整备份。
图8-3描述了扩展比特币网络中多种类型的节点、网关服务器、边缘路由器、钱包客户端以及它们相互连接所需的各类协议。
图8-3 显示各种节点类型,网关和协议的扩展比特币网络
8.4 比特币传输网络
虽然比特币点对点网络服务于各种各样类型节点的一般需求,但是对于比特币挖矿节点的特殊需求,它的网络延迟就显得太高了。
比特币矿工参与的是时效性很强的竞争,以解决工作证明问题,延长区块链((参见【第八章 挖矿】)。在参与这项竞争的同时,比特币矿工必须尽可能缩短从传播一个获胜区块到开始下一轮竞争之间的时间。挖矿中,网络延迟与利润率直接相关。
比特币传输网络是一种尝试最小化矿工之间区块传输延迟的网络。最初的比特币传输网络Bitcoin Relay Network 是由核心开发人员Matt Corallo在2015年创建的,目的是在极低延迟的矿工之间实现区块的快速同步。该网络由几个专门的节点组成,这些节点托管在世界各地的Amazon Web Services基础设施上,用于连接大多数矿工和采矿池。
这个最初的比特币传输网络在2016年被取代,同样是Matt Corallo创建的Fast Internet Bitcoin Relay Engine 或者 FIBRE。 FIBRE是一种基于UDP的传输网络,可以节点网络内传输区块。 FIBRE实现了压缩区块优化,以进一步减少数据传输量和网络延迟。
康奈尔大学研究的另一个传输网络(仍在提案阶段)是 Falcon。 Falcon使用“直通路由”而不是“存储转发”来减少延迟,方法是在接收到区块时就开始部分传输,而不是等到接收到完整的区块。
传输网络不是替代比特币的点对点网络。相反,它们是重叠网络,在具有特殊需求的节点之间提供额外的连接,就像高速公路不能替代乡村道路,而是交通繁忙的两点之间的捷径,仍然需要通过小路连接高速公路。
8.5 网络发现
当新的网络节点启动后,它必须发现网络中的其他比特币节点才能参与。要启动这个过程,新节点必须在网络上发现至少一个现有节点并连接到该节点。其他节点的地理位置在哪儿都没有关系,比特币网络拓扑结构没有地理位置的定义。因此,可以随机选择任何现有的比特币节点。
为了连接到一个已知的对等点,节点会建立一个TCP连接,通常连接到端口8333(通常被称为比特币使用的端口),或者如果指定了另一个端口,则连接到另一个端口。在建立连接时,该节点会通过发送一条包含基本认证内容的version消息开始“握手”通信过程(见图8-4)。这一过程包括如下内容:
▷ nVersion
定义了客户端所“说”的比特币点对点协议所采用的版本(例如:70002)。
▷ nLocalServices
一组该节点支持的本地服务列表,当前仅支持NODE_NETWORK
▷ nTime
当前时间
▷ addrYou
从该节点看到的远程节点的IP地址
▷ addrMe
本地节点所发现的本机IP地址
▷ subver
指示当前节点运行的软件类型的子版本号(例如:”/Satoshi:0.9.2.1/”)
▷ BestHeight
当前节点区块链的区块高度
(version网络消息的例子请参见GitHub )
版本消息始终是任何对等节点发送给另一个节点的第一条消息。 接收版本消息的本地对等节点将检查远端节点报告的nVersion,并确定远端对等节点是否兼容。 如果远程对等节点兼容,则本地节点将确认版本消息,并通过发送一个verack建立连接。
新节点如何找到对等节点? 第一种方法是使用一些“DNS种子”来查询DNS,DNS种子就是提供比特币节点IP地址列表的DNS服务器。 其中一些DNS种子提供了稳定的比特币侦听节点的IP地址静态列表。 一些DNS种子是BIND(Berkeley Internet Name Daemon)的自定义实现,它从爬虫程序或长时间运行的比特币节点收集的比特币节点地址列表中返回一个随机子集。 Bitcoin Core客户端包含五种不同DNS种子的名称。 不同DNS种子的所有权的多样性和实现的多样性为初始引导过程提供了高水平的可靠性。 在Bitcoin Core客户端中,DNS种子选项是否启用由选项开关 -dnsseed控制(默认设置为1,使用DNS种子)。
或者,一个刚刚启动的节点,对网络一无所知,必须被赋予至少一个比特币节点的IP地址,之后才可以通过进一步的引见建立连接。 命令行参数-seednode可用于连接到一个节点,仅用于将其用作引见种子。 在使用初始种子节点完成引见后,客户端将断开连接并使用新发现的对等节点。
图8-4 对等节点之间的初始握手
一旦建立一个或多个连接后,新节点发送一条包含自身IP地址的addr消息给其相邻节点。相邻节点再将此条addr消息依次转发给它们各自的相邻节点,从而保证新节点被其他节点知道、更好连接。另外,新接入的节点可以向它的相邻节点发送getaddr消息,要求它们返回其已知对等节点的IP地址列表。通过这种方式,节点可以找到需连接到的对等节点,并向网络发布它的消息以便其他节点查找。图8-5描述了这种地址发现协议。
图8-5 地址传播和发现
一个节点必须连接到几个不同的对等节点,以便建立进入比特币网络的不同路径。路径不是持久性的,节点可以随时加入或者离开,因此节点必须在失去旧连接时继续发现新节点,并在其他节点启动时提供帮助。节点启动时只需要一个连接,因为第一个节点可以将它引见给它的对等节点,而这些节点又会进一步提供引见。一个节点,如果连接到大量的其他对等节点,这既没必要,也是对网络资源的浪费。在启动完成后,节点会记住它最近成功连接的对等节点,以便当重新启动后它可以迅速与先前的对等节点网络重新建立连接。如果先前的网络的对等节点对连接请求无应答,该节点可以使用种子节点重新启动。
在运行Bitcoin Core客户端的节点上,您可以使用 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=<IP地址> 选项来指定一个或多个IP地址,从而实现手动指定IP地址列表,取消自动节点管理功能。如果采用此选项,节点只连接到这些指定的节点IP地址,而不会自动发现并维护对等节点之间的连接。
如果已建立的连接没有数据通信,所在的节点会定期发送信息以维持连接。如果节点的某个连接长达90分钟没有任何通信,它会认为自己已经从网络断开,开始查找一个新的对等节点。因此,网络可以动态地调整节点变化和网络问题,并且可以根据需要在没有任何中央控制的情况下进行有机的增长和收缩。
8.6 全节点
全节点是指维持包含全部交易信息的完整区块链的节点。更加准确地说,这样的节点应当被称为“完整区块链节点”。在比特币发展的早期,所有节点都是全节点;当前的 Bitcoin Core 客户端也是完整区块链节点。但在过去的两年中出现了许多新型客户端,它们不需要维持完整的区块链,而是作为轻量级客户端运行。在下面的章节里我们会对这些轻量级客户端进行详细介绍。
完整区块链节点维护完整的、最新的包含全部交易信息的比特币区块链副本,这样的节点可以独立地进行建立并校验区块链,从第一区块(创世区块)一直建立到网络中最新的区块。完整区块链节点可以独立权威验证任何交易,而不需要借助任何其他节点或其他信息来源。完整区块节点通过比特币网络获取包含交易信息的新区块更新,在验证无误后将此更新合并至本地的区块链副本中。
运行完整区块链节点可以给您一种纯粹的比特币体验:不需借助或信任其他系统即可独立地对所有交易信息进行验证。 很容易判断是否正在运行完整节点,因为它需要超过100 GB的持久存储(磁盘空间)来存储完整的区块链。如果您需要很大的磁盘空间、并且同步比特币网络耗时2至3天,那么您运行的正是全节点。这就是摆脱中心化权威、获得完全的独立自由所要付出的代价。
尽管目前还有一些使用不同编程语言及软件架构构建的其他的完整区块链客户端存在,但是最常用的仍然是Bitcoin Core客户端,它也被称为“Satoshi客户端”。比特币网络中超过75%的节点运行着不同版本的Bitcoin Core客户端。它在版本消息中发送的子版本字符串中被标识为“Satoshi”,就像前面看到的getpeerinfo命令显示的,例如/Satoshi:0.8.6/。
8.7 交换“库存”
一个全节点连接到对等节点之后,首先做的就是尝试构建完整的区块链。如果该节点是一个全新节点,它就不包含任何区块链信息,它只知道一个区块——在客户端软件中静态内置的创世区块。新节点需要从0号区块(创世区块)开始下载数十万区块的全部内容,跟网络同步、并重建完整区块链。
同步区块链的过程从发送version消息开始,这是因为该消息中含有的BestHeight字段包含了一个节点当前的区块链高度(区块数量)。节点可以从它的对等节点中得到版本消息,了解双方各自有多少区块,从而可以与其自身区块链所拥有的区块数量进行比较。对等节点们会交换一个getblocks消息,其中包含他们本地区块链的顶端区块哈希值(指纹)。如果某个对等节点识别出它接收到的哈希值并不属于顶端区块,而是属于一个非顶端区块的旧区块,那么它就能推断出:其自身的本地区块链比其他对等节点的区块链更长。
拥有更长区块链的对等节点比其他节点有更多的区块,可以识别出哪些区块们是其他节点需要“补充”的。它会识别出第一批可供分享的500个区块,通过inv(inventory)消息把这些区块的哈希值传播出去。缺少这些区块的节点便可以通过各自发送的getdata消息来请求得到完整区块信息,用包含在inv消息中的哈希值来识别被请求的区块,检索缺失的区块。
在下例中,我们假设某节点只含有创世区块。它收到了来自对等节点的inv消息,其中包含了区块链中后500个区块的哈希值。于是它开始向所有与之相连的对等节点请求区块,并通过分摊工作量的方式防止单一对等节点被批量请求所压垮。该节点会追踪记录其每个对等节点连接上“正在传输”(指那些它已经发出了请求但还没有接收到)的区块数量,并且检查该数量有没有超过上限( MAX_BLOCKS_IN_TRANSIT_PER_PEER )。用这种办法,如果一个节点需要更新大量区块,它会在上一请求完成后才发送对新区块的请求,从而允许对等节点控制更新速度,不至于压垮网络。每一个区块在被接收后就会被添加至区块链中,这一过程详见【第九章区块链】。随着本地区块链的逐步增加,越来越多的区块被请求和接收,整个过程将一直持续到该节点与全网络完成同步为止。
任何时候节点如果离线,都会将本地区块链与对等节点进行比较,并检索任何丢失的区块。如果一个节点只离线几分钟,可能只会缺失几个区块;当它离线长达一个月,可能会缺失上千个区块。但无论哪种情况,它都会从发送 getblocks 消息开始,收到一个inv响应,接着开始下载缺失的区块。库存和区块传播协议如图8-6所示。
图8-6节点通过检索来自对等节点的区块同步区块链
8.8 简易支付验证 (Simplified Payment Verification (SPV) )节点
并非所有的节点都有能力存储完整的区块链。许多比特币客户端被设计成运行在空间和功耗受限的设备上,如智能手机、平板电脑、嵌入式系统等。对于这样的设备,通过简化的支付验证SPV,simplified payment verification的方式可以使它们在不必存储完整区块链的情况下进行工作。这种类型的客端被称为SPV客户端或轻量级客户端。随着比特币的普及,SPV节点逐渐变成比特币节点(尤其是比特币钱包)所采用的最常见的形式。
SPV节点只需下载区块头,而不用下载包含在每个区块中的交易信息。由此产生的不含交易信息的区块链,大小只有完整区块链的1/1000。SPV节点不能构建所有可用于消费的UTXO的全貌,这是由于它们并不知道网络上所有交易的完整信息。SPV节点验证交易时所使用的方法略有不同,这个方法需依赖对等节点“按需”提供区块链相关部分的局部视图。
打个比方来说,每个全节点就像是一个在陌生城市里的游客,他带着一张包含每条街道、每个地址的详细地图。相比之下,SPV节点更像是只知道这座陌生城市一条主街道名字的游客,只能随机询问陌生人转弯方向。虽然两种游客都可以通过实地考察来验证一条街是否存在,但没有地图的游客不知道那个街道位于哪里,也不知道附近还有什么其他街道。没有地图的游客在“教堂街23号”的前面,并不知道这个城市里是否还有其他“教堂街23号”,也不知道面前的这个是否是要找的那个。对他来说,最好的方式就是向足够多的人问路,并且希望不会有人抢劫他。
简易支付验证是通过参考交易在区块链中的深度,而不是高度,来验证它们。一个拥有完整区块链的节点会构造一条验证链,这条链是由沿着区块链按时间倒序一直追溯到创世区块的数千区块及交易组成。而一个SPV节点会验证所有区块的链(但不是所有的交易),并且把区块链和与自己有关的交易链接起来。
例如,一个全节点要检查第300,000号区块中的某个交易,它会把从该区块开始一直回溯到创世区块的300,000个区块全部都链接起来,并建立一个完整的UTXO数据库,通过确认该UTXO是否还未被支付来证实交易的有效性。SPV节点则不能验证UTXO是否还未被支付。相反地,SPV节点会在该交易信息和它所在区块之间用merkle路径(见【9.7 Merkle 树】)建立一条链接。然后SPV节点一直等待,直到序号从300,001到300,006的六个区块堆叠在该交易所在的区块之上,并通过确立交易的深度是在第300,006区块~第300,001区块之下来验证交易的有效性。事实上,如果网络中的其他节点都接受了第300,000区块,并通过足够的工作在该块之上又生成了6个区块,就是代理其证明该交易不是双重支付。
如果一个交易实际上不存在,SPV节点不会误认为该交易存在于某区块中。SPV节点会通过请求merkle路径证明以及验证区块链中的工作量证明,来证实交易的存在性。可是,一个交易的存在是可能对SPV节点“隐藏”的。SPV节点毫无疑问可以证实某个交易的存在性,但它不能验证该交易不存在(譬如同一个UTXO的双重支付),这是因为SPV节点没有一份关于所有交易的记录。这个漏洞会被针对SPV节点的拒绝服务攻击或双重支付攻击所利用。为了防御这些攻击,SPV节点需要随机连接到多个节点,以增加至少连接一个可靠节点的概率。这种随机连接的需求意味着SPV节点也容易受到网络分区攻击或Sybil攻击,这种情况更多是,SPV节点被连接到虚假节点或虚假网络中,没有连接到可靠节点或真正的比特币网络。
在绝大多数的实际情况中,具有良好连接的SPV节点是足够安全的,它在资源需求、实用性和安全性之间维持恰当的平衡。当然,如果要保证万无一失的安全性,最可靠的方法还是运行完整区块链的节点。
提示 完整的区块链节点验证交易的时候是通过检查整个链中在该交易之下的数千个区块来保证这个UTXO没有被支付。而 SPV节点是通过检查在其上面的少数区块将它压在下面的深度来验证交易。
SPV节点使用的是一条getheaders消息,而不是getblocks消息来获得区块头。响应的对等节点将用一条headers 消息发送多达2000个区块头。这一过程和全节点获取所有区块的过程没什么区别。SPV节点还在与对等节点的连接上设置了过滤器,用以过滤从对等节点发来的未来区块和交易数据流。任何有关的交易都是通过一条getdata的请求来读取的。对等节点生成一条包含交易信息的tx消息作为响应。区块头的同步过程如图8-7所示。
图8-7 SPV节点同步区块头
由于SPV节点需要读取特定交易从而选择性地验证交易,这样就又产生了隐私风险。与全区块链节点收集每一个区块内的全部交易所不同的是,SPV节点对特定数据的请求可能无意中透露了钱包里的地址信息。例如,监控网络的第三方可以跟踪某个SPV节点上的钱包所请求的全部交易信息,并且利用这些交易信息把比特币地址和钱包的用户关联起来,从而损害了用户的隐私。
在引入SPV轻量级节点后不久,比特币开发人员添加了一个名为布隆过滤器bloom filters的功能来解决SPV节点的隐私风险。 Bloom过滤器允许SPV节点通过使用概率而不是固定模式的过滤机制接收交易的一个子集,无需精确地泄露他们感兴趣的地址。
8.9 布隆过滤器
布隆过滤器是一种基于概率的过滤方法,允许用户无需精确指定来描述特定模式。它给用户提供了一种有效的方式表示搜索模式同时又保护他们的隐私。在SPV节点里,这一方法被用来向对等节点请求发送匹配特定模式的交易,同时又不会泄露地址,密钥和搜索的交易。
用我们之前的例子,一位手中没有地图的游客需要询问去特定“地方教堂街23号”的路线。如果她向陌生人问到这条街, 不经意之间,就暴露了自己的目的地。布隆过滤器则会这样问,“附近有带‘堂’字的街道吗?”这样的问法会较少泄露目的地的信息。这种方法,这位游客可以仔细指定自己的地址,比如“以‘堂街’结尾”或者“‘教’字开头的街道”。通过改变搜索的精度,她可以或多或少地透露信息,代价是获得更多或更少的具体结果。如果她问的不太具体,会得到更多可能的地址和更好的隐私,但很多结果是无关的。如果她要求的非常具体,她得到的结果较少,但会失去隐私。
布隆过滤器可以让SPV节点指定交易的搜索模式,可以基于准确性或私密性进行调节。一个非常具体的布隆过滤器会生成更准确的结果,但代价是透露SPV节点感兴趣的模式,从而揭示用户钱包拥有的地址。反之,如果过滤器只包含简单的关键词,将会产生更多交易的更多数据,其中许多与节点无关,但节点会保持更好的隐私。
8.9.1 布隆过滤器如何工作
布隆过滤器通过一个由N个二进制数字(bit字段)的可变长度数组以及可变数量的M个哈希函数实现的。这些哈希函数的输出值始终在1和N之间,该数值与二进制位数组相对应。并且该函数为确定性函数,也就是说任何一个使用相同布隆过滤器的节点通过该函数都能对特定输入得到同一个的结果。布隆过滤器的准确性和私密性能通过改变长度(N)和哈希函数的数量(M)来调节。
在图8-8中,我们用一个小型的16位数组和3个哈希函数来演示布隆过滤器的应用原理。
图8-8 一个简单的布隆过滤器的例子,16位的字段和3个哈希函数
布隆过滤器数组里的每一个位的初始值都为零。要将模式添加到布隆过滤器,每个哈希函数都要依次哈希这个模式。该输入经第一个哈希函数运算后得到了一个在1和N之间的数,它在该数组(编号依次为1至N)中所对应的位被置为1,这样把哈希函数的输出记录下来。接着再进行下一个哈希函数的运算,把另外一位置为1;以此类推。当全部M个哈希函数都运算过之后,一共有M个位的值从0变成了1,这个搜索模式也被“记录”在了布隆过滤器里。
图8-9显示了向图8-8里的简易Bloom过滤器添加模式“A”。
图8-9 向简易布隆过滤器添加模式“A”
增加第二个模式就是简单地重复之前的步骤。模式依次通过各哈希函数运算之后,相应的位改变为1,布隆过滤器记录下该模式。需要注意的是,当布隆过滤器里添加了更多的模式时,对应的某个哈希函数的输出值的位可能已经是1了,这种情况下,该位不会再次改变。也就是说,随着更多的模式指向了重复的位,布隆过滤器随着位1的增加而饱和,准确性也就开始降低了。该过滤器之所以是基于概率的数据结构,就是因为模式的增加会导致准确性的降低。 准确性取决于模式的数量以及数组大小(N)和哈希函数的多少(M)。更多位的数组和更多的哈希函数会记录更多的模式以提高准确性。而小的数组及有限的哈希函数只能记录有限的模式从而降低准确性。
图8-10显示了向该简易布隆过滤器里增加第二个模式“B”。
图8-10 向该简易布隆过滤器里增加第二个模式“B”
为测试某一模式是否被记录在某个布隆过滤器中,我们将该模式逐一代入各哈希函数中运算,并将所得的结果与原数组进行对比。如果所有的结果对应的位都变为了1,则表示这个模式有可能已被该过滤器记录。因为这些字节1也有可能是其他模式运算的重叠结果,所以这一结论并不确定,但是是相当可能的。简单来说,Bloom过滤器正匹配代表着“可能是”。
图8-11是一个验证模式“X”是否在前述Bloom过滤器中的图例。相应的比特位都被置为1,所以这个关键词很有可能是匹配的。
图8-11 在布隆过滤器中测试模式“X”的存在。结果是概率正匹配,意思是“也许”
另一方面,如果我们代入模式计算后的结果任何某位为0,说明该模式并没有被记录在过滤器里。负匹配的结果不是可能,而是一定。也就是说,负匹配代表着“一定不是”。
图8-12是一个验证模式“Y”是否存在于简易布隆过滤器中的图例。图中某个结果字段为0,该字段一定没有被匹配。
图8-12 在bloom过滤器中测试模式“Y”的存在。 结果是明确的否定匹配,意思是“绝对没有!”
8.10 SPV节点如何使用布隆过滤器
布隆过滤器用于过滤SPV节点从其对等节点接收的交易(和包含它们的区块),仅选择SPV节点感兴趣的交易,而不会泄露其感兴趣的地址或密钥。
SPV节点将初始化“过滤器”为“空”,在该状态下,布隆过滤器将不会匹配任何模式。然后,SPV节点通过从其钱包控制的任何UTXO中提取公钥哈希,脚本哈希和交易ID,然后列出所有感兴趣的地址,密钥和哈希值。 SPV节点再将其中的每一个添加到布隆过滤器,如果这些模式存在于交易中,则布隆过滤器将“匹配”,而不会透露这些模式内容。
然后,SPV节点将向对等节点发送一个filterload消息,其中包含本连接上使用的布隆过滤器。在对等节点上,针对每个传入交易检查布隆过滤器。全节点根据布隆过滤器检查交易的几个部分,寻找匹配,包括:
-
交易ID
-
每个交易输出的锁定脚本的数据组件(脚本中的每个密钥和哈希)
-
每个交易输入
-
每个输入签名数据组件(或见证脚本)
通过检查所有这些组件,可以使用布隆过滤器来匹配公钥哈希,脚本,OP_RETURN值,签名中的公钥或任何将来的智能合约或复杂脚本的组件。
在建立过滤器之后,对等节点会将按照布隆过滤器测试每个交易的输出。只有与过滤器匹配的交易才会发送到节点。
作为对节点的getdata消息的响应,对等节点将发送一条merkleblock消息,该消息仅包含与过滤器匹配的区块的区块头和每个匹配交易的默克尔树路径(参见【9.7 Merkle 树】)。然后,再发送包含过滤器匹配的交易的tx消息。
当全节点向SPV节点发送交易时,SPV节点会丢弃任何误报,并使用正确匹配的交易来更新其UTXO集和钱包余额。随着它更新自己的UTXO集视图,它还会修改布隆过滤器,以匹配任何引用其刚刚发现的UTXO的交易。然后,全节点使用新的布隆过滤器来匹配新交易,并重复整个过程。
设置布隆过滤器的节点可以通过发送filteradd消息将模式交互式添加到过滤器。要清除布隆过滤器,节点可以发送一个filterclear消息。由于无法从布隆过滤器中删除模式,因此如果不再需要该模式,则节点必须清除并重新发送新的布隆过滤器。
BIP-37 (Peer Services)中定义了SPV节点的网络协议和布隆过滤机制。
8.11 SPV节点和隐私
实现SPV的节点的隐私性比全节点弱一些。全节点接收所有交易,因此不会泄露它的钱包中是否使用某个地址。 SPV节点接收与其钱包中的地址相关的经过过滤的列表。因此,它降低了所有者的隐私。
布隆过滤器是减少隐私损失的一种方式。没有它们,SPV节点将不得不明确地列出它感兴趣的地址,造成严重的隐私违规。然而,即使使用过滤器,监控SPV客户端流量的对手或直接连接它的点对点网络中的节点也可以随时随地收集足够的信息来了解SPV客户端的钱包中的地址。
8.12 加密和认证连接
比特币的大多数新用户假想比特币节点的网络通信是加密的。其实,比特币的最初实现完全是明文通信。虽然这不是全节点的主要隐私问题,但SPV节点是一个很大的问题。
作为增加比特币点对点网络隐私性和安全性的方法,有两种解决方案可以提供通信加密:通过BIP-150/151的Tor传输和P2P认证和加密。
8.12.1 Tor网络传输
Tor代表洋葱路由网络The Onion Routing network,是一个软件项目和网络,通过提供匿名,不可追踪和隐私的随机网络路径实现数据的加密和封装。
Bitcoin Core提供了多种配置选项,允许运行通过Tor网络传输流量的比特币节点。此外,Bitcoin Core还可以提供Tor隐藏服务,允许其他Tor节点通过Tor网络直接连接到您的节点。
从Bitcoin Core版本0.12开始,如果能够连接到本地Tor服务,节点将自动提供隐藏的Tor服务。如果您安装Tor并且运行Bitcoin Core进程账户具有足够权限的用户访问Tor的认证cookie,那么就能自动运行。使用debug标志打开Bitcoin Core对于Tor服务的调试,如下所示:
$ bitcoind --daemon --debug=tor
你在日志中看到“tor:ADD_ONION success”,表示Bitcoin Core已经向Tor网络添加了隐藏的服务。
您可以在Bitcoin Core文档(docs / tor.md)和各种在线教程中找到有关运行Bitcoin Core作为Tor隐藏服务的更多说明。
8.12.2 点对点认证和加密
BIP-150和BIP-151两个比特币改进方案在比特币网络中增加支持了点对点认证和加密。这两个BIP定义了可由兼容的比特币节点提供的可选服务。 BIP-151为启用BIP-151的两个节点之间的所有通信实现协商加密。 BIP-150提供可选的对等身份认证,允许节点使用ECDSA和私钥验证彼此的身份。 BIP-150要求在认证之前,两个节点按照BIP-151建立了加密通信。
截至2017年1月,BIP-150和BIP-151未在Bitcoin Core中实施。但是,这两个提案已由至少一个名为bcoin的比特币替代客户端实施。
BIP-150和BIP-151允许用户运行连接到受信任的全节点的SPV客户端,使用加密和身份验证来保护SPV客户端的隐私。
此外,可以使用身份验证来创建可信比特币节点的网络,防止中间人攻击。最后,点对点加密如果广泛部署,将加强比特币对流量分析和隐私侵权监控的抵抗力,特别是在互联网使用受到严格控制和监控的极权主义国家。
标准定义在BIP-150 (Peer Authentication) and BIP-151 (Peer-to-Peer Communication Encryption).
8.13 交易池
比特币网络中几乎每个节点都会维护一份未确认交易的临时列表,被称为内存池memory pool, mempool, 或交易池transaction pool。节点们利用这个池来追踪记录那些被网络所知晓、但还未被区块链所包含的交易。例如,钱包节点会利用这个交易池来记录那些网络已经接收但还未被确认的、属于该用户钱包的传入支付。
随着交易被接收和验证,它们被添加到交易池并通知到相邻节点,从而传播到网络中。
有些节点的实现还维护一个单独的孤儿交易池。如果一个交易的输入引用的是一个未知的交易,比如找不到其父交易,该孤儿交易就会被暂时储存在孤儿交易池中直到父交易到达。
当一个交易被添加到交易池中,会同时检查孤儿交易池,看是否有某个孤儿交易引用了此交易的输出(是其子交易)。任何匹配的孤儿交易会被进行验证。如果验证有效,它们会从孤儿交易池中删除,并添加到交易池中,延续以这个父交易开始的链。鉴于新加入交易池的交易不再是孤儿交易,前述过程重复递归寻找进一步的后代,直至所有的后代都被找到。通过这个过程,父交易的到达触发了一个相互依赖的交易链的级联重建,在整个链的下游将孤儿和他们的父交易重新联合起来。
交易池和孤儿交易池(如有实施)都是存储在本地内存中,并不是存储在永久性存储设备里。更准确的说,它们是随网络传入的消息动态填充的。节点启动时,两个池都是空的,随着网络中新交易不断被接收,两个池逐渐被填充。
有些比特币客户端的实现还维护一个UTXO数据库,也称UTXO池,是区块链中所有未花费交易输出的集合。“UTXO 池”的名字听上去与交易池相似,但它代表了不同的数据集。UTXO池不同于交易池和孤立交易池的地方在于,它在初始化时不为空,而是包含了从创世区块以来的数以百万计的所有未花费交易输出条目。UTXO池可能会被安置在本地内存,或者作为一个包含索引的数据库表安置在永久性存储设备中。
交易池和孤儿交易池代表的是单个节点的本地视角。取决于节点的启动时间或重启时间,不同节点的两池内容可能有很大差别。相反地,UTXO池代表的是全网的自发共识,因此,不同节点间UTXO池的内容差别不大。此外,交易池和孤儿交易池只包含未确认交易,而UTXO池只包含已确认输出。