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

Store full votes in vote cache #4518

Merged
merged 10 commits into from
Mar 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 18 additions & 20 deletions nano/core_test/active_transactions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ TEST (active_transactions, keep_local)
// ASSERT_EQ (1, node.scheduler.size ());
}

TEST (active_transactions, inactive_votes_cache)
TEST (inactive_votes_cache, basic)
{
nano::test::system system (1);
auto & node = *system.nodes[0];
Expand All @@ -259,7 +259,7 @@ TEST (active_transactions, inactive_votes_cache)
/**
* This test case confirms that a non final vote cannot cause an election to become confirmed
*/
TEST (active_transactions, inactive_votes_cache_non_final)
TEST (inactive_votes_cache, non_final)
{
nano::test::system system (1);
auto & node = *system.nodes[0];
Expand All @@ -285,7 +285,7 @@ TEST (active_transactions, inactive_votes_cache_non_final)
ASSERT_FALSE (election->confirmed ());
}

TEST (active_transactions, inactive_votes_cache_fork)
TEST (inactive_votes_cache, fork)
{
nano::test::system system{ 1 };
auto & node = *system.nodes[0];
Expand Down Expand Up @@ -325,7 +325,7 @@ TEST (active_transactions, inactive_votes_cache_fork)
ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached));
}

TEST (active_transactions, inactive_votes_cache_existing_vote)
TEST (inactive_votes_cache, existing_vote)
{
nano::test::system system;
nano::node_config node_config = system.default_config ();
Expand Down Expand Up @@ -365,12 +365,13 @@ TEST (active_transactions, inactive_votes_cache_existing_vote)
ASSERT_EQ (send->hash (), last_vote1.hash);
ASSERT_EQ (nano::vote::timestamp_min * 1, last_vote1.timestamp);
// Attempt to change vote with inactive_votes_cache
nano::unique_lock<nano::mutex> active_lock (node.active.mutex);
node.vote_cache.vote (send->hash (), vote1);
auto cache = node.vote_cache.find (send->hash ());
ASSERT_TRUE (cache);
ASSERT_EQ (1, cache->voters ().size ());
cache->fill (election);
node.vote_cache.insert (vote1);
auto cached = node.vote_cache.find (send->hash ());
ASSERT_EQ (1, cached.size ());
for (auto const & cached_vote : cached)
{
node.active.vote (cached_vote);
}
// Check that election data is not changed
ASSERT_EQ (2, election->votes ().size ());
auto last_vote2 (election->votes ()[key.pub]);
Expand All @@ -380,7 +381,7 @@ TEST (active_transactions, inactive_votes_cache_existing_vote)
ASSERT_EQ (0, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached));
}

TEST (active_transactions, inactive_votes_cache_multiple_votes)
TEST (inactive_votes_cache, multiple_votes)
{
nano::test::system system;
nano::node_config node_config = system.default_config ();
Expand Down Expand Up @@ -426,8 +427,7 @@ TEST (active_transactions, inactive_votes_cache_multiple_votes)
auto vote2 = nano::test::make_vote (nano::dev::genesis_key, { send1 }, 0, 0);
node.vote_processor.vote (vote2, std::make_shared<nano::transport::inproc::channel> (node, node));

ASSERT_TIMELY (5s, node.vote_cache.find (send1->hash ()));
ASSERT_TIMELY_EQ (5s, node.vote_cache.find (send1->hash ())->voters ().size (), 2);
ASSERT_TIMELY_EQ (5s, node.vote_cache.find (send1->hash ()).size (), 2);
ASSERT_EQ (1, node.vote_cache.size ());
node.scheduler.priority.activate (nano::dev::genesis_key.pub, node.store.tx_begin_read ());
std::shared_ptr<nano::election> election;
Expand All @@ -436,7 +436,7 @@ TEST (active_transactions, inactive_votes_cache_multiple_votes)
ASSERT_EQ (2, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached));
}

