BIP: 37
Layer: Peer Services
Title: 连接布隆过滤器
Author: Mike Hearn <hearn@google.com>
Matt Corallo <bip37@bluematt.me>
Comments-Summary: No comments yet.
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0037
Status: Final
Type: Standards Track
Created: 2012-10-24
License: PD
这个BIP为P2P协议增加了新的支持,允许同行减少他们发送的交易数据量。 在版本握手完成之后,节点可以选择在每个连接上设置过滤器。过滤器被定义为从交易派生的数据的布隆过滤器。 布隆过滤器是一个概率数据结构,允许测试集成员 - 他们可以有误报,但不是假的否定。
本文不会详细介绍布隆过滤器如何工作,读者可以参考维基百科进行介绍。
随着比特币使用量的增长,下载数据块和交易广播所需的带宽量也随之增加。 实施简化支付验证的客户不会试图完全验证区块链,而只是检查区块标题连接在一起是正确的,并且相信高难度链中的交易实际上是有效的。 有关此模式的更多详细信息,请参阅比特币纸张。
今天,SPV客户端必须下载块和全部广播交易的全部内容,才能抛弃绝大多数与钱包无关的交易。 这减慢了他们的同步过程,浪费了用户带宽(在手机上经常被测量)并且增加了内存使用。 所有这三个问题都触发了真正的用户投诉Android“比特币钱包”应用程序,实施SPV模式。 为了使链同步更快,更便宜,并且能够在内存有限的旧电话上运行,我们希望远程对等方在通过网络发送不相关的事务之前丢弃它们。
实现既定目标的最明显的方法是让客户端将密钥列表上传到远程节点。 出于以下原因,我们采取更复杂的方法:
我们开始在协议中添加三条新消息:
filterload
其中设置当前布隆过滤器在连接上filteradd
它将给定的数据元素添加到连接当前过滤器,而不需要设置全新的数据元素filterclear
这会删除当前的过滤器并返回到BIP37的常规使用。请注意,不存在filterremove命令,因为从本质上来说,Bloom过滤器是仅附加数据结构。 一旦添加了一个元素,就不能从头开始重建整个结构。
filterload
命令定义如下:
字段大小 | 描述 | 数据类型 | 注释 |
---|---|---|---|
? | filter | uint8_t[] | 过滤器本身只是一个任意字节对齐大小的位字段。 最大大小是36,000字节。 |
4 | nHashFuncs | uint32_t | 在此过滤器中使用的散列函数的数量。 该字段中允许的最大值是50。 |
4 | nTweak | uint32_t | 将随机值添加到布隆过滤器所使用的哈希函数中的种子值。 |
1 | nFlags | uint8_t | 一组控制如何将匹配项添加到过滤器的标志。 |
请参阅下面有关Bloom过滤器算法的说明,以及如何选择nHashFuncs和过滤器大小以获得所需的误报率。
在接收到一个filterload
命令后,远程节点将立即将其宣告的广播事务(在inv包中)限制为匹配过滤器的事务,其中匹配算法在下面指定。 这些标志控制匹配算法的更新行为。
filteradd
命令定义如下:
字段大小 | 描述 | 数据类型 | 注释 |
---|---|---|---|
? | data | uint8_t[] | 要添加到当前过滤器的数据元素。 |
数据字段的大小必须小于或等于520字节(任何可能匹配的对象的最大大小)。
给定的数据元素将被添加到布隆过滤器。 必须先使用filterload
提供过滤器。 这个命令是有用的,如果一个新的密钥或脚本被添加到一个客户端钱包,同时它的网络连接打开,它避免了重新计算和发送一个全新的过滤器到每个节点的需要(虽然这样做通常建议 保持匿名)。
filterclear
命令完全没有参数。
在设置过滤器后,节点不仅停止通告不匹配的交易,还可以为过滤的块提供服务。 一个过滤的块由merkleblock消息定义,并且定义如下:
字段大小 | 描述 | 数据类型 | 注释 |
---|---|---|---|
4 | version | uint32_t | 块的版本信息,根据软件版本创建此块 |
32 | prev_block | char[32] | 先前块的特定块的引用的哈希值 |
32 | merkle_root | char[32] | 对Merkle树集合的引用,它是与此块相关的所有事务的散列 |
4 | timestamp | uint32_t | 创建此块时的时间戳记录(限于2106!) |
4 | bits | uint32_t | 计算的难度目标被用于这个块 |
4 | nonce | uint32_t | 用于生成此块…随机数,以允许头的变化,并计算不同的散列 |
4 | total_transactions | uint32_t | 块中的交易数量(包括不匹配的) |
? | hashes | uint256[] | 散列深度优先(包括标准的varint大小前缀) |
? | flags | byte[] | 标志位,每8个字节打包,最低有效位先(包括标准的varint大小前缀) |
请参阅下面的部分merkle树散列和标志的格式。
因此,merkleblock消息是一个块头,加上一个merkle树的一部分,可以用来提取匹配过滤器的事务的标识信息,并证明匹配的事务数据确实出现在解决的块中。 客户可以使用这些数据来确保远程节点没有给他们提供从未出现在实际块中的假交易,虽然说谎通过遗漏仍然是可能的。
版本命令扩展了一个新的字段:
字段大小 | 描述 | 数据类型 | 注释 |
---|---|---|---|
1 byte | fRelay | bool | 如果为false,则在收到过滤器{load,add,clear}命令之前,不会公布广播事务。 如果丢失或者为真,协议行为不会发生变化。 |
希望使用Bloom过滤的SPV客户端通常会在版本消息中将fRelay设置为false,然后根据自己的钱包(或其子集,如果它们重叠不同的对等方)设置过滤器。 在设置过滤器之前,能够选择退出inv消息可以防止客户端在完成版本握手和设置过滤器之间的短暂时间窗口中充斥流量。
getdata
命令被扩展为允许inv
子消息中的新类型。类型字段现在可以是MSG_FILTERED_BLOCK(== 3)
而不是MSG_BLOCK
。如果连接上没有设置过滤器,则忽略过滤块的请求。 如果已设置过滤器,则会为请求的块散列返回一个merkleblock
消息。此外,由于merkleblock消息仅包含交易散列列表,因此匹配过滤器的交易也应在发送merkleblock后的单独tx消息中发送。 这避免了本来需要的慢速往返(接收哈希,没有看到一些这些事务,请求)。 请注意,由于目前没有办法请求节点中已经在块中的事务(除了请求完整块)外,请求节点还没有用inv
发送或通告的一组匹配事务必须是 发送并且任何与该过滤器相匹配的附加交易也可以被发送。 这允许客户端(例如参考客户端)限制其必须记住给定节点的inv
数量,同时仍然向节点提供所需的所有事务。
筛选器可以针对任意数据进行测试,以查看客户端是否插入了该数据。 因此,出现了什么样的数据应该被插入/测试的问题。
要确定事务是否与过滤器匹配,使用以下算法。 一旦找到匹配,算法就会中止。
通过这种方式,地址,键和脚本散列(对于P2SH输出)都可以被添加到过滤器中。 您还可以匹配在输入或输出中标记有众所周知的数据元素的事务类,例如,实现各种形式的智能属性。
为了确保你可以在你的钱包中找到交易消费的输出,即使你对他们的形式一无所知。 正如你所看到的,一旦设置了连接,过滤器就不是静态的,并且可以在整个连接生命周期中改变。 这样做是为了避免以下竞争条件:
通过在步骤2中利用所发现的端点原子地更新布隆过滤器,在步骤3中过滤器将与TX 2匹配,并且尽管在处理第一和第二块的节点之间没有暂停,客户端将会了解所有相关交易。
过滤器的nFlags字段控制节点的精确更新行为,并且是一个位字段。
BLOOM_UPDATE_NONE(0)
表示找到匹配项时不调整过滤器。BLOOM_UPDATE_ALL(1)
表示如果过滤器匹配scriptPubKey中的任何数据元素,则将outpoint序列化并插入到过滤器中。BLOOM_UPDATE_P2PUBKEY_ONLY(1)
表示只有匹配scriptPubKey中的数据元素,并且该脚本具有标准的“pay to pubkey”或“pay to multisig”形式,才会将outpoint插入到过滤器中。这些区别有助于避免由于错误的阳性率增加而使滤波器过快劣化。 我们可以观察到,一个预计只接收标准付款地址表单的钱包的钱包不需要自动过滤器更新,因为任何花费其自己的输出的交易在输入中具有可预测的数据元素(公钥 哈希到地址)。如果钱包可能收到付费地址输出以及付费到付费或付费多输出,那么BLOOM_UPDATE_P2PUBKEY_ONLY是合适的,因为它避免了最常见输出类型的过滤器的不必要的扩展,但仍能确保正确的行为 付款明确指定密钥。
显然,nFlags == 1或nFlags == 2意味着随着更多的链扫描,过滤器会变得更脏。 客户应该监测观察到的假阳性率,并定期用干净的刷新过滤器。
Merkle树是一种将一组项目排列为树的叶节点的方式,其中内部节点是其子哈希连接的哈希(hash)。 根节点被称为Merkle根。每个比特币块都包含从块交易形成的树的Merkle根。 通过提供树内部节点的一些元素(称为Merkle分支),形成了一个证明,即在开采时给定的事务确实在块中,但是证明的大小远小于原始块的大小。
由于部分块消息包含整个块中的事务数量,所以先前已知该Merkle树的形状。 再次,遍历这棵树,计算遍历节点的散列:
部分merkle树对象仅在以下情况下有效:
布隆过滤器是一个位域,其中根据将数据元素馈送到一组不同的散列函数来设置位。 所使用的散列函数的数量是过滤器的参数。 在比特币中,我们使用32位Murmur哈希函数的版本3。 为了得到N个“不同的”散列函数,我们只需用下面的公式初始化Murmur算法:
nHashNum * 0xFBA4C795 + nTweak
即如果过滤器用4个散列函数初始化并且调整为0x00000005,则当需要第二函数(索引1)时,h1将等于4221880218。
使用 filterload
命令加载过滤器时,可以选择两个参数。 一个是以字节为单位的过滤器的大小。 另一个是要使用的散列函数的数量。 要选择参数,您可以使用以下公式:
假设N是你想插入到集合中的元素的数量,P是误报的概率,其中1.0是“匹配所有”,零是无法实现的。
以字节为单位的过滤器的大小S由(-1 / pow(log(2), 2) * N * log(P)) / 8
给出。当然,您必须确保它不超过最大大小(36,000 :因为它代表20,000个项目的过滤器,误报率<0.1%或10,000个项目,误报率<0.0001%)。
所需散列函数的数量由S * 8 / N * log(2)
给出。
该文件被放置在公共领域。
https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki
https://zh.wikipedia.org/wiki/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8
https://en.wikipedia.org/wiki/Bloom_filter
https://en.bitcoin.it/wiki/Smart_Property
https://github.com/cpselvis/zhihu-crawler/wiki/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8%E7%9A%84%E5%8E%9F%E7%90%86%E5%92%8C%E5%AE%9E%E7%8E%B0