区块链上的应用应该如何构建出来?随着 L1、L2 、智能合约、模块化基础设施的成熟,答案逐渐清晰起来。
撰文:Jolestar,Rooch 联合创始人
本文尝试从演化角度讨论 Rollup Layer2 的发展以及演进,主要解答以下几个问题:
区块链的「三难问题」一直是困扰业界的一个难题,如果我们认为 Layer1 区块链应该首先保证「去中心化」和「安全」,那将「扩展性」方案从 Layer1 迁移出来就是自然的选择了,于是有了 Layer2。那新的难题就是如何通过 Layer1 来保证 Layer2 的安全。
最初有一种想法是定时将 Layer2 应用的状态树根写到 Layer1,这样可以通过状态证明来校验应用的状态,类似于交易所储备金证明。但这种方式第三方无法通过公开的方式验证两次状态转换是正确的。
为了更深入的探讨这个问题,我们抽象一下,任何程序的状态都可以通过一个状态转换公式表达:
σt+1 ≡ Υ(σt, T)
这个公式来自于 Ethereum 黄皮书,但它可以代表任意的程序。在这里 Υ 代表程序,σ 代表状态。状态 σt+1 由程序 Y 通过状态 σt 和交易 T 计算得出。交易 T 代表程序的输入。任意时候,如果 σt 是确定的,程序 Y 是确定的,T 是确定的,那 σt+1 就是确定的。
所以要提供公开的可验证性,关键是 Y 要公开可用,历史上所有的 T 要公开可用并且顺序确定,中间的状态可通过 Y 和 T 重新计算得到。而程序的公开可用我们可以通过开源来实现,关键是 T 公开可用如何保证,这就引入了数据可用性(DA)的概念。
数据可用性需要有个公开的不可篡改的账本来记录应用的交易。自然想到,区块链账本就是这样一个系统,于是将 Layer2 的交易写回 Layer1,保证数据可用性,这也就是 Rollup 名称的来源。
所以 Layer2 系统中需要有个角色收集用户的交易,进行排序并写入到 DA,这个角色叫 定序器(Sequencer)。这里的交易序列叫 Canonical Transaction Chain。
保证了数据的可用性,每个人都可以通过自己运行程序执行交易来得到最终的状态。但这里并没有达成共识,因为每个人不确定自己得到的结果是否和其他人的结果一致,毕竟软件或者硬件故障也可能导致数据不一致。所以需要另外一个角色将交易执行后的状态树根定时公布出来,供大家校验自己的状态,这个角色叫 提案者(Proposer)。这里每次提交的状态也构成了一个状态序列,和交易序列对应,叫 State Commitment Chain。
到这里,我们达到了应用的可验证性。如果某个人运行的结果和 Proposer 提交的状态不一致,并确定不是自己的问题,那就是 Proposer 作弊或者出错了,那怎么让别人也知道呢?这就需要引入仲裁者(Arbitrator)的角色。仲裁者需要是一个可信第三方,链上合约正好可以承担这个角色。
仲裁有两个方案:
我们还需要实现 Layer1 和 Layer2 之间的资产互通。于是构建一个 Layer1 到 Layer2 之间的桥,通过状态证明来进行资产结算。而 Layer2 在 Layer1 的状态根由 Layer1 的仲裁合约保证,我们可以认为这个桥的安全也受仲裁合约保证。
至此,我们得到了一个由 Layer1 保证安全,并且可以和 Layer1 进行资产互通的 Rollup Layer2 方案。
当然,Rollup 方案也做了一些妥协:
这里总结一下 Layer2 的安全以及交易的最终确定性:
根据前面的分析,Rollup 解决方案中,链上的多个合约承担不同的职能,代表不同的模块。那自然想到,能否将模块拆分到多个链,从而获得更高的扩展性。这就是模块化区块链以及模块化 Rollup 的思路。
模块化在这里有两层含义:
我们可以认为有三个主要的模块层:
将 DA 职能迁移出来,用一个独立的解决方案,获得的首要好处是 Layer2 的交易 Gas 费至少降低一个数量级。
从安全方面来看,即便是 DA 链的去中心化弱于 Ethereum,但 DA 层对安全的保证主要是挑战期内的交易,过了挑战期后,DA 主要是为了方便其他节点同步数据,对安全并没有保障作用,所以去中心化的要求可以降低一个层次。
DA 专用链可以提供更高的存储带宽和更低的存储成本,并且针对多个应用共享 DA 进行专门的设计。这也是当前如 Celestia、Polygon Avail 这样的 DA 链的立足点。
将 DA 层拆分出去后,我们得到了下图的架构:
上图中由 DA 来承担保存 Canonical Transaction Chain 的职责,而给 Layer1 留了一个 L1ToL2 Transaction Queue 来实现 Layer1 和 Layer2 之间的消息通信,用户也可以直接写交易给这个 Queue,确保 Layer2 的 Permissionless,Sequencer 无法审核用户或者交易。
但这里会引入一个新的难题,如果 Sequencer 写入 DA 的交易序列和 Proposer 执行的交易序列不一致,仲裁合约如何判定?一种方案是 DA 链和仲裁链之间有一个跨链桥,实现在仲裁合约中校验 DA 链提供的数据证明。但这种方案依赖 DA 和其他链之间的跨链桥的实现,DA 的方案选型会受限制。另外一种方案是引入排序证明。
我们可以认为 Sequencer 实际上也属于 DA 方案的一部分,它相当于一个 App-Specific 的 DA,主要基于以下理由:
如果要求 Sequencer 给每个交易生成一个 Sequence Proof,则可以解决两个问题:
Sequence Proof 具有以下特性:
Sequence Proof 的工作原理:
用户或者执行节点提交交易给 Sequencer,Sequencer 将 Sequence Proof 返回给用户,同时同步给其他节点。如果 Sequencer 在提交给 DA 之前丢弃或者篡改了交易的顺序,用户或者其他节点可以提交 Sequence Proof 给仲裁合约,从而惩罚 Sequencer。仲裁合约需要从 State Commitment Chain 合约中读取交易累加器的根,从而校验 Sequence Proof。
分场景探讨一下:
我们通过引入 Sequence Proof 让 Layer2 的协议变得更安全。
将 Sequencer 划分给 DA,只负责交易的验证和排序,带来的另外一个好处是容易实现交易的流水线以及并行执行。
验证交易时,需要验证签名和是否有足够的 Gas 费,而 Gas 费的校验需要依赖状态。如果我们为了保证验证交易不会被执行交易阻塞,允许 Sequencer 验证交易依赖的状态和最新状态之间有一定的延迟(秒级),会导致 Gas 校验会不太准确,有被 DDoS 攻击的风险。
但我们认为 Sequencer 属于 DA 是一个正确的方向,所以值得我们进一步研究。比如可以将交易费中 DA 部分拆分出来,通过 UTXO(Sui Move Object) 表达,则可以降低 Gas 费检测成本。
Sequencer 给交易排序然后输出成交易流水线,然后同步给 Proposer 以及其他节点。每个节点可以根据自己的服务器情况来选择并行方案。每个节点需要保证:只并行没有因果关系的交易,有因果关系的交易必须按 Sequencer 的顺序执行,那最终的结果就是一致的。
Proposer 需要定时提交状态树的根,以及累加器的根到链上的 State Commitment Chain 合约中。
于是我们得到了一个低 Gas 费,高 TPS,并且更安全的模块化 Layer2: Rooch。
Optimistic Rollup 方案中,链上的仲裁合约如何判定链下的交易执行出错,一直是一个难题。最初的想法是 Layer1 上重新执行一遍 Layer2 的交易,但这种方案的难题是 Layer1 的合约要模拟 Layer2 的交易执行,成本很高,也会限制 Layer2 交易的复杂度。
最后业界摸索出一种交互式证明的方案。因为任何复杂的交易,最终会转换成机器指令执行,如果我们找到产生分歧的指令,则只需要在链上模拟执行指令即可。
还用上面那个状态转换公式:
σt+1 ≡ Υ(σt, T)
这里 Υ 代表指令,T 代表指令输入,σ 代表指令所依赖的内存状态。如果在执行过程中,给每个 σ 都生成一个状态证明。 控辩双方可以通过交互,发现双方的分歧点 m,将 m-1 的状态 σ 以及指令 m 提交到链上仲裁合约模拟执行,仲裁合约执行后就可以给出判定。
所以剩下的问题就是通过什么方式来生成证明,主要有两个方案:
OMO 是一个拥有单步状态证明能力的通用字节码模拟器,为多链执行环境设计。有了 OMO 的支持,可以实现仲裁层的模块化。任意支持图灵完备合约的链,都可以在合约中模拟 OMO 的指令,作为仲裁层。
业界一直在争论 Optimistic Rollup 和 ZK Rollup 孰优孰劣,但我们认为将二者结合起来可以兼得两种方案的优点。
在前面的 Optimistic 方案基础上,我们再引入一个新的角色,ZK Prover。它批量给 Proposer 提交的交易状态生成有效证明,并提交给仲裁合约。仲裁合约验证后,就可以判定该交易在 Layer1 上达到了最终确定性,可以进行 Layer2 到 Layer1 的提款交易的结算。
这种方案的优点:
在 ZK 的方案以及硬件加速成熟之前,我们可以先通过 Optimistic 构建生态,同时模块化方案可以让 ZK 最后无缝接入进来。
如果我们进一步思考模块化的趋势,自然想到,既然 DA 可以迁移到别的链,那结算层是否也可以部署到别的链?
Layer1 和 Layer2 之间的资产结算主要依赖两个组件,一个是 Bridge,一个是 State Commitment Chain,从 Bridge 结算的时候,需要依赖 State Commitment Chain 校验 Layer2 的状态证明。 Bridge 当然可以部署到多个链,但 State Commitment Chain 只能有一个权威的版本,由仲裁合约保证安全。
这个方向还需要深入研究,但有个初步的方案。其他链上的 State Commitment Chain 都是仲裁链(Ethereum)上的镜像。这个镜像并不需要同步全部的 Layer2 State Root 到其他链,而是用户按需通过 Ethereum 的状态证明做映射。
当然,其他链上还需要能校验 Ethereum 上的状态证明,所以需要知道 Ethereum 上的状态根。当前,将 Ethereum 上的状态根同步到其他节点有两个方案:1. 依赖 Oracle。2. 嵌入 Ethereum 轻节点,校验 Ethereum 的区块头。
这样我们就可以得到一个支持多链结算,但安全由 Ethereum 保证的 Layer2 方案。
这种方案和跨链的区别:
通过模块化,开发者可以通过 Rooch 组合出不同的应用。
Rooch 的模块化方案可以适应于不同类型以及阶段的应用。比如开发者可以先通过部署合约在 Rooch Ethereum Layer2 上验证自己的想法,等成长起来后,将应用迁移到独立的基于 Rooch 搭建的 App-Specific Rollup 中。
再或者开发者直接通过 Sovereign Rollup 方式启动应用,因为应用早期对安全性要求不高,也没有和其他链互通资产的需求,先做到可验证。等应用成长起来,有了互通资产的需求,对安全性要求变高,这时候可以启用结算以及仲裁模块从而保证资产的安全。
由前面的分析可以看出,无论哪种组合方式,都依赖 DA。DA 在去中心化应用中扮演的角色类似于 Web2 系统的日志平台,可以用来做审计,支持大数据分析,进行 AI 训练等。未来会有很多应用和服务围绕 DA 建立起来。当前已有 Celestia,Polygoin avail,未来还会有 EigenLayer,Ethereum danksharding 等。
根据前面的分析,我们得出 Sequencer 的角色应该属于 DA 的一部分,如果 DA 层能为应用提供交易校验能力,并且有足够的性能,实际上完全可以由 DA 来承担 Sequencer 的职责,用户直接写交易到 DA。当然能否使用应用的 Token 付 DA 的 Gas 费是另外一个需要解决的问题。
新的应用形态会促使新的编程语言爆发,这在 Web2 时代已经验证。而 Move 会成为构建 Web3 DApp 的最佳语言。除了 Move 本身的语言特性外,还基于以下理由:
我在 17 年底进入区块链领域,当时业界有非常多的团队尝试在区块链领域构建应用。可惜当时基础设施尚不完备,业界尚未摸索出一个可复制的构建应用的模式,大多数应用类项目以失败告终,打击了开发者和投资者。区块链上的应用应该如何构建出来?这个问题一直让我思考了五年。
而现在,随着 Layer1,Layer2 以及智能合约,模块化基础设施的成熟,这个问题的答案也逐渐清晰起来。
希望在即将到来的 Web3 DApp 爆发潮中, Rooch 可以助开发者一臂之力,让应用更快的构建,真正的落地。
【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。