TEST (active_transactions, inactive_votes_cache_election_start)
TEST (inactive_votes_cache, election_start)
{
nano::test::system system;
nano::node_config node_config = system.default_config ();
Expand Down Expand Up @@ -528,8 +528,7 @@ TEST (active_transactions, inactive_votes_cache_election_start)
// A late block arrival also checks the inactive votes cache
ASSERT_TRUE (node.active.empty ());
auto send4_cache (node.vote_cache.find (send4->hash ()));
ASSERT_TRUE (send4_cache);
ASSERT_EQ (3, send4_cache->voters ().size ());
ASSERT_EQ (3, send4_cache.size ());
node.process_active (send3);
// An election is started for send6 but does not
ASSERT_FALSE (node.block_confirmed_or_being_confirmed (send3->hash ()));
Expand Down Expand Up @@ -982,8 +981,7 @@ TEST (active_transactions, fork_replacement_tally)
node1.vote_processor.vote (vote, std::make_shared<nano::transport::inproc::channel> (node1, node1));
node1.vote_processor.flush ();
// ensure vote arrives before the block
ASSERT_TIMELY (5s, node1.vote_cache.find (send_last->hash ()));
ASSERT_TIMELY_EQ (5s, 1, node1.vote_cache.find (send_last->hash ())->size ());
ASSERT_TIMELY_EQ (5s, 1, node1.vote_cache.find (send_last->hash ()).size ());
node1.network.publish_filter.clear ();
node2.network.flood_block (send_last);
ASSERT_TIMELY (5s, node1.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in) > 1);
Expand Down Expand Up @@ -1537,7 +1535,7 @@ TEST (active_transactions, allow_limited_overflow)
{
// Non-final vote, so it stays in the AEC without getting confirmed
auto vote = nano::test::make_vote (nano::dev::genesis_key, { block });
node.vote_cache.vote (block->hash (), vote);
node.vote_cache.insert (vote);
}

// Ensure active elections overfill AEC only up to normal + hinted limit
Expand Down Expand Up @@ -1575,7 +1573,7 @@ TEST (active_transactions, allow_limited_overflow_adapt)
{
// Non-final vote, so it stays in the AEC without getting confirmed
auto vote = nano::test::make_vote (nano::dev::genesis_key, { block });
node.vote_cache.vote (block->hash (), vote);
node.vote_cache.insert (vote);
}

// Ensure hinted election amount is bounded by hinted limit
Expand Down
139 changes: 62 additions & 77 deletions nano/core_test/vote_cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ TEST (vote_cache, construction)
ASSERT_EQ (0, vote_cache.size ());
ASSERT_TRUE (vote_cache.empty ());
auto hash1 = nano::test::random_hash ();
ASSERT_FALSE (vote_cache.find (hash1));
ASSERT_TRUE (vote_cache.find (hash1).empty ());
}

