--- 摄于 2017 年 9 月 藏川线前段
考虑了挺久,最后还是决定先将 BFT 模块拿出来讲解,executor 和 chain 在 cita 的流程中都是在最后处理数据和落盘的模块,先讲解数据来源可能会更有利于理解整体思路。我看代码的顺序(chain->jsonrpc->network->executor->auth->bft)与文章的顺序不太一致,导致在看完第三个模块之后才开始有一个整体的概念。这系列文章的目的是帮助读者轻松(不大量阅读源码)的了解 cita 的工作流程,对 cita 有一个完整的整体认知。
分布式应用绕不过去的话题就是共识算法,甚至可以说,共识算法就是分布式的核心。经典的拜占庭问题、CAP 理论是分布式方面的阶段性总结。我在分布式领域还是个菜鸟, 对整个学术理论的理解几乎都来自于一些经典书籍,对共识算法的了解也仅限于 Raft、TenderMint 这几个比较有名的算法,并且也仅仅是了解流程,代码实现方面几乎没有什么实践性的操作。这一篇文章,仅仅是针对 cita-bft 代码的一些浅层次讲解,更深入的部分,实力有限,理论知识匮乏,就不献丑了。
cita-bft 总体来说是 TenderMint 算法的变种实现,两轮投票后达成共识、投票开始的时候锁住该高度的 block,一轮没有达成共识则进入下一轮(round)投票,直到完成该高度的共识,保证整个链不会分叉。下面这个结构体就是整个 bft 的状态机状态:
pub enum Step {
Propose,
ProposeWait,
Prevote,
PrevoteWait,
PrecommitAuth,
Precommit,
PrecommitWait,
Commit,
CommitWait,
}
bft 的代码实现,就是一个大的状态机,以及一个定时器,状态由链上消息通信以及计时器 timeout 机制推动。
代码:https://github.com/cryptape/cita-bft/blob/develop/src/core/votetime.rs#L35
pub struct WaitTimer {
timer_seter: Receiver<TimeoutInfo>,
timer_notify: Sender<TimeoutInfo>,
thpool: ThreadPool,
}
计时器,读写各一个 channel,加上一个线程池,它的任务很简单,接收状态机的计时命令,线程池计时完成后,将超时状态回复给 bft 状态机,bft 根据接收到的投票信息以及当前状态,决定是进入下一个状态还是回滚等操作。
代码:https://github.com/cryptape/cita-bft/blob/develop/src/main.rs#L185
thread::spawn(move || loop {
let (key, body) = rx_sub.recv().unwrap();
let tx = mq2main.clone();
let pool = threadpool.clone();
pool.execute(move || {
tx.send((key, body)).unwrap();
});
});
这个部分也很简单,监听消息,立刻转发给 bft 状态机。
代码:https://github.com/cryptape/cita-bft/blob/develop/src/core/cita_bft.rs#L108
pub struct TenderMint {
pub_sender: Sender<PubType>,
pub_recver: Receiver<TransType>,
timer_seter: Sender<TimeoutInfo>,
timer_notity: Receiver<TimeoutInfo>,
params: TendermintParams,
height: usize,
round: usize,
step: Step,
proof: TendermintProof,
pre_hash: Option<H256>,
votes: VoteCollector,
proposals: ProposalCollector,
proposal: Option<H256>,
lock_round: Option<usize>,
locked_vote: Option<VoteSet>,
// lock_round set, locked block means itself,else means proposal's block
locked_block: Option<Block>,
//tx_pool: Pool,
wal_log: Wal,
send_filter: HashMap<Address, (usize, Step, Instant)>,
last_commit_round: Option<usize>,
htime: Instant,
auth_manage: AuthorityManage,
consensus_power: bool,
unverified_msg: HashMap<(usize, usize), Message>,
// VecDeque might work, Almost always it is better to use Vec or VecDeque instead of LinkedList
block_txs: VecDeque<(usize, BlockTxs)>,
block_proof: Option<(usize, BlockWithProof)>,
// when snaphsot restore, clear wal data
is_snapshot: bool,
}
这个结构体非常多 field,共识期间需要维护一堆状态,例如,当前节点是否有投票权利 consensus_power
,接到投票后,需要先序列化在本地 wal_log
,整个链的共识节点列表,即公钥管理 auth_manage
,当前投票的 block locked_block
,接到的prev_hash 验证完毕,until_block 验证完毕,交易信息未确认块 unverified_msg
,链上各个节点的状态 send_filter
,上一个高度的共识结果 block_proof
,以及每一次进入投票阶段后,每一轮投票收到的票的 collector。
代码:https://github.com/cryptape/cita-bft/blob/develop/src/core/cita_bft.rs#L1365
消息来源分为两大类:
Step::ProposeWait
状态。Step::PrevoteWait
和 Step::PrecommitWait
状态。Step::Commit
更改为 Step::CommitWait
; 如果高度等于上一次共识高度 - 1,则说明落盘出现了异常,bft 重新将上一次的共识结果发送到 MQ(如果还保留着的话)。同时根据 Richstatus 中的共识列表、prev_hash 等重要信息更新自身的信息。Step::Precommit
, 检查投票信息,如果投票信息已经大于 2/3 了,那么随即转入 Step::PrecommitWait
状态;如果验证失败,清除待验证交易信息,并广播,投空票,转入 Step::Precommit
状态,检查投票信息,如果投票信息已经大于 2/3 了,那么随即转入 Step::PrecommitWait
状态。代码:https://github.com/cryptape/cita-bft/blob/develop/src/core/cita_bft.rs#L1261
一般在状态机进入 *Wait
状态的时候,就会发一个计时消息给计时器,根据状态的不同有些是即刻返回超时消息,有些是等待一定时间后返回超时消息。
总体思路就是,在等待其他节点投票结果的过程中,给一个时间限制,在到达时间限定范围后,检查投票是否已经达到 2/3 ,即共识已经达成可以转入下一个状态,如果共识完成,则发送共识结果给 chain,如果没有,则或重发消息,或整体重新开始一轮投票,或当前 proposal 作废,重新开始。
分布式应用的算法有两大类,拜占庭容错和非拜占庭容错,都挺复杂的,学术理论研究和工程实现也各不相同,我对这一块的了解相当匮乏,书也没看几本,论文就更不用说了,惭愧。
不管怎么说,目前 cita-bft 的代码实现讲解还是在磕磕绊绊中完成了,下一篇应该可以进入 executor 了,虽然本系列文章并没有打算深入讲 evm 虚拟机执行交易的过程,但是可以根据下一篇的讲解自行研究嘛。
请登录后评论
评论区
加载更多