作者:John Newbery

来源:https://johnnewbery.com/an-intro-to-bitcoin-core-fee-estimation

为什么要有手续费?

比特币的区块空间是一种有限的资源,只要价格够低,需求量就一定会超过供给量。假如区块空间是免费的,人们会极尽利用之能事 —— 去中心化赌博啦、上传比特币白皮书的完整备份啦、带时间戳的个人文件啦,都是已经出现过的一些例子。

为了确保有限的区块空间被安排给对它评价最高的人(也就是愿意为之支付最高价格的人),比特币设置了一个手续费市场。实际上,交易池(mempool)就像一个去中心化的清算所(clearinghouse)—— 用户将对区块空间的竞标价格放到交易池中(形式是附加了手续费的交易),而矿工会基于交易所附加的手续费选择交易、放到下一个区块中。你给出的手续费越多,你的交易 “打败” 其他人的交易 —— 矿工选择在下一个区块中包含你的交易而非其他人的交易 —— 的几率也就越大。

为何手续费估计是一个难题

用户怎么知道给出多高的手续费是合适的呢?事实证明这是一个非常难的问题,原因如下:

  • 供给是不可预测的。从较长的时间尺度看,供给是可以预测的。每 10 分钟就能供给大约 2 MB 的区块空间(更准确地来说,每 10 分钟供给 400 万的 block weight)(译者注:“weight” 是比特币在隔离见证升级后使用的度量交易体积的单位)。但因为区块出现的概率是泊松分布的(Poisson distribution),所以在较短的时间尺度上,供给是不稳定且不可预测的。每 100 个区块中,就有一个是在前一个区块挖出后的 7 秒内出现的,还有一个是花了 45 分钟才挖出的。也就是说,我们可能很 “幸运”,几分钟之内就挖出了好多个块,从而交易池内所有的高手续费交易都清空了。另一方面,也有可能一个半小时(甚至更长时间)都出不了一个块,这时候,交易池会逐渐塞满支付更高手续费的交易。
  • 需求也是不可预测的。我们确实可以在交易流中看到循环 —— 周末通常比工作日要更 “安静”,所以交易池会更空,手续费也更低。但是,就像供给端一样,短期内的需求也是不可预测的。举个例子,即使在周末,需求也会在比特币价格剧烈波动时突然高涨。
  • 不同的用户有不同的要求。就像任何市场一样,每个参与者都有不同的 “购买” 区块空间的理由。我可能由一笔非常紧急的交易,希望在接下来半个小时内确认,或者我希望在接下来 6 个小时内关闭一个过期的智能合约,又或者,我想要给一些东西打上时间戳,只要在接下来几周内确认就好。也许并没有一个手续费估计模型能够适应所有场景的需要。

因此,为一个特定的场景估计合适的手续费,是非常困难的,但它又是一个可用的系统的非常重要的方面。要是为你估计的手续费太高,你每次发出比特币交易都会浪费一点钱。要是为你估计的手续费太低,你的交易可能就无法在你想要的时间内确认,这样的系统用起来也会很费劲。

我们可以根据多种数据点来估计手续费。现在我们回过头来看看它们。注意,这些手续费估计技术会被用于全验证节点。我们需要打开交易池、下载完整的区块链,以便基于网络中可以观测到的事件来估计手续费。

基于即时交易池的手续费估计

一种简单的手续费估计算法会检查你的交易池,并将你的交易手续费设置成使你的交易位于支付最高手续费的前 2MB 交易队列的末尾。你可能会期待这样的策略足以使你的交易在下一个区块得到确认。然而,因为下面的原因,事情不会这么简单:

  • 最新的交易可能不会进入下一个区块链。在你提出你的交易的时候,矿工可能已经在挖掘下一个区块了。矿工会不会在下一个区块包含你的交易,取决于他们刷新所挖区块的内容的方式。、
  • 无论你在什么时候把交易发到交易池中,你要等待下一个区块出现的时间的期望值都是 10 分钟。这是区块出现所遵循的泊松分布的一个基本(但经常被误解)的属性。即使你在上一个区块挖出的 8 分钟后将交易发到交易池中,预计你要等待的时间不是 2 分钟 —— 是 另一个 10 分钟。因此,你不是在跟现在位于交易池中交易相竞争,你是在跟接下来(大约)10 分钟内将出现在交易池中的交易相竞争。
  • 仅观察交易池的瞬时状态并没有考虑到区块可能会很快出现或者很慢出现。它也许能告诉你,为使你的交易被打包到下一个(或再下一个)区块中,你应该给出多少的手续费;但它无法告诉你,如果你只需要在接下来 100 个或 200 个区块内打包交易,只需给出多少手续费。
  • 实际上并没有 统一的 交易池。每个节点都维护着自己的交易池,池中的内容因为许多因素而有所不同,例如它运行的时间、它跟其它节点的联通性、它本地的交易池策略,等等。你在自己的交易池中看到的情形,未必是矿工交易池中的情形。

