Skip to content

Commit

Permalink
Bounded election buckets with dynamic reprioritization (#4626)
Browse files Browse the repository at this point in the history
* Keep track of started elections in buckets

* Stats

* Tests

* Fix compiler warning

* add nano::scheduler::priority_bucket config

# Conflicts:
#	nano/node/scheduler/bucket.cpp

* Fix test

* Explicitly pass config to bucket

* Bucket tests

---------

Co-authored-by: gr0vity-dev <[email protected]>
  • Loading branch information
pwojcikdev and gr0vity-dev committed Jul 11, 2024
1 parent a7a2c0b commit 21abfc2
Show file tree
Hide file tree
Showing 18 changed files with 688 additions and 452 deletions.
157 changes: 29 additions & 128 deletions nano/core_test/active_elections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ TEST (active_elections, confirm_frontier)
ASSERT_GT (election2->confirmation_request_count, 0u);
}

TEST (active_elections, keep_local)
// TODO: Adjust for new behaviour of bounded buckets
TEST (active_elections, DISABLED_keep_local)
{
nano::test::system system{};

Expand Down Expand Up @@ -1293,35 +1294,33 @@ TEST (active_elections, list_active)
TEST (active_elections, vacancy)
{
std::atomic<bool> updated = false;
{
nano::test::system system;
nano::node_config config = system.default_config ();
config.active_elections.size = 1;
auto & node = *system.add_node (config);
nano::state_block_builder builder;
auto send = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.link (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
node.active.vacancy_update = [&updated] () { updated = true; };
ASSERT_EQ (nano::block_status::progress, node.process (send));
ASSERT_EQ (1, node.active.vacancy (nano::election_behavior::priority));
ASSERT_EQ (0, node.active.size ());
auto election1 = nano::test::start_election (system, node, send->hash ());
ASSERT_TIMELY (1s, updated);
updated = false;
ASSERT_EQ (0, node.active.vacancy (nano::election_behavior::priority));
ASSERT_EQ (1, node.active.size ());
election1->force_confirm ();
ASSERT_TIMELY (1s, updated);
ASSERT_EQ (1, node.active.vacancy (nano::election_behavior::priority));
ASSERT_EQ (0, node.active.size ());
}
nano::test::system system;
nano::node_config config = system.default_config ();
config.active_elections.size = 1;
auto & node = *system.add_node (config);
nano::state_block_builder builder;
auto send = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.link (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
node.active.vacancy_update = [&updated] () { updated = true; };
ASSERT_EQ (nano::block_status::progress, node.process (send));
ASSERT_EQ (1, node.active.vacancy (nano::election_behavior::priority));
ASSERT_EQ (0, node.active.size ());
auto election1 = nano::test::start_election (system, node, send->hash ());
ASSERT_TIMELY (1s, updated);
updated = false;
ASSERT_EQ (0, node.active.vacancy (nano::election_behavior::priority));
ASSERT_EQ (1, node.active.size ());
election1->force_confirm ();
ASSERT_TIMELY (1s, updated);
ASSERT_EQ (1, node.active.vacancy (nano::election_behavior::priority));
ASSERT_EQ (0, node.active.size ());
}

/*
Expand Down Expand Up @@ -1389,101 +1388,3 @@ TEST (active_elections, limit_vote_hinted_elections)
// Ensure there was no overflow of elections
ASSERT_EQ (0, node.stats.count (nano::stat::type::active_elections_dropped, nano::stat::detail::priority));
}

/*
* Tests that when AEC is running at capacity from normal elections, it is still possible to schedule a limited number of hinted elections
*/
TEST (active_elections, allow_limited_overflow)
{
nano::test::system system;
nano::node_config config = system.default_config ();
const int aec_limit = 20;
config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
config.active_elections.size = aec_limit;
config.active_elections.hinted_limit_percentage = 20; // Should give us a limit of 4 hinted elections
auto & node = *system.add_node (config);

auto blocks = nano::test::setup_independent_blocks (system, node, aec_limit * 4);

// Split blocks in two halves
std::vector<std::shared_ptr<nano::block>> blocks1 (blocks.begin (), blocks.begin () + blocks.size () / 2);
std::vector<std::shared_ptr<nano::block>> blocks2 (blocks.begin () + blocks.size () / 2, blocks.end ());

// Even though automatic frontier confirmation is disabled, AEC is doing funny stuff and inserting elections, clear that
WAIT (1s);
node.active.clear ();
ASSERT_TRUE (node.active.empty ());

// Insert the first part of the blocks into normal election scheduler
for (auto const & block : blocks1)
{
node.scheduler.priority.activate (node.ledger.tx_begin_read (), block->account ());
}

// Ensure number of active elections reaches AEC limit and there is no overfill
ASSERT_TIMELY_EQ (5s, node.active.size (), node.active.limit (nano::election_behavior::priority));
// And it stays that way without increasing
ASSERT_ALWAYS (1s, node.active.size () == node.active.limit (nano::election_behavior::priority));

// Insert votes for the second part of the blocks, so that those are scheduled as hinted elections
for (auto const & block : blocks2)
{
// 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.insert (vote);
}

// Ensure active elections overfill AEC only up to normal + hinted limit
ASSERT_TIMELY_EQ (5s, node.active.size (), node.active.limit (nano::election_behavior::priority) + node.active.limit (nano::election_behavior::hinted));
// And it stays that way without increasing
ASSERT_ALWAYS (1s, node.active.size () == node.active.limit (nano::election_behavior::priority) + node.active.limit (nano::election_behavior::hinted));
}

/*
* Tests that when hinted elections are present in the AEC, normal scheduler adapts not to exceed the limit of all elections
*/
TEST (active_elections, allow_limited_overflow_adapt)
{
nano::test::system system;
nano::node_config config = system.default_config ();
const int aec_limit = 20;
config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
config.active_elections.size = aec_limit;
config.active_elections.hinted_limit_percentage = 20; // Should give us a limit of 4 hinted elections
auto & node = *system.add_node (config);

auto blocks = nano::test::setup_independent_blocks (system, node, aec_limit * 4);

// Split blocks in two halves
std::vector<std::shared_ptr<nano::block>> blocks1 (blocks.begin (), blocks.begin () + blocks.size () / 2);
std::vector<std::shared_ptr<nano::block>> blocks2 (blocks.begin () + blocks.size () / 2, blocks.end ());

// Even though automatic frontier confirmation is disabled, AEC is doing funny stuff and inserting elections, clear that
WAIT (1s);
node.active.clear ();
ASSERT_TRUE (node.active.empty ());

// Insert votes for the second part of the blocks, so that those are scheduled as hinted elections
for (auto const & block : blocks2)
{
// 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.insert (vote);
}

// Ensure hinted election amount is bounded by hinted limit
ASSERT_TIMELY_EQ (5s, node.active.size (), node.active.limit (nano::election_behavior::hinted));
// And it stays that way without increasing
ASSERT_ALWAYS (1s, node.active.size () == node.active.limit (nano::election_behavior::hinted));

// Insert the first part of the blocks into normal election scheduler
for (auto const & block : blocks1)
{
node.scheduler.priority.activate (node.ledger.tx_begin_read (), block->account ());
}

// Ensure number of active elections reaches AEC limit and there is no overfill
ASSERT_TIMELY_EQ (5s, node.active.size (), node.active.limit (nano::election_behavior::priority));
// And it stays that way without increasing
ASSERT_ALWAYS (1s, node.active.size () == node.active.limit (nano::election_behavior::priority));
}
Loading

0 comments on commit 21abfc2

Please sign in to comment.