Hotstuff源码分析(六)(demo实验)

目录

1.前言

2.源码分析

 环境准备

启动文件

hotstuff_app

hotstuff_client

实验结果


1.前言

前4章笔者学习分析了hotstuff底层源码及相关逻辑,但是还是会觉得不同的模块逻辑很孤立,这一章笔者尝试从源码提供的demo实验入手,逐步去理解hotstuff的执行,笔者会尽量还原逻辑。

2.源码分析

 环境准备

# ensure openssl and libevent are installed on your machine, more
    # specifically, you need:
    #
    # CMake >= 3.9 (cmake)
    # C++14 (g++)
    # libuv >= 1.10.0 (libuv1-dev)//加密和通信安全库
    # openssl >= 1.1.0 (libssl-dev)//异步I/O和事件驱动库
    #
    # on Ubuntu: sudo apt-get install libssl-dev libuv1-dev cmake make

启动文件

    # start 4 demo replicas with scripts/run_demo.sh
    # then, start the demo client with scripts/run_demo_client.sh

run_demo.sh

#!/bin/bash
# 定义一个包含 0 到 3 的数组,表示默认的副本编号
rep=({0..3})

# 如果脚本的命令行参数个数大于 0,则使用传入的参数作为副本编号
if [[ $# -gt 0 ]]; then
    rep=($@)
fi

# 设定无限制的栈大小,避免在简单演示中因引导副本产生的长承诺解析链导致栈溢出
ulimit -s unlimited

# 循环启动指定编号的副本
for i in "${rep[@]}"; do
    echo "starting replica $i" # 输出正在启动的副本编号
    # 使用 valgrind 进行内存泄漏检查并启动 hotstuff 应用,结果保存到 log 文件中
    #valgrind --leak-check=full ./examples/hotstuff-app --conf hotstuff-sec${i}.conf > log${i} 2>&1 &
    
    # 使用 gdb 进行调试并启动 hotstuff 应用,获取回溯信息,结果保存到 log 文件中
    #gdb -ex r -ex bt -ex q --args ./examples/hotstuff-app --conf hotstuff-sec${i}.conf > log${i} 2>&1 &
    
    # 正常启动 hotstuff 应用,并将输出保存到对应的 log 文件中
    ./examples/hotstuff-app --conf ./hotstuff-sec${i}.conf > log${i} 2>&1 &
done

# 等待所有后台进程结束
wait
  • 该脚本首先定义了一个包含默认副本编号(0 到 3)的数组。
  • 如果脚本执行时传入了命令行参数,这些参数将覆盖默认的副本编号。
  • ulimit -s unlimited 命令用来设置栈大小为无限制,以防止栈溢出。
  • 脚本使用 for 循环遍历每个副本编号,依次启动对应的 HotStuff 应用程序,并将其输出保存到日志文件中。
  • 注释掉的 valgrindgdb 命令行用于调试和内存泄漏检查。
  • 脚本最后使用 wait 命令等待所有后台进程结束,确保所有副本都正常启动和运行。

run_demo_client.sh

#!/bin/bash
# 尝试像在 run_demo.sh 中那样首先运行副本,然后运行 run_demo_client.sh。
# 使用 Ctrl-C 终止提议的副本(例如副本 0)。这将触发领导者轮换。
# 尝试杀死进程并再次运行 run_demo_client.sh,一旦新的领导者变得稳定,新命令应该仍然能够通过(被复制)。

# 运行 hotstuff 客户端,设置索引为 0,并无限次迭代 (--iter -1),
# 允许最多 4 个异步请求同时进行 (--max-async 4)。
./examples/hotstuff-client --idx 0 --iter -1 --max-async 4
  • 脚本旨在演示 HotStuff 共识协议中的领导者轮换机制。
  • 在运行副本后,运行此客户端脚本,启动 HotStuff 客户端与指定的副本进行通信。
  • 脚本默认连接的副本索引为 0,并且设置为无限次迭代请求(--iter -1),允许最多 4 个异步请求同时进行(--max-async 4)。
  • 通过手动终止提议的副本(例如使用 Ctrl-C),可以触发领导者轮换过程。
  • 在领导者轮换后,重新运行客户端,新的命令应该仍然可以成功复制到其他副本上,确保系统的容错性。

hotstuff_app

run_demo.sh文件中会启动hotstuff-app(执行文件)并传递了参数 --conf ./hotstuff-sec${i}.conf。我们先来看这个conf文件,里面包括了私钥,tls私钥、tls证书和idx,当传参给执行文件时会把这个文件的内容传进去。

privkey = 445fa01dbbb9d0510ab6d6f630c94ab43ba21ad91f31d47da92f7b679ba2f582
tls-privkey = 308204a10201000282010100c5a358cee61ca7592dbe62cd28225426dc3fc65b33432043efb5e8fcfaf334720b2000d10c422172e6f94f82746928a6d51193342a1555684eedb141321170cb02cc7b189f5506cc30cb5c26d9b84123328ed6df61fc1a6582b046def7a662bb15d84e29ffa279f1d853c25ca79fbe3fab0200f8c85566a5eedb98df4a958e8512a647258e55a7a56c1ca8b05607b38b01b6a2669af9a70efa59141f6079341c6044f615687be756d4830ea359139ea35e23f34ce226215a1a2fb8271cc60a037841e44c460e149a71d3e2fa907a48677ad18af2e7348343932f5f29ac5241cd20ce64e6798ada684164cd1109c3a969662d9aaf9897fedc5a4e256eb92bf7bd02011102820100516160cdaa0bcc7003c6dd6388ff139787de0661c9d0589471c35fefb2a060e3aa3a5ab06e75954d6e2a6c088a496b1784e91e7ee426e6eeb7169448058eb5f93d6341bed83211db9b9f07d3c30fa259c9861c3ddd0d7447ea84d1e356ea28a7635911205a33d7dc0dc822dadb9c2129466a3ca2acd7def9080011c55af249bd98e3f84a5475a43cc0369cf7fa5e4ecdd1c09ac0b0a41bd19d8af70cad8f1b6aec66a22179e5dbf9c446c645e9daa9080b455fca64365d8e02a686fbd707198302db43cd37629033574561c025d89d29c393746ce12fa01bbe60feb3fab292928ead3f8f6def6b9a7dffcd2139f6e8d1f79844d216b28dfae29dc6ba8c11063102818100f770f6dea12236dd39cb46ba96c171f3cfa92c961a12bfb62108e82e0bac280554a9ad4b99c4faee9287467573eaf6cfcba360753668c40ab1b015fbbb95f6a2d3c2402966971fa54540befdc39b58962f7d0cfaa96ff268df02efab32238544ed75fd6586bd93e38934c9d59b037f3cd7f727b38c854e1061de1f68bf43a26d02818100cc7963002015bd593af15276f185cb5a34a76bb36633c85bc86c6640419612a5a8eb2cc1a6ad43e51c2b545f88a7e8c0096a6110b85de7d1f458eca33267f9fde6b4489e346304787d33c8e1033e96b502a53ea6ad2c55299cae973719587d24468f164930f58a56aac2c7eabd68870a1056f06b8bcec5b3d2156c8a1375d89102818048c6df326ba0a6b9897805be68933fa20fe676868023a1cc27d57176f45fcf8918e69c61879449cdb2a041e64f451b6a4af3d1136a5b0c7b9dac42b3736857994d57400c2d3b81c7327c7468c10f9286867012e04ff3bfc47dd3afe70ebf273263f586c381fb85d982b52c4de24c52996cb21abc56818f6e3ae6fa2ddde6b74d028180180e47e1e5a83464d9c209b3a3f19f740631d06f756f80fbbd39ede97120b6e6501baae99b2371663f8ca083b5b966ad2e48c02015b0b1dc77198540604877c3848dae30bade78ff1dc9db65c4257b245aaa075ee732645f3f9c11ca3f37964080c58a26ba773d739b9e71df6193d3a6d4beef1bb618537e912fb26a98e0b01102818100950d7d5ec366f8bca918011359590c6e7d6fe4d3e8922d68468c3e24277f27621db17cb41d0dcddf1f77e289e49dc92101d8544e84281ec7732fbfd4e9194924a2adac4e22ef79c2ad61428f1fa03e6affa3b0de07f857de87a2e758953122b0d693cf49e54ede4eba44dcdc2c5cbd6751af5117fd107fc0e4ffb18f8774d79e
tls-cert = 308202e4308201cc020101300d06092a864886f70d01010505003039310b300906035504061302555331163014060355040a0c0d6c696273616c746963696461653112301006035504030c096c6f63616c686f7374301e170d3139303730323036353535365a170d3139303730323036353535365a3039310b300906035504061302555331163014060355040a0c0d6c696273616c746963696461653112301006035504030c096c6f63616c686f737430820120300d06092a864886f70d01010105000382010d00308201080282010100c5a358cee61ca7592dbe62cd28225426dc3fc65b33432043efb5e8fcfaf334720b2000d10c422172e6f94f82746928a6d51193342a1555684eedb141321170cb02cc7b189f5506cc30cb5c26d9b84123328ed6df61fc1a6582b046def7a662bb15d84e29ffa279f1d853c25ca79fbe3fab0200f8c85566a5eedb98df4a958e8512a647258e55a7a56c1ca8b05607b38b01b6a2669af9a70efa59141f6079341c6044f615687be756d4830ea359139ea35e23f34ce226215a1a2fb8271cc60a037841e44c460e149a71d3e2fa907a48677ad18af2e7348343932f5f29ac5241cd20ce64e6798ada684164cd1109c3a969662d9aaf9897fedc5a4e256eb92bf7bd020111300d06092a864886f70d0101050500038201010009fc34f2835e4c14de1e03202c0f0884acb51e7568b286a9b3a374e8b69f304600a2787303cd382e52923db352cf91587ea7a33da5116c886c67775b5f96357971dddb568948fbb12987523a8be0e2e537284f8ee591af17c6d58410e41d32fc634f99289089915fb4273feaaca45d3442820155f5014fbd5d19fef7954729e4439218cad6a83b9dcd21834aaec379a276f163599cdac6b9513d0c07310852bb4104a58141a158d302908c23761899a4c68c3bf81115f302d41f42ff38150c0b7dc798fd7319abec6bd9365e00bf0a0998c3b97fe4834f0688e95d1eea4df4031274c781c6661cab085d629cb924c5c65b865ae98aa2349875fafe62b7eacbc5
idx = 0

main函数

int main(int argc, char **argv) {
    // 创建一个 Config 对象,指定配置文件名为 "hotstuff.conf"
    Config config("hotstuff.conf");

    // 创建一个 ElapsedTime 对象,并开始计时
    ElapsedTime elapsed;
    elapsed.start();

    // 定义并初始化所有的配置选项
    auto opt_blk_size = Config::OptValInt::create(1); // 块大小,默认值为 1
    auto opt_parent_limit = Config::OptValInt::create(-1); // 父节点限制,默认值为 -1(无限制)
    auto opt_stat_period = Config::OptValDouble::create(10); // 统计周期,默认值为 10 秒
    auto opt_replicas = Config::OptValStrVec::create(); // 复制节点列表
    auto opt_idx = Config::OptValInt::create(0); // 当前节点索引,默认值为 0
    auto opt_client_port = Config::OptValInt::create(-1); // 客户端端口,默认值为 -1(未指定)
    auto opt_privkey = Config::OptValStr::create(); // 私钥文件路径
    auto opt_tls_privkey = Config::OptValStr::create(); // TLS 私钥文件路径
    auto opt_tls_cert = Config::OptValStr::create(); // TLS 证书文件路径
    auto opt_help = Config::OptValFlag::create(false); // 帮助标志,默认值为 false
    auto opt_pace_maker = Config::OptValStr::create("dummy"); // 节奏控制器类型,默认值为 "dummy"
    auto opt_fixed_proposer = Config::OptValInt::create(1); // 固定提议者的 ID,默认值为 1
    auto opt_base_timeout = Config::OptValDouble::create(1); // 基本超时时间,默认值为 1 秒
    auto opt_prop_delay = Config::OptValDouble::create(1); // 提议延迟时间,默认值为 1 秒
    auto opt_imp_timeout = Config::OptValDouble::create(11); // 弹劾超时时间,默认值为 11 秒
    auto opt_nworker = Config::OptValInt::create(1); // 验证线程数量,默认值为 1
    auto opt_repnworker = Config::OptValInt::create(1); // 复制节点网络线程数量,默认值为 1
    auto opt_repburst = Config::OptValInt::create(100); // 复制节点网络的突发大小,默认值为 100
    auto opt_clinworker = Config::OptValInt::create(8); // 客户端网络线程数量,默认值为 8
    auto opt_cliburst = Config::OptValInt::create(1000); // 客户端网络的突发大小,默认值为 1000
    auto opt_notls = Config::OptValFlag::create(false); // 是否禁用 TLS,默认值为 false
    auto opt_max_rep_msg = Config::OptValInt::create(4 << 20); // 最大复制节点消息大小,默认值为 4MB
    auto opt_max_cli_msg = Config::OptValInt::create(65536); // 最大客户端消息大小,默认值为 64KB

    // 添加配置选项到 Config 对象中
    config.add_opt("block-size", opt_blk_size, Config::SET_VAL); // 设置块大小
    config.add_opt("parent-limit", opt_parent_limit, Config::SET_VAL); // 设置父节点限制
    config.add_opt("stat-period", opt_stat_period, Config::SET_VAL); // 设置统计周期
    config.add_opt("replica", opt_replicas, Config::APPEND, 'a', "add an replica to the list"); // 添加复制节点
    config.add_opt("idx", opt_idx, Config::SET_VAL, 'i', "specify the index in the replica list"); // 指定复制节点索引
    config.add_opt("cport", opt_client_port, Config::SET_VAL, 'c', "specify the port listening for clients"); // 指定客户端端口
    config.add_opt("privkey", opt_privkey, Config::SET_VAL); // 设置私钥文件路径
    config.add_opt("tls-privkey", opt_tls_privkey, Config::SET_VAL); // 设置 TLS 私钥文件路径
    config.add_opt("tls-cert", opt_tls_cert, Config::SET_VAL); // 设置 TLS 证书文件路径
    config.add_opt("pace-maker", opt_pace_maker, Config::SET_VAL, 'p', "specify pace maker (dummy, rr)"); // 指定节奏控制器类型
    config.add_opt("proposer", opt_fixed_proposer, Config::SET_VAL, 'l', "set the fixed proposer (for dummy)"); // 设置固定提议者 ID
    config.add_opt("base-timeout", opt_base_timeout, Config::SET_VAL, 't', "set the initial timeout for the Round-Robin Pacemaker"); // 设置基本超时时间
    config.add_opt("prop-delay", opt_prop_delay, Config::SET_VAL, 't', "set the delay that follows the timeout for the Round-Robin Pacemaker"); // 设置提议延迟时间
    config.add_opt("imp-timeout", opt_imp_timeout, Config::SET_VAL, 'u', "set impeachment timeout (for sticky)"); // 设置弹劾超时时间
    config.add_opt("nworker", opt_nworker, Config::SET_VAL, 'n', "the number of threads for verification"); // 设置验证线程数量
    config.add_opt("repnworker", opt_repnworker, Config::SET_VAL, 'm', "the number of threads for replica network"); // 设置复制节点网络线程数量
    config.add_opt("repburst", opt_repburst, Config::SET_VAL, 'b', ""); // 设置复制节点网络的突发大小
    config.add_opt("clinworker", opt_clinworker, Config::SET_VAL, 'M', "the number of threads for client network"); // 设置客户端网络线程数量
    config.add_opt("cliburst", opt_cliburst, Config::SET_VAL, 'B', ""); // 设置客户端网络的突发大小
    config.add_opt("notls", opt_notls, Config::SWITCH_ON, 's', "disable TLS"); // 禁用 TLS
    config.add_opt("max-rep-msg", opt_max_rep_msg, Config::SET_VAL, 'S', "the maximum replica message size"); // 设置最大复制节点消息大小
    config.add_opt("max-cli-msg", opt_max_cli_msg, Config::SET_VAL, 'S', "the maximum client message size"); // 设置最大客户端消息大小
    config.add_opt("help", opt_help, Config::SWITCH_ON, 'h', "show this help info"); // 显示帮助信息

    // 创建一个 EventContext 对象,用于事件处理
    EventContext ec;

    // 解析命令行参数
    config.parse(argc, argv);

    // 如果帮助标志被设置,则打印帮助信息并退出
    if (opt_help->get()) {
        config.print_help();
        exit(0);
    }

    // 获取命令行参数中指定的复制节点索引和客户端端口
    auto idx = opt_idx->get();
    auto client_port = opt_client_port->get();

    // 解析复制节点列表
    std::vector<std::tuple<std::string, std::string, std::string>> replicas;
    for (const auto &s: opt_replicas->get()) {
        auto res = trim_all(split(s, ","));
        if (res.size() != 3)
            throw HotStuffError("invalid replica info"); // 如果格式不正确,抛出异常
        replicas.push_back(std::make_tuple(res[0], res[1], res[2])); // 将解析结果存储到 replicas 向量中
    }

    // 验证复制节点索引是否有效
    if (!(0 <= idx && (size_t)idx < replicas.size()))
        throw HotStuffError("replica idx out of range");

    // 获取指定复制节点的绑定地址
    std::string binding_addr = std::get<0>(replicas[idx]);

    // 如果客户端端口未指定,则从绑定地址中提取客户端端口
    if (client_port == -1) {
        auto p = split_ip_port_cport(binding_addr);
        size_t idx;
        try {
            client_port = stoi(p.second, &idx); // 尝试将端口号从字符串转换为整数
        } catch (std::invalid_argument &) {
            throw HotStuffError("client port not specified"); // 如果转换失败,抛出异常
        }
    }

    // 创建 NetAddr 对象,用于监听来自复制节点的连接
    NetAddr plisten_addr{split_ip_port_cport(binding_addr).first};

    // 获取父节点限制配置项的值
    auto parent_limit = opt_parent_limit->get();

    // 根据节奏控制器类型创建相应的节奏控制器对象
    hotstuff::pacemaker_bt pmaker;
    if (opt_pace_maker->get() == "dummy")
        pmaker = new hotstuff::PaceMakerDummyFixed(opt_fixed_proposer->get(), parent_limit); // Dummy 类型节奏控制器
    else
        pmaker = new hotstuff::PaceMakerRR(ec, parent_limit, opt_base_timeout->get(), opt_prop_delay->get()); // Round-Robin 类型节奏控制器

    // 创建网络配置对象
    HotStuffApp::Net::Config repnet_config;
    ClientNetwork<opcode_t>::Config clinet_config;

    // 设置最大消息大小
    repnet_config.max_msg_size(opt_max_rep_msg->get());
    clinet_config.max_msg_size(opt_max_cli_msg->get());

    // 如果 TLS 私钥和证书路径不为空,并且 TLS 未禁用,则启用 TLS
    if (!opt_tls_privkey->get().empty() && !opt_notls->get()) {
        auto tls_priv_key = new salticidae::PKey(
                salticidae::PKey::create_privkey_from_der(
                    hotstuff::from_hex(opt_tls_privkey->get())));
        auto tls_cert = new salticidae::X509(
                salticidae::X509::create_from_der(
                    hotstuff::from_hex(opt_tls_cert->get())));
        repnet_config
            .enable_tls(true)
            .tls_key(tls_priv_key)
            .tls_cert(tls_cert);
    }

    // 设置复制节点网络和客户端网络的线程数和突发数
    repnet_config
        .burst_size(opt_repburst->get())
        .nworker(opt_repnworker->get());
    clinet_config
        .burst_size(opt_cliburst->get())
        .nworker(opt_clinworker->get());

    // 创建 HotStuff 应用对象
    papp = new HotStuffApp(opt_blk_size->get(),
                        opt_stat_period->get(),
                        opt_imp_timeout->get(),
                        idx,
                        hotstuff::from_hex(opt_privkey->get()),
                        plisten_addr,
                        NetAddr("0.0.0.0", client_port),
                        std::move(pmaker),
                        ec,
                        opt_nworker->get(),
                        repnet_config,
                        clinet_config);

    // 解析复制节点列表并创建复制节点的网络地址、私钥和证书
    std::vector<std::tuple<NetAddr, bytearray_t, bytearray_t>> reps;
    for (auto &r: replicas) {
        auto p = split_ip_port_cport(std::get<0>(r));
        reps.push_back(std::make_tuple(
            NetAddr(p.first),
            hotstuff::from_hex(std::get<1>(r)),
            hotstuff::from_hex(std::get<2>(r))));
    }

    // 定义一个关闭回调函数,当收到 SIGINT 或 SIGTERM 信号时停止应用程序
    auto shutdown = [&](int) { papp->stop(); };
    salticidae::SigEvent ev_sigint(ec, shutdown);
    salticidae::SigEvent ev_sigterm(ec, shutdown);
    ev_sigint.add(SIGINT);
    ev_sigterm.add(SIGTERM);

    // 启动 HotStuff 应用程序
    papp->start(reps);

    // 停止计时并打印消耗时间
    elapsed.stop(true);

    return 0; // 程序正常结束
}

配置文件初始化:

Config config("hotstuff.conf");

程序开始时,创建了一个 Config 对象,用于读取和解析配置文件 "hotstuff.conf"。 hotstuff.conf文件如下,包括区块大小,pacemaker的类别(rr),以及不同副本的端口号等

block-size = 1
pace-maker = rr
replica = 127.0.0.1:10000;20000, 039f89215177475ac408d079b45acef4591fc477dd690f2467df052cf0c7baba23, 542865a568784c4e77c172b82e99cb8a1a53b7bee5f86843b04960ea4157f420
replica = 127.0.0.1:10001;20001, 0278740a5bec75e333b3c93965b1609163b15d2e3c2fdef141d4859ec70c238e7a, c261250345ebcd676a0edeea173526608604f626b2e8bc4fd2142d3bde1d44d5
replica = 127.0.0.1:10002;20002, 0269eb606576a315a630c2483deed35cc4bd845abae1c693f97c440c89503fa92e, 065b010aed5629edfb5289e8b22fc6cc6b33c4013bfdd128caba80c3c02d6d78
replica = 127.0.0.1:10003;20003, 03e6911bf17e632eecdfa0dc9fc6efc9ddca60c0e3100db469a3d3d62008044a53, 6540a0fea67efcb08f53ec3a952df4c3f0e2e07c2778fd92320807717e29a651

计时器启动:

创建并启动了一个计时器,用于记录程序的运行时间。

定义配置选项并将配置选项添加到 Config 对象中:

定义并初始化了多个配置选项,比如区块大小、父节点限制、统计周期等。这些选项用于在命令行或配置文件中配置应用程序的行为。然后将配置选项添加到 Config 对象中,这样可以通过命令行参数或配置文件来设置这些选项的值。

auto opt_blk_size = Config::OptValInt::create(1);
auto opt_parent_limit = Config::OptValInt::create(-1);
auto opt_stat_period = Config::OptValDouble::create(10);
...

config.add_opt("block-size", opt_blk_size, Config::SET_VAL);
config.add_opt("parent-limit", opt_parent_limit, Config::SET_VAL);
...

解析命令行参数: 

解析传递给程序的命令行参数,将其值与之前定义的配置选项关联。

config.parse(argc, argv);

显示帮助信息并退出:

如果用户请求帮助信息(通过 --help 参数),程序会打印帮助信息并退出。

if (opt_help->get()) {
    config.print_help();
    exit(0);
}

解析复制节点列表:

从配置中读取复制节点列表(定义在hotstuff.conf中),并将每个节点的信息解析并存储在 replicas 向量中。res[i]如下:

res[0]:127.0.0.1:10000;20000

res[1]:039f89215177475ac408d079b45acef4591fc477dd690f2467df052cf0c7baba23

res[2]:542865a568784c4e77c172b82e99cb8a1a53b7bee5f86843b04960ea4157f420

    std::vector<std::tuple<std::string, std::string, std::string>> replicas;
    for (const auto &s: opt_replicas->get()) {
        auto res = trim_all(split(s, ","));
        if (res.size() != 3)
            throw HotStuffError("invalid replica info"); // 如果格式不正确,抛出异常
        replicas.push_back(std::make_tuple(res[0], res[1], res[2])); // 将解析结果存储到 replicas 向量中
    }

验证复制节点索引有效性:

检查启动协议的当前节点的索引是否在有效范围内。

auto idx = opt_idx->get();
if (!(0 <= idx && (size_t)idx < replicas.size()))
        throw HotStuffError("replica idx out of range");

创建客户端端口和副本监听地址:

  • 获取binding_addr(127.0.0.1:10000;20000
  • 尝试获取客户端端口(因为是demo,所以配置环境中没有指定)➡从副本地址(binding_addr)中获取➡client_port=20000
  • 从绑定地址中提取 IP 地址,并创建用于监听来自复制节点连接的 NetAddr 对象plisten_addr(127.0.0.1)。
// 获取指定复制节点的绑定地址
std::string binding_addr = std::get<0>(replicas[idx]);

auto client_port = opt_client_port->get();

// 如果客户端端口未指定,则从绑定地址中提取客户端端口
if (client_port == -1) {
    auto p = split_ip_port_cport(binding_addr);
    size_t idx;
    try {
        client_port = stoi(p.second, &idx); // 尝试将端口号从字符串转换为整数
    } catch (std::invalid_argument &) {
        throw HotStuffError("client port not specified"); // 如果转换失败,抛出异常
    }
}

NetAddr plisten_addr{split_ip_port_cport(binding_addr).first};
  •  127.0.0.1:10000;20000➡split_ip_port_cport函数➡(127.0.0.1:10000,20000
// 分割IP和端口号的函数
std::pair<std::string, std::string> split_ip_port_cport(const std::string &s) {
    auto ret = trim_all(split(s, ";")); // 通过分号分割并去除空格
    if (ret.size() != 2)
        throw std::invalid_argument("invalid cport format"); // 格式不对时抛出异常
    return std::make_pair(ret[0], ret[1]); // 返回IP和端口号
}

创建节奏控制器: 

根据配置中的节奏控制器类型创建相应的节奏控制器对象。可以是 PaceMakerDummyFixedPaceMakerRR。(在hotstuff.conf中配置的是rr,因此是启动既具备父块选择的逻辑,也具备轮流提议者的机制的pacemaker)(节奏器在第四章)

// 获取父节点限制配置项的值
auto parent_limit = opt_parent_limit->get();

hotstuff::pacemaker_bt pmaker;
if (opt_pace_maker->get() == "dummy")
    pmaker = new hotstuff::PaceMakerDummyFixed(opt_fixed_proposer->get(), parent_limit);
else
    pmaker = new hotstuff::PaceMakerRR(ec, parent_limit, opt_base_timeout->get(), opt_prop_delay->get());

网络配置(peer网络和client网络)

 初始化复制节点网络和客户端网络的配置,包括最大消息大小、线程数等设置。并且启动TLS

    // 创建网络配置对象
    HotStuffApp::Net::Config repnet_config;//using Net = PeerNetwork<opcode_t>;
    ClientNetwork<opcode_t>::Config clinet_config;//using salticidae::ClientNetwork;

    // 设置最大消息大小
    repnet_config.max_msg_size(opt_max_rep_msg->get());//auto opt_max_rep_msg = Config::OptValInt::create(4 << 20); 最大复制节点消息大小,默认值为 4MB
    
    clinet_config.max_msg_size(opt_max_cli_msg->get());//auto opt_max_cli_msg = Config::OptValInt::create(65536);最大客户端消息大小,默认值为 64KB

    // 如果 TLS 私钥和证书路径不为空,并且 TLS 未禁用,则启用 TLS
    if (!opt_tls_privkey->get().empty() && !opt_notls->get()) {
        auto tls_priv_key = new salticidae::PKey(
                salticidae::PKey::create_privkey_from_der(
                    hotstuff::from_hex(opt_tls_privkey->get())));
        auto tls_cert = new salticidae::X509(
                salticidae::X509::create_from_der(
                    hotstuff::from_hex(opt_tls_cert->get())));//using salticidae::from_hex;
        repnet_config
            .enable_tls(true)
            .tls_key(tls_priv_key)
            .tls_cert(tls_cert);
    }

    // 设置复制节点网络和客户端网络的线程数和突发数
    repnet_config
        .burst_size(opt_repburst->get())//auto opt_repburst = Config::OptValInt::create(100); // 复制节点网络的突发大小,默认值为 100
        .nworker(opt_repnworker->get());//auto opt_repnworker = Config::OptValInt::create(1); // 复制节点网络线程数量,默认值为 1
    clinet_config
        .burst_size(opt_cliburst->get())//auto opt_cliburst = Config::OptValInt::create(1000); // 客户端网络的突发大小,默认值为 1000
        .nworker(opt_clinworker->get());//auto opt_clinworker = Config::OptValInt::create(8); // 客户端网络线程数量,默认值为 8

创建 HotStuff 应用程序实例:

构建协议app并且启动协议

salticidae::BoxObj<HotStuffApp> papp = nullptr; // HotStuffApp的全局对象

// 创建 HotStuff 应用对象
papp = new HotStuffApp(opt_blk_size->get(),
                    opt_stat_period->get(),
                    opt_imp_timeout->get(),
                    idx,
                    hotstuff::from_hex(opt_privkey->get()),
                    plisten_addr,
                    NetAddr("0.0.0.0", client_port),
                    std::move(pmaker),
                    ec,
                    opt_nworker->get(),
                    repnet_config,
                    clinet_config);

// 解析复制节点列表并创建复制节点的网络地址、私钥和证书
std::vector<std::tuple<NetAddr, bytearray_t, bytearray_t>> reps;
for (auto &r: replicas) {
    auto p = split_ip_port_cport(std::get<0>(r));
    reps.push_back(std::make_tuple(
        NetAddr(p.first),
        hotstuff::from_hex(std::get<1>(r)),
        hotstuff::from_hex(std::get<2>(r))));
}

// 定义一个关闭回调函数,当收到 SIGINT 或 SIGTERM 信号时停止应用程序
auto shutdown = [&](int) { papp->stop(); };
salticidae::SigEvent ev_sigint(ec, shutdown);
salticidae::SigEvent ev_sigterm(ec, shutdown);
ev_sigint.add(SIGINT);
ev_sigterm.add(SIGTERM);

// 启动 HotStuff 应用程序
papp->start(reps);

// 停止计时并打印消耗时间
elapsed.stop(true);

HostuffApp

HotStuffApp 类实现了 HotStuff 共识协议的核心功能。它管理与客户端的通信、处理请求和响应、维护定时器以及进行系统统计信息打印。

class HotStuffApp: public HotStuff {
    double stat_period; // 统计周期
    double impeach_timeout; // 弹劾超时时间
    EventContext ec; // 事件上下文
    EventContext req_ec; // 请求事件上下文
    EventContext resp_ec; // 响应事件上下文
    /** 副本和客户端之间的网络消息传递。*/
    ClientNetwork<opcode_t> cn;
    /** 定时器对象,用于调度周期性打印系统统计信息 */
    TimerEvent ev_stat_timer;
    /** 定时器对象,用于监控简单弹劾的进展 */
    TimerEvent impeach_timer;
    /** 客户端RPC的监听地址 */
    NetAddr clisten_addr;

    // 未确认的命令映射,使用命令的哈希值作为键
    std::unordered_map<const uint256_t, promise_t> unconfirmed;

    using conn_t = ClientNetwork<opcode_t>::conn_t;
    using resp_queue_t = salticidae::MPSCQueueEventDriven<std::pair<Finality, NetAddr>>;//多生产者单消费者队列

    /* 用于处理发送给客户端响应的专用线程 */
    std::thread req_thread;
    std::thread resp_thread;
    resp_queue_t resp_queue; // 响应队列
    salticidae::BoxObj<salticidae::ThreadCall> resp_tcall; // 响应线程调用对象
    salticidae::BoxObj<salticidae::ThreadCall> req_tcall; // 请求线程调用对象

    // 客户端请求命令处理器
    void client_request_cmd_handler(MsgReqCmd &&, const conn_t &);

    // 解析命令的静态方法
    static command_t parse_cmd(DataStream &s) {
        auto cmd = new CommandDummy();
        s >> *cmd;
        return cmd;
    }

    // 重置弹劾定时器
    void reset_imp_timer() {
        impeach_timer.del(); // 删除当前定时器
        impeach_timer.add(impeach_timeout); // 添加新的定时器,设置为弹劾超时
    }

    // 状态机执行逻辑的重写函数
    void state_machine_execute(const Finality &fin) override {
        reset_imp_timer(); // 每次执行时重置弹劾定时器
#ifndef HOTSTUFF_ENABLE_BENCHMARK
        HOTSTUFF_LOG_INFO("replicated %s", std::string(fin).c_str()); // 打印日志信息
#endif
    }

#ifdef HOTSTUFF_MSG_STAT
    std::unordered_set<conn_t> client_conns; // 客户端连接集合
    void print_stat() const; // 打印统计信息
#endif

    public:
    // 构造函数
    HotStuffApp(
        uint32_t blk_size, // 区块大小,表示每个区块中包含的命令数量
        double stat_period, // 统计周期,指定多长时间进行一次系统统计信息打印
        double impeach_timeout, // 弹劾超时时间,指定在没有达成共识时,触发弹劾的等待时间
        ReplicaID idx, // 当前副本的唯一标识(ID)
        const bytearray_t &raw_privkey, // 副本的私钥,用于签名和验证
        NetAddr plisten_addr, // 副本之间通信的监听地址,用于与其他副本建立连接
        NetAddr clisten_addr, // 客户端通信的监听地址,用于接受客户端请求
        hotstuff::pacemaker_bt pmaker, // 节奏器(Pacemaker),用于驱动共识协议的进展
        const EventContext &ec, // 事件上下文,管理事件的循环和调度
        size_t nworker, // 工作线程的数量,用于并行处理任务
        const Net::Config &repnet_config, // 副本网络的配置参数
        const ClientNetwork<opcode_t>::Config &clinet_config // 客户端网络的配置参数
    );

   / 启动函数
    // reps 参数是一个包含多个副本信息的向量,每个元素都是一个三元组
    // 三元组中的元素分别表示副本的网络地址、公开密钥(公钥)和验证密钥
    void start(const std::vector<std::tuple<NetAddr, bytearray_t, bytearray_t>> &reps) {
        // 启动 HotStuffApp 实例并开始接受客户端请求和处理共识
        // 这个函数可能会初始化网络连接、启动工作线程以及设置事件循环
    }

    // 停止函数
    // 停止 HotStuffApp 实例的运行
    void stop() {
        // 停止所有运行中的线程、断开网络连接、清理资源
        // 这个函数会确保应用程序干净地关闭,不会留下悬挂的操作
    }
};

 HotstuffApp构造函数

构造与客户端之间的通信网络并引用基类(Hotstuff)的构造函数,回看前几章,Hotstuff构造会引用HotstuffBase构造(负责peer网络构造以及调用核心类HotstuffCore构造函数),HotstuffCore函数负责构造核心逻辑构造(创世区块、尾块等等)

HotStuffApp::HotStuffApp(uint32_t blk_size,
                        double stat_period,
                        double impeach_timeout,
                        ReplicaID idx,
                        const bytearray_t &raw_privkey,
                        NetAddr plisten_addr,
                        NetAddr clisten_addr,
                        hotstuff::pacemaker_bt pmaker,
                        const EventContext &ec,
                        size_t nworker,
                        const Net::Config &repnet_config,
                        const ClientNetwork<opcode_t>::Config &clinet_config)
    : HotStuff(blk_size, idx, raw_privkey,
               plisten_addr, std::move(pmaker), ec, nworker, repnet_config),
      stat_period(stat_period),
      impeach_timeout(impeach_timeout),
      ec(ec),
      cn(req_ec, clinet_config),
      clisten_addr(clisten_addr) {
    /* 准备用于发送确认的线程 */
    resp_tcall = new salticidae::ThreadCall(resp_ec); // 创建一个新的线程调用对象,用于响应处理线程
    req_tcall = new salticidae::ThreadCall(req_ec); // 创建另一个线程调用对象,用于请求处理线程
    
    /* 注册响应队列处理程序 */
    resp_queue.reg_handler(resp_ec, [this](resp_queue_t &q) {
        std::pair<Finality, NetAddr> p; // 定义队列对,包含最终性和网络地址
        while (q.try_dequeue(p)) { // 尝试从队列中出队元素
            try {
                cn.send_msg(MsgRespCmd(std::move(p.first)), p.second); // 发送响应消息给客户端
            } catch (std::exception &err) {
                HOTSTUFF_LOG_WARN("unable to send to the client: %s", err.what()); // 发送失败时记录警告
            }
        }
        return false; // 返回 false 表示处理程序没有继续等待新的消息
    });

    /* 注册客户端消息处理程序 */
    cn.reg_handler(salticidae::generic_bind(&HotStuffApp::client_request_cmd_handler, this, _1, _2));
    cn.start(); // 启动客户端网络
    cn.listen(clisten_addr); // 开始监听客户端连接
}

HotstuffApp启动函数

HotStuffApp::start 函数的主要目的是初始化和启动 HotStuffApp 实例所需的各个组件,包括统计定时器、弹劾定时器、请求和响应处理线程,以及客户端连接管理。它设置了定时器以处理周期性任务,并开始了事件主循环以处理实时事件。这使得 HotStuffApp 实例能够全面启动并稳定运行。

void HotStuffApp::start(const std::vector<std::tuple<NetAddr, bytearray_t, bytearray_t>> &reps) {
    // 初始化系统统计定时器,并设置回调函数
    ev_stat_timer = TimerEvent(ec, [this](TimerEvent &) {
        HotStuff::print_stat(); // 打印基础统计信息
        HotStuffApp::print_stat(); // 打印应用级别的统计信息
        // HotStuffCore::prune(100); // 可选:修剪核心数据,保留最新的100个条目
        ev_stat_timer.add(stat_period); // 重新设置定时器,继续下一个统计周期
    });
    ev_stat_timer.add(stat_period); // 添加并启动统计定时器

    // 初始化弹劾定时器,并设置回调函数
    impeach_timer = TimerEvent(ec, [this](TimerEvent &) {
        if (get_decision_waiting().size()) // 如果有未决的决策(共识尚未达成)
            get_pace_maker()->impeach(); // 启动节奏器的弹劾过程,尝试更换领导者
        reset_imp_timer(); // 重置弹劾定时器
    });
    impeach_timer.add(impeach_timeout); // 添加并启动弹劾定时器

    // 打印启动信息和参数
    HOTSTUFF_LOG_INFO("** starting the system with parameters **");
    HOTSTUFF_LOG_INFO("blk_size = %lu", blk_size); // 打印区块大小
    HOTSTUFF_LOG_INFO("conns = %lu", HotStuff::size()); // 打印当前连接数
    HOTSTUFF_LOG_INFO("** starting the event loop...");

    // 调用基类的 start 函数,启动共识协议并初始化副本信息
    HotStuff::start(reps);

    // 注册客户端连接处理器
    cn.reg_conn_handler([this](const salticidae::ConnPool::conn_t &_conn, bool connected) {
        auto conn = salticidae::static_pointer_cast<conn_t::type>(_conn);
        if (connected)
            client_conns.insert(conn); // 如果连接建立,添加到客户端连接集合
        else
            client_conns.erase(conn); // 如果连接断开,从集合中移除
        return true; // 返回 true 表示处理成功
    });

    // 启动请求处理线程
    req_thread = std::thread([this]() { req_ec.dispatch(); });
    // 启动响应处理线程
    resp_thread = std::thread([this]() { resp_ec.dispatch(); });

    // 进入事件主循环,开始处理事件
    ec.dispatch();
}

HotstuffApp停止函数

void HotStuffApp::stop() {
    // 异步调用请求处理线程的停止方法
    papp->req_tcall->async_call([this](salticidae::ThreadCall::Handle &) {
        req_ec.stop(); // 停止请求事件循环
    });

    // 异步调用响应处理线程的停止方法
    papp->resp_tcall->async_call([this](salticidae::ThreadCall::Handle &) {
        resp_ec.stop(); // 停止响应事件循环
    });

    // 等待请求和响应处理线程结束
    req_thread.join();
    resp_thread.join();

    // 停止主事件循环
    ec.stop();
}

 请求获取

void HotStuffApp::client_request_cmd_handler(MsgReqCmd &&msg, const conn_t &conn) {
    const NetAddr addr = conn->get_addr(); // 获取客户端的网络地址
    auto cmd = parse_cmd(msg.serialized); // 解析客户端消息中的命令
    const auto &cmd_hash = cmd->get_hash(); // 获取命令的哈希值
    HOTSTUFF_LOG_DEBUG("processing %s", std::string(*cmd).c_str()); // 记录调试信息,显示正在处理的命令

    // 执行命令并将结果放入响应队列
    exec_command(cmd_hash, [this, addr](Finality fin) {
        resp_queue.enqueue(std::make_pair(fin, addr)); // 将最终性和客户端地址加入响应队列
    });
}

hotstuff_client

基础定义

定义了一些全局变量、请求结构体等

// 全局变量定义
EventContext ec; // 事件上下文,用于处理事件循环
ReplicaID proposer; // 提议者的 ID
size_t max_async_num; // 最大并发发送命令数
int max_iter_num; // 最大迭代次数
uint32_t cid; // 客户端 ID
uint32_t cnt = 0; // 命令计数器
uint32_t nfaulty; // 允许的故障副本数量

// 请求结构体,用于存储命令、确认数和计时器
struct Request {
    command_t cmd; // 命令
    size_t confirmed; // 已确认的数量
    salticidae::ElapsedTime et; // 计时器
    Request(const command_t &cmd): cmd(cmd), confirmed(0) { et.start(); } // 初始化请求
};

// 网络类型定义
using Net = salticidae::MsgNetwork<opcode_t>;

// 存储副本连接和等待中的请求
std::unordered_map<ReplicaID, Net::conn_t> conns; // 副本ID到连接对象的映射
std::unordered_map<const uint256_t, Request> waiting; // 命令哈希到请求对象的映射
std::vector<NetAddr> replicas; // 存储副本地址的向量
std::vector<std::pair<struct timeval, double>> elapsed; // 用于基准测试的时间数据
std::unique_ptr<Net> mn; // 网络对象的智能指针

main函数

相对于app来说,client只需要关注客户端与节点的交互,笔者还是先从main函数入手

int main(int argc, char **argv) {
    // 创建配置对象,读取配置文件 "hotstuff.conf"
    Config config("hotstuff.conf"); 

    // 配置选项初始化
    auto opt_idx = Config::OptValInt::create(0); // 副本索引,默认为 0
    auto opt_replicas = Config::OptValStrVec::create(); // 副本地址列表
    auto opt_max_iter_num = Config::OptValInt::create(100); // 最大迭代次数,默认为 100
    auto opt_max_async_num = Config::OptValInt::create(10); // 最大并发发送命令数,默认为 10
    auto opt_cid = Config::OptValInt::create(-1); // 客户端 ID,默认为 -1,表示使用默认值
    auto opt_max_cli_msg = Config::OptValInt::create(65536); // 最大客户端消息大小,默认为 64K

    // 信号处理,优雅停止事件循环
    // 定义信号处理函数,停止事件循环
    auto shutdown = [&](int) { ec.stop(); };
    salticidae::SigEvent ev_sigint(ec, shutdown); // 处理 SIGINT 信号
    salticidae::SigEvent ev_sigterm(ec, shutdown); // 处理 SIGTERM 信号
    ev_sigint.add(SIGINT); // 将 SIGINT 信号与 shutdown 处理程序关联
    ev_sigterm.add(SIGTERM); // 将 SIGTERM 信号与 shutdown 处理程序关联

    // 初始化网络对象
    mn = std::make_unique<Net>(ec, Net::Config().max_msg_size(opt_max_cli_msg->get())); // 创建网络对象,并设置最大消息大小
    mn->reg_handler(client_resp_cmd_handler); // 注册客户端响应命令的处理回调函数
    mn->start(); // 启动网络对象,使其开始接收和处理消息

    // 解析配置选项
    config.add_opt("idx", opt_idx, Config::SET_VAL); // 添加副本索引选项
    config.add_opt("cid", opt_cid, Config::SET_VAL); // 添加客户端 ID 选项
    config.add_opt("replica", opt_replicas, Config::APPEND); // 添加副本地址列表选项
    config.add_opt("iter", opt_max_iter_num, Config::SET_VAL); // 添加最大迭代次数选项
    config.add_opt("max-async", opt_max_async_num, Config::SET_VAL); // 添加最大并发发送命令数选项
    config.add_opt("max-cli-msg", opt_max_cli_msg, Config::SET_VAL, 'S', "the maximum client message size"); // 添加最大客户端消息大小选项
    config.parse(argc, argv); // 解析命令行参数并更新配置选项

    // 获取配置值
    auto idx = opt_idx->get(); // 获取副本索引
    max_iter_num = opt_max_iter_num->get(); // 获取最大迭代次数
    max_async_num = opt_max_async_num->get(); // 获取最大并发发送命令数
    std::vector<std::string> raw; // 存储副本地址的原始字符串列表
    // 解析副本地址列表
    for (const auto &s: opt_replicas->get()) {
        auto res = salticidae::trim_all(salticidae::split(s, ",")); // 去除多余空格并拆分地址
        if (res.size() < 1)
            throw HotStuffError("format error"); // 如果格式错误,则抛出异常
        raw.push_back(res[0]); // 提取 IP 和端口,存储在 raw 向量中
    }

    // 检查副本索引是否在有效范围内
    if (!(0 <= idx && (size_t)idx < raw.size() && raw.size() > 0))
        throw std::invalid_argument("out of range"); // 如果副本索引超出范围,则抛出异常
    
    // 设置客户端 ID,如果未指定,则使用副本索引作为默认值
    cid = opt_cid->get() != -1 ? opt_cid->get() : idx; 
    // 解析副本地址,并存储在 replicas 向量中
    for (const auto &p: raw) {
        auto _p = split_ip_port_cport(p); // 拆分 IP 和端口
        size_t _;
        replicas.push_back(NetAddr(NetAddr(_p.first).ip, htons(stoi(_p.second, &_)))); // 存储副本地址
    }

    // 计算允许的故障副本数
    nfaulty = (replicas.size() - 1) / 3;
    HOTSTUFF_LOG_INFO("nfaulty = %zu", nfaulty); // 输出允许的故障副本数

    // 连接到所有副本
    connect_all(); 
    // 尝试发送命令(可能会进入循环)
    while (try_send()); 

    // 启动事件循环,处理事件
    ec.dispatch(); 

#ifdef HOTSTUFF_ENABLE_BENCHMARK
    // 如果启用了基准测试,输出基准测试结果
    for (const auto &e: elapsed) {
        char fmt[64];
        struct tm *tmp = localtime(&e.first.tv_sec); // 获取当前时间
        // 格式化时间并输出基准测试结果
        strftime(fmt, sizeof fmt, "%Y-%m-%d %H:%M:%S.%%06u [hotstuff info] %%.6f\n", tmp);
        fprintf(stderr, fmt, e.first.tv_usec, e.second);
    }
#endif

    return 0; // 程序正常退出
}

除了配置、验证、网络启动,就是connect_all连接所有副本并try_send发送命令,以及client_resp_cmd_handler响应副本,我们主要关注这三个函数。

connect_all

连接所有副本并存储在conns中

void connect_all() {
    // 遍历副本地址,建立与每个副本的连接
    for (size_t i = 0; i < replicas.size(); i++)
        // 使用 mn->connect_sync() 同步连接到副本,并将连接存储在 conns 中
        conns.insert(std::make_pair(i, mn->connect_sync(replicas[i])));
}

try_send

当发送给每个副本后,协议会根据pacemaker的提议者id来决定谁是提议者。

// 尝试发送命令
bool try_send(bool check = true) {
    // 检查是否满足发送条件(可选的检查和异步命令数量限制)
    if ((!check || waiting.size() < max_async_num) && max_iter_num) {
        // 创建一个新的命令,并构造请求消息
        auto cmd = new CommandDummy(cid, cnt++); // 创建新命令
        MsgReqCmd msg(*cmd); // 包装命令为请求消息
        // 遍历所有副本连接,将消息发送到每个副本
        for (auto &p: conns) mn->send_msg(msg, p.second); // 发送消息到所有副本
#ifndef HOTSTUFF_ENABLE_BENCHMARK
        // 如果未启用基准测试,则记录发送的命令的日志
        HOTSTUFF_LOG_INFO("send new cmd %.10s", get_hex(cmd->get_hash()).c_str());
#endif
        // 将命令添加到等待中的请求中
        waiting.insert(std::make_pair(cmd->get_hash(), Request(cmd)));
        // 减少最大迭代次数(如果设置了限制)
        if (max_iter_num > 0)
            max_iter_num--;
        return true; // 成功发送命令
    }
    return false; // 未满足发送条件
}

client_resp_cmd_handler

获取副本决策信息并移除waiting中的请求,然后重新发送命令

// 处理客户端响应命令的回调函数
void client_resp_cmd_handler(MsgRespCmd &&msg, const Net::conn_t &) {
    // 处理客户端响应消息
    auto &fin = msg.fin; // 获取响应消息中的命令完成信息
    HOTSTUFF_LOG_DEBUG("got %s", std::string(msg.fin).c_str());
    const uint256_t &cmd_hash = fin.cmd_hash; // 获取命令的哈希值
    auto it = waiting.find(cmd_hash); // 查找等待中的请求
    if (it == waiting.end()) return; // 如果请求不在等待中,则返回
    auto &et = it->second.et; // 获取该请求的计时器
    et.stop(); // 停止计时器以记录响应时间
    if (++it->second.confirmed <= nfaulty) return; // 如果确认数未达到阈值,则返回
#ifndef HOTSTUFF_ENABLE_BENCHMARK
    // 如果未启用基准测试,则记录响应的日志
    HOTSTUFF_LOG_INFO("got %s, wall: %.3f, cpu: %.3f",
                        std::string(fin).c_str(),
                        et.elapsed_sec, et.cpu_elapsed_sec);
#else
    // 如果启用了基准测试,则记录时间数据
    struct timeval tv;
    gettimeofday(&tv, nullptr); // 获取当前时间
    elapsed.push_back(std::make_pair(tv, et.elapsed_sec)); // 记录基准测试数据
#endif
    // 移除已处理的请求
    waiting.erase(it);
    // 尝试发送更多命令(如果有待发送的命令)
    while (try_send());
}

实验结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值