作者:Sparrow

来源:https://sparrowwallet.com/docs/server-performance.html

本文最近一次更新时间是 2022 年 2 月 1 日。

Sparrow Wallet 依赖于 “Electrum 服务端协议” 来检索和发送交易的信息。本文档致力于为不同的完全索引 Electrum 服务端实现在常用硬件上的表现提供一个合理的、及时的基准比较。本文还基于这些发现,讨论了这些实现所采取的两种不同方法。

背景

Electrum 服务端是一种比特币地址索引。简单来说,你给服务端提供随便一个地址,它就会返回跟这个地址相关的交易。比特币的参考实现(译者注:指 Bitcoin Core)并不支持这种功能(可能永远不会支持)。注意,你也可以使用 “致密区块过滤器” 获得这种信息,但这种方法并不支持交易池中的交易,性能表现也差得多

当然也有别的类型的地址索引器,但 Electrum 服务端协议是目前为之最广泛使用的比特币地址索引协议。本基准测试针对的是完全地址索引。从隐私性角度看,完全地址索引是重要的,因为它不会在服务端存储任何一个钱包的细节。对于真正的冷存储来说,所有关于钱包的细节都应该仅存储在钱包文件中,一旦连接中止,就不应在服务端留下任何信息。这就将 BWTEPS 这样的必须存储向它提供的钱包的地址的项目排除在外。

动机

早前由 Jameson Lopp 撰写的一份性能报告是在 2020 年 7 月出版的,也值得一看

本文档以两个元素为基础:

  1. 运行在树莓派 4 型上,而不是运行在 AWS 服务器上
  2. 使用这些项目的最新版本,并且每当增加一个重要变更,都重新测试

因此,希望在单主板电脑上运行自己的 Electrum 服务端的 Sparrow 用户会发现本文档的优点:可以出于自己的需要,对比不同的实现。

硬件

为了模拟大部分关心隐私的 Sparrow 用户的情况,我们在所有测试中都使用单主板电脑(SBC)。目前,最常见的 SBC 是树莓派 4 型。本基准测试使用的是其 8 GB 内存的版本,操作系统是 Ubuntu 21.10 64 位版。

比特币区块链以及任何建立在它基础上的索引,在数据体积上都非常大(当前大约是 0.5 TB)。因此,人们常常推荐使用固态硬盘(而非常见的机械硬盘)作为存储媒介。本测试使用了一个容量位 1 TB 的外置固态硬盘,以 USB 连接。

参与比较的项目

ElectrumX

ElectrumX 是最初的 Electrum 服务端项目为走向 ElectrumX 而在 2017 年停止后推出的第二代 Electrum 服务端实现。在 ElectrumX 的原作者决定不再支持比特币区块链之后,Electrum 的开发者们复刻了这个项目。现在,它在这里维护:https://github.com/spesmilo/electrumx

运行 ElectrumX 的一个显著困难出现在初次建构索引的时候。在我们这次测试所用的硬件上,建构索引花费了大约一周的时间。不过,请注意,这样的索引可以在更强大的计算机上建构出来,再迁移到 SBC 上。

  • 当前的数据库大小:75 GB
  • 要求在 Bitcoin Core 中启用 txindex(交易索引)
  • 不提供二进制文件
  • 测试版本:ElectrumX 1.16

Electrs

ElectrumX 的设计初衷是放在公开服务端上,而 Electrs 的设计场景是个人用户。因此,它的存储要求更低(但 CPU 用量更高,我们后面会看到)。它的代码现在在这里维护:https://github.com/romanz/electrs

与 ElectrumX 相反,建构索引在我们这次测试所用的硬件上只花费了 12 ~ 24 小时。这也使它成为了所有预先构建的节点软件包的首选。

  • 当前的数据库大小:32 GB
  • 不要求在 Bitcoin Core 上启用 txindex
  • 不提供二进制文件
  • 测试版本:Electrs 0.9.4

Fulcrum

Fulcrum 是一个使用现代 C++ 语言编写的新实现。虽然它比 ElectrumX 和 Electrs 占用了更多的硬盘,但它的性能好得多。它的代码库在:https://github.com/cculianu/Fulcrum

Fulcrum 的索引可以在测试所用的设备上用 2 ~ 3 天建构出来,具体取决于你的配置。这介于 Electrs 和 ElectrumX 之间,但是,一旦索引建构好之后,其性能是可圈可点的。

  • 当前的数据库大小:102 GB
  • 要求在 Bitcoin Core 上启用 txindex
  • 给 Linux(x86-64 和 arm64)和 Windows 提供了二进制文件
  • 测试版本:Fulcrum 1.6.0

