原文标题:《前 Arbitrum 技术大使解读 Arbitrum 的组件结构(下)》
原文作者:罗奔奔,前 Arbitrum 技术大使,极客 Web3 贡献者
本文是 Arbitrum 前技术大使 及 智能合约自动化审计公司 Goplus Security 前联合创始人罗奔奔对 Arbitrum One 的技术解读。
在上一篇文章《Arbitrum 的组件结构解读(上)》,我们介绍了 Arbitrum 核心组件中的 排序器、Validator、Sequencer Inbox 合约、Rollup Block、非交互式欺诈证明的作用,而在今天的文章中,我们将重点讲解 Arbitrum 核心组件中与跨链消息传递及抗审查交易入口相关的组件。
正文:此前的文章中,我们曾提到,Sequencer Inbox 合约专门在 Layer1 上接收排序器发布的交易数据包 Batch。同时,我们指出,Sequencer Inbox 又被称作快箱,与之相对的是慢箱 Delayed Inbox(简称 Inbox)。下面,我们将对 Delayed Inbox 等与跨链消息传递相关的组件进行细致解读。
跨链交易可分为 L1 到 L2(充值)与 L2 到 L1(提现)。注意这⾥所说的充值和提现未必与资产跨链相关,可以是不直接附带资产的消息传递。所以这两个词仅仅表示跨链相关行为的两个方向。
跨链交易与纯 L2 交易相比,跨链交易在 L1 和 L2 这两个不同的系统中进行了信息互换,因此过程更复杂。
另外,通常我们说的跨链行为,是在两个毫不相关的网络上,用见证人模式的跨链桥进行的跨链,这种跨链的安全性取决于跨链桥的运营者,历史上基于见证人模式的跨链桥被盗事件频繁发生。
而在 Rollup 与 ETH 主网之间的跨链行为,与上述跨链有本质不同,因为 Layer2 的状态是由记录在 Layer1 上的数据决定的,只要你使用的是 Rollup 官方的跨链桥,其在运作结构上是绝对安全的。
这也凸显出 Rollup 的本质,它只是在用户角度看,像⼀条独立的链,但实际上所谓的「Layer2」只是 Rollup 对用户敞开的快速展示窗口,它的真实链式结构还是刻录在 Layer1 上。所以,我们可以认为 L2 算半条链,或者说是「在 Layer1 上创造出的一条链」。
需要注意,跨链都是异步和非原子性的,它不可能像在一条链上一样做完一笔交易确认后就知道结果,也不能保证另一侧一定会在某个时间点发生某些事。因此跨链有可能因为一些软性问题而失败,但只要使用正确的手段,诸如可重试票据(Retryable Ticket),就不会发生资金卡住等硬性问题。
可重试票据是通过 Arbitrum 官方桥充值时,用到的基本工具,ETH 和 ERC20 的充值都会使⽤到。其生命周期分为三步:
1. 在 L1 上提交票据。在 Delayed Inbox 合约中使用 createRetryableTicket() 方法创建充值票据,并提交。
2. L2 上自动兑付。大部分情况下,排序器可以自动帮用户兑付票据,无需后续的手动操作。
3. L2 上手动兑付。部分边缘情况,如 L2 上 gas 价格突然激增,票据上预付的 gas 不够,则无法自动兑付。此时需要用户手动操作。
注意,如果自动兑付失败,需要在 7 日内手动兑付票据,否则要么票据将会被删除(资金会永久损失),要么需要为票据的保存支付一定费用来续租。
另外,对于 Arbitrum 官方桥的提现流程,虽然和充值行为在流程上有一定对称相似性,但并没有 Retryables 这个概念,一方面可以从 Rollup 协议本身理解,另一方面我们可以从一些区别进行理解:
·提现的过程中不存在自动兑付,因为 EVM 没有定时器或自动化,而 L2 上可以实现自动兑付,是排序器帮忙实现的,所以 L1 上用户要手动与 Outbox 合约交互,以 Claim 取回资产。
·提现也不存在票据过期的问题,只要过了挑战期,可以在任意时间领取。
ERC-20 资产的跨链是复杂的。我们可以思考几个问题:
·一个在 L1 上部署的代币,它在 L2 上要如何部署?
·它的 L2 对应合约需要预先手动部署,还是系统可以自动为跨过来的、但尚未部署合约的代币 自动部署资产合约?
·L1 上的 ERC-20 资产,在 L2 对应的合约地址是什么?是否该和 L1 一致?
·在 L2 上原生发行的代币,如何跨链至 L1?
·拥有特殊功能的代币,如可调整数量的 Rebase 型代币,自增长生息代币,如何跨链?
我们不打算全部回答这些问题,因为展开太过复杂。这些问题仅是用来说明 ERC20 跨链的复杂性。
目前非常多扩容方案使用的都是白名单+手动清单的方案,来规避各种复杂的问题和边界情况。
Arbitrum 使用了 Gateway 系统,解决了大部分 ERC20 跨链的痛点,具有以下特性:
·Gateway 组件在 L1 和 L2 成对出现。
·Gateway Router 负责维护 Token L1<->Token L2 之间的地址映射,以及 some token<->some gateway 之间的映射。
·Gateway 本身可分为 StandardERC20 gateway,Generic-custom gateway,Custom gateway 等等,用以解决不同类型的和功能 ERC20 的桥接问题。
我们以比较简单的 WETH 跨链为例,来说明自定义 gateway 的必要性。
WETH 是一种 ETH 的 ERC20 等价物。Ether 作为主币,很多 dApp 中的复杂功能是无法实现的,因此需要一个 ERC20 的等价物。向 WETH 合约内转入一些 ETH,它们会被锁在合约内,并生成出相同数量的 WETH。
同理,也可以销毁 WETH,取出 ETH。显然,流通的 WETH 和锁仓的 ETH 数量永远是 1:1 的。
如果现在把 WETH 直接跨链到 L2 上,我们会发现一些奇怪的问题:
·无法在 L2 上把 WETH 进行 Unwrap 变成 ETH,因为 L2 上并没有锁仓对应的 ETH。
·Wrap 功能可以使用,但这些新生成的 WETH 如果跨回到 L1,也无法在 L1 上解封装为 ETH,因为 L1 和 L2 上的 WETH 合约不是「对称的」。
显然这违反了 WETH 的设计原理。那么 WETH 在跨链时,不论是充值还是提现,都需要先 Unwrap 成 ETH 后,再跨到对面,然后 Wrap 成 WETH。这个也就是 WETH Gateway 的作用。
其他有更复杂逻辑的代币同理,需要更复杂和精心设计的 Gateway 才能正常在跨链环境下工作。Arbitrum 的自定义 Gateway 继承了普通 Gateway 的跨链通信逻辑,并允许开发者自定义与代币逻辑相关的跨链行为,可满足大部分需求。
与快箱也即 SequencerInbox 相对应的是慢箱 Inbox(全称 Delayed Inbox)。为什么要有快慢之分呢?因为快箱是专门接收排序器发布的 L2 交易 Batch 的,所有未经排序器在 L2 网络内预处理的交易,都不该出现在快箱合约中。
慢箱的第⼀点作用是,处理 L1 到 L2 的充值行为。用户通过慢箱进行充值,排序器监听到后再反映在 L2 上,最终这笔充值记录会被排序器包含进 L2 的交易序列中,并提交至快箱合约 Sequencer Inbox。
在这个例子中,用户直接向快箱提交充值交易是不合适的,因为提交到快箱 Sequencer Inbox 中的交易,会干扰到 Layer2 正常的交易排序,然后会影响到排序器的工作。
慢箱的第⼆个作用,是抗审查。用户直接提交至慢箱合约中的交易,排序器⼀般会在 10 分钟内归集到快箱中。但如果排序器恶意忽略你的请求,慢箱还有⼀个强制归集 force inclusion 功能:
如果交易被提交至 Delayed Inbox 中,经过 24 小时,慢箱中的交易仍未被排序器包含至交易序列中,用户可以在 Layer1 上手动触发 force inclusion 函数,把被排序器忽略掉的交易请求,强制归集到快箱 Sequencer Inbox 中,之后就会被全体 Arbitrum One 节点监听到,会被强制包含进 Layer2 交易序列里。
我们刚才提到过,快箱⾥的数据就是 L2 的历史数据实体。所以在被恶意审查的情况下,通过慢箱可以让交易指令最终包含进 L2 账本中,这涵盖了强制提款等逃离 Layer2 的场景。
由此可以看出,对任何⼀个方向和层次的交易,排序器最终都无法永久审查你。
慢箱 Inbox 的几个核心函数:
·depositETH(),最简单的充值 ETH 的函数。
·createRetryableTicket(),可用于 ETH 和 ERC20 以及消息的充值。相较 depositETH() 而言,有更高的灵活性,例如可指定充值后 L2 的收款地址等。
·forceInclusion(),也即强制归集功能,任何⼈都可以调⽤。该函数会校验,提交至慢箱合约中的某笔交易,是否过了 24 小时还没被处理。如果条件满足,则将对消息进行强制归集。
不过需要注意,force Inclusion 函数实际上位于快箱合约中,只是为了⽅便理解,我们将其放在慢箱这⾥⼀起讲解。
出站箱 Outbox 只与提现有关,可以理解为提现行为的记录和管理系统:
·我们知道,Arbitrum 官方桥的提现需要等待约 7 天的挑战期结束,Rollup Block 最终敲定后,提款行为才可以实施。⽤户在挑战期结束后,向 Layer1 上的 Outbox 合约提交相应的 Merkle Proof,它再与其他职能的合约通信(如解锁其他合约中锁定的资产),最终完成提现。
·OutBox 合约会记录哪些 L2 到 L1 的跨链消息已经被处理过,以防止有人反复提交执行过的提现请求。它通过mapping(uint256 => bytes32) public spent,记录提现请求的 spent Index 与信息对应关系,如果 mapping[spentIndex] != bytes32(0) 则该请求已被提现过。原理类似于防止重放攻击的交易计数器 Nonce。
下面我们将以 ETH 为例完整讲解充值与提现的流程。ERC20 与之不同的仅仅是走了 Gateway,就不再赘述。
1. 用户调用慢箱的 depositETH() 函数。
2. 该函数会继续调用 bridge.enqueueDelayedMessage(),在 bridge 合约中记录该消息,并将 ETH 发送往 bridge 合约。所有的 ETH 充值资金,都保管在 bridge 合约中,相当于一个充值地址。
3. 排序器监听到慢箱中的充值消息,将充值操作反映⾄L2 数据库中,⽤户可以在 L2 网络看到自己充进来的资产。
4. 排序器将该笔充值记录包含进交易批次 batch,提交给 L1 上的快箱合约。
1. 用户在 L2 上调用 ArbSys 合约的 withdrawEth() 函数,在 L2 上销毁相应数量的 ETH。
2. 排序器将该提现请求发送至快箱。
3. Validator 节点根据快箱中的交易序列,创建新的 Rollup Block,其中会包含上述提款交易。
4. Rollup Block 度过了挑战期并被确认后,⽤户可以在 L1 上调用 Outbox.execute Transaction() 函数,证明参数由前面提到的 ArbSys 合约给出。
5. Outbox 合约确认无误后,解锁 bridge 中相应数额的 ETH 发送给用户。
使用乐观 Rollup 官方桥提现就会出现等待挑战期的问题。我们可以用私营的第三方跨链桥来规避这个问题:
·原子锁交换。这种方式只是在双方在各自链内进行了资产的互换,并且具有原子性,只要⼀方提供了 Preimage,双方⼀定可以得到应有的资产。但问题是流动性比较稀缺,需要点对点地寻找对手方。
·见证人跨链桥。⼀般类型的跨链桥都属于见证人桥。用户提交自己的提现请求,提现目的地指向第三方桥的运营者或流动性池。见证人发现跨链交易已提交到 L1 的快箱合约后,就可以直接在 L1 端向用户转账。这种方式本质上是用另⼀套共识系统来监视 Layer2,并根据其已提交至 Layer1 上的数据进行操作。问题是,这种模式下的安全系数不如 Rollup 官方桥高。
force Inclusion() 强制归集功能用于对抗定序器的审查,任何 L2 本地交易、L1 到 L2 交易和 L2 到 L1 交易,都可以使用该功能实现。定序器的恶意审查严重影响了交易体验,大部分情况下我们会选择提现离开 L2,因此下面以强制提现为例介绍 forceInclusion 的用法。
回顾在 ETH 提现步骤中,只有步骤 1、2 是涉及到定序器审查的,所以只需要更改这两步:
·调用 L1 上慢箱合约中的 inbox.sendL2Message(),输入参数就是在 L2 上调用 withdrawEth() 时需要输入的参数。该消息会共享给 L1 上的 bridge 合约。
·等待 24 小时的强制归集等待期后,调用快箱中的 force Inclusion() 进行强制归集,快箱合约会检视 bridge 中是否有对应消息。
最终用户可以在 Outbox 中提现,其余步骤由同正常的提现相同。
另外,arbitrum-tutorials 中也有使用 Arb SDK 的详细教程去指导用户如何通过 forceInclusion() 去进行 L2 本地交易和 L2 到 L1 交易。
原文链接
欢迎加入律动 BlockBeats 官方社群:
Telegram 订阅群:https://t.me/theblockbeats
Telegram 交流群:https://t.me/BlockBeats_App
Twitter 官方账号:https://twitter.com/BlockBeatsAsia