diff --git a/rai/core_test/node.cpp b/rai/core_test/node.cpp index 9d2a0a7d..c9167008 100644 --- a/rai/core_test/node.cpp +++ b/rai/core_test/node.cpp @@ -1459,3 +1459,24 @@ TEST (node, balance_observer) ASSERT_GT (200, iterations); } } + +TEST (node, bootstrap_connection_scaling) +{ + rai::system system (24000, 1); + auto & node1 (*system.nodes[0]); + node1.bootstrap_initiator.bootstrap (); + auto & attempt = node1.bootstrap_initiator.attempt; + ASSERT_EQ (34, attempt->target_connections (25000)); + ASSERT_EQ (4, attempt->target_connections (0)); + ASSERT_EQ (64, attempt->target_connections (50000)); + ASSERT_EQ (64, attempt->target_connections (10000000000)); + node1.config.bootstrap_connections = 128; + ASSERT_EQ (64, attempt->target_connections (0)); + ASSERT_EQ (64, attempt->target_connections (50000)); + node1.config.bootstrap_connections_max = 256; + ASSERT_EQ (128, attempt->target_connections (0)); + ASSERT_EQ (256, attempt->target_connections (50000)); + node1.config.bootstrap_connections_max = 0; + ASSERT_EQ (1, attempt->target_connections (0)); + ASSERT_EQ (1, attempt->target_connections (50000)); +} diff --git a/rai/node/bootstrap.cpp b/rai/node/bootstrap.cpp index ec455718..d2e35597 100644 --- a/rai/node/bootstrap.cpp +++ b/rai/node/bootstrap.cpp @@ -5,9 +5,14 @@ #include -// Updated 1-27-18 +constexpr double bootstrap_connection_scale_target = 50000.0; +constexpr double bootstrap_connection_warmup_time = 5.0; +constexpr double bootstrap_minimum_block_rate = 10.0; +constexpr double bootstrap_minimum_termination_time = 30.0; +constexpr unsigned bootstrap_max_new_connections = 10; constexpr unsigned bootstrap_peer_frontier_minimum = rai::rai_network == rai::rai_networks::rai_live_network ? 339000 : 0; + rai::block_synchronization::block_synchronization (boost::log::sources::logger_mt & log_a) : log (log_a) { @@ -971,12 +976,26 @@ struct block_rate_cmp } }; +unsigned rai::bootstrap_attempt::target_connections (size_t pulls_remaining) +{ + if (node->config.bootstrap_connections >= node->config.bootstrap_connections_max) { + return std::max(1U, node->config.bootstrap_connections_max); + } + + // Only scale up to bootstrap_connections_max for large pulls. + double step = std::min (1.0, std::max (0.0, (double)pulls_remaining / bootstrap_connection_scale_target)); + double target = (double)node->config.bootstrap_connections + (double)(node->config.bootstrap_connections_max - node->config.bootstrap_connections) * step; + return std::max(1U, (unsigned)(target + 0.5f)); +} + void rai::bootstrap_attempt::populate_connections () { double rate_sum = 0.0; + size_t num_pulls = 0; std::priority_queue, std::vector>, block_rate_cmp> sorted_connections; { std::unique_lock lock (mutex); + num_pulls = pulls.size (); for (auto & c : clients) { if (auto client = c.lock ()) @@ -984,13 +1003,13 @@ void rai::bootstrap_attempt::populate_connections () double elapsed = client->elapsed_seconds (); auto rate = client->block_rate (); rate_sum += rate; - if (client->elapsed_seconds () > 5.0 && client->block_count > 0) + if (client->elapsed_seconds () > bootstrap_connection_warmup_time && client->block_count > 0) { sorted_connections.push (client); } // Force-stop the slowest peers, since they can take the whole bootstrap hostage by dribbling out blocks on the last remaining pull. // This is ~1.5kilobits/sec. - if (elapsed > 30.0 && rate < 10.0) + if (elapsed > bootstrap_minimum_termination_time && rate < bootstrap_minimum_block_rate) { client->stop (true); } @@ -998,12 +1017,14 @@ void rai::bootstrap_attempt::populate_connections () } } + auto target = target_connections (num_pulls); + // We only want to drop slow peers when more than 2/3 are active. 2/3 because 1/2 is too aggressive, and 100% rarely happens. // Probably needs more tuning. - if (sorted_connections.size () >= (node->config.bootstrap_connections * 2) / 3 && node->config.bootstrap_connections >= 4) + if (sorted_connections.size () >= (target * 2) / 3 && target >= 4) { // 4 -> 1, 8 -> 2, 16 -> 4, arbitrary, but seems to work well. - auto drop = (int)roundf (sqrtf ((float)node->config.bootstrap_connections - 2.0f)); + auto drop = (int)roundf (sqrtf ((float)target - 2.0f)); for (int i = 0; i < drop; i++) { auto client = sorted_connections.top (); @@ -1018,9 +1039,9 @@ void rai::bootstrap_attempt::populate_connections () BOOST_LOG (node->log) << boost::str (boost::format ("Bulk pull connections: %1%, rate: %2% blocks/sec, remaining account pulls: %3%, total blocks: %4%") % connections.load () % (int)rate_sum % pulls.size () % (int)total_blocks.load ()); } - if (connections < node->config.bootstrap_connections) + if (connections < target) { - auto delta = std::min ((node->config.bootstrap_connections - connections) * 2, 10U); + auto delta = std::min ((target - connections) * 2, bootstrap_max_new_connections); // TODO - tune this better // Not many peers respond, need to try to make more connections than we need. for (int i = 0; i < delta; i++) diff --git a/rai/node/bootstrap.hpp b/rai/node/bootstrap.hpp index 1c40519e..4d7694ce 100644 --- a/rai/node/bootstrap.hpp +++ b/rai/node/bootstrap.hpp @@ -88,6 +88,7 @@ public: void add_pull (rai::pull_info const &); bool still_pulling (); void process_fork (MDB_txn *, std::shared_ptr); + unsigned target_connections (size_t pulls_remaining); std::deque> clients; std::weak_ptr connection_frontier_request; std::weak_ptr frontiers; diff --git a/rai/node/node.cpp b/rai/node/node.cpp index cac10e39..bb464ff0 100644 --- a/rai/node/node.cpp +++ b/rai/node/node.cpp @@ -750,7 +750,8 @@ password_fanout (1024), io_threads (std::max (4, std::thread::hardware_concurrency ())), work_threads (std::max (4, std::thread::hardware_concurrency ())), enable_voting (true), -bootstrap_connections (16), +bootstrap_connections (4), +bootstrap_connections_max (64), callback_port (0), lmdb_max_dbs (128) { @@ -822,6 +823,7 @@ void rai::node_config::serialize_json (boost::property_tree::ptree & tree_a) con tree_a.put ("work_threads", std::to_string (work_threads)); tree_a.put ("enable_voting", enable_voting); tree_a.put ("bootstrap_connections", bootstrap_connections); + tree_a.put ("bootstrap_connections_max", bootstrap_connections_max); tree_a.put ("callback_address", callback_address); tree_a.put ("callback_port", std::to_string (callback_port)); tree_a.put ("callback_target", callback_target); @@ -966,6 +968,7 @@ bool rai::node_config::deserialize_json (bool & upgraded_a, boost::property_tree auto work_threads_l (tree_a.get ("work_threads")); enable_voting = tree_a.get ("enable_voting"); auto bootstrap_connections_l (tree_a.get ("bootstrap_connections")); + auto bootstrap_connections_max_l (tree_a.get ("bootstrap_connections_max")); callback_address = tree_a.get ("callback_address"); auto callback_port_l (tree_a.get ("callback_port")); callback_target = tree_a.get ("callback_target"); @@ -979,6 +982,7 @@ bool rai::node_config::deserialize_json (bool & upgraded_a, boost::property_tree io_threads = std::stoul (io_threads_l); work_threads = std::stoul (work_threads_l); bootstrap_connections = std::stoul (bootstrap_connections_l); + bootstrap_connections_max = std::stoul (bootstrap_connections_max_l); lmdb_max_dbs = std::stoi (lmdb_max_dbs_l); result |= peering_port > std::numeric_limits::max (); result |= logging.deserialize_json (upgraded_a, logging_l); diff --git a/rai/node/node.hpp b/rai/node/node.hpp index 6d1586ea..edf38c5c 100644 --- a/rai/node/node.hpp +++ b/rai/node/node.hpp @@ -400,6 +400,7 @@ public: unsigned work_threads; bool enable_voting; unsigned bootstrap_connections; + unsigned bootstrap_connections_max; std::string callback_address; uint16_t callback_port; std::string callback_target;