The Conflux Consensus Layer Design and Implementation
The Conflux consensus layer processes all incoming blocks received from the synchronization layer, produces the total order of blocks based on the Conflux GHAST consensus algorithm, and invokes the underlying transaction execution engine to run transactions in the determined order. It provides the information necessary to assist block generator to prepare the block skeleton of new blocks. It also notifies the transaction pool about processed transactions so that the pool can make better transaction selection decisions.
This document is to provide a high-level overview for readers who want to understand the rust implementation of the Conflux consensus layer (in directory core/src/consensus). For more implementation details, see inlined comments in the code. For more information about the Conflux consensus algorithm, see Conflux Protocol Specification and Conflux paper (https://arxiv.org/abs/1805.03870).
Design Goals
The consensus layer has the following design goals.
-
Process new blocks in the background following the consensus algorithm consistently.
-
We want to minimize the memory usage of each block in the consensus graph. Even with the checkpoint mechanism, the graph will contain 300K-500K blocks in the normal case and more than 1M blocks when facing liveness attacks. This may stress the memory.
-
We want to process each block fast. Because full/archive nodes have to process every block from the original genesis when they catch up with the network from scratch, fast block process is important to keep the catch up period short.
-
Robust against potential attacks. Malicious attackers may generate bad blocks at arbitrary positions in the TreeGraph.
Structures and Components
ConsensusGraph
ConsensusGraph
(core/src/consensus/mod.rs) is the main struct of the
consensus layer. The synchronization layer constructs ConsensusGraph
with a
BlockDataManager
which stores all block metadata information on disk.
ConsensusGraph::on_new_block()
is the key function to send new blocks to the
ConsensusGraph
struct to process. It also provides a set of public functions
to query the status of blocks/transactions. This should be the main interface
with which other components interact.
ConsensusGraphInner
ConsensusGraphInner
(core/src/consensus/consensus_inner/mod.rs) is the inner
structure of ConsensusGraph
. ConsensusGraph::on_new_block()
acquires the
write lock of the inner struct at the start of the function. The rest are
query functions that only acquire read locks.
The internal structure of ConsensusGraphInner
is fairly complicated.
Generally speaking, it maintains two kinds of information. The first kind of
information is the state of the whole TreeGraph, i.e., the current pivot
chain, timer chain, difficulty, etc.. The second kind of information is
the state of each block (i.e., ConsensusGraphNode
struct for each block).
Each block corresponds to a ConsensusGraphNode
struct for its information.
When it first enters ConsensusGraphInner
, it will be inserted into
ConsensusGraphInner::arena : Slab<ConsensusGraphNode>
. The index in the
slab will become the arena index of the block in ConsensusGraphInner
. We use
the arena index to represent a block internally instead of H256
because it is
much cheaper. We will refer back to the fields in ConsensusGraphInner
and
ConsensusGraphNode
when we talk about algorithm mechanism and their
implementations.
ConsensusNewBlockHandler
ConsensusNewBlockHandler
(core/src/consensus/consensus_inner/consensus_new_block_handler.rs) contains a
set of routines for processing a new block. In theory, this code could be part
of ConsensusGraphInner
because it mostly manipulates the inner struct.
However, these routines are all subroutine of the on_new_block()
and the
consensus_inner/mod.rs is already very complicated. We therefore decided to put
them into a separate file.
ConsensusExecutor
ConsensusExecutor
(core/src/consensus/consensus_inner/consensus_executor.rs)
is the interface struct for the standalone transaction execution thread.
ConsensusExecutor::enqueue_epoch()
allows other threads to send an execution
task to execute the epoch of a given pivot chain block asynchronously. Once the
computation finishes, the resulting state root will be stored into
BlockDataManager
. Other threads can call
ConsensusExecutor::wait_for_result()
to wait for the execution of an epoch if
desired. In the current implementation, ConsensusExecutor
also contains the
routines for the calculation for block rewards, including
get_reward_execution_info()
and its subroutines.