Rollup Bridge 介绍(八):Arbitrum 原生桥
来源:    发布时间: 2023-12-28 17:22   45 次浏览   大小:  16px  14px  12px
Arbitrum 原生桥是基于 Arbitrum 底层的讯息机制搭建而成,专为 Arbitrum 设计与服务的跨链桥,由 Arbitrum 官方所维护,是目前市面上与 Arbitrum 相容性最好、安全性最高的跨链桥。

Arbitrum 原生桥是基于 Arbitrum 底层的讯息机制搭建而成,专为 Arbitrum 设计与服务的跨链桥,由 Arbitrum 官方所维护,是目前市面上与 Arbitrum 相容性最好、安全性最高的跨链桥。

在接下来的段落里,笔者将先介绍 Arbitrum 底层的讯息机制,讯息机制是连接 Ethereum(L1) 与 Arbitrum(L2)的核心,不只确保了 L2 上交易内容与顺序的唯一性,还能让 L1 与 L2 之间能够互相通讯。

讯息与交易两个词在 Arbitrum 文件或是程序中常常会交替使用,本文以讯息来统称介绍。

* 本文中提到 L1 都是指 Ethereum,L2 都是指 Arbitrum。 

在了解 Arbitrum 讯息机制的运作原理之后,接着会介绍 Arbitrum 在跨链讯息中独有的 Retryable Ticket 机制,并在文末以「ERC20 token 通过 Arbiturm 原生桥在 L1 与 L2 之间进行跨链转移」为范例,让读者能更了解原生桥的实用场景。

对于 Rollup 和 Arbitrum 运作原理还不熟悉的读者,非常推荐事先阅读这篇 Arbitrum 开发者文件对 Rollup 的介绍:Arbitrum Rollup Basics

讯息机制

Arbitrum 在 L1 上有许多处理讯息的合约,包含 InboxOutboxBridge 以及 SequencerInbox,这些合约都可以在 Arbitrum GitHub repository 中看到。

本文参考的合约实操和链接,皆以 Arbitrum GitHub repository 中的 2d002950 commit 为基准。

其中 SequencerInbox 是决定讯息内容与顺序的唯一参考源(Single Source of Truth),也就是说,SequencerInbox 记录的讯息(包含 L1 和 L2 之间的跨链讯息、和 L2 上原生的交易),从头到尾依序执行一遍,可以得到当下 L2 链上的状态。目前只有官方维运的 Sequencer 节点有权力直接对 SequencerInbox 写入讯息,以确保讯息排序的一致性。

相对于 SequencerInbox 只允许 Sequencer 节点写入,Inbox 提供了开放的入口,让一般使用者与第三方节点也能够写入讯息至 Arbitrum,为 Arbitrum 注入更多的应用空间。通过 Inbox 写入的讯息会先被保存在 Bridge 里,最后会定期被 Sequencer 收入到 SequencerInbox,或是可以借由 Force Inclusion 机制(后面会介绍)强制收入到 SequencerInbox。

Outbox 负责记录、验证并处理 L2 至 L1 的跨链讯息(例如从 L2 提领 token 至 L1),让 L2 发起的跨链讯息能够正确地在 L1 执行兑现。

让我们先用一个架构图来表达他们之间的关联:

Arbitrum Message Flow

此架构图以使用者的视角出发,描绘了许多不同情境下的讯息流程,在接下来的小节中会逐一的拆解介绍。

Sequencer Flow

Arbitrum Message Flow through Sequencer

通过官方提供的 Offchain Labs RPC 节点(即 Sequencer 节点),使用者可以将 L2 原生交易和 L2 -> L1 跨链讯息委派给 Sequencer 处理。Sequencer 收到使用者的讯息后,会先行执行讯息内容,即时更新 Sequencer 本地节点上的 L2 状态,此时讯息虽然还未上链,但使用者已经可以从 Sequencer 身上获得最新状态。为了节省将讯息写入 L1 SequencerInbox 的成本,Sequencer 会在时间和容量的限制允许之下,尽可能收集多笔讯息,将它们打包成一个 batch,以 batch 的方式一次将多笔讯息写入 SequencerInbox。

* 在此提供一笔 Sequencer 写入 batch 的 L1 交易参考,该交易中呼叫的合约方法实操可以参考 SequencerInbox 合约的原始码

