From 0827da71830bddb0fe5fe8dfc1cae408c5e25c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20W=C3=B3jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Fri, 11 Apr 2025 13:46:17 +0200 Subject: [PATCH] Merge pull request #4872 from pwojcikdev/bootstrap-stale-elections Bootstrap stale elections --- nano/core_test/active_elections.cpp | 37 ++++++++ nano/lib/constants.hpp | 6 +- nano/lib/stats_enums.hpp | 1 + nano/lib/thread_roles.cpp | 4 +- nano/lib/thread_roles.hpp | 2 +- nano/node/active_elections.cpp | 106 ++++++++++++---------- nano/node/active_elections.hpp | 12 ++- nano/node/bootstrap/bootstrap_service.cpp | 6 ++ nano/node/bootstrap/bootstrap_service.hpp | 5 + nano/node/election.cpp | 6 ++ nano/node/election.hpp | 1 + nano/slow_test/node.cpp | 2 +- 12 files changed, 128 insertions(+), 60 deletions(-) diff --git a/nano/core_test/active_elections.cpp b/nano/core_test/active_elections.cpp index c1afd0ebb..b9772311d 100644 --- a/nano/core_test/active_elections.cpp +++ b/nano/core_test/active_elections.cpp @@ -1482,3 +1482,40 @@ TEST (active_elections, broadcast_block_on_activation) ASSERT_TIMELY (5s, node1->active.active (send1->qualified_root ())); ASSERT_TIMELY (5s, node2->block_or_pruned_exists (send1->hash ())); } + +TEST (active_elections, bootstrap_stale) +{ + nano::test::system system; + + // Configure node with short stale threshold for testing + nano::node_config node_config = system.default_config (); + node_config.active_elections.bootstrap_stale_threshold = 2s; // Short threshold for faster testing + + auto & node = *system.add_node (node_config); + + // Create a test block + nano::keypair key; + 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) + .balance (nano::dev::constants.genesis_amount - nano::Knano_ratio) + .link (key.pub) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*system.work.generate (nano::dev::genesis->hash ())) + .build (); + + // Process the block and start an election + node.process_active (send); + + // Ensure election starts + std::shared_ptr election; + ASSERT_TIMELY (5s, (election = node.active.election (send->qualified_root ())) != nullptr); + + // Check initial state + ASSERT_EQ (0, node.stats.count (nano::stat::type::active_elections, nano::stat::detail::bootstrap_stale)); + + // Wait for bootstrap_stale_threshold to pass and the statistic to be incremented + ASSERT_TIMELY (5s, node.stats.count (nano::stat::type::active_elections, nano::stat::detail::bootstrap_stale) > 0); +} \ No newline at end of file diff --git a/nano/lib/constants.hpp b/nano/lib/constants.hpp index de6e82222..65ce81f93 100644 --- a/nano/lib/constants.hpp +++ b/nano/lib/constants.hpp @@ -84,7 +84,7 @@ public: default_rpc_port (45000), default_ipc_port (46000), default_websocket_port (47000), - aec_loop_interval_ms (300), // Update AEC ~3 times per second + aec_loop_interval (300ms), // Update AEC ~3 times per second cleanup_period (default_cleanup_period), merge_period (std::chrono::milliseconds (250)), keepalive_period (std::chrono::seconds (15)), @@ -120,7 +120,7 @@ public: } else if (is_dev_network ()) { - aec_loop_interval_ms = 20; + aec_loop_interval = 20ms; cleanup_period = std::chrono::seconds (1); merge_period = std::chrono::milliseconds (10); keepalive_period = std::chrono::seconds (1); @@ -147,7 +147,7 @@ public: uint16_t default_rpc_port; uint16_t default_ipc_port; uint16_t default_websocket_port; - unsigned aec_loop_interval_ms; + std::chrono::milliseconds aec_loop_interval; std::chrono::seconds cleanup_period; std::chrono::milliseconds cleanup_period_half () const diff --git a/nano/lib/stats_enums.hpp b/nano/lib/stats_enums.hpp index 8d6aba5a8..85b16dbc7 100644 --- a/nano/lib/stats_enums.hpp +++ b/nano/lib/stats_enums.hpp @@ -490,6 +490,7 @@ enum class detail stopped, confirm_dependent, forks_cached, + bootstrap_stale, // unchecked put, diff --git a/nano/lib/thread_roles.cpp b/nano/lib/thread_roles.cpp index a7a991741..af3bf9452 100644 --- a/nano/lib/thread_roles.cpp +++ b/nano/lib/thread_roles.cpp @@ -46,8 +46,8 @@ std::string nano::thread_role::get_string (nano::thread_role::name role) case nano::thread_role::name::ledger_notifications: thread_role_name_string = "Ledger notif"; break; - case nano::thread_role::name::request_loop: - thread_role_name_string = "Request loop"; + case nano::thread_role::name::aec_loop: + thread_role_name_string = "AEC"; break; case nano::thread_role::name::wallet_actions: thread_role_name_string = "Wallet actions"; diff --git a/nano/lib/thread_roles.hpp b/nano/lib/thread_roles.hpp index fb4bcd5e2..83c842d66 100644 --- a/nano/lib/thread_roles.hpp +++ b/nano/lib/thread_roles.hpp @@ -20,7 +20,7 @@ enum class name vote_rebroadcasting, block_processing, ledger_notifications, - request_loop, + aec_loop, wallet_actions, bootstrap_initiator, bootstrap_connections, diff --git a/nano/node/active_elections.cpp b/nano/node/active_elections.cpp index 9845e0d28..90097ca1e 100644 --- a/nano/node/active_elections.cpp +++ b/nano/node/active_elections.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -94,8 +95,8 @@ void nano::active_elections::start () debug_assert (!thread.joinable ()); thread = std::thread ([this] () { - nano::thread_role::set (nano::thread_role::name::request_loop); - request_loop (); + nano::thread_role::set (nano::thread_role::name::aec_loop); + run (); }); } @@ -110,6 +111,28 @@ void nano::active_elections::stop () clear (); } +void nano::active_elections::run () +{ + nano::unique_lock lock{ mutex }; + while (!stopped) + { + auto const stamp = std::chrono::steady_clock::now (); + + node.stats.inc (nano::stat::type::active, nano::stat::detail::loop); + + tick_elections (lock); + debug_assert (!lock.owns_lock ()); + lock.lock (); + + auto const min_sleep = node.network_params.network.aec_loop_interval / 2; + auto const wakeup = std::max (stamp + node.network_params.network.aec_loop_interval, std::chrono::steady_clock::now () + min_sleep); + + condition.wait_until (lock, wakeup, [this, wakeup] { + return stopped || std::chrono::steady_clock::now () >= wakeup; + }); + } +} + auto nano::active_elections::block_cemented (std::shared_ptr const & block, nano::block_hash const & confirmation_root, std::shared_ptr const & source_election) -> block_cemented_result { debug_assert (!mutex.try_lock ()); @@ -245,41 +268,43 @@ int64_t nano::active_elections::vacancy (nano::election_behavior behavior) const return std::min (election_vacancy (behavior), election_winners_vacancy ()); } -void nano::active_elections::request_confirm (nano::unique_lock & lock_a) +void nano::active_elections::tick_elections (nano::unique_lock & lock) { - debug_assert (lock_a.owns_lock ()); + debug_assert (lock.owns_lock ()); - std::size_t const this_loop_target_l (roots.size ()); - auto const elections_l{ list_active_impl (this_loop_target_l) }; + auto const election_list = list_active_impl (); - lock_a.unlock (); + lock.unlock (); nano::confirmation_solicitor solicitor (node.network, node.config); solicitor.prepare (node.rep_crawler.principal_representatives (std::numeric_limits::max ())); - std::size_t unconfirmed_count_l (0); nano::timer elapsed (nano::timer_state::started); - /* - * Loop through active elections in descending order of proof-of-work difficulty, requesting confirmation - * - * Only up to a certain amount of elections are queued for confirmation request and block rebroadcasting. The remaining elections can still be confirmed if votes arrive - * Elections extending the soft config.size limit are flushed after a certain time-to-live cutoff - * Flushed elections are later re-activated via frontier confirmation - */ - for (auto const & election_l : elections_l) + std::deque> stale_elections; + for (auto const & election : election_list) { - bool const confirmed_l (election_l->confirmed ()); - unconfirmed_count_l += !confirmed_l; - - if (election_l->transition_time (solicitor)) + if (election->transition_time (solicitor)) { - erase (election_l->qualified_root); + erase (election->qualified_root); + } + else if (election->duration () > config.bootstrap_stale_threshold) + { + stale_elections.push_back (election); } } solicitor.flush (); - lock_a.lock (); + + if (bootstrap_stale_interval.elapse (config.bootstrap_stale_threshold / 2)) + { + node.stats.add (nano::stat::type::active_elections, nano::stat::detail::bootstrap_stale, stale_elections.size ()); + + for (auto const & election : stale_elections) + { + node.bootstrap.prioritize (election->account ()); + } + } } void nano::active_elections::cleanup_election (nano::unique_lock & lock_a, std::shared_ptr election) @@ -342,20 +367,19 @@ void nano::active_elections::cleanup_election (nano::unique_lock & } } -std::vector> nano::active_elections::list_active (std::size_t max_a) +std::vector> nano::active_elections::list_active (std::size_t max_count) { nano::lock_guard guard{ mutex }; - return list_active_impl (max_a); + return list_active_impl (max_count); } -std::vector> nano::active_elections::list_active_impl (std::size_t max_a) const +std::vector> nano::active_elections::list_active_impl (std::size_t max_count) const { std::vector> result_l; - result_l.reserve (std::min (max_a, roots.size ())); + result_l.reserve (std::min (max_count, roots.size ())); { auto & sorted_roots_l (roots.get ()); - std::size_t count_l{ 0 }; - for (auto i = sorted_roots_l.begin (), n = sorted_roots_l.end (); i != n && count_l < max_a; ++i, ++count_l) + for (auto i = sorted_roots_l.begin (), n = sorted_roots_l.end (); i != n && result_l.size () < max_count; ++i) { result_l.push_back (i->election); } @@ -363,27 +387,6 @@ std::vector> nano::active_elections::list_active return result_l; } -void nano::active_elections::request_loop () -{ - nano::unique_lock lock{ mutex }; - while (!stopped) - { - auto const stamp_l = std::chrono::steady_clock::now (); - - node.stats.inc (nano::stat::type::active, nano::stat::detail::loop); - - request_confirm (lock); - debug_assert (lock.owns_lock ()); - - if (!stopped) - { - auto const min_sleep_l = std::chrono::milliseconds (node.network_params.network.aec_loop_interval_ms / 2); - auto const wakeup_l = std::max (stamp_l + std::chrono::milliseconds (node.network_params.network.aec_loop_interval_ms), std::chrono::steady_clock::now () + min_sleep_l); - condition.wait_until (lock, wakeup_l, [&wakeup_l, &stopped = stopped] { return stopped || std::chrono::steady_clock::now () >= wakeup_l; }); - } - } -} - nano::election_insertion_result nano::active_elections::insert (std::shared_ptr const & block_a, nano::election_behavior election_behavior_a, erased_callback_t erased_callback_a) { debug_assert (block_a); @@ -639,7 +642,8 @@ nano::error nano::active_elections_config::serialize (nano::tomlconfig & toml) c toml.put ("optimistic_limit_percentage", optimistic_limit_percentage, "Limit of optimistic elections as percentage of `active_elections_size`. \ntype:uint64"); toml.put ("confirmation_history_size", confirmation_history_size, "Maximum confirmation history size. If tracking the rate of block confirmations, the websocket feature is recommended instead. \ntype:uint64"); toml.put ("confirmation_cache", confirmation_cache, "Maximum number of confirmed elections kept in cache to prevent restarting an election. \ntype:uint64"); - + toml.put ("max_election_winners", max_election_winners, "Maximum size of election winner details set. \ntype:uint64"); + toml.put ("bootstrap_stale_threshold", bootstrap_stale_threshold.count (), "Time after which additional bootstrap attempts are made to find missing blocks for an election. \ntype:seconds"); return toml.get_error (); } @@ -650,6 +654,8 @@ nano::error nano::active_elections_config::deserialize (nano::tomlconfig & toml) toml.get ("optimistic_limit_percentage", optimistic_limit_percentage); toml.get ("confirmation_history_size", confirmation_history_size); toml.get ("confirmation_cache", confirmation_cache); + toml.get ("max_election_winners", max_election_winners); + toml.get_duration ("bootstrap_stale_threshold", bootstrap_stale_threshold); return toml.get_error (); } diff --git a/nano/node/active_elections.hpp b/nano/node/active_elections.hpp index d7c863f48..5721f658e 100644 --- a/nano/node/active_elections.hpp +++ b/nano/node/active_elections.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -50,6 +51,8 @@ public: std::size_t confirmation_cache{ 65536 }; // Maximum size of election winner details set std::size_t max_election_winners{ 1024 * 16 }; + + std::chrono::seconds bootstrap_stale_threshold{ 60s }; }; /** @@ -129,8 +132,9 @@ public: // Events nano::observer_set<> vacancy_updated; private: - void request_loop (); - void request_confirm (nano::unique_lock &); + void run (); + void tick_elections (nano::unique_lock &); + // Erase all blocks from active and, if not confirmed, clear digests from network filters void cleanup_election (nano::unique_lock & lock_a, std::shared_ptr); @@ -139,7 +143,7 @@ private: void notify_observers (nano::secure::transaction const &, nano::election_status const & status, std::vector const & votes) const; std::shared_ptr election_impl (nano::qualified_root const &) const; - std::vector> list_active_impl (std::size_t max_count) const; + std::vector> list_active_impl (std::size_t max_count = std::numeric_limits::max ()) const; private: // Dependencies active_elections_config const & config; @@ -163,6 +167,8 @@ private: bool stopped{ false }; std::thread thread; + nano::interval bootstrap_stale_interval; + friend class election; public: // Tests diff --git a/nano/node/bootstrap/bootstrap_service.cpp b/nano/node/bootstrap/bootstrap_service.cpp index 6f4578821..dfc757120 100644 --- a/nano/node/bootstrap/bootstrap_service.cpp +++ b/nano/node/bootstrap/bootstrap_service.cpp @@ -163,6 +163,12 @@ void nano::bootstrap_service::reset () throttle.reset (); } +void nano::bootstrap_service::prioritize (nano::account const & account) +{ + nano::lock_guard lock{ mutex }; + accounts.priority_set (account); +} + bool nano::bootstrap_service::send (std::shared_ptr const & channel, async_tag tag) { debug_assert (tag.type != query_type::invalid); diff --git a/nano/node/bootstrap/bootstrap_service.hpp b/nano/node/bootstrap/bootstrap_service.hpp index 439384177..7d5017eca 100644 --- a/nano/node/bootstrap/bootstrap_service.hpp +++ b/nano/node/bootstrap/bootstrap_service.hpp @@ -46,6 +46,11 @@ public: */ void reset (); + /** + * Adds an account to the priority set + */ + void prioritize (nano::account const & account); + std::size_t blocked_size () const; std::size_t priority_size () const; std::size_t score_size () const; diff --git a/nano/node/election.cpp b/nano/node/election.cpp index 6ccf519b9..677f56402 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -805,6 +805,12 @@ void nano::election::force_confirm () confirm_once (lock); } +nano::account nano::election::account () const +{ + nano::lock_guard guard{ mutex }; + return status.winner->account (); +} + std::unordered_map> nano::election::blocks () const { nano::lock_guard guard{ mutex }; diff --git a/nano/node/election.hpp b/nano/node/election.hpp index ac82afd40..9dcb50419 100644 --- a/nano/node/election.hpp +++ b/nano/node/election.hpp @@ -142,6 +142,7 @@ public: // Information nano::root const root; nano::qualified_root const qualified_root; + nano::account account () const; std::vector votes_with_weight () const; nano::election_behavior behavior () const; nano::election_state state () const; diff --git a/nano/slow_test/node.cpp b/nano/slow_test/node.cpp index 9bd9863cb..7245ffc26 100644 --- a/nano/slow_test/node.cpp +++ b/nano/slow_test/node.cpp @@ -1776,7 +1776,7 @@ TEST (node, mass_block_new) nano::node_config node_config = system.default_config (); node_config.backlog_scan.enable = false; auto & node = *system.add_node (node_config); - node.network_params.network.aec_loop_interval_ms = 500; + node.network_params.network.aec_loop_interval = 500ms; #ifndef NDEBUG auto const num_blocks = 5000;