如何利用虚拟通道来创建状态通道网络
在本文中,我们介绍了一种叫作 虚拟通道(virtual channel) 的新型状态通道结构。虚拟通道不仅使得付费文件流(点击此处,查看 demo!)等新型应用场景成为可能,还可以简化去中心化的 Graph 查询支付、Filecoin 内容检索、带有经济激励机制的状态提供者网络等有趣的应用场景。
动机
让我们来设计一个免信任的付费文件流支付系统。这个系统中有 seeder(上传文件者)和 leecher(下载文件者)。leecher 从多个 seeder 那里付费下载一份文件的不同部分。使用以太坊主网交易来支付费用是不可能的,因为以太坊主网的吞吐量低于每秒 50 笔交易,而且(截至 5月 28 日发稿时)最低转账成本也在 2 美元以上。Optimistic Rollup 和 ZK Rollup 可以提高吞吐量并降低交易成本。StarkEx 的 ZK Rollup 可以将吞吐量提高至每秒 3000 笔交易,同时将每笔交易的成本降至 0.03 美元。假设 leecher 愿意支付 1 美元下载1GB 大小的文件,且整个文件以 256 KB 为单位切分成了多个部分,leecher 需要支付大约 5000 笔费用。当网速为 20MB/s 时,用户每秒支付 80 次费用,且每笔费用为 0.0002 美元。Rollup 的吞吐量达不到许多 leacher 的要求,而且交易成本还是太高了。
为了达到交易吞吐量和成本要求,基础状态通道是个不错的选择。 状态通道创建完成后,leecher 就可以免费进行多笔小额付款,且吞吐量只受 leecher 和 seeder 之间通信信道的带宽以及二者所使用硬件的影响。 基础状态通道带来的挑战是,leecher 需要提交交易到主网上,与每位拥有他所需文件部分的 seeder 都建立状态通道。由于 leecher 与多位 seeder 都需要短暂交互,创建多条状态通道的成本是极其昂贵的。
虚拟通道可以完美解决我们的设计问题。虚拟通道尤其适用于轴辐式拓扑结构。多位参与者先与一个免信任的中间方建立连接,然后任意两位(或多位)参与者再通过质押的方式创建私密通道。中间方并不知道私密通道内运行的是什么应用。另外,任何参与者都可以发起链上挑战找回自己的资金,即使通道内的其他参与者和中间方处于离线状态或作恶。
总的来说,只要 Alice和Bob之间有经过中间方的路径,无论网络拓扑结构是什么样的,虚拟通道都可以让 Alice 开设与 Bob之间的私密通道。
背景知识
不同的区块链生态系统都将状态通道视为一种可以让少数参与者:
-
建立链上联系(通道)并存入资金;
-
私下交换信息,有条件地在参与者之间转移资金;
-
关闭链上通道,完成各方之间资金结算的方法。
我们之前已经在 statechannels.org 网站上发文介绍了如何构建状态通道(中文译本),以及如何通过一个叫作账本通道(ledger channel)的架构让一条状态通道为另一条状态通道提供资金。这些架构要求通道参与者必须在链上建立直接联系。换言之,如果 Alice和 Bob 之前从未交互过,现在却想开通状态通道,他们必须签署(链下)协议,并将资金存入合约内。
但虚拟状态通道可以实现以下应用场景:
-
Alice与 免信任 的中间方创建状态通道。假设这个中间方叫作 Irene。
-
Bob 同上。
-
在 Irene 以及创建好的通道的帮助下,Alice 和 Bob 可以创建一条新的私密通道并存入资金。创建这个私密通道 不需要通过链上 交互。我们将 Alice 和 Bob之间的通道叫作虚拟通道。
就付费文件流这个应用场景而言,Alice 先要通过主网交易与中间方 Irene 创建一条通道。接着,她可以通过虚拟方式与任意数量的对等节点相连,只要后者与 Irene 之间也存在链上通道。一旦连接成功,Alice 就可以持续支付数据下载费。
我们的协议 Nitro 可以实现以下应用场景:
-
帮助 Alice 和 Bob 创建虚拟通道的中间方 Irene 实际上不在这个虚拟通道内。Irene 只是帮忙创建通道,并在通道关闭时结算资金而已。因此,Alice 和 Bob 之间可以实现完全私密的交流,并将 Irene 排除在这个应用的关键路径之外。更重要的是,Alice 和 Bob 可以在私密通道内运行多个不同的应用,无需 Irene 针对这些应用实现任何新的逻辑。因此,虚拟通道是 通用可编程的多跳状态通道 (就像以太坊赋予区块链可编程性那样),因为每条状态通道都可以根据自己的一套 “规则” 来创建。Alice 可以向 Bob 付费购买某个文件内容。Bob 也可以向 Alice 付费进行 Graph 查询。
-
所有 Nitro 通道都是可组合的。因此,获得资金的通道可以通过递归的方式为其它通道提供资金,无论前者是通过何种方式获得资金的。一旦某条通道(直接通过链上合约、账本通道或虚拟筹资的方式)获得资金,它就可以运行任何应用或为其它通道提供资金。
Nitro 的替代方案
其它协议(如 Raiden)可以让两位参与者无需在链上存入额外资金,即可通过中间方创建通道。一个成熟的模式是,使用经过哈希的时间锁通过中间方将 Alice 的付款路由给 Bob。这个模式有一个很明显的缺点:所有付款必须通过中间方路由。
一些比较新颖的架构(如 Scalar)可以让 Alice 和 Bob 通过中间方创建通道后实现点对点付款。然而,这些架构依然需要中间方理解 Alice 和 Bob 所运行的应用,因为通道余额最终要通过中间方取回。有了 Nitro 虚拟通道,中间方就可以被隔离在 Alice 和 Bob 之间的通道外。
通道内部的资金转移
本文将深入介绍 Nitro 通道是如何获得资金的,以及免信任的安全虚拟通道是如何实现的!
Alice 和 Bob之间的 Nitro 通道的资金来自:
-
Alice 和 Bob 联合签署链下协议,使用特定的初始结果创建通道。
-
Alice 和 Bob 按照初始结果指定的顺序存入资金,增加链上裁决者(adjudicator)合约中记录的通道
holdings
。
这里的 “结果” 是一个指示裁决者在通道最终敲定时如何分配资金的结构(查看我们的最新文章!)在 Nitro 中,“结果” 列出了 {目标、数量} 对的优先级:
{A: 7, B: 3}
命令通道
C
的裁决者先将 7 枚代币支付给
A
(Alice),再将 3 枚代币支付给
B
(Bob),并将
C
的结果归零。
这么说有一点不准确:我们其实是按照字典中键的顺序在为目标分配优先级。因此,
{B: 3, A: 7}
会先付款给 Bob,然后才轮到 Alice。你可以将它看作是 Python 3.7+ 式的有序字典。
上图显示了 Alice 和 Bob 是如何根据 {A: 7, B: 3} 这一结果从通道 C 中取走代币的。根据链上裁决者的记录,通道中共有 10 枚代币。Alice 或 Bob 将通道结果记录到链上。这个结果记录下来后,Alice 和 Bob 就可以取走代币。于是,Alice 的外部账户中增加了 7 枚代币,Bob 的外部账户增加了 3 枚代币。
虽然上述结果暗示目标是用户账户(EOA), 但是在 Nitro 中,通道本身也可以是目标。 这样一来,通道 L 也可以充当 “私密账本”,因为 Nitro 可以让一次性一次性将资金存入账本通道,然后为多条子通道提供资金。Nitro 避免了回到 Layer1 的需求,以及由此产生的延迟和成本!例如:
假设
C2
是一条 Nitro 通道,
{A: 4, B: 1, C2: 5}
命令裁决者向 Alice 支付 4 枚代币,再向 Bob 支付 1 枚代币,然后将
C2
的代币持有量增加 5 。
以上是一个简短的介绍。如果你想要深入了解Nitro,请查看我们的相关博客文章!
如何利用保证来实现免信任架构
接下来,我们将介绍当两位参与者 没有链上关系 时,如何通过一个安全的架构来创建三方通道。这个架构不仅能让账本通道为其它通道提供资金,还能实现虚拟通道。两位参与者分别是 Alice(A)和 Bob(B),中间方是 Irene(I)。
初始设置:三条独立通道。
首先要有一对账本通道
L
(位于 Alice 和 Irene 之间)和
L'
(位于 Bob和 Irene之间)。通常情况下,
L
和
L'
是早就创建好的。这是因为 Irene 存在的目的就是在人们之间建立连接 —— Alice 可以使用 L 来同时连接 Bob、Cheryl、David 和 Eve。如果
L
和
L'
不存在,
L
可以使用结果
{A: 4, I: 6}
创建,
L'
可以使用结果
{B: 6, I: 4}
创建。
L
和
L'
各自在链上存储10枚代币。
另外还有一条独立通道
J
是使用结果
{A: 4, B: 6, I: 10}
创建的。请注意,在向通道存入资金之前,参与者必须先就这一结果达成共识。一旦
J
有了资金之后,这条通道就可以用来为 Alice 和 Bob 之间创建的任意一条私密应用通道提供资金。
步骤 1 和 2:账本通道转变为向
J
提供资金。
Alice 和 Irene 将
L
的结果转变为
{J: 10}
。Bob 和 Irene 将
L'
的结果转换成
{J: 10}
。
这个设计够好了吗?
Alice 必须考虑以下几点:
-
Bob 和 Irene 是不是可信的?
-
Alice 无法控制
L'
上发生的事。
我们来思考一下步骤 2 之后如何取走
J
的资金。
-
L
和L'
的结果被记录到链上。 -
L
和L'
的资金都被转移到J
。代币经由转账操作从一条通道转移到另一条通道。J
现在有了 20 枚代币,可谓资金充足。 -
Alice 可以从
J
中取走 4 枚代币,Bob 可以从J
中取走 6 枚代币。Irene 可以从J
中取走 10 枚代币。
漂亮!现在每个人都取走了自己应得的代币。但是,我们来设想一个场景:Alice 和 Bob 串谋起来欺骗 Irene。假设步骤 1 发生后,Bob 拒绝参与步骤 2。然后就会发生以下情况:
-
L
的结果被记录在链上。现在,J
有了10 个代币,记录在链上的结果是{A: 4, B: 6, I: 10}
。 -
Alice 和 Bob 分别取走 4 枚和 6 枚代币。
请注意,Bob 从
J
那里获得了 6 枚代币,尽管他自己根本没有向
J
转过代币。结果变成了:Alice 获得了 4 枚代币,Bob 获得了 6 枚代币,Irene 什么也没有。Irene 被坑惨了!
你可能会想,如果调换一下结果
{A: 4, B: 6, I: 10}
中目标的顺序,就可以创建出一个安全的架构。然而,无论怎么调换顺序,总会有人蒙受损失!
保证是如何发挥作用的
在上述场景中,我们使用了转账操作在通道之间转移资金。通过 转账 操作,资金可以从一个通道转移到目标通道。在本小节中,我们将引入 索取(claim) 操作来将资金从目标通道转移至 特定的目标地址 1 。为了实现 索取 操作,我们需要 保证 。
我们先来介绍一个新的数据结构。 保证 是指定以下的结果:
-
目标,即,一条通道;
-
数量;
-
优先级,即,目标的优先级列表。优先级的作用是指示裁决者如何改变目标通道的结果项的优先级。
我们来看一下
J
是如何获得资金的:
初始设置: 创建三个独立的通道。
-
通道
J
是使用结果{A: 4, B: 6, I: 10}
创建的。 -
L
是使用结果{A: 4, I: 6}
创建的。 -
L'
是使用结果{B: 6, I:4}
创建的。
步骤 1 和 2:
账本通道转变为向
J
提供资金(没有先后顺序之分):
-
L
的结果更新为{J: {amount: 10, priorities: [A, I]}
。请注意,我们使用了一种新的符号来表明L
的结果只有一项,即,包含目标J
、数量和地址优先级列表的保证。 -
L'
的结果是{J: {amount: 10, priorities: [B, I]}
。
L
和
L'
的结果各自包含一个
保证
。由于转账操作不支持这些保证,我们代之以索取操作。索取操作接受保证以及保证的目标通道作为输入。
我们来看一下索取操作是如何运作的。假设
-
J
的结果{A: 4, B: 6, I: 10}
被记录到了链上。 -
L
的结果{J: {amount: 10, priorities: [A, I]}}
被记录到了链上。
如果有人请求执行
L
的结果中的保证,则会触发三个效果:
-
将 4 枚代币发送给
A
,6枚代币发送给I
。 -
因为(1),
A
和I
的余额在J
的结果中被调整为{B: 6, I: 4}
。 -
L
的结果被调整为{}
—— 保证被删除。
在这个操作中,优先级的目的是告诉裁决者
跳过
带有
B
的结果项,这样 Irene 就可以拿回她在创建
L
时出的那部分资金。因此,裁决者会先看到保证中优先级最高的目标
A
,并将 4 枚代币转给
A
,
J
的结果会相应更新。然后,裁决者才会看到目标
I
。在
J
的结果中,Irene 应该获得 10 枚代币,但这时(L 的结果中)只剩下 6 枚代币。因此,这 6 枚代币被发送给了 Irene,
J
的结果再次更新。请注意,优先级一定要是
[A, I]
而非
[I, A]
。如果优先级是
[I, A]
,Irene 就会通过索取操作获得 10 枚代币,Alice 就失去了原本属于她的 4 枚代币!
有了索取操作,Alice 和 Bob 再也不能串谋起来骗取 Irene 的资金了。
?最理想的情况
你可能已经注意到了,为了让 Alice 他们取回资金,总共需要 3 个链上操作:将联合通道和转账结果记录到链上,并调用
索取
操作。请注意,这是最糟糕的情况。假设 Bob 将 4 枚代币转给 Alice 后,Alice 和 Bob 想要关闭
J
。如果 Bob 和 Irene 配合的话,则:
-
Alice、Bob 和 Irene 同意以结果
{A: 8, B: 2, I: 10}
敲定J
。 -
Alice 和 Irene 可以更新
L
,安全地删除为J
提供资金的保证。L
的结果变成了{A: 8, I: 5}
。 -
现在,Alice 可以使用
L
里的代币为其它通道提供资金了。Alice 也可以选择取走资金。
总而言之,在协作式案例中,Alice 可以使用
L
内的资金为多条不同的应用通道提供资金,也可以从这些取走资金,无需进行任何链上交易。
虚拟通道
在上一节中,我们已经介绍了如何在两个参与方不存在链上关系的情况下创建三方通道。细心的读者应该注意到了,更新
J
必须经过中间方签字。在本文的开头,我们打算在
A
和
B
之间创建一条私密通道。幸运的是,Nitro 的可组合性让我们可以创建一条由
J
提供资金的私密应用通道
X
。具体架构如下图所示。你已经掌握理解这个架构所需的一切概念了。不过,如果你有任何问题,欢迎向我们提问!
未来计划
本文介绍的虚拟通道架构是 Nitro 协议论文中介绍的架构的进化版。最值得一提的是,这个架构不需要创建专门的保证者通道。
Nitro 虚拟通道即将引入另一个更新,免去对联合通道的需求。有了这个更新,账本通道
L
和
L'
就可以为应用通道
X
提供资金。
本文由 Mike Kerzhner 和 Andrew Stewart 基于 Tom Close 所著论文《Nitro 协议》撰写,感谢 Robert Drost、Joseph Chow、George Knee 和 Colin Kennedy 的反馈。
注
-
我们还可以将 索取 操作理解成:
-
将资金从 L 转入 J,改变 J 的结果。
-
将资金从 J 转入目标地址。
如果账本通道的结果是
{J: {amount: 10, priorities: [A, I]}
且
J
的结果是
{A: 4, B: 6, I: 10}
,
索取
可以描述成:
-
将 10枚代币从
L
转入J
。 -
J
的结果变成{A: 4, I: 6, B: 6, I: 4}
-
然后在
J
上调用面向A
和I
的转账。