SCOPE——以太坊Layer 2的同步可组合性协议

以太坊中文 发布于 2025-08-26 阅读 125

SCOPE是一个最小化的协议,用于实现基于推送的同步可组合性,使得以太坊和rollup上的合约可以相互调用并立即处理结果,如同生活在单一链上。

感谢 EllieLucaFlorianLadislaus 提供的所有反馈,以及各个团队的审阅。反馈不一定代表认可。

SCOPE 是一个基于推送的同步可组合性最小协议,使以太坊和 Rollup 上的合约能够相互调用并立即处理结果,就像它们位于同一条链上一样。它支持所有方向(L1↔L2 和 L2↔L2),在单个原子执行范围内完成。一个最小概念验证可以在这里找到。

动机

以太坊以 Rollup 为核心的路线图提供了一条在保证安全性的同时实现扩展的路径,但代价是碎片化。每个 Rollup 都作为独立的执行环境运行,拥有自己的状态、用户和开发者生态系统。这种碎片化削弱了以太坊最初拥有的一项核心属性:可组合性

可组合性使得智能合约能够像乐高积木一样交互:无需许可、富有表现力且即时。当我们跨 Rollup 水平扩展时,必须努力修复这种碎片化。理想状态是同步可组合性(SC),即一条链上的智能合约可以直接调用另一条链上的合约并立即消费结果,保留单一共享区块空间的开发者体验。

围绕跨链意图的努力越来越多;然而,这些方法往往狭隘地聚焦于代币转账。可组合性是一个更广泛的目标:它使合约能够跨链协调逻辑,而不仅仅是流动性。Fabric 一直专注于引导基于以太坊的 Rollup(based rollups)的发展,因为它们不仅能够实现 Rollup 之间的同步可组合性,更重要的是能够实现以太坊与 Rollup 之间的同步可组合性。在此基础上,SCOPE(以太坊同步可组合性协议)是一个旨在实现同步可组合性完整愿景的框架,最终加强以太坊的网络效应。

背景

同步可组合性是指一条链上的合约能够调用另一条链上的函数,并在同一执行上下文(例如单个 L1 插槽)内立即接收并处理结果。关键在于,跨链交互必须是原子性的:要么双方都成功,要么都不成功。

两种值得注意的设计已经展示了原子同步可组合性:

  • CIRC(协调的跨 Rollup 通信)引入了一个基于邮箱的框架,用于在 Rollup 之间进行高效、可验证的消息传递。CIRC 是一种基于拉取的设计:一条链上的合约可以检查从另一条链发送的消息,并根据这些消息的条件执行。然而,CIRC 不允许消息触发执行,它需要两笔交易:一笔在源链上写入消息,另一笔在目标链上消费消息。
  • Ultra Transactions 采用基于推送的模型,将所有跨链活动打包成单个 L1 捆绑交易,其中包含 blob 和结算证明。如果引入 XCALLOPTIONS 预编译,合约可以无缝调用其他链上的合约。任何 L1 合约如果集成了 ExtensionOracle 合约并愿意信任 Ultra 交易的证明系统,就可以将其执行推迟到更便宜的 Rollup 执行环境。

SCOPE

是什么?

SCOPE 建立在上述两种方法之上,提供了一个通用框架,用于实现同步、最小信任的跨链函数调用:

  • CIRC 继承了使用邮箱承诺的高效、可验证的消息记账。
  • Ultra Transactions 采用了基于推送的执行模型,并利用账户抽象打包器将跨链执行统一到单个原子范围内。

SCOPE 提供了两者:

  • 一套标准化的智能合约(例如 ScopedCallable),Rollup 可以继承这些合约来支持 SC 调用。
  • 一个对推导友好的协议,Rollup 可以实现该协议,以确保跨链执行、打包和验证期间的兼容性。

ELI5

想象以太坊是大陆,每个 Rollup 是近海的一个岛屿。如今,岛屿之间的通信就像在瓶中发送消息。消息漂移几分钟或几小时后才到达,发送者得不到任何确认,更不用说可用的回应了。SCOPE 让所有岛屿仿佛都与大陆相连,能够立即进行双向的完整对话。你可以发言、得到答案并立即采取行动,恢复了岛屿形成之前以太坊曾经拥有的无缝协调能力。

