hotstuff源码分析(二)

1.前言

上一章分析了源代码中的config(日志与宏)、util(区块分析器)、task(任务、任务池)、type(辅助工具:克隆、错误基类)、crypto(椭圆曲线加密)五个文件部分。这一章我们深入探讨entitypromiss

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;
}
  • p1p2 是两个独立的 Promise,它们分别解析为 1020
  • 使用 promise::all 等待这两个 Promise 完成,并将它们的结果作为一个向量传递给 then 方法。
  • then 方法计算结果的总和并输出
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值