实现 Utreexo 目标方面的进展

摘要:在本文中,100x 集团受资助人 Calvin Kim 探讨了关于 Utreexo 好处的几种说法,以及这些好处的实现程度。Calvin 解释了 Utreexo 在最新的实现过程中取得的重大进展,然而在普通用户使用该技术之前,还有很多工作要做。

在 2020 年 7 月的上一个示范版本中,我们指出,未来的 Utreexo 项目将把Utreexo 累加器实现到 btcd 中,这是 Go 中现有的比特币实现。我很高兴地说,这个实现已经为另一个示范版本做好了准备。在此版本中,可以演示一种名为 “Compact State Node”(简称CSN)的新修剪模式。

在我 2020 年 4 月发表的名为 “ELI5:Utreexo - 一个规模化的解决方案” 的文章中,我就 CSN 所能提供的优势提出了几项主张。这些主张分别是:

  1. 几千字节的新全节点模式,其同步速度与 hdd 上的 ssd 一样快。
  2. 允许初始区块下载的并行化。
  3. 通过允许共识独立于数据库实现来增强比特币的安全性(当前使用的是由 Google 建立)。
  4. 无需分叉就可以将 Utreexo 带入比特币。

以目前的发展状况来看,目前已经实现了主张 3 和 4。主张 1 的部分实现是因为非 Utreexo 数据成为节点大小减少到千字节的瓶颈。主张 2 目前仍在进行中。

为什么主张 3 很重要

通过允许共识独立于数据库的实现,加强了比特币的安全性(目前使用的是由 Google 制定)。

几年来,提高比特币安全性的主要重点之一就是消除比特币的任何外部依赖性。外部依赖性是指任何比特币开发者未编写的代码,但却是比特币软件运行所必需的,在任何对安全至关重要的项目中,都应尽可能避免外部依赖关系,因为它们可能是 bug 的来源。通过审查外部依赖性然后保留审查后的代码副本,可以将这种风险最小化。然而,这种方法并不完美,不如由比特币开发者直接编写、测试和审查的代码。为此,比特币开发者一直在从比特币中消除各种外部依赖(删除 OpenSSH 代码就是一个例子)。

目前,最大的外部依赖是用于存储 Unspent Transaction Output 集(UTXO 集)和区块索引的数据库。使用的数据库来自 Google,名为 “LevelDB”。LevelDB 没有 bug 对比特币的安全性至关重要。如果 LevelDB 存在 bug,可能会导致双花或意外分叉发生。事实上在 2013 年,Berkeley DB(在 LevelDB 之前使用的数据库)在 Bitcoin Core 中的使用存在一个 bug,导致较旧的 Bitcoin Core 节点在读取 225430 区块时失败,从而导致意外分叉。

(前述 UTXO 集是目前世界上所有能花费的比特币的集合。UTXO 集对于比特币的安全性至关重要,因为这直接构成比特币共识的一部分,从中删除 LevelDB 将极大地帮助改善比特币的弹性。)

实现主张 3

之所以需要一个数据库是因为 UTXO 集包含超过 6000 万个 UTXO,所有的UTXO 都必须被追踪并快速访问,因为访问速度慢会减慢初始区块的下载速度。在这种需要快速访问大量小数据的情况下,经常会用到数据库。

然而,有了 UTXO CSN,我们根本不需要数据库。相反,我们要做的是让 UTXO的花费者提供 UTXO 数据和证明 UTXO 存在的 Utreexo 累加器证明。这样我们就无需在 Utreexo CSN 实现中保留 UTXO 集。由于我们不需要保留 UTXO 集,因此我们可以从比特币共识的另一个重要部分中移除 LevelDB。

下表为当前区块验证与用于扩展主链的 Utreexo CSN 区块验证的工作方式的比较:

当前区块验证Utreexo CSN 区块验证
1. 检查工作证明1.检查工作证明
2. 对于每个输入,检查被引用的输出是否存在于 UTXO 集 (LevelDB)中。2. 检查区块中引用的每一个 UTXO 都有一个 Utreexo 累加器证明,并进行验证。 
3. 验证每个输入的脚本和签名3. 验证每个输入的脚本和签名

这里唯一的区别是,Utreexo CSN 区块验证没有数据库访问权限。相反,它使用Utreexo 证明验证。

代码的更改相当小,大部分的区块验证函数都保持不变。在检查累加器证明后,现在已验证的 UTXO 数据(用于验证区块所需的数据)被转换为称为 UtxoViewpoint(在 Bitcoin Core 中为 CCoinsView)的现有 UTXO 集缓存结构,,然后传递给现有的验证函数。

为什么主张 4 很重要

不需分叉就能将 Utreexo 带入比特币

