From d3b180a2c5b59c62cde4134de09a8c5fb0d14a69 Mon Sep 17 00:00:00 2001 From: gr0vity-dev Date: Mon, 2 Dec 2024 22:32:22 +0100 Subject: [PATCH 1/6] Make election behaviour mutable --- nano/node/election.cpp | 5 +++-- nano/node/election.hpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/nano/node/election.cpp b/nano/node/election.cpp index ee64237059..e0fd27800d 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -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: @@ -314,7 +314,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: @@ -771,6 +771,7 @@ std::vector nano::election::votes_with_weight () co nano::election_behavior nano::election::behavior () const { + nano::lock_guard guard{ mutex }; return behavior_m; } diff --git a/nano/node/election.hpp b/nano/node/election.hpp index 718cbf680c..9a6850a384 100644 --- a/nano/node/election.hpp +++ b/nano/node/election.hpp @@ -180,7 +180,7 @@ class election final : public std::enable_shared_from_this mutable nano::uint128_t final_weight{ 0 }; mutable std::unordered_map 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; From a5bf4d76f6ef877a335a5f1359e2590efd64a67f Mon Sep 17 00:00:00 2001 From: gr0vity-dev Date: Mon, 2 Dec 2024 22:35:00 +0100 Subject: [PATCH 2/6] Add transition_priority method to election class --- nano/node/election.cpp | 19 +++++++++++++++++++ nano/node/election.hpp | 1 + 2 files changed, 20 insertions(+) diff --git a/nano/node/election.cpp b/nano/node/election.cpp index e0fd27800d..fc3a33dc23 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -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 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 guard{ mutex }; diff --git a/nano/node/election.hpp b/nano/node/election.hpp index 9a6850a384..1fe0ad1895 100644 --- a/nano/node/election.hpp +++ b/nano/node/election.hpp @@ -87,6 +87,7 @@ class election final : public std::enable_shared_from_this public: // State transitions bool transition_time (nano::confirmation_solicitor &); void transition_active (); + bool transition_priority (); void cancel (); public: // Status From cb39f8d70b903a3d680fd44a341d7f61719fefe5 Mon Sep 17 00:00:00 2001 From: gr0vity-dev Date: Tue, 3 Dec 2024 08:28:05 +0100 Subject: [PATCH 3/6] Make use of transition_priority() in active_elections --- nano/lib/stats_enums.hpp | 2 ++ nano/node/active_elections.cpp | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/nano/lib/stats_enums.hpp b/nano/lib/stats_enums.hpp index 2022b29083..c2fec6a159 100644 --- a/nano/lib/stats_enums.hpp +++ b/nano/lib/stats_enums.hpp @@ -426,6 +426,8 @@ enum class detail // active insert, insert_failed, + transition_priority, + transition_priority_failed, election_cleanup, // active_elections diff --git a/nano/node/active_elections.cpp b/nano/node/active_elections.cpp index bd29634bb2..9735ce1dae 100644 --- a/nano/node/active_elections.cpp +++ b/nano/node/active_elections.cpp @@ -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 (); From d4578911c5fa116f711c0ec46588de97151e2366 Mon Sep 17 00:00:00 2001 From: gr0vity-dev Date: Tue, 3 Dec 2024 08:28:23 +0100 Subject: [PATCH 4/6] Add testcase election_scheduler.transition_optimistic_to_priority --- nano/core_test/election_scheduler.cpp | 53 +++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/nano/core_test/election_scheduler.cpp b/nano/core_test/election_scheduler.cpp index cd2d642ed6..5f6a147e9e 100644 --- a/nano/core_test/election_scheduler.cpp +++ b/nano/core_test/election_scheduler.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -157,6 +158,58 @@ 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); + + // 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.stats.clear (); + ASSERT_EQ (0, node.stats.count (nano::stat::type::election, nano::stat::detail::broadcast_vote)); + 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, 1, node.stats.count (nano::stat::type::election, nano::stat::detail::broadcast_vote)); + 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". From 0f1960d3b79b655c3e0ca5491dac284c071b338b Mon Sep 17 00:00:00 2001 From: gr0vity Date: Tue, 3 Dec 2024 15:30:53 +0100 Subject: [PATCH 5/6] extend election status with vote_broadcast_count and adjust testcase --- nano/core_test/election_scheduler.cpp | 5 ++--- nano/node/election.cpp | 6 +++++- nano/node/election.hpp | 1 + nano/node/election_status.hpp | 1 + nano/node/json_handler.cpp | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/nano/core_test/election_scheduler.cpp b/nano/core_test/election_scheduler.cpp index 5f6a147e9e..21af2c6a7e 100644 --- a/nano/core_test/election_scheduler.cpp +++ b/nano/core_test/election_scheduler.cpp @@ -193,20 +193,19 @@ TEST (election_scheduler, transition_optimistic_to_priority) 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.stats.clear (); - ASSERT_EQ (0, node.stats.count (nano::stat::type::election, nano::stat::detail::broadcast_vote)); 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, 1, node.stats.count (nano::stat::type::election, nano::stat::detail::broadcast_vote)); + ASSERT_TIMELY_EQ (1s, 2, election->current_status ().status.vote_broadcast_count); ASSERT_TRUE (node.active.active (*block)); } diff --git a/nano/node/election.cpp b/nano/node/election.cpp index fc3a33dc23..c42b55b771 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -27,7 +27,7 @@ nano::election::election (nano::node & node_a, std::shared_ptr 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::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, 1, 0, nano::election_status_type::ongoing }), + status ({ block_a, 0, 0, std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, 0, 1, 0, nano::election_status_type::ongoing }), height (block_a->sideband ().height), root (block_a->root ()), qualified_root (block_a->qualified_root ()) @@ -440,6 +440,7 @@ void nano::election::confirm_if_quorum (nano::unique_lock & 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 ()) @@ -596,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 (last_blocks.size ()); status_l.voter_count = nano::narrow_cast (last_votes.size ()); return nano::election_extended_status{ status_l, last_votes, last_blocks, tally_impl () }; @@ -625,6 +627,7 @@ void nano::election::broadcast_vote_locked (nano::unique_lock & 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 ())) { @@ -822,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); diff --git a/nano/node/election.hpp b/nano/node/election.hpp index 1fe0ad1895..78e6c5d8d9 100644 --- a/nano/node/election.hpp +++ b/nano/node/election.hpp @@ -97,6 +97,7 @@ class election final : public std::enable_shared_from_this std::shared_ptr winner () const; std::chrono::milliseconds duration () const; std::atomic confirmation_request_count{ 0 }; + std::atomic vote_broadcast_count{ 0 }; nano::tally_t tally () const; bool have_quorum (nano::tally_t const &) const; diff --git a/nano/node/election_status.hpp b/nano/node/election_status.hpp index 548bf8e2bf..a69a490982 100644 --- a/nano/node/election_status.hpp +++ b/nano/node/election_status.hpp @@ -35,6 +35,7 @@ class election_status final std::chrono::milliseconds election_end{ std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()) }; std::chrono::milliseconds election_duration{ std::chrono::duration_values::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 }; diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index 8c442f8f12..735da1e0a4 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -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::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, 1, 0, nano::election_status_type::active_confirmation_height }; + nano::election_status status{ block_l, 0, 0, std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, 0, 1, 0, nano::election_status_type::active_confirmation_height }; node.active.recently_cemented.put (status); // Trigger callback for confirmed block auto account = block_l->account (); From bcf8ac0464b693e1d51c02b02defb00edef8442b Mon Sep 17 00:00:00 2001 From: gr0vity Date: Tue, 3 Dec 2024 15:53:13 +0100 Subject: [PATCH 6/6] simplify election_status initialization Replace inline initializations with the new constructor syntax while maintaining the same behavior. --- nano/node/election.cpp | 2 +- nano/node/election_status.hpp | 9 +++++++++ nano/node/json_handler.cpp | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/nano/node/election.cpp b/nano/node/election.cpp index c42b55b771..125c4675fb 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -27,7 +27,7 @@ nano::election::election (nano::node & node_a, std::shared_ptr 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::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, 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 ()) diff --git a/nano/node/election_status.hpp b/nano/node/election_status.hpp index a69a490982..d75b7e3a99 100644 --- a/nano/node/election_status.hpp +++ b/nano/node/election_status.hpp @@ -39,5 +39,14 @@ class election_status final 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 block_a, election_status_type type_a = nano::election_status_type::ongoing) : + winner (block_a), + type (type_a) + { + block_count = 1; + } }; } diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index 735da1e0a4..708e0e3f98 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -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::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, 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 ();