hotstuff源码分析(三)(下)

目录

1.前言

2.源码分析

propose网络消息封装

vote网络消息封装

block网络消息封装

消息获取器FetchContext

 区块交付处理器BlockDeliveryContext

带网络实现HotstuffBase


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_cmdon_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_cmdasync_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:已交付的区块数量。
    • nsentnrecv:发送和接收的消息数量。
    • 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_pendingcmd_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:调用 PaceMakerinit 方法,将当前对象 (this) 作为参数传入。PaceMaker 是 HotStuff 协议中用于协调节奏(节拍)的核心组件。
  • 启动事件循环:如果 ec_looptrue,则启动事件循环。事件循环用于处理网络事件、计时器事件等。
  • 命令处理程序注册:将一个处理函数注册为 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>;

  • 27
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值