因为这些原因,仅靠对交易池情形的瞬时捕捉,不足以给出估计手续费所需的充分信息。

基于历史区块的手续费估计

这种方法乍看起来似乎更加合理,因为区块链本身就是交易被纳入的不可篡改的记录。但是,如果我们只看历史区块,我们的手续费估计算法就可以轻易被矿工玩弄。矿工可以用私人的、高手续费的交易塞满区块。只看历史区块的算法将被欺骗、一直提高估计值,因为看起来只有更高的手续费才能被区块确认。

这对矿工来说是非常便宜的攻击,因为对他们来说,成本只是因此排除掉的真实交易所支付的手续费。如果我们的手续费估计算法只将先在交易池中出现、然后得到区块确认的交易纳入计算,则矿工的成本会大大提高,攻击也会变得更不可行。矿工将不得不先广播自己的高手续费率交易,因此将承担交易被其他矿工打包、自己真的支付手续费的风险。

基于交易池历史的手续费估计

因此,Bitcoin Core 考虑交易池中交易的历史数据 以及 最新区块中的交易数据。只要我们拥有过去交易的足够大的样本,我们就可以精确地估计让交易在确定区块数量内上链所需的手续费。

Bitcoin Core 如何估计手续费(v0.15 以前)

注 1:Bitcoin Core 的手续费估计法相当复杂。本文只能简单介绍该手续费估计算法背后的概念,也作出了一些简化。想了解更多细节,请看 Alex Morcos 的《手续费估计算法的概要讲解》 ;若想获得全部细节,请看代码本身

注 2:本文仅介绍 Bitcoin Core v0.14 的手续费估计算法。从 v0.15 开始,这套算法有了许多更改,我会在下一篇文章中讲解。

概念:bucket 和 target

我们将从介绍该算法所用的一些概念开始:bucket(直译为 “桶”)和 target(直译为 “目标”)。

比特币交易的手续费率会落在一个从 1 聪/字节几百聪/字节 的区间中,一些异常值甚至会更大(见 Jochen Hoenicke 的当前交易池情形可视化)。跟踪每一笔交易的手续费率会消耗大量的计算和存储空间,所以 Bitcoin Core 做的第一件事是根据手续费率将这些交易分组,分到 “桶” 里面,每个 “桶” 都对应着一个手续费率区间。有点像 Jochen 的可视化工具,按手续费高低将交易分组并标上不同的颜色。

Bitcoin Core 希望能够对非常大的手续费率区间作出估计,所以手续费最低的一个 “桶” 包含的是从 1 聪/字节 到 1.1 聪/字节的交易(1.1 比 1 高出 10%),下一个桶包含的是从 1.1 聪/字节到 1.21 聪/字节的交易(1.21 又比 1.1 高出 10%),再下一个桶是从 1.21 到比 1.21 高出 10% 的值,以此类推。这个指数上升的间隔一直延伸到 10000 聪/字节。最后一个 “桶” 容纳高于 9400 聪/字节 的交易。按照今天的汇率(4200 美元)计算,这么高的费率意味着一笔中等大小的交易(225 字节)需要支付 88 美元。