Electrs-esplora

这是 Electrs 项目的一个复刻,建构了额外的索引,以为企业用户提供更好的性能。不过,因为其空间需求(约 800 GB),这个实现对我们的测试来说是不合适的。注意,它可以使用 --lightmode 标签来运行,硬盘空间要求会减少一半。不过,即使如此,1 TB 的容量也几乎要用光了,因为光区块链自身就有 420 GB 了。

addrindexrs

这是 Electrs 项目的另一个复刻,被 Samourai Dojo 的后台使用,用来检索历史交易数据。从性能上看,没有重大的提升,不值得当成一个单独的实现。

索引编制

在运行性能测试之前,我们要更深入地考虑索引初始化过程。这三个参加测试的实现的初始化用时在 1 天到 1 周之间。我们逐个来看:

ElectrumX 的编制索引的速度是最慢的,有可能是因为其编程语言是 Python,它是单线程的,而且解析区块的速度较慢。不过,它所建立的索引可以在不触及 Bitcoin Core 的前提下响应常见的请求,这使它成了可扩展性更好的服务端。在使用 ElectrumX 时,请一定使用 64 位的操作系统 —— 不然编制索引的速度还会慢得多。

Electrs 编制索引的速度更快,因为其索引也没有那么复杂,而且因为区块检索和解析都是高度优化的。需要这样做,因为 Electrs 不会在索引中存储足以响应大多数请求的信息 —— 相反,它需要在正常操作期间重新索引区块。这个取舍在钱包的深度增加时就变成了问题的来源,我们后面会说。

Fulcrum 编制出来的索引稍大于 ElectrumX 的,但速度快得多。因为非常消耗资源,索引编制的性能得益于其更加底层的编程语言。注意,通过使用 “fast sync” 配置选项,编制索引的速度可以提高。后面我们会知道,这套综合的索引会在操作时提供令人惊讶的性能。

测试

为了提供有意义的数据,我们用了一个很大的钱包(使用过大约 3000 个地址)。

这个基准测试致力于测试来自 Sparrow 钱包的两种常见(但完全不同)的服务端载荷:

  1. 初次加载一个钱包。因为加载过的 Sparrow 钱包已经包含了许多钱包数据(交易和区块),不需要完全重新检索,这个测试针对的是地址订阅。地址订阅让 Sparrow 可以 “订阅” 一个地址,让服务端在地址有动作时提供更新。此外,订阅请求会返回一个影响该地址的所有交易 id(txid)以及区块高度的哈希值。这第二项要求是理解服务端性能的关键。这个测试度量了订阅钱包中的所有地址要多长时间。
  2. 刷新钱包。理想情况下,钱包永远不需要刷新 —— 钱包应该只需要增量更新。但是,通信问题和服务端问题可能产生换数据,这就需要通过在 Sparrow(视图菜单)中刷新钱包来解决。这个测试度量了仅仅检索所有钱包数据(交易和区块)所需的时间,因为在刷新的时候,地址已经订阅了。

所有参加测试的服务端时间都支持批量请求。在本次测试中,单个批次的请求数量是 50 个。

结果

测试 1:初始化载荷

测试对象冷启动第一轮第二轮第三轮
ElectrumX52655 ms40721 ms54143 ms49011 ms
Electrs322386 ms393303 ms384036 ms427722 ms
Fulcrum2333 ms1413 ms1472 ms1413 ms

一句话总结:Fulcrum 比 ElectrumX 快 22 倍,比 Electrs 快 300 倍。

测试 2:钱包刷新

测试对象冷启动第一轮第二轮第三轮
ElectrumX114466 ms66175 ms80133 ms75489 ms
Electrs17562 ms11621 ms11219 ms11521 ms
Fulcrum14152 ms7854 ms7382 ms7442 ms

一句话总结:Fulcrum 比 ElectrumX 快 8 倍,比 Electrs 快 1.5 倍。

讨论

测试 1 的结果一开始让我们非常惊讶。因为 Fulcrum 和 Electrs 的性能差距非常大(1.4 秒 vs. 6 分钟,处理的是相同的数据!)。这主要是因为 Electrs 并不存储找到跟一个地址相关的交易的所有数据。

