学习
核心概念
交易
交易执行流

交易执行流

本文档主要目标是解释 Rooch 中交易的处理流程,让 DApp 开发者以及 Rooch 的开发者深入理解 Rooch 的设计和实现,从而更容易的参与到 DApp 以及 Rooch 的开发中。同时,本文档也试图解答一些常见问题,比如交易的执行顺序,交易的最终确定性等。

用户视角

从用户的调用视角来看,Rooch 的交易执行流程如下:

Rooch transaction flow user perspective

  1. 用户通过 SDK,或者 CLI 将交易发送到 Rooch RPC API。同时,Rooch 也支持 Ethereum RPC API,所以用户也可以通过 MetaMask 等支持 Ethereum RPC 的客户端发送交易给 Rooch。未来,Rooch 会支持更多 L1 的 RPC API。
  2. Rooch 的各种 RPC API 收到交易后,统一发送给 RPC Service 进行处理。
  3. RPC Service 会调用 Executor, Sequencer, Proposer 等模块,对交易进行处理。
  4. Sequencer 以及 Proposer 会定时批量和后面的 L1 交互。

功能视角

从系统内部组件的功能视角来看,Rooch 的交易执行流程如下:

Rooch transaction flow user perspective

  1. RPC Service 收到不同的 API 的交易后,会先将多链交易发送给 Executor 进行 validate_tx
  2. 在 Executor 的 validate_tx 中:
    • 先调用 rooch_framework::address_mapping::resolve 方法,将多链地址转换成 Rooch 地址(Move 地址)。
    • 然后调用 rooch_framework::transaction_validator::validate 方法对交易的 Authenticator 进行验证。Authenticator 代表交易的 sender 对自己身份的证明,通常是一种签名。Authenticator 中包含一个 scheme 字段来标志 Authenticator 的类型。当前支持 ED25519 和 Ethereum 的 ECDSA 两种内置的 Authenticator,未来会支持更多的签名类型,以及允许开发者自定义 Authenticator。这也是 AccountAbstraction 的一部分。另外账户的 sequence_number(相当于 Ethereum 中的 nonce) 也会在 validate 中进行验证。
    • 通过合约对交易验证后,Executor 基于交易构造 TxContext,将多链交易统一转换为 MoveOSTransaction, 并返回。
  3. RPC Service 收到 Executor 的 validate_tx 返回值后进行判断,如果交易未验证通过,则直接给用户返回错误,否则将交易发送给 Sequencer 进行 sequence_tx。因为 validate_tx 是只读方法,不会修改状态,所以这一步返回错误并没有副作用。
  4. Sequencer 将交易添加到 Accumulator 中,获得该交易的 tx_order,构造 TransactionSequenceInfoTransactionSequenceInfo 包含 Sequencer 对该交易的 tx_order 的签名,以及 tx_accumulator_root。Sequencer 会定时批量将交易提交给 DA 。
  5. RPC Service 收到 Sequencer 的 sequence_tx 返回值后,将交易发送给 Executor 进行 execute_tx
  6. 在 Executor 的 execute_tx 中,Executor 直接调用 MoveOS 执行交易。
    • 首先,MoveOS 会执行 rooch_framework::transaction_validator::pre_execute 方法,对交易进行前置处理。前置处理中,当前会自动创建账户以及对多链地址和 Move 地址进行映射。未来,AccountAbstraction 中的 Gas 费用相关的需求也会在前置处理中实现,比如 Gas 交换以及代付。
    • 然后,MoveOS 会调用用户定义的方法,执行交易。
    • 最后,MoveOS 会执行 rooch_framework::transaction_validator::post_execute 方法,进行后置处理。后置处理中,当前会自动更新账户的 sequence_number,以及 Gas 费用的扣取。
    • 执行过程中,pre_execute,execute,post_execute 共享一个 TxContext, 可以通过 TxContext 传递数据。
    • 注意,如果执行过程中,用户定义的方法执行失败,MoveOS 会自动回滚状态,但 pre_executepost_execute 依然会执行,Gas 费用只扣取用户实际执行消耗的部分。
  7. RPC Service 收到 execute_tx 返回的 TransactionExecutionInfo 后,将交易发送给 Proposer 进行 propose_tx。Proposer 将交易打包成区块,定时将区块提交到 L1 的 StateCommitment 合约。注意,这里的区块并不包含交易的原始数据,相当于区块头,它包含 Rooch 的 state_root 以及交易的 tx_accumulator_root
  8. 最后 RPC Service 将 TransactionSequenceInfoTransactionExecutionInfo 返回给用户,代表交易执行成功并确认。