SCOPE 不仅使 Rollup 能够原子性地在彼此之间以及 L1 之间发送消息,还允许一条链上的用户调用其他链上的函数,并立即接收和处理结果。这提供了在单条链上操作的体验,同时保留了 Rollup 的可扩展性优势。

工作原理

核心上,SCOPE 形式化了可验证的基于推送的跨链交易所需的记账模型。每个参与的链维护四个滚动哈希和一个字节映射,代表跨链请求和响应的集体序列:

  • requestsOutHash:记录此链发起的传出跨链调用。
  • requestsInHash:记录将在本链上执行的传入跨链调用。
  • responsesOutHash:记录本链执行的跨链调用的传出响应。
  • responsesInHash:记录本链发起的跨链调用的传入响应。
  • responsesIn:以原始字节形式记录本链发起的跨链调用的传入响应。

scopedCallable 接口定义了如何更新这些值:

interface IScopedCallable {
    /// @notice 描述跨链函数调用的结构体。
    struct ScopedRequest {
        address to;
        uint256 value;
        uint256 gasLimit;
        bytes data;
    }

    /// @notice 发起一个同步跨链调用。
    /// @dev 发送一个事件,更新 nonce,并更新 requestsOutHash。
    ///      从 responsesIn 数组中读取结果(由排序器预填充)。
    /// @param targetChainId 执行 `ScopedRequest` 的目标链的 ID。
    /// @param from 在源链上发起跨链调用的地址。
    /// @param request 目标链的编码函数调用。
    /// @return response 从 responsesIn 数组返回的结果字节。
    function scopedCall(
        uint256 targetChainId,
        address from,
        ScopedRequest calldata request
    ) external payable returns (bytes memory response);

    /// @notice 执行一个跨链调用。
    /// @dev 由排序器调用。更新 requestsInHash,发送事件,并更新 responsesOutHash。
    /// @param sourceChainId 发起 `ScopedRequest` 的源链的 ID。
    /// @param from 源链上的发送者地址。
    /// @param nonce 用于去重的唯一 nonce。
    /// @param request 本地执行的编码调用。
    function handleScopedCall(
        uint256 sourceChainId,
        address from,
        uint256 nonce,
        ScopedRequest calldata request
    ) external;

    /// @notice 用预先模拟的跨链调用响应预填充 responsesIn 数组。
    /// @dev 每个响应更新对应链 ID 的 responsesInHash。
    /// @dev 所有数组必须具有相同的长度(即 chainIds[i] 对应于 reqHashes[i])。
    /// @param chainIds 响应来源的链 ID。
    /// @param reqHashes 原始跨链请求的哈希。
    /// @param responses 来自目标链的执行结果。
    function fillResponsesIn(
        uint256[] calldata chainIds,
        bytes32[] calldata reqHashes,
        bytes[] calldata responses
    ) external;

    /// @notice 返回给定链的当前滚动邮箱哈希。
    /// @param chainId 跟踪滚动哈希的远程链 ID。
    /// @return requestsOut 此链出站请求的滚动哈希。
    /// @return requestsIn 此链入站请求的滚动哈希。
    /// @return responsesOut 此链出站响应的滚动哈希。
    /// @return responsesIn 此链入站响应的滚动哈希。
    function getRollingHashes(uint256 chainId)
        external
        view
        returns (
            bytes32 requestsOut,
            bytes32 requestsIn,
            bytes32 responsesOut,
            bytes32 responsesIn
        );
}

向开发者暴露的核心原语是 scopedCall() 函数。该函数允许一个 Rollup 上的合约同步调用另一个 Rollup 上的函数并立即消费结果。当 scopedCall() 被调用时,它将一个唯一的请求标识符追加到源链的 requestsOutHash 中,并从本地的 responsesIn 映射中读取预填充的响应。从调用者的角度来看,因为排序器已经模拟了目标链上的调用并预先填充了 responsesIn,所以这种交互看起来是同步的。scopedCall 这个名称反映了整个跨链交互(请求、执行和响应)在单个原子执行范围内完成,从而在跨链之间模拟出本地可组合性的效果。