/*
Expand All @@ -57,16 +57,12 @@ TEST (vote_cache, insert_one_hash)
auto rep1 = create_rep (7);
auto hash1 = nano::test::random_hash ();
auto vote1 = nano::test::make_vote (rep1, { hash1 }, 1024 * 1024);
vote_cache.vote (vote1->hashes.front (), vote1);
vote_cache.insert (vote1);
ASSERT_EQ (1, vote_cache.size ());

auto peek1 = vote_cache.find (hash1);
ASSERT_TRUE (peek1);
ASSERT_EQ (peek1->hash (), hash1);
ASSERT_EQ (peek1->voters ().size (), 1);
ASSERT_EQ (peek1->voters ().front ().representative, rep1.pub); // account
ASSERT_EQ (peek1->voters ().front ().timestamp, 1024 * 1024); // timestamp
ASSERT_EQ (peek1->tally (), 7);
ASSERT_EQ (peek1.size (), 1);
ASSERT_EQ (peek1.front (), vote1);

auto tops = vote_cache.top (0);
ASSERT_EQ (tops.size (), 1);
Expand All @@ -92,17 +88,17 @@ TEST (vote_cache, insert_one_hash_many_votes)
auto vote1 = nano::test::make_vote (rep1, { hash1 }, 1 * 1024 * 1024);
auto vote2 = nano::test::make_vote (rep2, { hash1 }, 2 * 1024 * 1024);
auto vote3 = nano::test::make_vote (rep3, { hash1 }, 3 * 1024 * 1024);
vote_cache.vote (vote1->hashes.front (), vote1);
vote_cache.vote (vote2->hashes.front (), vote2);
vote_cache.vote (vote3->hashes.front (), vote3);
vote_cache.insert (vote1);
vote_cache.insert (vote2);
vote_cache.insert (vote3);

// We have 3 votes but for a single hash, so just one entry in vote cache
ASSERT_EQ (1, vote_cache.size ());
auto peek1 = vote_cache.find (hash1);
ASSERT_TRUE (peek1);
ASSERT_EQ (peek1->voters ().size (), 3);
// Tally must be the sum of rep weights
ASSERT_EQ (peek1->tally (), 7 + 9 + 11);
ASSERT_EQ (peek1.size (), 3);
// Verify each vote is present
ASSERT_TRUE (std::find (peek1.begin (), peek1.end (), vote1) != peek1.end ());
ASSERT_TRUE (std::find (peek1.begin (), peek1.end (), vote2) != peek1.end ());
ASSERT_TRUE (std::find (peek1.begin (), peek1.end (), vote3) != peek1.end ());

auto tops = vote_cache.top (0);
ASSERT_EQ (tops.size (), 1);
Expand Down Expand Up @@ -136,61 +132,54 @@ TEST (vote_cache, insert_many_hashes_many_votes)
auto vote3 = nano::test::make_vote (rep3, { hash3 }, 1024 * 1024);
auto vote4 = nano::test::make_vote (rep4, { hash1 }, 1024 * 1024);
// Insert first 3 votes in cache
vote_cache.vote (vote1->hashes.front (), vote1);
vote_cache.vote (vote2->hashes.front (), vote2);
vote_cache.vote (vote3->hashes.front (), vote3);
vote_cache.insert (vote1);
vote_cache.insert (vote2);
vote_cache.insert (vote3);
// Ensure all of those are properly inserted
ASSERT_EQ (3, vote_cache.size ());
ASSERT_TRUE (vote_cache.find (hash1));
ASSERT_TRUE (vote_cache.find (hash2));
ASSERT_TRUE (vote_cache.find (hash3));
ASSERT_EQ (1, vote_cache.find (hash1).size ());
ASSERT_EQ (1, vote_cache.find (hash2).size ());
ASSERT_EQ (1, vote_cache.find (hash3).size ());

// Ensure that first entry in queue is the one for hash3 (rep3 has the highest weight of the first 3 reps)
auto tops1 = vote_cache.top (0);
ASSERT_EQ (tops1.size (), 3);
ASSERT_EQ (tops1[0].hash, hash3);
ASSERT_EQ (tops1[0].tally, 11);

auto peek1 = vote_cache.find (tops1[0].hash);
ASSERT_TRUE (peek1);
ASSERT_EQ (peek1->voters ().size (), 1);
ASSERT_EQ (peek1->tally (), 11);
ASSERT_EQ (peek1->hash (), hash3);
auto peek1 = vote_cache.find (hash3);
ASSERT_EQ (peek1.size (), 1);
ASSERT_EQ (peek1.front (), vote3);

// Now add a vote from rep4 with the highest voting weight
vote_cache.vote (vote4->hashes.front (), vote4);
vote_cache.insert (vote4);

// Ensure that the first entry in queue is now the one for hash1 (rep1 + rep4 tally weight)
auto tops2 = vote_cache.top (0);
ASSERT_EQ (tops1.size (), 3);
ASSERT_EQ (tops2[0].hash, hash1);
ASSERT_EQ (tops2[0].tally, 7 + 13);

auto pop1 = vote_cache.find (tops2[0].hash);
ASSERT_TRUE (pop1);
ASSERT_EQ ((*pop1).voters ().size (), 2);
ASSERT_EQ ((*pop1).tally (), 7 + 13);
ASSERT_EQ ((*pop1).hash (), hash1);
auto pop1 = vote_cache.find (hash1);
ASSERT_EQ (pop1.size (), 2);
ASSERT_TRUE (std::find (pop1.begin (), pop1.end (), vote1) != pop1.end ());
ASSERT_TRUE (std::find (pop1.begin (), pop1.end (), vote4) != pop1.end ());

// The next entry in queue should be hash3 (rep3 tally weight)
ASSERT_EQ (tops2[1].hash, hash3);
ASSERT_EQ (tops2[1].tally, 11);

auto pop2 = vote_cache.find (tops2[1].hash);
ASSERT_EQ ((*pop2).voters ().size (), 1);
ASSERT_EQ ((*pop2).tally (), 11);
ASSERT_EQ ((*pop2).hash (), hash3);
ASSERT_TRUE (vote_cache.find (hash3));
auto pop2 = vote_cache.find (hash3);
ASSERT_EQ (pop2.size (), 1);
ASSERT_EQ (pop2.front (), vote3);

// And last one should be hash2 with rep2 tally weight
ASSERT_EQ (tops2[2].hash, hash2);
ASSERT_EQ (tops2[2].tally, 9);

auto pop3 = vote_cache.find (tops2[2].hash);
ASSERT_EQ ((*pop3).voters ().size (), 1);
ASSERT_EQ ((*pop3).tally (), 9);
ASSERT_EQ ((*pop3).hash (), hash2);
ASSERT_TRUE (vote_cache.find (hash2));
auto pop3 = vote_cache.find (hash2);
ASSERT_EQ (pop3.size (), 1);
ASSERT_EQ (pop3.front (), vote2);
}

/*
Expand All @@ -206,8 +195,8 @@ TEST (vote_cache, insert_duplicate)
auto rep1 = create_rep (9);
auto vote1 = nano::test::make_vote (rep1, { hash1 }, 1 * 1024 * 1024);
auto vote2 = nano::test::make_vote (rep1, { hash1 }, 1 * 1024 * 1024);
vote_cache.vote (vote1->hashes.front (), vote1);
vote_cache.vote (vote2->hashes.front (), vote2);
vote_cache.insert (vote1);
vote_cache.insert (vote2);
ASSERT_EQ (1, vote_cache.size ());
}

Expand All @@ -223,18 +212,15 @@ TEST (vote_cache, insert_newer)
auto hash1 = nano::test::random_hash ();
auto rep1 = create_rep (9);
auto vote1 = nano::test::make_vote (rep1, { hash1 }, 1 * 1024 * 1024);
vote_cache.vote (vote1->hashes.front (), vote1);
vote_cache.insert (vote1);
auto peek1 = vote_cache.find (hash1);
ASSERT_TRUE (peek1);
ASSERT_EQ (peek1.size (), 1);
ASSERT_EQ (peek1.front (), vote1);
auto vote2 = nano::test::make_final_vote (rep1, { hash1 });
vote_cache.vote (vote2->hashes.front (), vote2);
vote_cache.insert (vote2);
auto peek2 = vote_cache.find (hash1);
ASSERT_TRUE (peek2);
ASSERT_EQ (1, vote_cache.size ());
ASSERT_EQ (1, peek2->voters ().size ());
// Second entry should have timestamp greater than the first one
ASSERT_GT (peek2->voters ().front ().timestamp, peek1->voters ().front ().timestamp);
ASSERT_EQ (peek2->voters ().front ().timestamp, std::numeric_limits<uint64_t>::max ()); // final timestamp
ASSERT_EQ (peek2.size (), 1);
ASSERT_EQ (peek2.front (), vote2); // vote2 should replace vote1 as it has a higher timestamp
}

/*
Expand All @@ -249,16 +235,15 @@ TEST (vote_cache, insert_older)
auto hash1 = nano::test::random_hash ();
auto rep1 = create_rep (9);
auto vote1 = nano::test::make_vote (rep1, { hash1 }, 2 * 1024 * 1024);
vote_cache.vote (vote1->hashes.front (), vote1);
vote_cache.insert (vote1);
auto peek1 = vote_cache.find (hash1);
ASSERT_TRUE (peek1);
ASSERT_EQ (peek1.size (), 1);
ASSERT_EQ (peek1.front (), vote1);
auto vote2 = nano::test::make_vote (rep1, { hash1 }, 1 * 1024 * 1024);
vote_cache.vote (vote2->hashes.front (), vote2);
vote_cache.insert (vote2);
auto peek2 = vote_cache.find (hash1);
ASSERT_TRUE (peek2);
ASSERT_EQ (1, vote_cache.size ());
ASSERT_EQ (1, peek2->voters ().size ());
ASSERT_EQ (peek2->voters ().front ().timestamp, peek1->voters ().front ().timestamp); // timestamp2 == timestamp1
ASSERT_EQ (peek2.size (), 1);
ASSERT_EQ (peek2.front (), vote1); // vote1 should still be in cache as it has a higher timestamp
}

/*
Expand All @@ -280,24 +265,24 @@ TEST (vote_cache, erase)
auto vote1 = nano::test::make_vote (rep1, { hash1 }, 1024 * 1024);
auto vote2 = nano::test::make_vote (rep2, { hash2 }, 1024 * 1024);
auto vote3 = nano::test::make_vote (rep3, { hash3 }, 1024 * 1024);
vote_cache.vote (vote1->hashes.front (), vote1);
vote_cache.vote (vote2->hashes.front (), vote2);
vote_cache.vote (vote3->hashes.front (), vote3);
vote_cache.insert (vote1);
vote_cache.insert (vote2);
vote_cache.insert (vote3);
ASSERT_EQ (3, vote_cache.size ());
ASSERT_FALSE (vote_cache.empty ());
ASSERT_TRUE (vote_cache.find (hash1));
ASSERT_TRUE (vote_cache.find (hash2));
ASSERT_TRUE (vote_cache.find (hash3));
ASSERT_FALSE (vote_cache.find (hash1).empty ());
ASSERT_FALSE (vote_cache.find (hash2).empty ());
ASSERT_FALSE (vote_cache.find (hash3).empty ());
vote_cache.erase (hash2);
ASSERT_EQ (2, vote_cache.size ());
ASSERT_TRUE (vote_cache.find (hash1));
ASSERT_FALSE (vote_cache.find (hash2));
ASSERT_TRUE (vote_cache.find (hash3));
ASSERT_FALSE (vote_cache.find (hash1).empty ());
ASSERT_TRUE (vote_cache.find (hash2).empty ());
ASSERT_FALSE (vote_cache.find (hash3).empty ());
vote_cache.erase (hash1);
vote_cache.erase (hash3);
ASSERT_FALSE (vote_cache.find (hash1));
ASSERT_FALSE (vote_cache.find (hash2));
ASSERT_FALSE (vote_cache.find (hash3));
ASSERT_TRUE (vote_cache.find (hash1).empty ());
ASSERT_TRUE (vote_cache.find (hash2).empty ());
ASSERT_TRUE (vote_cache.find (hash3).empty ());
ASSERT_TRUE (vote_cache.empty ());
}

Expand All @@ -319,7 +304,7 @@ TEST (vote_cache, overfill)
auto rep1 = create_rep (count - n);
auto hash1 = nano::test::random_hash ();
auto vote1 = nano::test::make_vote (rep1, { hash1 }, 1024 * 1024);
vote_cache.vote (vote1->hashes.front (), vote1);
vote_cache.insert (vote1);
}
ASSERT_LT (vote_cache.size (), count);
// Check that oldest votes are dropped first
Expand All @@ -343,7 +328,7 @@ TEST (vote_cache, overfill_entry)
{
auto rep1 = create_rep (9);
auto vote1 = nano::test::make_vote (rep1, { hash1 }, 1024 * 1024);
vote_cache.vote (vote1->hashes.front (), vote1);
vote_cache.insert (vote1);
}
ASSERT_EQ (1, vote_cache.size ());
}
Expand All @@ -359,9 +344,9 @@ TEST (vote_cache, age_cutoff)
auto hash1 = nano::test::random_hash ();
auto rep1 = create_rep (9);
auto vote1 = nano::test::make_vote (rep1, { hash1 }, 3);
vote_cache.vote (vote1->hashes.front (), vote1);
vote_cache.insert (vote1);
ASSERT_EQ (1, vote_cache.size ());
ASSERT_TRUE (vote_cache.find (hash1));
ASSERT_FALSE (vote_cache.find (hash1).empty ());

auto tops1 = vote_cache.top (0);
ASSERT_EQ (tops1.size (), 1);
Expand Down
Loading
Loading