diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index fe0da9a46..5079a23d9 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -325,6 +325,30 @@ TEST (node, auto_bootstrap_reverse) ASSERT_TIMELY (10s, node1->balance (key2.pub) == node0->config.receive_minimum.number ()); } +TEST (node, auto_bootstrap_age) +{ + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + nano::node_flags node_flags; + node_flags.disable_bootstrap_bulk_push_client = true; + node_flags.disable_lazy_bootstrap = true; + node_flags.bootstrap_interval = 1; + auto node0 = system.add_node (config, node_flags); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.logging, system.work, node_flags)); + ASSERT_FALSE (node1->init_error ()); + node1->start (); + system.nodes.push_back (node1); + ASSERT_NE (nullptr, nano::establish_tcp (system, *node1, node0->network.endpoint ())); + ASSERT_TIMELY (10s, node1->bootstrap_initiator.in_progress ()); + // 4 bootstraps with frontiers age + ASSERT_TIMELY (10s, node0->stats.count (nano::stat::type::bootstrap, nano::stat::detail::initiate_legacy_age, nano::stat::dir::out) >= 3); + // More attempts with frontiers age + ASSERT_GE (node0->stats.count (nano::stat::type::bootstrap, nano::stat::detail::initiate_legacy_age, nano::stat::dir::out), node0->stats.count (nano::stat::type::bootstrap, nano::stat::detail::initiate, nano::stat::dir::out)); + + node1->stop (); +} + TEST (node, receive_gap) { nano::system system (1); diff --git a/nano/lib/stats.cpp b/nano/lib/stats.cpp index d56b66fbd..27d29cefd 100644 --- a/nano/lib/stats.cpp +++ b/nano/lib/stats.cpp @@ -658,6 +658,9 @@ std::string nano::stat::detail_to_string (uint32_t key) case nano::stat::detail::initiate: res = "initiate"; break; + case nano::stat::detail::initiate_legacy_age: + res = "initiate_legacy_age"; + break; case nano::stat::detail::initiate_lazy: res = "initiate_lazy"; break; diff --git a/nano/lib/stats.hpp b/nano/lib/stats.hpp index d1a513669..cf4865630 100644 --- a/nano/lib/stats.hpp +++ b/nano/lib/stats.hpp @@ -285,6 +285,7 @@ public: // bootstrap, callback initiate, + initiate_legacy_age, initiate_lazy, initiate_wallet_lazy, diff --git a/nano/node/bootstrap/bootstrap.cpp b/nano/node/bootstrap/bootstrap.cpp index d500ad569..ce6113843 100644 --- a/nano/node/bootstrap/bootstrap.cpp +++ b/nano/node/bootstrap/bootstrap.cpp @@ -31,7 +31,7 @@ nano::bootstrap_initiator::~bootstrap_initiator () stop (); } -void nano::bootstrap_initiator::bootstrap (bool force, std::string id_a) +void nano::bootstrap_initiator::bootstrap (bool force, std::string id_a, uint32_t const frontiers_age_a) { if (force) { @@ -40,8 +40,8 @@ void nano::bootstrap_initiator::bootstrap (bool force, std::string id_a) nano::unique_lock lock (mutex); if (!stopped && find_attempt (nano::bootstrap_mode::legacy) == nullptr) { - node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate, nano::stat::dir::out); - auto legacy_attempt (std::make_shared (node.shared (), attempts.incremental++, id_a)); + node.stats.inc (nano::stat::type::bootstrap, frontiers_age_a == std::numeric_limits::max () ? nano::stat::detail::initiate : nano::stat::detail::initiate_legacy_age, nano::stat::dir::out); + auto legacy_attempt (std::make_shared (node.shared (), attempts.incremental++, id_a, frontiers_age_a)); attempts_list.push_back (legacy_attempt); attempts.add (legacy_attempt); lock.unlock (); @@ -67,7 +67,7 @@ void nano::bootstrap_initiator::bootstrap (nano::endpoint const & endpoint_a, bo stop_attempts (); node.stats.inc (nano::stat::type::bootstrap, nano::stat::detail::initiate, nano::stat::dir::out); nano::lock_guard lock (mutex); - auto legacy_attempt (std::make_shared (node.shared (), attempts.incremental++, id_a)); + auto legacy_attempt (std::make_shared (node.shared (), attempts.incremental++, id_a, std::numeric_limits::max ())); attempts_list.push_back (legacy_attempt); attempts.add (legacy_attempt); if (frontiers_confirmed) diff --git a/nano/node/bootstrap/bootstrap.hpp b/nano/node/bootstrap/bootstrap.hpp index 4e120fae2..5ba580c34 100644 --- a/nano/node/bootstrap/bootstrap.hpp +++ b/nano/node/bootstrap/bootstrap.hpp @@ -82,7 +82,7 @@ public: explicit bootstrap_initiator (nano::node &); ~bootstrap_initiator (); void bootstrap (nano::endpoint const &, bool add_to_peers = true, bool frontiers_confirmed = false, std::string id_a = ""); - void bootstrap (bool force = false, std::string id_a = ""); + void bootstrap (bool force = false, std::string id_a = "", uint32_t const frontiers_age_a = std::numeric_limits::max ()); void bootstrap_lazy (nano::hash_or_account const &, bool force = false, bool confirmed = true, std::string id_a = ""); void bootstrap_wallet (std::deque &); void run_bootstrap (); diff --git a/nano/node/bootstrap/bootstrap_frontier.cpp b/nano/node/bootstrap/bootstrap_frontier.cpp index 24e44506f..a1dd4b1f4 100644 --- a/nano/node/bootstrap/bootstrap_frontier.cpp +++ b/nano/node/bootstrap/bootstrap_frontier.cpp @@ -12,12 +12,13 @@ constexpr unsigned nano::bootstrap_limits::bulk_push_cost_limit; constexpr size_t nano::frontier_req_client::size_frontier; -void nano::frontier_req_client::run () +void nano::frontier_req_client::run (uint32_t const frontiers_age_a) { nano::frontier_req request; request.start.clear (); - request.age = std::numeric_limits::max (); + request.age = frontiers_age_a; request.count = std::numeric_limits::max (); + frontiers_age = frontiers_age_a; auto this_l (shared_from_this ()); connection->channel->send ( request, [this_l](boost::system::error_code const & ec, size_t size_a) { @@ -68,7 +69,7 @@ void nano::frontier_req_client::receive_frontier () void nano::frontier_req_client::unsynced (nano::block_hash const & head, nano::block_hash const & end) { - if (bulk_push_cost < nano::bootstrap_limits::bulk_push_cost_limit) + if (bulk_push_cost < nano::bootstrap_limits::bulk_push_cost_limit && frontiers_age == std::numeric_limits::max ()) { attempt->add_bulk_push_target (head, end); if (end.is_zero ()) diff --git a/nano/node/bootstrap/bootstrap_frontier.hpp b/nano/node/bootstrap/bootstrap_frontier.hpp index 19b8e64ae..742151a54 100644 --- a/nano/node/bootstrap/bootstrap_frontier.hpp +++ b/nano/node/bootstrap/bootstrap_frontier.hpp @@ -13,7 +13,7 @@ class frontier_req_client final : public std::enable_shared_from_this const &, std::shared_ptr const &); - void run (); + void run (uint32_t const frontiers_age_a); void receive_frontier (); void received_frontier (boost::system::error_code const &, size_t); void unsynced (nano::block_hash const &, nano::block_hash const &); @@ -30,6 +30,7 @@ public: /** A very rough estimate of the cost of `bulk_push`ing missing blocks */ uint64_t bulk_push_cost; std::deque> accounts; + uint32_t frontiers_age{ std::numeric_limits::max () }; static size_t constexpr size_frontier = sizeof (nano::account) + sizeof (nano::block_hash); }; class bootstrap_server; diff --git a/nano/node/bootstrap/bootstrap_legacy.cpp b/nano/node/bootstrap/bootstrap_legacy.cpp index 648444b5f..0742ff3a3 100644 --- a/nano/node/bootstrap/bootstrap_legacy.cpp +++ b/nano/node/bootstrap/bootstrap_legacy.cpp @@ -5,8 +5,9 @@ #include -nano::bootstrap_attempt_legacy::bootstrap_attempt_legacy (std::shared_ptr const & node_a, uint64_t incremental_id_a, std::string const & id_a) : -nano::bootstrap_attempt (node_a, nano::bootstrap_mode::legacy, incremental_id_a, id_a) +nano::bootstrap_attempt_legacy::bootstrap_attempt_legacy (std::shared_ptr const & node_a, uint64_t const incremental_id_a, std::string const & id_a, uint32_t const frontiers_age_a) : +nano::bootstrap_attempt (node_a, nano::bootstrap_mode::legacy, incremental_id_a, id_a), +frontiers_age (frontiers_age_a) { node->bootstrap_initiator.notify_listeners (true); } @@ -152,11 +153,14 @@ void nano::bootstrap_attempt_legacy::attempt_restart_check (nano::unique_lockshared ()); auto this_l (shared_from_this ()); - node->background ([node_l, this_l]() { + auto duration (std::chrono::duration_cast (std::chrono::steady_clock::now () - attempt_start).count ()); + auto frontiers_age_l (frontiers_age != std::numeric_limits::max () ? frontiers_age + duration : frontiers_age); + node->background ([node_l, this_l, frontiers_age_l]() { node_l->bootstrap_initiator.remove_attempt (this_l); + auto id_l (this_l->id); // Delay after removing current attempt - node_l->workers.add_timed_task (std::chrono::steady_clock::now () + std::chrono::milliseconds (50), [node_l]() { - node_l->bootstrap_initiator.bootstrap (true); + node_l->workers.add_timed_task (std::chrono::steady_clock::now () + std::chrono::milliseconds (50), [node_l, id_l, frontiers_age_l]() { + node_l->bootstrap_initiator.bootstrap (true, id_l, frontiers_age_l); }); }); } @@ -314,7 +318,7 @@ bool nano::bootstrap_attempt_legacy::request_frontier (nano::unique_lock (connection_l, this_l)); - client->run (); + client->run (frontiers_age); frontiers = client; future = client->promise.get_future (); } diff --git a/nano/node/bootstrap/bootstrap_legacy.hpp b/nano/node/bootstrap/bootstrap_legacy.hpp index 4b298282c..cae60ab43 100644 --- a/nano/node/bootstrap/bootstrap_legacy.hpp +++ b/nano/node/bootstrap/bootstrap_legacy.hpp @@ -16,7 +16,7 @@ class node; class bootstrap_attempt_legacy : public bootstrap_attempt { public: - explicit bootstrap_attempt_legacy (std::shared_ptr const & node_a, uint64_t incremental_id_a, std::string const & id_a = ""); + explicit bootstrap_attempt_legacy (std::shared_ptr const & node_a, uint64_t const incremental_id_a, std::string const & id_a = "", uint32_t const frontiers_age_a = std::numeric_limits::max ()); void run () override; bool consume_future (std::future &); void stop () override; @@ -39,5 +39,6 @@ public: std::vector> bulk_push_targets; std::atomic account_count{ 0 }; std::atomic frontiers_confirmation_pending{ false }; + uint32_t frontiers_age; }; } \ No newline at end of file diff --git a/nano/node/node.cpp b/nano/node/node.cpp index 07f655100..b6dfe4fa5 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -851,7 +851,45 @@ void nano::node::ongoing_bootstrap () ++warmed_up; } } - bootstrap_initiator.bootstrap (); + if (network_params.network.is_dev_network () && flags.bootstrap_interval != 0) + { + // For test purposes allow faster automatic bootstraps + next_wakeup = std::chrono::seconds (flags.bootstrap_interval); + ++warmed_up; + } + // Differential bootstrap with max age (75% of all legacy attempts) + uint32_t frontiers_age (std::numeric_limits::max ()); + auto bootstrap_weight_reached (ledger.cache.block_count >= ledger.bootstrap_weight_max_blocks); + auto previous_bootstrap_count (stats.count (nano::stat::type::bootstrap, nano::stat::detail::initiate, nano::stat::dir::out) + stats.count (nano::stat::type::bootstrap, nano::stat::detail::initiate_legacy_age, nano::stat::dir::out)); + /* + - Maximum value for 25% of attempts or if block count is below preconfigured value (initial bootstrap not finished) + - Node shutdown time minus 1 hour for start attempts (warm up) + - Default age value otherwise (1 day for live network, 1 hour for beta) + */ + if (bootstrap_weight_reached) + { + if (warmed_up < 3) + { + // Find last online weight sample (last active time for node) + uint64_t last_sample_time (0); + auto transaction (store.tx_begin_read ()); + for (auto i (store.online_weight_begin (transaction)), n (store.online_weight_end ()); i != n; ++i) + { + last_sample_time = i->first; + } + uint64_t time_since_last_sample = std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()).count () - last_sample_time / std::pow (10, 9); // Nanoseconds to seconds + if (time_since_last_sample + 60 * 60 < std::numeric_limits::max ()) + { + frontiers_age = std::max (time_since_last_sample + 60 * 60, network_params.bootstrap.default_frontiers_age_seconds); + } + } + else if (previous_bootstrap_count % 4 != 0) + { + frontiers_age = network_params.bootstrap.default_frontiers_age_seconds; + } + } + // Bootstrap and schedule for next attempt + bootstrap_initiator.bootstrap (false, boost::str (boost::format ("auto_bootstrap_%1%") % previous_bootstrap_count), frontiers_age); std::weak_ptr node_w (shared_from_this ()); workers.add_timed_task (std::chrono::steady_clock::now () + next_wakeup, [node_w]() { if (auto node_l = node_w.lock ()) diff --git a/nano/node/nodeconfig.hpp b/nano/node/nodeconfig.hpp index fedac7ded..5314a00cf 100644 --- a/nano/node/nodeconfig.hpp +++ b/nano/node/nodeconfig.hpp @@ -154,5 +154,6 @@ public: size_t block_processor_verification_size{ 0 }; size_t inactive_votes_cache_size{ 16 * 1024 }; size_t vote_processor_capacity{ 144 * 1024 }; + size_t bootstrap_interval{ 0 }; // For testing only }; } diff --git a/nano/secure/common.cpp b/nano/secure/common.cpp index c59aa8dd9..1220dcac3 100644 --- a/nano/secure/common.cpp +++ b/nano/secure/common.cpp @@ -175,6 +175,7 @@ nano::bootstrap_constants::bootstrap_constants (nano::network_constants & networ lazy_retry_limit = network_constants.is_dev_network () ? 2 : frontier_retry_limit * 4; lazy_destinations_retry_limit = network_constants.is_dev_network () ? 1 : frontier_retry_limit / 4; gap_cache_bootstrap_start_interval = network_constants.is_dev_network () ? std::chrono::milliseconds (5) : std::chrono::milliseconds (30 * 1000); + default_frontiers_age_seconds = network_constants.is_dev_network () ? 1 : network_constants.is_beta_network () ? 60 * 60 : 24 * 60 * 60; // 1 second for dev network, 1 hour for beta, 24 hours for live } // Create a new random keypair diff --git a/nano/secure/common.hpp b/nano/secure/common.hpp index a3c2b736b..0e8c9f0dc 100644 --- a/nano/secure/common.hpp +++ b/nano/secure/common.hpp @@ -448,6 +448,7 @@ public: unsigned lazy_retry_limit; unsigned lazy_destinations_retry_limit; std::chrono::milliseconds gap_cache_bootstrap_start_interval; + uint32_t default_frontiers_age_seconds; }; /** Constants whose value depends on the active network */