在目标链上,排序器执行 handleScopedCall(),它将相同的请求标识符混合到其 requestsInHash 中,执行 ScopedRequest,并用结果更新 responsesOutHash。然后,这个输出被中继回源链,并插入到源链的 responsesIn 中。

在结算时,桥接器验证两条链是否遵守了请求和响应的正确顺序。具体来说,它检查:

  • 源链的 requestsOutHash 是否与目标链的 requestsInHash 匹配。
  • 源链的 responsesInHash 是否与目标链的 responsesOutHash 匹配。

如果任何调用被跳过、重新排序或篡改,这些滚动哈希将不匹配,Rollup 将无法结算,从而确保原子性。

L2↔L2 同步可组合性

SCOPE 在 L2↔L2 交互中尤其干净地工作。假设 Rollup A 上的一个合约需要调用 Rollup B 上的一个函数。共享排序器观察到 A 的 scopedCall(),并立即在 B 上注入一个匹配的 handleScopedCall()。在 B 上执行目标函数并获得 response 后,排序器在 A 上预填充一个 fillResponsesIn() 交易,以便当 A 的 scopedCall() 实际运行时,它可以同步地读取并处理 response,就像本地调用一样。

image

H(0) 的初始哈希开始,流程产生:B.requestsInHash = A.requestsOutHash = H(H(0) || H(ScopedRequest))A.responsesInHash = B.responsesOutHash = H(H(0) || H(response))。如果排序器错误地注入请求,B 的 requestsInHash 将与 A 的 requestsOutHash(源自 scopedCall())不匹配。如果排序器错误地中继响应,A 的 responsesInHash 将与 B 的 responsesOutHash(源自 handleScopedCall())不匹配。任何一个不匹配都会破坏结算时的相等性检查,相当于篡改了 EVM 执行,标准的 state transition proofs 将拒绝它。

优势

  • 并行证明: 每个 Rollup 并行地独立证明自己的状态转换,因为两条链都有完整的有序交易序列。唯一的跨链依赖是结算时最终的滚动哈希等价性检查。
  • 无强制共享排序器: 虽然共享排序器可以优化延迟,但只要 Rollup 共享一个结算层并相互信任彼此的排序,SCOPE 就可以与独立排序的 Rollup 一起工作。这使得它直接兼容像 Optimism Superchain 这样的生态系统。
  • 无需实时证明: 与同步 L1↔L2 不同,L2↔L2 调用不需要在同一插槽内生成有效性证明。唯一的要求是参与的 Rollup 最终一起结算,以便可以验证滚动哈希等价性。
  • 通过共享承诺摊销成本: 当两个 Rollup 承诺进行共享的 L2↔L2 执行时,它们 1) 可以共享 blob 空间,2) 共享单个有效性证明。这减少了每个 Rollup 的开销,并允许较小的 Rollup 在多个参与者之间摊销 blob 和证明成本。

L1↔L2 同步可组合性

假设共享排序和共享结算,L2↔L2 同步调用相对容易推理,但引入 L1 会使事情复杂化。为了使同步 L1↔L2 scopedCall() 可行,我们需要解决三个相互交织的挑战:对 L1 区块空间的控制、链之间的原子性以及代表用户执行 L1 交易的能力。

SCOPE 的核心是一个超级构建者,灵感来自 Ultra Transactions 模型。超级构建者负责模拟整个跨链调用(包括 L1 和 L2 部分),并确保一切在单个 L1 插槽内原子结算。这需要与 L1 提议者和 L2 排序器紧密协调,或者理想情况下,超级构建者同时扮演这两个角色。

L1 区块空间控制: 第一个挑战是只有 L1 提议者决定区块中包含什么。如果 L2 排序器基于 L1 状态模拟了 scopedCall(),但 L1 提议者通过插入或重新排序交易使该状态无效,那么 L2 的模拟无效,结算将失败。为了避免这种情况,超级构建者在 L2 证明生成时,必须对 L1 内容具有确定性。这意味着超级构建者要么是 L1 提议者,要么与之协调排序。

