目录
1.前言
前半章主要分析了hotstuff核心代码的基本实现(不包括网络部分),以及相关的一些提议、投票、决策信息的定义。下半章笔者将分析hotstuff的网络实现部分。
2.源码分析
首先我们要知道,网络实现的协议下,因为要传输信息,因此需要对消息、区块等进一步封装。
propose网络消息封装
MsgPropose对prorose的封装主要是将其序列化和反序列化更方便
struct MsgPropose {
static const opcode_t opcode = 0x0;// 操作码
DataStream serialized;// 序列化的数据流
Proposal proposal;// 提案
MsgPropose(const Proposal &); // 构造函数,初始化提案
/** 仅将数据移动到 serialized,不立即解析*/
MsgPropose(DataStream &&s): serialized(std::move(s)) {}
/** 现在解析序列化数据到 blks,使用 `hsc->storage`*/
void postponed_parse(HotStuffCore *hsc);
};
// MsgPropose 类的静态成员变量 opcode 的定义
const opcode_t MsgPropose::opcode;
// MsgPropose 类的构造函数,初始化时序列化 Proposal 对象
MsgPropose::MsgPropose(const Proposal &proposal) {
serialized << proposal; // 将 proposal 对象序列化到 serialized 流中
}
// MsgPropose 类的延期解析函数,待实际需要时才进行解析
void MsgPropose::postponed_parse(HotStuffCore *hsc) {
proposal.hsc = hsc; // 设置 proposal 的 hsc 成员为传入的 HotStuffCore 指针
serialized >> proposal; // 从 serialized 流中反序列化 proposal 对象
}
vote网络消息封装
struct MsgVote {
static const opcode_t opcode = 0x1;// 操作码
DataStream serialized;// 序列化的数据流
Vote vote;// 投票
MsgVote(const Vote &);// 构造函数,初始化投票
MsgVote(DataStream &&s): serialized(std::move(s)) {}
void postponed_parse(HotStuffCore *hsc);
};
// MsgVote 类的静态成员变量 opcode 的定义
const opcode_t MsgVote::opcode;
// MsgVote 类的构造函数,初始化时序列化 Vote 对象
MsgVote::MsgVote(const Vote &vote) {
serialized << vote; // 将 vote 对象序列化到 serialized 流中
}
// MsgVote 类的延期解析函数,待实际需要时才进行解析
void MsgVote::postponed_parse(HotStuffCore *hsc) {
vote.hsc = hsc; // 设置 vote 的 hsc 成员为传入的 HotStuffCore 指针
serialized >> vote; // 从 serialized 流中反序列化 vote 对象
}
block网络消息封装
MsgReqBlock和MsgRespBlock
这两种结构体分别用于区块链系统中的区块请求和响应机制:
MsgReqBlock
用于发起区块请求,包含了请求的区块哈希值。MsgRespBlock
用于响应区块请求,包含了请求的区块数据,并提供了延期解析的方法来处理实际的数据处理和存储。
1.请求区块MsgReqBlock
struct MsgReqBlock {
static const opcode_t opcode = 0x2; // 操作码
DataStream serialized; // 序列化的数据流
std::vector<uint256_t> blk_hashes; // 请求的区块哈希值
MsgReqBlock() = default; // 默认构造函数
MsgReqBlock(const std::vector<uint256_t> &blk_hashes); // 构造函数,初始化区块哈希值
MsgReqBlock(DataStream &&s); // 构造函数,使用数据流初始化
};
// MsgReqBlock 类的静态成员变量 opcode 的定义
const opcode_t MsgReqBlock::opcode;
// MsgReqBlock 类的构造函数,初始化时序列化请求的区块哈希列表
MsgReqBlock::MsgReqBlock(const std::vector<uint256_t> &blk_hashes) {
serialized << htole((uint32_t)blk_hashes.size()); // 将 blk_hashes 的大小序列化为小端格式
for (const auto &h: blk_hashes)
serialized << h; // 将每个区块哈希序列化到 serialized 流中
}
// MsgReqBlock 类的构造函数,使用 DataStream 对象反序列化数据
MsgReqBlock::MsgReqBlock(DataStream &&s) {
uint32_t size; // 区块哈希列表的大小
s >> size; // 从流中读取区块哈希列表的大小
size = letoh(size); // 将大小从小端格式转换为主机字节序
blk_hashes.resize(size); // 调整 blk_hashes 向量的大小以适应读取的哈希列表
for (auto &h: blk_hashes) s >> h; // 从流中读取每个区块哈希
}
2.响应区块MsgRespBlock
struct MsgRespBlock {
static const opcode_t opcode = 0x3; // 操作码
DataStream serialized; // 序列化的数据流
std::vector<block_t> blks; // 响应的区块
MsgRespBlock(const std::vector<block_t> &blks); // 构造函数,初始化区块
MsgRespBlock(DataStream &&s): serialized(std::move(s)) {}
void postponed_parse(HotStuffCore *hsc);
};
// MsgRespBlock 类的静态成员变量 opcode 的定义
const opcode_t MsgRespBlock::opcode;
// MsgRespBlock 类的构造函数,初始化时序列化区块列表
MsgRespBlock::MsgRespBlock(const std::vector<block_t> &blks) {
serialized << htole((uint32_t)blks.size()); // 将 blks 的大小序列化为小端格式
for (auto blk: blks) serialized << *blk; // 将每个区块序列化到 serialized 流中
}
// MsgRespBlock 类的延期解析函数,待实际需要时才进行解析
void MsgRespBlock::postponed_parse(HotStuffCore *hsc) {
uint32_t size; // 区块列表的大小
serialized >> size; // 从流中读取区块列表的大小
size = letoh(size); // 将大小从小端格式转换为主机字节序
blks.resize(size); // 调整 blks 向量的大小以适应读取的区块列表
for (auto &blk: blks) {
Block _blk; // 临时区块对象
_blk.unserialize(serialized, hsc); // 从流中反序列化区块数据
blk = hsc->storage->add_blk(std::move(_blk), hsc->get_config()); // 将反序列化的区块添加到存储中
}
}
消息获取器FetchContext
FetchContext类用于管理从其他副本获取数据的过程,包括发送请求、处理超时和跟踪请求的状态。逻辑如下:
-
请求处理:
FetchContext
主要负责从副本获取区块或命令数据。它通过send
函数向指定副本发送请求消息,并通过add_replica
函数添加副本到集合中。 -
超时处理: 使用
timeout
定时器管理请求的超时情况。超时回调函数timeout_cb
负责重新发送请求并重置超时,以确保请求能够在合理时间内得到响应。 -
移动语义: 移动构造函数支持高效的对象移动,特别是在处理异步请求和超时时,可以避免不必要的拷贝操作。
-
模板化设计:
FetchContext
是一个模板类,可以处理不同类型的实体(如区块或命令)。这种设计使得代码具有更好的灵活性和复用性。
/*这个类 FetchContext 在 HotStuff 共识协议中用于处理获取(fetch)区块或命令的上下文信息。
具体来说,这个类用于管理从其他副本获取数据的过程,包括发送请求、处理超时和跟踪请求的状态。*/
template<EntityType ent_type>
class FetchContext: public promise_t {
TimerEvent timeout; // 超时事件
HotStuffBase *hs; // HotStuffBase 的指针
MsgReqBlock fetch_msg; // 请求消息
const uint256_t ent_hash; // 实体哈希值
std::unordered_set<PeerId> replicas; // 副本集合
// 超时回调函数
inline void timeout_cb(TimerEvent &);
public:
// 禁用拷贝构造和赋值运算符
FetchContext(const FetchContext &) = delete;
FetchContext &operator=(const FetchContext &) = delete;
// 移动构造函数
FetchContext(FetchContext &&other);
// 构造函数,初始化实体哈希和 HotStuffBase 指针
FetchContext(const uint256_t &ent_hash, HotStuffBase *hs);
~FetchContext() {}
// 发送请求消息给指定副本
inline void send(const PeerId &replica);
// 重置超时
inline void reset_timeout();
// 添加副本,如果 fetch_now 为 true,则立即发送请求消息
inline void add_replica(const PeerId &replica, bool fetch_now = true);
};
/** FetchContext 类模板的移动构造函数 */
template<EntityType ent_type>
FetchContext<ent_type>::FetchContext(FetchContext && other):
promise_t(static_cast<const promise_t &>(other)), // 调用基类的移动构造函数
hs(other.hs), // 移动 HotStuffBase 指针
fetch_msg(std::move(other.fetch_msg)), // 移动请求消息
ent_hash(other.ent_hash), // 移动实体哈希值
replicas(std::move(other.replicas)) { // 移动副本集合
other.timeout.del(); // 删除原对象的超时事件
// 创建新的超时事件
timeout = TimerEvent(hs->ec,
std::bind(&FetchContext::timeout_cb, this, _1));
reset_timeout(); // 重置超时
}
/** ENT_TYPE_CMD 的超时回调函数 */
template<>
inline void FetchContext<ENT_TYPE_CMD>::timeout_cb(TimerEvent &) {
HOTSTUFF_LOG_WARN("cmd fetching %.10s timeout", get_hex(ent_hash).c_str());
// 重新发送请求消息给所有副本
for (const auto &replica: replicas)
send(replica);
reset_timeout(); // 重置超时
}
/** ENT_TYPE_BLK 的超时回调函数 */
template<>
inline void FetchContext<ENT_TYPE_BLK>::timeout_cb(TimerEvent &) {
HOTSTUFF_LOG_WARN("block fetching %.10s timeout", get_hex(ent_hash).c_str());
// 重新发送请求消息给所有副本
for (const auto &replica: replicas)
send(replica);
reset_timeout(); // 重置超时
}
/** FetchContext 类模板的构造函数 */
template<EntityType ent_type>
FetchContext<ent_type>::FetchContext(
const uint256_t &ent_hash, HotStuffBase *hs):
promise_t([](promise_t){}), // 调用基类的构造函数
hs(hs), ent_hash(ent_hash) { // 初始化成员变量
fetch_msg = std::vector<uint256_t>{ent_hash}; // 创建请求消息
// 创建超时事件
timeout = TimerEvent(hs->ec,
std::bind(&FetchContext::timeout_cb, this, _1));
reset_timeout(); // 重置超时
}
template<EntityType ent_type>
void FetchContext<ent_type>::send(const PeerId &replica) {
hs->part_fetched_replica[replica]++; // 增加副本的部分获取区块数量
hs->pn.send_msg(fetch_msg, replica); // 发送请求消息给副本
}
template<EntityType ent_type>
void FetchContext<ent_type>::reset_timeout() {
// 设置随机超时
timeout.add(salticidae::gen_rand_timeout(ent_waiting_timeout));
}
template<EntityType ent_type>
void FetchContext<ent_type>::add_replica(const PeerId &replica, bool fetch_now) {
// 如果副本集合为空且立即获取,则发送请求消息
if (replicas.empty() && fetch_now)
send(replica);
replicas.insert(replica); // 将副本添加到集合中
}
区块交付处理器BlockDeliveryContext
BlockDeliveryContext 类是 HotStuff 协议中的一个类,用于管理区块的交付上下文。(可以认为就是有个管理交付的promise,只不过多了个计时器)
/*BlockDeliveryContext 类是一个处理区块交付的上下文类,继承自 promise_t。它用于管理区块交付过程中的状态,包括计时和 Promise 处理
具体来说,BlockDeliveryContext 用于记录区块从请求到交付完成所经过的时间,并提供相关的回调机制。*/
class BlockDeliveryContext: public promise_t {
public:
ElapsedTime elapsed; // 记录经过的时间
// 禁用赋值运算符
BlockDeliveryContext &operator=(const BlockDeliveryContext &) = delete;
// 拷贝构造函数
BlockDeliveryContext(const BlockDeliveryContext &other):
promise_t(static_cast<const promise_t &>(other)),
elapsed(other.elapsed) {}
// 移动构造函数
BlockDeliveryContext(BlockDeliveryContext &&other):
promise_t(static_cast<const promise_t &>(other)),
elapsed(std::move(other.elapsed)) {}
// 带回调函数的构造函数,初始化 promise_t 并开始计时
template<typename Func>
BlockDeliveryContext(Func callback): promise_t(callback) {
elapsed.start();
}
};
带网络实现HotstuffBase
/** HotStuff 协议(带网络实现)。*/
class HotStuffBase: public HotStuffCore {
using BlockFetchContext = FetchContext<ENT_TYPE_BLK>; // 定义 BlockFetchContext 类型
using CmdFetchContext = FetchContext<ENT_TYPE_CMD>; // 定义 CmdFetchContext 类型
friend BlockFetchContext; // 将 BlockFetchContext 设为友元类
friend CmdFetchContext; // 将 CmdFetchContext 设为友元类
public:
using Net = PeerNetwork<opcode_t>; // 定义网络类型
using commit_cb_t = std::function<void(const Finality &)>; // 定义提交回调类型
protected:
/** 副本网络中的绑定地址 */
NetAddr listen_addr;
/** 区块大小 */
size_t blk_size;
/** libevent 句柄 */
EventContext ec;
salticidae::ThreadCall tcall; // 线程调用对象
VeriPool vpool; // 验证池
std::vector<PeerId> peers; // 副本列表
private:
/** libevent 句柄是否由自身拥有 */
bool ec_loop;
/** 网络栈 */
Net pn;
std::unordered_set<uint256_t> valid_tls_certs; // 有效的 TLS 证书集合
#ifdef HOTSTUFF_BLK_PROFILE
BlockProfiler blk_profiler; // 区块分析器
#endif
pacemaker_bt pmaker; // PaceMaker 对象
/* 异步任务队列 */
std::unordered_map<const uint256_t, BlockFetchContext> blk_fetch_waiting; // 区块获取等待队列
std::unordered_map<const uint256_t, BlockDeliveryContext> blk_delivery_waiting; // 区块交付等待队列
std::unordered_map<const uint256_t, commit_cb_t> decision_waiting; // 决策等待队列
using cmd_queue_t = salticidae::MPSCQueueEventDriven<std::pair<uint256_t, commit_cb_t>>; // 命令队列类型
cmd_queue_t cmd_pending; // 命令等待队列
std::queue<uint256_t> cmd_pending_buffer; // 命令等待缓冲队列
/* 统计数据 */
uint64_t fetched; // 获取的区块数量
uint64_t delivered; // 交付的区块数量
mutable uint64_t nsent; // 发送的消息数量
mutable uint64_t nrecv; // 接收的消息数量
mutable uint32_t part_parent_size; // 部分父区块大小
mutable uint32_t part_fetched; // 部分获取的区块数量
mutable uint32_t part_delivered; // 部分交付的区块数量
mutable uint32_t part_decided; // 部分决策的区块数量
mutable uint32_t part_gened; // 部分生成的区块数量
mutable double part_delivery_time; // 部分交付时间
mutable double part_delivery_time_min; // 最小部分交付时间
mutable double part_delivery_time_max; // 最大部分交付时间
mutable std::unordered_map<const PeerId, uint32_t> part_fetched_replica; // 每个副本的部分获取区块数量
void on_fetch_cmd(const command_t &cmd); // 处理获取命令
void on_fetch_blk(const block_t &blk); // 处理获取区块
bool on_deliver_blk(const block_t &blk); // 处理交付区块
/** 处理共识消息:<提议> */
inline void propose_handler(MsgPropose &&, const Net::conn_t &);
/** 处理共识消息:<投票> */
inline void vote_handler(MsgVote &&, const Net::conn_t &);
/** 获取完整区块数据 */
inline void req_blk_handler(MsgReqBlock &&, const Net::conn_t &);
/** 接收一个区块 */
inline void resp_blk_handler(MsgRespBlock &&, const Net::conn_t &);
inline bool conn_handler(const salticidae::ConnPool::conn_t &, bool);
void do_broadcast_proposal(const Proposal &) override; // 广播提议
void do_vote(ReplicaID, const Vote &) override; // 投票
void do_decide(Finality &&) override; // 决策
void do_consensus(const block_t &blk) override; // 共识处理
protected:
/** 被调用以复制命令的执行,应用程序应实现此功能以进行应用程序状态转换。 */
virtual void state_machine_execute(const Finality &) = 0;
public:
HotStuffBase(uint32_t blk_size,
ReplicaID rid,
privkey_bt &&priv_key,
NetAddr listen_addr,
pacemaker_bt pmaker,
EventContext ec,
size_t nworker,
const Net::Config &netconfig);
~HotStuffBase();
/** HotStuffBase 的 API */
/** 提交待决定的命令。 */
void exec_command(uint256_t cmd_hash, commit_cb_t callback);
void start(std::vector<std::tuple<NetAddr, pubkey_bt, uint256_t>> &&replicas,
bool ec_loop = false);
size_t size() const { return peers.size(); }
const auto &get_decision_waiting() const { return decision_waiting; }
ThreadCall &get_tcall() { return tcall; }
PaceMaker *get_pace_maker() { return pmaker.get(); }
void print_stat() const;
virtual void do_elected() {}
//#ifdef HOTSTUFF_AUTOCLI
// virtual void do_demand_commands(size_t) {}
//#endif
/** 辅助函数 */
/** 返回在 Command 获取时已解决的 promise(包含 command_t cmd)。 */
promise_t async_fetch_cmd(const uint256_t &cmd_hash, const PeerId *replica, bool fetch_now = true);
/** 返回在 Block 获取时已解决的 promise(包含 block_t blk)。 */
promise_t async_fetch_blk(const uint256_t &blk_hash, const PeerId *replica, bool fetch_now = true);
/** 返回在 Block 交付时已解决的 promise(即前缀已获取,包含 block_t blk)。 */
promise_t async_deliver_blk(const uint256_t &blk_hash, const PeerId &replica);
};
1. 网络通信与处理
HotStuffBase
通过使用 PeerNetwork
实现了与其他节点的网络通信。该类处理了各种与共识相关的网络消息和事件:
Net pn
:HotStuffBase
类中的网络栈,用于处理网络通信。- 消息处理函数:
propose_handler
:处理提议消息(MsgPropose
):先检查提议者的id是否正确,然后获取提案信息与提案区块,接着异步交付(加入等待交付队列,只有交付后该队列中的promiss才会解决),交付后就完成接提议。
void HotStuffBase::propose_handler(MsgPropose &&msg, const Net::conn_t &conn) {
const PeerId &peer = conn->get_peer_id(); // 获取连接的 PeerId
if (peer.is_null()) return;
msg.postponed_parse(this); // 延迟解析消息
auto &prop = msg.proposal; // 获取提案
block_t blk = prop.blk;
if (!blk) return;
if (peer != get_config().get_peer_id(prop.proposer)) {//找存不存在这个副本id
LOG_WARN("invalid proposal from %d", prop.proposer);
return;
}
// 异步交付区块,并在完成后调用 on_receive_proposal
promise::all(std::vector<promise_t>{
async_deliver_blk(blk->get_hash(), peer)
}).then([this, prop = std::move(prop)]() {
on_receive_proposal(prop);
});
}
vote_handler
:处理投票消息(MsgVote
):先检查投票者id是否正确,然后异步检查区块交付情况以及验证投票(将验证任务加入验证池,然后确保投票中的区块与被投票的区块id相同),最后执行接受投票动作。
void HotStuffBase::vote_handler(MsgVote &&msg, const Net::conn_t &conn) {
const auto &peer = conn->get_peer_id(); // 获取连接的 PeerId
if (peer.is_null()) return;
msg.postponed_parse(this); // 延迟解析消息
RcObj<Vote> v(new Vote(std::move(msg.vote))); // 创建 Vote 对象
// 异步交付区块并验证投票
promise::all(std::vector<promise_t>{
async_deliver_blk(v->blk_hash, peer),
v->verify(vpool),
}).then([this, v=std::move(v)](const promise::values_t values) {
if (!promise::any_cast<bool>(values[1]))
LOG_WARN("invalid vote from %d", v->voter);
else
on_receive_vote(*v); // 处理接收到的投票
});
}
req_blk_handler
:处理区块请求消息(MsgReqBlock
):处理其它副本发送的区块请求信息,然后加入区块获取队列,获取完后将区块打包成MsgResBlock发送给副本。
void HotStuffBase::req_blk_handler(MsgReqBlock &&msg, const Net::conn_t &conn) {
const PeerId replica = conn->get_peer_id(); // 获取连接的 PeerId
if (replica.is_null()) return;
auto &blk_hashes = msg.blk_hashes; // 获取请求的区块哈希列表
std::vector<promise_t> pms;
for (const auto &h: blk_hashes)
pms.push_back(async_fetch_blk(h, nullptr)); // 异步获取每个区块
// 当所有区块获取完成后,发送响应消息
promise::all(pms).then([replica, this](const promise::values_t values) {
std::vector<block_t> blks;
for (auto &v: values) {
auto blk = promise::any_cast<block_t>(v);
blks.push_back(blk);
}
pn.send_msg(MsgRespBlock(blks), replica);
});
}
resp_blk_handler
:处理区块响应消息(MsgRespBlock
)。
void HotStuffBase::resp_blk_handler(MsgRespBlock &&msg, const Net::conn_t &) {
msg.postponed_parse(this); // 延迟解析消息
for (const auto &blk: msg.blks)
if (blk) on_fetch_blk(blk); // 处理每个获取到的区块
}
conn_handler
:处理连接事件:与TLS的启用有关
bool HotStuffBase::conn_handler(const salticidae::ConnPool::conn_t &conn, bool connected) {
if (connected) {
if (!pn.enable_tls) return true; // 如果不启用 TLS,则直接返回 true
auto cert = conn->get_peer_cert(); // 获取对等方证书
return valid_tls_certs.count(salticidae::get_hash(cert->get_der())); // 检查证书是否有效
}
return true;
}
- 广播和投票:
do_broadcast_proposal
:用于广播提议。
void HotStuffBase::do_broadcast_proposal(const Proposal &prop) {
pn.multicast_msg(MsgPropose(prop), peers); // 广播提案消息到所有对等节点
}
do_vote
:用于投票:beat_resp是pacemaker里的函数,它接受最后提议者的 ID,并通过 promise
将这个信息传递给调用者。然后根据自身是否为提议者来决定自己处理投票还是发送投票
void HotStuffBase::do_vote(ReplicaID last_proposer, const Vote &vote) {
pmaker->beat_resp(last_proposer).then([this, vote](ReplicaID proposer) {
if (proposer == get_id()) {
on_receive_vote(vote); // 如果自己是提议者,直接处理投票
} else {
pn.send_msg(MsgVote(vote), get_config().get_peer_id(proposer)); // 否则,发送投票消息给提议者
}
});
}
2. 区块和命令的获取与交付
该类管理从其他副本获取区块和命令的上下文,并处理这些数据的获取和交付:
blk_fetch_waiting
: 用于管理正在等待获取的区块。blk_delivery_waiting
: 用于管理正在等待交付的区块。decision_waiting
: 用于管理等待决策的区块或命令。- 函数:
on_fetch_cmd
和 on_fetch_blk
:处理命令和区块的获取。
void HotStuffBase::on_fetch_blk(const block_t &blk) {
#ifdef HOTSTUFF_BLK_PROFILE
// 如果启用了块性能分析,记录当前区块的传输信息
blk_profiler.get_tx(blk->get_hash());
#endif
// 输出调试信息,显示获取到的区块的哈希值
LOG_DEBUG("fetched %.10s", get_hex(blk->get_hash()).c_str());
part_fetched++; // 更新统计信息,表示已获取区块的数量增加
fetched++; // 更新统计信息,表示总共已获取的区块数量增加
const uint256_t &blk_hash = blk->get_hash(); // 获取区块的哈希值
auto it = blk_fetch_waiting.find(blk_hash); // 查找是否有等待该区块的请求
if (it != blk_fetch_waiting.end()) // 如果找到了对应的请求
{
it->second.resolve(blk); // 解决该请求,传递获取到的区块
blk_fetch_waiting.erase(it); // 从等待队列中移除已解决的请求
}
}
on_deliver_blk
:处理区块的交付:调用基类的交付函数交付区块并解决待交付队列中的promise。
bool HotStuffBase::on_deliver_blk(const block_t &blk) {
const uint256_t &blk_hash = blk->get_hash(); // 获取区块的哈希值
bool valid; // 记录区块是否有效
/* sanity check: all parents must be delivered */
for (const auto &p: blk->get_parent_hashes())
assert(storage->is_blk_delivered(p)); // 确保所有父区块都已交付
// 调用基类方法处理区块交付,并根据结果设置 valid
if ((valid = HotStuffCore::on_deliver_blk(blk)))
{
// 区块有效,记录调试信息
LOG_DEBUG("block %.10s delivered", get_hex(blk_hash).c_str());
part_parent_size += blk->get_parent_hashes().size(); // 更新父区块数量统计
part_delivered++; // 更新统计信息,表示已交付的区块数量增加
delivered++; // 更新统计信息,总共已交付的区块数量增加
}
else
{
// 区块无效,记录警告信息
LOG_WARN("dropping invalid block");
}
bool res = true; // 初始化返回值
auto it = blk_delivery_waiting.find(blk_hash); // 查找是否有等待该区块的交付请求
if (it != blk_delivery_waiting.end()) // 如果找到了对应的请求
{
auto &pm = it->second; // 获取请求上下文
if (valid)
{
// 区块有效,停止计时,并更新统计信息
pm.elapsed.stop(false);
auto sec = pm.elapsed.elapsed_sec;
part_delivery_time += sec;
part_delivery_time_min = std::min(part_delivery_time_min, sec);
part_delivery_time_max = std::max(part_delivery_time_max, sec);
pm.resolve(blk); // 解决请求,传递有效的区块
}
else
{
// 区块无效,拒绝请求
pm.reject(blk);
res = false; // 设置返回值为 false
// TODO: 是否需要从存储中释放无效区块?
}
blk_delivery_waiting.erase(it); // 从等待队列中移除已处理的请求
}
return res; // 返回区块是否有效的结果
}
async_fetch_cmd
和 async_fetch_blk
:异步获取命令和区块:将未获取的区块加入到待获取区块队列。
promise_t HotStuffBase::async_fetch_blk(const uint256_t &blk_hash,
const PeerId *replica,
bool fetch_now) {
if (storage->is_blk_fetched(blk_hash)) // 如果区块已经被获取,是否在缓存中
return promise_t([this, &blk_hash](promise_t pm){
pm.resolve(storage->find_blk(blk_hash)); // 立即解决 promise,返回已获取的区块
});
auto it = blk_fetch_waiting.find(blk_hash); // 查找是否有等待该区块的请求
if (it == blk_fetch_waiting.end()) // 如果没有找到
{
#ifdef HOTSTUFF_BLK_PROFILE
blk_profiler.rec_tx(blk_hash, false); // 如果启用了性能分析,记录区块传输信息
#endif
it = blk_fetch_waiting.insert(
std::make_pair(
blk_hash,
BlockFetchContext(blk_hash, this))).first; // 创建新的 BlockFetchContext 并插入等待队列
}
if (replica != nullptr)
it->second.add_replica(*replica, fetch_now); // 如果指定了副本,添加副本并决定是否立即发送请求
return static_cast<promise_t &>(it->second); // 返回与区块请求相关联的 promise 对象
}
async_deliver_blk
:异步交付区块:将未交付区块加入交付待区块队列
-
promise_t HotStuffBase::async_deliver_blk(const uint256_t &blk_hash,0 const PeerId &replica) { // 如果区块已经被交付,立即返回一个 resolved 的 promise if (storage->is_blk_delivered(blk_hash)) return promise_t([this, &blk_hash](promise_t pm) { pm.resolve(storage->find_blk(blk_hash)); }); auto it = blk_delivery_waiting.find(blk_hash); // 查找是否有等待该区块交付的请求 if (it != blk_delivery_waiting.end()) return static_cast<promise_t &>(it->second); // 如果找到了,直接返回 // 创建一个新的 BlockDeliveryContext,并插入 blk_delivery_waiting 队列 BlockDeliveryContext pm{[](promise_t){}}; it = blk_delivery_waiting.insert(std::make_pair(blk_hash, pm)).first; // 异步获取区块,如果成功获取到区块,继续处理 async_fetch_blk(blk_hash, &replica).then([this, replica](block_t blk) { std::vector<promise_t> pms; // 用于存储多个异步操作的 promise 对象 const auto &qc = blk->get_qc(); assert(qc); // 确保区块具有 QC if (blk == get_genesis()) pms.push_back(promise_t([](promise_t &pm){ pm.resolve(true); })); else pms.push_back(blk->verify(this, vpool)); // 验证区块 pms.push_back(async_fetch_blk(qc->get_obj_hash(), &replica)); // 获取 QC 引用的区块 for (const auto &phash: blk->get_parent_hashes()) pms.push_back(async_deliver_blk(phash, replica)); // 递归交付父区块 // 当所有异步操作完成时,继续处理 promise::all(pms).then([this, blk](const promise::values_t values) { auto ret = promise::any_cast<bool>(values[0]) && this->on_deliver_blk(blk); if (!ret) HOTSTUFF_LOG_WARN("verification failed during async delivery"); }); }); return static_cast<promise_t &>(pm); // 返回 BlockDeliveryContext 的 promise 对象 }
3. 状态管理与统计
该类追踪了与区块交付、获取、决策等相关的各种状态和统计数据:
- 统计字段:
fetched
:已获取的区块数量。delivered
:已交付的区块数量。nsent
和nrecv
:发送和接收的消息数量。part_parent_size
,part_fetched
,part_delivered
,part_decided
,part_gened
:与部分区块操作相关的统计。part_delivery_time
,part_delivery_time_min
,part_delivery_time_max
:与区块交付时间相关的统计。part_fetched_replica
:每个副本部分获取的区块数量。- 函数:
print_stat打印相关状态
void HotStuffBase::print_stat() const {
// 打印统计信息的开始
LOG_INFO("===== begin stats =====");
// 打印队列信息的标题
LOG_INFO("-------- queues -------");
// 打印等待获取区块的队列大小
LOG_INFO("blk_fetch_waiting: %lu", blk_fetch_waiting.size());
// 打印等待交付区块的队列大小
LOG_INFO("blk_delivery_waiting: %lu", blk_delivery_waiting.size());
// 打印等待决策的队列大小
LOG_INFO("decision_waiting: %lu", decision_waiting.size());
// 打印其他信息的标题
LOG_INFO("-------- misc ---------");
// 打印获取到的区块数量
LOG_INFO("fetched: %lu", fetched);
// 打印交付的区块数量
LOG_INFO("delivered: %lu", delivered);
// 打印命令缓存的大小
LOG_INFO("cmd_cache: %lu", storage->get_cmd_cache_size());
// 打印区块缓存的大小
LOG_INFO("blk_cache: %lu", storage->get_blk_cache_size());
// 打印过去 10 秒内的统计信息标题
LOG_INFO("------ misc (10s) -----");
// 打印过去 10 秒内获取的区块数量
LOG_INFO("fetched: %lu", part_fetched);
// 打印过去 10 秒内交付的区块数量
LOG_INFO("delivered: %lu", part_delivered);
// 打印过去 10 秒内决策的数量
LOG_INFO("decided: %lu", part_decided);
// 打印过去 10 秒内生成的数量
LOG_INFO("gened: %lu", part_gened);
// 打印过去 10 秒内的平均父区块大小,如果没有交付的区块,则结果为 0
LOG_INFO("avg. parent_size: %.3f",
part_delivered ? part_parent_size / double(part_delivered) : 0);
// 打印过去 10 秒内的交付时间:平均、最小和最大时间
LOG_INFO("delivery time: %.3f avg, %.3f min, %.3f max",
part_delivered ? part_delivery_time / double(part_delivered) : 0,
part_delivery_time_min == double_inf ? 0 : part_delivery_time_min,
part_delivery_time_max);
// 重置统计数据,以便下一个周期的统计
part_parent_size = 0;
part_fetched = 0;
part_delivered = 0;
part_decided = 0;
part_gened = 0;
part_delivery_time = 0;
part_delivery_time_min = double_inf;
part_delivery_time_max = 0;
#ifdef HOTSTUFF_MSG_STAT
// 打印副本消息统计信息的标题
LOG_INFO("--- replica msg. (10s) ---");
size_t _nsent = 0; // 记录过去 10 秒内发送的消息数量
size_t _nrecv = 0; // 记录过去 10 秒内接收的消息数量
// 遍历所有副本,获取每个副本的消息统计信息
for (const auto &replica: peers) {
auto conn = pn.get_peer_conn(replica); // 获取与副本的连接
if (conn == nullptr) continue; // 如果连接为空,跳过
size_t ns = conn->get_nsent(); // 获取发送的消息数量
size_t nr = conn->get_nrecv(); // 获取接收的消息数量
size_t nsb = conn->get_nsentb(); // 获取发送的字节数
size_t nrb = conn->get_nrecvb(); // 获取接收的字节数
conn->clear_msgstat(); // 清除消息统计数据
// 打印副本的消息统计信息,包括消息数量和字节数
LOG_INFO("%s: %u(%u), %u(%u), %u",
get_hex10(replica).c_str(), ns, nsb, nr, nrb, part_fetched_replica[replica]);
// 累加发送和接收的消息数量
_nsent += ns;
_nrecv += nr;
// 重置每个副本的部分获取区块数量
part_fetched_replica[replica] = 0;
}
// 更新总发送和接收的消息数量
nsent += _nsent;
nrecv += _nrecv;
// 打印过去 10 秒内发送和接收的消息数量
LOG_INFO("sent: %lu", _nsent);
LOG_INFO("recv: %lu", _nrecv);
// 打印所有时间内的总发送和接收的消息数量
LOG_INFO("--- replica msg. total ---");
LOG_INFO("sent: %lu", nsent);
LOG_INFO("recv: %lu", nrecv);
#endif
// 打印统计信息的结束
LOG_INFO("====== end stats ======");
}
4. 异步任务管理
该类使用 cmd_pending
和 cmd_pending_buffer
来管理异步任务的排队与执行,确保命令能够有序地被处理。
void HotStuffBase::exec_command(uint256_t cmd_hash, commit_cb_t callback) {
// 将命令哈希和对应的提交回调函数加入待处理队列 cmd_pending 中
/*using cmd_queue_t = salticidae::MPSCQueueEventDriven<std::pair<uint256_t, commit_cb_t>>;
cmd_queue_t cmd_pending; */
cmd_pending.enqueue(std::make_pair(cmd_hash, callback));
}
5. 共识状态机执行
作为 HotStuff 共识协议的一部分,HotStuffBase
提供了一些虚函数和接口,以便在具体实现中扩展和实现具体的共识逻辑:
- 共识与决策函数:
do_consensus
:处理区块共识。
void HotStuffBase::do_consensus(const block_t &blk) {
pmaker->on_consensus(blk); // 处理共识事件
}
do_decide
:处理最终决策。
void HotStuffBase::do_decide(Finality &&fin) {
part_decided++; // 更新统计数据
state_machine_execute(fin); // 执行状态机决策
auto it = decision_waiting.find(fin.cmd_hash);
if (it != decision_waiting.end()) {
it->second(std::move(fin)); // 执行决策回调
decision_waiting.erase(it); // 从等待队列中移除
}
}
- 状态机执行:
state_machine_execute
:一个纯虚函数,要求派生类实现用于应用程序状态转换的命令执行逻辑。
6. 事件与线程管理
该类管理事件循环和线程调用,用于处理异步事件和并发操作:
EventContext ec
: 事件上下文,用于管理事件循环。salticidae::ThreadCall tcall
: 线程调用对象,允许在不同线程中调度任务。
7. PaceMaker 的集成(会在第四章展开分析)
HotStuffBase
集成了 PaceMaker
,用于管理共识协议的节奏和调度:
pmaker
:PaceMaker
对象的智能指针,用于协调共识过程中的提议和投票。
8. 回调机制
HotStuffBase
提供了回调机制以处理最终决策后的状态变化:
commit_cb_t
: 定义了一个回调类型,当区块或命令达成最终性时会调用该回调函数。
事例(仅参考,非源代码)
class Finality {
public:
std::string info;
Finality(std::string i) : info(i) {}
};
using commit_cb_t = std::function<void(const Finality &)>;
void onCommit(const Finality &finality) {
std::cout << "Block committed with finality: " << finality.info << std::endl;
}
void processBlock(commit_cb_t callback) {
// 模拟区块处理并最终达成共识
Finality finality("Block #42 has reached finality");
// 达到最终性后,调用回调函数
callback(finality);
}
int main() {
// 传递回调函数
processBlock(onCommit);
return 0;
}
在 main
函数中,我们将 onCommit
回调函数传递给 processBlock
,然后 processBlock
在区块处理完成后调用 onCommit
,输出最终性信息。
9. 启动与运行
该类提供了接口用于初始化和启动协议实例:
- 构造函数
HotStuffBase
: 进行各种成员的初始化。
HotStuffBase::HotStuffBase(uint32_t blk_size,
ReplicaID rid,
privkey_bt &&priv_key,
NetAddr listen_addr,
pacemaker_bt pmaker,
EventContext ec,
size_t nworker,
const Net::Config &netconfig):
HotStuffCore(rid, std::move(priv_key)), // 调用基类 HotStuffCore 的构造函数
listen_addr(listen_addr), // 初始化监听地址
blk_size(blk_size), // 初始化块大小
ec(ec), // 初始化事件上下文
tcall(ec), // 初始化线程调用
vpool(ec, nworker), // 初始化验证池
pn(ec, netconfig), // 初始化对等网络
pmaker(std::move(pmaker)), // 初始化 PaceMaker
fetched(0), delivered(0), // 初始化统计数据
nsent(0), nrecv(0),
part_parent_size(0),
part_fetched(0),
part_delivered(0),
part_decided(0),
part_gened(0),
part_delivery_time(0),
part_delivery_time_min(double_inf),
part_delivery_time_max(0){
// 注册处理消息的处理程序
pn.reg_handler(salticidae::generic_bind(&HotStuffBase::propose_handler, this, _1, _2));
pn.reg_handler(salticidae::generic_bind(&HotStuffBase::vote_handler, this, _1, _2));
pn.reg_handler(salticidae::generic_bind(&HotStuffBase::req_blk_handler, this, _1, _2));
pn.reg_handler(salticidae::generic_bind(&HotStuffBase::resp_blk_handler, this, _1, _2));
pn.reg_conn_handler(salticidae::generic_bind(&HotStuffBase::conn_handler, this, _1, _2));
pn.reg_error_handler([](const std::exception_ptr _err, bool fatal, int32_t async_id) {
try {
std::rethrow_exception(_err); // 抛出捕获的异常
} catch (const std::exception &err) {
HOTSTUFF_LOG_WARN("network async error: %s\n", err.what());
}
});
pn.start(); // 启动对等网络
pn.listen(listen_addr); // 监听指定地址
}
start
: 启动协议实例,初始化网络连接并进入事件循环。
void HotStuffBase::start(
std::vector<std::tuple<NetAddr, pubkey_bt, uint256_t>> &&replicas, // 副本信息,包含网络地址、公钥和证书哈希
bool ec_loop) { // 是否启动事件循环
// 遍历所有副本信息
for (size_t i = 0; i < replicas.size(); i++) {
auto &addr = std::get<0>(replicas[i]); // 获取副本的网络地址
auto cert_hash = std::move(std::get<2>(replicas[i])); // 获取副本的证书哈希,并移动到 local 变量中
valid_tls_certs.insert(cert_hash); // 添加有效的 TLS 证书到有效证书集合中
// 根据是否启用 TLS 选择 PeerId 生成方式
auto peer = pn.enable_tls ? salticidae::PeerId(cert_hash) : salticidae::PeerId(addr);
// 添加副本到 HotStuffCore 中
HotStuffCore::add_replica(i, peer, std::move(std::get<1>(replicas[i]))); // i 为副本 ID,peer 为对等节点标识符,公钥用于身份验证
if (addr != listen_addr) { // 如果副本地址不是当前监听地址
peers.push_back(peer); // 将副本的 PeerId 添加到对等节点列表
pn.add_peer(peer); // 将对等节点添加到 PeerNetwork 中
pn.set_peer_addr(peer, addr); // 设置对等节点的网络地址
pn.conn_peer(peer); // 连接对等节点
}
}
// 计算允许的故障节点数
uint32_t nfaulty = peers.size() / 3; // 允许的故障节点数是总对等节点数除以 3
if (nfaulty == 0)
LOG_WARN("too few replicas in the system to tolerate any failure"); // 如果允许的故障节点数为 0,输出警告信息
on_init(nfaulty); // 调用初始化函数,传入允许的故障节点数
pmaker->init(this); // 初始化 PaceMaker,并将当前对象作为参数传入
// 如果需要事件循环,则启动事件循环
if (ec_loop)
ec.dispatch(); // 启动事件循环,开始处理事件
// 注册命令处理程序,用于处理待处理的命令队列
cmd_pending.reg_handler(ec, [this](cmd_queue_t &q) {
std::pair<uint256_t, commit_cb_t> e; // 定义事件对,包含命令哈希和提交回调
while (q.try_dequeue(e)) { // 从队列中尝试出队命令
ReplicaID proposer = pmaker->get_proposer(); // 获取当前的提议者副本 ID
const auto &cmd_hash = e.first; // 获取命令哈希
auto it = decision_waiting.find(cmd_hash); // 查找命令哈希在决策等待集合中的位置
if (it == decision_waiting.end()) // 如果命令哈希不在集合中
it = decision_waiting.insert(std::make_pair(cmd_hash, e.second)).first; // 将命令哈希和回调插入集合中
else
e.second(Finality(id, 0, 0, 0, cmd_hash, uint256_t())); // 如果命令哈希已存在,则立即调用回调,表示命令未决
if (proposer != get_id()) continue; // 如果当前副本不是提议者,则继续下一个命令
cmd_pending_buffer.push(cmd_hash); // 将命令哈希推入待处理缓冲区
if (cmd_pending_buffer.size() >= blk_size) { // 如果待处理缓冲区的大小达到了块大小
std::vector<uint256_t> cmds; // 创建命令向量
for (uint32_t i = 0; i < blk_size; i++) { // 从缓冲区中取出块大小个命令
cmds.push_back(cmd_pending_buffer.front()); // 获取缓冲区前端的命令哈希并添加到命令向量
cmd_pending_buffer.pop(); // 从缓冲区中移除该命令
}
pmaker->beat().then([this, cmds = std::move(cmds)](ReplicaID proposer) { // 提交一个节奏信号,并在信号处理完成后执行回调
if (proposer == get_id()) // 如果当前副本是提议者
on_propose(cmds, pmaker->get_parents()); // 调用提议函数,传入命令向量和父节点信息
});
return true; // 成功处理了命令
}
}
return false; // 处理未成功,队列为空或无更多命令
});
}
- 遍历副本信息:函数首先遍历传入的
replicas
列表,这个列表包含了所有副本的网络地址、公钥和证书哈希。 - 处理每个副本:
- 获取每个副本的网络地址 (
addr
) 和证书哈希 (cert_hash
)。 - 根据是否启用了 TLS,选择不同的方式生成
PeerId
。 - 将每个副本信息(包括
PeerId
和公钥)添加到HotStuffCore
中。 - 如果副本的地址与当前节点的监听地址不同(即副本不是当前节点),则将副本添加到
peers
列表,设置网络地址并尝试连接。
- 获取每个副本的网络地址 (
- 计算允许的故障节点数:允许的故障节点数等于总副本节点数除以 3,这是分布式系统中通常的拜占庭容错系数。
- 检查故障容忍度:如果允许的故障节点数为 0(即副本数量不足以容忍任何故障),系统会发出警告。
- 调用初始化函数:将允许的故障节点数传递给
on_init,上半章有说,用于
解决协议中初始区块b0的qc证书、引用以及协议中最高QC(最高QC是协议在网络中判断是否接受区块或信息的其中一个要求)的初始化。 - 初始化 PaceMaker:调用
PaceMaker
的init
方法,将当前对象 (this
) 作为参数传入。PaceMaker
是 HotStuff 协议中用于协调节奏(节拍)的核心组件。 - 启动事件循环:如果
ec_loop
为true
,则启动事件循环。事件循环用于处理网络事件、计时器事件等。 - 命令处理程序注册:将一个处理函数注册为
cmd_pending
队列的处理程序。当队列中有新命令时,这个函数会被调用。 - 处理命令:
- 从队列中取出命令哈希和回调函数。
- 获取当前提议者的 ID,并检查命令是否已经在决策等待队列中。如果不在,则添加;否则,直接执行回调。
- 如果当前副本是提议者,将命令哈希放入待处理缓冲区,并在缓冲区达到块大小时创建一个新的区块。
- 节奏处理:通过调用
pmaker->beat()
发出节奏信号,并在信号处理完成后检查当前节点是否为提议者。如果当前副本是提议者,则调用on_propose
函数提出新块。
加密实现的Hotstuff
/** HotStuff 协议(由加密实现模板化)。 */
template<typename PrivKeyType = PrivKeyDummy,
typename PubKeyType = PubKeyDummy,
typename PartCertType = PartCertDummy,
typename QuorumCertType = QuorumCertDummy>
class HotStuff: public HotStuffBase {
using HotStuffBase::HotStuffBase; // 继承 HotStuffBase 的构造函数
protected:
/** 创建部分证书 */
part_cert_bt create_part_cert(const PrivKey &priv_key, const uint256_t &blk_hash) override {
HOTSTUFF_LOG_DEBUG("create part cert with priv=%s, blk_hash=%s",
get_hex10(priv_key).c_str(), get_hex10(blk_hash).c_str());
// 创建并返回一个新的 PartCertType 对象
return new PartCertType(
static_cast<const PrivKeyType &>(priv_key),
blk_hash);
}
/** 解析部分证书 */
part_cert_bt parse_part_cert(DataStream &s) override {
// 创建一个新的 PartCertType 对象
PartCert *pc = new PartCertType();
// 从数据流中读取部分证书
s >> *pc;
return pc; // 返回部分证书对象
}
/** 创建法定证书 */
quorum_cert_bt create_quorum_cert(const uint256_t &blk_hash) override {
// 创建并返回一个新的 QuorumCertType 对象
return new QuorumCertType(get_config(), blk_hash);
}
/** 解析法定证书 */
quorum_cert_bt parse_quorum_cert(DataStream &s) override {
// 创建一个新的 QuorumCertType 对象
QuorumCert *qc = new QuorumCertType();
// 从数据流中读取法定证书
s >> *qc;
return qc; // 返回法定证书对象
}
public:
// 构造函数,初始化各种成员变量
HotStuff(uint32_t blk_size,
ReplicaID rid,
const bytearray_t &raw_privkey,
NetAddr listen_addr,
pacemaker_bt pmaker,
EventContext ec = EventContext(),
size_t nworker = 4,
const Net::Config &netconfig = Net::Config()):
HotStuffBase(blk_size,
rid,
new PrivKeyType(raw_privkey),
listen_addr,
std::move(pmaker),
ec,
nworker,
netconfig) {}
// 启动函数,启动共识协议
void start(const std::vector<std::tuple<NetAddr, bytearray_t, bytearray_t>> &replicas, bool ec_loop = false) {
std::vector<std::tuple<NetAddr, pubkey_bt, uint256_t>> reps;
for (auto &r: replicas)
reps.push_back(
std::make_tuple(
std::get<0>(r), // 获取副本的网络地址
new PubKeyType(std::get<1>(r)), // 创建公钥
uint256_t(std::get<2>(r)) // 获取副本的哈希值
));
HotStuffBase::start(std::move(reps), ec_loop); // 调用基类的启动函数
}
};
// 定义不使用签名的 HotStuff 实现
using HotStuffNoSig = HotStuff<>;
// 定义使用 Secp256k1 签名的 HotStuff 实现
using HotStuffSecp256k1 = HotStuff<PrivKeySecp256k1, PubKeySecp256k1,
PartCertSecp256k1, QuorumCertSecp256k1>;