注意:

  1. 当前的这个流程中,并没有包含 Challenger 以及 fraud-proof, zk-proof 的逻辑。这部分内容会在后续的版本中更新。
  2. 整个流程包含了 Executor,Sequencer,Proposer多个组件,但这些组件可能并不在同一个节点中,它们可能是远程 P2P 网络通信。这部分内容也会在后续的版本中更新。
  3. 以上的流程基于当前版本的设计来描述,并且部分逻辑尚未完全实现,未来会持续更新。

交易包含

Rooch 中有两种方式实现交易包含:

  1. 直接请求 Sequencer
  2. 通过 L1 提交

在 Rooch 支持多链结算,任意一条结算链均可实现交易包含

1. 直接请求 Sequencer

用户可以直接请求 Sequencer 对交易进行包含,Sequencer 会将交易添加到 Accumulator 中,并立刻返回交易的全局顺序。 这种包含是可仲裁的(通过仲裁链),但 Sequencer 可以不响应用户请求从而实现交易审查攻击。当用户发现交易无法被包含时,可通过第二种方式提交交易。

2. 通过 L1 提交

用户可以通过 L1 发起 L2 调用,等待 Sequencer 抓取并执行。这也是 L1 向 L2 发送消息的运行机制。 该方式使得 Sequencer 无法隐蔽的实现审查交易,因为任何人都可以很容易发现 L1 上的合约调用与相关事件,以及 L2 上是否包含该交易的事实。 因此,只要我们把这个链下验证过程重现在 L2 上,那么便可以复用 Optimistic Rollups 的欺诈证明实现对作恶 Sequencer 的惩罚。这需要:

  1. L1 信息在 L2 上的审计跟踪:
    • 我们在 Rooch 上用合约实现等同 Optimistic Rollups 安全假设(只需有一个诚实节点中继区块信息)的各 L1 的轻节点,为欺诈证明提供审计跟踪的凭据。
  2. 轻节点的可仲裁时效性承诺。打破“实现交易包含需要 L2 轻节点验证,L2 轻节点验证需要交易包含(包含中继消息)“的循环。

Sequencer 关于时效的承诺:

  1. Sequencer 承诺 L1ToL2 事件产生后将在一定时间内(t1)生成相应的 L2 tx 并完成执行。
  2. Sequencer 承诺 L2 上的 L1 区块信息与 L1 当前时间的最大延迟为 t2。 t2 <= t1/2,以确保 Rooch 上的合约肯定有能力验证已经超时 t1 的 L1 的事件。
  3. Sequencer 承诺 L2 output commitment 提交的最大间隔为 t3。 t3 << t2,以确保 L2 上的 L1 区块信息更新 tx 的执行结果可用于仲裁,同时也意味着该结果可被 verifier 验证和挑战以确保区块信息的正确性。L1 合约会检查每次 output commitment 提交的间隔。

仲裁流程

  1. 用户可以通过 L1 发起 L2 调用,产生 L1ToL2 事件,时间为 t0。
  2. 在任意结算链上的任意用户发现 Sequencer 在 t0+t1 时间内未完成交易,任意用户可在链下生成 L1ToL2 事件的存在证明(包含区块头)向仲裁链申请交易包含仲裁。

当仲裁合约收到仲裁请求,首先对用户提供的存在证明进行基本形式验证:

  1. 需满足:当前时间 - 区块时间 > t1,否则仲裁终止。
  2. 通过 merkle 树验证事件存在于区块中,否则仲裁终止。

