协议演进 / BIP/比特币改进提案 / BIP-158
BIP-158
定义用 Golomb-Rice 编码压缩的紧凑区块过滤器结构,并给出供普通钱包使用的 basic 基础过滤器类型。本 BIP 目前处于已部署状态。
一、档案卡
| 编号 | BIP-158 |
|---|---|
| 标题 | Compact Block Filters for Light Clients |
| 作者 | Olaoluwa Osuntokun <[email protected]>, Alex Akselrod <[email protected]> |
| 指定日期 | 2017-05-24 |
| 层级 | Peer Services(对等服务) |
| 类型 | Specification(规范类) |
| 状态 | Deployed(已部署) |
| 依赖文档 | Requires: BIP-157 |
| 阅读版 | https://bips.dev/158 |
| 官方原文 | https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki |
二、放在哪一族
轻客户端紧凑过滤器一族,是 BIP-157 协议的另一半:BIP-157 规定轻客户端如何向全节点索取过滤器、如何用过滤器头互相校验;BIP-158 规定过滤器这个数据结构本身——每个区块要收进哪些条目、用什么数学结构装、参数取多少。文件头里两篇互相 Requires,缺一不可。
它也是对 BIP-37 布隆过滤器的替代构造:同样回答“某个条目在不在这个区块里”这类成员查询,但换了数据结构与使用方向。BIP-37 是客户端定制过滤器、由节点代查;本篇是节点为每个区块生成一份固定过滤器、供所有客户端下载自查。原文附录 A 专门记下了为何放弃布隆过滤等备选编码:同样的误报率下,布隆过滤器的体积比本篇的 Golomb 编码集更大,这是被否掉的主因。
三、它要解决什么
BIP-157 定的协议需要一种按块确定的过滤器:对同一个区块,任何人独立构造都得到同一份字节;且要尽量小,因为轻客户端为同步钱包,需要把大量区块的过滤器都下载回来——过滤器本身的大小加上因误报而多下载的整块,共同决定总带宽。
原文 Motivation 说得很收敛:本文件为 BIP-157 的轻客户端协议定义最初的一种过滤器类型 basic,其设计目标就是压低普通钱包场景下的过滤器体积。采用的结构是 Golomb 编码集(Golomb-coded set,GCS):用 Golomb-Rice 编码压缩一组条目的哈希值,以概率结构换体积——集合内的条目必然命中,集合外的条目以 1/M 的概率误报命中,其中 M 是可调参数。
四、规则本身
4.1 Golomb 编码集怎么构造
每个区块的过滤器,是把与该块相关的一组数据条目压成一个 GCS。构造分四步(细节以原文伪代码为准):
- 哈希映射:用 SipHash(参数固定为 c=2、d=4,密钥 k 为 128 位)把每个原始条目哈希为 64 位整数,再均匀映射到区间 [0, N×M) 内,其中 N 是条目数、M 是目标误报率的倒数。映射用“乘以区间长度后取 128 位乘积的高 64 位”的办法代替取模,避免昂贵的除法。查询时的误报率即为 1/M;N 与 M 都必须小于 2^32 以防溢出。
- 排序:把映射后的整数按升序排列。
- 作差:逐个计算相邻值之差(delta)。由于哈希值均匀分布,这些差值近似服从几何分布。
- Golomb-Rice 编码:把每个差值拆成商与余数:商以一元编码写出(q 个 1 后跟一个 0),余数以 P 位大端写出,其中 P 是余数编码的位长参数。几何分布的数据用这种编码能得到最优压缩。
查询时按相反顺序逐个解码差值、累加还原出已排序的哈希集合,再把待查条目以同样方式哈希后比对;因为集合有序,解码值一旦超过目标即可提前终止,也不必把整个解压结果同时放进内存。若要一次查多个条目,还可把查询集同样哈希排序后与解压流并行比对,效率更高(原文附录 B 给了伪代码)。
一份完整的已序列化过滤器,就是在压缩字节前面以 CompactSize 写上条目数 N(解码时必需);空过滤器(N 为零)写为一个全零字节。
4.2 basic 类型:装什么、参数多少
原文只定义了这一种初始过滤器类型:
- 类型代码:Basic,
0x00 - 参数:M = 784931,P = 19。原文说明,若 P 与 M 可独立选取,M 约取 1.497137×2^P 接近最优;这组取值的实证分析结果是使“过滤器自身体积 + 误报导致的整块下载”总带宽最小。
- SipHash 密钥 k:取该区块哈希(标准小端表示)的前 16 字节——因此密钥按块确定、且逐块不同。
内容清单是硬性的。对区块中的每一笔交易,basic 过滤器必须恰好收进:
- 每个输入所花费的前一输出脚本(即被花费输出的 scriptPubKey),coinbase 交易的输入除外;
- 每个输出的 scriptPubKey,但以 OP_RETURN 开头的输出脚本全部除外。
空条目不得收进集合。原文解释为何排除 OP_RETURN 输出:为了让过滤器将来能经软分叉被承诺进链内留出余地——一个很可能的做法是在 coinbase 中再加一个类似现行见证承诺的 OP_RETURN 输出来承诺过滤器;若 OP_RETURN 输出本身又被收进过滤器,就会形成“承诺依赖被承诺对象”的循环。因此先把它们整体排除在外。
4.3 服务位宣告
本篇分配了一个新服务位:NODE_COMPACT_FILTERS,取值为 1 << 6。置位即表示:该节点必须应答 BIP-157 中针对过滤器类型 0x00(即本篇 basic 类型)的全部消息。轻客户端据此先判断对端能不能提供过滤器,再发出索取请求。
兼容性上,原文的结论是:这种过滤器构造与现有软件并不冲突,只是需要实现新的过滤器生成与查询逻辑。参考实现见原文:轻客户端、 全节点索引与 GCS 库均有链接。
五、谁必须动
普通持币人(轻钱包用户):无直接动作;钱包在后台下载的就是按本篇格式生成的过滤器。
钱包与轻客户端开发者:需实现 GCS 的解码与成员查询(包括多条目批量查询),按 basic 的内容清单理解过滤器命中意味着什么,并为误报留出下载整块核实的路径。
全节点:选择支持本篇的节点,要按块按清单确定性地生成 basic 过滤器:取齐每个输入的前一输出脚本与每个输出的 scriptPubKey(除 OP_RETURN 输出),以区块哈希前 16 字节为密钥完成编码,并置 NODE_COMPACT_FILTERS 服务位对外供应。
共识层:完全不动。过滤器是链下索引,不是区块的一部分;OP_RETURN 排除规则只是为“将来可能把过滤器承诺进共识”预留的不循环设计,并不表示该承诺已经发生。
六、现在算不算数,以及一个常见误读
状态为 Deployed(已部署)。按 BIP 体系的用法,这表示该过滤器格式已随 BIP-157 协议被实际部署进运行中的实现——两份文档同日指定、互相依赖,应成对阅读:谈“紧凑过滤器”时,协议流程看 BIP-157,字节结构与参数看本篇。原文没有给出各钱包、各节点的采用清单,本页也不臆测具体产品的支持情况;可确认的只有规范层面的状态。
常见误读:
“过滤器说命中,就证明我关心的交易一定在这个区块里;说没命中则肯定不在。”
后半句对、前半句错。GCS 是概率结构:集合内的条目必然被查出,所以“没命中”可以断定不在块内;但“命中”带有 1/M 的误报概率(basic 参数下 M = 784931),完全可能是另一个条目的哈希恰好撞上。原文的整个参数选取都是在为这种误报定价——误报的代价是白下载一个整块核实。因此命中只是一个“值得把整块下载回来亲自确认”的信号,而不是证明本身。
七、延伸
- 阅读版:https://bips.dev/158
- 官方原文:https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki
- 相关:BIP-157 客户端区块过滤(本篇所依赖)、BIP-37 连接布隆过滤
- 仓库总目录:https://github.com/bitcoin/bips
