作者:Fanis Michalakis

来源:https://fanismichalakis.fr/posts/anchor-outputs/

本博客的前面两篇 “长文” 致力于搜寻特殊类型的闪电通道,它们 以自身的优势强化了闪电网络的用户体验。这一次,我们要研究 “锚点输出(anchor output)”,其目标是保证通道能快速关闭(即使单方面关闭也不例外)。本文将详细介绍闪电通道是如何关闭的、有什么样的问题、锚点输出如何能解决这些问题。最后,我们将看看这个新机制的推出会如何影响用户。

关闭闪电通道

在闪电网络中,一个通道的状态(例如,通道两端的余额分布)是以 “承诺交易” 的形式保存的。这些比特币交易已经由通道的两位参与者签过名,但不会发到比特币区块链上,除非某个参与者准备关闭通道。如果通道的状态改变了(资金从通道的一端移动到了另一端),前一笔承诺交易就会被 “撤销”,双方会制作和签名新的一笔承诺交易(依然不会广播),来记录新的资金分布情形。

在双方合作关闭通道的情形(也叫 “双向关闭”)中,双方一起构造一笔关闭通道的交易,将通道中的资金根据最新状态分别发给两方的账户。而在关闭交易的构造期间,双方可以讨论他们希望为这笔链上交易支付的手续费。交易构建完成,双方就签名,然后将交易广播到比特币网络中。这比单纯广播最后一笔承诺交易要好,因为双方可以根据交易池的拥堵情形调整手续费。

但在强制关闭的情形中,也就是双方无法就通道关闭交易达成一致的情形中,希望关闭通道的一方要把自己所知的最后一笔承诺交易公布出来 1。这笔承诺交易是在双方最后一次在通道内移动资金的时候合作构造的。在构造的时候,双方也要决定这笔交易的手续费 —— 有点奇怪对不对?他们俩根本就不知道这笔交易会不会真正被发到链上,也不知道这笔交易什么时候会发到链上。

因为在交易构造好之后,手续费就没法改变了;而重新构造交易又需要双方的签名;所以,你必须确保交易支付了足够多的手续费、能够在广播之后的合理时间内得到区块确认 2。这是不可能的,因为你根本不知道这笔交易什么时候会广播。即使你知道它什么时候会广播,你可能也不知道那时候比特币网络的交易手续费(除非那个时刻就近在眼前)。为了避免这种情形,闪电网络实现通常会给承诺交易安排数倍于交易构建时网络手续费水平的手续费。

为什么我们不能使用小额的手续费,等到交易广播时再追加呢?

交易签名之后,其手续费就无法改变了(因为改变手续费会改变整笔交易,让签名作废)。而且,一旦公布,整个网络都会知晓这笔交易。不过,依然有一些办法可以提高(或者说追加)一笔交易的手续费:

  • 构建、签名和公布一笔新的交易,花费跟原交易相同的输入,但支付更高的手续费。这个叫做 “手续费替换(RBF)”。
  • 使用 子交易 花费尚未确认的交易的其中一个输出,并在子交易中附加较高的手续费。这叫做 “子为父偿”,它能激励矿工打包这笔未确认的交易,因为这样他们才能打包这笔高手续费的子交易。

所以,我们不能用 RBF 或 CPFP 来提高交易的手续费吗?这将允许闪电节点为承诺交易附加尽可能少的手续费,等需要的时候再为之追加手续费。

我们当然可以这样做,就是有一点麻烦。

保护网络不受 DoS 攻击

RBF 和 CPFP 在比特币中实现时,还附带了一些保护措施,用于防止恶意人士利用这些新功能来执行拒绝服务式攻击(DoS)。这种攻击的内核就是要求执行会耗尽资源(CPU、内存,等等)的操作,让组成网络的计算机慢下来甚至完全停机。