在比特币这样的去中心化系统中,需要为一个新功能进行分叉是一个很大的风险/挑战。对于比特币来说,硬分叉几乎是不可能的,因为启用功能的好处会被链分裂的可能性所抵消。软分叉也很难推广,因为它们需要社区的大力支持。

另一方面,如果无需分叉就可以选择加入一项新功能,则该功能的部署会简单得多。BIP-152: Compact Block Relay 是一个该功能已经被广泛采用的例子,并且不需要分叉。选择加入 Compact Blocks 的节点可以选择这样做,并且由于该功能是自定义加入的,因此对于不想使用它的人不会有任何改变。

实现主张 4

这是最容易实现的主张,因为在 Tadge Dryja 第一次写出 Utreexo 论文的时候就已经解决了。我们通过使用称为桥接节点(bridge nodes)的过渡节点来桥接新的 Utreexo 节点和当前的比特币节点,避免了软分叉。

当一个非 Utreexo 节点连接到桥接节点时,桥接节点的行为与当前比特币全节点相同。然而,当 Utreexo 节点连接到桥接节点时,它将在为非 Utreexo 节点服务的一般区块之上提供 Utreexo 证明。

正如发布的文章中所述,提供的 Utreexo 二进制文件被硬编码为 *only* 连接到我们运行的桥接节点,以避免干扰比特币测试网网络。

为什么主张 1 很重要

新的全节点模式,只需几千字节,同步速度与 Hdd 上的 ssd 一样快。

前述的 UTXO 集是运行完整节点的必要部分。然而,随着采用率的提高和可用的比特币总量被分割成越来越细的数量,该 UTXO 集的大小将会增加。目前,UTXO 集约为 4GB,但这可能会增长到一个对于低成本设备来说难以承受的大小。如果比特币要被广泛采用,则缩小此 UTXO 集至关重要。

在当前的比特币节点中,当区块中引用任何一个 UTXO 时,该节点就需要从磁盘或内存中的缓存中获取该 UTXO。而如果节点的磁盘很慢则会是目前比特币的瓶颈之一。对于修剪节点,这更是一个制约因素,因为修剪区块时会将缓存的 UTXO 写入磁盘。正如比特币开发者 Pieter Wuille 在此处指出的那样,这导致修剪的节点与未修剪的节点相比,同步速度较慢。

使用 Utreexo CSNs 时,由于 UTXO 集没有磁盘读取,所以不会出现减速。这样一来,无论是 nvme ssd 还是 hdd,Utreexo CSN 的性能几乎都一样。

主张 1 的当前进展

“只有几千字节的全节点” 的主张未能实现,因为包括区块头在内的其他元数据占据了几百兆字节。即使 chainstate 很小,其他数据在实现 “几千字节的全节点” 的目标时也变得不可忽视。在这个版本中,我们以几百兆的数据来妥协。

仅将 chainstate 与 Bitcoin Core 进行比较,看起来像这样:

正如您所看到的,Utreexo CSN 的 chainstate 大小仅为 424 字节,因此,对于该节点占用的整个大小,它成为舍入误差。事实上,用于在重启时重新连接到已知peers 的 peers.json 文件占用了 205 千字节,这比 chainstate 约大 483 倍。

以下是使用 NVMe SSD 与 HDD 时,修剪的 Bitcoin Core 与 Utreexo csn 的性能差异。

测试是通过将被基准测试的节点指向另一个分别读取 NVMe SSD 的本地 Utreexo 桥接节点来完成。在 Bitcoin Core 中使用了默认 testnet3 assumevalid 区块 1,864,000。在 CSN 中,实现了 assumevalid,并设置为区块 1,864,000。在testnet3 上测试到区块高度 1,906,000。

使用的硬件是:

  • 处理器: AMD Ryzen 3600
  • 内存:Samsung 32GB DDR4 2666MHz
  • 本地服务节点 NVMe 硬盘:2TB Sandisk ULTRA M.2 NVMe
  • 测试节点 NVMe 硬盘:1TB HP SSD EX950 M.2
  • 测试节点硬盘:Western Digital WD10EZEX-22BN5A0 1TB 7200RPM

赋予比特币核心节点的标志是:

  • -prune=550
  • -connect=127.0.0.1
  • -disablewallet
  • -blocksonly 
  • -testnet

使用 Bitcoin Core,在 NVMe SSD 上运行它需要 784 秒,而在 HDD 上需要 1066秒。使用 Utreexo CSN,在 NVMe SSD 上运行需要 1,643 秒,而在 HDD 上需要 1,700 秒。