实时结算: 接下来,原子性要求两条链一起结算,如果滚动哈希检查失败则回滚。但对于 L1↔L2,只有 Rollup 状态可以回滚。为了保持原子性,所有 scopedCall() 活动(L1 函数调用、blob 提交和证明验证)必须打包到单个 L1 交易中。如果任何滚动哈希检查失败,整个包将回滚,回滚 L1 和 Rollup 状态。重要的是,因为 L2 必须消费 L1 状态,它必须在同一 L1 插槽内模拟并结算,这引入了 L2↔L2 场景中不存在的实时证明要求。

委托执行: 最后,Rollup 通常允许其排序器代表用户注入交易,例如在存款后铸造 ETH。L1 本身不支持这种委托,因此为了支持 L2→L1 scopedCall(),我们依赖 EIP-7702 的委托执行。用户签署一个授权某个操作的 payload,打包器将该 payload 包装成 L1 交易。

image

示例

这个示例展示了使用 scopedCall() 的跨链代币交换,其中 L1 合约与 L2 交互以执行交换并立即将所得的 ERC-20 提回到 L1。排序器通过 fillResponsesIn() 预填充交换结果,使得在 Rollup 结算之前,可以在同一交易中完成提现。与标准的基于 Merkle 证明的提现不同,这里的 withdraw() 调用可以是无需许可的,因为整个包(包括提现)受到原子回滚的保护,如果证明失败或滚动哈希不匹配,则会回滚,防止任何未经授权的资金流失。

image

模拟

在模拟和排序时,超级构建者必须确保每个 Rollup 遵循跨链调用的偏序:

  • 在源链上,所有 scopedCall 必须按明确定义的相对顺序出现。
  • 在目标链上,所有相应的 handleScopedCall 必须以匹配的相对顺序出现。
  • 其他所有内容(普通交易)可以交错,只要它们不会以改变滚动哈希的方式改变 ScopedRequest payload 或计算出的 response

具体来说:

  • 在排序一个 scopedCall(req) 之后,超级构建者不得包含会改变 req 的源链交易,例如改变 tovaluegasLimitdata
  • 在模拟 handleScopedCall(req) 并捕获 response 之后,超级构建者不得包含会改变 response 的目标链交易。

image

为了模拟,超级构建者将:

  1. 拦截源链的 scopedCall()
  2. 在本地更新源链的 requestsOutHash
  3. 在目标链中插入并执行 handleScopedCall()
  4. response 传回源链的执行

通过重复这个过程,超级构建者将确定调用 fillResponsesIn() 所需的所有 response 值。

附录

这里我们分析流行的 Rollup 栈,以确定需要哪些更改才能支持 SCOPE,假设目标是同步 L1↔L2 可组合性。

SCOPE 核心需求(L1↔L2)

  • 共享排序器:一个共同的排序器必须协调所有参与 Rollup 的跨链交易流。
  • L1 客户端修改:L1→L2 scopedCall() 在模拟和执行期间需要不同的行为。
  • L2 客户端修改:除了核心状态转换函数外,Rollup 必须证明所有跨链请求和响应的滚动哈希等价性,确保参与链之间的可验证一致性。
  • L1 桥接器修改:桥接器必须跟踪滚动哈希(requestsInHashrequestsOutHashresponsesInHashresponsesOutHash),以便在结算时通过等价性检查强制执行原子性。
  • 实时证明:Rollup 必须在同一 L1 插槽内生成并提交有效性证明(即非争议性 Rollup),以参与原子性 scopedCall() 执行。
  • L1 提议者协调:排序器必须是 L1 提议者,或者必须获得状态锁,以保证 scopedCall() 的 L1 部分完全按照模拟执行。
  • 返回值支持:提前模拟跨链调用,并在 L1 和 L2 上跟踪 responsesOutHashresponsesInHashresultsIn 映射。这允许调用合约像本地调用一样同步消费返回值。

案例研究:Ethrex

Ethrex 栈通过特权交易支持基于推送的 L1→L2 跨链调用(无返回值),以及基于拉取的 L2→L1 消息传递。