就 RBF 而言,替换交易必须遵循的一条规则是 “所支付的手续费绝对值不低于原交易的手续费”3。这条规则有两个目的。其一,它要激励矿工打包替换交易(而不是原交易)。其二,它要防止有人在网络中滥发低手续费的替换交易。如果替换交易不必支付更高的手续费,攻击者就可以在网络中大肆广播替换交易、使之在节点网络中转发(也即主动消耗节点带宽),从而零成本地攻击网络(因为这些交易不可能上链)。

至于 CPFP,风险是攻击者利用交易池的工作模式、耗尽一个节点的计算资源。出于效率理由,交易池中的每一笔交易都会指明其依然存在于交易池中的所有的祖先(父代交易、祖代交易,等等)以及后代(子代交易、孙代交易,等等)。这主要是为了帮助矿工挑选整体上支付更高手续费的交易,并拒绝手续费更低的交易。这种设计的一个缺点在于,它会让交易池移除一笔交易的操作变得更加耗费资源,尤其是被移除的交易有许多后代的时候。因此,为防止 DoS 攻击利用这一点,CPFP 有一个限制:交易池中给定的一笔交易不能拥有超过 25 个后代(包括自身在内),而且所有后代的总体积不能超过 101 KvB(它自己也包括在内)。祖先数量的限制也是一样。

交易钉死

这两个限制在防止滥用 RBF 和 CPFP 时是必要的。但它们也有缺点:当某用户想给一笔闪电网络承诺交易追加手续费时,它们也可以反过来被用作攻击界面。这样的攻击叫做 “交易钉死(transaction pining)”,发生在两个(乃至多个)参与者都能给同一笔交易追加手续费的时候。

攻击思路是很简单的:通道的其中一个参与者可以故意为原本的承诺交易追加手续费,使之触发(RBF 或 CPFP)的抗 DoS 限制,使另一方无法再为之追加手续费。如此一来,攻击者就能给对方强加自己的交易版本(以及相应的手续费)、阻止对方按自己的意愿追加手续费。

举个例子,滥用 RBF 抗 DoS 措施的办法是发一笔比原交易体积更大但手续费率更低的交易。因为规则要求的是替换交易所支付的手续费比原交易(及其后代)更高(但并不要求手续费率更高),所以对等节点如果想要使用 RBF 来追加手续费,就必须支付比对方所设置的更高的手续费。

CPFP 的抗 DoS 措施也可以被用来钉死一笔交易,只需要给原交易附加 24 个子交易(或者触发体积限制)即可。另一方将无法再用 CPFP 来为原交易追加手续费。

因此,攻击者可以利用 RBF 和 CPFP 的抗 DoS 措施、在比特币网络负载量很大或手续费很高时推迟承诺交易的确认。这样的攻击,如果精心策划,可以让攻击者从诚实的闪电网络用户手中窃取资金。所以,找出一些微调规则的办法,使我们能安全地为闪电网络承诺交易追加手续费,是至关重要的。

Carve Out 与锚点输出

CPFP 的 “Carve Out” 策略就是这样的微调之一。它允许为一笔交易加入第 25 个后代交易,当且仅当这笔后代交易的重量没有超过 1000 vB、且其只拥有一个未确认的祖先的时候。或者,换句话说,即使一笔未确认的交易已经有 24 个后代 4,Carve Out 也允许为这笔交易追加一个子交易,只要这个子交易的重量没有超过 1000 vB。

