Conflux 共识层的设计与实现
Conflux 的共识层处理从同步层接收到的所有区块,根据 Conflux GHAST 共识算法产生区块的完整顺序,并调用底层的交易执行引擎以按确定的顺序运行交易。 它提供了必要的信息,以协助 区块生成器 准备新区块的骨架。 它还通知 交易池(transaction pool) 已处理好的交易,以便交易池可以做出更好的交易选择决策。
本文档旨在为想要了解 Conflux 共识层(位于目录core/src/consensus中)的 Rust 实现的读者提供高级概述。 对于更多的实现细节,请查看代码中的内联注释。 对于 Conflux 共识算法的更多信息,请参阅 Conflux 协议规范和 Conflux 论文(https://arxiv.org/abs/1805.03870)。
设计目标
共识层有以下设计目标。
-
在后台按照一致的共识算法处理新的区块
-
我们希望最小化共识图中每个块的内存使用。 即使有检查点机制,在正常情况下图中会包含 300K-500K 个块,在面对存活性攻击时可能会超过 1M 个块。 这可能会给内存带来压力。
-
我们想要快速处理每个区块。 因为全节点/归档节点在从零开始同步网络时必须处理从_初始创世区块_开始的之后每一个区块,因此快速处理区块对于缩短所需时间是非常重要的。
-
面对潜在攻击时具有稳健性。 恶意攻击者可能会在TreeGraph的任意位置生成恶意区块。
结构与组成部分。
共识图(ConsensusGraph)
ConsensusGraph
(core/src/consensus/mod.rs)是共识层的主要结构体。 同步层通过一个存储所有区块元数据信息的 BlockDataManager
来构建 ConsensusGraph
。
ConsensusGraph::on_new_block()
是将新区块发送给 ConsensusGraph
结构体进行处理的关键函数。 它还提供了一组公共函数,用于查询区块/交易的状态。 这应该是其他组件与之交互的主要接口。
共识图核心(ConsensusGraphInner)
ConsensusGraphInner
(core/src/consensus/consensus_inner/mod.rs)是 ConsensusGraph
的内部结构。 ConsensusGraph::on_new_block()
在函数开始时会获取内部结构的写入锁。 其余的查询函数只会获取读锁。
ConsensusGraphInner
的内部结构相当复杂。
一般来说,它维护两种类型的信息。 第一种信息是整个TreeGraph的状态,即当前的_pivot chain_、timer chain、_difficulty_等等。 第二种信息是每个区块的状态(即每个区块的ConsensusGraphNode
结构)。
每个区块对应一个 ConsensusGraphNode
结构,用于存储其信息。
当第一次进入 ConsensusGraphInner
时,它将被插入到 ConsensusGraphInner::arena : Slab<ConsensusGraphNode>
中。 在Slab中的索引将成为 ConsensusGraphInner
中区块的arena索引。 我们在内部使用arena索引来表示一个区块,而不是使用H256
,因为它更加经济高效。 在讨论算法机制和实现时,我们将回顾 ConsensusGraphInner
和 ConsensusGraphNode
中的字段。
ConsensusNewBlockHandler
ConsensusNewBlockHandler
(core/src/consensus/consensus_inner/consensus_new_block_handler.rs) contains a
set of routines for processing a new block. 从理论上讲,这段代码可以成为 ConsensusGraphInner
的一部分,因为它主要操作内部结构。
然而,这些程序都是 on_new_block()
的 子程序,而且consensus_inner/mod.rs已经非常复杂了。 因此,我们决定将它们放入一个单独的文件中。
ConsensusExecutor
ConsensusExecutor
(core/src/consensus/consensus_inner/consensus_executor.rs)是独立交易执行线程的接口结构体。
ConsensusExecutor::enqueue_epoch() 允许其他线程异步地向执行线程发送一个执行任务,以执行给定 pivot chain 区块的纪元。 Once the
computation finishes, the resulting state root will be stored into
BlockDataManager
. 如有需要,其它线程可以调用
ConsensusExecutor::wait_for_result()
以等待执行一个纪元. In the current implementation, ConsensusExecutor
also contains the
routines for the calculation for block rewards, including
get_reward_execution_info()
and its subroutines.
ConfirmationMeter
ConfirmationMeter
(core/src/consensus/consensus_inner/confirmation_meter.rs)
conservatively calculates the confirmation risk of each pivot chain block. Its
result will be useful for the storage layer to determine when it is safe to
discard old snapshots. 如果我们决定提供关于区块确认的RPC查询,它还可以用于提供这样的RPC服务。
AnticoneCache and PastsetCache
AnticoneCache
(core/src/consensus/anticone_cache.rs) 和 PastsetCache
(core/src/consensus/pastset_cache.rs) 是两个结构体,它们为 ConsensusGraphInner
中的数据结构实现了定制化的缓存。 In the implementation of
the inner struct, we need to calculate and store the anticone set and the past
set of each block. However, it is not possible to store all of these sets in
memory. 因此,我们实现了缓存样式的数据结构来存储最近插入/访问的区块集合。 If an anticone/past set is not found in the
cache, we will recalculate the set in the current inner struct implementation.
重要算法机制
Conflux 共识层中有几个重要的算法机制。 在这里,我们将从实现的角度来讨论它们。 See XXX for the algorithmic reasoning behind them.
主链和全序
Conflux 共识算法的基本思想是首先让所有人都同意一个主链(pivot chain)。 然后,从主链开始,通过拓扑排序来扩展总排序,覆盖所有的区块。 只要主链不发生改变/重组,区块的总排序以及派生的交易顺序将保持不变。
与比特币/以太坊相比,Conflux的共识机制有两个关键的不同之处:
-
与仅仅将枢轴链纳入总排序不同,Conflux中几乎每个区块都会进入总排序。
-
The transaction validity and the block validity are independent. 例如,如果一个交易已经被包含在区块中或由于余额不足无法执行,那么该交易就是无效的。 这些无效的交易将在执行过程中成为空操作(noop) 。 然而,与比特币和以太坊不同,包含这种无效交易的区块并不会变为无效。
In ConsensusGraphInner
, the arena index of the current pivot chain blocks are
stored in order in the pivot_chain[]
vector. 为了维护它,我们按照 GHAST 规则计算新插入区块与当前最佳区块之间的最近公共祖先 (LCA)。 If the fork corresponding to the newly inserted
block for the LCA ended up to be heavier, we will update the pivot_chain[]
from
the forked point.
Timer Chain
Blocks whose PoW quality is timer_chain_difficulty_ratio
times higher than the target
difficulty are timer blocks. The is_timer
field of the block will be set to
True. 然后共识算法找到最长的计时器块链(更准确地说,是累积难度最大的链),类似于比特币共识算法寻找最长链的方式。 最长计时器链的竞技场索引将存储到 timer_chain[]
。
计时器链的原理是提供一种粗粒度的时间测量,很难被恶意攻击者影响 因为计时器块
罕见并缓慢生成(如果timer_chain_difficulty_ratio
是适当的
高), 恶意攻击者不能阻止计时器链的增长,除非
它拥有大多数计算能力。 因此,在一个块的先前设置中出现了多少计时器链块,是关于该块的最新可能生成时间的良好指示。 We compute this value for each block and
store it in timer_chain_height
field of the block.