可以从文档《Electrs 数据库方案》中看出,它只会存储跟一个地址有关的区块高度。更具体一些,交易的脚本公钥会被哈希,然后取这个 script hash 的前 8 个字节,作为一个关键字,而其值就是跟这个 script hash 相关的交易的已确认区块高度。这意味着,区块必须通过 P2P 接口从 Bitcoin Core 中检索出来,然后解析得到匹配这个地址的输出的交易。对于一个重度使用的钱包,比如我们用在测试中的这个,每次载入都会有大量的交易必须重新解析出来。具体到我们这个钱包,这需要从 Bitcoin Core 中拉取 3.5 GB 的数据,然后解析出来;每次载入这个钱包都需要这样的拉取和解析哦!

这给 SBC 带来了巨大的 CPU 负担,无论是 Bitcoin Core 还是 Electrs 的进程。最大的问题是,有时候,Electrs 会应接不暇,完全无法处理任何别的请求,也无法在此期间为 Sparrow 提供已订阅的地址有新交易的通知。这可能会导致具有相当深度的钱包在加载时出现过时的或者坏的数据。如果有你曾在使用 Electrs 时被迫使用 Sparrow 的 “刷新钱包” 功能,可能就是这个原因。

ElectrumX 和 Fulctrum 则相反,存储了完全地址索引查找所需的数据。更具体来说,在给服务端提供一个地址时,它能返回跟这个地址有关的所有交易的 ID,以及相应的区块高度。这让 Sparrow 这样的钱包可以高效地检查是否已经知晓了影响这个钱包的所有现存交易,然后只请求自己还不知晓的交易。在跟 Bitcoin Core 的交易索引(txindex=1)相结合时,这个办法可以非常高效,就像 Fulcrum 的测试结果那样。

即使知道了这些,Fulcrum 的性能也让人印象深刻。显然,使用最先的 c++17 语言是非常有好处的,不仅因为它更快,而且因为它在不同平台上的性能是一致的。而在 ElectrumX 中, Python 似乎不那么适合这个应用。不仅因为它更慢,而且因为编制索引和查询的性能都会因底层的硬件架构而变化 —— 这个基准测试的上一个版本使用了 32 位的操作系统,显示出更快的查询性能,但索引速度慢得多。值得指出的是,在查询的时候,ElectrumX 的 CPU 用量比 Fulcrum 更高(持续时间也更长)。

结论

我们可以在一定程度上确认,未来会继续的两种趋势:钱包的深度会增加,存储的成本会降低。

因此,Fulcrum 成为了这次基准测试的显然胜出者。虽然它编制索引的速度稍慢于 Electrs,然而,一旦索引编制完成,可扩展和强大性能的好处会显著盖过这个一次性的成本,不仅在查询的速度和可靠性方面,也在硬件的生命长度上。

虽然 Electrs 在硬件空间非常有限、钱包深度也有限时有一些好处,Fulcrum 是我们认为与 Sparrow 配对的理想服务端实现。

技术注释

理解如何开发一套高效的地址索引也是有趣的事。我们来看看 Fulcrum 和 ElectrumX 是如何存储数据的。在 ElectrumX 中,类似于 Electrs,历史数据存储 script hash 的前 11 个字节作为关键字(关键字的长度稍长,使存储负担增加,但也会降低不正确查找的 “假阳性” 概率)。Fulcrum 存储每个 script hash 的全部 32 字节,这意味着更大的数据库体积,但完全没有假阳性问题(译者注:“假阳性” 在这里的意思是被检测手段命中,但找出的交易并不是我们需要的)。

与 Electrs 相反(将 script hash 匹配到一个区块高度),ElectrumX 中存储的、跟 script hash 对应的值是一个列表,包含的是区块链上与其有关的交易的有序数字(叫做 tx_num)。tx_num 可以这样解释:每一笔交易在一个区块中都是有序的(有位置的),所以可以通过查找一笔交易在整条区块链中的位置来为它确定一个累加数字。它本质上是 txid 的一个简写,但它不仅减少了要存储的数据,而且允许快速查找区块高度。

举个例子,txid 是从一个包含了按照区块链排序的所有 txid 的逻辑文件决定的,而文件中的偏移量就是 tx_num 乘以 32 字节(一个 SHA256 哈希值的字节长度)。类似地,Fulcrum 和 ElectrumX 都在内存中维护一个元组,每个索引号都对应着区块高度,而索引的数值包含了存在于该高度的区块内的交易的累加数字。这意味着,从一个 script hash 检索得到 tx_nums 之后,就可以为每一个 tx_num 快速搜索 txid 和区块高度。这就是它们实现对比 Electrs 的性能优势的原因。这部分性能的代价是更大的磁盘占用 —— Electrum 多占用了 21 GB 的空间来存放逻辑文件、指行从 tx_num 到 txid 的查找。软件工程通常都涉及取舍,而在这里,舍弃换来的是查询性能上的显著优化。

(完)