Skip to content

Commit

Permalink
Filter duplicate votes
Browse files Browse the repository at this point in the history
- qc_chain does not aggregate a signature from a finalizer that has already voted on a proposal
- added test_pacemaker::duplicate()
- added hotstuff_8 unit test to test for duplicate votes

This fix is related to #1548 (lack of duplicate filtering only manifests with message propagation)
  • Loading branch information
fcecin committed Sep 23, 2023
1 parent 10d3435 commit c764526
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 0 deletions.
2 changes: 2 additions & 0 deletions libraries/hotstuff/include/eosio/hotstuff/test_pacemaker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ namespace eosio { namespace hotstuff {

void pipe(const std::vector<test_pacemaker::hotstuff_message>& messages);

void duplicate(hotstuff_message_index msg_type);

void dispatch(std::string memo, int count, hotstuff_message_index msg_type = hs_all_messages);

std::vector<hotstuff_message> dispatch(std::string memo, hotstuff_message_index msg_type = hs_all_messages);
Expand Down
8 changes: 8 additions & 0 deletions libraries/hotstuff/qc_chain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,14 @@ 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
vector<name> finalizers = _pacemaker->get_finalizers();
for (size_t i=0; i<finalizers.size(); ++i)
if (finalizers[i] == vote.finalizer)
if (finalizer_set.test(i))
return;

if (finalizer_set.any())
_current_qc.set_active_agg_sig(fc::crypto::blslib::aggregate({_current_qc.get_active_agg_sig(), vote.sig }));
else
Expand Down
139 changes: 139 additions & 0 deletions libraries/hotstuff/test/test_hotstuff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1162,5 +1162,144 @@ BOOST_AUTO_TEST_CASE(hotstuff_7) try {

} 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_replicas); // complete connection graph

hotstuff_test_handler ht;

ht.initialize_qc_chains(tpm, unique_replicas);

tpm.set_proposer("bpa"_n);
tpm.set_leader("bpa"_n);
tpm.set_next_leader("bpa"_n);
tpm.set_finalizers(unique_replicas);

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()
12 changes: 12 additions & 0 deletions libraries/hotstuff/test/test_pacemaker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ namespace eosio::hotstuff {
}
}

void test_pacemaker::duplicate(hotstuff_message_index msg_type) {
std::vector<test_pacemaker::hotstuff_message> 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 = dup;
}

std::vector<test_pacemaker::hotstuff_message> test_pacemaker::dispatch(std::string memo, hotstuff_message_index msg_type) {

std::vector<test_pacemaker::hotstuff_message> dispatched_messages;
Expand Down

0 comments on commit c764526

Please sign in to comment.