当前的 L1→L2

  1. 调用 CommonBridge.sendToL2() 发送一个任意的 SendValues payload,该 payload 编码了目标 L2 函数调用。payload 的哈希被追加到 pendingTxHashes 数组中,并发出一个 PrivilegedTxSent 事件。
  2. 排序器监听 PrivilegedTxSent 事件,并向 L2 内存池注入一个 PrivilegedL2Transaction。该交易执行 SendValues 中编码的函数调用。
  3. 在结算期间,证明系统收集所有 PrivilegedL2Transactions,计算它们的滚动哈希,并验证它是否与通过 OnChainProposer.verifyBatch() 链上计算的 pendingTxHashes 的滚动哈希匹配。

该机制相当于在 SCOPE 模型下验证 L1 requestsOutHash 与 L2 requestsInHash 匹配。

当前的 L2→L1

  1. 调用 L2ToL1Messenger.sendMessageToL1(bytes32 data) 发出一个 L1Message 事件,其中 data 是发送消息的哈希。
  2. 排序器从所有这样的 data 值构建一个 Merkle 树,并在 L2 结算期间提交根。
  3. 为了最终确定消息,用户提供原始消息和 Merkle 证明,以验证该消息已被 L2 提交。

目前,L1 桥接器只支持代币提现。由 CommonBridge 合约发起的通用 L1 函数调用尚未支持,但添加起来会很直接。

SCOPE 兼容性需要:

  • sendToL2() 替换为 scopedCall(),引入 responsesOutHashresponsesInHashresultsIn 映射,以允许调用合约立即消费跨链函数调用的返回值。
  • 排序器不应等待 L1 上发出的 PrivilegedTxSent 事件,而应在 L1 区块确认之前预注入 PrivilegedL2Transactions,从而实现跨链的同步执行。
  • 将基于拉取的 L2→L1 消息传递替换为基于推送的 scopedCall(),该调用从 L2 发起,并通过 L1 上的 handleScopedCall() 处理。这使得任意 L1 合约调用能够在同一 L1 插槽内执行。

案例研究:OP Stack

OP Stack 支持双向、基于推送的跨链调用(无返回值)。

SCOPE 也可以应用于 Superchain,以实现 L2↔L2 同步可组合性,而无需共享排序器或实时证明,只要参与的 Rollup 共享一个结算层并相互信任彼此的排序。

当前的 L1→L2

  1. 调用 L1 CrossDomainMessenger.sendMessage() 允许将任意的不透明字节作为 calldata 发送到目标 L2 合约。
  2. OptimismPortal.depositTransaction() 将任何 ETH 锁定在保险箱中,并发出一个 TransactionDeposited 事件。
  3. 排序器监听 TransactionDeposited 事件,并在 L2 上注入一个交易,该交易调用 CrossDomainMessenger.relayMessage(),使用之前发送的数据执行 L2 函数调用。

推导管道确保所有 TransactionDeposited 事件都对应一个被中继的消息。否则,排序器构成欺诈。

当前的 L2→L1

  1. 调用 L2 CrossDomainMessenger.sendMessage() 允许将任意的不透明字节作为 calldata 发送到目标 L1 合约。
  2. L2ToL1MessagePasser.initiateWithdrawal()sentMessages 映射中记录消息哈希,并发出一个 MessagePassed 事件。
  3. 排序器提议一个 L2 output,其中包含一个 output_root,承诺 sentMessages 映射的状态。
  4. 用户通过调用 L1 OptimismPortal.proveWithdrawalTransaction() 并附带 Merkle 证明来证明消息包含。
  5. 在欺诈证明窗口过后,调用 L1 OptimismPortal.finalizeWithdrawalTransaction() 执行 L1 函数调用。

如果使用有效性证明,这种基于拉取的方法可以减少为两笔交易:一笔在 L2 上发起消息,另一笔在 L1 上证明并最终执行。

