From 78666822fc064c7ceec2aa0bbbd299dfe4076f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Thu, 26 Sep 2024 18:04:13 +0200 Subject: [PATCH 01/12] Inconsistent enable / enabled config naming --- nano/core_test/active_elections.cpp | 6 +++--- nano/core_test/node.cpp | 6 +++--- nano/core_test/toml.cpp | 8 ++++---- nano/core_test/vote_processor.cpp | 4 ++-- nano/node/monitor.cpp | 6 +++--- nano/node/monitor.hpp | 2 +- nano/node/scheduler/hinted.cpp | 6 +++--- nano/node/scheduler/hinted.hpp | 2 +- nano/node/scheduler/optimistic.cpp | 8 ++++---- nano/node/scheduler/optimistic.hpp | 2 +- nano/node/scheduler/priority.cpp | 2 +- nano/node/scheduler/priority.hpp | 2 +- 12 files changed, 27 insertions(+), 27 deletions(-) diff --git a/nano/core_test/active_elections.cpp b/nano/core_test/active_elections.cpp index 6c13d4a02..5a38433ec 100644 --- a/nano/core_test/active_elections.cpp +++ b/nano/core_test/active_elections.cpp @@ -434,8 +434,8 @@ TEST (inactive_votes_cache, election_start) nano::test::system system; nano::node_config node_config = system.default_config (); node_config.backlog_population.enable = false; - node_config.priority_scheduler.enabled = false; - node_config.optimistic_scheduler.enabled = false; + node_config.priority_scheduler.enable = false; + node_config.optimistic_scheduler.enable = false; auto & node = *system.add_node (node_config); nano::block_hash latest (node.latest (nano::dev::genesis_key.pub)); nano::keypair key1, key2; @@ -1332,7 +1332,7 @@ TEST (active_elections, limit_vote_hinted_elections) nano::node_config config = system.default_config (); const int aec_limit = 10; config.backlog_population.enable = false; - config.optimistic_scheduler.enabled = false; + config.optimistic_scheduler.enable = false; config.active_elections.size = aec_limit; config.active_elections.hinted_limit_percentage = 10; // Should give us a limit of 1 hinted election auto & node = *system.add_node (config); diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index 373728e62..82d5d6871 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -3761,9 +3761,9 @@ TEST (node, local_block_broadcast) // Disable active elections to prevent the block from being broadcasted by the election auto node_config = system.default_config (); - node_config.priority_scheduler.enabled = false; - node_config.hinted_scheduler.enabled = false; - node_config.optimistic_scheduler.enabled = false; + node_config.priority_scheduler.enable = false; + node_config.hinted_scheduler.enable = false; + node_config.optimistic_scheduler.enable = false; node_config.local_block_broadcaster.rebroadcast_interval = 1s; auto & node1 = *system.add_node (node_config); auto & node2 = *system.make_disconnected_node (); diff --git a/nano/core_test/toml.cpp b/nano/core_test/toml.cpp index 617f655af..eb1cb68ae 100644 --- a/nano/core_test/toml.cpp +++ b/nano/core_test/toml.cpp @@ -245,11 +245,11 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.rocksdb_config.read_cache, defaults.node.rocksdb_config.read_cache); ASSERT_EQ (conf.node.rocksdb_config.write_cache, defaults.node.rocksdb_config.write_cache); - ASSERT_EQ (conf.node.optimistic_scheduler.enabled, defaults.node.optimistic_scheduler.enabled); + ASSERT_EQ (conf.node.optimistic_scheduler.enable, defaults.node.optimistic_scheduler.enable); ASSERT_EQ (conf.node.optimistic_scheduler.gap_threshold, defaults.node.optimistic_scheduler.gap_threshold); ASSERT_EQ (conf.node.optimistic_scheduler.max_size, defaults.node.optimistic_scheduler.max_size); - ASSERT_EQ (conf.node.hinted_scheduler.enabled, defaults.node.hinted_scheduler.enabled); + ASSERT_EQ (conf.node.hinted_scheduler.enable, defaults.node.hinted_scheduler.enable); ASSERT_EQ (conf.node.hinted_scheduler.hinting_threshold_percent, defaults.node.hinted_scheduler.hinting_threshold_percent); ASSERT_EQ (conf.node.hinted_scheduler.check_interval.count (), defaults.node.hinted_scheduler.check_interval.count ()); ASSERT_EQ (conf.node.hinted_scheduler.block_cooldown.count (), defaults.node.hinted_scheduler.block_cooldown.count ()); @@ -754,11 +754,11 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.rocksdb_config.read_cache, defaults.node.rocksdb_config.read_cache); ASSERT_NE (conf.node.rocksdb_config.write_cache, defaults.node.rocksdb_config.write_cache); - ASSERT_NE (conf.node.optimistic_scheduler.enabled, defaults.node.optimistic_scheduler.enabled); + ASSERT_NE (conf.node.optimistic_scheduler.enable, defaults.node.optimistic_scheduler.enable); ASSERT_NE (conf.node.optimistic_scheduler.gap_threshold, defaults.node.optimistic_scheduler.gap_threshold); ASSERT_NE (conf.node.optimistic_scheduler.max_size, defaults.node.optimistic_scheduler.max_size); - ASSERT_NE (conf.node.hinted_scheduler.enabled, defaults.node.hinted_scheduler.enabled); + ASSERT_NE (conf.node.hinted_scheduler.enable, defaults.node.hinted_scheduler.enable); ASSERT_NE (conf.node.hinted_scheduler.hinting_threshold_percent, defaults.node.hinted_scheduler.hinting_threshold_percent); ASSERT_NE (conf.node.hinted_scheduler.check_interval.count (), defaults.node.hinted_scheduler.check_interval.count ()); ASSERT_NE (conf.node.hinted_scheduler.block_cooldown.count (), defaults.node.hinted_scheduler.block_cooldown.count ()); diff --git a/nano/core_test/vote_processor.cpp b/nano/core_test/vote_processor.cpp index 3f656794e..8b298dea1 100644 --- a/nano/core_test/vote_processor.cpp +++ b/nano/core_test/vote_processor.cpp @@ -20,8 +20,8 @@ TEST (vote_processor, codes) auto node_config = system.default_config (); // Disable all election schedulers node_config.backlog_population.enable = false; - node_config.hinted_scheduler.enabled = false; - node_config.optimistic_scheduler.enabled = false; + node_config.hinted_scheduler.enable = false; + node_config.optimistic_scheduler.enable = false; auto & node = *system.add_node (node_config); auto blocks = nano::test::setup_chain (system, node, 1, nano::dev::genesis_key, false); diff --git a/nano/node/monitor.cpp b/nano/node/monitor.cpp index 47b9f8905..44ce038bb 100644 --- a/nano/node/monitor.cpp +++ b/nano/node/monitor.cpp @@ -19,7 +19,7 @@ nano::monitor::~monitor () void nano::monitor::start () { - if (!config.enabled) + if (!config.enable) { return; } @@ -115,7 +115,7 @@ void nano::monitor::run_one () nano::error nano::monitor_config::serialize (nano::tomlconfig & toml) const { - toml.put ("enable", enabled, "Enable or disable periodic node status logging\ntype:bool"); + toml.put ("enable", enable, "Enable or disable periodic node status logging\ntype:bool"); toml.put ("interval", interval.count (), "Interval between status logs\ntype:seconds"); return toml.get_error (); @@ -123,7 +123,7 @@ nano::error nano::monitor_config::serialize (nano::tomlconfig & toml) const nano::error nano::monitor_config::deserialize (nano::tomlconfig & toml) { - toml.get ("enable", enabled); + toml.get ("enable", enable); auto interval_l = interval.count (); toml.get ("interval", interval_l); interval = std::chrono::seconds{ interval_l }; diff --git a/nano/node/monitor.hpp b/nano/node/monitor.hpp index a76f753bb..a351297d2 100644 --- a/nano/node/monitor.hpp +++ b/nano/node/monitor.hpp @@ -17,7 +17,7 @@ public: nano::error serialize (nano::tomlconfig &) const; public: - bool enabled{ true }; + bool enable{ true }; std::chrono::seconds interval{ 60s }; }; diff --git a/nano/node/scheduler/hinted.cpp b/nano/node/scheduler/hinted.cpp index a507d5e51..38592313a 100644 --- a/nano/node/scheduler/hinted.cpp +++ b/nano/node/scheduler/hinted.cpp @@ -31,7 +31,7 @@ void nano::scheduler::hinted::start () { debug_assert (!thread.joinable ()); - if (!config.enabled) + if (!config.enable) { return; } @@ -260,7 +260,7 @@ nano::scheduler::hinted_config::hinted_config (nano::network_constants const & n nano::error nano::scheduler::hinted_config::serialize (nano::tomlconfig & toml) const { - toml.put ("enable", enabled, "Enable or disable hinted elections\ntype:bool"); + toml.put ("enable", enable, "Enable or disable hinted elections\ntype:bool"); toml.put ("hinting_threshold", hinting_threshold_percent, "Percentage of online weight needed to start a hinted election. \ntype:uint32,[0,100]"); toml.put ("check_interval", check_interval.count (), "Interval between scans of the vote cache for possible hinted elections. \ntype:milliseconds"); toml.put ("block_cooldown", block_cooldown.count (), "Cooldown period for blocks that failed to start an election. \ntype:milliseconds"); @@ -271,7 +271,7 @@ nano::error nano::scheduler::hinted_config::serialize (nano::tomlconfig & toml) nano::error nano::scheduler::hinted_config::deserialize (nano::tomlconfig & toml) { - toml.get ("enable", enabled); + toml.get ("enable", enable); toml.get ("hinting_threshold", hinting_threshold_percent); auto check_interval_l = check_interval.count (); diff --git a/nano/node/scheduler/hinted.hpp b/nano/node/scheduler/hinted.hpp index 12eea0469..8a90564dc 100644 --- a/nano/node/scheduler/hinted.hpp +++ b/nano/node/scheduler/hinted.hpp @@ -41,7 +41,7 @@ public: nano::error serialize (nano::tomlconfig & toml) const; public: - bool enabled{ true }; + bool enable{ true }; std::chrono::milliseconds check_interval{ 1000 }; std::chrono::milliseconds block_cooldown{ 10000 }; unsigned hinting_threshold_percent{ 10 }; diff --git a/nano/node/scheduler/optimistic.cpp b/nano/node/scheduler/optimistic.cpp index 5e4440d05..6a8c0b9b9 100644 --- a/nano/node/scheduler/optimistic.cpp +++ b/nano/node/scheduler/optimistic.cpp @@ -29,7 +29,7 @@ void nano::scheduler::optimistic::start () { debug_assert (!thread.joinable ()); - if (!config.enabled) + if (!config.enable) { return; } @@ -72,7 +72,7 @@ bool nano::scheduler::optimistic::activate_predicate (const nano::account_info & bool nano::scheduler::optimistic::activate (const nano::account & account, const nano::account_info & account_info, const nano::confirmation_height_info & conf_info) { - if (!config.enabled) + if (!config.enable) { return false; } @@ -183,7 +183,7 @@ std::unique_ptr nano::scheduler::optimistic::col nano::error nano::scheduler::optimistic_config::deserialize (nano::tomlconfig & toml) { - toml.get ("enable", enabled); + toml.get ("enable", enable); toml.get ("gap_threshold", gap_threshold); toml.get ("max_size", max_size); @@ -192,7 +192,7 @@ nano::error nano::scheduler::optimistic_config::deserialize (nano::tomlconfig & nano::error nano::scheduler::optimistic_config::serialize (nano::tomlconfig & toml) const { - toml.put ("enable", enabled, "Enable or disable optimistic elections\ntype:bool"); + toml.put ("enable", enable, "Enable or disable optimistic elections\ntype:bool"); toml.put ("gap_threshold", gap_threshold, "Minimum difference between confirmation frontier and account frontier to become a candidate for optimistic confirmation\ntype:uint64"); toml.put ("max_size", max_size, "Maximum number of candidates stored in memory\ntype:uint64"); diff --git a/nano/node/scheduler/optimistic.hpp b/nano/node/scheduler/optimistic.hpp index a89ce0290..87bfdc991 100644 --- a/nano/node/scheduler/optimistic.hpp +++ b/nano/node/scheduler/optimistic.hpp @@ -37,7 +37,7 @@ public: nano::error serialize (nano::tomlconfig & toml) const; public: - bool enabled{ true }; + bool enable{ true }; /** Minimum difference between confirmation frontier and account frontier to become a candidate for optimistic confirmation */ std::size_t gap_threshold{ 32 }; diff --git a/nano/node/scheduler/priority.cpp b/nano/node/scheduler/priority.cpp index 030b7c654..10ce1f54f 100644 --- a/nano/node/scheduler/priority.cpp +++ b/nano/node/scheduler/priority.cpp @@ -55,7 +55,7 @@ void nano::scheduler::priority::start () debug_assert (!thread.joinable ()); debug_assert (!cleanup_thread.joinable ()); - if (!config.enabled) + if (!config.enable) { return; } diff --git a/nano/node/scheduler/priority.hpp b/nano/node/scheduler/priority.hpp index e80d6b74b..7c35bd1d4 100644 --- a/nano/node/scheduler/priority.hpp +++ b/nano/node/scheduler/priority.hpp @@ -31,7 +31,7 @@ public: // TODO: Serialization & deserialization public: - bool enabled{ true }; + bool enable{ true }; }; class buckets; From dbb72034d9f8d8bd31e8dd380a1dc1c883c659fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Fri, 2 Aug 2024 19:41:18 +0200 Subject: [PATCH 02/12] Use normal atomic in `thread_pool` --- nano/core_test/utility.cpp | 1 + nano/lib/thread_pool.hpp | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nano/core_test/utility.cpp b/nano/core_test/utility.cpp index 730714dc8..791a89d10 100644 --- a/nano/core_test/utility.cpp +++ b/nano/core_test/utility.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include diff --git a/nano/lib/thread_pool.hpp b/nano/lib/thread_pool.hpp index b8eb29f9c..f6346c542 100644 --- a/nano/lib/thread_pool.hpp +++ b/nano/lib/thread_pool.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -44,7 +43,7 @@ private: std::atomic stopped{ false }; unsigned num_threads; std::unique_ptr thread_pool_m; - nano::relaxed_atomic_integral num_tasks{ 0 }; + std::atomic num_tasks{ 0 }; /** Set the names of all the threads in the thread pool for easier identification */ std::latch thread_names_latch; From 7baf378420e94c2ac4f086b54b3c2481ed5950f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Tue, 27 Aug 2024 20:19:28 +0200 Subject: [PATCH 03/12] Implement frontier scanning --- nano/core_test/toml.cpp | 4 +- nano/lib/rate_limiting.hpp | 4 +- nano/lib/stats_enums.hpp | 29 +- nano/lib/thread_roles.cpp | 5 +- nano/lib/thread_roles.hpp | 1 + nano/node/CMakeLists.txt | 8 +- nano/node/bootstrap/bootstrap_config.cpp | 2 + nano/node/bootstrap/bootstrap_config.hpp | 20 +- .../bootstrap_ascending/frontier_scan.cpp | 187 ++++++++++++ .../bootstrap_ascending/frontier_scan.hpp | 76 +++++ nano/node/bootstrap_ascending/service.cpp | 269 ++++++++++++++++-- nano/node/bootstrap_ascending/service.hpp | 20 +- nano/node/messages.hpp | 2 +- 13 files changed, 588 insertions(+), 39 deletions(-) create mode 100644 nano/node/bootstrap_ascending/frontier_scan.cpp create mode 100644 nano/node/bootstrap_ascending/frontier_scan.hpp diff --git a/nano/core_test/toml.cpp b/nano/core_test/toml.cpp index eb1cb68ae..1ef6deba9 100644 --- a/nano/core_test/toml.cpp +++ b/nano/core_test/toml.cpp @@ -601,7 +601,8 @@ TEST (toml, daemon_config_deserialize_no_defaults) [node.bootstrap_ascending] enable = false - enable_database_scan = false + enable_frontier_scan = false + enable_database_scan = true enable_dependency_walker = false channel_limit = 999 database_rate_limit = 999 @@ -780,6 +781,7 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.vote_processor.batch_size, defaults.node.vote_processor.batch_size); ASSERT_NE (conf.node.bootstrap_ascending.enable, defaults.node.bootstrap_ascending.enable); + ASSERT_NE (conf.node.bootstrap_ascending.enable_frontier_scan, defaults.node.bootstrap_ascending.enable_frontier_scan); ASSERT_NE (conf.node.bootstrap_ascending.enable_database_scan, defaults.node.bootstrap_ascending.enable_database_scan); ASSERT_NE (conf.node.bootstrap_ascending.enable_dependency_walker, defaults.node.bootstrap_ascending.enable_dependency_walker); ASSERT_NE (conf.node.bootstrap_ascending.channel_limit, defaults.node.bootstrap_ascending.channel_limit); diff --git a/nano/lib/rate_limiting.hpp b/nano/lib/rate_limiting.hpp index f67246787..5e209f4d6 100644 --- a/nano/lib/rate_limiting.hpp +++ b/nano/lib/rate_limiting.hpp @@ -64,10 +64,10 @@ class rate_limiter final { public: // initialize with limit 0 = unbounded - rate_limiter (std::size_t limit, double burst_ratio); + rate_limiter (std::size_t limit, double burst_ratio = 1.0); bool should_pass (std::size_t buffer_size); - void reset (std::size_t limit, double burst_ratio); + void reset (std::size_t limit, double burst_ratio = 1.0); private: nano::rate::token_bucket bucket; diff --git a/nano/lib/stats_enums.hpp b/nano/lib/stats_enums.hpp index c8f3e5f81..e00adb8b4 100644 --- a/nano/lib/stats_enums.hpp +++ b/nano/lib/stats_enums.hpp @@ -60,11 +60,13 @@ enum class type blockprocessor_overfill, bootstrap_ascending, bootstrap_ascending_accounts, - bootstrap_ascending_verify, + bootstrap_ascending_verify_blocks, + bootstrap_ascending_verify_frontiers, bootstrap_ascending_process, bootstrap_ascending_request, bootstrap_ascending_reply, bootstrap_ascending_next, + bootstrap_ascending_frontiers, bootstrap_server, bootstrap_server_request, bootstrap_server_overfill, @@ -117,6 +119,7 @@ enum class detail inserted, erased, request, + request_failed, broadcast, cleanup, top, @@ -423,7 +426,7 @@ enum class detail missing_cookie, invalid_genesis, - // bootstrap ascending + // bootstrap_ascending missing_tag, reply, throttled, @@ -431,13 +434,18 @@ enum class detail timeout, nothing_new, account_info_empty, + frontiers_empty, loop_database, loop_dependencies, + loop_frontiers, + loop_frontiers_processing, duplicate_request, invalid_response_type, timestamp_reset, + process_frontiers, + dropped_frontiers, - // bootstrap ascending accounts + // bootstrap_ascending_accounts prioritize, prioritize_failed, block, @@ -446,11 +454,21 @@ enum class detail dependency_update, dependency_update_failed, + // bootstrap_ascending_frontiers + done, + done_range, + done_empty, + next_by_requests, + next_by_timestamp, + advance, + advance_failed, + next_none, next_priority, next_database, next_blocking, next_dependency, + next_frontier, blocking_insert, blocking_erase_overflow, @@ -461,6 +479,10 @@ enum class detail deprioritize, deprioritize_failed, sync_dependencies, + frontiers_processed, + frontiers_prioritized, + frontiers_outdated, + frontiers_pending, request_blocks, request_account_info, @@ -468,6 +490,7 @@ enum class detail // active started_hinted, started_optimistic, + // rep_crawler channel_dead, query_target_failed, diff --git a/nano/lib/thread_roles.cpp b/nano/lib/thread_roles.cpp index 76f467cd0..913c4aaa7 100644 --- a/nano/lib/thread_roles.cpp +++ b/nano/lib/thread_roles.cpp @@ -101,7 +101,10 @@ std::string nano::thread_role::get_string (nano::thread_role::name role) thread_role_name_string = "Voting que"; break; case nano::thread_role::name::ascending_bootstrap: - thread_role_name_string = "Bootstrap asc"; + thread_role_name_string = "Ascboot"; + break; + case nano::thread_role::name::ascending_bootstrap_worker: + thread_role_name_string = "Ascboot work"; break; case nano::thread_role::name::bootstrap_server: thread_role_name_string = "Bootstrap serv"; diff --git a/nano/lib/thread_roles.hpp b/nano/lib/thread_roles.hpp index 5896318c4..9092392a7 100644 --- a/nano/lib/thread_roles.hpp +++ b/nano/lib/thread_roles.hpp @@ -41,6 +41,7 @@ enum class name bootstrap_server, telemetry, ascending_bootstrap, + ascending_bootstrap_worker, bootstrap_server_requests, bootstrap_server_responses, scheduler_hinted, diff --git a/nano/node/CMakeLists.txt b/nano/node/CMakeLists.txt index 419e00a72..548d16c0d 100644 --- a/nano/node/CMakeLists.txt +++ b/nano/node/CMakeLists.txt @@ -44,13 +44,15 @@ add_library( bootstrap/bootstrap.cpp bootstrap/bootstrap_server.hpp bootstrap/bootstrap_server.cpp - bootstrap_ascending/common.hpp - bootstrap_ascending/throttle.hpp - bootstrap_ascending/throttle.cpp bootstrap_ascending/account_sets.hpp bootstrap_ascending/account_sets.cpp bootstrap_ascending/database_scan.hpp bootstrap_ascending/database_scan.cpp + bootstrap_ascending/common.hpp + bootstrap_ascending/frontier_scan.hpp + bootstrap_ascending/frontier_scan.cpp + bootstrap_ascending/throttle.hpp + bootstrap_ascending/throttle.cpp bootstrap_ascending/peer_scoring.hpp bootstrap_ascending/peer_scoring.cpp bootstrap_ascending/service.hpp diff --git a/nano/node/bootstrap/bootstrap_config.cpp b/nano/node/bootstrap/bootstrap_config.cpp index ff1e847da..2264776e0 100644 --- a/nano/node/bootstrap/bootstrap_config.cpp +++ b/nano/node/bootstrap/bootstrap_config.cpp @@ -34,6 +34,7 @@ nano::error nano::bootstrap_ascending_config::deserialize (nano::tomlconfig & to toml.get ("enable", enable); toml.get ("enable_database_scan", enable_database_scan); toml.get ("enable_dependency_walker", enable_dependency_walker); + toml.get ("enable_frontier_scan", enable_frontier_scan); toml.get ("channel_limit", channel_limit); toml.get ("database_rate_limit", database_rate_limit); @@ -59,6 +60,7 @@ nano::error nano::bootstrap_ascending_config::serialize (nano::tomlconfig & toml toml.put ("enable", enable, "Enable or disable the ascending bootstrap. Disabling it is not recommended and will prevent the node from syncing.\ntype:bool"); toml.put ("enable_database_scan", enable_database_scan, "Enable or disable the 'database scan` strategy for the ascending bootstrap.\ntype:bool"); toml.put ("enable_dependency_walker", enable_dependency_walker, "Enable or disable the 'dependency walker` strategy for the ascending bootstrap.\ntype:bool"); + toml.put ("enable_frontier_scan", enable_frontier_scan, "Enable or disable the 'frontier scan` strategy for the ascending bootstrap.\ntype:bool"); toml.put ("channel_limit", channel_limit, "Maximum number of un-responded requests per channel.\nNote: changing to unlimited (0) is not recommended.\ntype:uint64"); toml.put ("database_rate_limit", database_rate_limit, "Rate limit on scanning accounts and pending entries from database.\nNote: changing to unlimited (0) is not recommended as this operation competes for resources on querying the database.\ntype:uint64"); diff --git a/nano/node/bootstrap/bootstrap_config.hpp b/nano/node/bootstrap/bootstrap_config.hpp index af7b98bcb..cd2c9b8d7 100644 --- a/nano/node/bootstrap/bootstrap_config.hpp +++ b/nano/node/bootstrap/bootstrap_config.hpp @@ -22,6 +22,19 @@ public: std::chrono::milliseconds cooldown{ 1000 * 3 }; }; +// TODO: This should be moved next to `frontier_scan` class +class frontier_scan_config final +{ +public: + // TODO: Serialize & deserialize + + unsigned head_parallelistm{ 128 }; + unsigned consideration_count{ 4 }; + std::size_t candidates{ 1000 }; + std::chrono::milliseconds cooldown{ 1000 * 5 }; + std::size_t max_pending{ 16 }; +}; + // TODO: This should be moved next to `bootstrap_ascending` class class bootstrap_ascending_config final { @@ -31,12 +44,14 @@ public: public: bool enable{ true }; - bool enable_database_scan{ true }; + bool enable_database_scan{ false }; bool enable_dependency_walker{ true }; + bool enable_frontier_scan{ true }; // Maximum number of un-responded requests per channel, should be lower or equal to bootstrap server max queue size std::size_t channel_limit{ 16 }; std::size_t database_rate_limit{ 256 }; + std::size_t frontier_rate_limit{ 100 }; std::size_t database_warmup_ratio{ 10 }; std::size_t max_pull_count{ nano::bootstrap_server::max_blocks }; std::chrono::milliseconds request_timeout{ 1000 * 5 }; @@ -45,6 +60,7 @@ public: std::size_t block_processor_threshold{ 1000 }; std::size_t max_requests{ 1024 }; - nano::account_sets_config account_sets; + account_sets_config account_sets; + frontier_scan_config frontier_scan; }; } diff --git a/nano/node/bootstrap_ascending/frontier_scan.cpp b/nano/node/bootstrap_ascending/frontier_scan.cpp new file mode 100644 index 000000000..28fab99de --- /dev/null +++ b/nano/node/bootstrap_ascending/frontier_scan.cpp @@ -0,0 +1,187 @@ +#include + +#include +#include + +nano::bootstrap_ascending::frontier_scan::frontier_scan (frontier_scan_config const & config_a, nano::stats & stats_a) : + config{ config_a }, + stats{ stats_a } +{ + // Divide nano::account numeric range into consecutive and equal ranges + nano::uint256_t max_account = std::numeric_limits::max (); + nano::uint256_t range_size = max_account / config.head_parallelistm; + + for (unsigned i = 0; i < config.head_parallelistm; ++i) + { + // Start at 1 to avoid the burn account + nano::uint256_t start = (i == 0) ? 1 : i * range_size; + nano::uint256_t end = (i == config.head_parallelistm - 1) ? max_account : start + range_size; + + heads.emplace_back (frontier_head{ nano::account{ start }, nano::account{ end } }); + } + + release_assert (!heads.empty ()); +} + +nano::account nano::bootstrap_ascending::frontier_scan::next () +{ + auto const cutoff = std::chrono::steady_clock::now () - config.cooldown; + + auto & heads_by_timestamp = heads.get (); + for (auto it = heads_by_timestamp.begin (); it != heads_by_timestamp.end (); ++it) + { + auto const & head = *it; + + if (head.requests < config.consideration_count || head.timestamp < cutoff) + { + stats.inc (nano::stat::type::bootstrap_ascending_frontiers, (head.requests < config.consideration_count) ? nano::stat::detail::next_by_requests : nano::stat::detail::next_by_timestamp); + + debug_assert (head.next.number () >= head.start.number ()); + debug_assert (head.next.number () < head.end.number ()); + + auto result = head.next; + + heads_by_timestamp.modify (it, [this] (auto & entry) { + entry.requests += 1; + entry.timestamp = std::chrono::steady_clock::now (); + }); + + return result; + } + } + + stats.inc (nano::stat::type::bootstrap_ascending_frontiers, nano::stat::detail::next_none); + return { 0 }; +} + +bool nano::bootstrap_ascending::frontier_scan::process (nano::account start, std::deque> const & response) +{ + debug_assert (std::all_of (response.begin (), response.end (), [&] (auto const & pair) { return pair.first.number () >= start.number (); })); + + stats.inc (nano::stat::type::bootstrap_ascending_frontiers, nano::stat::detail::process); + + // Find the first head with head.start <= start + auto & heads_by_start = heads.get (); + auto it = heads_by_start.upper_bound (start); + release_assert (it != heads_by_start.begin ()); + it = std::prev (it); + + bool done = false; + heads_by_start.modify (it, [this, &response, &done] (frontier_head & entry) { + entry.completed += 1; + + for (auto const & [account, _] : response) + { + // Only consider candidates that actually advance the current frontier + if (account.number () > entry.next.number ()) + { + entry.candidates.insert (account); + } + } + + // Trim the candidates + while (entry.candidates.size () > config.candidates) + { + release_assert (!entry.candidates.empty ()); + entry.candidates.erase (std::prev (entry.candidates.end ())); + } + + // Special case for the last frontier head that won't receive larger than max frontier + if (entry.completed >= config.consideration_count * 2 && entry.candidates.empty ()) + { + stats.inc (nano::stat::type::bootstrap_ascending_frontiers, nano::stat::detail::done_empty); + entry.candidates.insert (entry.end); + } + + // Check if done + if (entry.completed >= config.consideration_count && !entry.candidates.empty ()) + { + stats.inc (nano::stat::type::bootstrap_ascending_frontiers, nano::stat::detail::done); + + // Take the last candidate as the next frontier + release_assert (!entry.candidates.empty ()); + auto it = std::prev (entry.candidates.end ()); + + debug_assert (entry.next.number () < it->number ()); + entry.next = *it; + entry.processed += entry.candidates.size (); + entry.candidates.clear (); + entry.requests = 0; + entry.completed = 0; + entry.timestamp = {}; + + // Bound the search range + if (entry.next.number () >= entry.end.number ()) + { + stats.inc (nano::stat::type::bootstrap_ascending_frontiers, nano::stat::detail::done_range); + entry.next = entry.start; + } + + done = true; + } + }); + + return done; +} + +std::unique_ptr nano::bootstrap_ascending::frontier_scan::collect_container_info (std::string const & name) +{ + auto collect_progress = [&] () { + auto composite = std::make_unique ("progress"); + for (int n = 0; n < heads.size (); ++n) + { + auto const & head = heads[n]; + + boost::multiprecision::cpp_dec_float_50 start{ head.start.number ().str () }; + boost::multiprecision::cpp_dec_float_50 next{ head.next.number ().str () }; + boost::multiprecision::cpp_dec_float_50 end{ head.end.number ().str () }; + + boost::multiprecision::cpp_dec_float_50 progress = (next - start) * boost::multiprecision::cpp_dec_float_50 (1000000) / (end - start); + + composite->add_component (std::make_unique (container_info{ std::to_string (n), progress.convert_to (), 6 })); + } + return composite; + }; + + auto collect_candidates = [&] () { + auto composite = std::make_unique ("candidates"); + for (int n = 0; n < heads.size (); ++n) + { + auto const & head = heads[n]; + composite->add_component (std::make_unique (container_info{ std::to_string (n), head.candidates.size (), 0 })); + } + return composite; + }; + + auto collect_responses = [&] () { + auto composite = std::make_unique ("responses"); + for (int n = 0; n < heads.size (); ++n) + { + auto const & head = heads[n]; + composite->add_component (std::make_unique (container_info{ std::to_string (n), head.completed, 0 })); + } + return composite; + }; + + auto collect_processed = [&] () { + auto composite = std::make_unique ("processed"); + for (int n = 0; n < heads.size (); ++n) + { + auto const & head = heads[n]; + composite->add_component (std::make_unique (container_info{ std::to_string (n), head.processed, 0 })); + } + return composite; + }; + + auto total_processed = std::accumulate (heads.begin (), heads.end (), std::size_t{ 0 }, [] (auto total, auto const & head) { + return total + head.processed; + }); + + auto composite = std::make_unique (name); + composite->add_component (std::make_unique (container_info{ "total_processed", total_processed, 0 })); + composite->add_component (collect_progress ()); + composite->add_component (collect_candidates ()); + composite->add_component (collect_responses ()); + composite->add_component (collect_processed ()); + return composite; +} diff --git a/nano/node/bootstrap_ascending/frontier_scan.hpp b/nano/node/bootstrap_ascending/frontier_scan.hpp new file mode 100644 index 000000000..382c59b47 --- /dev/null +++ b/nano/node/bootstrap_ascending/frontier_scan.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace mi = boost::multi_index; + +namespace nano::bootstrap_ascending +{ +class frontier_scan +{ +public: + frontier_scan (frontier_scan_config const &, nano::stats &); + + nano::account next (); + bool process (nano::account start, std::deque> const & response); + + std::unique_ptr collect_container_info (std::string const & name); + +private: // Dependencies + frontier_scan_config const & config; + nano::stats & stats; + +private: + struct frontier_head + { + frontier_head (nano::account start_a, nano::account end_a) : + start{ start_a }, + end{ end_a }, + next{ start_a } + { + } + + // The range of accounts to scan is [start, end) + nano::account start; + nano::account end; + + nano::account next; + std::set candidates; + + unsigned requests{ 0 }; + unsigned completed{ 0 }; + std::chrono::steady_clock::time_point timestamp{}; + size_t processed{ 0 }; // Total number of accounts processed + }; + + // clang-format off + class tag_sequenced {}; + class tag_start {}; + class tag_timestamp {}; + + using ordered_heads = boost::multi_index_container>, + mi::ordered_unique, + mi::member>, + mi::ordered_non_unique, + mi::member> + >>; + // clang-format on + + ordered_heads heads; +}; +} \ No newline at end of file diff --git a/nano/node/bootstrap_ascending/service.cpp b/nano/node/bootstrap_ascending/service.cpp index 450767f03..1de0f696c 100644 --- a/nano/node/bootstrap_ascending/service.cpp +++ b/nano/node/bootstrap_ascending/service.cpp @@ -29,9 +29,12 @@ nano::bootstrap_ascending::service::service (nano::node_config const & node_conf logger{ logger_a }, accounts{ config.account_sets, stats }, database_scan{ ledger }, + frontiers{ config.frontier_scan, stats }, throttle{ compute_throttle_size () }, scoring{ config, node_config_a.network_params.network }, - database_limiter{ config.database_rate_limit, 1.0 } + database_limiter{ config.database_rate_limit }, + frontiers_limiter{ config.frontier_rate_limit }, + workers{ 1, nano::thread_role::name::ascending_bootstrap_worker } { // TODO: This is called from a very congested blockprocessor thread. Offload this work to a dedicated processing thread block_processor.batch_processed.add ([this] (auto const & batch) { @@ -57,6 +60,7 @@ nano::bootstrap_ascending::service::~service () debug_assert (!priorities_thread.joinable ()); debug_assert (!database_thread.joinable ()); debug_assert (!dependencies_thread.joinable ()); + debug_assert (!frontiers_thread.joinable ()); debug_assert (!timeout_thread.joinable ()); } @@ -65,6 +69,7 @@ void nano::bootstrap_ascending::service::start () debug_assert (!priorities_thread.joinable ()); debug_assert (!database_thread.joinable ()); debug_assert (!dependencies_thread.joinable ()); + debug_assert (!frontiers_thread.joinable ()); debug_assert (!timeout_thread.joinable ()); if (!config.enable) @@ -94,6 +99,14 @@ void nano::bootstrap_ascending::service::start () }); } + if (config.enable_frontier_scan) + { + frontiers_thread = std::thread ([this] () { + nano::thread_role::set (nano::thread_role::name::ascending_bootstrap); + run_frontiers (); + }); + } + timeout_thread = std::thread ([this] () { nano::thread_role::set (nano::thread_role::name::ascending_bootstrap); run_timeouts (); @@ -111,10 +124,11 @@ void nano::bootstrap_ascending::service::stop () nano::join_or_pass (priorities_thread); nano::join_or_pass (database_thread); nano::join_or_pass (dependencies_thread); + nano::join_or_pass (frontiers_thread); nano::join_or_pass (timeout_thread); } -void nano::bootstrap_ascending::service::send (std::shared_ptr const & channel, async_tag tag) +bool nano::bootstrap_ascending::service::send (std::shared_ptr const & channel, async_tag tag) { debug_assert (tag.type != query_type::invalid); debug_assert (tag.source != query_source::invalid); @@ -125,6 +139,8 @@ void nano::bootstrap_ascending::service::send (std::shared_ptr ().insert (tag); } + on_request.notify (tag, channel); + nano::asc_pull_req request{ network_constants }; request.id = tag.id; @@ -152,6 +168,16 @@ void nano::bootstrap_ascending::service::send (std::shared_ptrsend ( request, nullptr, nano::transport::buffer_drop_policy::limiter, nano::transport::traffic_type::bootstrap); + + return true; // TODO: Return channel send result } std::size_t nano::bootstrap_ascending::service::priority_size () const @@ -409,6 +437,24 @@ nano::block_hash nano::bootstrap_ascending::service::wait_blocking () return result; } +nano::account nano::bootstrap_ascending::service::wait_frontier () +{ + nano::account result{ 0 }; + + wait ([this, &result] () { + debug_assert (!mutex.try_lock ()); + result = frontiers.next (); + if (!result.is_zero ()) + { + stats.inc (nano::stat::type::bootstrap_ascending_next, nano::stat::detail::next_frontier); + return true; + } + return false; + }); + + return result; +} + bool nano::bootstrap_ascending::service::request (nano::account account, size_t count, std::shared_ptr const & channel, query_source source) { debug_assert (count > 0); @@ -436,11 +482,7 @@ bool nano::bootstrap_ascending::service::request (nano::account account, size_t tag.start = account; } - on_request.notify (tag, channel); - - send (channel, tag); - - return true; // Request sent + return send (channel, tag); } bool nano::bootstrap_ascending::service::request_info (nano::block_hash hash, std::shared_ptr const & channel, query_source source) @@ -451,11 +493,17 @@ bool nano::bootstrap_ascending::service::request_info (nano::block_hash hash, st tag.start = hash; tag.hash = hash; - on_request.notify (tag, channel); + return send (channel, tag); +} - send (channel, tag); +bool nano::bootstrap_ascending::service::request_frontiers (nano::account start, std::shared_ptr const & channel, query_source source) +{ + async_tag tag{}; + tag.type = query_type::frontiers; + tag.source = source; + tag.start = start; - return true; // Request sent + return send (channel, tag); } void nano::bootstrap_ascending::service::run_one_priority () @@ -549,6 +597,103 @@ void nano::bootstrap_ascending::service::run_dependencies () } } +void nano::bootstrap_ascending::service::run_one_frontier () +{ + wait ([this] () { + return !accounts.priority_half_full (); + }); + wait ([this] () { + return frontiers_limiter.should_pass (1); + }); + wait ([this] () { + return workers.num_queued_tasks () < config.frontier_scan.max_pending; + }); + wait_tags (); + auto channel = wait_channel (); + if (!channel) + { + return; + } + auto frontier = wait_frontier (); + if (frontier.is_zero ()) + { + return; + } + request_frontiers (frontier, channel, query_source::frontiers); +} + +void nano::bootstrap_ascending::service::run_frontiers () +{ + nano::unique_lock lock{ mutex }; + while (!stopped) + { + lock.unlock (); + stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::loop_frontiers); + run_one_frontier (); + lock.lock (); + } +} + +void nano::bootstrap_ascending::service::process_frontiers (std::deque> const & frontiers) +{ + stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::process_frontiers); + + size_t outdated = 0; + size_t pending = 0; + + // Accounts with outdated frontiers to sync + std::deque result; + { + auto transaction = ledger.tx_begin_read (); + + auto should_prioritize = [&] (nano::account const & account, nano::block_hash const & frontier) { + if (ledger.any.block_exists_or_pruned (transaction, frontier)) + { + return false; + } + if (auto info = ledger.any.account_get (transaction, account)) + { + if (info->head != frontier) + { + outdated++; + return true; // Frontier is outdated + } + return false; + } + if (auto receivable = ledger.any.receivable_lower_bound (transaction, account, { 0 })) + { + if (receivable->first.account == account) + { + pending++; + return true; // Account doesn't exist but has pending blocks in the ledger + } + return false; + } + return false; + }; + + for (auto const & [account, frontier] : frontiers) + { + if (should_prioritize (account, frontier)) + { + result.push_back (account); + } + } + } + + stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::frontiers_processed, frontiers.size ()); + stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::frontiers_prioritized, result.size ()); + stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::frontiers_outdated, outdated); + stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::frontiers_pending, pending); + + nano::lock_guard guard{ mutex }; + + for (auto const & account : result) + { + accounts.priority_set (account); + } +} + void nano::bootstrap_ascending::service::cleanup_and_sync () { debug_assert (!mutex.try_lock ()); @@ -622,7 +767,7 @@ void nano::bootstrap_ascending::service::process (nano::asc_pull_ack const & mes } bool operator() (const nano::asc_pull_ack::frontiers_payload & response) const { - return false; // TODO: Handle frontiers + return type == query_type::frontiers; } bool operator() (const nano::empty_payload & response) const { @@ -664,7 +809,7 @@ void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::bloc { case verify_result::ok: { - stats.inc (nano::stat::type::bootstrap_ascending_verify, nano::stat::detail::ok); + stats.inc (nano::stat::type::bootstrap_ascending_verify_blocks, nano::stat::detail::ok); stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::blocks, nano::stat::dir::in, response.blocks.size ()); auto blocks = response.blocks; @@ -705,7 +850,7 @@ void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::bloc break; case verify_result::nothing_new: { - stats.inc (nano::stat::type::bootstrap_ascending_verify, nano::stat::detail::nothing_new); + stats.inc (nano::stat::type::bootstrap_ascending_verify_blocks, nano::stat::detail::nothing_new); nano::lock_guard lock{ mutex }; accounts.priority_down (tag.account); @@ -717,7 +862,7 @@ void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::bloc break; case verify_result::invalid: { - stats.inc (nano::stat::type::bootstrap_ascending_verify, nano::stat::detail::invalid); + stats.inc (nano::stat::type::bootstrap_ascending_verify_blocks, nano::stat::detail::invalid); } break; } @@ -731,24 +876,69 @@ void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::acco if (response.account.is_zero ()) { stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::account_info_empty); + return; } - else - { - stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::account_info); - // Prioritize account containing the dependency - { - nano::lock_guard lock{ mutex }; - accounts.dependency_update (tag.hash, response.account); - accounts.priority_set (response.account); - } + stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::account_info); + + // Prioritize account containing the dependency + { + nano::lock_guard lock{ mutex }; + accounts.dependency_update (tag.hash, response.account); + accounts.priority_set (response.account); } } void nano::bootstrap_ascending::service::process (const nano::asc_pull_ack::frontiers_payload & response, const async_tag & tag) { - // TODO: Make use of frontiers info + debug_assert (tag.type == query_type::frontiers); + debug_assert (!tag.start.is_zero ()); + + if (response.frontiers.empty ()) + { + stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::frontiers_empty); + return; + } + stats.inc (nano::stat::type::bootstrap_ascending_process, nano::stat::detail::frontiers); + + auto result = verify (response, tag); + switch (result) + { + case verify_result::ok: + { + stats.inc (nano::stat::type::bootstrap_ascending_verify_frontiers, nano::stat::detail::ok); + stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::frontiers, nano::stat::dir::in, response.frontiers.size ()); + + { + nano::lock_guard lock{ mutex }; + frontiers.process (tag.start.as_account (), response.frontiers); + } + + // Allow some overfill to avoid unnecessarily dropping responses + if (workers.num_queued_tasks () < config.frontier_scan.max_pending * 4) + { + workers.push_task ([this, frontiers = response.frontiers] { + process_frontiers (frontiers); + }); + } + else + { + stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::dropped_frontiers); + } + } + break; + case verify_result::nothing_new: + { + stats.inc (nano::stat::type::bootstrap_ascending_verify_frontiers, nano::stat::detail::nothing_new); + } + break; + case verify_result::invalid: + { + stats.inc (nano::stat::type::bootstrap_ascending_verify_frontiers, nano::stat::detail::invalid); + } + break; + } } void nano::bootstrap_ascending::service::process (const nano::empty_payload & response, const async_tag & tag) @@ -816,6 +1006,35 @@ nano::bootstrap_ascending::service::verify_result nano::bootstrap_ascending::ser return verify_result::ok; } +nano::bootstrap_ascending::service::verify_result nano::bootstrap_ascending::service::verify (nano::asc_pull_ack::frontiers_payload const & response, async_tag const & tag) const +{ + auto const & frontiers = response.frontiers; + + if (frontiers.empty ()) + { + return verify_result::nothing_new; + } + + // Ensure frontiers accounts are in ascending order + nano::account previous{ 0 }; + for (auto const & [account, _] : frontiers) + { + if (account.number () <= previous.number ()) + { + return verify_result::invalid; + } + previous = account; + } + + // Ensure the frontiers are larger or equal to the requested frontier + if (frontiers.front ().first.number () < tag.start.as_account ().number ()) + { + return verify_result::invalid; + } + + return verify_result::ok; +} + auto nano::bootstrap_ascending::service::info () const -> nano::bootstrap_ascending::account_sets::info_t { nano::lock_guard lock{ mutex }; @@ -840,6 +1059,8 @@ std::unique_ptr nano::bootstrap_ascending::servi composite->add_component (std::make_unique (container_info{ "throttle_successes", throttle.successes (), 0 })); composite->add_component (accounts.collect_container_info ("accounts")); composite->add_component (database_scan.collect_container_info ("database_scan")); + composite->add_component (frontiers.collect_container_info ("frontiers")); + composite->add_component (workers.collect_container_info ("workers")); return composite; } diff --git a/nano/node/bootstrap_ascending/service.hpp b/nano/node/bootstrap_ascending/service.hpp index 4f3f3668d..652c7b862 100644 --- a/nano/node/bootstrap_ascending/service.hpp +++ b/nano/node/bootstrap_ascending/service.hpp @@ -5,13 +5,15 @@ #include #include #include +#include +#include #include #include #include #include +#include #include #include -#include #include #include @@ -63,6 +65,7 @@ namespace bootstrap_ascending blocks_by_hash, blocks_by_account, account_info_by_hash, + frontiers, }; enum class query_source @@ -71,6 +74,7 @@ namespace bootstrap_ascending priority, database, blocking, + frontiers, }; struct async_tag @@ -101,8 +105,11 @@ namespace bootstrap_ascending void run_one_database (bool should_throttle); void run_dependencies (); void run_one_blocking (); + void run_one_frontier (); + void run_frontiers (); void run_timeouts (); void cleanup_and_sync (); + void process_frontiers (std::deque> const & frontiers); /* Waits for a condition to be satisfied with incremental backoff */ void wait (std::function const & predicate) const; @@ -122,10 +129,13 @@ namespace bootstrap_ascending /* Waits for next available blocking block */ nano::block_hash next_blocking (); nano::block_hash wait_blocking (); + /* Waits for next available frontier scan range */ + nano::account wait_frontier (); bool request (nano::account, size_t count, std::shared_ptr const &, query_source); bool request_info (nano::block_hash, std::shared_ptr const &, query_source); - void send (std::shared_ptr const &, async_tag tag); + bool request_frontiers (nano::account, std::shared_ptr const &, query_source); + bool send (std::shared_ptr const &, async_tag tag); void process (nano::asc_pull_ack::blocks_payload const & response, async_tag const & tag); void process (nano::asc_pull_ack::account_info_payload const & response, async_tag const & tag); @@ -146,6 +156,7 @@ namespace bootstrap_ascending * - ok: otherwise, if all checks pass */ verify_result verify (nano::asc_pull_ack::blocks_payload const & response, async_tag const & tag) const; + verify_result verify (nano::asc_pull_ack::frontiers_payload const & response, async_tag const & tag) const; size_t count_tags (nano::account const & account, query_source source) const; size_t count_tags (nano::block_hash const & hash, query_source source) const; @@ -158,6 +169,7 @@ namespace bootstrap_ascending nano::bootstrap_ascending::database_scan database_scan; nano::bootstrap_ascending::throttle throttle; nano::bootstrap_ascending::peer_scoring scoring; + nano::bootstrap_ascending::frontier_scan frontiers; // clang-format off class tag_sequenced {}; @@ -181,6 +193,7 @@ namespace bootstrap_ascending // Requests for accounts from database have much lower hitrate and could introduce strain on the network // A separate (lower) limiter ensures that we always reserve resources for querying accounts from priority queue nano::rate_limiter database_limiter; + nano::rate_limiter frontiers_limiter; nano::interval sync_dependencies_interval; @@ -190,7 +203,10 @@ namespace bootstrap_ascending std::thread priorities_thread; std::thread database_thread; std::thread dependencies_thread; + std::thread frontiers_thread; std::thread timeout_thread; + + nano::thread_pool workers; }; nano::stat::detail to_stat_detail (service::query_type); diff --git a/nano/node/messages.hpp b/nano/node/messages.hpp index 00f1070b0..cba54744d 100644 --- a/nano/node/messages.hpp +++ b/nano/node/messages.hpp @@ -724,7 +724,7 @@ public: // Payload definitions static frontier deserialize_frontier (nano::stream &); public: // Payload - std::vector frontiers; + std::deque frontiers; public: // Logging void operator() (nano::object_stream &) const; From 84fef77255fd7101067a86bec8a1ba26e00daabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Wed, 25 Sep 2024 21:54:56 +0200 Subject: [PATCH 04/12] Improved database iteration --- .../bootstrap_ascending/database_scan.hpp | 2 + nano/node/bootstrap_ascending/iterators.hpp | 187 ++++++++++++++++++ nano/node/bootstrap_ascending/service.cpp | 55 ++++-- nano/store/account.hpp | 13 +- nano/store/iterator.hpp | 5 +- 5 files changed, 239 insertions(+), 23 deletions(-) create mode 100644 nano/node/bootstrap_ascending/iterators.hpp diff --git a/nano/node/bootstrap_ascending/database_scan.hpp b/nano/node/bootstrap_ascending/database_scan.hpp index 3c61be9a6..77bb011c1 100644 --- a/nano/node/bootstrap_ascending/database_scan.hpp +++ b/nano/node/bootstrap_ascending/database_scan.hpp @@ -8,6 +8,7 @@ namespace nano::bootstrap_ascending { +// TODO: Rename to *_scanner struct account_database_iterator { explicit account_database_iterator (nano::ledger &); @@ -20,6 +21,7 @@ struct account_database_iterator size_t completed{ 0 }; }; +// TODO: Rename to *_scanner struct pending_database_iterator { explicit pending_database_iterator (nano::ledger &); diff --git a/nano/node/bootstrap_ascending/iterators.hpp b/nano/node/bootstrap_ascending/iterators.hpp new file mode 100644 index 000000000..48efd1fa8 --- /dev/null +++ b/nano/node/bootstrap_ascending/iterators.hpp @@ -0,0 +1,187 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace nano::bootstrap_ascending +{ +struct account_database_crawler +{ + using value_type = std::pair; + + static constexpr size_t sequential_attempts = 10; + + account_database_crawler (nano::store::component & store, nano::store::transaction const & transaction, nano::account const & start) : + store{ store }, + transaction{ transaction }, + it{ store.account.end () }, + end{ store.account.end () } + { + seek (start); + } + + void seek (nano::account const & account) + { + it = store.account.begin (transaction, account); + update_current (); + } + + void advance () + { + if (it == end) + { + debug_assert (!current); + return; + } + + ++it; + update_current (); + } + + void advance_to (nano::account const & account) + { + if (it == end) + { + debug_assert (!current); + return; + } + + // First try advancing sequentially + for (size_t count = 0; count < sequential_attempts && it != end; ++count, ++it) + { + // Break if we've reached or overshoot the target account + if (it->first.number () >= account.number ()) + { + update_current (); + return; + } + } + + // If that fails, perform a fresh lookup + seek (account); + } + + std::optional current{}; + +private: + void update_current () + { + if (it != end) + { + current = *it; + } + else + { + current = std::nullopt; + } + } + + nano::store::component & store; + nano::store::transaction const & transaction; + + nano::store::account::iterator it; + nano::store::account::iterator const end; +}; + +struct pending_database_crawler +{ + using value_type = std::pair; + + static constexpr size_t sequential_attempts = 10; + + pending_database_crawler (nano::store::component & store, nano::store::transaction const & transaction, nano::account const & start) : + store{ store }, + transaction{ transaction }, + it{ store.pending.end () }, + end{ store.pending.end () } + { + seek (start); + } + + void seek (nano::account const & account) + { + it = store.pending.begin (transaction, { account, 0 }); + update_current (); + } + + // Advance to the next account + void advance () + { + if (it == end) + { + debug_assert (!current); + return; + } + + auto const starting_account = it->first.account; + + // First try advancing sequentially + for (size_t count = 0; count < sequential_attempts && it != end; ++count, ++it) + { + // Break if we've reached the next account + if (it->first.account != starting_account) + { + update_current (); + return; + } + } + + if (it != end) + { + // If that fails, perform a fresh lookup + seek (starting_account.number () + 1); + } + + update_current (); + } + + void advance_to (nano::account const & account) + { + if (it == end) + { + debug_assert (!current); + return; + } + + // First try advancing sequentially + for (size_t count = 0; count < sequential_attempts && it != end; ++count, ++it) + { + // Break if we've reached or overshoot the target account + if (it->first.account.number () >= account.number ()) + { + update_current (); + return; + } + } + + // If that fails, perform a fresh lookup + seek (account); + } + + std::optional current{}; + +private: + void update_current () + { + if (it != end) + { + current = *it; + } + else + { + current = std::nullopt; + } + } + + nano::store::component & store; + nano::store::transaction const & transaction; + + nano::store::pending::iterator it; + nano::store::pending::iterator const end; +}; +} \ No newline at end of file diff --git a/nano/node/bootstrap_ascending/service.cpp b/nano/node/bootstrap_ascending/service.cpp index 1de0f696c..0739040ca 100644 --- a/nano/node/bootstrap_ascending/service.cpp +++ b/nano/node/bootstrap_ascending/service.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -636,6 +637,14 @@ void nano::bootstrap_ascending::service::run_frontiers () void nano::bootstrap_ascending::service::process_frontiers (std::deque> const & frontiers) { + release_assert (!frontiers.empty ()); + + // Accounts must be passed in ascending order + debug_assert (std::adjacent_find (frontiers.begin (), frontiers.end (), [] (auto const & lhs, auto const & rhs) { + return lhs.first.number () >= rhs.first.number (); + }) + == frontiers.end ()); + stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::process_frontiers); size_t outdated = 0; @@ -646,30 +655,42 @@ void nano::bootstrap_ascending::service::process_frontiers (std::dequefirst == account) { - return false; - } - if (auto info = ledger.any.account_get (transaction, account)) - { - if (info->head != frontier) + // Check for frontier mismatch + if (account_crawler.current->second.head != frontier) { - outdated++; - return true; // Frontier is outdated + // Check if frontier block exists in our ledger + if (!block_exists (frontier)) + { + outdated++; + return true; // Frontier is outdated + } } - return false; + return false; // Account exists and frontier is up-to-date } - if (auto receivable = ledger.any.receivable_lower_bound (transaction, account, { 0 })) + + // Check if account has pending blocks in our ledger + if (pending_crawler.current && pending_crawler.current->first.account == account) { - if (receivable->first.account == account) - { - pending++; - return true; // Account doesn't exist but has pending blocks in the ledger - } - return false; + pending++; + return true; // Account doesn't exist but has pending blocks in the ledger } - return false; + + return false; // Account doesn't exist in the ledger and has no pending blocks, can't be prioritized right now }; for (auto const & [account, frontier] : frontiers) diff --git a/nano/store/account.hpp b/nano/store/account.hpp index 749cd9c84..41ee47417 100644 --- a/nano/store/account.hpp +++ b/nano/store/account.hpp @@ -19,6 +19,9 @@ namespace nano::store */ class account { +public: + using iterator = store::iterator; + public: virtual void put (store::write_transaction const &, nano::account const &, nano::account_info const &) = 0; virtual bool get (store::transaction const &, nano::account const &, nano::account_info &) = 0; @@ -26,10 +29,10 @@ public: virtual void del (store::write_transaction const &, nano::account const &) = 0; virtual bool exists (store::transaction const &, nano::account const &) = 0; virtual size_t count (store::transaction const &) = 0; - virtual iterator begin (store::transaction const &, nano::account const &) const = 0; - virtual iterator begin (store::transaction const &) const = 0; - virtual iterator rbegin (store::transaction const &) const = 0; - virtual iterator end () const = 0; - virtual void for_each_par (std::function, iterator)> const &) const = 0; + virtual store::iterator begin (store::transaction const &, nano::account const &) const = 0; + virtual store::iterator begin (store::transaction const &) const = 0; + virtual store::iterator rbegin (store::transaction const &) const = 0; + virtual store::iterator end () const = 0; + virtual void for_each_par (std::function, store::iterator)> const &) const = 0; }; } // namespace nano::store diff --git a/nano/store/iterator.hpp b/nano/store/iterator.hpp index 7fe957d72..47d57d579 100644 --- a/nano/store/iterator.hpp +++ b/nano/store/iterator.hpp @@ -12,6 +12,9 @@ namespace nano::store template class iterator final { +public: + using value_type = std::pair; + public: iterator (std::nullptr_t) { @@ -63,7 +66,7 @@ public: } private: - std::pair current; + value_type current; std::unique_ptr> impl; }; } // namespace nano::store From 0a4252d47ae7cb37d5501eedc618976e0ef20e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Thu, 26 Sep 2024 16:37:11 +0200 Subject: [PATCH 05/12] Renaming --- .../bootstrap_ascending/database_scan.cpp | 38 +++++-------------- .../bootstrap_ascending/database_scan.hpp | 18 +++------ 2 files changed, 15 insertions(+), 41 deletions(-) diff --git a/nano/node/bootstrap_ascending/database_scan.cpp b/nano/node/bootstrap_ascending/database_scan.cpp index 7a7b287fc..511653f98 100644 --- a/nano/node/bootstrap_ascending/database_scan.cpp +++ b/nano/node/bootstrap_ascending/database_scan.cpp @@ -13,8 +13,8 @@ nano::bootstrap_ascending::database_scan::database_scan (nano::ledger & ledger_a) : ledger{ ledger_a }, - accounts_iterator{ ledger }, - pending_iterator{ ledger } + account_scanner{ ledger }, + pending_scanner{ ledger } { } @@ -43,8 +43,8 @@ void nano::bootstrap_ascending::database_scan::fill () { auto transaction = ledger.store.tx_begin_read (); - auto set1 = accounts_iterator.next_batch (transaction, batch_size); - auto set2 = pending_iterator.next_batch (transaction, batch_size); + auto set1 = account_scanner.next_batch (transaction, batch_size); + auto set2 = pending_scanner.next_batch (transaction, batch_size); queue.insert (queue.end (), set1.begin (), set1.end ()); queue.insert (queue.end (), set2.begin (), set2.end ()); @@ -52,14 +52,14 @@ void nano::bootstrap_ascending::database_scan::fill () bool nano::bootstrap_ascending::database_scan::warmed_up () const { - return accounts_iterator.warmed_up () && pending_iterator.warmed_up (); + return account_scanner.completed > 0 && pending_scanner.completed > 0; } std::unique_ptr nano::bootstrap_ascending::database_scan::collect_container_info (std::string const & name) const { auto composite = std::make_unique (name); - composite->add_component (std::make_unique (container_info{ "accounts_iterator", accounts_iterator.completed, 0 })); - composite->add_component (std::make_unique (container_info{ "pending_iterator", pending_iterator.completed, 0 })); + composite->add_component (std::make_unique (container_info{ "account_scan", account_scanner.completed, 0 })); + composite->add_component (std::make_unique (container_info{ "pending_scan", pending_scanner.completed, 0 })); return composite; } @@ -67,12 +67,7 @@ std::unique_ptr nano::bootstrap_ascending::datab * account_database_iterator */ -nano::bootstrap_ascending::account_database_iterator::account_database_iterator (nano::ledger & ledger_a) : - ledger{ ledger_a } -{ -} - -std::deque nano::bootstrap_ascending::account_database_iterator::next_batch (nano::store::transaction & transaction, size_t batch_size) +std::deque nano::bootstrap_ascending::account_database_scanner::next_batch (nano::store::transaction & transaction, size_t batch_size) { std::deque result; @@ -96,21 +91,11 @@ std::deque nano::bootstrap_ascending::account_database_iterator:: return result; } -bool nano::bootstrap_ascending::account_database_iterator::warmed_up () const -{ - return completed > 0; -} - /* * pending_database_iterator */ -nano::bootstrap_ascending::pending_database_iterator::pending_database_iterator (nano::ledger & ledger_a) : - ledger{ ledger_a } -{ -} - -std::deque nano::bootstrap_ascending::pending_database_iterator::next_batch (nano::store::transaction & transaction, size_t batch_size) +std::deque nano::bootstrap_ascending::pending_database_scanner::next_batch (nano::store::transaction & transaction, size_t batch_size) { std::deque result; @@ -160,8 +145,3 @@ std::deque nano::bootstrap_ascending::pending_database_iterator:: return result; } - -bool nano::bootstrap_ascending::pending_database_iterator::warmed_up () const -{ - return completed > 0; -} \ No newline at end of file diff --git a/nano/node/bootstrap_ascending/database_scan.hpp b/nano/node/bootstrap_ascending/database_scan.hpp index 77bb011c1..0b7baa218 100644 --- a/nano/node/bootstrap_ascending/database_scan.hpp +++ b/nano/node/bootstrap_ascending/database_scan.hpp @@ -8,28 +8,22 @@ namespace nano::bootstrap_ascending { -// TODO: Rename to *_scanner -struct account_database_iterator +struct account_database_scanner { - explicit account_database_iterator (nano::ledger &); + nano::ledger & ledger; std::deque next_batch (nano::store::transaction &, size_t batch_size); - bool warmed_up () const; - nano::ledger & ledger; nano::account next{ 0 }; size_t completed{ 0 }; }; -// TODO: Rename to *_scanner -struct pending_database_iterator +struct pending_database_scanner { - explicit pending_database_iterator (nano::ledger &); + nano::ledger & ledger; std::deque next_batch (nano::store::transaction &, size_t batch_size); - bool warmed_up () const; - nano::ledger & ledger; nano::pending_key next{ 0, 0 }; size_t completed{ 0 }; }; @@ -53,8 +47,8 @@ private: void fill (); private: - account_database_iterator accounts_iterator; - pending_database_iterator pending_iterator; + account_database_scanner account_scanner; + pending_database_scanner pending_scanner; std::deque queue; From fb12ea9e4774ee59efe37e4ae1df9f40b2980961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Thu, 26 Sep 2024 16:50:14 +0200 Subject: [PATCH 06/12] Rewrite previous database scan in terms of database crawlers --- .../bootstrap_ascending/database_scan.cpp | 60 ++++++------------- .../bootstrap_ascending/database_scan.hpp | 2 +- 2 files changed, 18 insertions(+), 44 deletions(-) diff --git a/nano/node/bootstrap_ascending/database_scan.cpp b/nano/node/bootstrap_ascending/database_scan.cpp index 511653f98..327870cf7 100644 --- a/nano/node/bootstrap_ascending/database_scan.cpp +++ b/nano/node/bootstrap_ascending/database_scan.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -64,24 +65,24 @@ std::unique_ptr nano::bootstrap_ascending::datab } /* - * account_database_iterator + * account_database_scanner */ std::deque nano::bootstrap_ascending::account_database_scanner::next_batch (nano::store::transaction & transaction, size_t batch_size) { std::deque result; - auto it = ledger.store.account.begin (transaction, next); - auto const end = ledger.store.account.end (); + account_database_crawler crawler{ ledger.store, transaction, next }; - for (size_t count = 0; it != end && count < batch_size; ++it, ++count) + for (size_t count = 0; crawler.current && count < batch_size; crawler.advance (), ++count) { - auto const & account = it->first; + auto const & [account, info] = crawler.current.value (); result.push_back (account); - next = account.number () + 1; + next = account.number () + 1; // TODO: Handle account number overflow } - if (it == end) + // Empty current value indicates the end of the table + if (!crawler.current) { // Reset for the next ledger iteration next = { 0 }; @@ -92,54 +93,27 @@ std::deque nano::bootstrap_ascending::account_database_scanner::n } /* - * pending_database_iterator + * pending_database_scanner */ std::deque nano::bootstrap_ascending::pending_database_scanner::next_batch (nano::store::transaction & transaction, size_t batch_size) { std::deque result; - auto it = ledger.store.pending.begin (transaction, next); - auto const end = ledger.store.pending.end (); + pending_database_crawler crawler{ ledger.store, transaction, next }; - // TODO: This pending iteration heuristic should be encapsulated in a pending_iterator class and reused across other components - // The heuristic is to advance the iterator sequentially until we reach a new account or perform a fresh lookup if the account has too many pending blocks - // This is to avoid the overhead of performing a fresh lookup for every pending account as majority of accounts have only a few pending blocks - auto advance_iterator = [&] () { - auto const starting_account = it->first.account; - - // For RocksDB, sequential access is ~10x faster than performing a fresh lookup (tested on my machine) - const size_t sequential_attempts = 10; - - // First try advancing sequentially - for (size_t count = 0; count < sequential_attempts && it != end; ++count, ++it) - { - if (it->first.account != starting_account) - { - break; - } - } - - // If we didn't advance to the next account, perform a fresh lookup - if (it != end && it->first.account != starting_account) - { - it = ledger.store.pending.begin (transaction, { starting_account.number () + 1, 0 }); - } - - debug_assert (it == end || it->first.account != starting_account); - }; - - for (size_t count = 0; it != end && count < batch_size; advance_iterator (), ++count) + for (size_t count = 0; crawler.current && count < batch_size; crawler.advance (), ++count) { - auto const & account = it->first.account; - result.push_back (account); - next = { account.number () + 1, 0 }; + auto const & [key, info] = crawler.current.value (); + result.push_back (key.account); + next = key.account.number () + 1; // TODO: Handle account number overflow } - if (it == end) + // Empty current value indicates the end of the table + if (!crawler.current) { // Reset for the next ledger iteration - next = { 0, 0 }; + next = { 0 }; ++completed; } diff --git a/nano/node/bootstrap_ascending/database_scan.hpp b/nano/node/bootstrap_ascending/database_scan.hpp index 0b7baa218..f6a425fdc 100644 --- a/nano/node/bootstrap_ascending/database_scan.hpp +++ b/nano/node/bootstrap_ascending/database_scan.hpp @@ -24,7 +24,7 @@ struct pending_database_scanner std::deque next_batch (nano::store::transaction &, size_t batch_size); - nano::pending_key next{ 0, 0 }; + nano::account next{ 0 }; size_t completed{ 0 }; }; From 4b65877fe5b3f96986990b2654af48144106c679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:07:58 +0200 Subject: [PATCH 07/12] Comments and cleanup --- .../bootstrap_ascending/frontier_scan.cpp | 1 + .../bootstrap_ascending/frontier_scan.hpp | 17 +- nano/node/bootstrap_ascending/service.cpp | 160 +++++++++--------- nano/node/bootstrap_ascending/service.hpp | 3 +- 4 files changed, 97 insertions(+), 84 deletions(-) diff --git a/nano/node/bootstrap_ascending/frontier_scan.cpp b/nano/node/bootstrap_ascending/frontier_scan.cpp index 28fab99de..e955fd3fc 100644 --- a/nano/node/bootstrap_ascending/frontier_scan.cpp +++ b/nano/node/bootstrap_ascending/frontier_scan.cpp @@ -136,6 +136,7 @@ std::unique_ptr nano::bootstrap_ascending::front boost::multiprecision::cpp_dec_float_50 next{ head.next.number ().str () }; boost::multiprecision::cpp_dec_float_50 end{ head.end.number ().str () }; + // Progress in the range [0, 1000000] since we can only represent `size_t` integers in the container_info data boost::multiprecision::cpp_dec_float_50 progress = (next - start) * boost::multiprecision::cpp_dec_float_50 (1000000) / (end - start); composite->add_component (std::make_unique (container_info{ std::to_string (n), progress.convert_to (), 6 })); diff --git a/nano/node/bootstrap_ascending/frontier_scan.hpp b/nano/node/bootstrap_ascending/frontier_scan.hpp index 382c59b47..bee3db24f 100644 --- a/nano/node/bootstrap_ascending/frontier_scan.hpp +++ b/nano/node/bootstrap_ascending/frontier_scan.hpp @@ -19,6 +19,10 @@ namespace mi = boost::multi_index; namespace nano::bootstrap_ascending { +/* + * Frontier scan divides the account space into ranges and scans each range for outdated frontiers in parallel. + * This class is used to track the progress of each range. + */ class frontier_scan { public: @@ -34,6 +38,7 @@ private: // Dependencies nano::stats & stats; private: + // Represents a range of accounts to scan, once the full range is scanned (goes past `end`) the head wraps around (to the `start`) struct frontier_head { frontier_head (nano::account start_a, nano::account end_a) : @@ -44,9 +49,10 @@ private: } // The range of accounts to scan is [start, end) - nano::account start; - nano::account end; + nano::account const start; + nano::account const end; + // We scan the range by querying frontiers starting at 'next' and gathering candidates nano::account next; std::set candidates; @@ -54,6 +60,11 @@ private: unsigned completed{ 0 }; std::chrono::steady_clock::time_point timestamp{}; size_t processed{ 0 }; // Total number of accounts processed + + nano::account index () const + { + return start; + } }; // clang-format off @@ -65,7 +76,7 @@ private: mi::indexed_by< mi::random_access>, mi::ordered_unique, - mi::member>, + mi::const_mem_fun>, mi::ordered_non_unique, mi::member> >>; diff --git a/nano/node/bootstrap_ascending/service.cpp b/nano/node/bootstrap_ascending/service.cpp index 0739040ca..67ba813c9 100644 --- a/nano/node/bootstrap_ascending/service.cpp +++ b/nano/node/bootstrap_ascending/service.cpp @@ -635,86 +635,6 @@ void nano::bootstrap_ascending::service::run_frontiers () } } -void nano::bootstrap_ascending::service::process_frontiers (std::deque> const & frontiers) -{ - release_assert (!frontiers.empty ()); - - // Accounts must be passed in ascending order - debug_assert (std::adjacent_find (frontiers.begin (), frontiers.end (), [] (auto const & lhs, auto const & rhs) { - return lhs.first.number () >= rhs.first.number (); - }) - == frontiers.end ()); - - stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::process_frontiers); - - size_t outdated = 0; - size_t pending = 0; - - // Accounts with outdated frontiers to sync - std::deque result; - { - auto transaction = ledger.tx_begin_read (); - - auto const start = frontiers.front ().first; - account_database_crawler account_crawler{ ledger.store, transaction, start }; - pending_database_crawler pending_crawler{ ledger.store, transaction, start }; - - auto block_exists = [&] (nano::block_hash const & hash) { - return ledger.any.block_exists_or_pruned (transaction, hash); - }; - - auto should_prioritize = [&] (nano::account const & account, nano::block_hash const & frontier) { - account_crawler.advance_to (account); - pending_crawler.advance_to (account); - - // Check if account exists in our ledger - if (account_crawler.current && account_crawler.current->first == account) - { - // Check for frontier mismatch - if (account_crawler.current->second.head != frontier) - { - // Check if frontier block exists in our ledger - if (!block_exists (frontier)) - { - outdated++; - return true; // Frontier is outdated - } - } - return false; // Account exists and frontier is up-to-date - } - - // Check if account has pending blocks in our ledger - if (pending_crawler.current && pending_crawler.current->first.account == account) - { - pending++; - return true; // Account doesn't exist but has pending blocks in the ledger - } - - return false; // Account doesn't exist in the ledger and has no pending blocks, can't be prioritized right now - }; - - for (auto const & [account, frontier] : frontiers) - { - if (should_prioritize (account, frontier)) - { - result.push_back (account); - } - } - } - - stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::frontiers_processed, frontiers.size ()); - stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::frontiers_prioritized, result.size ()); - stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::frontiers_outdated, outdated); - stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::frontiers_pending, pending); - - nano::lock_guard guard{ mutex }; - - for (auto const & account : result) - { - accounts.priority_set (account); - } -} - void nano::bootstrap_ascending::service::cleanup_and_sync () { debug_assert (!mutex.try_lock ()); @@ -968,6 +888,86 @@ void nano::bootstrap_ascending::service::process (const nano::empty_payload & re debug_assert (false, "empty payload"); // Should not happen } +void nano::bootstrap_ascending::service::process_frontiers (std::deque> const & frontiers) +{ + release_assert (!frontiers.empty ()); + + // Accounts must be passed in ascending order + debug_assert (std::adjacent_find (frontiers.begin (), frontiers.end (), [] (auto const & lhs, auto const & rhs) { + return lhs.first.number () >= rhs.first.number (); + }) + == frontiers.end ()); + + stats.inc (nano::stat::type::bootstrap_ascending, nano::stat::detail::process_frontiers); + + size_t outdated = 0; + size_t pending = 0; + + // Accounts with outdated frontiers to sync + std::deque result; + { + auto transaction = ledger.tx_begin_read (); + + auto const start = frontiers.front ().first; + account_database_crawler account_crawler{ ledger.store, transaction, start }; + pending_database_crawler pending_crawler{ ledger.store, transaction, start }; + + auto block_exists = [&] (nano::block_hash const & hash) { + return ledger.any.block_exists_or_pruned (transaction, hash); + }; + + auto should_prioritize = [&] (nano::account const & account, nano::block_hash const & frontier) { + account_crawler.advance_to (account); + pending_crawler.advance_to (account); + + // Check if account exists in our ledger + if (account_crawler.current && account_crawler.current->first == account) + { + // Check for frontier mismatch + if (account_crawler.current->second.head != frontier) + { + // Check if frontier block exists in our ledger + if (!block_exists (frontier)) + { + outdated++; + return true; // Frontier is outdated + } + } + return false; // Account exists and frontier is up-to-date + } + + // Check if account has pending blocks in our ledger + if (pending_crawler.current && pending_crawler.current->first.account == account) + { + pending++; + return true; // Account doesn't exist but has pending blocks in the ledger + } + + return false; // Account doesn't exist in the ledger and has no pending blocks, can't be prioritized right now + }; + + for (auto const & [account, frontier] : frontiers) + { + if (should_prioritize (account, frontier)) + { + result.push_back (account); + } + } + } + + stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::frontiers_processed, frontiers.size ()); + stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::frontiers_prioritized, result.size ()); + stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::frontiers_outdated, outdated); + stats.add (nano::stat::type::bootstrap_ascending, nano::stat::detail::frontiers_pending, pending); + + nano::lock_guard guard{ mutex }; + + for (auto const & account : result) + { + accounts.priority_set (account); + } +} + nano::bootstrap_ascending::service::verify_result nano::bootstrap_ascending::service::verify (const nano::asc_pull_ack::blocks_payload & response, const nano::bootstrap_ascending::service::async_tag & tag) const { auto const & blocks = response.blocks; diff --git a/nano/node/bootstrap_ascending/service.hpp b/nano/node/bootstrap_ascending/service.hpp index 652c7b862..ba49ea6c4 100644 --- a/nano/node/bootstrap_ascending/service.hpp +++ b/nano/node/bootstrap_ascending/service.hpp @@ -109,7 +109,6 @@ namespace bootstrap_ascending void run_frontiers (); void run_timeouts (); void cleanup_and_sync (); - void process_frontiers (std::deque> const & frontiers); /* Waits for a condition to be satisfied with incremental backoff */ void wait (std::function const & predicate) const; @@ -142,6 +141,8 @@ namespace bootstrap_ascending void process (nano::asc_pull_ack::frontiers_payload const & response, async_tag const & tag); void process (nano::empty_payload const & response, async_tag const & tag); + void process_frontiers (std::deque> const & frontiers); + enum class verify_result { ok, From 8bec4ee12abc210061ca90b390d19a4df0009bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Thu, 26 Sep 2024 18:20:20 +0200 Subject: [PATCH 08/12] Allow disabling priority accounts scan --- nano/node/bootstrap/bootstrap_config.hpp | 1 + nano/node/bootstrap_ascending/service.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/nano/node/bootstrap/bootstrap_config.hpp b/nano/node/bootstrap/bootstrap_config.hpp index cd2c9b8d7..f94b1d151 100644 --- a/nano/node/bootstrap/bootstrap_config.hpp +++ b/nano/node/bootstrap/bootstrap_config.hpp @@ -44,6 +44,7 @@ public: public: bool enable{ true }; + bool enable_scan{ true }; bool enable_database_scan{ false }; bool enable_dependency_walker{ true }; bool enable_frontier_scan{ true }; diff --git a/nano/node/bootstrap_ascending/service.cpp b/nano/node/bootstrap_ascending/service.cpp index 67ba813c9..ff35244d3 100644 --- a/nano/node/bootstrap_ascending/service.cpp +++ b/nano/node/bootstrap_ascending/service.cpp @@ -79,10 +79,13 @@ void nano::bootstrap_ascending::service::start () return; } - priorities_thread = std::thread ([this] () { - nano::thread_role::set (nano::thread_role::name::ascending_bootstrap); - run_priorities (); - }); + if (config.enable_scan) + { + priorities_thread = std::thread ([this] () { + nano::thread_role::set (nano::thread_role::name::ascending_bootstrap); + run_priorities (); + }); + } if (config.enable_database_scan) { From ed4c175f949b854f5e8858fb71f98ca58694ae7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Fri, 27 Sep 2024 14:13:38 +0200 Subject: [PATCH 09/12] Tests --- nano/core_test/bootstrap_ascending.cpp | 289 ++++++++++++++++++++++ nano/node/bootstrap_ascending/service.cpp | 12 + nano/node/bootstrap_ascending/service.hpp | 2 + 3 files changed, 303 insertions(+) diff --git a/nano/core_test/bootstrap_ascending.cpp b/nano/core_test/bootstrap_ascending.cpp index 015abe203..025510646 100644 --- a/nano/core_test/bootstrap_ascending.cpp +++ b/nano/core_test/bootstrap_ascending.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -266,3 +267,291 @@ TEST (bootstrap_ascending, trace_base) // std::cerr << "node1: " << node1.network.endpoint () << std::endl; ASSERT_TIMELY (10s, node1.block (receive1->hash ()) != nullptr); } + +/* + * Tests that bootstrap will prioritize existing accounts with outdated frontiers + */ +TEST (bootstrap_ascending, frontier_scan) +{ + nano::test::system system; + + nano::node_flags flags; + flags.disable_legacy_bootstrap = true; + nano::node_config config; + // Disable other bootstrap strategies + config.bootstrap_ascending.enable_scan = false; + config.bootstrap_ascending.enable_dependency_walker = false; + // Disable election activation + config.backlog_population.enable = false; + config.priority_scheduler.enable = false; + config.optimistic_scheduler.enable = false; + config.hinted_scheduler.enable = false; + + // Prepare blocks for frontier scan (genesis 10 sends -> 10 opens -> 10 updates) + std::vector> sends; + std::vector> opens; + std::vector> updates; + { + auto source = nano::dev::genesis_key; + auto latest = nano::dev::genesis->hash (); + auto balance = nano::dev::genesis->balance ().number (); + + size_t const count = 10; + + for (int n = 0; n < count; ++n) + { + nano::keypair key; + nano::block_builder builder; + + balance -= 1; + auto send = builder + .state () + .account (source.pub) + .previous (latest) + .representative (source.pub) + .balance (balance) + .link (key.pub) + .sign (source.prv, source.pub) + .work (*system.work.generate (latest)) + .build (); + + latest = send->hash (); + + auto open = builder + .state () + .account (key.pub) + .previous (0) + .representative (key.pub) + .balance (1) + .link (send->hash ()) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (); + + auto update = builder + .state () + .account (key.pub) + .previous (open->hash ()) + .representative (0) + .balance (1) + .link (0) + .sign (key.prv, key.pub) + .work (*system.work.generate (open->hash ())) + .build (); + + sends.push_back (send); + opens.push_back (open); + updates.push_back (update); + } + } + + // Initialize nodes with blocks without the `updates` frontiers + std::vector> blocks; + blocks.insert (blocks.end (), sends.begin (), sends.end ()); + blocks.insert (blocks.end (), opens.begin (), opens.end ()); + system.set_initialization_blocks ({ blocks.begin (), blocks.end () }); + + auto & node0 = *system.add_node (config, flags); + ASSERT_TRUE (nano::test::process (node0, updates)); + + // No blocks should be broadcast to the other node + auto & node1 = *system.add_node (config, flags); + ASSERT_ALWAYS_EQ (100ms, node1.ledger.block_count (), blocks.size () + 1); + + // Frontier scan should detect all the accounts with missing blocks + ASSERT_TIMELY (10s, std::all_of (updates.begin (), updates.end (), [&node1] (auto const & block) { + return node1.ascendboot.prioritized (block->account ()); + })); +} + +/* + * Tests that bootstrap will prioritize not yet existing accounts with pending blocks + */ +TEST (bootstrap_ascending, frontier_scan_pending) +{ + nano::test::system system; + + nano::node_flags flags; + flags.disable_legacy_bootstrap = true; + nano::node_config config; + // Disable other bootstrap strategies + config.bootstrap_ascending.enable_scan = false; + config.bootstrap_ascending.enable_dependency_walker = false; + // Disable election activation + config.backlog_population.enable = false; + config.priority_scheduler.enable = false; + config.optimistic_scheduler.enable = false; + config.hinted_scheduler.enable = false; + + // Prepare blocks for frontier scan (genesis 10 sends -> 10 opens) + std::vector> sends; + std::vector> opens; + { + auto source = nano::dev::genesis_key; + auto latest = nano::dev::genesis->hash (); + auto balance = nano::dev::genesis->balance ().number (); + + size_t const count = 10; + + for (int n = 0; n < count; ++n) + { + nano::keypair key; + nano::block_builder builder; + + balance -= 1; + auto send = builder + .state () + .account (source.pub) + .previous (latest) + .representative (source.pub) + .balance (balance) + .link (key.pub) + .sign (source.prv, source.pub) + .work (*system.work.generate (latest)) + .build (); + + latest = send->hash (); + + auto open = builder + .state () + .account (key.pub) + .previous (0) + .representative (key.pub) + .balance (1) + .link (send->hash ()) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (); + + sends.push_back (send); + opens.push_back (open); + } + } + + // Initialize nodes with blocks without the `updates` frontiers + std::vector> blocks; + blocks.insert (blocks.end (), sends.begin (), sends.end ()); + system.set_initialization_blocks ({ blocks.begin (), blocks.end () }); + + auto & node0 = *system.add_node (config, flags); + ASSERT_TRUE (nano::test::process (node0, opens)); + + // No blocks should be broadcast to the other node + auto & node1 = *system.add_node (config, flags); + ASSERT_ALWAYS_EQ (100ms, node1.ledger.block_count (), blocks.size () + 1); + + // Frontier scan should detect all the accounts with missing blocks + ASSERT_TIMELY (10s, std::all_of (opens.begin (), opens.end (), [&node1] (auto const & block) { + return node1.ascendboot.prioritized (block->account ()); + })); +} + +/* + * Bootstrap should not attempt to prioritize accounts that can't be immediately connected to the ledger (no pending blocks, no existing frontier) + */ +TEST (bootstrap_ascending, frontier_scan_cannot_prioritize) +{ + nano::test::system system; + + nano::node_flags flags; + flags.disable_legacy_bootstrap = true; + nano::node_config config; + // Disable other bootstrap strategies + config.bootstrap_ascending.enable_scan = false; + config.bootstrap_ascending.enable_dependency_walker = false; + // Disable election activation + config.backlog_population.enable = false; + config.priority_scheduler.enable = false; + config.optimistic_scheduler.enable = false; + config.hinted_scheduler.enable = false; + + // Prepare blocks for frontier scan (genesis 10 sends -> 10 opens -> 10 sends -> 10 opens) + std::vector> sends; + std::vector> opens; + std::vector> sends2; + std::vector> opens2; + { + auto source = nano::dev::genesis_key; + auto latest = nano::dev::genesis->hash (); + auto balance = nano::dev::genesis->balance ().number (); + + size_t const count = 10; + + for (int n = 0; n < count; ++n) + { + nano::keypair key, key2; + nano::block_builder builder; + + balance -= 1; + auto send = builder + .state () + .account (source.pub) + .previous (latest) + .representative (source.pub) + .balance (balance) + .link (key.pub) + .sign (source.prv, source.pub) + .work (*system.work.generate (latest)) + .build (); + + latest = send->hash (); + + auto open = builder + .state () + .account (key.pub) + .previous (0) + .representative (key.pub) + .balance (1) + .link (send->hash ()) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build (); + + auto send2 = builder + .state () + .account (key.pub) + .previous (open->hash ()) + .representative (key.pub) + .balance (0) + .link (key2.pub) + .sign (key.prv, key.pub) + .work (*system.work.generate (open->hash ())) + .build (); + + auto open2 = builder + .state () + .account (key2.pub) + .previous (0) + .representative (key2.pub) + .balance (1) + .link (send2->hash ()) + .sign (key2.prv, key2.pub) + .work (*system.work.generate (key2.pub)) + .build (); + + sends.push_back (send); + opens.push_back (open); + sends2.push_back (send2); + opens2.push_back (open2); + } + } + + // Initialize nodes with blocks without the `updates` frontiers + std::vector> blocks; + blocks.insert (blocks.end (), sends.begin (), sends.end ()); + blocks.insert (blocks.end (), opens.begin (), opens.end ()); + system.set_initialization_blocks ({ blocks.begin (), blocks.end () }); + + auto & node0 = *system.add_node (config, flags); + ASSERT_TRUE (nano::test::process (node0, sends2)); + ASSERT_TRUE (nano::test::process (node0, opens2)); + + // No blocks should be broadcast to the other node + auto & node1 = *system.add_node (config, flags); + ASSERT_ALWAYS_EQ (100ms, node1.ledger.block_count (), blocks.size () + 1); + + // Frontier scan should not detect the accounts + ASSERT_ALWAYS (1s, std::none_of (opens2.begin (), opens2.end (), [&node1] (auto const & block) { + return node1.ascendboot.prioritized (block->account ()); + })); +} \ No newline at end of file diff --git a/nano/node/bootstrap_ascending/service.cpp b/nano/node/bootstrap_ascending/service.cpp index ff35244d3..52c1d1eee 100644 --- a/nano/node/bootstrap_ascending/service.cpp +++ b/nano/node/bootstrap_ascending/service.cpp @@ -217,6 +217,18 @@ std::size_t nano::bootstrap_ascending::service::score_size () const return scoring.size (); } +bool nano::bootstrap_ascending::service::prioritized (nano::account const & account) const +{ + nano::lock_guard lock{ mutex }; + return accounts.prioritized (account); +} + +bool nano::bootstrap_ascending::service::blocked (nano::account const & account) const +{ + nano::lock_guard lock{ mutex }; + return accounts.blocked (account); +} + /** Inspects a block that has been processed by the block processor - Marks an account as blocked if the result code is gap source as there is no reason request additional blocks for this account until the dependency is resolved - Marks an account as forwarded if it has been recently referenced by a block that has been inserted. diff --git a/nano/node/bootstrap_ascending/service.hpp b/nano/node/bootstrap_ascending/service.hpp index ba49ea6c4..e602a2673 100644 --- a/nano/node/bootstrap_ascending/service.hpp +++ b/nano/node/bootstrap_ascending/service.hpp @@ -47,6 +47,8 @@ namespace bootstrap_ascending std::size_t blocked_size () const; std::size_t priority_size () const; std::size_t score_size () const; + bool prioritized (nano::account const &) const; + bool blocked (nano::account const &) const; nano::bootstrap_ascending::account_sets::info_t info () const; private: // Dependencies From a66b8e8a750ee5682fb86a3937e0dc00983f5eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Mon, 14 Oct 2024 20:21:10 +0200 Subject: [PATCH 10/12] Reduce frontier request rate --- nano/node/bootstrap/bootstrap_config.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nano/node/bootstrap/bootstrap_config.hpp b/nano/node/bootstrap/bootstrap_config.hpp index f94b1d151..b1881825d 100644 --- a/nano/node/bootstrap/bootstrap_config.hpp +++ b/nano/node/bootstrap/bootstrap_config.hpp @@ -52,7 +52,7 @@ public: // Maximum number of un-responded requests per channel, should be lower or equal to bootstrap server max queue size std::size_t channel_limit{ 16 }; std::size_t database_rate_limit{ 256 }; - std::size_t frontier_rate_limit{ 100 }; + std::size_t frontier_rate_limit{ 8 }; std::size_t database_warmup_ratio{ 10 }; std::size_t max_pull_count{ nano::bootstrap_server::max_blocks }; std::chrono::milliseconds request_timeout{ 1000 * 5 }; From cf91806cc4d3f6edadc9639d25af6358fd9c6736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Sun, 27 Oct 2024 13:24:52 +0100 Subject: [PATCH 11/12] Rename file to `crawlers.hpp` --- nano/node/bootstrap_ascending/{iterators.hpp => crawlers.hpp} | 0 nano/node/bootstrap_ascending/database_scan.cpp | 2 +- nano/node/bootstrap_ascending/service.cpp | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename nano/node/bootstrap_ascending/{iterators.hpp => crawlers.hpp} (100%) diff --git a/nano/node/bootstrap_ascending/iterators.hpp b/nano/node/bootstrap_ascending/crawlers.hpp similarity index 100% rename from nano/node/bootstrap_ascending/iterators.hpp rename to nano/node/bootstrap_ascending/crawlers.hpp diff --git a/nano/node/bootstrap_ascending/database_scan.cpp b/nano/node/bootstrap_ascending/database_scan.cpp index e2328d752..8501f828b 100644 --- a/nano/node/bootstrap_ascending/database_scan.cpp +++ b/nano/node/bootstrap_ascending/database_scan.cpp @@ -1,6 +1,6 @@ #include +#include #include -#include #include #include #include diff --git a/nano/node/bootstrap_ascending/service.cpp b/nano/node/bootstrap_ascending/service.cpp index 223992604..037802504 100644 --- a/nano/node/bootstrap_ascending/service.cpp +++ b/nano/node/bootstrap_ascending/service.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include From 1e15e8d9523a5f2b4d30f0240d54b922d9b9a4b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Sun, 27 Oct 2024 13:37:28 +0100 Subject: [PATCH 12/12] Stats --- nano/lib/stats_enums.hpp | 6 ++---- nano/node/bootstrap_ascending/service.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/nano/lib/stats_enums.hpp b/nano/lib/stats_enums.hpp index 52fdb1fb0..9c764de6a 100644 --- a/nano/lib/stats_enums.hpp +++ b/nano/lib/stats_enums.hpp @@ -140,6 +140,8 @@ enum class detail empty, done, retry, + prioritized, + pending, // processing queue queue, @@ -485,10 +487,6 @@ enum class detail deprioritize, deprioritize_failed, sync_dependencies, - frontiers_processed, - frontiers_prioritized, - frontiers_outdated, - frontiers_pending, request_blocks, request_account_info, diff --git a/nano/node/bootstrap_ascending/service.cpp b/nano/node/bootstrap_ascending/service.cpp index 037802504..56152ed42 100644 --- a/nano/node/bootstrap_ascending/service.cpp +++ b/nano/node/bootstrap_ascending/service.cpp @@ -975,10 +975,10 @@ void nano::bootstrap_ascending::service::process_frontiers (std::deque guard{ mutex };