diff --git a/libraries/hotstuff/include/eosio/hotstuff/test_pacemaker.hpp b/libraries/hotstuff/include/eosio/hotstuff/test_pacemaker.hpp index 258086e1be..b093b14c84 100644 --- a/libraries/hotstuff/include/eosio/hotstuff/test_pacemaker.hpp +++ b/libraries/hotstuff/include/eosio/hotstuff/test_pacemaker.hpp @@ -9,12 +9,20 @@ 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); @@ -27,13 +35,21 @@ namespace eosio { namespace hotstuff { 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(std::string node1, std::string node2); - void dispatch(std::string memo, int count); + void pipe(const std::vector& messages); - std::vector dispatch(std::string memo); + void duplicate(hotstuff_message_index msg_type); + + 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); @@ -66,6 +82,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 @@ -74,9 +92,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/qc_chain.cpp b/libraries/hotstuff/qc_chain.cpp index 981bd9aff4..4f6162bdbf 100644 --- a/libraries/hotstuff/qc_chain.cpp +++ b/libraries/hotstuff/qc_chain.cpp @@ -426,6 +426,15 @@ namespace eosio::hotstuff { auto increment_version = fc::make_scoped_exit([this]() { ++_state_version; }); const hs_bitset& finalizer_set = _current_qc.get_active_finalizers(); + + // if a finalizer has already aggregated a vote signature for the current QC, just discard this vote + + const auto& finalizers = _pacemaker->get_finalizer_set().finalizers; + for (size_t i=0; i replicas, std::vector replica_keys){ _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 (fc::crypto::blslib::bls_private_key r : replicas) { for (size_t i = 0 ; i < replicas.size() ; i++){ fc::crypto::blslib::bls_private_key sk = fc::crypto::blslib::bls_private_key(replica_keys[i]); bls_key_map_t keys{{sk.get_public_key(), sk}}; @@ -113,10 +107,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"; @@ -151,6 +141,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;i keys){ BOOST_AUTO_TEST_CASE(hotstuff_1) try { //test optimistic responsiveness (3 confirmations per block) test_pacemaker tpm; + tpm.connect(unique_replica_keys); // complete connection graph + hotstuff_test_handler ht; std::vector sks = map_to_sks(unique_replica_keys); finalizer_set fset = create_fs(unique_replica_keys); @@ -326,6 +323,8 @@ BOOST_AUTO_TEST_CASE(hotstuff_2) try { //test slower network (1 confirmation per block) test_pacemaker tpm; + tpm.connect(unique_replica_keys); // complete connection graph + hotstuff_test_handler ht; std::vector sks = map_to_sks(unique_replica_keys); finalizer_set fset = create_fs(unique_replica_keys); @@ -414,6 +413,8 @@ BOOST_AUTO_TEST_CASE(hotstuff_2) try { //test leader rotation test_pacemaker tpm; + tpm.connect(unique_replica_keys); // complete connection graph + hotstuff_test_handler ht; std::vector sks = map_to_sks(unique_replica_keys); finalizer_set fset = create_fs(unique_replica_keys); @@ -533,6 +534,8 @@ BOOST_AUTO_TEST_CASE(hotstuff_4) try { //test loss and recovery of liveness on new block test_pacemaker tpm; + tpm.connect(unique_replica_keys); // complete connection graph + hotstuff_test_handler ht; std::vector sks = map_to_sks(unique_replica_keys); finalizer_set fset = create_fs(unique_replica_keys); @@ -764,7 +767,9 @@ BOOST_AUTO_TEST_CASE(hotstuff_5) try { //simulating a fork, where test_pacemaker tpm1; + tpm1.connect(replica_set_1); // complete connection graph test_pacemaker tpm2; + tpm2.connect(replica_set_2); // complete connection graph hotstuff_test_handler ht1; hotstuff_test_handler ht2; @@ -898,6 +903,8 @@ BOOST_AUTO_TEST_CASE(hotstuff_5) try { //test simple separation between the (single) proposer and the leader; includes one leader rotation test_pacemaker tpm; + tpm.connect(unique_replica_keys); // complete connection graph + hotstuff_test_handler ht; std::vector sks = map_to_sks(unique_replica_keys); finalizer_set fset = create_fs(unique_replica_keys); @@ -1014,5 +1021,282 @@ BOOST_AUTO_TEST_CASE(hotstuff_5) try { } FC_LOG_AND_RETHROW(); +BOOST_AUTO_TEST_CASE(hotstuff_7) try { + + //test leader rotation with a non-complete connection graph (simple message propagation test) + + test_pacemaker tpm; + tpm.connect(unique_replica_keys); // start with a complete connection graph, then subtract + + // TODO: when propagation is implemented in qc_chain, uncomment this to force an additional hop of communication between A and B + //tpm.disconnect( { "bpa"_n, "bpb"_n } ); // if propagation is implemented and works, the inclusion of this line is OK and doesn't fail the test + + hotstuff_test_handler ht; + std::vector sks = map_to_sks(unique_replica_keys); + finalizer_set fset = create_fs(unique_replica_keys); + + ht.initialize_qc_chains(tpm, unique_replicas, sks); + tpm.set_proposer("bpa"_n); + tpm.set_leader("bpa"_n); + tpm.set_next_leader("bpa"_n); + tpm.set_finalizer_set(fset); + + auto qcc_bpa = std::find_if(ht._qc_chains.begin(), ht._qc_chains.end(), [&](const auto& q){ return q.first == "bpa"_n; }); + finalizer_state fs_bpa; + qcc_bpa->second->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_CASE(hotstuff_8) try { + + //test optimistic responsiveness (3 confirmations per block) + //same as hotstuff_1, but with a duplication of vote messages as a regression test for vote duplication filtering + + test_pacemaker tpm; + tpm.connect(unique_replica_keys); // complete connection graph + + hotstuff_test_handler ht; + std::vector sks = map_to_sks(unique_replica_keys); + finalizer_set fset = create_fs(unique_replica_keys); + + ht.initialize_qc_chains(tpm, unique_replicas, sks); + tpm.set_proposer("bpa"_n); + tpm.set_leader("bpa"_n); + tpm.set_next_leader("bpa"_n); + tpm.set_finalizer_set(fset); + + auto qcc_bpa = std::find_if(ht._qc_chains.begin(), ht._qc_chains.end(), [&](const auto& q){ return q.first == "bpa"_n; }); + finalizer_state fs_bpa; + qcc_bpa->second->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); + + ht.print_bp_state("bpa"_n, ""); + + tpm.set_current_block_id(ids[0]); //first block + + tpm.beat(); //produce first block and associated proposal + + tpm.dispatch(""); //send proposal to replicas (prepare on first block) + + ht.print_bp_state("bpa"_n, ""); + + 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")); + + // produce duplicate votes: should not fail the test if qc_chain is filtering duplicate votes. + // we cannot use pipe(dispatch()) here because pipe will append the duplicate votes like this to the pending message queue: + // abcdefghijklmnopqrstuabcdefghijklmnopqrstu + // however, after receiving 15 unique votes, the quorum is met and the duplicate votes are discared by the quorum rule. + // tpm.duplicate() will duplicate like this: aabbccddee...ssttuu, which will exercise the duplicate vote filter (bitset test). + tpm.duplicate(test_pacemaker::hs_vote); + + tpm.dispatch(""); //send votes on proposal (prepareQC on first block) + + tpm.dispatch(""); //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")); + + tpm.dispatch(""); //propagating votes on new proposal (precommitQC on first block) + + tpm.dispatch(""); //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.dispatch(""); //propagating votes on new proposal (commitQC on first block) + + tpm.dispatch(""); //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")); + + tpm.dispatch(""); //propagating votes on new proposal (decide on first block) + + tpm.set_current_block_id(ids[1]); //second block + + tpm.beat(); //produce second block and associated proposal + + tpm.dispatch(""); //send proposal to replicas (prepare on second block) + + qcc_bpa->second->get_state(fs_bpa); + BOOST_CHECK_EQUAL(fs_bpa.b_leaf.str(), std::string("a8c84b7f9613aebf2ae34f457189d58de95a6b0a50d103a4c9e6405180d6fffb")); + BOOST_CHECK_EQUAL(fs_bpa.high_qc.proposal_id.str(), std::string("487e5fcbf2c515618941291ae3b6dcebb68942983d8ac3f61c4bdd9901dadbe7")); + BOOST_CHECK_EQUAL(fs_bpa.b_lock.str(), std::string("aedf8bb1ee70bd6e743268f7fe0f8171418aa43a68bb9c6e7329ffa856896c09")); + BOOST_CHECK_EQUAL(fs_bpa.b_exec.str(), std::string("4b43fb144a8b5e874777f61f3b37d7a8b06c33fbc48db464ce0e8788ff4edb4f")); + + tpm.dispatch(""); //send votes on proposal (prepareQC on second block) + + tpm.dispatch(""); //send proposal to replicas (precommit on second block) + + qcc_bpa->second->get_state(fs_bpa); + BOOST_CHECK_EQUAL(fs_bpa.b_leaf.str(), std::string("4af7c22e5220a61ac96c35533539e65d398e9f44de4c6e11b5b0279e7a79912f")); + BOOST_CHECK_EQUAL(fs_bpa.high_qc.proposal_id.str(), std::string("a8c84b7f9613aebf2ae34f457189d58de95a6b0a50d103a4c9e6405180d6fffb")); + BOOST_CHECK_EQUAL(fs_bpa.b_lock.str(), std::string("487e5fcbf2c515618941291ae3b6dcebb68942983d8ac3f61c4bdd9901dadbe7")); + BOOST_CHECK_EQUAL(fs_bpa.b_exec.str(), std::string("aedf8bb1ee70bd6e743268f7fe0f8171418aa43a68bb9c6e7329ffa856896c09")); + + tpm.dispatch(""); //propagating votes on new proposal (precommitQC on second block) + + tpm.dispatch(""); //send proposal to replicas (commit on second block) + + qcc_bpa->second->get_state(fs_bpa); + BOOST_CHECK_EQUAL(fs_bpa.b_leaf.str(), std::string("ab04f499892ad5ebd209d54372fd5c0bda0288410a084b55c70eda40514044f3")); + BOOST_CHECK_EQUAL(fs_bpa.high_qc.proposal_id.str(), std::string("4af7c22e5220a61ac96c35533539e65d398e9f44de4c6e11b5b0279e7a79912f")); + BOOST_CHECK_EQUAL(fs_bpa.b_lock.str(), std::string("a8c84b7f9613aebf2ae34f457189d58de95a6b0a50d103a4c9e6405180d6fffb")); + BOOST_CHECK_EQUAL(fs_bpa.b_exec.str(), std::string("487e5fcbf2c515618941291ae3b6dcebb68942983d8ac3f61c4bdd9901dadbe7")); + + tpm.dispatch(""); //propagating votes on new proposal (commitQC on second block) + + tpm.dispatch(""); //send proposal to replicas (decide on second block) + + qcc_bpa->second->get_state(fs_bpa); + BOOST_CHECK_EQUAL(fs_bpa.b_leaf.str(), std::string("9eeffb58a16133517d8d2f6f90b8a3420269de3356362677055b225a44a7c151")); + BOOST_CHECK_EQUAL(fs_bpa.high_qc.proposal_id.str(), std::string("ab04f499892ad5ebd209d54372fd5c0bda0288410a084b55c70eda40514044f3")); + BOOST_CHECK_EQUAL(fs_bpa.b_lock.str(), std::string("4af7c22e5220a61ac96c35533539e65d398e9f44de4c6e11b5b0279e7a79912f")); + BOOST_CHECK_EQUAL(fs_bpa.b_exec.str(), std::string("a8c84b7f9613aebf2ae34f457189d58de95a6b0a50d103a4c9e6405180d6fffb")); + + tpm.dispatch(""); //send proposal to replicas (decide on second block) + + qcc_bpa->second->get_state(fs_bpa); + BOOST_CHECK_EQUAL(fs_bpa.b_leaf.str(), std::string("9eeffb58a16133517d8d2f6f90b8a3420269de3356362677055b225a44a7c151")); + BOOST_CHECK_EQUAL(fs_bpa.high_qc.proposal_id.str(), std::string("9eeffb58a16133517d8d2f6f90b8a3420269de3356362677055b225a44a7c151")); + BOOST_CHECK_EQUAL(fs_bpa.b_lock.str(), std::string("4af7c22e5220a61ac96c35533539e65d398e9f44de4c6e11b5b0279e7a79912f")); + BOOST_CHECK_EQUAL(fs_bpa.b_exec.str(), std::string("a8c84b7f9613aebf2ae34f457189d58de95a6b0a50d103a4c9e6405180d6fffb")); + + //check bpb as well + qcc_bpb->second->get_state(fs_bpb); + BOOST_CHECK_EQUAL(fs_bpb.high_qc.proposal_id.str(), std::string("ab04f499892ad5ebd209d54372fd5c0bda0288410a084b55c70eda40514044f3")); + BOOST_CHECK_EQUAL(fs_bpb.b_lock.str(), std::string("4af7c22e5220a61ac96c35533539e65d398e9f44de4c6e11b5b0279e7a79912f")); + BOOST_CHECK_EQUAL(fs_bpb.b_exec.str(), std::string("a8c84b7f9613aebf2ae34f457189d58de95a6b0a50d103a4c9e6405180d6fffb")); + + 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 e2ceb4f0b0..db5a5947b8 100644 --- a/libraries/hotstuff/test/test_pacemaker.cpp +++ b/libraries/hotstuff/test/test_pacemaker.cpp @@ -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(std::string node1, std::string 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,34 @@ 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); + } + } + + void test_pacemaker::duplicate(hotstuff_message_index msg_type) { + std::vector dup; + for (const auto& msg_pair : _pending_message_queue) { + const auto& [sender_id, msg] = msg_pair; + size_t v_index = msg.index(); + dup.push_back(msg_pair); + if (v_index == msg_type) + dup.push_back(msg_pair); } + _pending_message_queue = std::move(dup); } - 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 +99,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)); @@ -178,47 +218,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, const std::string& 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, const std::string& 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, const std::string& 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, const std::string& 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++; } }