diff --git a/libraries/hotstuff/include/eosio/hotstuff/test_pacemaker.hpp b/libraries/hotstuff/include/eosio/hotstuff/test_pacemaker.hpp index cfa6b05239..db6ab6ba8b 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/test_pacemaker.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/test_pacemaker.hpp @@ -7,31 +7,44 @@ namespace eosio { namespace hotstuff { class test_pacemaker : public base_pacemaker { public: + using hotstuff_message = std::pair>; + enum hotstuff_message_index { + hs_proposal = 0, + hs_vote = 1, + hs_new_block = 2, + hs_new_view = 3, + hs_all_messages + }; + //class-specific functions bool is_qc_chain_active(const name & qcc_name) { return _qcc_deactivated.find(qcc_name) == _qcc_deactivated.end(); } - using hotstuff_message = std::pair>; - void set_proposer(name proposer); void set_leader(name leader); void set_next_leader(name next_leader); - void set_finalizers(std::vector finalizers); + void set_finalizers(const std::vector& finalizers); void set_current_block_id(block_id_type id); void set_quorum_threshold(uint32_t threshold); - void add_message_to_queue(hotstuff_message msg); + void add_message_to_queue(const hotstuff_message& msg); + + void connect(const std::vector& nodes); + + void disconnect(const std::vector& nodes); - void pipe(std::vector messages); + bool is_connected(name node1, name node2); - void dispatch(std::string memo, int count); + void pipe(const std::vector& messages); - std::vector dispatch(std::string memo); + void dispatch(std::string memo, int count, hotstuff_message_index msg_type = hs_all_messages); + + std::vector dispatch(std::string memo, hotstuff_message_index msg_type = hs_all_messages); void activate(name replica); void deactivate(name replica); @@ -64,6 +77,8 @@ namespace eosio { namespace hotstuff { void send_hs_message_warning(const uint32_t sender_peer, const chain::hs_message_warning code); + private: + std::vector _pending_message_queue; // qc_chain id to qc_chain object @@ -72,9 +87,10 @@ namespace eosio { namespace hotstuff { // qc_chain ids in this set are currently deactivated set _qcc_deactivated; - private: - - std::vector _message_queue; + // network topology: key (node name) is connected to all nodes in the mapped set. + // double mapping, so if _net[a] yields b, then _net[b] yields a. + // this is a filter; messages to self won't happen even if _net[x] yields x. + map> _net; name _proposer; name _leader; diff --git a/libraries/hotstuff/test/test_hotstuff.cpp b/libraries/hotstuff/test/test_hotstuff.cpp index 997708fe91..456e4d0535 100644 --- a/libraries/hotstuff/test/test_hotstuff.cpp +++ b/libraries/hotstuff/test/test_hotstuff.cpp @@ -46,12 +46,6 @@ class hotstuff_test_handler { _qc_chains.clear(); - // These used to be able to break the tests. Might be useful at some point. - _qc_chains.reserve( 100 ); - //_qc_chains.reserve( 10000 ); - //_qc_chains.reserve( 15 ); - //_qc_chains.reserve( replicas.size() ); - for (name r : replicas) { qc_chain *qcc_ptr = new qc_chain(r, &tpm, {r}, {}, hotstuff_logger); std::shared_ptr qcc_shared_ptr(qcc_ptr); @@ -94,10 +88,6 @@ class hotstuff_test_handler { std::cout << "\n"; } - void print_msg_queue(test_pacemaker &tpm){ - print_msgs(tpm._pending_message_queue); - } - void print_pm_state(test_pacemaker &tpm){ std::cout << "\n"; std::cout << " leader : " << tpm.get_leader() << "\n"; @@ -138,6 +128,11 @@ class hotstuff_test_handler { std::cout << "\n"; } + + void dispatch(test_pacemaker& tpm, int hops, test_pacemaker::hotstuff_message_index msg_type, const std::string& memo = "") { + for (int i=0;isecond->get_state(fs_bpa); + auto qcc_bpb = std::find_if(ht._qc_chains.begin(), ht._qc_chains.end(), [&](const auto& q){ return q.first == "bpb"_n; }); + finalizer_state fs_bpb; + qcc_bpb->second->get_state(fs_bpb); + auto qcc_bpc = std::find_if(ht._qc_chains.begin(), ht._qc_chains.end(), [&](const auto& q){ return q.first == "bpc"_n; }); + finalizer_state fs_bpc; + qcc_bpc->second->get_state(fs_bpc); + + tpm.set_current_block_id(ids[0]); //first block + + tpm.beat(); //produce first block and associated proposal + + ht.dispatch(tpm, 2, test_pacemaker::hs_proposal); //send proposal to replicas (prepare on first block) + + qcc_bpa->second->get_state(fs_bpa); + BOOST_CHECK_EQUAL(fs_bpa.b_leaf.str(), std::string("a252070cd26d3b231ab2443b9ba97f57fc72e50cca04a020952e45bc7e2d27a8")); + BOOST_CHECK_EQUAL(fs_bpa.high_qc.proposal_id.str(), std::string("0000000000000000000000000000000000000000000000000000000000000000")); + BOOST_CHECK_EQUAL(fs_bpa.b_lock.str(), std::string("0000000000000000000000000000000000000000000000000000000000000000")); + BOOST_CHECK_EQUAL(fs_bpa.b_exec.str(), std::string("0000000000000000000000000000000000000000000000000000000000000000")); + + ht.dispatch(tpm, 2, test_pacemaker::hs_vote); //send votes on proposal (prepareQC on first block) + + ht.dispatch(tpm, 2, test_pacemaker::hs_proposal); //send proposal to replicas (precommit on first block) + + qcc_bpa->second->get_state(fs_bpa); + BOOST_CHECK_EQUAL(fs_bpa.b_leaf.str(), std::string("4b43fb144a8b5e874777f61f3b37d7a8b06c33fbc48db464ce0e8788ff4edb4f")); + BOOST_CHECK_EQUAL(fs_bpa.high_qc.proposal_id.str(), std::string("a252070cd26d3b231ab2443b9ba97f57fc72e50cca04a020952e45bc7e2d27a8")); + BOOST_CHECK_EQUAL(fs_bpa.b_lock.str(), std::string("0000000000000000000000000000000000000000000000000000000000000000")); + BOOST_CHECK_EQUAL(fs_bpa.b_exec.str(), std::string("0000000000000000000000000000000000000000000000000000000000000000")); + + ht.dispatch(tpm, 2, test_pacemaker::hs_vote); //propagating votes on new proposal (precommitQC on first block) + + ht.dispatch(tpm, 2, test_pacemaker::hs_proposal); //send proposal to replicas (commit on first block) + + qcc_bpa->second->get_state(fs_bpa); + BOOST_CHECK_EQUAL(fs_bpa.b_leaf.str(), std::string("aedf8bb1ee70bd6e743268f7fe0f8171418aa43a68bb9c6e7329ffa856896c09")); + BOOST_CHECK_EQUAL(fs_bpa.high_qc.proposal_id.str(), std::string("4b43fb144a8b5e874777f61f3b37d7a8b06c33fbc48db464ce0e8788ff4edb4f")); + BOOST_CHECK_EQUAL(fs_bpa.b_lock.str(), std::string("a252070cd26d3b231ab2443b9ba97f57fc72e50cca04a020952e45bc7e2d27a8")); + BOOST_CHECK_EQUAL(fs_bpa.b_exec.str(), std::string("0000000000000000000000000000000000000000000000000000000000000000")); + + tpm.set_next_leader("bpb"_n); //leader is set to rotate on next block + + ht.dispatch(tpm, 2, test_pacemaker::hs_vote); //propagating votes on new proposal (commitQC on first block) + + ht.dispatch(tpm, 2, test_pacemaker::hs_proposal); //send proposal to replicas (decide on first block) + + qcc_bpa->second->get_state(fs_bpa); + BOOST_CHECK_EQUAL(fs_bpa.b_leaf.str(), std::string("487e5fcbf2c515618941291ae3b6dcebb68942983d8ac3f61c4bdd9901dadbe7")); + BOOST_CHECK_EQUAL(fs_bpa.high_qc.proposal_id.str(), std::string("aedf8bb1ee70bd6e743268f7fe0f8171418aa43a68bb9c6e7329ffa856896c09")); + BOOST_CHECK_EQUAL(fs_bpa.b_lock.str(), std::string("4b43fb144a8b5e874777f61f3b37d7a8b06c33fbc48db464ce0e8788ff4edb4f")); + BOOST_CHECK_EQUAL(fs_bpa.b_exec.str(), std::string("a252070cd26d3b231ab2443b9ba97f57fc72e50cca04a020952e45bc7e2d27a8")); + + ht.dispatch(tpm, 2, test_pacemaker::hs_vote); //propagating votes on new proposal (decide on first block) + + tpm.set_proposer("bpb"_n); //leader has rotated + tpm.set_leader("bpb"_n); + + tpm.set_current_block_id(ids[1]); //second block + + tpm.beat(); //produce second block and associated proposal + + ht.dispatch(tpm, 2, test_pacemaker::hs_proposal); //send proposal to replicas (prepare on second block) + + qcc_bpb->second->get_state(fs_bpb); + BOOST_CHECK_EQUAL(fs_bpb.b_leaf.str(), std::string("1511035fdcbabdc5e272a3ac19356536252884ed77077cf871ae5029a7502279")); + BOOST_CHECK_EQUAL(fs_bpb.high_qc.proposal_id.str(), std::string("aedf8bb1ee70bd6e743268f7fe0f8171418aa43a68bb9c6e7329ffa856896c09")); + BOOST_CHECK_EQUAL(fs_bpb.b_lock.str(), std::string("4b43fb144a8b5e874777f61f3b37d7a8b06c33fbc48db464ce0e8788ff4edb4f")); + BOOST_CHECK_EQUAL(fs_bpb.b_exec.str(), std::string("a252070cd26d3b231ab2443b9ba97f57fc72e50cca04a020952e45bc7e2d27a8")); + + ht.dispatch(tpm, 2, test_pacemaker::hs_vote); //send votes on proposal (prepareQC on second block) + + ht.dispatch(tpm, 2, test_pacemaker::hs_proposal); //send proposal to replicas (precommit on second block) + + qcc_bpb->second->get_state(fs_bpb); + BOOST_CHECK_EQUAL(fs_bpb.b_leaf.str(), std::string("6462add7d157da87931c859cb689f722003a20f30c0f1408d11b872020903b85")); + BOOST_CHECK_EQUAL(fs_bpb.high_qc.proposal_id.str(), std::string("1511035fdcbabdc5e272a3ac19356536252884ed77077cf871ae5029a7502279")); + BOOST_CHECK_EQUAL(fs_bpb.b_lock.str(), std::string("aedf8bb1ee70bd6e743268f7fe0f8171418aa43a68bb9c6e7329ffa856896c09")); + BOOST_CHECK_EQUAL(fs_bpb.b_exec.str(), std::string("4b43fb144a8b5e874777f61f3b37d7a8b06c33fbc48db464ce0e8788ff4edb4f")); + + ht.dispatch(tpm, 2, test_pacemaker::hs_vote); //propagating votes on new proposal (precommitQC on second block) + + ht.dispatch(tpm, 2, test_pacemaker::hs_proposal); //send proposal to replicas (commit on second block) + + qcc_bpb->second->get_state(fs_bpb); + BOOST_CHECK_EQUAL(fs_bpb.b_leaf.str(), std::string("fd77164bf3898a6a8f27ccff440d17ef6870e75c368fcc93b969066cec70939c")); + BOOST_CHECK_EQUAL(fs_bpb.high_qc.proposal_id.str(), std::string("6462add7d157da87931c859cb689f722003a20f30c0f1408d11b872020903b85")); + BOOST_CHECK_EQUAL(fs_bpb.b_lock.str(), std::string("1511035fdcbabdc5e272a3ac19356536252884ed77077cf871ae5029a7502279")); + BOOST_CHECK_EQUAL(fs_bpb.b_exec.str(), std::string("aedf8bb1ee70bd6e743268f7fe0f8171418aa43a68bb9c6e7329ffa856896c09")); + + ht.dispatch(tpm, 2, test_pacemaker::hs_vote); //propagating votes on new proposal (commitQC on second block) + + ht.dispatch(tpm, 2, test_pacemaker::hs_proposal); //send proposal to replicas (decide on second block) + + qcc_bpb->second->get_state(fs_bpb); + BOOST_CHECK_EQUAL(fs_bpb.b_leaf.str(), std::string("89f468a127dbadd81b59076067238e3e9c313782d7d83141b16d9da4f2c2b078")); + BOOST_CHECK_EQUAL(fs_bpb.high_qc.proposal_id.str(), std::string("fd77164bf3898a6a8f27ccff440d17ef6870e75c368fcc93b969066cec70939c")); + BOOST_CHECK_EQUAL(fs_bpb.b_lock.str(), std::string("6462add7d157da87931c859cb689f722003a20f30c0f1408d11b872020903b85")); + BOOST_CHECK_EQUAL(fs_bpb.b_exec.str(), std::string("1511035fdcbabdc5e272a3ac19356536252884ed77077cf871ae5029a7502279")); + + //check bpa as well + qcc_bpa->second->get_state(fs_bpa); + BOOST_CHECK_EQUAL(fs_bpa.high_qc.proposal_id.str(), std::string("fd77164bf3898a6a8f27ccff440d17ef6870e75c368fcc93b969066cec70939c")); + BOOST_CHECK_EQUAL(fs_bpa.b_lock.str(), std::string("6462add7d157da87931c859cb689f722003a20f30c0f1408d11b872020903b85")); + BOOST_CHECK_EQUAL(fs_bpa.b_exec.str(), std::string("1511035fdcbabdc5e272a3ac19356536252884ed77077cf871ae5029a7502279")); + + //check bpc as well + qcc_bpc->second->get_state(fs_bpc); + BOOST_CHECK_EQUAL(fs_bpc.high_qc.proposal_id.str(), std::string("fd77164bf3898a6a8f27ccff440d17ef6870e75c368fcc93b969066cec70939c")); + BOOST_CHECK_EQUAL(fs_bpc.b_lock.str(), std::string("6462add7d157da87931c859cb689f722003a20f30c0f1408d11b872020903b85")); + BOOST_CHECK_EQUAL(fs_bpc.b_exec.str(), std::string("1511035fdcbabdc5e272a3ac19356536252884ed77077cf871ae5029a7502279")); + + BOOST_CHECK_EQUAL(fs_bpa.b_finality_violation.str(), std::string("0000000000000000000000000000000000000000000000000000000000000000")); + +} FC_LOG_AND_RETHROW(); + BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/hotstuff/test/test_pacemaker.cpp b/libraries/hotstuff/test/test_pacemaker.cpp index 87aa3822a1..5780c5624c 100644 --- a/libraries/hotstuff/test/test_pacemaker.cpp +++ b/libraries/hotstuff/test/test_pacemaker.cpp @@ -15,7 +15,7 @@ namespace eosio::hotstuff { _next_leader = next_leader; }; - void test_pacemaker::set_finalizers(std::vector finalizers) { + void test_pacemaker::set_finalizers(const std::vector& finalizers) { _finalizers = finalizers; }; @@ -27,11 +27,36 @@ namespace eosio::hotstuff { _quorum_threshold = threshold; } - void test_pacemaker::add_message_to_queue(hotstuff_message msg) { + void test_pacemaker::add_message_to_queue(const hotstuff_message& msg) { _pending_message_queue.push_back(msg); } - void test_pacemaker::pipe(std::vector messages) { + void test_pacemaker::connect(const std::vector& nodes) { + for (auto it1 = nodes.begin(); it1 != nodes.end(); ++it1) { + for (auto it2 = std::next(it1); it2 != nodes.end(); ++it2) { + _net[*it1].insert(*it2); + _net[*it2].insert(*it1); + } + } + } + + void test_pacemaker::disconnect(const std::vector& nodes) { + for (auto it1 = nodes.begin(); it1 != nodes.end(); ++it1) { + for (auto it2 = std::next(it1); it2 != nodes.end(); ++it2) { + _net[*it1].erase(*it2); + _net[*it2].erase(*it1); + } + } + } + + bool test_pacemaker::is_connected(name node1, name node2) { + auto it = _net.find(node1); + if (it == _net.end()) + return false; + return it->second.count(node2) > 0; + } + + void test_pacemaker::pipe(const std::vector& messages) { auto itr = messages.begin(); while (itr != messages.end()) { _pending_message_queue.push_back(*itr); @@ -39,17 +64,22 @@ namespace eosio::hotstuff { } } - void test_pacemaker::dispatch(std::string memo, int count) { + void test_pacemaker::dispatch(std::string memo, int count, hotstuff_message_index msg_type) { for (int i = 0 ; i < count ; i++) { - this->dispatch(memo); + this->dispatch(memo, msg_type); } } - std::vector test_pacemaker::dispatch(std::string memo) { + std::vector test_pacemaker::dispatch(std::string memo, hotstuff_message_index msg_type) { + + std::vector dispatched_messages; + std::vector kept_messages; - std::vector dispatched_messages = _pending_message_queue; - _message_queue = _pending_message_queue; + std::vector message_queue = _pending_message_queue; + // Need to clear the persisted message queue here because new ones are inserted in + // the loop below as a side-effect of the on_hs...() calls. Messages that are not + // propagated in the loop go into kept_messages and are reinserted after the loop. _pending_message_queue.clear(); size_t proposals_count = 0; @@ -57,37 +87,35 @@ namespace eosio::hotstuff { size_t new_blocks_count = 0; size_t new_views_count = 0; - auto msg_itr = _message_queue.begin(); - while (msg_itr!=_message_queue.end()) { - - size_t v_index = msg_itr->second.index(); - - if (v_index==0) - ++proposals_count; - else if (v_index==1) - ++votes_count; - else if (v_index==2) - ++new_blocks_count; - else if (v_index==3) - ++new_views_count; - else - throw std::runtime_error("unknown message variant"); - - if (msg_itr->second.index() == 0) - on_hs_proposal_msg(std::get(msg_itr->second), msg_itr->first); - else if (msg_itr->second.index() == 1) - on_hs_vote_msg(std::get(msg_itr->second), msg_itr->first); - else if (msg_itr->second.index() == 2) - on_hs_new_block_msg(std::get(msg_itr->second), msg_itr->first); - else if (msg_itr->second.index() == 3) - on_hs_new_view_msg(std::get(msg_itr->second), msg_itr->first); - else - throw std::runtime_error("unknown message variant"); - - ++msg_itr; + for (const auto& msg_pair : message_queue) { + const auto& [sender_id, msg] = msg_pair; + size_t v_index = msg.index(); + + if (msg_type == hs_all_messages || msg_type == v_index) { + + if (v_index == hs_proposal) { + ++proposals_count; + on_hs_proposal_msg(std::get(msg), sender_id); + } else if (v_index == hs_vote) { + ++votes_count; + on_hs_vote_msg(std::get(msg), sender_id); + } else if (v_index == hs_new_block) { + ++new_blocks_count; + on_hs_new_block_msg(std::get(msg), sender_id); + } else if (v_index == hs_new_view) { + ++new_views_count; + on_hs_new_view_msg(std::get(msg), sender_id); + } else { + throw std::runtime_error("unknown message variant"); + } + + dispatched_messages.push_back(msg_pair); + } else { + kept_messages.push_back(msg_pair); + } } - _message_queue.clear(); + _pending_message_queue.insert(_pending_message_queue.end(), kept_messages.begin(), kept_messages.end()); if (memo != "") { ilog(" === ${memo} : ", ("memo", memo)); @@ -177,46 +205,30 @@ namespace eosio::hotstuff { void test_pacemaker::send_hs_message_warning(const uint32_t sender_peer, const chain::hs_message_warning code) { } void test_pacemaker::on_hs_proposal_msg(const hs_proposal_message& msg, name id) { - auto qc_itr = _qcc_store.begin(); - while (qc_itr != _qcc_store.end()){ - const name & qcc_name = qc_itr->first; - std::shared_ptr & qcc_ptr = qc_itr->second; - if (qcc_ptr->get_id_i() != id && is_qc_chain_active(qcc_name) ) + for (const auto& [qcc_name, qcc_ptr] : _qcc_store) { + if (qcc_ptr->get_id_i() != id && is_qc_chain_active(qcc_name) && is_connected(id, qcc_ptr->get_id_i())) qcc_ptr->on_hs_proposal_msg(0, msg); - qc_itr++; } } void test_pacemaker::on_hs_vote_msg(const hs_vote_message& msg, name id) { - auto qc_itr = _qcc_store.begin(); - while (qc_itr != _qcc_store.end()) { - const name & qcc_name = qc_itr->first; - std::shared_ptr & qcc_ptr = qc_itr->second; - if (qcc_ptr->get_id_i() != id && is_qc_chain_active(qcc_name) ) + for (const auto& [qcc_name, qcc_ptr] : _qcc_store) { + if (qcc_ptr->get_id_i() != id && is_qc_chain_active(qcc_name) && is_connected(id, qcc_ptr->get_id_i())) qcc_ptr->on_hs_vote_msg(0, msg); - qc_itr++; } } void test_pacemaker::on_hs_new_block_msg(const hs_new_block_message& msg, name id) { - auto qc_itr = _qcc_store.begin(); - while (qc_itr != _qcc_store.end()) { - const name & qcc_name = qc_itr->first; - std::shared_ptr & qcc_ptr = qc_itr->second; - if (qcc_ptr->get_id_i() != id && is_qc_chain_active(qcc_name) ) + for (const auto& [qcc_name, qcc_ptr] : _qcc_store) { + if (qcc_ptr->get_id_i() != id && is_qc_chain_active(qcc_name) && is_connected(id, qcc_ptr->get_id_i())) qcc_ptr->on_hs_new_block_msg(0, msg); - qc_itr++; } } void test_pacemaker::on_hs_new_view_msg(const hs_new_view_message& msg, name id) { - auto qc_itr = _qcc_store.begin(); - while (qc_itr != _qcc_store.end()){ - const name & qcc_name = qc_itr->first; - std::shared_ptr & qcc_ptr = qc_itr->second; - if (qcc_ptr->get_id_i() != id && is_qc_chain_active(qcc_name) ) + for (const auto& [qcc_name, qcc_ptr] : _qcc_store) { + if (qcc_ptr->get_id_i() != id && is_qc_chain_active(qcc_name) && is_connected(id, qcc_ptr->get_id_i())) qcc_ptr->on_hs_new_view_msg(0, msg); - qc_itr++; } }