The main objective of this document is to explain the transaction processing flow in Rooch, in order to help DApp developers and Rooch developers to gain a deeper understanding of the design and implementation of Rooch, and thus participate more easily in the development of DApp and Rooch. At the same time, this document also attempts to answer some common questions, such as the execution order of transactions, transaction finality, and so on.
From the user's perspective of calling, the transaction execution process in Rooch is as follows:
- Users send transactions to Rooch RPC API via SDK or CLI. In addition, Rooch also supports Ethereum RPC API, so users can also send transactions to Rooch via clients that support Ethereum RPC, such as MetaMask. In the future, Rooch will support more L1's RPC APIs.
- After receiving the transactions, Rooch's various RPC APIs send them to RPC Service for processing.
- RPC Service will call modules such as Executor, Sequencer, Proposer, etc. to process the transactions.
- The Sequencer and Proposer will interact with the backend L1 periodically in batches.
From the functional perspective of internal components, the transaction execution process in Rooch is as follows:
- After receiving transactions from different APIs, RPC Service first sends the multi-chain transactions to Executor for
- In Executor's
rooch_framework::address_mapping::resolvemethod is called first to convert the multi-chain address to Rooch address (Move address).
- Then call the
rooch_framework::transaction_validator::validatemethod to verify the transaction's
sender's proof of identity, usually a signature. The
schemefield in the
Authenticatoris used to indicate the type of the
Authenticator. Currently, two built-in
ECDSA, are supported, and more signature types will be supported in the future, as well as allowing developers to customize
Authenticator. This is also part of AccountAbstraction. In addition, the
noncein Ethereum) of the account will also be validated in
- After validated by the contract, Executor constructs
TxContextbased on the transaction, uniformly converts the multi-chain transaction into
MoveOSTransaction, and returns it.
- After receiving the return value of
validate_txfrom Executor, RPC Service decide whether the transaction has passed verification. If not, an error is returned directly to the user. Otherwise, the transaction is sent to Sequencer for
validate_txis a read-only method and does not modify the state, returning an error at this step has no side effects.
- Sequencer adds the transaction to the Accumulator, obtains the
tx_orderof the transaction, and constructs
TransactionSequenceInfocontains the signature of the
tx_orderassigned to the transaction by Sequencer and
tx_accumulator_root. Sequencer periodically submits the transactions to DA in batches.
- After RPC Service receives the return value of
sequence_txfrom Sequencer, it sends the transaction to Executor for
- In Executor's
execute_tx, MoveOS executes the transaction directly.
- First, MoveOS executes the
rooch_framework::transaction_validator::pre_executemethod for preprocessing the transaction. In the preprocessing, an account is automatically created and multi-chain addresses are mapped to Move addresses. In the future, Gas-related requirements in AccountAbstraction will also be implemented in the preprocessing, such as Gas exchange and Gas payment agents.
- Then, MoveOS calls the user-defined methods to execute the transaction.
- Finally, MoveOS executes the
rooch_framework::transaction_validator::post_executemethod for post-processing the transaction. In the post-processing, the account's
sequence_numberis updated automatically and Gas fees are deducted.
- During the execution process,
TxContext, which can be used to pass data between them.
- Note that if the user-defined method fails during execution, MoveOS will automatically roll back the state, but
post_executewill still be executed, and Gas fees will only be charged for the actual consumption of the user's execution.
- First, MoveOS executes the
- RPC Service sends the transaction to Proposer for
execute_tx's return value, and Proposer packs the transaction into a block and periodically submits the block to L1's StateCommitment contract. Note that the block here does not contain the original data of the transaction, similar to the block header, it contains Rooch's
state_rootand the transaction's
- Finally, RPC Service returns
TransactionExecutionInfoto the user to represent a successful and confirmed transaction execution.
- The logic of Challenger and fraud-proof, zk-proof is not included in the current flow. This part will be updated in future versions.
- The entire process includes multiple components such as
Proposer, but these components may not be on the same node, and they may communicate remotely through P2P network. This part will also be updated in future versions.
- The above process is described based on the current design and some logic has not yet been fully implemented, and it will continue to be updated in the future.
There are two ways to implement transaction containment in Rooch:
- Direct request to Sequencer
- Submission via L1
*In Rooch, there is multi-chain settlement, so any one of the settlement chains can be used to include a transaction.
You can request Sequencer to include a transaction directly. Sequencer will add the transaction to the Accumulator and immediately return the global order of the transaction. This inclusion is arbitrable (via the arbitration chain), but Sequencer can implement a transaction censorship attack by not responding to the user's request. When the user realizes that the transaction cannot be included, the transaction can be submitted in the second way.
A user can make an L2 call via L1 and wait for Sequencer to fetch it and execute it. This is the mechanism by which L1 sends messages to L2. This approach makes it impossible for Sequencer to covertly implement a review of the transaction, because anyone can easily find out about the contract call and related events on L1, and whether L2 contains the transaction. Therefore, if we reproduce this off-chain verification process on L2, we can reuse Optimistic Rollups' proof of fraud to penalize the evil Sequencer. This requires:
- an audit trail of L1 messages on L2:
- We contractually implement light nodes for each L1 on Rooch that are equivalent to the Optimistic Rollups security assumption (that only one honest node relays block information) to provide audit trail credentials for fraud proofs.
- Arbitrable timeliness promise for light nodes. Breaking the cycle of "L2 light node verification is required to realize transaction inclusion, and L2 light node verification requires transaction inclusion (including relay message)".
Sequencer's commitment on timeliness:
- Sequencer promises that the corresponding L2 tx will be generated and executed within a certain time (t1) after the L1ToL2 event is generated.
- Sequencer promises that the maximum delay between the L1 block information on L2 and the current time on L1 is t2.
t2 <= t1/2, to ensure that the contract on Rooch must have the ability to validate events on L1 that have timed out by t1.
- Sequencer commits to a maximum interval of t3 between L2 output commitment submissions.
t3 << t2to ensure that the result of L1 block message update tx on L2 is available for arbitration, and also means that the result can be verified and challenged by the verifier to ensure that the block message is correct. the L1 contract checks the interval between each output commitment submission. The L1 contract checks the interval between each output commitment submission.
- a user can initiate an L2 call via L1, generating an L1ToL2 event at t0.
- When any user in any settlement chain finds that Sequencer has not completed the transaction within t0+t1, any user can generate the existence proof of L1ToL2 event (including block header) under the chain and apply to the arbitration chain for arbitration of transaction inclusion.
When the arbitration contract receives the arbitration request, it first verifies the existence proof provided by the user in the basic form:
- need to meet: the current time - block time > t1, otherwise the arbitration is terminated.
- verifies that the event exists in the block through merkle tree, otherwise the arbitration is terminated.
After the above verification, Sequencer needs to provide the existence proof within t4 time, or it will be penalized:
- provide L1 latest block header information, and its corresponding L2 tx sequence proof. The Arbitration Chain can prevent Sequencer from providing false evidence by comparing the Output validation results submitted by L2 to the Arbitration Chain. (The Arbitration Chain first compares the content of the L1 Block Header with the L2 tx, and then verifies the Accumulation Tree Proof)
- If the block time of the latest block header does not satisfy t2, Sequencer is penalized and the challenge is terminated. The user may subsequently initiate inclusion arbitration for the same transaction every t1 on a continuous basis, and Sequencer's inaction will face progressively heavier penalties1.
- provide the block header of the height of the block in which the L1ToL2 event occurred, and proof of its corresponding L2 tx sequence (same as 1 for arbitration chain verification).
- If the block header provided in 3 does not match the user's evidence, the arbitration is terminated.
- At this point, both L2 and the user acknowledge that the L1ToL2 event has been generated, and the next step is to verify that L2 contains the event within the specified time.
- Provide the accumulator proof of the L2 tx contained in the event.
- The arbitration contract verifies the accumulator proof and whether it satisfies the t1 promise (the root of the sequence as part of the L2 output will carry the latest block header information of the arbitration chain recorded by L2 at the time of the submission, so it can determine the time), and penalizes the Sequencer if the verification fails.
- t1: 24 hours
- t2: 8 hours
- t4: 2 hours
The execution order of transactions is determined by the Sequencer. The Sequencer adds transactions to the Accumulator, obtains the global order of the transaction immediately and signs the order of the transaction, which is a promise to the user that it will not modify the order or discard the transaction. The Accumulator can provide a proof of the order of the transaction. If the order of the transaction submitted by the Sequencer to DA is inconsistent with the previous commitment, the user can prove that the behavior of the Sequencer is malicious and punish the Sequencer.
There is no transaction pool in Rooch, and the execution result of a transaction is determined immediately. After the client submits the transaction, the result is immediately obtained without waiting for asynchronous consensus confirmation. Because if the execution order of the transaction is determined, the program is determined, and the execution result of the transaction is also determined. There are some security assumptions here, because the security assumptions of L2 are built on the basis of counterfactual (opens in a new tab) causal reasoning. If cheating behavior is will to be punished, the rational choice is not to cheat.
- The execution order of transactions is ensured by the Sequencer, and users use the aforementioned counter-incentive mechanism to restrict the behavior of Sequencer. However, if the Sequencer ignores this punishment, it may also cause uncertainty in the execution result of the transaction. This is a security guarantee method based on economic game theory.
- If Executor modifies the execution result of the transaction, it may also cause the user to obtain incorrect results. In this case, the user can run Executor to verify the result (a stateless light node will be provided in the future) or confirm the results through multiple Executors. This risk is similar to the risk of trusting a certain L1 RPC node.
- Proposer periodically announces Rooch's
state_rooton L1, and Executor can verify its own state based on this
state_root. If an inconsistency is found, it may be a problem with the Executor or with the Proposer. The two parties can arbitrate the state through L1's arbitration contract and roll back their own state to the correct
state_root. If it is Proposer's error, Proposer will be punished.
- Rooch's state reaches final determination on L1 and requires a challenge period. In the future, we will combine zk-proof to compress this period.
- Software bugs themselves can also bring about uncertainty in the state, which takes time to verify and fix. In extreme cases, it may be necessary to rely on social consensus to resolve it.
In Rooch, applications and developers can assume that transactions are immediately determined, and their security depends on a set of counter-incentive mechanisms that have been widely used in blockchain. Of course, the network of counter-incentive mechanisms needs to be gradually constructed, with users and developers participating in it.
Eventually the Sequencer will be refused to update its status by the L1 contract. The purpose of this penalty mechanism is to prevent the Sequencer from being malicious, but it can also cause the Sequencer to be unable to provide service. This requires a Sequencer rotation mechanism, where malicious Sequencer nodes are forcibly replaced with new nodes. Sophisticated replacement of Sequencer nodes relies on the Rooch ecosystem to flourish, so this will be introduced in the next phase of Rooch. ↩