Skip to content

Commit

Permalink
Merge pull request #4799 from gr0vity-dev/prs/upgrade_elections
Browse files Browse the repository at this point in the history
Make election behavior mutable to support dynamic behaviour transition
  • Loading branch information
pwojcikdev authored Dec 4, 2024
2 parents 45c3045 + bcf8ac0 commit 61e6a6b
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 5 deletions.
52 changes: 52 additions & 0 deletions nano/core_test/election_scheduler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <nano/node/scheduler/component.hpp>
#include <nano/node/scheduler/priority.hpp>
#include <nano/secure/ledger.hpp>
#include <nano/test_common/chains.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/testutil.hpp>

Expand Down Expand Up @@ -157,6 +158,57 @@ TEST (election_scheduler, activate_one_flush)
ASSERT_TIMELY (5s, node.active.election (send1->qualified_root ()));
}

/*
* Tests that an optimistic election can be transitioned to a priority election.
*
* The test:
* 1. Creates a chain of 2 blocks with an optimistic election for the second block
* 2. Confirms the first block in the chain
* 3. Attempts to start a priority election for the second block
* 4. Verifies that the existing optimistic election is transitioned to priority
* 5. Verifies a new vote is broadcast after the transition
*/
TEST (election_scheduler, transition_optimistic_to_priority)
{
nano::test::system system;
nano::node_config config = system.default_config ();
config.optimistic_scheduler.gap_threshold = 1;
config.enable_voting = true;
config.hinted_scheduler.enable = false;
config.network_params.network.vote_broadcast_interval = 15000ms;
auto & node = *system.add_node (config);

// Add representative
const nano::uint128_t rep_weight = nano::Knano_ratio * 100;
nano::keypair rep = nano::test::setup_rep (system, node, rep_weight);
system.wallet (0)->insert_adhoc (rep.prv);

// Create a chain of blocks - and trigger an optimistic election for the last block
const int howmany_blocks = 2;
auto chains = nano::test::setup_chains (system, node, /* single chain */ 1, howmany_blocks, nano::dev::genesis_key, /* do not confirm */ false);
auto & [account, blocks] = chains.front ();

// Wait for optimistic election to start for last block
auto const & block = blocks.back ();
ASSERT_TIMELY (5s, node.vote_router.active (block->hash ()));
auto election = node.active.election (block->qualified_root ());
ASSERT_EQ (election->behavior (), nano::election_behavior::optimistic);
ASSERT_TIMELY_EQ (1s, 1, election->current_status ().status.vote_broadcast_count);

// Confirm first block to allow upgrading second block's election
nano::test::confirm (node.ledger, blocks.at (howmany_blocks - 1));

// Attempt to start priority election for second block
node.active.insert (block, nano::election_behavior::priority);

// Verify priority transition
ASSERT_EQ (election->behavior (), nano::election_behavior::priority);
ASSERT_EQ (1, node.stats.count (nano::stat::type::active_elections, nano::stat::detail::transition_priority));
// Verify vote broadcast after transitioning
ASSERT_TIMELY_EQ (1s, 2, election->current_status ().status.vote_broadcast_count);
ASSERT_TRUE (node.active.active (*block));
}

