🤝The CO2 Consensus Engine

Here you will find an overview of the CO2 consensus engine. It details the technical differentiations and improvements made from the standard POA consensus engine, Clique.

So, why “CO2”?

You might be wondering why we chose this name for our consensus engine. The name comes directly from the participants in block generation. The C is a reference to Sequencing (the Sequencer), and the O stands for Operation (which is composed of an Executor and an Evaluator). Since there are two modes of operation, we added the “2.”

The name is completely organic and has nothing to do with the fact that we’re called Soda Labs we promise 😉

The CO2 engine was not built entirely from scratch. It is based on the concepts of the POA (Proof of Authority) consensus engine Clique. However, almost as much was removed from Clique as was added. The signing mechanism was completely redesigned. While the system remains turn based, there is now only one valid signer for each turn. This means there is no concept of out of turn signing in CO2, which also means we gain instant finality at the cost of liveness. If a signer fails to produce a block, the network must wait until that signer participates again.

We also removed Clique’s mechanism for adding and removing signers, which eliminates the concept of epochs. Another major change is the block sealing process, which now requires certain nodes to synchronize before a block can be signed. In addition, we modified how headers are validated and even changed the structure of both the header and the block itself.

As described in earlier sections, the CO2 engine introduces concepts that Clique did not have, most notably the concept of roles. In CO2 there are three types of nodes: Sequencing, Operation, and Gateway. Gateway nodes never generate or sign blocks, but the other two roles do. Let’s first look at how Executors (part of the Operation role) generate, validate, and sign blocks.

Pseudo block validation: For an Executor to validate the pseudo block and accept it as the new chain head, it must perform two tasks. First, it validates that the block is signed by the Sequencer and that the header follows the normal EVM protocol. Second, it validates that the roots recorded in the header match the expected roots after the state changes required by the block are applied. To perform this state transition, Executors execute the block while replacing the result of private operations with a static placeholder. This ensures the resulting state matches the state root in the block header.

Block generation: After validation, Executors parse the transactions from the accepted pseudo block and begin evaluation. When a private opcode is encountered, execution is delegated to the MPC Evaluator paired with that Executor. The Evaluators work together to calculate the encrypted result, which is returned to the Executors. Executors update the state with this result and record it in the Transcript.

Sealing process: Executors seal the canonical block at the same height as the pseudo block. Both Executors must sign the block, and the signatures are ordered lexicographically by their keys in the header. Because both signatures are required, Executors must wait for each other, and the blocks must be identical. Signatures are exchanged between Executors and can also be passed through other nodes in the network. Whenever an Executor receives a signature, it validates it against the seal hash it used to sign. Once signed, the block is propagated to all nodes (including Gateway nodes) and validated. As described earlier, private opcode state transitions are handled through the Transcript, making the resulting state verifiable.

Now let’s go over the way the Sequencer generates, validates, and signs blocks.

Pseudo block validation: To validate a canonical block, a node must perform several checks. First, it confirms that the previous pseudo block header is present and signed by the Sequencer. Second, it verifies that the Transcript is included in the block. Third, it checks that both the hash of the pseudo header and the hash of the Transcript are recorded in the canonical block header. Fourth, it ensures the block is signed by the Executors, which also confirms that the Transcript and pseudo header are signed by the Executors. The validation process also takes into account the order in which the signatures appear in the block. Finally, the node validates the roots by performing state transition. As explained earlier, this state transition is achieved using the Transcript included in the block, which provides the correct result for executing private opcodes that may appear in the transactions.

Block generation: This process is similar to vanilla EVM block generation. Transactions are taken from the transaction pool and ordered by financial benefit for the node operator. These transactions are then evaluated, with one caveat: the results of private opcode evaluation are replaced with placeholders of the same variable size.

Sealing process: Sealing the pseudo block is identical to sealing a normal EVM block. This process has not been changed.

To make things clearer here are the three methods of execution:

Pseudo execution: A 32-byte placeholder is set as the result of any private operation. This is performed during pseudo block creation by the Sequencer and during pseudo block evaluation by the Executors, as well as by the Sequencer during sync.

MPC execution: Actual private execution is performed using the MPC ‘oracle’. This occurs during canonical block creation by the Executors.

Transcript execution: A copy of the private computation result is used as the trusted result instead of performing any private computation. This is performed by the Sequencer and Gateway nodes, as well as by the Executors during sync, when validating canonical blocks.

And here are the methods by which we evaluate each type of block:

Pseudo block validation:

  • Validate that the header roots match the lists found in the body (transaction root, storage root).

  • Validate that state transition yields a world state and storage state that match the state and storage roots in the header.

  • Validate that gas costs have been calculated correctly.

  • Validate that the header is signed by the Sequencer.

Canonical block validation (includes all the steps for a pseudo block, plus the following):

  • Validate that the Transcript included in the block body hashes to the Transcript hash recorded in the extra data field.

  • Validate that the pseudo header included in the block body is signed by the Sequencer, that the block numbers match, and that it hashes (signature included) to the pseudo header hash found in the extra data field.

  • Validate that both Executor signatures are valid.

After a block is validated, it is inserted into the chain. As already mentioned, there can be at most one pseudo block in the entire chain, and it can only exist as the chain head. To enforce this rule, we need to consider a few possible states:

a few possible states:

Head Number (N)

Got pseudo block N

Got canonical block N

Got pseudo block N+1

Got canonical block N+1

Head is pseudo

Reject

Accept

Reject

Reject

Head is canonical

Reject

Reject

Accept*

Accept**

* Except Gateway nodes do not accept pseudo blocks at all. ** During sync all nodes would accept a sequence of canonical blocks.

When we accept a canonical block over a pseudo block at the head of the chain, we are effectively performing a reorg. This process leverages Ethereum’s existing fork-choice rules. As mentioned earlier, pseudo blocks always have a difficulty of 1. Canonical blocks, on the other hand, are assigned a difficulty of 5. This ensures that whenever a canonical block appears at the same height as a pseudo block, the chain will always prefer the canonical block. In practice, this means there is a mini fork at each block in the gcEVM, where canonical blocks are guaranteed to win.

Last updated