SCOPE——面向以太坊二层网络的同步可组合性协议
SCOPE是一个最小化的推送式同步可组合性协议,使以太坊和Rollup上的合约能够相互调用并立即处理结果,如同在单链上运行。
感谢 Ellie、Luca、Florian 和 Ladislaus 提供的所有反馈,以及审阅此文的各个团队。反馈并不必然代表认同。
SCOPE 是一种基于推送的同步可组合性最小化协议,使以太坊和 Rollup 上的合约能够相互调用并立即处理结果,就像它们运行在单一条链上一样。它支持所有方向(L1↔L2 和 L2↔L2),均在一个原子执行范围内完成。最小概念验证可在此查看。
动机
以太坊以 Rollup 为核心的路线图提供了一条在保持安全性的同时进行拓展的路径,但代价是碎片化。每个 Rollup 作为一个独立的执行环境运行,拥有自己的状态、用户和开发者生态系统。这种碎片化削弱了以太坊最初强大起来的一个核心属性:可组合性。
可组合性允许智能合约像乐高积木一样交互:无许可、富有表现力且即时。随着我们跨 Rollup 进行水平扩展,必须努力弥合这种碎片化。理想状态是同步可组合性(SC),即一个链上的智能合约可以直接调用另一个链上的合约并立即消费结果,保持单一共享区块空间的开发者体验。
围绕跨链意图的努力日益增多;然而,这些方法往往狭隘地专注于代币转账。可组合性是一个更广泛的目标:它使合约能够跨链协调逻辑,而不仅仅是流动性。Fabric 一直专注于引领 Based Rollup 的发展,因为它们独特地实现了同步可组合性,不仅是在 Rollup 之间,更重要的是在以太坊和 Rollup 之间。在此基础之上,SCOPE(Ethereum 同步可组合性协议)是一个旨在实现同步可组合性完整愿景的框架,最终增强以太坊的网络效应。
背景
同步可组合性是一种特性,允许一个链上的合约在另一个链上调用函数,并在同一执行上下文(例如单个 L1 Slot)内立即接收并根据结果采取行动。关键在于,跨链交互必须是原子的:要么双方都成功,要么都失败。
有两个值得注意的设计已经展示了原子同步可组合性:
- CIRC(协调性跨 Rollup 通信)引入了一种基于邮箱的框架,用于 Rollup 之间高效、可验证的消息传递。CIRC 是一种基于拉取的设计:一个链上的合约可以检查从另一个链发送的消息,并根据这些消息决定其执行。然而,CIRC 不允许消息触发执行,它需要两笔交易:一笔在源链上写入消息,另一笔在目标链上消费消息。
- Ultra Transactions 采用了一种基于推送的模型,将所有跨链活动打包成一个包含 Blob 和结算证明的单一 L1 捆绑交易。如果引入了
XCALLOPTIONS预编译,合约可以无缝地调用其他链上的合约。任何 L1 合约如果与ExtensionOracle合约集成,并愿意信任 Ultra 交易的证明系统,都可以将其执行推迟到更便宜的 Rollup 执行环境。
SCOPE
它是什么?
SCOPE 建立在上述两种方法之上,提供了一个通用框架,用于同步、最小信任的跨链函数调用:
- 从 CIRC 继承而来的是使用邮箱承诺进行高效、可验证的消息核算。
- 从 Ultra Transactions 继承而来的是采用基于推送的执行模型,并利用账户抽象捆绑器将跨链执行统一到单个原子范围内。
SCOPE 提供了两者:
- 一套标准化的智能合约(例如
ScopedCallable),Rollup 可以继承以支持 SC 调用。 - 一种易于派生的协议,Rollup 可以实现该协议以确保跨链执行、捆绑和验证期间的兼容性。
用大白话解释
把以太坊想象成大陆,每个 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 目标链的 ID,`ScopedRequest` 将在其上执行。
/// @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,就像本地调用一样。
从初始哈希 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 调用不需要在同一 Slot 内生成有效性证明。唯一的要求是参与的 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 Slot 内原子结算。这需要与 L1 提议者和 L2 排序器紧密协调,或者理想情况下,超级构建者同时扮演两者角色。
L1 区块空间控制: 第一个挑战是只有 L1 提议者决定区块中包含什么。如果 L2 排序器基于一个 L1 状态模拟了 scopedCall(),但 L1 提议者通过插入或重新排序交易使该状态失效,那么 L2 的模拟将无效,结算将失败。为了避免这种情况,超级构建者必须在 L2 证明生成时对 L1 内容具有确定性。这意味着超级构建者要么是 L1 提议者,要么与其协调排序。
实时结算: 接下来,原子性要求两个链一起结算,并在滚动哈希检查失败时回滚。但对于 L1↔L2,只有 Rollup 状态可以回滚。为了保持原子性,所有 scopedCall() 活动(L1 函数调用、Blob 提交和证明验证)必须捆绑到单个 L1 交易中。如果任何滚动哈希检查失败,整个捆绑交易将回滚,从而回滚 L1 和 Rollup 状态。重要的是,因为 L2 必须消费 L1 状态,它必须在同一个 L1 Slot 内模拟和结算,这引入了 L2↔L2 情况下不存在的实时证明要求。
委托执行: 最后,Rollup 通常允许其排序器代表用户注入交易,例如在存款后铸造 ETH。L1 原生不支持这种委托,因此为了支持 L2→L1 scopedCall(),我们依赖 EIP-7702 的委托执行。用户签署一个授权某项操作的负载,捆绑器将该负载包装成 L1 交易。
示例
本示例演示了使用 scopedCall() 的跨链代币交换,其中 L1 合约与 L2 交互以执行交换并立即将获得的 ERC-20 代币提取回 L1。排序器通过 fillResponsesIn() 预填充交换结果,允许在 Rollup 结算之前在同一笔交易中进行提取。与标准的 Merkle 证明提取不同,这里的 withdraw() 调用可以是无许可的,因为整个捆绑交易(包括提取)受到原子回滚的保护(如果证明失败或滚动哈希不匹配),从而防止任何未经授权的资金流失。
模拟
在模拟和排序时,超级构建者必须确保每个 Rollup 遵循跨链调用的偏序:
- 在源链上,所有
scopedCall必须按明确定义的相对顺序出现。 - 在目标链上,所有相应的
handleScopedCall必须按匹配的相对顺序出现。 - 其他所有内容(普通交易)可以交错,只要它们不会以改变滚动哈希的方式改变
ScopedRequest负载或计算出的response。
具体来说:
- 在对
scopedCall(req)排序后,超级构建者不得包含会改变req的源链交易,例如改变to、value、gasLimit或data。 - 在模拟
handleScopedCall(req)并捕获response后,超级构建者不得包含会改变response的目标链交易。
为了模拟,超级构建者将:
- 拦截源链的
scopedCall() - 在本地更新源链的
requestsOutHash - 将
handleScopedCall()插入并执行到目标链 - 将
response传递回源链的执行
通过重复此过程,超级构建者将确定调用 fillResponsesIn() 所需的所有 response 值。
附录
这里我们分析流行的 Rollup 堆栈,以确定支持 SCOPE 需要哪些更改,假设目标是同步的 L1↔L2 可组合性。
SCOPE 核心要求(L1↔L2)
- 共享排序器:一个共同的排序器必须协调所有参与 Rollup 之间的跨链交易流。
- L1 客户端修改:L1→L2
scopedCall()在模拟时和执行时需要不同的行为。 - L2 客户端修改:除了核心状态转换函数外,Rollup 必须证明所有跨链请求和响应的滚动哈希等价性,确保参与链之间的可验证一致性。
- L1 桥接器修改:桥接器必须跟踪滚动哈希(
requestsInHash、requestsOutHash、responsesInHash、responsesOutHash),以通过结算时的等价性检查强制原子性。 - 实时证明:Rollup 必须在同一 L1 Slot 内生成并提交有效性证明(即不可争议的 Rollup),才能参与原子
scopedCall()执行。 - L1 提议者协调:排序器必须是 L1 提议者,或者必须获得状态锁定,以保证 L1 端的
scopedCall()完全按模拟执行。 - 返回值支持:提前模拟跨链调用,并在 L1 和 L2 上跟踪
responsesOutHash、responsesInHash和resultsIn映射。这允许调用合约同步消费返回值,就像调用是本地的一样。
案例研究:Ethrex
Ethrex 堆栈通过特权交易支持基于推送的 L1→L2 跨链调用(无返回值),以及基于拉取的 L2→L1 消息传递。
L1→L2 当前状态
- 调用
CommonBridge.sendToL2()会发送一个任意的SendValues负载,其中编码了目标 L2 函数调用。负载的哈希被追加到pendingTxHashes数组中,并触发一个PrivilegedTxSent事件。 - 排序器监听
PrivilegedTxSent事件,并向 L2 内存池注入一个PrivilegedL2Transaction。该交易执行SendValues中编码的函数调用。 - 结算期间,证明系统收集所有
PrivilegedL2Transactions,计算它们的滚动哈希,并验证它是否与通过OnChainProposer.verifyBatch()在链上计算的pendingTxHashes的滚动哈希匹配。
在 SCOPE 模型下,该机制等同于验证 L1 requestsOutHash 匹配 L2 requestsInHash。
L2→L1 当前状态
- 调用
L2ToL1Messenger.sendMessageToL1(bytes32 data)会触发一个L1Message事件,其中data是正在发送的消息的哈希。 - 排序器从所有这些
data值构建一棵 Merkle 树,并在 L2 结算期间提交根。 - 要最终确定消息,用户提供原始消息和 Merkle 证明,以验证该消息已被 L2 提交。
目前,L1 桥接器仅支持代币提取。由 CommonBridge 合约发起的通用 L1 函数调用尚不支持,但添加起来会很简单。
SCOPE 兼容性要求:
- 将
sendToL2()替换为scopedCall(),引入responsesOutHash、responsesInHash和resultsIn映射,以允许调用合约立即消费跨链函数调用的返回值。 - 不再等待 L1 上发出的
PrivilegedTxSent事件,排序器应在 L1 区块确认之前预注入PrivilegedL2Transactions,从而实现跨链的同步执行。 - 将基于拉取的 L2→L1 消息传递替换为从 L2 发起并由 L1 上的
handleScopedCall()处理的基于推送的scopedCall()。这使得任意 L1 合约调用能够在同一 L1 Slot 内执行。
案例研究:OP Stack
OP Stack 支持双向、基于推送的跨链调用(无返回值)。
SCOPE 也可以应用于 SuperChain,以实现 L2↔L2 同步可组合性,无需共享排序器或实时证明,只要参与的 Rollup 共享结算层并相互信任对方的排序。
L1→L2 当前状态
- 调用 L1
CrossDomainMessenger.sendMessage()允许将任意不透明字节作为 Calldata 发送到目标 L2 合约。 OptimismPortal.depositTransaction()将任何 ETH 托管在保险箱中并触发一个TransactionDeposited事件。- 排序器监听
TransactionDeposited事件,并在 L2 上注入一笔调用CrossDomainMessenger.relayMessage()的交易,该交易使用之前发送的数据执行 L2 函数调用。
派生管线确保所有 TransactionDeposited 事件都对应一个被中继的消息。否则,排序器就是欺诈。
L2→L1 当前状态
- 调用 L2
CrossDomainMessenger.sendMessage()允许将任意不透明字节作为 Calldata 发送到目标 L1 合约。 L2ToL1MessagePasser.initiateWithdrawal()将消息哈希记录在sentMessages映射中,并触发一个MessagePassed事件。- 排序器提出一个包含
output_root的 L2output,提交到sentMessages映射的状态。 - 用户通过调用 L1
OptimismPortal.proveWithdrawalTransaction()并附带 Merkle 证明来证明消息的包含。 - 在欺诈证明窗口期过后,调用 L1
OptimismPortal.finalizeWithdrawalTransaction()来执行 L1 函数调用。
如果使用有效性证明,这种基于拉取的方法可以简化为两笔交易:一笔在 L2 上发起消息,另一笔在 L1 上证明并最终执行。
SCOPE 兼容性要求:
- 支持能够进行实时证明的有效性证明,以允许 Rollup 在单个 L1 Slot 内结算。
- 支持同步的 L1→L2
scopedCall(),允许op-node将SequencerConfDepth设置为0,并允许CrossDomainMessenger.relayMessage()在 L1 上TransactionDeposited事件之前被调用。 - 通过将当前多步 L2→L1 消息传递过程替换为单个由 L2 发起的
scopedCall(),启用同步的 L2→L1 调用。
案例研究:Taiko
Taiko 堆栈支持双向、基于拉取的跨链调用(无返回值)。
L1→L2
- 调用
Bridge.sendMessage()允许发送一个任意Message,其中编码了对目标 L2 合约的函数调用。该Message被哈希(创建一个_信号_)并存储在 L1SignalService合约中。 - 用户使用标准
eth_getProofRPC 生成一个存储证明,证明其信号存在于 L1 上。 - 每个 Taiko 区块以一个_锚定交易_开始,该交易注入当前的 L1 世界状态根和一组新信号。然后这些信号被写入 L2
SignalService合约。 - 通过使用
Message和 L1 存储证明调用 L2Bridge.processMessage(),用户证明该消息已包含在 L1 上。这是通过对照 L1 世界状态根和 L2SignalService合约验证信号来完成的。 _invokeMessageCall()随后在目标 L2 合约上执行Message中编码的函数调用。- 结算期间,系统验证锚定交易中报告的信号是否与写入 L1
SignalService的信号匹配。
等价信号检查在功能上等同于 SCOPE 中比较 L1 requestsOutHash 和 L2 requestsInHash。
L2→L1
- 用户调用
L2 Bridge.sendMessage(),将消息哈希(信号)存储在 L2SignalService合约中。 - 一旦 Rollup 结算且其世界状态最终确定,可以调用 L1
Bridge.processMessage(),并附上原始Message和存储证明,证明该信号存在于 L2SignalService合约中。 _invokeMessageCall()随后在 L1 合约上执行编码的函数调用。
SCOPE 兼容性要求:
- 将当前基于信号的流程替换为基于推送的模型,其中
Bridge.sendMessage()等同于scopedCall()。排序器不再记录单个信号并将其中继到目标链,而是直接中继完整的Message。源链将消息追加到滚动requestsOutHash,而目标链在handleScopedCall()期间计算匹配的requestsInHash。这消除了 Merkle 证明的需要,因为任何被篡改的Message都会导致滚动哈希检查失败,从而阻止结算。 Bridge.processMessage()等同于handleScopedCall(),应立即调用,执行Message并更新requestsInHash和responsesOutHash。此函数可以是无许可的,因为理性的提议者必须确保消息按正确顺序处理,以便 Rollup 结算(即,使requestsInHash匹配requestsOutHash)。
案例研究:Linea
Linea 堆栈支持双向跨链调用(无返回值),根据是否使用 Postman Service,采用基于推送或基于拉取的传递方式。
L1→L2 当前状态
- 调用
L1MessageService.sendMessage()将不透明字节(Calldata)发送到目标 L2 合约。消息哈希被纳入滚动哈希中,并在 L1 上触发一个MessageSent事件。 - 一个_协调器_服务监控这些事件,等待两个 L1 纪元以确保最终性,然后调用 L2 上的
L2MessageManager.anchorL1L2MessageHashes()。这会将消息哈希写入 L2 并触发一个RollingHashUpdated事件,确保相同的滚动哈希可以在两条链上重新计算。 - 最后,
L2MessageServiceV1.claimMessage()执行 L2 函数调用,设置一个标志以防止重放,并触发MessageClaimed。这可以由用户手动调用,也可以由 Postman Service 自动调用(如果用户预付了 L1 费用)。 - 结算时,L2 发出的最终
RollingHashUpdated会与 L1 上的滚动哈希进行核对,以验证跨链消息的一致性。
在 SCOPE 模型下,该机制等同于验证 L1 requestsOutHash 匹配 L2 requestsInHash。
L2→L1 当前状态
- 调用
L2MessageServiceV1.sendMessage()触发一个包含消息哈希的MessageSent事件,并将任意不透明字节编码为针对目标 L1 合约的 Calldata。 - 结算期间,证明者 从所有发出的消息哈希值构建一棵 Merkle 树,并将 Merkle 根提交到 L1。
- 要完成消息,用户(或 Postman Service)调用
L1MessageService.claimMessageWithProof(),并附上原始消息及其 Merkle 证明,从而执行 L1 函数调用。
SCOPE 兼容性要求:
- 移除
anchorL1L2MessageHashes()步骤,并在claimMessage()期间增量更新 L2 滚动哈希,该函数将等同于handleScopedCall()。排序器必须强制消息按正确顺序声明,以保证 L1 和 L2 之间的滚动哈希一致性。 - 将当前在
claimMessageWithProof()中使用的 Merkle 证明验证替换为基于推送的模型,使用从 L2 发起的scopedCall()。L2 跟踪requestsOutHash,当调用handleScopedCall()时,L1 计算匹配的requestsInHash。 LineaRollup.submitBlobs()和LineaService.finalizeBlocks()必须捆绑在一起并原子执行。这确保 L2 实时结算。
基于预确认和 SCOPE
预确认不是 SCOPE 的严格必要条件。可以想象一个“完全无政府状态”的 Based Rollup,任何人都可以充当超级构建者,提出包含跨链调用的有效捆绑交易,而不提供预确认。这种模型可行,但自然,预确认可以通过更早给用户确定其交易结果来提高用户体验。
执行预确认通常被认为是黄金标准,但 SCOPE 的操作顺序使其复杂化:由于需要填充 responsesIn 映射,scopedCall() 的后状态在模拟和执行之间不同。为了效率,超级构建者可能在所有模拟运行后插入单次 fillResponsesIn() 调用,而不是在每个 scopedCall() 之前。在 Rollup 上,可以通过在每个 scopedCall() 之前立即放置 fillResponsesIn() 来解决,这在 Gas 上是可行的。在 L1 上,另一种方法是让超级构建者预确认前滚动哈希和后滚动哈希以及它们对应的请求和响应数据。这种方法让用户可靠地了解跨链请求的结果,这种方式在出现故障时更容易证明,也更容易从超级构建者发布(因为它不需要中间状态根)。
先前的工作如 CUSTARD 探索了扩展“超级交易”预确认可行性的可能性,这些预确认在超级构建者的 Slot 之前做出。需要更多研究来确定当 ScopedRequests 依赖于无法用 CUSTARD 描述的技术锁定的任意有状态数据时(例如,ScopedRequest 可能包含在 scopedCall() 执行时确定的价格馈送数据,这很可能不同于其模拟和预确认的 Slot),这样的早期 scopedCall() 发布是否安全。
SCOPE vs AggLayer
SCOPE 和 AggLayer 都旨在实现无需信任的跨链消息传递,但它们从不同角度处理这个问题。AggLayer 是一个功能齐全的互操作性协议,拥有自己的结算规则,而 SCOPE(尽管名称中包含“协议”一词)主要是一个会计框架,可以覆盖在现有系统(如 AggLayer、Superchain 或 Elastic Network)之上。
两个系统都共享相同的悲观证明哲学:链独立证明自己的状态转换,并且仅当密码学等价性检查通过时才结算。在 AggLayer 中,这通过“本地退出树”体现,其根扮演着与 SCOPE 的 requestsOutHash 类似的角色。区别在于 AggLayer 和类似协议原生不支持跨链调用的同步返回值。消息发出,但没有在同一执行中返回任何东西。
SCOPE 通过同时跟踪入站响应扩展了此模型,例如通过 responsesInHash 或假设的“本地入口树”。这允许链同步消费返回数据,就像它们是统一环境的一部分一样。结果是从简单的消息传递转变为真正的共享执行范围,同时保持相同的结算时安全保证。
- 原文链接: ethresear.ch/t/scope-syn...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~