Sequencer 在处理完本地节点收集的讯息后,会接着从 L1 Bridge 提取部分尚未处理的讯息(即其他人通过 Inbox 写入的讯息),继续执行更新 Sequencer 本地节点上的 L2 状态。等到这些讯息都处理完后,Sequencer 会将本地的讯息 batch 写入 SequencerInbox,并以参数的方式告知 SequencerInbox 这次 batch 额外包含了多少笔来自 Bridge 里的讯息(合约原始码参考),SequencerInbox 会依照参数资讯,主动向 Bridge 收录对应数量的讯息,以保持 Sequencer 与 SequencerInbox 的历史同步。

Inbox Flow

Arbitrum Message Flow through Inbox

除了通过官方的 Sequencer 节点,使用者(或第三方节点)可以选择自行通过 L1 Inbox 发送讯息,包括 L2 原生交易、L2 -> L1 和 L1 -> L2 的跨链讯息,特别注意的是,L1 -> L2 跨链讯息只能借由 Inbox 发送,无法通过 Sequencer。Inbox 接收到使用者的讯息后,会对资料做基本的格式包装,包装后的讯息会接着被转发到 Bridge 合约保存起来,并等待 Sequencer 定期将尚未处理的讯息同步至 SequencerInbox。

在正常情形下,使用者通过 Inbox 发送的讯息,背后必须依赖 Sequencer 遵守规则,才能被收录至 SequencerInbox。若 Sequencer 刻意略过 Inbox 发送的讯息,进行审查攻击(Censorship Attack),使用者的讯息将不会被处理和执行。

因此,除了单方面依赖 Sequencer,Arbitrum 在 SequencerInbox 上设计了 Force Inclusion 机制,当某些条件达成时,例如 Sequencer 已经有足够长的时间未处理 Bridge 里的讯息,此时任何人都可以直接要求 SequencerInbox 收录 Bridge 里的讯息,来确保当 Sequencer 进行交易审查时,整个系统还是能公平地运作。

Outbox Flow

Arbitrum Message Flow through Outbox

Outbox 主要负责记录、执行 L2 ->L1 的跨链讯息,例如从 L2 提领 token 回 L1,使用者在进行 Outbox 流程时,必须先在 L2 网络中发起 L2 -> L1 跨链讯息(虽然上图并没有特别标示出SequencerInbox,但 L2 -> L1 跨链讯息如同前面小节介绍的流程,一样会先被收录到 SequencerInbox),接着就是等待包含这笔讯息的 Rollup 状态的挑战期结束,确定大家对 L2 状态无异议后,这笔 L2 -> L1 跨链讯息最后会被写进 L1 Outbox 里,等待使用者在 L1 执行兑现。

* 在此提供一笔在挑战期结束后,将 L2 至 L1 跨链讯息写入 Outbox 的 L1 交易参考,成功写入时 Outbox 会发出 OutboxEntryCreated 的事件。由于写入跨链讯息至 Outbox 是确认挑战期结束时中间的一个环节,单从这笔交易比较难看出合约互动的过程,想了解更深入的技术读者可以从这段 Rollup 合约的原始码向下挖掘。

当 Rollup 挑战期结束、L2 -> L1 跨链讯息成功写入 Outbox 后,使用者必须在 L1 主动请求 Outbox 执行交易,并提供这笔 L2 -> L1 跨链讯息存在 L2 的证据(可通过 Arbitrum 节点进行查询),Outbox 验证通过后,就会在 L1 执行兑现讯息的内容。

* 在此提供一笔从 Outbox 兑现跨链讯息的 L1 交易参考。

以上是 Arbitrum 底层讯息机制的概括介绍,接下来会继续介绍 Arbitrum 独有的 L1 -> L2 跨链讯息机制:Retryable Ticket。

Retryable Ticket — L1 -> L2 跨链讯息机制

在实际介绍跨链场景前,我们必须先了解一下 L1 -> L2 跨链讯息可能发生的问题,以及 Arbitrum 独有的 Retryable Ticket 机制如何优雅地解决这个困难。

L1 -> L2 跨链讯息传递的过程中参杂了许多不确定的因素(例如 L2 gas limit 估算误差、L2 gas price 波动等等),有可能 L1 -> L2 跨链讯息已经在 L1 成功发送,同时发生了与讯息相关的副作用(side effect),例如转移 token 给 Arbitrum 原生桥,最后讯息却在 L2 执行失败,这个状况会破坏 L1 -> L2 跨链讯息的原子性,导致 L1 和 L2 对状态有不同的认知。

Retryable Ticket 就是为了克服这个问题而设计出来的机制,我们先来了解一下一笔 L1 -> L2 跨链讯息的成本结构如下:

L1 -> L2 message fee =

L1 gas fee (L1 gas * L1 gas price) +

