作者:ZmnSCPxj
引言
我们这样定义 LSP(闪电网络服务商)的 “最后一公里” 问题:
- 从闪电网络接收自己的第一笔比特币的新用户必须为 “入账流动性(收款额度)” 支付。
- 与获取流动性相关的区块链操作必须在多个新用户间分摊,才能让每个用户的成本降下来。
除了上述问题,我们的解决方案还必须受到下列条件的约束:
我们必须保证 LSP 不能偷盗资金,即,仅仅实现 “只需一个诚实参与者” 的安全假设 是不够的,除非每个终端用户都可以为自己担当那个诚实参与者。
必须无需变更区块链的共识。
- 比特币区块链协议在实践中已经定型(ossified)。因为计划中每 4 年发生一次的增发减半会导致比特币回来的突然升高,进而导致对比特币兴趣的突然增加,大量的新用户会从成为跟比特币的共识有利益相关的实体。当上一批用户被说服了要变更共识的时候,新的一批用户又进来了,他们也必须同意相同的共识变更。如果一项共识变更无法在短于减半周期的时间里获得实际共识,它就可能根本不会发生,从而导致实践中的协议定型(更小、更易于处理的变更可能依然能通过,但更复杂的变更,比如 “限制条款(covenant)”,可能永远不会获得共识)。
当 LSP 必须重新分配资金的时候,我们必须能够对付少量终端用户离线的情形。
- 随着共享同一个 UTXO 的用户数量增加,出现一个乃至多个用户离线情形的概率也会上升。因此,当覆盖大量终端用户时,应对部分终端用户离线的能力是必要的。
- 已经证明,运行在移动端的软件可以通过 Android 或 iOS 的应用通知机制唤回到线上,因此,在现实中,移动客户端的在线是有很高保证的。不过,移动端依然可能偶然断网,所以,它们的运行时间依然不会像非移动设备那么好。因此,整套机制依然要能应对少量用户离线的情形。
上述约束立即排除了 Ark 和 BitVM2 桥。如果没有限制条款,这两者都有 “至少一个诚实参与者” 假设,而依照上文的推理,限制条款在现实中不会激活(OP_CTV
提议在 2020 年就已经大体完成了,但到今天(2024年)也还未获得共识;这已经超过了减半周期,所以永远不能获得共识了;SIGHASH_NOINPUT
的遭遇甚至更加糟糕)。两者都可以取消 “至少一个诚实参与者” 假设,仅当 LSP 需要重新分配资金时所有终端用户都同时在线;这就违反了上述最后一个约束。
在本文中我会提出一种叫做 “SuperScalar” 的构造,实际上,是一种分层超时树结构的 Decker-Wattenhofer 通道工厂。
配方
首先,我要(希望是)简单介绍一下组合出这种构造的三个模块:
- Decker-Wattenhofer 基于递减
nSequence
的链下更新机制; - “超时树(timeout tree)”,尤其是使用全体签名来模拟
OP_CTV
的变种,我称为 “超时签名树”; - 筑梯法。
请随意跳过你已经很熟悉的部分。
Decker-Wattenhofer
Decker-Wattenhofer 递减 nSequence
机制是一种链下的密码货币系统,允许一组利益相关的用户对一些状态变更达成一致,而无需每一个状态变更都发布到区块链上(这就是它被归类为 “链下” 机制的原因)。这听起来跟 Poon-Dryja 机制(译者注:即闪电通道机制)很像,但有如下区别:
- 参与者数量:
- Poon-Dryja 机制严格限定只能两方参与;
- Decker-Wattenhofer 递减
nSequence
机制可以容纳任意数量的参与者;
- 状态变更的次数:
- Poon-Dryja 机制理论上可以无限次更新状态(在现实的部署中,比如闪电网络的 BOLT 规范,会限制状态更新的次数,但这个上限是以十亿次为单位的,因此可以说也是无限的)。
- Decker-Wattenhofer 递减
nSequence
机制允许的状态更新次数更少。单个无连锁(chained)的构造可以提供少量次数的状态更新(少于 100 次,并且在实践中,每个有连锁的机制只能提供 4 次或 2 次)。标准的提议是连锁多个这样的构造,实际上就是让每一个被连锁的构造中可发生的状态变更次数倍乘(例如,连锁 3 个构造,每个构造都允许发生 4 次变更,那么就允许变更 4 x 4 x 4 = 64 次)。
- 单方面退出的规模和便利性:
- Poon-Dryja 机制只需要一笔承诺交易就可以单方面退出,同时需要额外一笔交易来回收资金。从发起承诺交易到允许取回资金之间的时延是一个常量。
- Decker-Wattenhofer 递减
nSequence
机制需要一笔 “弹出(kickoff)” 交易来开启单方退出流程,然后每一层都需要额外一笔带有时延的交易。如果连锁多层这样的构造来增加允许的状态变更次数,单方面退出时候所需的额外交易数量也会增加,所以所需的区块空间在实际中是O(log N)
,其中 N 是可支持的状态变更次数。在资金可以取回之前,每一层的交易都有可变的时延。
Decker-Wattenhofer 机制和 Poon-Dryja 机制都无需共识变更就可以在比特币中实现。
跟 Poon-Dryja 机制一样,Decker-Wattenhofer 递减 nSequence
机制具有一个链上 UTXO 作为 “注资输出点(funding outpoint)”。这个输出点是一个简单的 n-of-n 多签名装置,由签名状态变更的所有签名人组成。
如果是一个单层的 Decker-Wattenhofer 递减 nSequence
机制,那就只有两笔交易:
- 一笔 “弹出” 交易,花费注资输出点,并产生一个 n-of-n 多签名输出
- 一笔 “状态” 交易,花费弹出交易的输出,并且由
nSequence
要求一个具体的相对时间锁。该交易的输出就是被所有签名人一致同意的状态。
对于最初的状态,“状态” 交易的 nSequence
是设计好的最大相对时间锁。 举个例子,对一个意在允许 4 次状态变更的设计而言,合理的初始状态是 432 个区块的相对时间锁。
在变更状态的时候,如果所有签名人都同意一个新状态,他们就创建一笔花费注资输出点的新的状态交易,但带有比上一笔状态交易更短的 nSequence
相对时间锁。举个例子,对一个允许 4 次状态更新的设计来说:
状态编号 | 相对时间锁(以区块数量为单位) | 备注 |
---|---|---|
0 | 432 | 初始状态 |
1 | 288 | |
2 | 144 | |
3 | 0 | 最终状态;这个实例只能结束了 |
这就是它为什么叫做 “递减 nSequence
”机制;每次签名人们要批准一个新的状态时,表示新状态的交易就会使用一个更小的 nSequence
数值,直到它变成 0 。
因为最新的状态总是具有更短的相对时间锁(相比任何旧状态),它就总是可以比较早的状态更快获得确认。这一机制保证了,假设区块链层不拥堵,最新的状态就是在单方退出场景中会得到确认的那一笔交易。
nSequence
相对时间锁的差值需要足够大,以使各方都可以合理假设,即使在拥堵状态下,最先的状态也能比旧的状态先一步得到确认。因此,上文的案例使用的差值是 144 个区块。
如上文所说,这样的一套构造,只能更新少量几次状态,就会达到 “最终状态”。因此,Decker-Wattenhofer 建议在实际使用中连锁多个这样的构造。整个链条的第一个构造有一个输出,是签名人的 n-of-n 多签名装置;这个输出又作为下一个构造的输入,以此类推。只有最后一个构造具有状态交易(有多个输出)、表示整个系统中的资金的最新状态。
实际上,上一个构造的 “状态” 交易是通过下一个构造的 “弹出” 交易给省去(cut-through)了。
连锁的 Decker-Wattenhofer 机制就类似于多个数位的倒计时器。每当批准了一个新状态,链条中最后一个构造 —— 离注资交易最远的那个构造 —— 就倒数一次。不过,要是最后一个数位已经变成了 0,那么就在倒数第二个数位上倒数一次,并将最后一个数位重置为最大的 nSequence
。 类似地,要是倒数第二个数位变成了 0,那么就在倒数第三个数位上倒数一次,然后将后续的数位都重置为最大的 nSequence
。以此类推。
实际上, 链条中的最后一个构造是最有可能发生状态变更的(每次状态更新都要变更它),而链条中的第一个构造是最不可能发生变更的。
Initial state nSequence nSequence +----+------+ +-----+------+ +-----+-----------+funding -->| |n-of-n|-->| 432 |n-of-n|-->| 432 |...state...| +----+------+ +-----+------+ +-----+-----------+ kickoff tx state tx state tx======> nSequence nSequence +----+------+ +-----+------+ +-----+-----------+funding -->| |n-of-n|-->| 432 |n-of-n|-->| 288 |...state...| +----+------+ +-----+------+ +-----+-----------+ kickoff tx state tx state tx======> nSequence nSequence +----+------+ +-----+------+ +-----+-----------+funding -->| |n-of-n|-->| 432 |n-of-n|-->| 144 |...state...| +----+------+ +-----+------+ +-----+-----------+ kickoff tx state tx state tx======> nSequence nSequence +----+------+ +-----+------+ +-----+-----------+funding -->| |n-of-n|-->| 432 |n-of-n|-->| 0 |...state...| +----+------+ +-----+------+ +-----+-----------+ kickoff tx state tx state tx======> nSequence nSequence +----+------+ +-----+------+ +-----+-----------+funding -->| |n-of-n|-->| 288 |n-of-n|-->| 432 |...state...| +----+------+ +-----+------+ +-----+-----------+ kickoff tx state tx state tx
超时树
超时树是一种结合了 OP_CTV
树和超时的机制。
在没有 OP_CTV
的时候,可以使用一种变种:利用所有参与者的签名来强制实施 OP_CTV
限制条款。这一变种(我称为 “超时签名树” )是我要解释的重点。此外,我们还考虑了单个 LSP 向自己的客户提供这样一种机制的情形。
在超时签名树上,一个 LSP L
可以向一组客户提供闪电通道。单个得到确认的链上 UTXO 就可以为与不同客户开设的多条通道提供保证。
在初始化阶段,这个 LSP 先创造一棵由交易组成的树。在非叶子的节点的输出中,其子树上的客户会跟 LSP 一起组成一个 n-of-n 的多签名装置。这是标准的交易树构造,但超时树还添加了一个替代性的花费条件:LSP 可以在一段时间之后独自花费资金。同样的替代条件也存在于注资输出点中。
举个例子,如果 LSP L
有 8 个客户,标记为 A
到 H
,然后大家会形成一棵超时树。在(将得到链上确认的)注资输出点上会有这些条件:
A & B & ... & H & L
L & CLTV
那么,整棵树看起来就是这样的:
+--+---+ | |A&L| LN channel +>| +---+ | | |B&L| LN channel +--+----------+ | +--+---+ | | (A&B&L) |-+ | |or(L&CLTV)| +--+---+ +>| +----------+ | |C&L| LN channel +--+----------+ | | | (C&D&L) |-->| +---+ | |(A&..&D&L)| | | |or(L&CLTV)| | |D&L| LN channel | |or(L&CLTV)|-+ +--+----------+ +--+---+funding-->| +----------+ | |(E&..&H&L)|-+ +--+----------+ +--+---+ | |or(L&CLTV)| | | | (E&F&L) | | |E&L| LN channel +--+----------+ | | |or(L&CLTV)|-->| +---+ +>| +----------+ | |F&L| LN channel | | (G&H&L) | +--+---+ | |or(L&CLTV)|-+ +--+----------+ | +--+---+ | | |G&L| LN channel +>| +---+ | |H&L| LN channel +--+---+
(译者注:这棵树也可以从叶子往树根看。每个叶子都是 LSP 跟一个客户的闪电通道;而叶子节点以上,每一个节点都是下面所有参与者的多签名装置,外加 LSP 的超时花费装置。)
这个超时条件迫使所有的客户都要在超时之前回到线上,并退出这样的构造。退出可以是单方面的,也可以跟 LSP 配合。
在合作式退出情形中,客户端可以直接将自己在超时树上的通道中的所有资金通过闪电网络转移出去,可以通过互换服务转为链上资金,也可以转给同一个 LSP 的新的超时树,还可以转给另一个 LSP。
单方面退出就意味着,要把从树根到自己的闪电通道输出路径上的交易都公开,然后再单方面退出闪电通道(预期是 Poon-Dryja 通道)。
使用树结构的好处在于,单方面退出的代价比较小;一个客户要退出,仅仅只需发布 O(log N)
笔交易,然后大部分其他客户都可以留在树上。如果不使用树结构,那么一个客户单方面退出就会导致所有客户都单方面退出。
超时条件的好处在于,它鼓励所有客户同时退出,有可能是合作式退出,然后 LSP 就只需要发起一笔只有一个输入的交易(使用注资输出点中的 L & CLTV
条件),就可以从构造中清扫资金。即使一些客户也想要单方面退出,许多资金也可以通过每一个中间输出的 L & CLTV
条件来取回。
阶梯式
许多金融机构都提供一种金融合约:储户可以存一些钱进去,但在一段时间内无法取回,哪怕想取回一部分也不行;到期之后,储户可以取回原本的所有资金,外加一笔利息。这种合约也是不可转让的。这样的合约有许多名称:
- 存款证明(美国)
- 有保证的投资证书(加拿大)
- 定期存款(其它国家)
这样的合约是不灵活的;前面已经说了,到期之前你都无法取出资金。但是,精明的投资者会将自己可使用的自己分配到多个这样的合约中,让到期的日子错开一个月、一年。这种技术叫做 “筑梯法”。
举个例子,一个投资者可能有三份这样的合约,到期时间分别是 2024 年 12 月,2025 年 12 月以及 2026 年 12 月。在 2024 年 12 月的时候,第一份合约到期,然后这名投资人可以取出部分资金,然后将剩余资金重新投入到一份 2027 年 12 月到期的新合约中;或者,添加更多资金、一起投入到新合约中;或者,不再签订新合约,从而结束筑梯。
筑梯法给投资人提供了每月或者每年一次改变投资额度的能力,具体取决于你的阶梯有多宽。因此,即使基础的合约是不灵活的,筑梯法也能让投资者重新拿回少许灵活性,同时保持长期定期存款的优势。
SuperScalar 机制
筑梯法超时树结构的 Decker-Wattenhofer 通道工厂就是上述三种元素的结合。
超时树结构的 Decker-Wattenhofer 通道工厂
首先,让我们演示这两者的结合;我们会在后文的一个小节中专门把筑梯法加进去。
假设一个 LSP L
有 8 个客户,编号从 A
到 H
。因此,注资输出点有以下两种花费条件:
A & B & ... & H & L
L & CLTV
在初始化的时候,这个 LSP 要安排下列交易让客户签名,其中注资(funding)交易是一个 n-of-n 的多签名交易(由 A...H
和 L
组成):
nSequence +---+---+ | |A&L| LN channel | +---+ +-->|432|B&L| LN channel | | +---+ | | | L | | +---+---+ | | +---+---+ | | |C&L| LN channel +--+-----+ | | +---+ nSequence | |A&B&L|-+ +>|432|D&L| LN channel +---+----------+ +>| +-----+ | | +---+ | |(A&..&D&L)| | | |C&D&L|---+ | | L | +--+---------+ | |or(L&CLTV)|-+ +--+-----+ +---+---+funding->| |A&...&H&L|->|432+----------+ +--+---------+ | |(E&..&H&L)|-+ +--+-----+ +---+---+ kickoff tx | |or(L&CLTV)| | | |E&F&L|---+ | |E&L| LN channel +---+----------+ +>| +-----+ | | +---+ state tx | |G&H&L|-+ +>|432|F&L| LN channel +--+-----+ | | +---+ kickoff | | | L | tx | +---+---+ | | +---+---+ | | |G&L| LN channel | | +---+ +-->|432|H&L| LN channel | +---+ | | L | +---+---+ state tx
基本上,从一组客户建立一棵交易树的规则,利用了 “从叶子到树根” 的构造顺序:
- 首先,根据自设的叉数,将客户分散到叶子节点上(上例中的叉数是 2)。
- 这些叶子交易的输出是 LSP
L
与相应客户的通道,还有一个额外的输出(一笔额外的资金)由L
持有。
- 这些叶子交易的输出是 LSP
- 叶子节点总是状态交易。
- 它们有递减的
nSequence
。
- 它们有递减的
- 根据所需的叉数,建立这些叶子在树上的父节点。
- 状态交易的父交易是弹出交易。
- 它们的输出仅仅是相应子交易的所有者的 n-of-n 多签名。
- 弹出交易的父交易是状态交易。
- 状态交易输出除了有其分支交易的所有者的 n-of-n 多签名花费条件以外,还有一个替代性的
or (L & CLTV)
花费条件。 - 状态交易的输入有递减的
nSequence
。
- 状态交易输出除了有其分支交易的所有者的 n-of-n 多签名花费条件以外,还有一个替代性的
- 状态交易的父交易是弹出交易。
- 重复上一步,直至得到一个根节点。
- 如果最终的根节点是一笔状态交易,那么就在前面再添加一笔单输入、单输出的弹出交易。否则,直接使用这个根节点作为第一笔弹出交易。
- 在叉数为 2,客户数量为 8 时,根节点恰好是一笔弹出交易。
- 如果例子中的客户数量提高到 16,那么一次单方面退出所需发布的交易数量就是 4 笔,另外还需要在闪电通道中单方面退出的交易。
在树的分层中交替使用弹出交易和状态交易的原因,我们会在后文解释。
糟糕,A 需要入账流动性!
假设 A 在上述树结构中、她和 L
的通道中用尽了收款额度。那么 L
该如何为 A
提供流动性,而免于发起链上交易呢?
LSP 可以唤醒 B
—— 并且只需要 B
(假设A
在请求更多收款额度时已经上线)—— 以更新包含 A & L
通道和 B & L
通道的叶子节点。因为这个叶子节点只包含 A
、B
、L
的资金,所以只需要这三位在线;因此,这个机制可以更好地应对部分参与者离线、而 A
需要额外收款额度的情形。
当然,如果 B
无法在线,那么 LSP 就必须尝试别的办法,例如,在链上发起交易。这可能产生需要支付的链上手续费,LSP 会向 A
收取。
假设 B
回到了线上。那么,L
可以用自己专用的资金向 A & L
的通道注入资金。而无需要求客户C...H
在线。
当然,可能这个叶子中 L
自有的资金已经花完了,或者这个叶子已经到搭了最终的状态(即,nSequence = 0
)。这时候,这个 LSP 可以沿着树向上、找到更高层的状态交易、唤醒其他客户,以将其它叶子上的专有资金转移给 A
,并重设该层以下的节点的 nSequence
。这增加了需要同时在线的客户的数量,但依然不要求 所有 客户都在线。
实际上,不论什么时候一些客户用尽了入账流动性,最末端的状态交易都是最有可能要更新的。不过,如果一个叶子中的 L
专有资金已经用尽,那么就需要更新更上层的状态交易,让 LSP L
可以从别的叶子分配资金给 A
。
出事了,A 要退出!
假设 A
决定单方面退出。原因可能多种多样;重要的事情是,LSP 的任何客户都应该有能力单方面退出,并且需要发布的交易应该尽可能少。这种保证防止了 LSP 跑路,还保证了用户的自主性和资金控制。
为了让 A
退出,我们首先需要让 A & L
通道的注资输出点得到确认,然后使用标准的 Poon-Dryja 通道单方退出程序完全退出。因此,A
必须先公开从树根到自己的通道注资输出点的交易,如下图:
nSequence +---+---+ | |A&L| LN channel | +---+ +-->|432|B&L| LN channel | | +---+ | | | L | | +---+---+ | state tx | | +--+-----+ | nSequence | |A&B&L|-+ +---+----------+ +>| +-----+ | |(A&..&D&L)| | | |C&D&L| +--+---------+ | |or(L&CLTV)|-+ +--+-----+funding->| |A&...&H&L|->|432+----------+ kickoff +--+---------+ | |(E&..&H&L)| tx kickoff tx | |or(L&CLTV)| +---+----------+ state tx
不过,我们还要注意,得到确认的弹出交易的任何输出都必须被一笔得到确认的状态交易花掉。这是因为状态交易都有 nSequence
,而更小 nSequence
相对时间锁的交易应该在更大 nSequence
的交易之前得到确认,否则 Decker-Wattenhofer 机制就崩溃了(可能会让更旧的状态先得到确认)。
因此,在 A
想要退出的情形中,不仅同一叶子上的客户(B
)要在无意中退出,上一层状态交易中的邻居 C
和 D
也要退出。
nSequence +---+---+ | |A&L| LN channel | +---+ +-->|432|B&L| LN channel | | +---+ | | | L | | +---+---+ | | +---+---+ | | |C&L| LN channel +--+-----+ | | +---+ nSequence | |A&B&L|-+ +>|432|D&L| LN channel +---+----------+ +>| +-----+ | | +---+ | |(A&..&D&L)| | | |C&D&L|---+ | | L | +--+---------+ | |or(L&CLTV)|-+ +--+-----+ +---+---+funding->| |A&...&H&L|->|432+----------+ kickoff state tx +--+---------+ | |(E&..&H&L)| tx kickoff tx | |or(L&CLTV)| +---+----------+ state tx
这也解释了为什么我们在树上要交替使用 “状态” 交易和 “弹出” 交易,而不像一般的 Decker-Wattenhofer 构造那样,只有第一笔交易才是 “弹出” 交易。如果我们模仿一般的结构,那么只要一个客户需要退出,下游的所有状态交易(可能各有不同的 nSequence
相对时间锁)都必须发布,不然 Decker-Wattenhofer 机制就有被打破的风险。穿插的 “弹出” 交易正是为了充当后端,保证一个参与者单方面退出时只需要发布 O(log N)
笔交易,而不是整棵树 O(N)
。
这也是为什么弹出交易的输出不需要时间锁分支 L & CLTV
。弹出交易的输出必须被该输出的最新状态交易花掉,而状态交易已经有时间锁分支了。
在上面的例子中,B
、C
和 D
依然可以在自己的通道中转发 HTLC,但不再能便宜地向 L
购买额外的收款额度。相反,L
将需要在链上拼接额外的入账流动性,这会更贵。(也就是说,一个参与者退出不会导致 所有 参与者都退出;但依然会导致其他 一些 参与者部分退出;“部分” 的意思是他们的通道还在,但不能便宜地获得收款额度了。)
不过,从 E
到 H
的客户依然能够用上述机制购买入账流动性,因为他们所在的树并没有被发布到链上,而且依然能在链下更新。此外,得到超时窗口要结束的时候,这些客户可以通过闪电网络发起合作式退出(在他们跟 L
的通道中也不会再剩下资金),然后 L
就可以通过状态交易的 L & CLTV
分支清扫资金、让资金重新流动起来。
筑梯法
现在,加入第三种元素。
从 LSP L
的观点看,上述机制是一项投资。L
的希望是投资中可以赚取回报,手段有:
- 闪电网络路由费
- 在链下更便宜地售卖流动性
- 为维护整个机制而收取的手续费
此外,由于超时分支的存在,在到期之前,LSP 无法轻易取回资金。
因此,一个超时树结构的 Decker-Wattenhofer 实例非常像一笔定期存款,从 LSP 的角度看。
而就像我在前面提到的,精明的投资者会使用多个定期存款合约来筑梯,从而获得一些资金分配上的灵活性。
LSP 自身可以运行多个超时树结构的 Decker-Wattenhofer 实例,面对不同的客户、使用不同的条款,就像传统金融中的定期存款阶梯。在一个实例到期的时候,这个 LSP 可以开启一个新的实例、邀请即将结束的实例中的用户参加新的实例,还可以为转移收费。然后这个 LSP 就可以通过超时分支,在链上从即将结束的实例中回收资金。他会从路由费、销售收款额度和转移到新实例的特权中获得收入,而这些收益会保留在闪电通道中。
举个例子,一个 LSP 可以同时运行 30 个 Decker-Wattenhofer 工厂,每一天都会有一个工厂到期。当一个工厂即将结束的时候,他可以邀请该工厂中的客户进入一个新的工厂。这个 LSP可以将过期工厂中的钱投入新工厂、创建一个新的为期 30 天的工厂。然后,客户也可以从即将过期的工厂中转出资金到新工厂中。等到所有的客户都已经从即将过期的工厂中退出、合约完结,这个 LSP 可以直接花费整个 UTXO,交易只有一个输入和一个输出。
在下面这个具体的例子中,一个 LSP 构筑了由 9 个超时树 Decker-Wattenhofer 工厂组成的阶梯。每一级阶梯都有 7 天的 “活跃期” 和 2 天的 “结束期”。在结束期,客户可以加入在那两天中新建的工厂之一、将自己的资金转移到新工厂的一条新通道中。这给了用户一点余地;如果他们错过了第一天,还有机会在第二天转移。在实际的部署中,我会建议使用 30 天的活跃期和 3 天的结束期;因此,一个 LSP 将需要同时维护 33 个工厂。理想情况下,每天都会发生一笔只有一个输入和一个输出的交易;虽然如果某个客户不上线的话,这个 LSP 就需要发布从树根到该客户的输出的交易,这会增加需要发布的链上的交易。
Legend: ===== Active Period ::::: Dying PeriodDay | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 | 13 | 14 | 15 | 16 ===================================:::::::::: <---- each of this a factory ===================================:::::::::: ===================================:::::::::: ===================================:::::::::: ===================================:::::::::: ===================================:::::::::: ===================================:::::::::: ===================================:::::::::: Client can move ^ ===================================:::::::::: funds from first -----------+----^ ===================================:::::::::: factory to either of ^ these two new LSP uses the funds of the factories first factory to build this factory
不幸的是,因为缺乏限制条款,客户们需要在 LSP 构造新工厂的时候同时在线。如果他们错过这个时间,就需要在结束期的下一天再次尝试;如果他们在结束期的最后一天也错过了,他们就必须退出,要付出所有的代价。OP_CTV
允许 LSP 抢先将客户添加到新的工厂中,不需要客户立即回到线上(客户可以在结束期的 任何 时间回到线上,而不像无限制条款情形中,客户需要在结束期内的 一个 具体时间上线)。不过,这也有缺点,如果那些客户 真的 退出,而不是加入新工厂,LSP 将被迫在下一个活跃期锁定资金;而且,因为这个客户已经离开了,LSP 就不能获得 TA 的签名,也就无法利用 TA 的签名在链下重新分配资金 ,这会影响树上的相邻客户(即,同一棵树上的 其他 用户也将将无法便宜地买到入账流动性,因为 LSP 抢先将客户加到树上、但这个客户却退出了,(可以假设)不会再与 LSP 有什么联系)。
定期从旧工厂转移到新工厂的要求,也为流动性费用的定价提供了一个便利的节奏。
结束期越长,LSP 需要额外投入的资金就越多。这些资金也需要有回报,因此,延长结束期需要增加向客户收取的手续费。这是一个简单的 方便 vs. 代价的考量。
为了降低客户不得不单方面退出的概率,LSP 可以提供一种合作退出服务:客户将工厂内的资金置换成链上资金(使用标准的 HTLC,也即极为常见的 链下-链上 互换)。这让客户可以退出到链上,而无需发布单方退出交易;单方面退出会导致更多 UTXO 发布,也会增加 LSP 在相关工厂过期后管理链上资金的成本。
现实考量
希望我已经解释清楚了筑梯法超时树结构的 Decker-Wattenhofer 通道工厂的概念,让我们转向跟这个机制相关的现实考量。
为什么要有 L ?
在这个提议中,每一个叶子都带有一个专属于 LSP L
的输出,可以用来动态向需要收款额度的客户分配资金。
这就带来了一个问题:我们能够取消这个 L
输出吗?
举个例子,假设所有的叶子都仅仅由通道组成。所以 A
和 B
就会有一笔叶子交易,包含了通道 A & L
和 B & L
。
假设 A & L
用尽了 A
的收款额度,而 B & L
拥有收款额度。那么 LSP 可以从跟 B
的通道中移动资金到跟 A
的通道中。
问题在于,这不是激励兼容的。B
没有激励参与这个新状态的签名:因为他会损失有价值的东西,收款额度!
假设 A
和 B
都是在两个方向上都有大量闪电支付的用户。如果 B
签名了损失收款额度的交易,那么只要他先遇到一个出账支付的峰值,稍后遇到一个入账支付的峰值,就不得不向 LSP 购买更多收款额度。如果 A
已经用完了可以从状态变更中获得的收款额度,那么 LSP 就不得不移动到上一层、从其他客户那里获得更多流动性。这就需要更多的客户回到链上,从而也提高了其中一些客户不能按需在线的概率,这就强迫 LSP 发起昂贵的链上支付来让 B
获得更多收款额度。显然,更高的支出也会被 LSP 转加给客户。
那么,你可能会提议,LSP 可以给 B
支付、购买他手上的资金。
问题在于,最初,收款额度是 B
从 LSP 手上买的。 LSP 为了入账流动性而给B
支付,实际上相当于给一项已经付款的商品退款。你可以想象,退款总是糟糕的客户体验;作为流动性的卖家,LSP 肯定希望理想情况下所有已经售出的流动性都不会有人回头退款,就像每一个商家都希望的那样。
在实践中,如果每一单位的入账流动性都有一个价格,而且这个价格对 A
和对 B
是一样的,那么来自 B
的退款就会跟来自 A
的付款完全相等。也就是说,LSP 将根本没有动机建立这样的机制,因为根本无法从销售活动中赚到钱;从 A
手上获得的支付,就会用来从 B
手上购买同等数量的流动性。
如果 LSP 给 B
支付的价格小于从 A
处收取的支付,从而可以赚取差价,那么他就会强迫 B
结成糟糕的经济关系。如果 B
事后又需要买回流动性,而 LSP 如法炮制,收取更高的价格,那么 B
就在一卖一买中亏了钱。这将是一个零和游戏,没有人能赢到什么。
因此,LSP 能够实际提供入账流动性的唯一方法,就是在专属输出中锁定资金,实际上,这就是流动性的 “存货”。
重要的是,B
将永远不会参与使自己损失收款额度的计划。
另一种方法是,LSP 不售卖 入账流动性;即,不是 “LSP 售卖入账流动性,但不向客户收取闪电网络路由费” 这种模式,而是 “LSP 向客户收取非零的闪电网络路由费,然后决定将流动性分配到何处” 这种模式。后者没那么理想,因为可以假设客户总是比 LSP 更知道什么时候自己需要流动性;举个例子,一个商家可能有一些促销活动,或者一个新品要上架,那就可以预期需要更多的收款额度;而且,这一消息可以在提前向 LSP 购买收款额度的活动中明显地体现出来;这种情况下,这位客户可以等待树上的其他客户上线。
激励客户在线
即使有一个专门的 L
输出作为 “待售的流动性存货”,因为 B
也需要在线,那么一定程度上 B
也需要获得补偿。
最直接的办法是,在参与的过程中,B
可以从 LSP 向 A
收取的流动性费用中分得一小部分。
此外,LSP 也可以直接向 B
提供一小部分的免费流动性。两种办法在很大程度上是等价的,因为入账流动性也是有价值的,但这样做的好处是,B
可以趁自己和 A
都在线,提前获得更多流动性。稍后, A
可能会下线,让需要入账流动性的 B
只能使用昂贵的链上操作;所以,B
会更希望现在就获得一小部分免费的入账流动性(趁现在 A
也在线),而不是稍后再请求(那时候 A
可能离线了)。
客户分组
一些客户可能有规律地在一天中的某段时间地关闭电源(比如当地时间的晚上)。
LSP 可以监控客户的在线时段,然后根据他们在 24 小时中最有可能上线的时间将他们分组。然后,在为一个新的工厂构造交易树时,LSP 可以将 “最活跃时段” 相近的客户分在同一个叶子以及相邻叶子中。
这会提高一个客户需要收款额度时,同一叶子中的其他客户正好在线的概率;甚至,当一个叶子节点用尽了 L
专属的资金,相邻的叶子中的客户也更有可能在线。
十句话,如果一个 LSP 在全球都拥有客户,那么按时区分组就很有利,因为相邻时区的客户更有可能同时在线。
树的构造及叉数选择
叶子节点的最佳叉数是 2,因为这意味着只需要 3 个参与者在线,就能更新一个叶子:这叶子上的两个客户,以及 LSP 本身。这让状态更新的操作更可靠。
弹出交易的叉数可以设为 1。因为一笔弹出交易的所有输出都必须被花掉(在该弹出交易获得区块确认的时候),这减少了在某一客户单方面退出时受到影响的客户的数量。也减少了在一个叶子节点用尽 L
专属资金时需要唤醒的客户的数量。
不幸的是,较低的叉数意味着更大的树高:
- 更大的树高意味着在单方退出的情形中,需要发布的交易更多。
- 更大的树高意味着在单方退出的情形中,资金要经历更长的
nSequence
相对时间锁才能取出。- 这里的相对时间锁也迫使在客户处终结的 HTLC 将自己的最终 CLTV 差值下限(BOLT11 中的
min_final_cltv_expiry_delta
)设得比曝光其通道可能要经历的最大nSequence
时延要高,高出这个客户认为可以 “安全” 地赎回资金的时延。因此,保证nSequqnce
较低是非常重要的,因为这个时延也决定了公开网络上的 HTLC 将资金锁在未决 HTLC 中、降低公开网络容量的最长时延。
- 这里的相对时间锁也迫使在客户处终结的 HTLC 将自己的最终 CLTV 差值下限(BOLT11 中的
为缓解这些问题:
- 在临近叶子的层级中使用更小的叉数,而在更高的层级中使用更大的叉数。
- 不论如何,如果一个叶子节点无法再给一个客户提供额外的入账流动性,那么 LSP 总要 “回到上一级” 并唤醒更多客户。
- “回到上一级” 意味着需要在线的客户数量要倍增,与节点的叉数成正比。
- 如果需要被唤醒的群体足够大,他们全部都在线的概率就会非常低,以至于群体的规模再扩大一倍、四倍都不会有什么影响 —— 因为他们全都在线的概率已经非常低了,低到你会老实回到链上,而不指望这套机制。
- 在离开叶子几层之后,我们可以完全移除状态交易(即带有递减
nSequence
的交易)。实际上,这会变成用一个根超时签名树输出,来保障多个超时树结构的 Decker-Wattenhofer 工厂,这些工厂再保障客户的实际通道。- 同样地,如果 LSP 必须 “回到上一级”、从叶子上溯一层到两层状态交易,需要被唤醒的客户也会非常多,多到不太可能同时在线,所以,也可以通过使用更少的状态交易层级来减少单方面退出的时延。
- 非状态交易没有相对时间锁,因此在退出时不会造成额外的时延。
- LSP 可以将具有更高在线时间的客户分在一起,将他们放在叉数更大的节点中。
- 这样的客户将得到更好的服务(他们会跟其他更高在线时间的客户分在一起,因此,在他们需要入账流动性时,同组的人更有可能在线);而且单方面退出会更便宜也更快(更高的叉数意味着更低的树高,意味着需要发布更少的交易)。
- 低在线时间的客户和新客户将获得更多叉数为 2 和 1 的节点,因此单方面退出的代价更大,但也更好地隔绝了相邻客户下线的影响。
(完)