Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IF: Implement capabilities to modify, reorder, drop, and duplicate votes and add comprehensive vote processing Boost unit tests #2234

Merged
merged 9 commits into from
Feb 15, 2024

Conversation

linh2931
Copy link
Member

Implemented a test cluster consisting of 3 nodes (controllers) with capabilities to modify, reorder, drop, and duplicate votes:

  • Added 18 tests to cover vote processing, including IF transitions, long run of 1000 blocks, conflicting votes, delayed votes, dropped votes, out of order votes.
  • Fixed a mutex lockup issue.
  • Fixed a missed voting status check issue.

Resolved #2193 and #2147

libraries/chain/include/eosio/chain/controller.hpp Outdated Show resolved Hide resolved
unittests/finality_tests.cpp Outdated Show resolved Hide resolved
unittests/finality_tests.cpp Outdated Show resolved Hide resolved
unittests/finality_tests.cpp Outdated Show resolved Hide resolved
unittests/finality_tests.cpp Show resolved Hide resolved
unittests/finality_tests.cpp Outdated Show resolved Hide resolved
unittests/finality_tests.cpp Outdated Show resolved Hide resolved
@linh2931 linh2931 requested a review from heifner February 13, 2024 17:52
@ericpassmore
Copy link
Contributor

Note:start
group: IF
category: TEST
summary: Test cluster with additional coverage for vote processing, including IF transitions, long run of 1000 blocks, conflicting votes, delayed votes, dropped votes, and out of order votes.
Note:end

return {status, strong ? core.final_on_strong_qc_block_num : std::optional<uint32_t>{}};
return {status,
(status == vote_status::success && strong) ?
core.final_on_strong_qc_block_num : std::optional<uint32_t>{}};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change? It is not needed imo.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When it returns a non-null-optional, the caller will go ahead to call set_if_irreversible_block_num which is a waste. I also think it is cleaner: if a vote is bad, both return values (status and strong) should be set properly so that they cannot be used. @heifner ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my branch I have this as:

      std::optional<uint32_t> new_lib{};
      if (status == vote_status::success && state == pending_quorum_certificate::state_t::strong)
         new_lib = core.final_on_strong_qc_block_num;
      return {status, state, new_lib};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with Kevin's version.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It occurs to me that this is not correct. It assumes only votes for this->core comes in. This really should be updated_core. I will make that change in my PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I liked it better before any change. The meaning was clear. We're asking block_state to aggregate a vote into its pending_qc, and it returns a pair where:

pair.first is the status of applying the vote (so we know whether to propagate the vote)
pair.second is the new lib that pending_qc certifies (if it achieved strong status). The caller decides what to do with this info.

I'm not understanding why it helps to make it more complicated than that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is changing in my PR. We have to let fork_db tell us new lib.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't still fine to let aggregate_vote tell you which block its pending_qc is making final, even if that's not the lib that the controller will end up storing?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially, but as is it is not telling you the correct answer because it is not correctly taking previous votes into account. It is only looking at core not updated_core.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not understanding what updated_core is about? I thought you may need to update the core of other block state (the descendants), but why the one we are voting on?

Comment on lines 155 to 160
eosio::testing::tester node1;
eosio::testing::tester node2;
eosio::testing::tester node3;
uint32_t node1_prev_lib_bum {0};
uint32_t node2_prev_lib_bum {0};
uint32_t node3_prev_lib_bum {0};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a std::array<node_info, 3> with

struct node_info {
    eosio::testing::tester   node;
    uint32_t                 node_prev_lib_bum;
};

would remove code duplication (in member functions and elsewhere).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Done

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also move the implementation out of .hpp file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat minor, but I wonder if you shouldn't name them node0, node1, node2 if you use the array approach so they match index.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will make the change. That reduces confusion about which node is which. Thanks.

…ion; use array to represent nodes to reduce duplicate code
@linh2931 linh2931 requested review from heifner and greg7mdp February 14, 2024 16:37
…and reduce confusion; refactor return code handling in aggregate_vote
}

// called by add_vote, already protected by mutex
vote_status pending_quorum_certificate::add_strong_vote(const std::vector<uint8_t>& proposal_digest, size_t index,
const bls_public_key& pubkey, const bls_signature& sig,
uint64_t weight) {
if (auto s = _strong_votes.add_vote(proposal_digest, index, pubkey, sig); s != vote_status::success)
if (auto s = _strong_votes.add_vote(proposal_digest, index, pubkey, sig); s != vote_status::success) {
dlog("add_strong_vote returned failure");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this dlog useful since we have a non-conditional dlog in add_vote?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only happening when duplicate occurs; useful for debugging.

Comment on lines 77 to 79
static constexpr size_t node0 = 0; // node0 index in nodes array
static constexpr size_t node1 = 1; // node1 index in nodes array
static constexpr size_t node2 = 2; // node2 index in nodes array
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code will be cleaner if you define these instead:

   node_info& node0 = node[0];
   node_info& node1 = node[1];
   node_info& node2 = node[2];

and:

   void setup_node(node_info& node, eosio::chain::account_name local_finalizer);
   bool lib_advancing(node_info& node);
   eosio::chain::vote_status process_vote(node_info& node, size_t vote_index, vote_mode mode);
   eosio::chain::vote_status process_vote(node_info& node, vote_mode mode);

Code will look like this:

finality_test_cluster::finality_test_cluster() {
   using namespace eosio::testing;

   setup_node(node0, "node0"_n);
   setup_node(node1, "node1"_n);
   setup_node(node2, "node2"_n);

   // collect node1's votes
   node1.node.control->voted_block().connect( [&]( const eosio::chain::vote_message& vote ) {
      node1.votes.emplace_back(vote);
   });
   // collect node2's votes
   node2.node.control->voted_block().connect( [&]( const eosio::chain::vote_message& vote ) {
      node2.votes.emplace_back(vote);
   });
...

Comment on lines 35 to 38
for (size_t i = 0; i < node.size(); ++i) {
node[i].votes.clear();
node[i].prev_lib_num = node[i].node.control->if_irreversible_block_num();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for (size_t i = 0; i < node.size(); ++i) {
node[i].votes.clear();
node[i].prev_lib_num = node[i].node.control->if_irreversible_block_num();
}
for (auto& n : nodes {
n.votes.clear();
n.prev_lib_num = n.node.control->if_irreversible_block_num();
}

@linh2931 linh2931 merged commit 2c77713 into hotstuff_integration Feb 15, 2024
26 checks passed
@linh2931 linh2931 deleted the vote_tests branch February 15, 2024 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants