引介 | 一种以太坊 Layer-2 的通用桥
摘要: 在一个存在许多互不兼容的 Layer-2 方案的世界里,如何能以信任最小化的方式从某个系统中检索数据
随着走向成熟的以太坊 Layer-2 解决方案多了起来,ENS 也要能为整个生态系统提供服务,同时让 ENS 用户能够获得 Layer-2 解决方案给他们带来的效率提升。自 Vitalik 的一篇帖子提出了一种可能的方法之后,ENS 团队和广大的 ENS 和 L2 社区也一直在开发一种通用的 “Layer-2 桥”,让包括 ENS 在内的应用,能够以免信任的方式在多个链下信源处检索数据,进而使跨平台的互操作性成为可能。
在 10 月 27 号最新的一次工作会议上,我演示了这个想法的一个初步实现。本文中我会详细讲解这种解决方案。
目标
概要来说,Layer-2 和其它相关系统的工作原理都是减少与以太坊交互的需要,它们将原本需要在链上保存和访问的 状态 移到了别的地方,同时,保证在以太坊区块链上有足够多的信息能验证数据的正确性。举个例子,在 Rollup 这种常见的方案中,(Rollup 的)状态会存储在另外一个系统中,只有 witness 数据例如默克尔根会存储在以太坊区块链上(译者注:作者此处的举例不够完整,witness 还包括用户交易的原始数据)。有了这些 witness 数据和 Layer-2 解决方案的访问权,一个参与者就可以构建出对任意保护在 Layer-2 系统中的数据的有效性证明,并且可以由以太坊来验证。
这个定义比大多数人所认为的 “Layer-2” 要更加广泛 —— 它还包括了其它一些减少链上数据存储的工具,比如使用账户余额默克尔树的空投(airdrop),以及会触发事件但并不在链上存储余额的代币。
对于 ENS 和其它应用来说,关键问题在于,在一个存在许多互不兼容的 Layer-2 方案的世界里,如何能以信任最小化的方式 —— 也就是不引入任何新的信任假设 —— 从某个系统中检索数据,且不需要变成所有 Layer-2 方案的客户端、自己来存储可能有用的数据 。
一个幼稚的方法是,要求所有的系统都使用同样的 witness 数据格式。但这一点是不可能的,两个原因:第一,witness 数据的格式和类型都高度依赖于相关系统的实现细节,ZK Rollup 和 Optimistic Rollup 使用的元件必定不同;第二,客户端仍然无法实际获得数据。
实用的方法必须满足下列条件:
-
客户端不需要为它们可能与之交互的每一个系统提供显式支持。 -
客户端必须能够验证返回的数据是有效的,最好无需引入除相关 L2 方案自带假设以外的信任模型。 -
解决方案不会要求接入的 L2 平台产生结构性的变更。 -
第三方必须能够为 L2 平台开发接口,无需平台维护者的支持和参与。
解决方案概览
-
向合约发出查询数据的请求。合约并不直接返回所需的结果,而是返回两个值:一个 网关 URL,以及一个 calldata 前缀。 -
向该网关发送一个 HTTP POST 请求,请求与第一步中相同的数据。网关返回一个不透明值(opaque value),resolver(解析器) calldata。验证该 解析器 calldata 的起始位就是第一步中得到的 calldata 前缀。 -
查询合约,或者与之互动,提供第二步中得到的 解析器 calldata ,合约验证该数据的有效性,如果有效的话,返回 结果 或者执行交易。
工作案例
contract PreloadedToken is ERC20 {
mapping(address=>uint) preload;
function claimableBalance(address addr) external view returns(uint) {
return preload[addr];
}
function claim(address addr) external {
if(preload[addr] > 0) {
_mint(addr, preload[addr]);
preload[addr] = 0;
}
}
}
这个简单的解决方案有一个显而易见的问题:部署者必须在部署时将所有余额填充到 preload
映射中,这是一种非常昂贵的操作。他们会更愿意把数据存储在链下,然后让能够证明自己拥有余额的用户来提取自己的数额。用默克尔树很容易就能实现这一点:
contract PreloadedToken is ERC20 {
bytes32 merkleRoot;
mapping(address=>bool) claimed;
function claimableBalanceWithProof(address addr, uint balance, bytes proof) external view returns(uint) {
require(verifyProof(keccak256(addr, balance), proof));
if(!claimed[addr]) {
return balance;
}
return 0;
}
function claimWithProof(address addr, uint balance, bytes proof) external {
require(verifyProof(keccak256(addr, balance), proof);
if(claimed[addr]) {
return;
}
_mint(addr, balance);
claimed[addr] = true;
}
}
(为了简化,我们省略掉了 verifyProof
(验证证明功能)的实现)
claim
的签名和 claimbleBalance
的方法:string gateway;
function claimableBalance(address addr) external view returns(bytes prefix, string url) {
return (abi.encodeWithSelector(claimableBalanceWithProof.selector, addr), gateway);
}
function claim(address addr) external view returns(bytes prefix, string url) {
return (abi.encodeWithSelector(claimWithProof.selector, addr), gateway);
这些函数的调用者可以得到两个值:第一个值是一个后续 callback 的前缀;第二个值是一个网关服务的 URL。该前缀保证了两件事:callback 会用相关的 proof 函数来响应,并且其第一个参数会是所提供的地址。这防止了网关用给另一个地址的数据来响应请求。
claim1
为例,很直接就能实现:const args = tokenInterface.decodeFunctionData("claim", data);
const balance = balances[args.addr];
const proof = merkleTree.getProof(addr, balance);
return merkleInterface.encodeFunctionData("claimWithProof", [args.addr, balance, proof]);
(再一次,为了简洁,我们假设已经有了包括 getProof
函数在内的合适实现)
claim
调用解码函数调用数据,组装一个证明 —— 或者,在一个实际的 L2 方案中,参考 L2 来组装出一个证明 —— 然后将结果编码放在对 claimWithProof
的调用中,返回给客户端。claimableBalance
的实现也差不多,只是客户端使用 calldata 来调用合约,将返回值作为调用的最终结果。安全考虑和信任模型
ENS 应用
未解决的问题
-
虽然某些应用(比如 ENS )可以从合约指定网关 URL 所创造的额外间接层中获益,另一些应用,比如上文所示的 token 合约,最好把这些编码为该合约 ABI 的一部分来,使得用户更容易 fork。一个终极的解决方案最好能支持两种选择,且不会强加不必要的负担。 -
目前,客户端无法分别出一个返回无效 calldata(例如提供一个无效的证明)的网关和一个无论如何都会回滚的调用。需要作出一些规定来区分这两种情况 —— 举个例子,如果证明数据的验证不通过的话,要求合约使用一个特定的回滚理由。 -
它需要一个比 “以太坊 L2 通用桥” 更吸引人的名字。
自己试试
(完)
(文内有许多超链接,可点击左下 ”阅读原文“ 从 EthFans 网站上获取)
原文链接:
https://medium.com/the-ethereum-name-service/a-general-purpose-bridge-for-ethereum-layer-2s-e28810ec1d88
作者: Nick Johnson
作者:以太坊爱好者;来自链得得内容开放平台“得得号”,本文仅代表作者观点,不代表链得得官方立场凡“得得号”文章,原创性和内容的真实性由投稿人保证,如果稿件因抄袭、作假等行为导致的法律后果,由投稿人本人负责得得号平台发布文章,如有侵权、违规及其他不当言论内容,请广大读者监督,一经证实,平台会立即下线。如遇文章内容问题,请发送至邮箱:linggeqi@chaindd.com
评论(0)
Oh! no
您是否确认要删除该条评论吗?