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

Add per-bucket election limiter #4623

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
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
109 changes: 6 additions & 103 deletions nano/core_test/active_elections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ TEST (active_elections, keep_local)
nano::node_config node_config = system.default_config ();
node_config.enable_voting = false;
// Bound to 2, won't drop wallet created transactions, but good to test dropping remote
node_config.active_elections.size = 2;
node_config.priority_scheduler.bucket_maximum = 2;
// Disable frontier confirmation to allow the test to finish before
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;

Expand Down Expand Up @@ -192,7 +192,10 @@ TEST (active_elections, keep_local)
ASSERT_NE (nullptr, send6);

// force-confirm blocks
nano::test::confirm (node.ledger, send6);
auto election = nano::test::start_election (system, node, send6->hash ());
ASSERT_NE (nullptr, election);
election->force_confirm ();
ASSERT_TIMELY (5s, node.active.empty ());

nano::state_block_builder builder{};
const auto receive1 = builder.make_block ()
Expand Down Expand Up @@ -227,8 +230,7 @@ TEST (active_elections, keep_local)
node.process_active (receive3);

/// bound elections, should drop after one loop
ASSERT_TIMELY_EQ (5s, node.active.size (), node_config.active_elections.size);
// ASSERT_EQ (1, node.scheduler.size ());
ASSERT_TIMELY_EQ (5s, node.active.size (), node_config.priority_scheduler.bucket_maximum);
}

TEST (inactive_votes_cache, basic)
Expand Down Expand Up @@ -433,7 +435,6 @@ TEST (inactive_votes_cache, election_start)
nano::test::system system;
nano::node_config node_config = system.default_config ();
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
node_config.priority_scheduler.enabled = false;
node_config.optimistic_scheduler.enabled = false;
auto & node = *system.add_node (node_config);
nano::block_hash latest (node.latest (nano::dev::genesis_key.pub));
Expand Down Expand Up @@ -1389,101 +1390,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_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));
}
93 changes: 0 additions & 93 deletions nano/core_test/election_scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,96 +53,3 @@ TEST (election_scheduler, activate_one_flush)
system.nodes[0]->scheduler.priority.activate (system.nodes[0]->ledger.tx_begin_read (), nano::dev::genesis_key.pub);
ASSERT_TIMELY (5s, system.nodes[0]->active.election (send1->qualified_root ()));
}

/**
* Tests that the election scheduler and the active transactions container (AEC)
* work in sync with regards to the node configuration value "active_elections.size".
*
* The test sets up two forcefully cemented blocks -- a send on the genesis account and a receive on a second account.
* It then creates two other blocks, each a successor to one of the previous two,
* and processes them locally (without the node starting elections for them, but just saving them to disk).
*
* Elections for these latter two (B1 and B2) are started by the test code manually via `election_scheduler::activate`.
* The test expects E1 to start right off and take its seat into the AEC.
* E2 is expected not to start though (because the AEC is full), so B2 should be awaiting in the scheduler's queue.
*
* As soon as the test code manually confirms E1 (and thus evicts it out of the AEC),
* it is expected that E2 begins and the scheduler's queue becomes empty again.
*/
TEST (election_scheduler, no_vacancy)
{
nano::test::system system{};

nano::node_config config = system.default_config ();
config.active_elections.size = 1;
config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;

auto & node = *system.add_node (config);
nano::state_block_builder builder{};
nano::keypair key{};

// Activating accounts depends on confirmed dependencies. First, prepare 2 accounts
auto send = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.link (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 ();
ASSERT_EQ (nano::block_status::progress, node.process (send));
node.process_confirmed (nano::election_status{ send });

auto receive = builder.make_block ()
.account (key.pub)
.previous (0)
.representative (key.pub)
.link (send->hash ())
.balance (nano::Gxrb_ratio)
.sign (key.prv, key.pub)
.work (*system.work.generate (key.pub))
.build ();
ASSERT_EQ (nano::block_status::progress, node.process (receive));
node.process_confirmed (nano::election_status{ receive });

ASSERT_TIMELY (5s, nano::test::confirmed (node, { send, receive }));

// Second, process two eligible transactions
auto block1 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (send->hash ())
.representative (nano::dev::genesis_key.pub)
.link (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 2 * nano::Gxrb_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, node.process (block1));

// There is vacancy so it should be inserted
node.scheduler.priority.activate (node.ledger.tx_begin_read (), nano::dev::genesis_key.pub);
std::shared_ptr<nano::election> election{};
ASSERT_TIMELY (5s, (election = node.active.election (block1->qualified_root ())) != nullptr);

auto block2 = builder.make_block ()
.account (key.pub)
.previous (receive->hash ())
.representative (key.pub)
.link (key.pub)
.balance (0)
.sign (key.prv, key.pub)
.work (*system.work.generate (receive->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, node.process (block2));

// There is no vacancy so it should stay queued
node.scheduler.priority.activate (node.ledger.tx_begin_read (), key.pub);
ASSERT_TIMELY_EQ (5s, node.scheduler.priority.size (), 1);
ASSERT_EQ (node.active.election (block2->qualified_root ()), nullptr);

// Election confirmed, next in queue should begin
election->force_confirm ();
ASSERT_TIMELY (5s, node.active.election (block2->qualified_root ()) != nullptr);
ASSERT_TRUE (node.scheduler.priority.empty ());
}
Loading
Loading