Bootstrap stale elections

This commit is contained in:
Piotr Wójcik 2025-04-10 12:36:28 +02:00
commit 0cc46ae3ba
8 changed files with 87 additions and 18 deletions

View file

@ -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<nano::election> 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);
}

View file

@ -490,6 +490,7 @@ enum class detail
stopped,
confirm_dependent,
forks_cached,
bootstrap_stale,
// unchecked
put,

View file

@ -4,6 +4,7 @@
#include <nano/lib/numbers.hpp>
#include <nano/lib/threading.hpp>
#include <nano/node/active_elections.hpp>
#include <nano/node/bootstrap/bootstrap_service.hpp>
#include <nano/node/confirmation_solicitor.hpp>
#include <nano/node/confirming_set.hpp>
#include <nano/node/election.hpp>
@ -120,7 +121,8 @@ void nano::active_elections::run ()
node.stats.inc (nano::stat::type::active, nano::stat::detail::loop);
tick_elections (lock);
debug_assert (lock.owns_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);
@ -270,36 +272,39 @@ void nano::active_elections::tick_elections (nano::unique_lock<nano::mutex> & lo
{
debug_assert (lock.owns_lock ());
auto const elections_l = list_active_impl ();
auto const election_list = list_active_impl ();
lock.unlock ();
nano::confirmation_solicitor solicitor (node.network, node.config);
solicitor.prepare (node.rep_crawler.principal_representatives (std::numeric_limits<std::size_t>::max ()));
std::size_t unconfirmed_count_l (0);
nano::timer<std::chrono::milliseconds> 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<std::shared_ptr<nano::election>> 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.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<nano::mutex> & lock_a, std::shared_ptr<nano::election> election)
@ -637,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 ();
}
@ -648,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 ();
}

View file

@ -1,6 +1,7 @@
#pragma once
#include <nano/lib/enum_util.hpp>
#include <nano/lib/interval.hpp>
#include <nano/lib/numbers.hpp>
#include <nano/lib/observer_set.hpp>
#include <nano/node/election_behavior.hpp>
@ -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 };
};
/**
@ -164,6 +167,8 @@ private:
bool stopped{ false };
std::thread thread;
nano::interval bootstrap_stale_interval;
friend class election;
public: // Tests

View file

@ -163,6 +163,12 @@ void nano::bootstrap_service::reset ()
throttle.reset ();
}
void nano::bootstrap_service::prioritize (nano::account const & account)
{
nano::lock_guard<nano::mutex> lock{ mutex };
accounts.priority_set (account);
}
bool nano::bootstrap_service::send (std::shared_ptr<nano::transport::channel> const & channel, async_tag tag)
{
debug_assert (tag.type != query_type::invalid);

View file

@ -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;

View file

@ -805,6 +805,12 @@ void nano::election::force_confirm ()
confirm_once (lock);
}
nano::account nano::election::account () const
{
nano::lock_guard<nano::mutex> guard{ mutex };
return status.winner->account ();
}
std::unordered_map<nano::block_hash, std::shared_ptr<nano::block>> nano::election::blocks () const
{
nano::lock_guard<nano::mutex> guard{ mutex };

View file

@ -142,6 +142,7 @@ public: // Information
nano::root const root;
nano::qualified_root const qualified_root;
nano::account account () const;
std::vector<nano::vote_with_weight_info> votes_with_weight () const;
nano::election_behavior behavior () const;
nano::election_state state () const;