当前位置:首页区块链科普 | 一种以太坊 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 平台开发接口,无需平台维护者的支持和参与。

解决方案概览

我们提议的方案的核心是一种标准化的工具,让客户端能够从一个外部系统 —— 一个**服务 —— 处检索数据;以及一种标准化的方法,来验证返回的数据是正确的。

相应地,这里有两个主要的组成部分:第一个,是一个放在以太坊 Layer-1 上的智能合约,向客户端提供一个发现**并验证**响应正确性的工具;第二个,是一个**服务,理解如何与给定的 L2 系统交互、以及如何为合约的用途而格式化数据。

在该模下,获得数据的过程分三步:

科普 | 一种以太坊 Layer-2 的通用桥

  1. 向合约发出查询数据的请求。合约并不直接返回所需的结果,而是返回两个值:一个 ** URL,以及一个 calldata 前缀
  2. 向该**发送一个 HTTP POST 请求,请求与第一步中相同的数据。**返回一个不透明值(opaque value),reSOLver(解析器) calldata。验证该 解析器 calldata 的起始位就是第一步中得到的 calldata 前缀。
  3. 查询合约,或者与之互动,提供第二步中得到的 解析器 calldata ,合约验证该数据的有效性,如果有效的话,返回 结果 或者执行交易。

因为负责理解如何与 L2 交互的是**服务,所以这样一种简单的协议就可以让客户端从链下获得数据,并且不需要让客户端理解任何与 L2 相关的东西。为了使用这套系统,每一个应用都需要为自己意向交互的 L2 实现并部署一个**服务和一个验证合约。在大部分使用,这些**可以是非常通用的,降低了在不同应用间重复劳动的负担。

重要的是,这三个步骤的流程在调用者处可以完全抽象掉;一个理解这个协议的库就可以让整个流程看起来跟一个常规的 web3 合约调用一般无二,也就是说,不仅应用不需要知道自己在跟哪个 L2 交互,它们甚至完全不知道自己是在跟 L2 交互!

**返回错误或者误导性结果的能力受到协议本身的限制。合约所实现的验证逻辑保证了任何无效的结果都会在第三步被发现,同时,合约在第一步中返回的前缀,在第二步中得到验证;这些都放置了**用对某一次查询有效的答案来回应另一次查询。

工作案例

我们可以用一个预加载了一组余额的 ERC20 token 合约,以及一个本身是简单静态默克尔树的 “Layer-2” 来演示这条系统在实践中是如何运作的:

contract PreloadedToken is ERC20 {
mapping(address=gt;uint) preload;
function claimableBalance(address addr) external view returns(uint) {
return preload[addr];
}
function claim(address addr) external {
if(preload[addr] gt; 0) {
_mint(addr, preload[addr]);
preload[addr] = 0;
}
}
}

这个简单的解决方案有一个显而易见的问题:部署者必须在部署时将所有余额填充到 preload 映射中,这是一种非常昂贵的操作。他们会更愿意把数据存储在链下,然后让能够证明自己拥有余额的用户来提取自己的数额。用默克尔树很容易就能实现这一点:

contract PreloadedToken is ERC20 {
bytes32 merkleRoot;
mapping(address=gt;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 (验证证明功能)的实现)

这个方法非常有效,合约的作者也不再需要花费大量的 ETH 来预加载所有余额,一个默克尔根就足够了,而且调用者想申领余额的时候,可以自己支付证明 token 所有权的开销。

不过,现在调用者必须理解生成证明的具体流程,并且知道要到哪儿去获取余额清单来生成自己账户的证明。如果我们可以把第一个方案的接口(方便),与第二个方案的效率结合起来,那就完美了。这就是我们的方案。

首先,我们加入了匹配初始 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(quot;claimquot;, data);
const balance = balances[args.addr];
const proof = merkleTree.getProof(addr, balance);
return merkleInterface.encodeFunctionData(quot;claimWithProofquot;, [args.addr, balance, proof]);

(再一次,为了简洁,我们假设已经有了包括 getProof 函数在内的合适实现)

这里的**服务只需要为客户端所发送的 claim 调用解码函数调用数据,组装一个证明 —— 或者,在一个实际的 L2 方案中,参考 L2 来组装出一个证明 —— 然后将结果编码放在对 claimWithProof 的调用中,返回给客户端。

**,客户端验证返回的 calldata 是否以合约所断言的前缀开始,如果是,则使用交易发送 calldata 给合约。

claimableBalance 的实现也差不多,只是客户端使用 calldata 来调用合约,将返回值作为调用的最终结果。

安全考虑和信任模

假设客户端信任了原始合约 —— 我们的意思是,期望该合约会以特定的方式运行,而这可以通过检查它发布的源代码来验证 —— 那么这个系统就不会引入任何新的信任假设。虽然**的响应是一个外部流程,但其不良行为的范围仅限于拒绝服务。

首先,如果我们信任合约,我们同样也会信任它来制定一个** URL 来回应我们的查询请求。其次,我们也可以信任它来实现充分的验证、保证**的响应是准确的,既可以通过在第一步中指定 calldata 前缀、也可以通过在**一步中验证**的响应来保证。

因此,一个尝试用不正确的值来响应的** —— 无论是提交了不正确的数据,还是不正确的证明 —— 都会被执行验证步骤的合约发现。一个尝试正确响应、但使用非用户所发出请求的对应结果来响应的**,会在用户的 calldata 前缀检查中发现。客户端可以通过检查合约的行为来保证这些 —— 或者依赖于某些人对合约的检查 —— 都可以在开始交互前实现。

**可以完全拒绝响应,也就是拒绝服务,而且这种情况确实可能因为**恶意或者故障而发生。因为这一点,我们提议,任意最终规范,都应该让用户易于 fork 服务,并提供自己的**;就像现在用户能够 fork dApp 的前端一样。

ENS 应用

ENS 使用这套系统也会相对直接一些。解析器可以实现本文所述的协议,用于解析任何的数据字段,然后每一个希望支持 ENS 数据的存储和检索的 L2 都可以部署新的解析器实现和相应的**。希望使用 L2 的用户只需存储自己的记录到合适的 L2 中,并在以太坊上发送一笔一次性的交易来指定相关的解析器地址,来使用自己的域名。

为了让这个方案更通用,ENS 也应该改进,以支持某种形式的通配符解析(wildcard reSOLution),使得搜索域名失败时会向解析器咨询该域名的父域名 —— 如果 “foo.example.ETH” 不存在,那客户端就会在解析器内搜索 “example.ETH”这一功能使得其它系统可以存储 ENS 的整个子树,而不仅仅是单个域名的记录。

未解决的问题

  • 虽然某些应用(比如 ENS )可以从合约指定** URL 所创造的额外间接层中获益,另一些应用,比如上文所示的 token 合约,**把这些编码为该合约 ABI 的一部分来,使得用户更容易 fork。一个**的解决方案**能支持两种选择,且不会强加不必要的负担。
  • 目前,客户端无法分别出一个返回无效 calldata(例如提供一个无效的证明)的**和一个无论如何都会回滚的调用。需要作出一些规定来区分这两种情况 —— 举个例子,如果证明数据的验证不通过的话,要求合约使用一个特定的回滚理由。
  • 它需要一个比 “以太坊 L2 通用桥” 更吸引人的名字。

自己试试

我文章所有 demo 的源代码都可以在这里找到。

(完)

随着走向成熟的以太坊 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 使用的元件必定不同;第二,客户端仍然无法实际获得数据。实用的方法必须满足下列条件:

我们提议的方案的核心是一种标准化的工具,让客户端能够从一个外部系统 —— 一个**服务 —— 处检索数据;以及一种标准化的方法,来验证返回的数据是正确的。相应地,这里有两个主要的组成部分:第一个,是一个放在以太坊 Layer-1 上的智能合约,向客户端提供一个发现**并验证**响应正确性的工具;第二个,是一个**服务,理解如何与给定的 L2 系统交互、以及如何为合约的用途而格式化数据。在该模下,获得数据的过程分三步:

因为负责理解如何与 L2 交互的是**服务,所以这样一种简单的协议就可以让客户端从链下获得数据,并且不需要让客户端理解任何与 L2 相关的东西。为了使用这套系统,每一个应用都需要为自己意向交互的 L2 实现并部署一个**服务和一个验证合约。在大部分使用,这些**可以是非常通用的,降低了在不同应用间重复劳动的负担。重要的是,这三个步骤的流程在调用者处可以完全抽象掉;一个理解这个协议的库就可以让整个流程看起来跟一个常规的 web3 合约调用一般无二,也就是说,不仅应用不需要知道自己在跟哪个 L2 交互,它们甚至完全不知道自己是在跟 L2 交互!**返回错误或者误导性结果的能力受到协议本身的限制。合约所实现的验证逻辑保证了任何无效的结果都会在第三步被发现,同时,合约在第一步中返回的前缀,在第二步中得到验证;这些都放置了**用对某一次查询有效的答案来回应另一次查询。

我们可以用一个预加载了一组余额的 ERC20 token 合约,以及一个本身是简单静态默克尔树的 “Layer-2” 来演示这条系统在实践中是如何运作的:这个简单的解决方案有一个显而易见的问题:部署者必须在部署时将所有余额填充到 preload 映射中,这是一种非常昂贵的操作。他们会更愿意把数据存储在链下,然后让能够证明自己拥有余额的用户来提取自己的数额。用默克尔树很容易就能实现这一点:(为了简化,我们省略掉了 verifyProof (验证证明功能)的实现)这个方法非常有效,合约的作者也不再需要花费大量的 ETH 来预加载所有余额,一个默克尔根就足够了,而且调用者想申领余额的时候,可以自己支付证明 token 所有权的开销。不过,现在调用者必须理解生成证明的具体流程,并且知道要到哪儿去获取余额清单来生成自己账户的证明。如果我们可以把第一个方案的接口(方便),与第二个方案的效率结合起来,那就完美了。这就是我们的方案。首先,我们加入了匹配初始 claim 的签名和 claimbleBalance 的方法:这些函数的调用者可以得到两个值:第一个值是一个后续 callback 的前缀;第二个值是一个**服务的 URL。该前缀保证了两件事: callback 会用相关的 proof 函数来响应,并且其第一个参数会是所提供的地址。这防止了**用给另一个地址的数据来响应请求。接下来,我们需要实现一个**服务来,可以满足客户端的查询请求。以 claim1 为例,很直接就能实现:(再一次,为了简洁,我们假设已经有了包括 getProof 函数在内的合适实现)这里的**服务只需要为客户端所发送的 claim 调用解码函数调用数据,组装一个证明 —— 或者,在一个实际的 L2 方案中,参考 L2 来组装出一个证明 —— 然后将结果编码放在对 claimWithProof 的调用中,返回给客户端。**,客户端验证返回的 calldata 是否以合约所断言的前缀开始,如果是,则使用交易发送 calldata 给合约。claimableBalance 的实现也差不多,只是客户端使用 calldata 来调用合约,将返回值作为调用的最终结果。

假设客户端信任了原始合约 —— 我们的意思是,期望该合约会以特定的方式运行,而这可以通过检查它发布的源代码来验证 —— 那么这个系统就不会引入任何新的信任假设。虽然**的响应是一个外部流程,但其不良行为的范围仅限于拒绝服务。首先,如果我们信任合约,我们同样也会信任它来制定一个** URL 来回应我们的查询请求。其次,我们也可以信任它来实现充分的验证、保证**的响应是准确的,既可以通过在第一步中指定 calldata 前缀、也可以通过在**一步中验证**的响应来保证。因此,一个尝试用不正确的值来响应的** —— 无论是提交了不正确的数据,还是不正确的证明 —— 都会被执行验证步骤的合约发现。一个尝试正确响应、但使用非用户所发出请求的对应结果来响应的**,会在用户的 calldata 前缀检查中发现。客户端可以通过检查合约的行为来保证这些 —— 或者依赖于某些人对合约的检查 —— 都可以在开始交互前实现。**可以完全拒绝响应,也就是拒绝服务,而且这种情况确实可能因为**恶意或者故障而发生。因为这一点,我们提议,任意最终规范,都应该让用户易于 fork 服务,并提供自己的**;就像现在用户能够 fork dApp 的前端一样。

ENS 使用这套系统也会相对直接一些。解析器可以实现本文所述的协议,用于解析任何的数据字段,然后每一个希望支持 ENS 数据的存储和检索的 L2 都可以部署新的解析器实现和相应的**。希望使用 L2 的用户只需存储自己的记录到合适的 L2 中,并在以太坊上发送一笔一次性的交易来指定相关的解析器地址,来使用自己的域名。为了让这个方案更通用,ENS 也应该改进,以支持某种形式的通配符解析(wildcard reSOLution),使得搜索域名失败时会向解析器咨询该域名的父域名 —— 如果 “foo.example.ETH” 不存在,那客户端就会在解析器内搜索 “example.ETH”这一功能使得其它系统可以存储 ENS 的整个子树,而不仅仅是单个域名的记录。

我文章所有 demo 的源代码都可以在这里找到。(完)

温馨提示:

文章标题:科普 | 一种以太坊 Layer-2 的通用桥

文章链接:https://www.btchangqing.cn/136502.html

更新时间:2020年11月04日

本站大部分内容均收集于网络,若内容若侵犯到您的权益,请联系我们,我们将第一时间处理。

区块链行情

近期山寨市场的生存空间变小了吗?

2020-11-4 22:37:37

区块链行情

美国大选是否会影响比特币的走势?

2020-11-4 22:45:36

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索