SCOPE 兼容性需要:

  • 支持能够实时证明的有效性证明,以便 Rollup 在单个 L1 插槽内结算。
  • 通过允许 op-node 设置 SequencerConfDepth = 0,并允许在 L1 上发出 TransactionDeposited 事件之前调用 CrossDomainMessenger.relayMessage(),来支持同步 L1→L2 scopedCall()
  • 通过将当前的多步 L2→L1 消息传递过程替换为单一的 L2 发起的 scopedCall(),支持同步 L2→L1 调用。

案例研究:Taiko

Taiko 栈支持双向、基于拉取的跨链调用(无返回值)。

L1→L2

  1. 调用 Bridge.sendMessage() 允许发送一个任意的 Message,该消息编码了对目标 L2 合约的函数调用。Message 被哈希(创建了一个信号)并存储在 L1 SignalService 合约中。
  2. 用户使用标准 eth_getProof RPC 生成一个存储证明,证明他们的信号存在于 L1 上。
  3. 每个 Taiko 区块以一个锚点交易开始,该交易注入当前 L1 世界状态根和一个新的信号列表。然后这些信号被写入 L2 SignalService 合约。
  4. 通过调用 L2 Bridge.processMessage() 并附带 Message 和 L1 存储证明,用户证明该消息已在 L1 上包含。这是通过针对 L1 世界状态根和 L2 SignalService 合约验证信号来完成的。
  5. _invokeMessageCall() 然后在目标 L2 合约上执行 Message 中编码的函数调用。
  6. 在结算期间,系统验证锚点交易中报告的信号是否与写入 L1 SignalService 的信号匹配。

检查等效信号在功能上相当于在 SCOPE 中比较 L1 requestsOutHash 和 L2 requestsInHash

L2→L1

  1. 用户调用 L2 Bridge.sendMessage(),将消息哈希(信号)存储在 L2 SignalService 合约中。
  2. 一旦 Rollup 结算且其世界状态最终确定,就可以调用 L1 Bridge.processMessage(),并附带原始 Message 和存储证明,显示该信号存在于 L2 SignalService 合约中。
  3. 然后 _invokeMessageCall() 在 L1 合约上执行编码的函数调用。

SCOPE 兼容性需要:

  • 将当前基于信号流的流程替换为基于推送的模型,其中 Bridge.sendMessage() 等同于 scopedCall()。排序器不再记录单个信号并将其中继到目标链,而是直接中继完整的 Message。源链将消息追加到滚动 requestsOutHash,而目标链在 handleScopedCall() 期间计算匹配的 requestsInHash。这消除了对 Merkle 证明的需求,因为任何被篡改的 Message 都会导致滚动哈希检查失败,从而阻止结算。
  • Bridge.processMessage() 等同于 handleScopedCall(),应该立即调用,执行 Message 并更新 requestsInHashresponsesOutHash。这个函数可以是无需许可的,因为一个理性的提议者必须确保消息按正确的顺序处理,以便 Rollup 结算(即 requestsInHash 匹配 requestsOutHash)。

案例研究:Linea

Linea 栈支持双向跨链调用(无返回值),根据是否使用 Postman 服务,采用基于推送或基于拉取的交付方式。

当前的 L1→L2

  1. 调用 L1MessageService.sendMessage() 将不透明字节(calldata)发送到目标 L2 合约。消息哈希被纳入滚动哈希,并在 L1 上发出 MessageSent 事件。
  2. 一个协调器服务监控这些事件,等待两个 L1 周期以获得最终性,然后调用 L2 L2MessageManager.anchorL1L2MessageHashes()。这将消息哈希写入 L2 并发出 RollingHashUpdated 事件,确保两条链上可以重新计算相同的滚动哈希。
  3. 最后,L2MessageServiceV1.claimMessage() 执行 L2 函数调用,设置一个标志以防止重播,并发出 MessageClaimed。这可以由用户手动调用,或者如果用户预付了 L1 费用,则由 Postman 服务 自动调用。
  4. 在结算时,L2 发出的最终 RollingHashUpdated 与 L1 上的滚动哈希进行检查,以验证跨链的消息一致性。

该机制相当于在 SCOPE 模型下验证 L1 requestsOutHash 与 L2 requestsInHash 匹配。