但 Carve Out 并不能解决我们的问题。我们还必须保证,在这笔承诺交易公布后,任何一方都能花费一个输出(并且只能花费一个输出)。考虑有 3 个输出的承诺交易。假设 Alice 可以花费一个输出(输出 #1)、Bob 可以花费两个输出(输出 #2 和 #3)。Bob 依然可以钉死这笔交易:先为输出 #2 附加 24 个后代交易,再为 #3 附加一个子交易。他能这样做,完全有赖于 Carve Out,而他这样做了之后,Alice 就无法为这笔交易附加任何子交易了,那会超过 Carve Out 允许的限度。

如果承诺交易只有 2 个输出,每一方都只能花费其中一个输出,情形就完全不同了。Bob 可以为自己的输出附加 24 个后代,但是,他无法为 Alice 的输出附加交易。他也无法为自己的输出附加第 25 个后代,因为 Carve Out 策略只允许第 25 个后代是原交易的直系子代。而 Bob 如果附加,则会称为原交易的孙子的孙子的孙子 …… 等等。另一方面,感谢 Carve Out,Alice 可以为自己的输出增加一笔子交易 5。这笔承诺交易及其后代,在整体上稍微逾越了 25 个后代的规则,但依然在 Carve Out 允许的范围内。这样一来,即使 Bob 尝试钉死这笔交易,Alice 也依然可以使用 CPFP 为自己的输出追加手续费。

我们必须回答的最后一个问题是,如何保证每一方在承诺交易中最多只有一个输出可以花费。无论如何,承诺交易很容易拥有超过 2 个输出,比如通道中拥有一个还未结算的 HTLC(哈希时间锁合约)的时候。

秘诀在于,我们不需要让每一方都 绝对 只有一个输出。我们只需要让承诺交易,在公开之后,每一方都至多只有一个输出可以花费,就行了。这 就是 锚点输出的做法:承诺交易的每一个输出都带有一个 CSV(相对时间锁)条件,来防止这些输出在一个区块内被花费;同时,它增加了两个输出(每一方一个),专为双方可以使用 CPFP 来追加承诺交易的手续费而设。参与者可以在其它输出依然(被时间锁)锁定的时候,使用自己的这个输出作为锚点、附加一笔乃至更多的后代。

摘要:锚点输出是一笔承诺交易的两个输出(各属于通道的其中一方),这样任何一方都可以安全地为承诺交易追加手续费,而不必担心交易被对方钉死。这样一来,双方都有机会为关闭交易设置自己想要的手续费。

综合

承诺交易对闪电网络的安全模型至为关键,因为它允许通道的参与者单方面关闭通道。然而,从承诺交易的构建,到承诺交易的公布,中间可能存在时延,这使得它们可能会因为手续费设置不当而被阻塞在交易池中。幼稚的解决方案是,在构造承诺交易时故意设置过高的手续费;这是不够的,因为等到这笔承诺交易上链时,通行的手续费率可能比这个故意拔高的手续费率还要高,交易依然可能受阻。

手续费追加方法 RBF 和 CPFP 都有抗 DoS 措施,这些措施使它们也不足以应对闪电网络(两方可以提高同一笔交易的手续费)的需要。在 CPFP 中,相关的问题由 Carve Out 策略来解决。利用这种策略,通道的每一方都可以利用未确认的承诺交易中的一个专门的输出(叫做 “锚点输出”),安全地追加手续费。

它会如何影响终端用户?

就我所知,现在 3 大主流的闪电网络实现都已经支持锚点输出。举例来说,在 LND 客户端中,所有承诺交易都有专门的锚定输出、非锚点输出都有长达 1 个区块的相对时间锁,这已经是默认配置。但如果用户没有留存一些钱来发起子交易,这些措施也是无用的。这也是何以 LND 开始强制实施一个它们专有的规则:用户必须为每一个通道保留至少 10 万聪的链上余额。这也是为什么有时候用户会(惊讶地)发现,自己即使拥有 20 万聪,也无法开设一个 15 万聪的通道。这实际上也是促使我开展这份研究的原因:我自己也遭遇到了这方面的困惑。

一些资料

Bitcoin Optech 制作了一些很棒的主题页,覆盖了锚点输出交易钉死CPFP Carve Out

比特币和闪电网络开发者邮件组也是很棒的信息来源。举个例子,Matt Corallo 的初步立场说明

脚注

1. 这就是承诺交易的用处:即使对方不同意(或者离线了),也可以独自关闭交易。

2. 这是极为重要的,因为闪电网络的一部分安全模型就依赖于时间锁。

3. 根据 BIP125,“原交易” 指的是最初的那笔交易以及任何花费该交易但还未确认的交易(其后代)。

4. 它自己也要算进 25 个之中。

5. 在(相当容易管理的)条件下,这笔交易不能超过 1000 vB。