ULTRA TX 是一种实现可编程 L1 区块的方法,它通过 L1 meta tx bundler 和Based Rollup 的区块构建器,创建一个包含单个强大 L1 交易的 ULTRA TX,从而实现 L1 和 L2 之间的高效组合与聚合,实现 L1 和 L2 之间的无缝交互和数据共享,并为跨链互操作提供支持。
ULTRA TX 是一种实现可编程 L1 区块的方式,它解锁了远超在 L1 区块中使用标准 L1 交易的能力。你可以说 ULTRA TX 对于区块来说,就像 account abstraction 对于 EOA 来说一样。
本文将重点关注这对 L2 以及 L1 和 L2 之间的互操作性意味着什么。其他可能的用例将不在此处探讨。
当你将 L1 meta tx bundler 和 (based) rollup 区块构建器结合起来时,你就有了一个我们称之为 master builder 的实体。该 master builder 将构建仅包含单个极其强大且庞大的 L1 交易的 L1 区块。这个单独的交易此后将被称为 ULTRA TX。
这种设置使得为 L2 和 L1 进行组合和聚合变得高效且直接。
为了方便 L1 组合,ULTRA TX 应该位于 L1 区块的顶部,以便直接获得最新的 L1 状态。
这种方法不需要对 L1 进行任何更改。
由于这些原因,我相信我们正在走向一个几乎所有事情都将通过单个 L1 交易完成的未来。 ULTRA TX 可以逐步采用,区块的其余部分可以用传统方式构建。
结合实时证明(在某种合理的范围内),这可以实现理想的未来,即以太坊真正感觉像一条单一的链,其中 L1 和所有 L2 可以相互调用,并且每个 L2 都可以(但不需要)在每个 L1 区块中结算,通常不会损失效率。
展望未来,Gwyneth 经常会被提及,以便更具体地说明事情的实际运作方式。这仅仅是因为我最熟悉它。
block.gaslimit - gasleft() - intrinsic_gas_cost < 21000
。这使得即使在今天强制执行 L1 上的这一要求也没有问题。当区块不需要与 L1 同步组合时,可以删除区块顶部的要求。当仅在链上检查依赖的 L1 状态以确定是否为最新值时,也可以删除此要求(然后可以使用回滚保护将其提交给 L1 构建者)。这确实会对效率产生影响,并且可以轻松在链上读取的 L1 状态也存在限制。
使用 EOA 交易做好 L1 → L2 非常困难。你可以在 L1 上执行的操作非常有限:
可以通过在外部调用后面放置额外的功能来扩展 L1 功能(可能类似于本机 rollups 的可扩展方式)。对于 Gwyneth 而言,此调用是到 L2 的跨链调用。在构建和证明区块时,会创建该区块,就好像此额外功能也可在 L1 上使用一样。这些调用的输出会收集起来,并作为 ULTRA TX 的一部分发送到链上:
此额外数据由 master builder 生成和提供,而不是由用户生成和提供。用户无需签署任何其他数据或验证昂贵的证明。用户可以使用扩展功能以与用户与本机功能交互完全相同的方式与智能合约交互。
在其智能合约中使用扩展功能的开发人员也不必知道幕后实际发生的事情。
请注意,这种精确方法仅有效,因为 Gwyneth 可以“模拟” L1 交易的执行以将所有内容粘合在一起。在证明器中执行 L1 交易的方式与在提议 ULTRA TX 时在 L1 上执行的方式相同。这对于确保使用正确的输入来生成输出非常重要。
我将再次使用 Gwyneth 的同步可组合性方法作为示例(你可以在 此处 快速阅读相关内容,也可以在 此处、此处 和 此处 阅读)。简而言之,在 L2 上添加了一个额外的预编译,允许为外部调用切换链。 Gwyneth 还可以模拟所有 L1 交易,然后在之后将状态更新应用回 L1 。
我在这里要做的假设是,每个 L1 帐户都是一个智能合约帐户,并且所有 L2 都是基于的(多么好的假设!)。
现在可以轻松地完成构建区块,如下所示:
可以根据需要重复构建过程任意次数以生成任意数量的区块。这对于支持以快于 L1 区块时间包含的交易的执行预确认非常重要。并行化区块构建也很重要(请参阅下文)。
最后,生成整个过程的单个证明(请注意,这可能包含子证明,请参阅下面的并行化部分)。
ULTRA TX 最终在链上提议。交易的所有输入都用作证明的输入,并且证明会得到验证。如果证明无效,则整个交易都会回滚。
请注意,构建者必须支持才能正确包含交易的任何其他要求都可以作为交易元数据的一部分。这样,构建者可以轻松查看他们是否能够包含该交易,而无需执行该交易。可以在链上强制执行这些规则。例如,对于跨链交易, meta tx 将包含允许访问的链的列表。如果此列表不完整,则允许该交易回滚,构建者将获得交易费用。
上述简化流程是严格按顺序进行的,以便所有链可以以最简单的方式自由且同步地相互交互。如果一组链不需要彼此同步,也可以并行地为它们构建区块。可以以几乎相同的效率提交多个区块。这允许打破顺序瓶颈并实现更高的吞吐量。
即使,例如,一个链具有 L1 状态依赖性,也可以并行地构建区块。只有该区块中使用的状态子集才是重要的,以便该区块有效的实际最新值。
在这些区块之上可能有一个额外的层跟踪全局状态,而每个区块仅直接依赖于此子状态。每个区块都单独证明,然后在之后与附加状态检查聚合在一起。聚合证明将跟踪跨区块的全局最新状态,并将检查区块中使用的本地状态是否与当前的最新全局状态匹配。构建者只需要确保在并行构建区块时这些假设成立。
关于如何将此(以及更多内容)用于所有 rollups (不仅仅是 Gwyneth rollups )的泛化将在第 2 部分中介绍。此框架将被称为 GLUE 。之前的草图已在 此处 完成。它将包含在合理深度上的链下和链上必要接口,以使所有 L1 扩展程序都可以使用所提出的设计。
一些代码使事情更具体和有趣。为了简洁起见,省略了一些细节。
ULTRA TX 在链上的样子:
// 语言:自动检测
function proposeBlock(BlockMetadata[] calldata blocks) external payable {
for (uint i = 0; i < blocks.length; i++) {
_proposeBlock(blocks[i]);
}
_prove(blocks);
}
function _proposeBlock(BlockMetadata calldata _block) private {
for (uint i = 0; i < _block.l1Block.transactions.length; i++) {
Transaction calldata _tx = _block.l1Block.transactions[i];
for (uint j = 0; j < _tx.calls.length; j++) {
Call calldata call = _tx.calls[j];
// 在 ExtensionOracle 中设置返回数据
if (call.returnData.length > 0) {
(bool success, bytes memory result) = address(extensionOracle).call(abi.encode(call.returnData));
require(success == true, "call to extension oracle failed"); // 调用扩展 oracle 失败
}
// L1 account abstraction 调用
_tx.addr.call{value: call.value}(call.data);
}
// 如果有必要,应用 L1 状态差异
if (_tx.slots.length > 0) {
GwynethContract(_tx.addr).applyStateDelta(_tx.slots);
}
}
}
对于想要利用扩展功能的开发人员来说,它是这样的:
// 语言:scss
using EVM for address;
function xTransfer(uint256 fromChain, uint256 toChain, address to, uint256 value) public returns (uint256) {
return on(fromChain)._xTransfer(msg.sender, toChain, to, value);
}
function ChainAddress(uint256 chainId, xERC20 contractAddr) internal view returns (xERC20) {
return xERC20(address(contractAddr).onChain(chainId));
}
function on(uint256 chainId) internal view returns (xERC20) {
return ChainAddress(chainId, this);
}
如何将扩展程序公开给开发人员:
// 语言:C#
library EVM {
function xCallOptions(uint chainID) public view returns (bool) {
// 调用自定义预编译
bytes memory input = abi.encodePacked(version, chainID);
(bool success, bytes memory result) = xCallOptionsAddress.staticcall(input);
return success && bytes4(result) == xCallOptionsMagic;
}
function onChain(address addr, uint chainID) internal view returns (address) {
bool xCallOptionsAvailable = xCallOptions(chainID, false);
if (xCallOptionsAvailable) {
return addr;
} else {
return extensionOracle;
}
}
}
Extension Oracle 的样子:
// 语言:kotlin
contract ExtensionOracle {
uint private transient returndataCounter;
ReturnData[] private transient returndata;
fallback() external payable {
_returnData();
}
receive() external payable {
_returnData();
}
function _returnData() internal {
if (msg.sender == gwyneth) {
returndata = abi.decode(msg.data, (GwynethData.ReturnData[]));
} else {
require(returndataCounter < returndata.length, "invalid call pattern"); // 无效的调用模式
ReturnData memory returnData = returndata[returndataCounter++];
bytes memory data = returnData.data;
if (returnData.isRevert) {
assembly {
revert(add(data, 32), mload(data))
}
} else {
assembly {
return(add(data, 32), mload(data))
}
}
}
}
}
- 原文链接: ethresear.ch/t/ultra-tx-...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!