当前的 L2→L1

  1. 调用 L2MessageServiceV1.sendMessage() 发出一个 MessageSent 事件,其中包含消息哈希,并编码任意不透明字节作为目标 L1 合约的 calldata。
  2. 在结算期间,证明者从所有发出的消息哈希值构建一个 Merkle 树,并将 Merkle 根提交到 L1。
  3. 为了完成消息,用户(或 Postman 服务)调用 L1MessageService.claimMessageWithProof(),并附带原始消息及其 Merkle 证明,从而执行 L1 函数调用。

SCOPE 兼容性需要:

  • 移除 anchorL1L2MessageHashes() 步骤,改为在 claimMessage() 期间增量更新 L2 滚动哈希,claimMessage() 等同于 handleScopedCall()。排序器必须强制消息按正确顺序处理,以保证 L1 和 L2 之间的滚动哈希一致性。
  • claimMessageWithProof() 中使用的当前 Merkle 证明验证替换为基于推送的模型,使用从 L2 发起的 scopedCall()。L2 跟踪 requestsOutHash,当调用 handleScopedCall() 时,L1 计算匹配的 requestsInHash
  • LineaRollup.submitBlobs()LineaService.finalizeBlocks() 必须捆绑在一起并以原子方式执行。这确保 L2 实时结算。

基于以太坊的预确认与 SCOPE

预确认不是 SCOPE 的严格需求。可以想象一个“完全无政府”的基于以太坊的 Rollup,任何人都可以充当超级构建者,提议包含跨链调用的有效捆绑包,而不提供预确认。这种模型是可行的,但自然地,预确认可以通过让用户更早确定其交易结果来改善用户体验。

执行预确认通常被认为是黄金标准,但 SCOPE 的操作顺序使其复杂化:由于需要填充 responsesIn 映射,scopedCall() 的后状态在模拟和执行之间有所不同。为了提高效率,超级构建者可以在所有模拟运行后插入一个单一的 fillResponsesIn() 调用,而不是在每个 scopedCall() 之前。在Rollup上,可以通过将 fillResponsesIn() 放在每个 scopedCall() 紧前面来解决,这在 gas 上是可行的。在L1上,另一种方法是超级构建者预确认前后滚动哈希及其对应的请求和响应数据。这种方法让用户能够可靠地了解跨链请求的结果,并且在出现故障时更容易证明,也更容易从超级构建者发布(因为不需要中间状态根)。

之前的工作如 CUSTARD 探索了在超级构建者插槽之前做出的“超级交易”预确认的可行性扩展。需要更多研究来确定,当 ScopedRequest 依赖于无法用 CUSTARD 描述的技术锁定的任意有状态数据时(例如,ScopedRequest 可能包含在 scopedCall() 执行时确定的价格预言机数据,这很可能与其被模拟和预确认时的插槽不同),如此早期发布 scopedCall() 是否安全。

SCOPE 与 AggLayer 对比

SCOPE 和 AggLayer 都旨在实现无需信任的跨链消息传递,但它们从不同角度解决问题。AggLayer 是一个功能齐全的互操作性协议,拥有自己的结算规则,而 SCOPE,尽管名称中包含“协议”,但主要是一个会计框架,可以覆盖在现有系统(如 AggLayer、Superchain 或 Elastic Network)之上。

两个系统共享相同的悲观证明哲学:链独立证明自己的状态转换,并且只有在加密等价性检查通过时才结算。在 AggLayer 中,这通过“本地退出树”(Local Exit Tree)体现,其根与 SCOPE 的 requestsOutHash 扮演类似角色。区别在于,AggLayer 及类似协议本身不支持跨链调用的同步返回值。消息发出去了,但没有任何东西在同一执行中返回。

SCOPE 通过也跟踪入站响应(例如通过 responsesInHash 或假设的“本地进入树”)扩展了该模型。这使得链能够像在统一环境中一样同步消费返回数据。结果是从简单的消息传递转变为真正的共享执行范围,同时保留了相同的结算时安全保障。

  • 原文链接: ethresear.ch/t/scope-syn...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~

相关文章

0 条评论