/**
* Tests that the election scheduler and the active transactions container (AEC)
* work in sync with regards to the node configuration value "active_elections.size".
Expand Down
2 changes: 2 additions & 0 deletions nano/lib/stats_enums.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@ enum class detail
// active
insert,
insert_failed,
transition_priority,
transition_priority_failed,
election_cleanup,

// active_elections
Expand Down
17 changes: 17 additions & 0 deletions nano/node/active_elections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,23 @@ nano::election_insertion_result nano::active_elections::insert (std::shared_ptr<
else
{
result.election = existing->election;

// Upgrade to priority election to enable immediate vote broadcasting.
auto previous_behavior = result.election->behavior ();
if (election_behavior_a == nano::election_behavior::priority && previous_behavior != nano::election_behavior::priority)
{
bool transitioned = result.election->transition_priority ();
if (transitioned)
{
count_by_behavior[previous_behavior]--;
count_by_behavior[election_behavior_a]++;
node.stats.inc (nano::stat::type::active_elections, nano::stat::detail::transition_priority);
}
else
{
node.stats.inc (nano::stat::type::active_elections, nano::stat::detail::transition_priority_failed);
}
}
}

lock.unlock ();
Expand Down
30 changes: 27 additions & 3 deletions nano/node/election.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ nano::election::election (nano::node & node_a, std::shared_ptr<nano::block> cons
live_vote_action (live_vote_action_a),
node (node_a),
behavior_m (election_behavior_a),
status ({ block_a, 0, 0, std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values<std::chrono::milliseconds>::zero (), 0, 1, 0, nano::election_status_type::ongoing }),
status (block_a),
height (block_a->sideband ().height),
root (block_a->root ()),
qualified_root (block_a->qualified_root ())
Expand Down Expand Up @@ -152,7 +152,7 @@ bool nano::election::state_change (nano::election_state expected_a, nano::electi

std::chrono::milliseconds nano::election::confirm_req_time () const
{
switch (behavior ())
switch (behavior_m)
{
case election_behavior::manual:
case election_behavior::priority:
Expand Down Expand Up @@ -183,6 +183,25 @@ void nano::election::transition_active ()
state_change (nano::election_state::passive, nano::election_state::active);
}

bool nano::election::transition_priority ()
{
nano::lock_guard<nano::mutex> guard{ mutex };

if (behavior_m == nano::election_behavior::priority || behavior_m == nano::election_behavior::manual)
{
return false;
}

behavior_m = nano::election_behavior::priority;
last_vote = std::chrono::steady_clock::time_point{}; // allow new outgoing votes immediately

node.logger.debug (nano::log::type::election, "Transitioned election behavior to priority from {} for root: {}",
to_string (behavior_m),
qualified_root.to_string ());

return true;
}

void nano::election::cancel ()
{
nano::lock_guard<nano::mutex> guard{ mutex };
Expand Down Expand Up @@ -314,7 +333,7 @@ bool nano::election::transition_time (nano::confirmation_solicitor & solicitor_a

std::chrono::milliseconds nano::election::time_to_live () const
{
switch (behavior ())
switch (behavior_m)
{
case election_behavior::manual:
case election_behavior::priority:
Expand Down Expand Up @@ -421,6 +440,7 @@ void nano::election::confirm_if_quorum (nano::unique_lock<nano::mutex> & lock_a)
{
if (!is_quorum.exchange (true) && node.config.enable_voting && node.wallets.reps ().voting > 0)
{
++vote_broadcast_count;
node.final_generator.add (root, status.winner->hash ());
}
if (final_weight >= node.online_reps.delta ())
Expand Down Expand Up @@ -577,6 +597,7 @@ nano::election_extended_status nano::election::current_status_locked () const

nano::election_status status_l = status;
status_l.confirmation_request_count = confirmation_request_count;
status_l.vote_broadcast_count = vote_broadcast_count;
status_l.block_count = nano::narrow_cast<decltype (status_l.block_count)> (last_blocks.size ());
status_l.voter_count = nano::narrow_cast<decltype (status_l.voter_count)> (last_votes.size ());
return nano::election_extended_status{ status_l, last_votes, last_blocks, tally_impl () };
Expand Down Expand Up @@ -606,6 +627,7 @@ void nano::election::broadcast_vote_locked (nano::unique_lock<nano::mutex> & loc
if (node.config.enable_voting && node.wallets.reps ().voting > 0)
{
node.stats.inc (nano::stat::type::election, nano::stat::detail::broadcast_vote);
++vote_broadcast_count;

if (confirmed_locked () || have_quorum (tally_impl ()))
{
Expand Down Expand Up @@ -771,6 +793,7 @@ std::vector<nano::vote_with_weight_info> nano::election::votes_with_weight () co

nano::election_behavior nano::election::behavior () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return behavior_m;
}

Expand Down Expand Up @@ -802,6 +825,7 @@ void nano::election_extended_status::operator() (nano::object_stream & obs) cons
obs.write ("tally_amount", status.tally.to_string_dec ());
obs.write ("final_tally_amount", status.final_tally.to_string_dec ());
obs.write ("confirmation_request_count", status.confirmation_request_count);
obs.write ("vote_broadcast_count", status.vote_broadcast_count);
obs.write ("block_count", status.block_count);
obs.write ("voter_count", status.voter_count);
obs.write ("type", status.type);
Expand Down
4 changes: 3 additions & 1 deletion nano/node/election.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ class election final : public std::enable_shared_from_this<election>
public: // State transitions
bool transition_time (nano::confirmation_solicitor &);
void transition_active ();
bool transition_priority ();
void cancel ();

public: // Status
Expand All @@ -96,6 +97,7 @@ class election final : public std::enable_shared_from_this<election>
std::shared_ptr<nano::block> winner () const;
std::chrono::milliseconds duration () const;
std::atomic<unsigned> confirmation_request_count{ 0 };
std::atomic<unsigned> vote_broadcast_count{ 0 };

nano::tally_t tally () const;
bool have_quorum (nano::tally_t const &) const;
Expand Down Expand Up @@ -180,7 +182,7 @@ class election final : public std::enable_shared_from_this<election>
mutable nano::uint128_t final_weight{ 0 };
mutable std::unordered_map<nano::block_hash, nano::uint128_t> last_tally;

nano::election_behavior const behavior_m;
nano::election_behavior behavior_m;
std::chrono::steady_clock::time_point const election_start{ std::chrono::steady_clock::now () };

mutable nano::mutex mutex;
Expand Down
10 changes: 10 additions & 0 deletions nano/node/election_status.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,18 @@ class election_status final
std::chrono::milliseconds election_end{ std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::system_clock::now ().time_since_epoch ()) };
std::chrono::milliseconds election_duration{ std::chrono::duration_values<std::chrono::milliseconds>::zero () };
unsigned confirmation_request_count{ 0 };
unsigned vote_broadcast_count{ 0 };
unsigned block_count{ 0 };
unsigned voter_count{ 0 };
election_status_type type{ nano::election_status_type::inactive_confirmation_height };

election_status () = default;

election_status (std::shared_ptr<nano::block> block_a, election_status_type type_a = nano::election_status_type::ongoing) :
winner (block_a),
type (type_a)
{
block_count = 1;
}
};
}
2 changes: 1 addition & 1 deletion nano/node/json_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1196,7 +1196,7 @@ void nano::json_handler::block_confirm ()
else
{
// Add record in confirmation history for confirmed block
nano::election_status status{ block_l, 0, 0, std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values<std::chrono::milliseconds>::zero (), 0, 1, 0, nano::election_status_type::active_confirmation_height };
nano::election_status status{ block_l, nano::election_status_type::active_confirmation_height };
node.active.recently_cemented.put (status);
// Trigger callback for confirmed block
auto account = block_l->account ();
Expand Down

0 comments on commit 61e6a6b

Please sign in to comment.