From 878cce97e368ba3bcdb49401323aa2cfd305ff45 Mon Sep 17 00:00:00 2001 From: Wesley Shillingford Date: Tue, 26 Mar 2019 14:58:24 +0000 Subject: [PATCH] Ability to limit CPU rate for POW (#1734) * Rate limited POW Formatting Fix wallet build Formatting Fix test Change interval to nanoseconds Formatting * Formatting * Formatting again * Add CLI option to --debug_profile_generate to specify sleep interval. * Move thread sleep to outer while loop --- nano/core_test/node.cpp | 10 +++++-- nano/core_test/work_pool.cpp | 57 +++++++++++++++++++++++++++++++----- nano/lib/work.cpp | 22 +++++++++----- nano/lib/work.hpp | 3 +- nano/nano_node/daemon.cpp | 4 +-- nano/nano_node/entry.cpp | 24 ++++++++++----- nano/nano_wallet/entry.cpp | 4 +-- nano/node/node.cpp | 2 +- nano/node/nodeconfig.cpp | 8 ++++- nano/node/nodeconfig.hpp | 1 + nano/node/testing.cpp | 2 +- 11 files changed, 105 insertions(+), 32 deletions(-) diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index 60e94f20..0e421e77 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -32,7 +32,7 @@ TEST (node, block_store_path_failure) auto path (nano::unique_path ()); nano::logging logging; logging.init (path); - nano::work_pool work (std::numeric_limits::max (), nullptr); + nano::work_pool work (std::numeric_limits::max ()); auto node (std::make_shared (init, *service, 24000, path, alarm, logging, work)); ASSERT_TRUE (node->wallets.items.empty ()); node->stop (); @@ -47,7 +47,7 @@ TEST (node, password_fanout) nano::node_config config; config.peering_port = 24000; config.logging.init (path); - nano::work_pool work (std::numeric_limits::max (), nullptr); + nano::work_pool work (std::numeric_limits::max ()); config.password_fanout = 10; auto node (std::make_shared (init, *service, path, alarm, config, work)); auto wallet (node->wallets.create (100)); @@ -698,10 +698,12 @@ TEST (node_config, v16_v17_upgrade) // These config options should not be present ASSERT_FALSE (tree.get_optional_child ("tcp_client_timeout")); ASSERT_FALSE (tree.get_optional_child ("tcp_server_timeout")); + ASSERT_FALSE (tree.get_optional_child ("pow_sleep_interval")); config.deserialize_json (upgraded, tree); // The config options should be added after the upgrade ASSERT_TRUE (!!tree.get_optional_child ("tcp_client_timeout")); ASSERT_TRUE (!!tree.get_optional_child ("tcp_server_timeout")); + ASSERT_TRUE (!!tree.get_optional_child ("pow_sleep_interval")); ASSERT_TRUE (upgraded); auto version (tree.get ("version")); @@ -723,19 +725,23 @@ TEST (node_config, v17_values) // Check config is correct tree.put ("tcp_client_timeout", 1); tree.put ("tcp_server_timeout", 0); + tree.put ("pow_sleep_interval", 0); config.deserialize_json (upgraded, tree); ASSERT_FALSE (upgraded); ASSERT_EQ (config.tcp_client_timeout.count (), 1); ASSERT_EQ (config.tcp_server_timeout.count (), 0); + ASSERT_EQ (config.pow_sleep_interval.count (), 0); // Check config is correct with other values tree.put ("tcp_client_timeout", std::numeric_limits::max () - 100); tree.put ("tcp_server_timeout", std::numeric_limits::max ()); + tree.put ("pow_sleep_interval", std::numeric_limits::max () - 100); upgraded = false; config.deserialize_json (upgraded, tree); ASSERT_FALSE (upgraded); ASSERT_EQ (config.tcp_client_timeout.count (), std::numeric_limits::max () - 100); ASSERT_EQ (config.tcp_server_timeout.count (), std::numeric_limits::max ()); + ASSERT_EQ (config.pow_sleep_interval.count (), std::numeric_limits::max () - 100); } // Regression test to ensure that deserializing includes changes node via get_required_child diff --git a/nano/core_test/work_pool.cpp b/nano/core_test/work_pool.cpp index 0d635439..5c1216a2 100644 --- a/nano/core_test/work_pool.cpp +++ b/nano/core_test/work_pool.cpp @@ -1,13 +1,14 @@ #include #include +#include #include #include TEST (work, one) { nano::network_params params; - nano::work_pool pool (std::numeric_limits::max (), nullptr); + nano::work_pool pool (std::numeric_limits::max ()); nano::change_block block (1, 1, nano::keypair ().prv, 3, 4); block.block_work_set (pool.generate (block.root ())); uint64_t difficulty; @@ -18,7 +19,7 @@ TEST (work, one) TEST (work, validate) { nano::network_params params; - nano::work_pool pool (std::numeric_limits::max (), nullptr); + nano::work_pool pool (std::numeric_limits::max ()); nano::send_block send_block (1, 1, 2, nano::keypair ().prv, 4, 6); uint64_t difficulty; ASSERT_TRUE (nano::work_validate (send_block, &difficulty)); @@ -30,7 +31,7 @@ TEST (work, validate) TEST (work, cancel) { - nano::work_pool pool (std::numeric_limits::max (), nullptr); + nano::work_pool pool (std::numeric_limits::max ()); auto iterations (0); auto done (false); while (!done) @@ -47,7 +48,7 @@ TEST (work, cancel) TEST (work, cancel_many) { - nano::work_pool pool (std::numeric_limits::max (), nullptr); + nano::work_pool pool (std::numeric_limits::max ()); nano::uint256_union key1 (1); nano::uint256_union key2 (2); nano::uint256_union key3 (1); @@ -75,10 +76,10 @@ TEST (work, opencl) auto opencl (nano::opencl_work::create (true, { 0, 0, 16 * 1024 }, logging)); if (opencl != nullptr) { - nano::work_pool pool (std::numeric_limits::max (), opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) { + nano::work_pool pool (std::numeric_limits::max (), std::chrono::nanoseconds (0), opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) { return opencl->generate_work (root_a, difficulty_a); } - : std::function (nano::uint256_union const &, uint64_t)> (nullptr)); + : std::function (nano::uint256_union const &, uint64_t)> (nullptr)); ASSERT_NE (nullptr, pool.opencl); nano::uint256_union root; uint64_t difficulty (0xff00000000000000); @@ -121,7 +122,7 @@ TEST (work, opencl_config) TEST (work, difficulty) { - nano::work_pool pool (std::numeric_limits::max (), nullptr); + nano::work_pool pool (std::numeric_limits::max ()); nano::uint256_union root (1); uint64_t difficulty1 (0xff00000000000000); uint64_t difficulty2 (0xfff0000000000000); @@ -143,3 +144,45 @@ TEST (work, difficulty) } while (nonce2 > difficulty3); ASSERT_GT (nonce2, difficulty2); } + +TEST (work, eco_pow) +{ + auto work_func = [](std::promise & promise, std::chrono::nanoseconds interval) { + nano::work_pool pool (1, interval); + constexpr auto num_iterations = 5; + + nano::timer timer; + timer.start (); + for (int i = 0; i < num_iterations; ++i) + { + nano::uint256_union root (1); + uint64_t difficulty1 (0xff00000000000000); + uint64_t difficulty2 (0xfff0000000000000); + uint64_t work (0); + uint64_t nonce (0); + do + { + work = pool.generate (root, difficulty1); + nano::work_validate (root, work, &nonce); + } while (nonce > difficulty2); + ASSERT_GT (nonce, difficulty1); + } + + promise.set_value_at_thread_exit (timer.stop ()); + }; + + std::promise promise1; + std::future future1 = promise1.get_future (); + std::promise promise2; + std::future future2 = promise2.get_future (); + + std::thread thread1 (work_func, std::ref (promise1), std::chrono::nanoseconds (0)); + std::thread thread2 (work_func, std::ref (promise2), std::chrono::nanoseconds (100000)); + + // Confirm that the eco pow rate limiter is working. + // It's possible under some unlucky circumstances that this fails. + ASSERT_LT (future1.get (), future2.get ()); + + thread1.join (); + thread2.join (); +} diff --git a/nano/lib/work.cpp b/nano/lib/work.cpp index 5c74d21d..4ba348f9 100644 --- a/nano/lib/work.cpp +++ b/nano/lib/work.cpp @@ -32,9 +32,10 @@ uint64_t nano::work_value (nano::block_hash const & root_a, uint64_t work_a) return result; } -nano::work_pool::work_pool (unsigned max_threads_a, std::function (nano::uint256_union const &, uint64_t)> opencl_a) : +nano::work_pool::work_pool (unsigned max_threads_a, std::chrono::nanoseconds pow_rate_limiter_a, std::function (nano::uint256_union const &, uint64_t)> opencl_a) : ticket (0), done (false), +pow_rate_limiter (pow_rate_limiter_a), opencl (opencl_a) { static_assert (ATOMIC_INT_LOCK_FREE == 2, "Atomic int needed"); @@ -71,6 +72,7 @@ void nano::work_pool::loop (uint64_t thread) blake2b_state hash; blake2b_init (&hash, sizeof (output)); std::unique_lock lock (mutex); + auto pow_sleep = pow_rate_limiter; while (!done || !pending.empty ()) { auto empty (pending.empty ()); @@ -101,6 +103,12 @@ void nano::work_pool::loop (uint64_t thread) blake2b_init (&hash, sizeof (output)); iteration -= 1; } + + // Add a rate limiter (if specified) to the pow calculation to save some CPUs which don't want to operate at full throttle + if (pow_sleep != std::chrono::nanoseconds (0)) + { + std::this_thread::sleep_for (pow_sleep); + } } lock.lock (); if (ticket == ticket_l) @@ -162,24 +170,24 @@ void nano::work_pool::stop () producer_condition.notify_all (); } -void nano::work_pool::generate (nano::uint256_union const & root_a, std::function const &)> callback_a) +void nano::work_pool::generate (nano::uint256_union const & hash_a, std::function const &)> callback_a) { - generate (root_a, callback_a, network_params.publish_threshold); + generate (hash_a, callback_a, network_params.publish_threshold); } -void nano::work_pool::generate (nano::uint256_union const & root_a, std::function const &)> callback_a, uint64_t difficulty_a) +void nano::work_pool::generate (nano::uint256_union const & hash_a, std::function const &)> callback_a, uint64_t difficulty_a) { - assert (!root_a.is_zero ()); + assert (!hash_a.is_zero ()); boost::optional result; if (opencl) { - result = opencl (root_a, difficulty_a); + result = opencl (hash_a, difficulty_a); } if (!result) { { std::lock_guard lock (mutex); - pending.push_back ({ root_a, callback_a, difficulty_a }); + pending.push_back ({ hash_a, callback_a, difficulty_a }); } producer_condition.notify_all (); } diff --git a/nano/lib/work.hpp b/nano/lib/work.hpp index 524e4bcb..d31b40f6 100644 --- a/nano/lib/work.hpp +++ b/nano/lib/work.hpp @@ -28,7 +28,7 @@ public: class work_pool final { public: - work_pool (unsigned, std::function (nano::uint256_union const &, uint64_t)> = nullptr); + work_pool (unsigned, std::chrono::nanoseconds = std::chrono::nanoseconds (0), std::function (nano::uint256_union const &, uint64_t)> = nullptr); ~work_pool (); void loop (uint64_t); void stop (); @@ -44,6 +44,7 @@ public: std::list pending; std::mutex mutex; std::condition_variable producer_condition; + std::chrono::nanoseconds pow_rate_limiter; std::function (nano::uint256_union const &, uint64_t)> opencl; nano::observer_set work_observers; }; diff --git a/nano/nano_node/daemon.cpp b/nano/nano_node/daemon.cpp index 4d2a5125..a5d2e5db 100644 --- a/nano/nano_node/daemon.cpp +++ b/nano/nano_node/daemon.cpp @@ -21,10 +21,10 @@ void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano:: config.node.logging.init (data_path); boost::asio::io_context io_ctx; auto opencl (nano::opencl_work::create (config.opencl_enable, config.opencl, config.node.logging)); - nano::work_pool opencl_work (config.node.work_threads, opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) { + nano::work_pool opencl_work (config.node.work_threads, config.node.pow_sleep_interval, opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) { return opencl->generate_work (root_a, difficulty_a); } - : std::function (nano::uint256_union const &, uint64_t)> (nullptr)); + : std::function (nano::uint256_union const &, uint64_t)> (nullptr)); nano::alarm alarm (io_ctx); nano::node_init init; try diff --git a/nano/nano_node/entry.cpp b/nano/nano_node/entry.cpp index 69a2b191..430939b5 100644 --- a/nano/nano_node/entry.cpp +++ b/nano/nano_node/entry.cpp @@ -99,7 +99,8 @@ int main (int argc, char * const * argv) ("platform", boost::program_options::value (), "Defines the for OpenCL commands") ("device", boost::program_options::value (), "Defines for OpenCL command") ("threads", boost::program_options::value (), "Defines count for OpenCL command") - ("difficulty", boost::program_options::value (), "Defines for OpenCL command, HEX"); + ("difficulty", boost::program_options::value (), "Defines for OpenCL command, HEX") + ("pow_sleep_interval", boost::program_options::value (), "Defines the amount to sleep inbetween each pow calculation attempt"); // clang-format on boost::program_options::variables_map vm; @@ -164,7 +165,7 @@ int main (int argc, char * const * argv) if (!key.decode_hex (key_it->second.as ())) { nano::keypair genesis (key.to_string ()); - nano::work_pool work (std::numeric_limits::max (), nullptr); + nano::work_pool work (std::numeric_limits::max ()); std::cout << "Genesis: " << genesis.prv.data.to_string () << "\n" << "Public: " << genesis.pub.to_string () << "\n" << "Account: " << genesis.pub.to_account () << "\n"; @@ -283,7 +284,14 @@ int main (int argc, char * const * argv) } else if (vm.count ("debug_profile_generate")) { - nano::work_pool work (std::numeric_limits::max (), nullptr); + auto pow_rate_limiter = std::chrono::nanoseconds (0); + auto pow_sleep_interval_it = vm.find ("pow_sleep_interval"); + if (pow_sleep_interval_it != vm.cend ()) + { + pow_rate_limiter = std::chrono::nanoseconds (boost::lexical_cast (pow_sleep_interval_it->second.as ())); + } + + nano::work_pool work (std::numeric_limits::max (), pow_rate_limiter); nano::change_block block (0, 0, nano::keypair ().prv, 0, 0); std::cerr << "Starting generation profiling\n"; while (true) @@ -364,10 +372,10 @@ int main (int argc, char * const * argv) { nano::logging logging; auto opencl (nano::opencl_work::create (true, { platform, device, threads }, logging)); - nano::work_pool work_pool (std::numeric_limits::max (), opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) { + nano::work_pool work_pool (std::numeric_limits::max (), std::chrono::nanoseconds (0), opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) { return opencl->generate_work (root_a, difficulty_a); } - : std::function (nano::uint256_union const &, uint64_t)> (nullptr)); + : std::function (nano::uint256_union const &, uint64_t)> (nullptr)); nano::change_block block (0, 0, nano::keypair ().prv, 0, 0); std::cerr << boost::str (boost::format ("Starting OpenCL generation profiling. Platform: %1%. Device: %2%. Threads: %3%. Difficulty: %4$#x\n") % platform % device % threads % difficulty); for (uint64_t i (0); true; ++i) @@ -402,7 +410,7 @@ int main (int argc, char * const * argv) } else if (vm.count ("debug_profile_verify")) { - nano::work_pool work (std::numeric_limits::max (), nullptr); + nano::work_pool work (std::numeric_limits::max ()); nano::change_block block (0, 0, nano::keypair ().prv, 0, 0); std::cerr << "Starting verification profiling\n"; while (true) @@ -478,7 +486,7 @@ int main (int argc, char * const * argv) std::cerr << boost::str (boost::format ("Starting pregenerating %1% blocks\n") % max_blocks); nano::system system (24000, 1); nano::node_init init; - nano::work_pool work (std::numeric_limits::max (), nullptr); + nano::work_pool work (std::numeric_limits::max ()); nano::logging logging; auto path (nano::unique_path ()); logging.init (path); @@ -590,7 +598,7 @@ int main (int argc, char * const * argv) std::cerr << boost::str (boost::format ("Starting pregenerating %1% votes\n") % max_votes); nano::system system (24000, 1); nano::node_init init; - nano::work_pool work (std::numeric_limits::max (), nullptr); + nano::work_pool work (std::numeric_limits::max ()); nano::logging logging; auto path (nano::unique_path ()); logging.init (path); diff --git a/nano/nano_wallet/entry.cpp b/nano/nano_wallet/entry.cpp index fb1335f3..08c3f1df 100644 --- a/nano/nano_wallet/entry.cpp +++ b/nano/nano_wallet/entry.cpp @@ -237,10 +237,10 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost std::shared_ptr gui; nano::set_application_icon (application); auto opencl (nano::opencl_work::create (config.opencl_enable, config.opencl, config.node.logging)); - nano::work_pool work (config.node.work_threads, opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) { + nano::work_pool work (config.node.work_threads, config.node.pow_sleep_interval, opencl ? [&opencl](nano::uint256_union const & root_a, uint64_t difficulty_a) { return opencl->generate_work (root_a, difficulty_a); } - : std::function (nano::uint256_union const &, uint64_t)> (nullptr)); + : std::function (nano::uint256_union const &, uint64_t)> (nullptr)); nano::alarm alarm (io_ctx); nano::node_init init; nano::node_flags flags; diff --git a/nano/node/node.cpp b/nano/node/node.cpp index acc667c7..b83753dd 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -3511,7 +3511,7 @@ nano::inactive_node::inactive_node (boost::filesystem::path const & path_a, uint path (path_a), io_context (std::make_shared ()), alarm (*io_context), -work (1, nullptr), +work (1), peering_port (peering_port_a) { boost::system::error_code error_chmod; diff --git a/nano/node/nodeconfig.cpp b/nano/node/nodeconfig.cpp index 9c9fc73e..79c4f5e6 100644 --- a/nano/node/nodeconfig.cpp +++ b/nano/node/nodeconfig.cpp @@ -8,6 +8,7 @@ namespace { const char * preconfigured_peers_key = "preconfigured_peers"; const char * signature_checker_threads_key = "signature_checker_threads"; +const char * pow_sleep_interval_key = "pow_sleep_interval"; const char * default_beta_peer_network = "peering-beta.nano.org"; const char * default_live_peer_network = "peering.nano.org"; } @@ -112,6 +113,7 @@ nano::error nano::node_config::serialize_json (nano::jsonconfig & json) const json.put ("unchecked_cutoff_time", unchecked_cutoff_time.count ()); json.put ("tcp_client_timeout", tcp_client_timeout.count ()); json.put ("tcp_server_timeout", tcp_server_timeout.count ()); + json.put ("pow_sleep_interval", pow_sleep_interval.count ()); nano::jsonconfig websocket_l; websocket_config.serialize_json (websocket_l); json.put_child ("websocket", websocket_l); @@ -244,6 +246,7 @@ bool nano::node_config::upgrade_json (unsigned version_a, nano::jsonconfig & jso json.put_child ("websocket", websocket_l); json.put ("tcp_client_timeout", tcp_client_timeout.count ()); json.put ("tcp_server_timeout", tcp_server_timeout.count ()); + json.put (pow_sleep_interval_key, pow_sleep_interval.count ()); upgraded = true; } case 17: @@ -379,8 +382,11 @@ nano::error nano::node_config::deserialize_json (bool & upgraded_a, nano::jsonco json.get ("allow_local_peers", allow_local_peers); json.get (signature_checker_threads_key, signature_checker_threads); - // Validate ranges + auto pow_sleep_interval_l (pow_sleep_interval.count ()); + json.get (pow_sleep_interval_key, pow_sleep_interval_l); + pow_sleep_interval = std::chrono::nanoseconds (pow_sleep_interval_l); + // Validate ranges if (online_weight_quorum > 100) { json.get_error ().set ("online_weight_quorum must be less than 100"); diff --git a/nano/node/nodeconfig.hpp b/nano/node/nodeconfig.hpp index d13d9049..71609a1b 100644 --- a/nano/node/nodeconfig.hpp +++ b/nano/node/nodeconfig.hpp @@ -58,6 +58,7 @@ public: std::chrono::seconds unchecked_cutoff_time{ std::chrono::seconds (4 * 60 * 60) }; // 4 hours std::chrono::seconds tcp_client_timeout{ std::chrono::seconds (5) }; std::chrono::seconds tcp_server_timeout{ std::chrono::seconds (30) }; + std::chrono::nanoseconds pow_sleep_interval{ 0 }; static std::chrono::seconds constexpr keepalive_period = std::chrono::seconds (60); static std::chrono::seconds constexpr keepalive_cutoff = keepalive_period * 5; static std::chrono::minutes constexpr wallet_backup_interval = std::chrono::minutes (5); diff --git a/nano/node/testing.cpp b/nano/node/testing.cpp index c026cf60..dd03ad44 100644 --- a/nano/node/testing.cpp +++ b/nano/node/testing.cpp @@ -21,7 +21,7 @@ std::string nano::error_system_messages::message (int ev) const nano::system::system (uint16_t port_a, uint16_t count_a) : alarm (io_ctx), -work (1, nullptr) +work (1) { auto scale_str = std::getenv ("DEADLINE_SCALE_FACTOR"); if (scale_str)