感谢Ellie 、 Luca 、 Florian和Ladislaus提供的所有反馈,以及参与审核的各团队。反馈并不代表认可。
SCOPE 是一个基于推送的同步可组合性的极简协议,它使以太坊和 Rollup 上的合约能够相互调用并立即处理结果,就像它们位于同一条链上一样。它支持单个原子执行范围内的所有方向,即 L1↔L2 和 L2↔L2。极简概念验证可在此处找到。
动机
以太坊以 Rollup 为中心的路线图在保证安全性的同时,也提供了一条实现扩容的途径,但这是以碎片化为代价的。每个 Rollup 都作为一个独立的执行环境运行,拥有各自的状态、用户和开发者生态系统。这种碎片化削弱了以太坊最初强大的核心属性:可组合性。
可组合性使智能合约能够像乐高积木一样进行交互:无需许可、富有表现力且即时可用。随着我们跨 Rollup 平台的水平扩展,我们必须努力弥合这种碎片化。理想的组合性是同步可组合性 (SC),即一条链上的智能合约可以直接调用另一条链上的合约并立即使用结果,从而保留单一共享区块空间的开发者体验。
围绕跨链目标的探索日益增多;然而,这些方法往往局限于代币转移。可组合性是一个更广泛的目标:它使合约能够协调跨链逻辑,而不仅仅是流动性。Fabric 一直致力于引领基于 Rollup 的协议的发展,因为它们以独特的方式实现了同步可组合性,不仅在 Rollup 之间,更重要的是在以太坊与 Rollup 之间。在此基础上,SCOPE(以太坊同步可组合性协议)是一个旨在实现同步可组合性完整愿景的框架,最终增强以太坊的网络效应。
背景
同步可组合性是一种属性,它允许一条链上的合约调用另一条链上的函数,并在同一执行上下文(例如,单个 Layer-1 插槽)内立即接收并执行结果。至关重要的是,跨链交互必须是原子性的:要么双方都成功,要么都不成功。
两个值得注意的设计已经证明了原子同步可组合性:
- CIRC (Coordinated Inter-Rollup Communication,协调式 Rollup 间通信)引入了一个基于邮箱的框架,用于在 Rollup 之间实现高效、可验证的消息传递。CIRC 采用拉式设计:一条链上的合约可以检查从另一条链发送的消息,并根据这些消息设定执行条件。然而,CIRC 不允许消息触发执行,它需要两笔交易:一笔在源链上写入消息,另一笔在目标链上消费消息。
- Ultra 交易采用基于推送的模型,将所有跨链活动打包成一个包含 blob 和结算证明的 L1 捆绑交易。如果引入
XCALLOPTIONS
预编译,合约可以无缝调用其他链上的合约。任何 L1 合约如果与ExtensionOracle
合约集成,并愿意信任 Ultra 交易的证明系统,都可以将其执行推迟到更便宜的 rollup 执行环境。
范围
它是什么?
SCOPE 在两种先前方法的基础上,为同步、信任最小化的跨链函数调用提供了一个通用框架:
- 它从CIRC继承了使用邮箱承诺的高效、可验证的消息会计。
- 从超级交易 (Ultra Transactions)来看,它采用基于推送的执行模型,并利用账户抽象捆绑器将跨链执行统一到单个原子范围内。
SCOPE 提供以下服务:
- Rollups 可以继承一组标准化智能合约(例如
ScopedCallable
),以支持 SC 调用。 - Rollups 可以实现一种派生友好协议,以确保跨链执行、捆绑和验证期间的兼容性。
ELI5
想象一下,以太坊是大陆,而每个 rollup 就像近海的岛屿。如今,岛屿之间的交流就像漂流瓶中的信息。信息会在漂流中漂流几分钟甚至几小时才能到达,发送者得不到任何确认,更不用说可用的回复了。SCOPE 让所有岛屿都感觉连接到大陆,双向对话可以即时进行。你可以发言,得到回复,并立即采取行动,恢复以太坊在岛屿形成之前的无缝协作。
SCOPE 不仅使 rollups 能够原子性地在彼此之间以及 Layer-1 之间发送消息,还允许一条链上的用户调用其他链上的函数并立即接收和处理结果。这不仅提供了在单链上操作的体验,同时保留了 rollups 的可扩展性优势。
它是如何工作的?
SCOPE 的核心是形式化可验证的基于推送的跨链交易所需的会计模型。每条参与链维护四个滚动哈希值和一个单字节映射,用于表示跨链请求和响应的集合序列:
-
requestsOutHash
:记录本链发起的传出跨链调用。 -
requestsInHash
:记录要在此链上执行的传入跨链调用。 -
responsesOutHash
:记录在此链上执行的跨链调用的传出响应。 -
responsesInHash
:记录由该链发起的跨链调用的传入响应。 -
responsesIn
:将该链发起的跨链调用的传入响应记录为原始字节。
scopedCallable
接口定义了如何更新这些值:
interface IScopedCallable {/// @notice Struct describing a cross-chain function call.struct ScopedRequest {address to;uint256 value;uint256 gasLimit;bytes data;}/// @notice Initiates a synchronous cross-chain call./// @dev Emits an event, updates a nonce, and updates the requestsOutHash./// Reads the result from the responsesIn array (pre-filled by the sequencer)./// @param targetChainId The ID of the chain the `ScopedRequest` will execute on./// @param from The address that initiated the cross-chain call on the source chain./// @param request Encoded function call for the destination chain./// @return response The result bytes returned from the responsesIn array.function scopedCall(uint256 targetChainId,address from,ScopedRequest calldata request) external payable returns (bytes memory response);/// @notice Executes a cross-chain call./// @dev Called by the sequencer. Updates requestsInHash, emits an event, and updates responsesOutHash./// @param sourceChainId The ID of the chain the `ScopedRequest` was initiated from./// @param from The sender address on the origin chain./// @param nonce A unique nonce for deduplication./// @param request Encoded call to execute locally.function handleScopedCall(uint256 sourceChainId,address from,uint256 nonce,ScopedRequest calldata request) external;/// @notice Pre-fills the responsesIn array with pre-simulated responses of cross-chain calls./// @dev Each response updates the responsesInHash for the corresponding chain ID./// @dev All arrays must have the same length (ie, chainIds[i] corresponds to reqHashes[i])./// @param chainIds The chain IDs from which the responses originate./// @param reqHashes The hashes of the original cross-chain requests./// @param responses The execution results from the destination chain.function fillResponsesIn(uint256[] calldata chainIds,bytes32[] calldata reqHashes,bytes[] calldata responses) external;/// @notice Returns the current rolling mailbox hashes for a given chain./// @param chainId The remote chain ID whose rolling hashes are tracked./// @return requestsOut Rolling hash of this chain's outbound requests./// @return requestsIn Rolling hash of this chain's inbound requests./// @return responsesOut Rolling hash of this chain's outbound responses./// @return responsesIn Rolling hash of this chain's inbound responses.function getRollingHashes(uint256 chainId)externalviewreturns (bytes32 requestsOut,bytes32 requestsIn,bytes32 responsesOut,bytes32 responsesIn);}
向开发人员公开的核心原语是scopedCall()
函数。该函数允许一个 rollup 上的合约同步调用另一个 rollup 上的函数并立即使用结果。调用scopedCall()
时,它会将一个唯一的请求标识符附加到源链的requestsOutHash
中,并从本地的responsesIn
映射中读取预先填充的响应。从调用者的角度来看,此交互似乎是同步的,因为定序*scopedCall*
已经在目标链上模拟了调用并预先填充了responsesIn
。scopedCall 这个名称反映了这样一个事实:整个跨链交互(请求、执行和响应)都在单个原子执行范围内解析,从而给人一种跨链本地可组合的感觉。
在目标链上,sequencer 执行handleScopedCall()
,它将相同的请求标识符混合到其requestsInHash
中,执行ScopedRequest
,并用结果更新responsesOutHash
。然后,此输出被转发回来并插入到源链的responsesIn
中。
在结算时,桥接器会验证两条链是否遵循了请求和响应的正确顺序。具体来说,它会检查:
- 源链的
requestsOutHash
与目标链的requestsInHash
匹配 - 源链的
responsesInHash
与目标链的responsesOutHash
匹配
如果任何调用被跳过、重新排序或篡改,这些滚动哈希将不会匹配,并且汇总将无法解决,从而确保原子性。
L2↔L2同步可组合性
SCOPE 在 L2↔L2 交互中运行得尤其干净。假设 Rollup A 上的合约需要调用 Rollup B 上的函数。共享的 sequencer 观察到 A 的scopedCall()
,并立即在 B 上注入一个匹配的handleScopedCall()
在 B 上执行目标函数并获取response
后,sequencer 会在 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 执行,标准状态转换证明将拒绝这种情况。优势
- 并行证明:由于两条链都包含完整的有序交易序列,因此每个 rollup 都独立地并行证明自身的状态转换。唯一的跨链依赖关系是最终结算时进行的滚动哈希等价性检查。
- 无需强制共享排序器:虽然共享排序器可以优化延迟,但 SCOPE 可以与独立排序的 rollup 兼容,只要它们共享一个结算层并相互信任彼此的排序。这使得它与 Optimism 超级链等生态系统直接兼容。
- 无需实时证明:与同步的 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 的核心是一个超级建造者,其灵感源自超级交易模型。超级建造者负责模拟整个跨链调用(包括 L1 和 L2 两部分),并确保所有操作在一个 L1 时隙内原子化完成。这需要与 L1 提议者和 L2 排序者紧密协作,理想情况下,超级建造者可以同时充当 L1 提议者和 L2 排序者的角色。
L1 区块空间控制:第一个挑战是只有 L1 提议者才能决定区块中包含哪些内容。如果 L2 排序器模拟了scopedCall()
并假设 L1 状态,但 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。sequencer 通过fillResponsesIn()
预填充交换结果,从而允许提取操作在 rollup 结算之前的同一笔交易中进行。与基于 Merkle 证明的标准提取不同,此处的withdraw()
调用无需许可,因为如果证明失败或滚动哈希值不匹配,整个 bundle(包括提取操作)都受到原子回滚的保护,从而防止任何未经授权的资金流失。
模拟
在模拟和排序时,超级构建者必须确保每个 rollup 都遵循跨链调用的部分顺序:
- 在源链上,所有
scopedCalls
必须按照明确定义的相对顺序出现。 - 在目标链上,所有相应的
handleScopedCalls
必须按照匹配的相对顺序出现。 - 所有其他内容(普通交易)都可以交错,只要它们不会以改变滚动哈希的方式改变
ScopedRequest
有效负载或计算的response
。
具体来说:
- 在对
scopedCall(req)
进行排序之后,超级构建器不得包含改变req
源链交易,例如,更改to
、value
、gasLimit
或data
。 - 在模拟
handleScopedCall(req)
并捕获response
之后,超级构建器不得包含会改变response
目标链交易。
为了模拟,超级建筑师将:
- 拦截源链的
scopedCall()
- 本地更新源链的
requestsOutHash
- 将
handleScopedCall()
插入目标链并执行 - 将
response
传回源链的执行
通过重复此过程,超级构建器将确定调用fillResponsesIn()
所需的所有response
值。
附录
在这里,我们分析了流行的汇总堆栈,以确定需要进行哪些更改来支持 SCOPE,假设目标是同步 L1↔L2 可组合性。
核心范围要求 (L1↔L2)
- 共享排序器:公共排序器必须协调所有参与汇总的跨链交易流。
- L1 客户端修改:L1→L2
scopedCall()
在模拟期间和执行期间需要不同的行为。 - L2 客户端修改:除了核心状态转换功能之外,rollups 必须证明所有跨链请求和响应的滚动哈希等价性,以确保参与链之间的可验证一致性。
- L1 桥修改:桥必须跟踪滚动哈希(
requestsInHash
、requestsOutHash
、responsesInHash
、responsesOutHash
)以通过结算期间的等价性检查来强制执行原子性。 - 实时证明:Rollup 必须在同一个 L1 slot 内生成并提交有效性证明(即可争议的 rollup),才能参与原子
scopedCall()
执行。 - L1 提议者协调:要么序列器必须是 L1 提议者,要么必须获得状态锁以保证
scopedCall()
的 L1 部分完全按照模拟执行。 - 返回值支持:提前模拟跨链调用,并跟踪 Layer1 和 Layer2 上的
responsesOutHash
、responsesInHash
和resultsIn
映射。这使得调用合约能够像本地调用一样同步使用返回值。
案例研究:Ethrex
Ethrex 堆栈支持通过特权交易进行基于推送的 L1→L2 跨链调用(无返回值)以及基于拉取的 L2→L1 消息传递。
L1→L2 今天
调用
CommonBridge.sendToL2()
会发送任意的SendValues
有效载荷,其中包含目标 L2 函数调用的编码。有效载荷的哈希值会附加到pendingTxHashes
数组中,并触发PrivilegedTxSent
事件。定序器监听 PrivilegedTxSent 事件,并将
PrivilegedL2Transaction
注入到 L2 内存池中。该交易执行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 CommonBridge
发起的通用 L1 函数调用尚不支持,但可以很容易地添加。
SCOPE 兼容性要求:
- 用
scopedCall()
sendToL2()
,引入responsesOutHash
、responsesInHash
和resultsIn
映射,以允许调用合约立即使用跨链函数调用的返回值。 - 序列器不应等待 L1 上发出的
PrivilegedTxSent
事件,而应在 L1 区块确认之前预先注入PrivilegedL2Transactions
,从而实现跨链同步执行。 - 将基于拉取的 L2→L1 消息传递替换为基于推送的
scopedCall()
该函数由 L2 发起,并通过 L1 上的handleScopedCall()
进行处理。这使得任意的 L1 合约调用都可以在同一个 L1 slot 内执行。
案例研究:OP Stack
OP Stack 支持双向、基于推送的跨链调用,无返回值。
SCOPE 还可以应用于 SuperChain,以实现 L2↔L2 同步可组合性,而无需共享排序器或实时证明,只要参与的 rollup 共享结算层并相互信任彼此的排序。
L1→L2 今天
- 调用 L1
CrossDomainMessenger.sendMessage()
允许将任意不透明字节作为调用数据发送到目标 L2 合约。 -
OptimismPortal.depositTransaction()
将任何 ETH 托管在锁箱中并发出TransactionDeposited
事件。 - 序列器监听
TransactionDeposited
事件并在 L2 上注入一个调用CrossDomainMessenger.relayMessage()
交易,该交易使用先前发送的数据执行 L2 函数调用。
派生管道确保所有TransactionDeposited
事件都与正在中继的消息相对应。否则,排序器就犯了欺诈行为。
L2→L1 今天
- 调用 L2
CrossDomainMessenger.sendMessage()
允许将任意不透明字节作为调用数据发送到目标 L1 合约。 -
L2ToL1MessagePasser.initiateWithdrawal()
在sentMessages
映射中记录消息哈希并发出MessagePassed
事件。 - 序列器提出一个 L2
output
,其中包含一个提交到sentMessages
映射状态的output_root
。 - 用户通过使用 merkle 证明调用 L1
OptimismPortal.proveWithdrawalTransaction()
来证明消息包含。 - 在欺诈证明窗口过去后,调用 L1
OptimismPortal.finalizeWithdrawalTransaction()
执行 L1 函数调用。
如果使用有效性证明,这种基于拉取的方法可以简化为两个交易:一个在 L2 上发起消息,另一个在 L1 上证明并完成执行。
SCOPE 兼容性要求:
- 支持能够实时证明的有效性证明,以允许汇总在单个 L1 插槽内结算。
- 通过允许
op-node
设置 [SequencerConfDepth](https://github.com/ethereum-optimism/optimism/blob/f70219a759e1da31e864c0ccdc2c757689aba3ec/op-node/rollup/driver/config.go#L12) = 0
并在 L1 上发出TransactionDeposited
事件之前启用CrossDomainMessenger.relayMessage()
来支持同步 L1→L2scopedCall()
。 - 通过用单个 L2 发起的
scopedCall()
替换当前的多步骤 L2→L1 消息传递过程,实现同步 L2→L1 调用。
案例研究:Taiko
Taiko 堆栈支持双向、基于拉取的跨链调用,且无返回值。
L1→L2
- 调用
Bridge.sendMessage()
可以发送任意Message
,该消息编码为对目标 L2 合约的函数调用。该Message
会被哈希处理(创建一个信号),并存储在 L1SignalService
合约中。 - 用户使用标准
eth_getProof
RPC 生成存储证明,证明他们的信号存在于 L1 上。 - 每个 Taiko 区块都以一个锚交易开始,该交易注入当前的 Layer-1 世界状态根和一系列新信号。这些信号随后被写入 Layer-2
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 稳定下来并且其世界状态最终确定,就可以使用原始
Message
和存储证明来调用 L1Bridge.processMessage()
以表明该信号存在于 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 服务,使用基于推送或拉取的传递。
L1→L2 今天
- 调用
L1MessageService.sendMessage()
会将不透明字节(calldata)发送到目标 L2 合约。消息哈希会被合并到滚动哈希中,并在 L1 上触发MessageSent
事件。 - 协调器服务会监控这些事件,等待两个 L1 epoch 的最终结果,然后在 L2 上调用
L2MessageManager.anchorL1L2MessageHashes()
。这会将消息哈希写入 L2,并发出RollingHashUpdated
事件,确保两条链上都能重新计算出相同的滚动哈希。 - 最后,
L2MessageServiceV1.claimMessage()
执行 L2 函数调用,设置一个标志以防止重放,并发出MessageClaimed
。这可以由用户手动调用,也可以由Postman 服务自动调用(如果用户预付了 L1 费用)。 - 在结算时,将 L2 发出的最终
RollingHashUpdated
与 L1 上的滚动哈希进行核对,以验证跨链的消息一致性。
此机制相当于在SCOPE模型下验证L1的requestsOutHash
与L2的requestsInHash
是否匹配。
L2→L1 今天
- 调用
L2MessageServiceV1.sendMessage()
会发出一个包含消息哈希值的MessageSent
事件,并将任意不透明字节编码为目标 L1 合约的调用数据。 - 在结算期间,证明者构建所有发出的消息哈希值的 Merkle 树,并将 Merkle 根提交给 L1。
- 为了完成消息,用户(或Postman 服务)使用原始消息及其 Merkle 证明调用
L1MessageService.claimMessageWithProof()
,从而执行 L1 函数调用。
SCOPE 兼容性要求:
- 移除
anchorL1L2MessageHashes()
步骤,改为在claimMessage()
期间逐步更新 L2 滚动哈希,这等效于handleScopedCall()
。sequencer 必须强制以正确的顺序声明消息,以保证 L1 和 L2 之间的滚动哈希一致性。 - 将
claimMessageWithProof()
中使用的 Merkle 证明验证替换为基于推送的模型,该模型使用由 L2 发起的scopedCall()
进行验证。L2 跟踪一个requestsOutHash
,而 L1 在调用handleScopedCall()
时计算匹配的requestsInHash
。 -
LineaRollup.submitBlobs()
和LineaService.finalizeBlocks()
必须捆绑在一起并以原子方式执行。这确保了 L2 实时结算。
基于预先确认和范围
SCOPE 并不严格要求预先确认。我们可以设想一个基于“完全无政府状态”的 Rollup,任何人都可以充当超级构建者,提出包含跨链调用的有效 bundle,而无需提供预先确认。这种模式有效,但预先确认自然可以通过让用户更早地确定交易结果来提升用户体验。
执行预置通常被认为是黄金标准,但 SCOPE 的操作顺序使其变得复杂:由于需要填充responsesIn
映射, scopedCall()
的后状态在模拟和执行之间有所不同。为了提高效率,超级构建器可能会在所有模拟运行之后插入一个fillResponsesIn()
调用,而不是在每个scopedCall()
之前。在rollups上,可以通过在每个scopedCall()
之前立即放置fillResponsesIn()
来解决这个问题,这是 gas 可行的。在L1上,另一种方法是让超级构建器预先确认滚动前和滚动后哈希及其相应的请求和响应数据。这种方法让用户能够可靠地了解跨链请求的结果,并且在发生故障时更容易证明,并且更容易从超级构建器发出(因为它不需要中间状态根)。
CUSTARD等先前的研究探索了在超级构建者槽位之前进行“超级交易”预置的可行性。当ScopedRequests
依赖于无法使用 CUSTARD 描述的技术锁定的任意状态数据时(例如, ScopedRequest
可能包含在scopedCall()
) 执行时确定的价格数据,而该价格数据很可能与其模拟和预确认的槽位不同),这种早期的scopedCall()
发出是否安全,需要更多研究来确定。
SCOPE 与 AggLayer
SCOPE 和 AggLayer 都旨在实现无需信任的跨链消息传递,但它们从不同的角度解决问题。AggLayer 是一个功能完善的互操作性协议,拥有自己的结算规则;而 SCOPE,尽管名称中带有“协议”一词,但主要是一个会计框架,可以叠加在 AggLayer、超级链或弹性网络等现有系统上。
两个系统都秉承相同的悲观证明理念:链独立地证明自身的状态转换,并且只有在通过密码等效性检查后才能结算。在 AggLayer 中,这体现在“本地出口树”中,其根节点的作用类似于 SCOPE 的requestsOutHash
。区别在于,AggLayer 和类似协议本身并不支持跨链调用的同步返回值。消息发出后,在同一次执行中不会有任何返回。
SCOPE 扩展了此模型,它还通过跟踪入站响应(例如通过responsesInHash
或假设的“本地入口树”)来扩展。这使得区块链能够同步使用返回数据,就像它们属于一个统一的环境一样。其结果是从简单的消息传递转变为真正的共享执行范围,同时保留了相同的结算时安全保障。