请注意,目前 Utreexo CSN 的实现还有很多需要性能优化的地方。由于我们分叉了 btcd 这个比 Bitcoin Core 慢许多的节点,所以其目前比 Bitcoin Core 慢。我们后续会发布一个着重性能的版本和文章。

为什么主张 2 很重要

允许初始区块下载的并行化

为了避免混淆,这里所说的并行化是指链级的并行化。这意味着单个节点将同时验证多个区块范围,例如:100,001~200,000 和 200,001~300,000。这里的要求并不是指区块级的并行化,即一个区块中的交易签名是并行验证的(因为这已经在btcd 和比特币核心中实现了)。

计算机中的并行化是指同时执行多个进程。这样可以更大程度地利用可用硬件(CPU),如果有闲置的硬件,则可以提高性能。近年来,由于物理限制,CPU 的开发在提高时钟速度方面遇到了困难。反过来,比起提高时钟速度,人们将更多的精力放在增加内核数量上。紧随其后的是软件开发范式,如今为了更好地利用更多 CPU 内核,人们更加重视并行化。

初始区块下载的并行化在同步整个节点所需的时间方面具有改变游戏规则的巨大潜力,使个人更容易运行一个完整的节点。更多的节点将使比特币网络更能抵御攻击。从这个意义而言,并行化可以视为增加了比特币的安全性。

主张 2 的当前进展

任何区块的验证都取决于前一个区块的 UTXO 设置。例如,如果我们要验证区块 501,我们需要在区块 500 处设置 UTXO。然而,要想在区块 500 处设置 UTXO,我们需要在区块 499 处设置 UTXO。这个依赖于前一个区块设置的 UTXO 的问题,可以一直追溯到创世区块(已硬编码于其中)。这就是链级并行化的困难所在。

有了 Utreexo,这个问题变得简单多了,因为 UTXO 集只有几百字节,而不是几千兆字节。这让我们可以将整个 UTXO 集示形式硬编码到软件中,作为并行验证的起点。

请注意,peer 可能是恶意的,并提供错误的 UTXO 集。但是这不会降低我们的安全性假设,因为我们使用不同的 CPU 内核来验证从创世区块到区块高度 499。我们利用这些 CPU 核原本会闲置的事实,从区块 501 继续验证。当创世区块到区块高度 499 的验证完成后,我们现在可以检查该区块和区块高度 500 设置的 UTXO 是否匹配。因此,硬编码的 UTXO 集代表只是作为使处理速度更快且一切经过验证的提示。

为了支持这种类型的链级并行化,代码库必须支持具有多个活动 chainstate。具有任意 n 个 chainstate(甚至只有两个)的主要困难点是必须追踪 n 个 UTXO 集。由于一个 UTXO 集需要一个数据库和磁盘上的 UTXO 集缓存来加速,这使得运行节点的硬件要求提高了许多。不过,由于 Utreexo CSN 取消了 UTXO 集存储的数据库,这就不是问题了。

拥有多个 chainstate(Bitcoin Core 中的 CChainState,btcd 中的 Blockchain 类型)的实现已经在进行中。对于 Utreexo CSN 来说,工作大大简化了,因为不需要为每个 chainstate 建立数据库。这使得任意 n 个 chainstate 的实现都是可行的。

目前我们还在研究如何处理每个 chainstate 的网络 p2p 消息。我们正在尝试不同的方法(有两个初始区块下载管理器;追踪哪个 chainstate 要求哪些区块;等等),但仍有一段路要走。

本版本的局限性

在当前版本中,不支持链重组和内存池。因此节点将在 “blockonly” 模式下运行,如果出现重组,节点将崩溃。这两件事尚未在 Utreexo 库中实现,这也是该版本仅用于演示的原因。我们这个版本不支持比特币主网,也不应将其用于真实货币,因为它仍处于早期阶段,并且存在已知的 bug。

展望未来

正如在 “主张 1 的当前进展” 中提到的,我们将对 Utreexo CSN 进行更多的性能优化,包括加快 Utreexo 累加器和 btcd 组件。目前我们已经意识到很多问题,一旦解决,将加快 CSN 的速度(这只是一个实现和测试的问题)。

实现重组支持的工作于去年开始,但由于有许多其他紧迫问题需要优先处理,因此暂停了这项工作。重组工作将在不久的将来实施。虽然内存池支持的实施还没有开始,但我们已经规划了一段时间。我殷切期望今年能够实现 mempool 支持。

目前 Utreexo 累加器的代码是用 Go 编写的。将累加器代码移植到 Rust 和 C++ 是我们持续努力的工作。我们不确定一切需要多长时间,但我们在代码方面已经有了一定的基础,并且可以很容易地利用人层级并行性。如果大家愿意贡献心力,还有很多事情需要做!