第二个概念是一笔交易进入交易池时候的区块号与被确认的区块号的差值。我们管它叫 “目标”(因为我们也会把这个值用在估计一笔交易需要附加多少的手续费才能在一定数量(目标)的区块内得到确认。

跟踪

为了估计一笔交易需要附加多少手续费,才能在一定数量的区块内得到确认,Bitcoin Core 存储着一个交易从进入交易池到进入区块的历史纪录。该记录跟踪:

A. 每一个手续费率 “桶” 内的交易数量;以及

B. 对任意的 “桶”-“目标” 对,成功在目标数量区块内进入区块链的交易数量

对任意的 “桶”-“目标” 对,Bitcoin Core 可以找出该桶内的一笔交易在目标个区块内加入区块链的概率,即以 B 除以 A。

响应主流手续费率的变更

比特币网络是一个动态变化的系统,而且主流的手续费率会持续变化(若不是如此,我们也完全不需要估计手续费了!!)。我们希望手续费估计算法能响应主流手续费率的变化,并为近期发生的事件赋予更高的权重,因此它们更有可能暗示了未来的手续费率。

因此,Bitcoin Core 是有了一个 指数加权的移动平均值,简单来说,就是更关注较新的区块而不是较老的区块。每次 Bitcoin Core 收到一个区块时,它就给旧区块的计数器乘以一个 0.998 的 衰减值(等价于设置了 346 个区块的 半衰期)。这意味着,当一个区块成为过去,它在我们的手续费估计算法中的权重会不断下降。2.4 天以前的一个区块,权重大概是最新区块的一半。而 4.8 天以前的区块的权重则是最新区块的 1/4,7.2 天前的则是 1/8,等等。

注意,这个移动平均值是独立于前面提到的 24 个区块的目标区间的。我们仅对小于等于 24 的交易上链目标数量感兴趣,但我们会使用往前追溯许多区块的历史数据。

estimateSmartFee():一个有用的接口

为了创建一个有用的接口,我们需要知道用户想问的问题是什么。比特币用户几乎肯定不会问:

要是我为自己的交易附加一个位于 X 桶的手续费率,它在 Y 个区块内得到确认的概率是多少?

相反,他们想知道的是:

如果我想让自己的交易在 Y 个区块内确认,我该附加多少的手续费呢?

estimateSmartFee() 就是被设计用来干这件事的,其工作流程如下:

  1. 用户提供一个期望交易能够确认的目标区块数量。
  2. 对该目标数量,检查手续费最高(高于 9400 聪/字节)的桶,然后验证,若使用这个费率,则交易能在目标个区块内在确认的概率超过 95%(要是该验证出错,那我们的麻烦就大了 —— 这意味着我们甚至支付 9400 聪/字节 的天文数字都不足以让交易上链。还记得吗,按当前的汇率这表示中等大小的交易需要支付约 88 美元)。
  3. 检查次高的桶(大约是 8600~9400 聪/字节),然后验证,若使用这个费率,则交易能在目标个区块内在确认的概率超过 95% 。
  4. 以此类推,逐步往下检查,直到发现一个桶,其在目标个区块内确认的概率是低于 95% 的。
  5. 取上一个桶(即能以 95% 的概率让交易在目标个区块内确认的最低价格的桶),并返回桶内所有交易的手续费中位数。

在计算每一个 目标-桶 对的概率时,算法都足够聪明,它可以确保有足够大的交易样本。这意味着,即使少数的高手续费交易因为某些理由而没有被确认,手续费估计算法也不会失效。

我们使用 95% 的高阈值,是因为我们希望估计算法可以告诉我们,使用哪一档的费率可以几乎保证交易能在目标区块内被确认。手续费估计算法并无为何一笔交易被打包的完美知识(举个例子,也许矿工为特定一族交易开了后门,使它们能比使用相同和更低费率的交易更快被确认),所以我们虽然设置了很高的标准,但没有设成 100%。

estimateSmartFee()estimatesmartfeesendtoaddresssendmany 三种 RPC 方法使用,也会在图形界面中发送交易时使用。

结论

手续费估计是一个难题。手续费估计算法,本质上是基于一个不完整的、关于过去的图片,来回答关于未来的问题。尚不清楚我们能如何度量不同手续费估计算法的性能,一部分原因是不同用户对系统有不同的要求。更进一步地说,比特币的手续费市场也是持续变化的,今天成功的算法未必能在更大的系统层变更之后依然成功(例如,出现了同样使用 SHA-256 挖矿的另一种币、隔离见证交易的出现、整体需求和交易池情形出现了长期变化,等等)。Bitcoin Core 提供了一种适合于大部分用户、适应于大部分手续费市场情形的估计算法。其它钱包软件可能使用不同的办法来估计手续费,在特定的一些场景下可能更加成功。

Bitcoin Core 的手续费估计算法并不想过度工程。它只记录和报告关于过去事件的有意义统计数据,并使用这些数据,为用户合理估计为使交易在一定数量的区块内确认应附加多少手续费。它并不尝试预见未来,而且也很容易解释一个估计是如何基于过去的统计数据产生出来的。它可以应对主流手续费市场的变化,并在广泛的条件下给出合理的估计。

更精巧的算法可能会尝试更前瞻一些。但是,算法越复杂,描述其操作和结果就越难,证明该算法可以抗操纵也越难。

有一些情况下,Bitcoin Core 的估计表型不是特别好,尤其是主流手续费市场出现剧烈变动的时候。Bitcoin Core v0.15 的手续费估计算法基于 v0.14 算法的框架,但做了许多优化,使之在剧烈变化的环境和异常事件中更加强健。我会在下一篇博文中介绍这些变更。【编辑:v0.15 的变化在此处可见:https://johnnewbery.com/whats-new-in-bitcoin-core-v0.15-pt2

感谢 David A. Harding 和 Jimmy Song。

(完)