1.前言
上一章分析了源代码中的config(日志与宏)、util(区块分析器)、task(任务、任务池)、type(辅助工具:克隆、错误基类)、crypto(椭圆曲线加密)五个文件部分。这一章我们深入探讨entity和promiss。
2.源码分析
1)Entity文件
该文件用于定义副本、区块和命令以及相关的副本管理工具和区块命令管理工具
replica副本
包括ReplicaInfo结构体和ReplicaConfig类,前者用于副本的结构定义,后者可看作管理副本的工具
/*
* ReplicaInfo 结构体用于存储副本的信息,包括副本的 ID(ReplicaID),
* 对等节点的 ID(PeerId),以及公钥(pubkey)。
*/
struct ReplicaInfo {
ReplicaID id; // 副本的唯一标识符
salticidae::PeerId peer_id; // 与副本相关联的对等节点 ID
pubkey_bt pubkey; // 指向副本公钥的智能指针
/*
* 构造函数,接受副本 ID,PeerId,以及一个公钥指针。
* pubkey 是通过 std::move 进行右值引用,以避免不必要的拷贝。
*/
ReplicaInfo(ReplicaID id,
const salticidae::PeerId &peer_id,
pubkey_bt &&pubkey):
id(id), peer_id(peer_id), pubkey(std::move(pubkey)) {}
/*
* 复制构造函数,深拷贝 ReplicaInfo 对象。
* 公钥使用 clone() 方法进行深拷贝,确保每个 ReplicaInfo 拥有独立的公钥副本。
*/
ReplicaInfo(const ReplicaInfo &other):
id(other.id), peer_id(other.peer_id),
pubkey(other.pubkey->clone()) {}
/*
* 移动构造函数,将另一个 ReplicaInfo 对象的资源移动到当前对象。
* 使用 std::move 转移 pubkey 所指向的对象所有权。
*/
ReplicaInfo(ReplicaInfo &&other):
id(other.id), peer_id(other.peer_id),
pubkey(std::move(other.pubkey)) {}
};
/*
* ReplicaConfig 类用于管理系统中所有副本的配置信息。
* 它包含一个副本信息的哈希表(replica_map),
* 以及系统中副本的总数(nreplicas)和多数派的数量(nmajority)。
*/
class ReplicaConfig {
// 使用 unordered_map 将 ReplicaID 映射到 ReplicaInfo 对象,用于快速查找。
std::unordered_map<ReplicaID, ReplicaInfo> replica_map;
public:
size_t nreplicas; // 系统中副本的总数
size_t nmajority; // 多数派的数量,通常为 nreplicas 的过半数
/*
* 默认构造函数,初始化副本总数和多数派数量为 0。
*/
ReplicaConfig(): nreplicas(0), nmajority(0) {}
/*
* 添加一个副本到配置中。
* 该方法接受一个副本 ID 和对应的 ReplicaInfo 对象,并将其插入到 replica_map 中。
* 每次添加副本后,副本总数 nreplicas 增加 1。
*/
void add_replica(ReplicaID rid, const ReplicaInfo &info) {
replica_map.insert(std::make_pair(rid, info));
nreplicas++;
}
/*
* 获取指定副本 ID 的 ReplicaInfo 对象。
* 如果找不到对应的副本 ID,则抛出 HotStuffError 异常。
*/
const ReplicaInfo &get_info(ReplicaID rid) const {
auto it = replica_map.find(rid);
if (it == replica_map.end())
throw HotStuffError("rid %s not found",
get_hex(rid).c_str()); // 抛出异常,表明找不到对应的副本 ID
return it->second;
}
/*
* 获取指定副本 ID 的公钥。
* 通过 get_info 方法获取副本信息,然后返回该副本的公钥。
*/
const PubKey &get_pubkey(ReplicaID rid) const {
return *(get_info(rid).pubkey);
}
/*
* 获取指定副本 ID 的 PeerId。
* 通过 get_info 方法获取副本信息,然后返回该副本的 PeerId。
*/
const salticidae::PeerId &get_peer_id(ReplicaID rid) const {
return get_info(rid).peer_id;
}
};
command命令
Command 类表示一个命令,继承自 Serializable。包含纯虚函数 get_hash 和 verify
/**Command 类表示一个命令,继承自 Serializable。包含纯虚函数 get_hash 和 verify,
需要具体实现类来定义。提供了将命令转换为字符串的方法。 */
class Command: public Serializable {
friend HotStuffCore;//友元,hottuffCore是基础协议
public:
virtual ~Command() = default;
virtual const uint256_t &get_hash() const = 0;//获取命令的hash值
virtual bool verify() const = 0;//验证命令
virtual operator std::string () const {
DataStream s;
s << "<cmd id=" << get_hex10(get_hash()) << ">";
return s;
}
};
using command_t = ArcObj<Command>;
block区块
定义了区块的各种信息和状态函数
/*Block 类表示一个区块,包含区块的各种信息和状态。
提供构造函数、序列化和反序列化方法、验证方法、获取区块信息的方法以及将区块转换为字符串的方法。*/
class Block {
// 允许 HotStuffCore 访问 Block 的私有成员
friend HotStuffCore;
// 存储父块的哈希值
std::vector<uint256_t> parent_hashes;
// 存储命令
std::vector<uint256_t> cmds;
// 存储法定证书
quorum_cert_bt qc;
// 额外的字节数组
bytearray_t extra;
// 以下字段可以从上面的字段派生
uint256_t hash; // 块的哈希值
std::vector<block_t> parents; // 父块的引用
block_t qc_ref; // 引用的区块
quorum_cert_bt self_qc; // 自己的法定证书
uint32_t height; // 块的高度
bool delivered; // 块是否已传递
int8_t decision; // 决策结果
// 记录已投票的副本 ID 集合
std::unordered_set<ReplicaID> voted;
public:
// 默认构造函数
Block():
qc(nullptr),
qc_ref(nullptr),
self_qc(nullptr),
height(0),
delivered(false),
decision(0) {}
// 带有初始化传递和决策的构造函数
Block(bool delivered, int8_t decision):
qc(new QuorumCertDummy()),
hash(salticidae::get_hash(*this)),
qc_ref(nullptr),
self_qc(nullptr),
height(0),
delivered(delivered),
decision(decision) {}
// 带有父块、命令、法定证书、额外信息、高度、法定证书引用、自身法定证书和决策的构造函数
Block(const std::vector<block_t> &parents,
const std::vector<uint256_t> &cmds,
quorum_cert_bt &&qc,
bytearray_t &&extra,
uint32_t height,
const block_t &qc_ref,
quorum_cert_bt &&self_qc,
int8_t decision = 0):
parent_hashes(get_hashes(parents)),
cmds(cmds),
qc(std::move(qc)),
extra(std::move(extra)),
hash(salticidae::get_hash(*this)),
parents(parents),
qc_ref(qc_ref),
self_qc(std::move(self_qc)),
height(height),
delivered(0),
decision(decision) {}
// 序列化函数
void serialize(DataStream &s) const;
// 反序列化函数
void unserialize(DataStream &s, HotStuffCore *hsc);
// 获取命令的常量引用
const std::vector<uint256_t> &get_cmds() const {
return cmds;
}
// 获取父块的常量引用
const std::vector<block_t> &get_parents() const {
return parents;
}
// 获取父块哈希值的常量引用
const std::vector<uint256_t> &get_parent_hashes() const {
return parent_hashes;
}
// 获取块哈希值的常量引用
const uint256_t &get_hash() const { return hash; }
// 验证函数
bool verify(const HotStuffCore *hsc) const;
// 异步验证函数
promise_t verify(const HotStuffCore *hsc, VeriPool &vpool) const;
// 获取决策结果
int8_t get_decision() const { return decision; }
// 判断块是否已传递
bool is_delivered() const { return delivered; }
// 获取块的高度
uint32_t get_height() const { return height; }
// 获取法定证书的常量引用
const quorum_cert_bt &get_qc() const { return qc; }
// 获取引用的法定证书的常量引用
const block_t &get_qc_ref() const { return qc_ref; }
// 获取额外信息的常量引用
const bytearray_t &get_extra() const { return extra; }
// 将块转换为字符串表示形式
operator std::string () const {
DataStream s;
s << "<block "
<< "id=" << get_hex10(hash) << " "
<< "height=" << std::to_string(height) << " "
<< "parent=" << get_hex10(parent_hashes[0]) << " "
<< "qc_ref=" << (qc_ref ? get_hex10(qc_ref->get_hash()) : "null") << ">";
return s;
}
};
/*BlockHeightCmp 是一个比较函数对象,用于根据区块的高度比较两个区块。这在需要排序或优先级队列的场景中非常有用。*/
struct BlockHeightCmp {
bool operator()(const block_t &a, const block_t &b) const {
return a->get_height() < b->get_height();
}
};
区块序列化以及验证实现
这里包括区块序列化和反序列化,比较简单易懂。区块验证分为普通验证(同步验证)和异步验证。HotstuffCore是协议,会记录相关整个共识机制的相关信息(第三章会提到)。验证中qc的verify函数在第一章有分析。大概逻辑就是每个区块的qc证书会获取签名,验证区块时调用该区块证书的verify函数来验证签名是否有效且达到固定数量。
// Block 类的成员函数,用于将区块的所有成员序列化到数据流中。
void Block::serialize(DataStream &s) const {
// 将父区块的哈希数量写入数据流,以小端字节序表示
s << htole((uint32_t)parent_hashes.size());
// 遍历并写入所有父区块的哈希值到数据流
// parent_hashes 是一个存储父区块哈希值的容器
for (const auto &hash: parent_hashes)
s << hash;
// 将命令的数量写入数据流,以小端字节序表示
// cmds 是一个存储区块命令的容器
s << htole((uint32_t)cmds.size());
// 遍历并写入所有命令到数据流
// 每个命令都被序列化并写入数据流
for (auto cmd: cmds)
s << cmd;
// 将 quorum certificate (QC) 写入数据流
// qc 是指向 quorum certificate 的智能指针
s << *qc;
// 将额外数据的大小写入数据流,以小端字节序表示
// extra 是存储额外数据的容器
s << htole((uint32_t)extra.size());
// 将额外数据写入数据流
// 如果 extra 不为空,这里会将额外数据的内容写入数据流
s << extra;
}
// Block 类的成员函数,用于从数据流中反序列化区块的所有成员。
void Block::unserialize(DataStream &s, HotStuffCore *hsc) {
uint32_t n;
// 从数据流中读取父区块哈希的数量,并转换为主机字节序
// 这里读取的是小端字节序的 uint32_t,然后转换成主机字节序
s >> n;
n = letoh(n); // letoh 是一个转换函数,将小端字节序转换为主机字节序
parent_hashes.resize(n); // 调整 parent_hashes 向量的大小以适应读取到的哈希数量
// 从数据流中读取所有父区块的哈希值
// 这里从数据流中按顺序读取哈希值并填充到 parent_hashes 容器中
for (auto &hash: parent_hashes)
s >> hash;
// 从数据流中读取命令的数量,并转换为主机字节序
s >> n;
n = letoh(n);
cmds.resize(n); // 调整 cmds 向量的大小以适应读取到的命令数量
// 从数据流中读取所有命令
// 这里从数据流中按顺序读取命令并填充到 cmds 容器中
for (auto &cmd: cmds)
s >> cmd;
// 从数据流中解析 quorum certificate (QC)
// 使用 HotStuffCore 的方法解析从数据流中读取到的 QC
qc = hsc->parse_quorum_cert(s);
// 从数据流中读取额外数据的大小,并转换为主机字节序
s >> n;
n = letoh(n);
// 如果额外数据的大小为0,则清空 extra
// 这里处理了额外数据大小为零的特殊情况
if (n == 0)
extra.clear();
else
{
// 从数据流中获取额外数据
// 从数据流中读取指定大小的额外数据并存储到 extra 容器中
auto base = s.get_data_inplace(n);
extra = bytearray_t(base, base + n); // 将额外数据存储到 extra 中
}
// 计算并更新区块的哈希值
// 这里使用 hash 函数计算当前区块的哈希值,并更新 hash 成员变量
this->hash = salticidae::get_hash(*this);
}
// Block 类的成员函数,用于验证区块的有效性。
bool Block::verify(const HotStuffCore *hsc) const {
// 检查 quorum certificate (QC) 的哈希是否与 Genesis 区块的哈希匹配
// 如果匹配,则返回 true,表示验证通过
// 否则,通过 QC 验证区块是否符合配置的要求
if (qc->get_obj_hash() == hsc->get_genesis()->get_hash())
return true;
return qc->verify(hsc->get_config());// 验证 QC 是否符合配置要求
}
// 异步验证区块的有效性,并返回一个 promise。
promise_t Block::verify(const HotStuffCore *hsc, VeriPool &vpool) const {
// 检查 quorum certificate (QC) 的哈希是否与 Genesis 区块的哈希匹配
// 如果匹配,则返回一个 resolved promise,表示验证通过
// 否则,通过 QC 的异步验证方法进行验证
if (qc->get_obj_hash() == hsc->get_genesis()->get_hash())
return promise_t([](promise_t &pm) { pm.resolve(true); });
return qc->verify(hsc->get_config(), vpool); // 异步验证 QC 是否符合配置要求
}
/*模板函数 get_hashes 接受一个包含Hashable对象的向量,并返回这些对象的哈希值向量。
Hashable必须具有get_hash方法。*/
template<typename Hashable>
inline static std::vector<uint256_t>
get_hashes(const std::vector<Hashable> &plist) {
std::vector<uint256_t> hashes;
for (const auto &p: plist)
hashes.push_back(p->get_hash());
return hashes;
}
区块与命令管理器
区块管理器EntityStorage用于管理区块和命令,添加其到缓存等作用
//定义实体类型的枚举,ENT_TYPE_CMD表示命令实体,ENT_TYPE_BLK表示区块实体
enum EntityType {
ENT_TYPE_CMD = 0x0,
ENT_TYPE_BLK = 0x1
};
/*EntityStorage 类用于管理区块和命令的缓存。
提供了添加、查找、验证、释放区块和命令的方法。*/
class EntityStorage {
// 用于缓存区块的哈希值和对应的区块指针
std::unordered_map<const uint256_t, block_t> blk_cache;
// 用于缓存命令的哈希值和对应的命令指针
std::unordered_map<const uint256_t, command_t> cmd_cache;
public:
// 检查区块是否已交付
bool is_blk_delivered(const uint256_t &blk_hash) {
auto it = blk_cache.find(blk_hash);
if (it == blk_cache.end()) return false;
return it->second->is_delivered();
}
// 检查区块是否已获取
bool is_blk_fetched(const uint256_t &blk_hash) {
return blk_cache.count(blk_hash);
}
// 添加一个新生成的区块到缓存
block_t add_blk(Block &&_blk, const ReplicaConfig &/*config*/) {
// 可以启用的验证区块代码
// if (!_blk.verify(config)) {
// HOTSTUFF_LOG_WARN("invalid %s", std::string(_blk).c_str());
// return nullptr;
// }
block_t blk = new Block(std::move(_blk));
return blk_cache.insert(std::make_pair(blk->get_hash(), blk)).first->second;
}
// 添加一个现有的区块到缓存
const block_t &add_blk(const block_t &blk) {
return blk_cache.insert(std::make_pair(blk->get_hash(), blk)).first->second;
}
// 根据区块哈希查找区块
block_t find_blk(const uint256_t &blk_hash) {
auto it = blk_cache.find(blk_hash);
return it == blk_cache.end() ? nullptr : it->second;
}/*如果迭代器等于 end(),说明没有找到对应的区块,返回空指针*/
// 检查命令是否已获取
bool is_cmd_fetched(const uint256_t &cmd_hash) {
return cmd_cache.count(cmd_hash);
}
// 添加一个命令到缓存
const command_t &add_cmd(const command_t &cmd) {
return cmd_cache.insert(std::make_pair(cmd->get_hash(), cmd)).first->second;
}
// 根据命令哈希查找命令
command_t find_cmd(const uint256_t &cmd_hash) {
auto it = cmd_cache.find(cmd_hash);
return it == cmd_cache.end() ? nullptr: it->second;
}
// 获取命令缓存的大小
size_t get_cmd_cache_size() {
return cmd_cache.size();
}
// 获取区块缓存的大小
size_t get_blk_cache_size() {
return blk_cache.size();
}
// 尝试释放命令,如果引用计数为 2(仅存储和命令本身)
bool try_release_cmd(const command_t &cmd) {
if (cmd.get_cnt() == 2) {
const auto &cmd_hash = cmd->get_hash();
cmd_cache.erase(cmd_hash);
return true;
}
return false;
}
// 尝试释放区块,如果引用计数为 2(仅存储和区块本身)
bool try_release_blk(const block_t &blk) {
if (blk.get_cnt() == 2) {
const auto &blk_hash = blk->get_hash();
#ifdef HOTSTUFF_PROTO_LOG
HOTSTUFF_LOG_INFO("releasing blk %.10s", get_hex(blk_hash).c_str());
#endif
// 可以启用的释放区块命令代码
// for (const auto &cmd: blk->get_cmds())
// try_release_cmd(cmd);
blk_cache.erase(blk_hash);
return true;
}
#ifdef HOTSTUFF_PROTO_LOG
else
HOTSTUFF_LOG_INFO("cannot release (%lu)", blk.get_cnt());
#endif
return false;
}
};
2)Promiss.hpp文件
该文件定义了一个C++的Promise库,类似于JavaScript中的Promise/A+。它允许你创建、链式调用和处理异步操作。主要分析其中重要的函数
promise_t类
promise_t
对象在这个 C++ Promise 库中扮演了核心角色,用于管理异步操作的执行、结果处理、以及错误处理。它类似于 JavaScript 中的 Promise
对象,主要用来处理异步操作,并提供了链式调用的能力。
class promise_t {
// 内部指针,指向实际的 Promise 对象
Promise *pm;
// 内部引用计数,跟踪有多少个 promise_t 实例引用相同的 Promise 对象
size_t *ref_cnt;
public:
// 声明友元类和友元函数,允许它们访问 promise_t 的私有成员
friend Promise;
template<typename PList> friend promise_t all(const PList &promise_list);
template<typename PList> friend promise_t race(const PList &promise_list);
// 默认构造函数
inline promise_t();
// 析构函数
inline ~promise_t();
// 带回调函数的构造函数
template<typename Func, disable_if_same_ref<Func, promise_t> * = nullptr>
inline promise_t(Func &&callback);
// 交换两个 promise_t 对象的内容
void swap(promise_t &other) {
std::swap(pm, other.pm);
std::swap(ref_cnt, other.ref_cnt);
}
// 拷贝赋值运算符
promise_t &operator=(const promise_t &other) {
if (this != &other) {
promise_t tmp(other);
tmp.swap(*this);
}
return *this;
}
// 移动赋值运算符
promise_t &operator=(promise_t &&other) {
if (this != &other) {
promise_t tmp(std::move(other));
tmp.swap(*this);
}
return *this;
}
// 拷贝构造函数
promise_t(const promise_t &other):
pm(other.pm),
ref_cnt(other.ref_cnt) {
++*ref_cnt;
}
// 移动构造函数
promise_t(promise_t &&other):
pm(other.pm),
ref_cnt(other.ref_cnt) {
other.pm = nullptr;
}
// 解引用操作符,返回实际的 Promise 对象
Promise *operator->() const {
return pm;
}
// 解决 Promise 的结果
template<typename T> inline void resolve(T result) const;
// 拒绝 Promise
template<typename T> inline void reject(T reason) const;
// 解决 Promise,无结果
inline void resolve() const;
// 拒绝 Promise,无原因
inline void reject() const;
// 处理 Promise 成功的情况
template<typename FuncFulfilled>
inline promise_t then(FuncFulfilled &&on_fulfilled) const;
// 处理 Promise 成功和失败的情况
template<typename FuncFulfilled, typename FuncRejected>
inline promise_t then(FuncFulfilled &&on_fulfilled,
FuncRejected &&on_rejected) const;
// 处理 Promise 失败的情况
template<typename FuncRejected>
inline promise_t fail(FuncRejected &&on_rejected) const;
};
接下来举几个例子来加深对这个类的用法
基本使用
//基本使用
int main() {
promise_t my_promise([](promise_t &resolve_promise) {
// 异步操作……
// 在异步操作完成后解析这个 promise
resolve_promise.resolve(42);
});
my_promise.then([](int result) {
std::cout << "Promise resolved with value: " << result << std::endl;
}).fail([](const std::string &error) {
std::cerr << "Promise rejected with error: " << error << std::endl;
});
return 0;
}
- 创建了一个
promise_t
对象my_promise
,其中resolve_promise.resolve(42)
代表异步操作成功,并返回42
。 then
方法处理成功的情况,输出结果。fail
方法处理失败的情况(在这个例子中不会触发)。
异步操作链式使用
//异步操作链式使用
int main() {
promise_t my_promise([](promise_t &resolve_promise) {
resolve_promise.resolve(42);
});
my_promise.then([](int result) {
return result * 2;
}).then([](int result) {
std::cout << "Result after chaining: " << result << std::endl;
});
return 0;
}
then
方法不仅可以处理结果,还可以返回一个新的结果,这个新结果会传递给下一个then
方法。
使用 all 处理并发操作
//使用 all 处理并发操作
int main() {
promise_t p1([](promise_t &resolve_promise) {
resolve_promise.resolve(10);
});
promise_t p2([](promise_t &resolve_promise) {
resolve_promise.resolve(20);
});
auto combined_promise = promise::all(std::vector<promise_t>{p1, p2});
combined_promise.then([](const promise::values_t &results) {
int sum = promise::any_cast<int>(results[0]) + promise::any_cast<int>(results[1]);
std::cout << "Sum of results: " << sum << std::endl;
});
return 0;
}
p1
和p2
是两个独立的 Promise,它们分别解析为10
和20
。- 使用
promise::all
等待这两个 Promise 完成,并将它们的结果作为一个向量传递给then
方法。 then
方法计算结果的总和并输出