以上验证完成后,Sequencer 需在 t4 时间内提供存在证明否则被惩罚:

  1. 提供 L1 最新区块头信息,及其对应的 L2 tx 序列证明。仲裁链可通过比较 L2 提交到仲裁链上的 Output 验证结果,避免 Sequencer 提供伪证。 (仲裁链首先比较 L1 区块头和 L2 tx 的内容对应关系,随后验证累加树证明)
  2. 若最新区块头的区块时间不满足 t2,惩罚 Sequencer,挑战终止。用户后续可每隔 t1 时间持续性发起同一交易的包含仲裁,Sequencer 的不作为将面临逐步加重的惩罚1
  3. 提供该 L1ToL2 事件所在区块高度的区块头,及其对应的 L2 tx 序列证明 (仲裁链验证方法同 1)。
  4. 若 3 中提供的区块头与用户证据不一致,仲裁终止。
  5. 至此,L2 和用户均承认该 L1ToL2 事件已经产生,接下来验证 L2 在规定时间内包含了该事件。
  6. 提供该事件包含的 L2 tx 的累加器证明。
  7. 仲裁合约验证累加器证明及其是否满足 t1 承诺(序列的根作为 L2 output 的一部份会携带提交时 L2 记录的最新仲裁链的区块头信息一并提交,故可判断时间),若验证失败,惩罚 Sequencer。
  • t1:24 小时
  • t2:8 小时
  • t4: 2 小时

FAQ

交易的执行顺序是怎么确定的?

交易的执行顺序是由 Sequencer 确定的。Sequencer 会将交易添加到 Accumulator 中,并立刻获取到交易的全局顺序,Sequencer 需要对该交易的顺序进行签名,相当于给用户一个承诺,承诺自己不会修改顺序或者丢弃交易。而 Accumulator 可以提供交易的顺序证明,如果最后 Sequencer 提交到 DA 的交易顺序和之前的承诺不一致,那么用户可以证明 Sequencer 的行为是恶意的,从对 Sequencer 进行惩罚。

交易的执行结果是怎么确定的?

Rooch 中没有交易池的概念,交易的执行结果是即时确定的,客户端提交交易后会立刻得到结果,不需要等待异步共识确认。因为如果交易的执行顺序是确定的,程序是确定的,那么交易的执行结果也是确定的。这里面包含一些安全假设,因为 L2 的安全假设构建在反事实因果推理 (opens in a new tab)基础之上,如果作弊行为会得到惩罚,理性的选择是不作弊。

  1. 交易的执行顺序是由 Sequencer 保证的,用户通过前面提到的反激励机制来约束 Sequencer 的行为,但如果 Sequencer 无视这种惩罚,也可能导致交易的执行结果的不确定。这是一种基于经济博弈的安全保证方法。
  2. 如果 Executor 窜改交易的执行结果,也可能导致用户得到错误的结果。这种情况下,用户可以自己运行 Executor 来验证结果(未来会提供无状态的轻节点),或者通过多个 Executor 进行结果确认。这个风险和用户信任某个 L1 RPC 节点的风险类似。
  3. Proposer 定时在 L1 上公布 Rooch 的 state_root,Executor 可以根据这个 state_root 来校验自己的状态。如果发现不一致,要么是 Executor 有问题,要么是 Proposer 有问题,双方可以通过 L1 的仲裁合约对状态进行仲裁,并将自己的状态回滚到正确的 state_root。如果是 Proposer 的错误,Proposer 会得到惩罚。
  4. Rooch 的状态在 L1 上达到最终确定,需要等待一个挑战公示周期。未来,我们会结合 zk-proof 来压缩这个周期。
  5. 软件的 Bug 本身也会带来状态的不确定性,这个需要时间来验证和修复,极端情况下,可能需要依赖社会共识来解决。

总结一下:

在 Rooch 中,应用和开发者可以认为交易是即时确定的,它的安全依赖于一套反激励机制,并且已经在区块链中广泛使用。当然,反激励机制的网络需要逐渐构建,用户和开发者都需要参与进来。

Footnotes

  1. 最终 Sequencer 将被 L1 合约拒绝更新状态。该惩罚机制的目的是防止 Sequencer 作恶,但也会导致 Sequencer 无法提供服务。 这需要 Sequencer 轮转机制配合,恶意 Sequencer 节点被强制替换为新的节点。 完善的 Sequencer 节点的替换依赖 Rooch 生态的蓬勃发展,因此这将在 Rooch 的下一阶段中被引入。