1.前言
上一章我们分析了hotstuff算法中entity和promiss文件,了解了区块、命令等的定义以及如何使用promiss_t类解决异步操作。这一章笔者将走入hotstuff的核心代码层consensus和hotstuff文件,细致分析整个共识的逻辑是如何运行的,篇幅稍微有点长。
2.源码分析
(1)consensus文件
consensus包含四个部分:HotstuffCore(无网络实现的核心协议)、Proposal(提议信息的抽象)、Vote(投票信息抽象)、Finality(决策信息抽象)。
0.相关结构定义
HotstuffCore
/** HotStuff 协议状态机的抽象(无网络实现)。*/
class HotStuffCore {
block_t b0; /**< 创世块 */
/* === 状态变量 === */
/** 数据块包含最高数据块的质量控制 */
std::pair<block_t, quorum_cert_bt> hqc; /**< 最高 QC */
block_t b_lock; /**< 锁定区块 */
block_t b_exec; /**< 最后执行的区块 */
uint32_t vheight; /**< 上次投票的区块高度 */
/* === 辅助变量 === */
privkey_bt priv_key; /**< 签名投票的私钥 */
std::set<block_t> tails; /**< 尾块集合 */
ReplicaConfig config; /**< 副本配置 */
/* === 异步事件队列 === */
/** 存储等待共识承诺的区块 */
std::unordered_map<block_t, promise_t> qc_waiting;
/** 存储等待提议承诺的单一承诺对象 */
promise_t propose_waiting;
/** 存储等待接收提议承诺的单一承诺对象 */
promise_t receive_proposal_waiting;
/** 存储等待高质量共识更新的单一承诺对象 */
promise_t hqc_update_waiting;
/* == 功能开关 == */
/** 总是投反对票,这对某些 PaceMakers 非常有用 */
bool vote_disabled;
block_t get_delivered_blk(const uint256_t &blk_hash);/*根据hash值判断区块是否已经交付*/
void sanity_check_delivered(const block_t &blk);//*检查一个区块是否已经被交付。如果没有交付,则抛出异常*/
void update(const block_t &nblk);
void update_hqc(const block_t &_hqc, const quorum_cert_bt &qc);//更新最高区块及 qc
void on_hqc_update();//辅助update_hqc,通过不断解决旧的hqc_update_waiting和设置新的promiset;
void on_qc_finish(const block_t &blk);
void on_propose_(const Proposal &prop);
void on_receive_proposal_(const Proposal &prop);
protected:
ReplicaID id; /**< 副本的身份 */
public:
BoxObj<EntityStorage> storage;
HotStuffCore(ReplicaID id, privkey_bt &&priv_key);
virtual ~HotStuffCore() {
b0->qc_ref = nullptr;
}
/** 状态机的输入由外部事件触发,应由类用户调用,并有适当的不变式。*/
/** 调用初始化协议,应在所有其他函数之前调用一次。*/
void on_init(uint32_t nfaulty);
/* TODO: 更好的 "交付 "名称?*/
/** 通知状态机一个区块已准备好处理。只有当区块本身被获取、包含 qc 的区块被获取且所有父块都已交付时,才会交付区块。用户应始终确保这一不变性。无效区块将被此函数丢弃。
* @return 如果有效,则返回 true */
bool on_deliver_blk(const block_t &blk);/*交付区块*/
/** 在提议信息送达时调用。信息中提到的区块应该已经送达。*/
void on_receive_proposal(const Proposal &prop);
/** 投票信息送达时调用。信息中提到的区块应该已经送达。*/
void on_receive_vote(const Vote &vote);
/** 调用提交待决定(执行)的新命令。"父级 "必须至少包含一个区块,第一个区块是真正的父级、而其他的则是叔叔/阿姨 */
block_t on_propose(const std::vector<uint256_t> &cmds,
const std::vector<block_t> &parents,
bytearray_t &&extra = bytearray_t());
/** 为抽象类构建具体实例所需的函数 */
/** 触发外部事件的状态机输出。 虚拟函数应由用户实现,以指定事件发生时的行为。*/
protected:
/** 在决定 cmd 时由 HotStuffCore 调用。*/
virtual void do_decide(Finality &&fin) = 0;
virtual void do_consensus(const block_t &blk) = 0;
/** 在广播新提案时由 HotStuffCore 调用。用户应将提议消息发送给除自己之外的所有副本。*/
virtual void do_broadcast_proposal(const Proposal &prop) = 0;
/** 向下一位提议者发送新投票时调用。 用户应将投票信息发送给*好的提议者,以获得良好的时效性,而安全性则始终由 HotStuffCore 保证。*/
virtual void do_vote(ReplicaID last_proposer, const Vote &vote) = 0;
/** 用户为这些多态数据类型插入详细实例。*/
public:
/** 创建证明区块投票的部分证书。*/
virtual part_cert_bt create_part_cert(const PrivKey &priv_key, const uint256_t &blk_hash) = 0;
/** 从序列化形式创建部分证书。*/
virtual part_cert_bt parse_part_cert(DataStream &s) = 0;
/** 创建一个法定人数证书,证明某个区块有 2f+1 票。*/
virtual quorum_cert_bt create_quorum_cert(const uint256_t &blk_hash) = 0;
/** 根据序列化形式创建法定人数证书。*/
virtual quorum_cert_bt parse_quorum_cert(DataStream &s) = 0;
/** 从序列化形式创建命令对象。*/
//virtual command_t parse_cmd(DataStream &s) = 0;
public:
/** 在当前配置中添加一个副本。此操作只能在运行 HotStuffCore 协议之前调用。*/
void add_replica(ReplicaID rid, const PeerId &peer_id, pubkey_bt &&pub_key);
/** 尝试修剪低于上次提交高度的块 - 僵化。*/
void prune(uint32_t staleness);
/** PaceMaker 可以使用这些函数监控核心协议状态过渡 */
/** 当块获得 QC 时,获取已解决的承诺。*/
promise_t async_qc_finish(const block_t &blk);
/** 当一个新区块被提出时,获取已解决的承诺。*/
promise_t async_wait_proposal();
/** 收到新建议时,获取已解决的承诺。*/
promise_t async_wait_receive_proposal();
/** 更新 hqc 时,获取已解决的承诺。*/
promise_t async_hqc_update();
/** 其他有用的函数 */
const block_t &get_genesis() const { return b0; }
const block_t &get_hqc() { return hqc.first; }
const ReplicaConfig &get_config() const { return config; }
ReplicaID get_id() const { return id; }
const std::set<block_t> get_tails() const { return tails; }
operator std::string () const;
void set_vote_disabled(bool f) { vote_disabled = f; }
};
Proposal
定义了提议中需要的提案者id,提议区块以及一些构造函数和序列化函数
/** 提议信息的抽象 */
struct Proposal: public Serializable {
ReplicaID proposer; // 提案的提出者的 ID
/** 正在提议的区块 */
block_t blk; // 提案中包含的区块
/** 核心对象的句柄以允许多态。用户应使用从 HotStuffCore 派生的类的对象指针 */
HotStuffCore *hsc; // HotStuffCore 的句柄,用于多态
// 默认构造函数,初始化 blk 和 hsc 为 nullptr
Proposal(): blk(nullptr), hsc(nullptr) {}
// 带参数的构造函数,用于初始化 proposer, blk 和 hsc
Proposal(ReplicaID proposer,
const block_t &blk,
HotStuffCore *hsc):
proposer(proposer),
blk(blk), hsc(hsc) {}
// 序列化函数,将 proposer 和 blk 写入数据流 s
void serialize(DataStream &s) const override {
s << proposer
<< *blk;
}
// 反序列化函数,从数据流 s 中读取 proposer 和 blk
void unserialize(DataStream &s) override {
assert(hsc != nullptr); // 确保 hsc 不为空
s >> proposer; // 从数据流中读取 proposer
Block _blk; // 临时 Block 对象
_blk.unserialize(s, hsc); // 反序列化 Block 对象
blk = hsc->storage->add_blk(std::move(_blk), hsc->get_config()); // 将反序列化的 Block 对象添加到存储中,并更新 blk
}
// 将 Proposal 对象转换为字符串表示形式
operator std::string () const {
DataStream s;
s << "<proposal "
<< "rid=" << std::to_string(proposer) << " "
<< "blk=" << get_hex10(blk->get_hash()) << ">";
return s;
}
};
Vote
在投票信息中,需要投票者id,投票区块哈希值以及投票用的证书(用于验证投票),和一些投票验证的函数
/** 投票信息的抽象 */
struct Vote: public Serializable {
ReplicaID voter; // 投票者的 ID
/** 正在投票的区块 */
uint256_t blk_hash; // 正在投票的区块的哈希值
/** 投票的有效性证明 */
part_cert_bt cert; // 投票的部分证书
/** 核心对象的句柄以允许多态 */
HotStuffCore *hsc; // HotStuffCore 的句柄,用于多态
// 默认构造函数,初始化 cert 和 hsc 为 nullptr
Vote(): cert(nullptr), hsc(nullptr) {}
// 带参数的构造函数,用于初始化 voter, blk_hash, cert 和 hsc
Vote(ReplicaID voter,
const uint256_t &blk_hash,
part_cert_bt &&cert,
HotStuffCore *hsc):
voter(voter),
blk_hash(blk_hash),
cert(std::move(cert)), hsc(hsc) {}
// 拷贝构造函数,深拷贝 cert
Vote(const Vote &other):
voter(other.voter),
blk_hash(other.blk_hash),
cert(other.cert ? other.cert->clone() : nullptr),
hsc(other.hsc) {}
// 默认移动构造函数
Vote(Vote &&other) = default;
// 序列化函数,将 voter, blk_hash 和 cert 写入数据流 s
void serialize(DataStream &s) const override {
s << voter << blk_hash << *cert;
}
// 反序列化函数,从数据流 s 中读取 voter, blk_hash 和 cert
void unserialize(DataStream &s) override {
assert(hsc != nullptr); // 确保 hsc 不为空
s >> voter >> blk_hash; // 从数据流中读取 voter 和 blk_hash
cert = hsc->parse_part_cert(s); // 解析并读取部分证书
}
// 验证投票的有效性
bool verify() const {
assert(hsc != nullptr); // 确保 hsc 不为空
return cert->verify(hsc->get_config().get_pubkey(voter)) && // 验证投票者的公钥
cert->get_obj_hash() == blk_hash; // 验证证书中的对象哈希是否与区块哈希一致
}
// 异步验证投票的有效性
promise_t verify(VeriPool &vpool) const {
assert(hsc != nullptr); // 确保 hsc 不为空
return cert->verify(hsc->get_config().get_pubkey(voter), vpool).then([this](bool result) {
return result && cert->get_obj_hash() == blk_hash; // 验证证书中的对象哈希是否与区块哈希一致
});
}
// 将 Vote 对象转换为字符串表示形式
operator std::string () const {
DataStream s;
s << "<vote "
<< "rid=" << std::to_string(voter) << " "
<< "blk=" << get_hex10(blk_hash) << ">";
return s;
}
};
Finality
决策信息结构体定义了决策时需要的参数和一些序列化函数
/** 决策信息的抽象 */
struct Finality: public Serializable {
ReplicaID rid; // 副本 ID
int8_t decision; // 决策结果
uint32_t cmd_idx; // 命令索引
uint32_t cmd_height; // 命令高度
uint256_t cmd_hash; // 命令哈希
uint256_t blk_hash; // 区块哈希
public:
// 默认构造函数
Finality() = default;
// 带参数的构造函数,用于初始化所有成员变量
Finality(ReplicaID rid,
int8_t decision,
uint32_t cmd_idx,
uint32_t cmd_height,
uint256_t cmd_hash,
uint256_t blk_hash):
rid(rid), decision(decision),
cmd_idx(cmd_idx), cmd_height(cmd_height),
cmd_hash(cmd_hash), blk_hash(blk_hash) {}
// 序列化函数,将所有成员变量写入数据流 s
void serialize(DataStream &s) const override {
s << rid << decision
<< cmd_idx << cmd_height
<< cmd_hash;
if (decision == 1) s << blk_hash; // 如果决策结果为 1,则写入区块哈希
}
// 反序列化函数,从数据流 s 中读取所有成员变量
void unserialize(DataStream &s) override {
s >> rid >> decision
>> cmd_idx >> cmd_height
>> cmd_hash;
if (decision == 1) s >> blk_hash; // 如果决策结果为 1,则读取区块哈希
}
// 将 Finality 对象转换为字符串表示形式
operator std::string () const {
DataStream s;
s << "<fin "
<< "decision=" << std::to_string(decision) << " "
<< "cmd_idx=" << std::to_string(cmd_idx) << " "
<< "cmd_height=" << std::to_string(cmd_height) << " "
<< "cmd=" << get_hex10(cmd_hash) << " "
<< "blk=" << get_hex10(blk_hash) << ">";
return s;
}
};
协议定义的内容有点多,没关系,我们通过cpp文件的逻辑去一步一步理解:
1.协议构造
首先要有构造函数,在cpp中重写了构造函数。构造一份协议需要副本id和副本持有的私钥,执行创建创世区块、然后将锁定区块和最后执行的区块初始化为创世区块(锁定区块以及最后执后执行区块的作用与hotstuff的三步共识有关,通过区块以及父区块的状态判断区块处于第几步)、初始化区块高度、移动构造私钥,初始化尾部区块tails、不禁用投票、初始化副本id以及存储链。
HotStuffCore::HotStuffCore(ReplicaID id,
privkey_bt &&priv_key):
b0(new Block(true, 1)), // 创建一个初始区块 b0,参数 true 和 1 可能表示区块的某些初始属性
b_lock(b0), // 初始化 锁定区块 为 b0
b_exec(b0), // 初始化 最后执行的区块 为 b0
vheight(0), // 初始化区块高度为 0
priv_key(std::move(priv_key)), // 移动构造函数,初始化私钥
tails{b0}, // 将 b0 添加到 tails 中
vote_disabled(false), // 初始化投票禁用状态为 false
id(id), // 初始化 ReplicaID
storage(new EntityStorage()) { // 创建新的存储实例
storage->add_blk(b0); // 将 b0 添加到存储中
}
2.协议初始化
协议初始化时传入的参数为nfaulty,即能容忍的最大拜占庭节点数,然后解决协议中初始区块b0的qc证书、引用以及协议中最高QC(最高QC是协议在网络中判断是否接受区块或信息的其中一个要求)的初始化。
void HotStuffCore::on_init(uint32_t nfaulty) {
// 计算系统允许的故障节点数,uint32_t nfaulty = peers.size() / 3,是总对等节点数除以 3
config.nmajority = config.nreplicas - nfaulty;
// 创建一个新的 QuorumCert 对象,为 b0 区块生成初始的 quorum certificate
b0->qc = create_quorum_cert(b0->get_hash());
// 计算该 quorum certificate 的内部数据,通常涉及到对证书的合法性验证和数据的准备
b0->qc->compute();
// 克隆生成的 quorum certificate,并将其设置为 b0 区块的 self_qc
b0->self_qc = b0->qc->clone();
// 设置 b0 区块的 quorum certificate 引用为自己
b0->qc_ref = b0;
// 初始化 HQC (High Quality Certificate) 为 b0 区块和 b0 区块的 quorum certificate 克隆对
hqc = std::make_pair(b0, b0->qc->clone());
}
3.协议更新
update函数是协议里重要的一个函数,实现hotstuff中的三/两段式共识。这里分析三段式共识:对于一个区块,获取其引用的父区块,若其父区块不存在或已经决策了就返回➡调用update_hqc函数更新hqc(将本地协议中记录的最高qc替换成父区块的hqc)➡获取父区块的引用区块(爷区块)并判断该区块是否已经决策➡为决策则将锁定该区块➡获取爷区块的引用区块(太爷爷区块)并判断是否已经决策➡未决策则将太爷爷区块及其所在分支上之前的区块都执行共识并标志已决策。(简单来说就是一个区块要满足其所在分支上之后有三个区块才能进行决策,并且成为协议最后执行的区块,而下一个区块则成为锁定区块)
void HotStuffCore::update(const block_t &nblk) {
/* nblk = b*, blk2 = b'', blk1 = b', blk = b */
#ifndef HOTSTUFF_TWO_STEP
/* three-step HotStuff */
const block_t &blk2 = nblk->qc_ref;
if (blk2 == nullptr) return; // 如果 blk2 为 null,返回
/* decided blk could possibly be incomplete due to pruning */
if (blk2->decision) return; // 如果 blk2 已经决策,返回
update_hqc(blk2, nblk->qc); // 更新 HQC
const block_t &blk1 = blk2->qc_ref;
if (blk1 == nullptr) return; // 如果 blk1 为 null,返回
if (blk1->decision) return; // 如果 blk1 已经决策,返回
if (blk1->height > b_lock->height) b_lock = blk1; // 更新 b_lock
const block_t &blk = blk1->qc_ref;
if (blk == nullptr) return; // 如果 blk 为 null,返回
if (blk->decision) return; // 如果 blk 已经决策,返回
/* commit requires direct parent */
if (blk2->parents[0] != blk1 || blk1->parents[0] != blk) return; // 确保父区块关系正确
#else
/* two-step HotStuff */
const block_t &blk1 = nblk->qc_ref;
if (blk1 == nullptr) return; // 如果 blk1 为 null,返回
if (blk1->decision) return; // 如果 blk1 已经决策,返回
update_hqc(blk1, nblk->qc); // 更新 HQC
if (blk1->height > b_lock->height) b_lock = blk1; // 更新 b_lock
const block_t &blk = blk1->qc_ref;
if (blk == nullptr) return; // 如果 blk 为 null,返回
if (blk->decision) return; // 如果 blk 已经决策,返回
/* commit requires direct parent */
if (blk1->parents[0] != blk) return; // 确保父区块关系正确
#endif
/* otherwise commit */
std::vector<block_t> commit_queue; // 用于存储需要提交的区块
block_t b;
for (b = blk; b->height > b_exec->height; b = b->parents[0])
{ /* TODO: also commit the uncles/aunts */
commit_queue.push_back(b); // 将需要提交的区块添加到队列中
}
if (b != b_exec)
throw std::runtime_error("safety breached :( " +
std::string(*blk) + " " +
std::string(*b_exec)); // 确保安全性,如果不符合预期则抛出异常
for (auto it = commit_queue.rbegin(); it != commit_queue.rend(); it++)
{
const block_t &blk = *it;
blk->decision = 1; // 标记区块为已决策
do_consensus(blk); // 执行共识过程
LOG_PROTO("commit %s", std::string(*blk).c_str()); // 记录提交日志
for (size_t i = 0; i < blk->cmds.size(); i++)
do_decide(Finality(id, 1, i, blk->height,
blk->cmds[i], blk->get_hash())); // 执行决策操作
}
b_exec = blk; // 更新 b_exec
}
update_hqc函数比较区块(在上面函数中是新区块引用区块的qc)的qc和本地协议的最高qc,选择最高的qc作为hqc。on_hqc_update是用于解决promiss的,而async_hqc_update定义了回调函数。
void HotStuffCore::update_hqc(const block_t &_hqc, const quorum_cert_bt &qc) {
if (_hqc->height > hqc.first->height) // 如果新 HQC 的高度更高
{
hqc = std::make_pair(_hqc, qc->clone()); // 更新 HQC 为新的更高高度的 QC
on_hqc_update(); // 处理 HQC 更新
}
}
void HotStuffCore::on_hqc_update() {
// 将当前的 HQC 更新等待承诺转移到本地变量 t
auto t = std::move(hqc_update_waiting);
// 将 HQC 更新等待承诺重置为默认状态
hqc_update_waiting = promise_t();
// 解决转移的承诺,表示 HQC 更新已经处理完成
t.resolve();
}
promise_t HotStuffCore::async_hqc_update() {
return hqc_update_waiting.then([this]() {
return hqc.first;
});
}
4.区块交付
区块交付指将协议接收到新区块后记录区块的行为。sanity_check_delivered函数用于判断区块是否已经交付,get_delivered_blk函数通过区块哈希值获取已交付的区块,on_deliver_blk函数用于交付区块。
这里分析一下on_deliver_blk的逻辑,分析区块是否已经交付➡未交付则筛选处引用的已交付的父区块➡检查区块是否有qc证书,有则将区块的引用与qc的引用相等(笔者认为是为了保证一致性)➡将协议中的tails尾块替换为新区块➡设置区块状态为交付。
/*检查一个区块是否已经被交付。如果没有交付,则抛出异常*/
void HotStuffCore::sanity_check_delivered(const block_t &blk) {
if (!blk->delivered)
throw std::runtime_error("block not delivered");
}
//根据区块hash判断hash是否已经交付并返回该区块
block_t HotStuffCore::get_delivered_blk(const uint256_t &blk_hash) {
block_t blk = storage->find_blk(blk_hash); // 从存储中查找区块
if (blk == nullptr || !blk->delivered) // 如果区块不存在或未交付
throw std::runtime_error("block not delivered");
return blk;
}
//交付区块
bool HotStuffCore::on_deliver_blk(const block_t &blk) {
if (blk->delivered) // 如果区块已经交付
{
LOG_WARN("attempt to deliver a block twice"); // 记录警告
return false;
}
blk->parents.clear(); // 清除当前区块的父区块列表
for (const auto &hash: blk->parent_hashes) // 遍历父区块的哈希值
blk->parents.push_back(get_delivered_blk(hash)); // 获取已交付的父区块并添加到父区块列表中
blk->height = blk->parents[0]->height + 1; // 设置当前区块的高度为父区块高度加 1
if (blk->qc) // 如果当前区块有 quorum certificate
{
block_t _blk = storage->find_blk(blk->qc->get_obj_hash()); // 查找 QC 引用的区块
if (_blk == nullptr)
throw std::runtime_error("block referred by qc not fetched"); // 如果找不到该区块,抛出异常
blk->qc_ref = std::move(_blk); // 设置当前区块的 QC 引用
} // 如果没有 QC,blk->qc_ref 保持为 null
for (auto pblk: blk->parents) tails.erase(pblk); // 从 tails 中移除父区块
tails.insert(blk); // 将当前区块添加到 tails 中
blk->delivered = true; // 标记区块为已交付
LOG_DEBUG("deliver %s", std::string(*blk).c_str()); // 记录调试信息
return true;
}
5.提议逻辑
on_propose函数,参数为命令cmds、引用的区块parents、额外信息extra。大致逻辑为:构建新区块➡交付区块➡更新协议update➡创建提议对象➡自身接受提议➡广播提议(网络实现的函数要在hotstuff文件中实现)
block_t HotStuffCore::on_propose(const std::vector<uint256_t> &cmds,
const std::vector<block_t> &parents,
bytearray_t &&extra) {
// 检查父区块列表是否为空
if (parents.empty())
throw std::runtime_error("empty parents"); // 抛出异常,表示父区块列表不能为空
// 从 tails 集合中删除所有父区块
for (const auto &blk: parents) tails.erase(blk);
/* 创建新块 */
// 使用存储接口添加新块到存储
block_t bnew = storage->add_blk(
new Block(
parents, // 父区块列表
cmds, // 命令列表
hqc.second->clone(), // 引用块的qc证书
std::move(extra), // 附加数据
parents[0]->height + 1, // 新块的高度(父区块的高度 + 1)
hqc.first, // 引用的块
nullptr // 父块的其他属性(通常用于拓展)
)
);
// 获取新块的哈希
const uint256_t bnew_hash = bnew->get_hash();
// 创建新块的自我确认证书
bnew->self_qc = create_quorum_cert(bnew_hash);
// 处理新块的交付(通常是存储和更新状态)
on_deliver_blk(bnew);
// 更新内部状态以反映新块的提议
update(bnew);
// 创建提议对象,用于广播和本地处理
Proposal prop(id, bnew, nullptr);
// 记录提议的日志信息
LOG_PROTO("propose %s", std::string(*bnew).c_str());
// 检查新块的高度是否符合预期
if (bnew->height <= vheight)
throw std::runtime_error("new block should be higher than vheight"); // 抛出异常,表示新块高度必须大于当前高度
// 自我接收提议(不需要通过网络发送)
on_receive_proposal(prop);
// 调用回调函数处理提议
on_propose_(prop);
// 广播提议到其他副本
do_broadcast_proposal(prop);
// 返回创建的新块
return bnew;
}
void HotStuffCore::on_propose_(const Proposal &prop) {
auto t = std::move(propose_waiting);
propose_waiting = promise_t();
t.resolve(prop);
}
promise_t HotStuffCore::async_wait_proposal() {
return propose_waiting.then([](const Proposal &prop) {
return prop;
});
}
on_receive_proposal接受提议函数逻辑:判断提议者是否为本地副本
➡是则检查提议的活跃性(区块引用的高度,因为延迟有可能导致提议者协议hqc更新慢了)或安全性(区块是否在锁定块的分支)➡创建投票信息并投票给提议者
➡否则检查区块是否在提议者协议中交付(因为第一次接触新区块信息)➡更新本地协议update➡检查提议的活跃性(区块引用的高度,因为延迟有可能导致提议者协议hqc更新慢了)或安全性(区块是否在锁定块的分支)➡调用on_qc_finish确保新区块引用的qc证书有效➡创建投票信息并投票给提议者
void HotStuffCore::on_receive_proposal(const Proposal &prop) {
// 记录接收到提议的日志
LOG_PROTO("got %s", std::string(prop).c_str());
// 检查提议者是否是当前副本
bool self_prop = prop.proposer == get_id();
// 获取提议中的块
block_t bnew = prop.blk;
// 如果提议者不是当前副本
if (!self_prop)
{
// 执行交付检查,确保块的合法性
sanity_check_delivered(bnew);
// 更新内部状态以反映新块
update(bnew);
}
bool opinion = false;
// 检查新块的高度是否大于当前的有效高度
if (bnew->height > vheight)
{
// 检查新块的确认证书是否有效,且其高度是否大于当前锁定块的高度
if (bnew->qc_ref && bnew->qc_ref->height > b_lock->height)
{
// 满足活跃条件(liveness condition),将其作为提议的意见
opinion = true;
// 更新有效高度为新块的高度
vheight = bnew->height;
}
else
{
// 满足安全条件(safety condition):扩展锁定分支
block_t b;
// 向上遍历块,直到找到与锁定块的分支一致的块
for (b = bnew;
b->height > b_lock->height;
b = b->parents[0]);
// 检查当前块是否在锁定块的同一分支上
if (b == b_lock) /* 在同一分支上 */
{
// 满足安全条件,将其作为提议的意见
opinion = true;
// 更新有效高度为新块的高度
vheight = bnew->height;
}
}
}
// 记录当前状态的日志
LOG_PROTO("now state: %s", std::string(*this).c_str());
// 如果提议者不是当前副本,并且新块的确认证书有效
if (!self_prop && bnew->qc_ref)
// 调用方法处理确认完成的情况
on_qc_finish(bnew->qc_ref);
// 调用回调函数处理提议
on_receive_proposal_(prop);
// 如果符合意见条件且投票未被禁用
if (opinion && !vote_disabled)
// 进行投票
do_vote(
prop.proposer, // 提议者 ID
Vote( // 投票对象
id, // 当前副本 ID
bnew->get_hash(), // 提议块的哈希值
create_part_cert(*priv_key, bnew->get_hash()), // 部分签名证书
this // 当前副本的指针
)
);
}
void HotStuffCore::on_receive_proposal_(const Proposal &prop) {
auto t = std::move(receive_proposal_waiting);
receive_proposal_waiting = promise_t();
t.resolve(prop);
}
promise_t HotStuffCore::async_wait_receive_proposal() {
return receive_proposal_waiting.then([](const Proposal &prop) {
return prop;
});
}
on_qc_finish函数用于确认引用区块的QC情况(笔者认为是在新区块出现时将父区块的QCpromiss解决)
void HotStuffCore::on_qc_finish(const block_t &blk) {
// 查找与块关联的 promise 对象
auto it = qc_waiting.find(blk);
// 如果找到了相关的 promise 对象
if (it != qc_waiting.end())
{
// 解决 promise,表示块的 QC 已经完成
it->second.resolve();
// 从等待列表中移除该块及其对应的 promise 对象
qc_waiting.erase(it);
}
}
promise_t HotStuffCore::async_qc_finish(const block_t &blk) {
// 检查块是否已经获得足够的投票(达到多数),若已达到直接返回一个已解决的 promise
if (blk->voted.size() >= config.nmajority)
return promise_t([](promise_t &pm) {
pm.resolve(); // 解决 promise,无需任何结果
});
// 查找是否已经有一个未完成的 promise 对象在等待这个块的 QC 完成
auto it = qc_waiting.find(blk);
// 如果找不到该块的 promise,则插入一个新的 promise
if (it == qc_waiting.end())
it = qc_waiting.insert(std::make_pair(blk, promise_t())).first;
// 返回与块关联的 promise 对象
return it->second;
}
6.接受投票
on_receive_vote函数逻辑:确认投票的块是否已交付及证书是否存在➡投票数没达到则将投票者记录在投票记录中➡若投票数在增加后达标则计算qc证书、更新最高块qc为hqc、处理区块完成的情况
void HotStuffCore::on_receive_vote(const Vote &vote) {
// 记录接收到的投票信息
LOG_PROTO("got %s", std::string(vote).c_str());
// 记录当前对象的状态
LOG_PROTO("now state: %s", std::string(*this).c_str());
// 根据投票的块哈希值获取已交付的块
block_t blk = get_delivered_blk(vote.blk_hash);
// 确保投票的证书存在
assert(vote.cert);
// 当前块已经收到的投票数量
size_t qsize = blk->voted.size();
// 如果投票数量已经达到多数(nmajority),则无需处理
if (qsize >= config.nmajority) return;
// 尝试将投票者的 ID 插入到块的投票集合中
if (!blk->voted.insert(vote.voter).second)
{
// 如果插入失败,说明该投票者已经投过票,记录警告信息
LOG_WARN("duplicate vote for %s from %d", get_hex10(vote.blk_hash).c_str(), vote.voter);
return;
}
// 获取块的确认证书
auto &qc = blk->self_qc;
// 如果块没有自己的确认证书
if (qc == nullptr)
{
// 记录警告信息,说明该块在自己提议之前没有确认证书
LOG_WARN("vote for block not proposed by itself");
// 创建新的确认证书
qc = create_quorum_cert(blk->get_hash());
}
// 将投票者的证书添加到确认证书中
qc->add_part(vote.voter, *vote.cert);
// 如果投票数量加上当前投票者的投票数等于多数票(nmajority)
if (qsize + 1 == config.nmajority)
{
// 计算确认证书的最终结果
qc->compute();
// 更新当前最高确认块(HQCs)为此块,并记录新的确认证书
update_hqc(blk, qc);
// 处理确认完成的情况
on_qc_finish(blk);
}
}
7.补充
包括剪枝操作,协议中副本管理器添加新副本、以及重载函数string(用于将协议转为数据流)。
void HotStuffCore::prune(uint32_t staleness) {
// `start` 用于遍历要删除的块
block_t start;
/* 跳过 `staleness` 个块,逐步向上遍历,直到找到需要开始修剪的块
这个循环用来定位到要开始修剪的位置 */
for (start = b_exec; staleness; staleness--, start = start->parents[0]) {
// 如果当前块没有父块,则直接返回,因为没有更多的块可以修剪
if (!start->parents.size()) return;
}
// 使用栈来进行深度优先搜索,以修剪从 `start` 开始的所有块
std::stack<block_t> s;
// 清空 `start` 块的 `qc_ref` 引用
start->qc_ref = nullptr;
// 将 `start` 块推入栈中
s.push(start);
while (!s.empty()) {
// 取得栈顶块
auto &blk = s.top();
// 如果当前块没有父块,说明它是一个叶子块,可以释放
if (blk->parents.empty()) {
// 尝试释放当前块的存储
storage->try_release_blk(blk);
// 弹出栈顶块
s.pop();
continue;
}
// 清空当前块的 `qc_ref` 引用
blk->qc_ref = nullptr;
// 将当前块的最后一个父块推入栈中
s.push(blk->parents.back());
// 从当前块的父块列表中移除最后一个父块
blk->parents.pop_back();
}
}
void HotStuffCore::add_replica(ReplicaID rid, const PeerId &peer_id,
pubkey_bt &&pub_key) {
// 将新的副本添加到配置中
config.add_replica(rid, ReplicaInfo(rid, peer_id, std::move(pub_key)));
// 将副本 ID 添加到 `b0` 的投票集合中
b0->voted.insert(rid);
}
HotStuffCore::operator std::string () const {
// 使用 DataStream 进行格式化输出
DataStream s;
// 将 `HotStuffCore` 对象的相关信息格式化为字符串
s << "<hotstuff "
<< "hqc=" << get_hex10(hqc.first->get_hash()) << " " // HQC 块的哈希值
<< "hqc.height=" << std::to_string(hqc.first->height) << " " // HQC 块的高度
<< "b_lock=" << get_hex10(b_lock->get_hash()) << " " // 锁定块的哈希值
<< "b_exec=" << get_hex10(b_exec->get_hash()) << " " // 执行块的哈希值
<< "vheight=" << std::to_string(vheight) << " " // 当前投票高度
<< "tails=" << std::to_string(tails.size()) << ">" // `tails` 列表的大小
;
// 返回格式化后的字符串
return s;
}