L2 gas fee (L2 gas * L2 gas price)

使用者在 L1 发起一笔 L1 -> L2 跨链讯息时,除了需要预估当下发送 L1 交易所需的 gas 费用之外,还需要事先预估好未来讯息在 L2 执行时所需要的 gas 费用,并在 L1 发起的跨链讯息里,以 call value 的方式,事先带上足够的 ETH,以支付跨链讯息未来在 L2 上执行所需的 gas 费用。

而唯有等到 L1 -> L2 跨链讯息实际在 L2 执行时,才能确切地知道执行所需的 gas 用量,与当下网络的 gas price 需求。因此,使用者在 L1 发送 L1 -> L2 跨链讯息时预估的未来 L2 执行 gas 费用,与实际状况会有一定程度上的误差,当讯息在 L1 call value 夹带的 ETH 不足以支付 L2 gas 费用时,讯息在 L2 上就会执行失败,使用者可能因此遭受损失。

举个例子来说,使用者在 L1 上发起一笔 L1 -> L2 跨链转移 token 的讯息,并以当下 L2 节点数据,预估好未来在 L2 执行的 gas 费用(以 L1 call value 方式夹带),顺利地在 L1 将跨链讯息和 token 交由 Arbitrum 原生桥处理。但不幸的是,讯息在 L2 执行时花费了比预期还多的 gas,使得使用者在 L1 发送讯息夹带的 ETH 不足以支付 L2 gas 费用,造成 L2 转移 token 给使用者的动作失败。在这个例子中,使用者不只没有在 L2 收到相应数量的 token,而且还没有 L2 链上的证据来取回 L1 上交由 Arbitrum 原生桥保管的 token,这些 token 将永远沉淀在 Arbitrum 原生桥里。

* 目前 Optimism L1 -> L2 跨链讯息也有相同的风险存在。

Arbitrum 独有的 Retryable Ticket 机制让 L1 -> L2 跨链讯息在 L2 上拥有成功执行前重试的能力,尽管无法百分之百控制所有不确定的因素,但可以为系统加入时间的维度,来消弥这些短期的波动。以上述的 gas 费用波动为例,当 L1 -> L2 跨链讯息在 L2 上因为 gas 费用高于预期而执行失败,这笔跨链讯息将会被暂时保存在 L2 上的 retry buffer 里,使用者可以等待 L2 网络顺畅后,再从 retry buffer 取出讯息进行重试(ArbRetryableTx.redeem)。

但 Retryable Ticket 并不是没有成本,Arbitrum 为了保存这些提供重试的讯息,需要消耗额外的储存资源,因此在建立 Retryable Ticket 时使用者需要额外支付保存资料的费用(submission cost),费用将与讯息的资料大小成正比。而每一笔 Retryable Ticket 都有其保存期限,以七天为单位,如果在保存期限到期前,想要延长 Retryable Ticket 的期限,只需要再支付一次保存费用,就可以再额外延长七天的时间。若 Retryable Ticket 最终不幸过期,讯息将会从 retry buffer 里永久移除,也就无法再进行重试。

* Arbitrum 开发者文件中有对 Retryable Ticket 做更细节的介绍,有兴趣了解更多的读者可以参考看看。

Arbitrum 原生桥在执行 L1 -> L2 跨链讯息时,都会使用 Retryable Ticket 来保障使用者的讯息最终能在 L2 上完成兑现。

下一章节将开始介绍 Arbitrum 原生桥的跨链场景,我们将会看到讯息机制以及 Retryable Ticket 如何在实际的应用中发挥功用。

原生桥跨链场景 — 以 ERC20 token 跨链转移为例

本章节以 ERC20 token(以下简称 token)通过 Arbitrum 原生桥在 L1 与 L2 之间进行跨链转移为例子,了解 Arbitrum 讯息机制以及 Retryable Ticket 如何在实际场景中发挥作用。

Arbitrum 原生桥对于跨链转移 token 的机制为「Escrow on L1, mint/burn on L2」,意即使用者在 L1 的 token 会全权交由 Arbitrum 原生桥托管,并在 L2 上铸造(mint)或销毁(burn)相同数量的 token,来达成出入金的动作。接下来会分别深入探讨 L1 -> L2 入金(deposit)以及 L2 -> L1 出金(withdraw)的流程细节。

Deposit (L1 -> L2)

在 L1 转移 token 至 L2 的情境中,由于 L2 是以铸造的方式,凭空在 L2 产生对应数量的 token。因此,L2 token 铸造的权限管控是一个非常重要的安全议题。 Arbitrum 基于先前介绍的讯息机制,在 L1 和 L2 各自搭建了互相对应的 Token Gateway 架构,使 L1/L2 Token Gateway 成为 Arbitrum 原生桥转移 token 的唯一出入口,L2 token 只需要赋予 L2 Token Gateway 铸造的权力,而 L2 Token Gateway 只接受来自 L1 Token Gateway 的跨链铸造请求,来保证 L1 -> L2 token 转移的安全性。

* L1/L2 Token Gateway 不只会记录与其对应的 Token Gateway 地址,也会记录 L1 token 与 L2 token 之间的地址映射关系(由 Arbitrum 官方设定),减少了 L1 与 L2 token 之间误转的风险。

整体流程如下图:

L1 -> L2 Deposit Flow

使用者首先需要通过 L1 Gateway Router(outboundTransfer)发起将 token 从 L1 转移至 L2 的请求,L1 Gateway Router 会为使用者找到有能力处理该 token 转账的 Gateway,以 ERC20 token 来说一般会是由 L1 ERC20 Gateway 来负责。L1 ERC20 Gateway 接到请求后,会将使用者要跨链转移的 token 数量托管到自己身上;接着,L1 ERC20 Gateway 会通过 Inbox 建立 Retryable Ticket(createRetryableTicket)的跨链讯息,讯息接收方为 L2 ERC20 Gateway,内容为跨链转移 token 的请求(finalizeInboundTransfer),Inbox 会协助标记这笔跨链讯息来自 L1 ERC20 Gateway;最后,这笔跨链讯息将由 Inbox 转发至 Bridge 保存。

等待一段时间后,Arbitrum 会协助将这笔跨链讯息发送到 L2 ERC20 Gateway,L2 ERC20 Gateway 会检查这笔转移 token 的讯息是否来自 L1 ERC20 Gateway,若不是,则拒绝处理。确认跨链讯息来自 L1 ERC20 Gateway 后,L2 ERC20 Gateway 会在 L2 铸造出对应的 token 数量给 L2 使用者,完成 L1 -> L2 deposit token 的流程。

Withdraw (L2 -> L1)

在 L2 转移 token 至 L1 的情境中,L2 是以销毁对应数量的方式,减少 L2 上的 token 总量。因此,L2 token 销毁的权限管控也是一个非常重要的安全议题。基于上一小节所介绍的 Token Gateway 架构,L2 token 只需要赋予 L2 Token Gateway 销毁的权力,而 L1 Token Gateway 只接受来自 L2 Token Gateway 的跨链解除 token 托管的请求,来保证 L2 -> L1 token 转移的安全性。

整体流程如下图:

L2 -> L1 Withdraw Flow

使用者首先需要通过 L2 Gateway Router(outboundTransfer)发起将 token 从 L2 转移至 L1 的请求,L2 Gateway Router 会为使用者找到有能力处理该 token 转账的 Gateway,以 ERC20 token 来说一般会是由 L2 ERC20 Gateway 来负责。 L2 ERC20 Gateway 接到请求后,会将 L2 上要移转的 token 进行销毁,并对 ArbSys 发出一笔跨链讯息,讯息接收方为 L1 ERC20 Gateway,内容为跨链移转 token 的请求(finalizeInboundTransfer),等待挑战期结束后,这笔跨链讯息会被写入 L1 上的 Outbox 以供兑现。

* ArbSys 是 Arbitrum 上预先部署好的合约,地址固定为 0x64,是 L2 与 L1 沟通的桥梁。

当讯息被成功写入 Outbox 后,使用者需要主动要求 Outbox 执行讯息(executeTransaction),并附上讯息存在的证明,Outbox 检查无误后,就会帮助使用者在 L1 执行讯息的内容。L1 ERC20 Gateway 接收到来自 Outbox 的转账讯息,会检查这笔转移 token 的讯息是否来自 L2 ERC20 Gateway,若不是,则拒绝处理。确认跨链讯息来自 L2 ERC20 Gateway 后,L1 ERC20 Gateway 会将使用者在 deposit 时交由它托管的 token,转回给使用者,完成 L2 -> L1 withdraw token 的流程。

结语

本文概括地介绍了 Arbitrum 原生桥的运作原理与实际场景,其中还有很多细节无法一一详述,想更深入了解的读者,非常推荐阅读 Arbitrum 开发者文件,文件中从设计缘由、运作原理、到落地实操都有非常清楚的介绍,对 Arbitrum 与 Rollup 会有更深刻的理解。