From 45f66d2bd69c6fe74232e6b6c473618ea521b003 Mon Sep 17 00:00:00 2001 From: Wesley Shillingford Date: Tue, 7 Apr 2020 16:21:21 +0100 Subject: [PATCH 1/5] Using relaxed atomics for counts not involved in control flow in conf height processor (#2651) * Using relaxed atomics for counters in conf height processor * Set memory order for pending_writes_size (Serg review) * Missed modifying confirmed_iterated_pairs_size * Use relaxed atomic wrapper * SFINAE out most invalid types * Using incorrect compare_exchange * Add missing type helper alias --- nano/core_test/confirmation_height.cpp | 6 +- nano/core_test/utility.cpp | 68 +++++++++++++++++ nano/lib/threading.hpp | 84 ++++++++++++++++++++- nano/node/confirmation_height_bounded.cpp | 6 +- nano/node/confirmation_height_bounded.hpp | 50 ++++++------ nano/node/confirmation_height_processor.hpp | 1 + nano/node/confirmation_height_unbounded.cpp | 10 +++ nano/node/confirmation_height_unbounded.hpp | 18 +++-- 8 files changed, 207 insertions(+), 36 deletions(-) diff --git a/nano/core_test/confirmation_height.cpp b/nano/core_test/confirmation_height.cpp index ffb4e474f5..02b6ca642c 100644 --- a/nano/core_test/confirmation_height.cpp +++ b/nano/core_test/confirmation_height.cpp @@ -1208,14 +1208,12 @@ TEST (confirmation_height, dependent_election) add_callback_stats (*node); - // Start an election and vote, should confirm the block - node->block_confirm (send2); - { // The write guard prevents the confirmation height processor doing any writes. - // Note: This test could still fail intermittently due to thread scheduling between active and confirmation height. system.deadline_set (10s); auto write_guard = node->write_database_queue.wait (nano::writer::testing); + // Start an election and vote, should confirm the block + node->block_confirm (send2); while (!node->write_database_queue.contains (nano::writer::confirmation_height)) { ASSERT_NO_ERROR (system.poll ()); diff --git a/nano/core_test/utility.cpp b/nano/core_test/utility.cpp index 70ebd2b9b1..afc2da014b 100644 --- a/nano/core_test/utility.cpp +++ b/nano/core_test/utility.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -163,3 +164,70 @@ TEST (filesystem, move_all_files) ASSERT_FALSE (boost::filesystem::exists (dummy_file1)); ASSERT_FALSE (boost::filesystem::exists (dummy_file2)); } + +TEST (relaxed_atomic_integral, basic) +{ + nano::relaxed_atomic_integral atomic{ 0 }; + ASSERT_EQ (0, atomic++); + ASSERT_EQ (1, atomic); + ASSERT_EQ (2, ++atomic); + ASSERT_EQ (2, atomic); + ASSERT_EQ (2, atomic.load ()); + ASSERT_EQ (2, atomic--); + ASSERT_EQ (1, atomic); + ASSERT_EQ (0, --atomic); + ASSERT_EQ (0, atomic); + ASSERT_EQ (0, atomic.fetch_add (2)); + ASSERT_EQ (2, atomic); + ASSERT_EQ (2, atomic.fetch_sub (1)); + ASSERT_EQ (1, atomic); + atomic.store (3); + ASSERT_EQ (3, atomic); + + uint32_t expected{ 2 }; + ASSERT_FALSE (atomic.compare_exchange_strong (expected, 1)); + ASSERT_EQ (3, expected); + ASSERT_EQ (3, atomic); + ASSERT_TRUE (atomic.compare_exchange_strong (expected, 1)); + ASSERT_EQ (1, atomic); + ASSERT_EQ (3, expected); + + // Weak can fail spuriously, try a few times + bool res{ false }; + for (int i = 0; i < 1000; ++i) + { + res |= atomic.compare_exchange_weak (expected, 2); + expected = 1; + } + ASSERT_TRUE (res); + ASSERT_EQ (2, atomic); +} + +TEST (relaxed_atomic_integral, many_threads) +{ + std::vector threads; + auto num = 4; + nano::relaxed_atomic_integral atomic{ 0 }; + for (int i = 0; i < num; ++i) + { + threads.emplace_back ([&atomic] { + for (int i = 0; i < 10000; ++i) + { + ++atomic; + atomic--; + atomic++; + --atomic; + atomic.fetch_add (2); + atomic.fetch_sub (2); + } + }); + } + + for (auto & thread : threads) + { + thread.join (); + } + + // Check values + ASSERT_EQ (0, atomic); +} \ No newline at end of file diff --git a/nano/lib/threading.hpp b/nano/lib/threading.hpp index 2ea5138059..1097a8670b 100644 --- a/nano/lib/threading.hpp +++ b/nano/lib/threading.hpp @@ -75,4 +75,86 @@ class thread_runner final std::vector threads; boost::asio::executor_work_guard io_guard; }; -} \ No newline at end of file + +/* Default memory order of normal std::atomic operations is std::memory_order_seq_cst which provides + a total global ordering of atomic operations are well as synchronization between threads. Weaker memory + ordering can provide benefits in some circumstances, such like in dumb counters where no other data is + dependent on the ordering of these operations. This assumes T is a type of integer, not bool or char. */ +template ::value>> +class relaxed_atomic_integral +{ +public: + relaxed_atomic_integral () noexcept = default; + constexpr relaxed_atomic_integral (T desired) noexcept : + atomic (desired) + { + } + + T operator= (T desired) noexcept + { + store (desired); + return atomic; + } + + relaxed_atomic_integral (relaxed_atomic_integral const &) = delete; + relaxed_atomic_integral & operator= (relaxed_atomic_integral const &) = delete; + + void store (T desired, std::memory_order order = std::memory_order_relaxed) noexcept + { + atomic.store (desired, order); + } + + T load (std::memory_order order = std::memory_order_relaxed) const noexcept + { + return atomic.load (std::memory_order_relaxed); + } + + operator T () const noexcept + { + return load (); + } + + bool compare_exchange_weak (T & expected, T desired, std::memory_order order = std::memory_order_relaxed) noexcept + { + return atomic.compare_exchange_weak (expected, desired, order); + } + + bool compare_exchange_strong (T & expected, T desired, std::memory_order order = std::memory_order_relaxed) noexcept + { + return atomic.compare_exchange_strong (expected, desired, order); + } + + T fetch_add (T arg, std::memory_order order = std::memory_order_relaxed) noexcept + { + return atomic.fetch_add (arg, order); + } + + T fetch_sub (T arg, std::memory_order order = std::memory_order_relaxed) noexcept + { + return atomic.fetch_sub (arg, order); + } + + T operator++ () noexcept + { + return fetch_add (1) + 1; + } + + T operator++ (int) noexcept + { + return fetch_add (1); + } + + T operator-- () noexcept + { + return fetch_sub (1) - 1; + } + + T operator-- (int) noexcept + { + return fetch_sub (1); + } + +private: + std::atomic atomic; +}; +} diff --git a/nano/node/confirmation_height_bounded.cpp b/nano/node/confirmation_height_bounded.cpp index 529c3e6046..55a6bf6c89 100644 --- a/nano/node/confirmation_height_bounded.cpp +++ b/nano/node/confirmation_height_bounded.cpp @@ -292,7 +292,7 @@ void nano::confirmation_height_bounded::prepare_iterated_blocks_for_cementing (p else { accounts_confirmed_info.emplace (preparation_data_a.account, confirmed_info_l); - accounts_confirmed_info_size = accounts_confirmed_info.size (); + ++accounts_confirmed_info_size; } preparation_data_a.checkpoints.erase (std::remove (preparation_data_a.checkpoints.begin (), preparation_data_a.checkpoints.end (), preparation_data_a.top_most_non_receive_block_hash), preparation_data_a.checkpoints.end ()); @@ -315,7 +315,7 @@ void nano::confirmation_height_bounded::prepare_iterated_blocks_for_cementing (p else { accounts_confirmed_info.emplace (std::piecewise_construct, std::forward_as_tuple (receive_details->account), std::forward_as_tuple (receive_details->height, receive_details->hash)); - accounts_confirmed_info_size = accounts_confirmed_info.size (); + ++accounts_confirmed_info_size; } if (receive_details->next.is_initialized ()) @@ -443,7 +443,7 @@ bool nano::confirmation_height_bounded::cement_blocks () if (it != accounts_confirmed_info.cend () && it->second.confirmed_height == pending.top_height) { accounts_confirmed_info.erase (pending.account); - accounts_confirmed_info_size = accounts_confirmed_info.size (); + --accounts_confirmed_info_size; } pending_writes.pop_front (); --pending_writes_size; diff --git a/nano/node/confirmation_height_bounded.hpp b/nano/node/confirmation_height_bounded.hpp index 4d32ad36dd..42064e068a 100644 --- a/nano/node/confirmation_height_bounded.hpp +++ b/nano/node/confirmation_height_bounded.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -38,9 +39,35 @@ class confirmation_height_bounded final nano::block_hash iterated_frontier; }; + class write_details final + { + public: + write_details (nano::account const &, uint64_t, nano::block_hash const &, uint64_t, nano::block_hash const &); + nano::account account; + // This is the first block hash (bottom most) which is not cemented + uint64_t bottom_height; + nano::block_hash bottom_hash; + // Desired cemented frontier + uint64_t top_height; + nano::block_hash top_hash; + }; + + /** The maximum number of blocks to be read in while iterating over a long account chain */ + static uint64_t constexpr batch_read_size = 4096; + + static uint32_t constexpr max_items{ 65536 }; + + // All of the atomic variables here just track the size for use in collect_container_info. + // This is so that no mutexes are needed during the algorithm itself, which would otherwise be needed + // for the sake of a rarely used RPC call for debugging purposes. As such the sizes are not being acted + // upon in any way (does not synchronize with any other data). + // This allows the load and stores to use relaxed atomic memory ordering. + std::deque pending_writes; + nano::relaxed_atomic_integral pending_writes_size{ 0 }; + static uint32_t constexpr pending_writes_max_size{ max_items }; /* Holds confirmation height/cemented frontier in memory for accounts while iterating */ std::unordered_map accounts_confirmed_info; - std::atomic accounts_confirmed_info_size{ 0 }; + nano::relaxed_atomic_integral accounts_confirmed_info_size{ 0 }; class receive_chain_details final { @@ -71,19 +98,6 @@ class confirmation_height_bounded final boost::optional & next_in_receive_chain; }; - class write_details final - { - public: - write_details (nano::account const &, uint64_t, nano::block_hash const &, uint64_t, nano::block_hash const &); - nano::account account; - // This is the first block hash (bottom most) which is not cemented - uint64_t bottom_height; - nano::block_hash bottom_hash; - // Desired cemented frontier - uint64_t top_height; - nano::block_hash top_hash; - }; - class receive_source_pair final { public: @@ -93,14 +107,6 @@ class confirmation_height_bounded final nano::block_hash source_hash; }; - /** The maximum number of blocks to be read in while iterating over a long account chain */ - static uint64_t constexpr batch_read_size = 4096; - - static uint32_t constexpr max_items{ 65536 }; - - std::deque pending_writes; - std::atomic pending_writes_size{ 0 }; - static uint32_t constexpr pending_writes_max_size{ max_items }; nano::timer timer; top_and_next_hash get_next_block (boost::optional const &, boost::circular_buffer_space_optimized const &, boost::circular_buffer_space_optimized const & receive_source_pairs, boost::optional &); diff --git a/nano/node/confirmation_height_processor.hpp b/nano/node/confirmation_height_processor.hpp index ea56f97511..1763b0fc43 100644 --- a/nano/node/confirmation_height_processor.hpp +++ b/nano/node/confirmation_height_processor.hpp @@ -51,6 +51,7 @@ class confirmation_height_processor final nano::condition_variable condition; std::atomic stopped{ false }; + // No mutex needed for the observers as these should be set up during initialization of the node std::vector)>> cemented_observers; std::vector> block_already_cemented_observers; diff --git a/nano/node/confirmation_height_unbounded.cpp b/nano/node/confirmation_height_unbounded.cpp index b2399839d3..a1221bf2c2 100644 --- a/nano/node/confirmation_height_unbounded.cpp +++ b/nano/node/confirmation_height_unbounded.cpp @@ -23,6 +23,7 @@ void nano::confirmation_height_unbounded::process () std::shared_ptr receive_details; auto current = original_hash; orig_block_callback_data.clear (); + orig_block_callback_data_size = 0; std::vector receive_source_pairs; release_assert (receive_source_pairs.empty ()); @@ -122,6 +123,7 @@ void nano::confirmation_height_unbounded::process () else { confirmed_iterated_pairs.emplace (std::piecewise_construct, std::forward_as_tuple (account), std::forward_as_tuple (confirmation_height, block_height)); + ++confirmed_iterated_pairs_size; } } @@ -191,6 +193,7 @@ void nano::confirmation_height_unbounded::collect_unconfirmed_receive_and_source else if (is_original_block) { orig_block_callback_data.push_back (hash); + ++orig_block_callback_data_size; } else { @@ -207,6 +210,7 @@ void nano::confirmation_height_unbounded::collect_unconfirmed_receive_and_source last_receive_details->block_callback_data.push_back (hash); implicit_receive_cemented_mapping[hash] = std::weak_ptr (last_receive_details); + implicit_receive_cemented_mapping_size = implicit_receive_cemented_mapping.size (); } } @@ -235,6 +239,7 @@ void nano::confirmation_height_unbounded::prepare_iterated_blocks_for_cementing else { confirmed_iterated_pairs.emplace (std::piecewise_construct, std::forward_as_tuple (preparation_data_a.account), std::forward_as_tuple (block_height, block_height)); + ++confirmed_iterated_pairs_size; } auto num_blocks_confirmed = block_height - preparation_data_a.confirmation_height; @@ -275,6 +280,7 @@ void nano::confirmation_height_unbounded::prepare_iterated_blocks_for_cementing } pending_writes.emplace_back (preparation_data_a.account, preparation_data_a.current, block_height, num_blocks_confirmed, block_callback_data); + ++pending_writes_size; } if (receive_details) @@ -298,9 +304,11 @@ void nano::confirmation_height_unbounded::prepare_iterated_blocks_for_cementing else { confirmed_iterated_pairs.emplace (std::piecewise_construct, std::forward_as_tuple (receive_account), std::forward_as_tuple (receive_details->height, receive_details->height)); + ++confirmed_iterated_pairs_size; } pending_writes.push_back (*receive_details); + ++pending_writes_size; } } @@ -335,6 +343,7 @@ bool nano::confirmation_height_unbounded::cement_blocks () logger.always_log ("Failed to write confirmation height for: ", pending.hash.to_string ()); ledger.stats.inc (nano::stat::type::confirmation_height, nano::stat::detail::invalid_block); pending_writes.clear (); + pending_writes_size = 0; return true; } #endif @@ -361,6 +370,7 @@ bool nano::confirmation_height_unbounded::cement_blocks () } total_pending_write_block_count -= pending.num_blocks_confirmed; pending_writes.erase (pending_writes.begin ()); + --pending_writes_size; } debug_assert (total_pending_write_block_count == 0); debug_assert (pending_writes.empty ()); diff --git a/nano/node/confirmation_height_unbounded.hpp b/nano/node/confirmation_height_unbounded.hpp index 194599a5e1..c95059bb3d 100644 --- a/nano/node/confirmation_height_unbounded.hpp +++ b/nano/node/confirmation_height_unbounded.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -53,18 +54,23 @@ class confirmation_height_unbounded final nano::block_hash source_hash; }; + // All of the atomic variables here just track the size for use in collect_container_info. + // This is so that no mutexes are needed during the algorithm itself, which would otherwise be needed + // for the sake of a rarely used RPC call for debugging purposes. As such the sizes are not being acted + // upon in any way (does not synchronize with any other data). + // This allows the load and stores to use relaxed atomic memory ordering. std::unordered_map confirmed_iterated_pairs; - std::atomic confirmed_iterated_pairs_size{ 0 }; + nano::relaxed_atomic_integral confirmed_iterated_pairs_size{ 0 }; std::unordered_map> block_cache; - std::atomic block_cache_size{ 0 }; + nano::relaxed_atomic_integral block_cache_size{ 0 }; std::shared_ptr get_block_and_sideband (nano::block_hash const &, nano::transaction const &); std::deque pending_writes; - std::atomic pending_writes_size{ 0 }; + nano::relaxed_atomic_integral pending_writes_size{ 0 }; std::vector orig_block_callback_data; - std::atomic orig_block_callback_data_size{ 0 }; - + nano::relaxed_atomic_integral orig_block_callback_data_size{ 0 }; std::unordered_map> implicit_receive_cemented_mapping; - std::atomic implicit_receive_cemented_mapping_size{ 0 }; + nano::relaxed_atomic_integral implicit_receive_cemented_mapping_size{ 0 }; + nano::timer timer; class preparation_data final From a045cc33248154ae6bb79796c715c3c5801efec7 Mon Sep 17 00:00:00 2001 From: Guilherme Lawless Date: Wed, 8 Apr 2020 08:35:05 +0100 Subject: [PATCH 2/5] Prevent more rare deadlocks due to races for condition variables (#2706) * Prevent more rare deadlocks due to races for condition variables * Use void * Revert unecessary condition check * Revert unecessary lock to stop --- nano/node/bootstrap/bootstrap_attempt.cpp | 20 ++++++++++++++++++- nano/node/bootstrap/bootstrap_attempt.hpp | 2 ++ nano/node/bootstrap/bootstrap_bulk_pull.cpp | 6 ++---- nano/node/bootstrap/bootstrap_connections.cpp | 14 ++++++------- 4 files changed, 30 insertions(+), 12 deletions(-) diff --git a/nano/node/bootstrap/bootstrap_attempt.cpp b/nano/node/bootstrap/bootstrap_attempt.cpp index 590960fbb7..d27ce94fa3 100644 --- a/nano/node/bootstrap/bootstrap_attempt.cpp +++ b/nano/node/bootstrap/bootstrap_attempt.cpp @@ -70,6 +70,24 @@ bool nano::bootstrap_attempt::still_pulling () return running && still_pulling; } +void nano::bootstrap_attempt::pull_started () +{ + { + nano::lock_guard guard (mutex); + ++pulling; + } + condition.notify_all (); +} + +void nano::bootstrap_attempt::pull_finished () +{ + { + nano::lock_guard guard (mutex); + --pulling; + } + condition.notify_all (); +} + void nano::bootstrap_attempt::stop () { { @@ -517,8 +535,8 @@ bool nano::bootstrap_attempt_legacy::request_frontier (nano::unique_lockbootstrap_initiator.connections->add_pull (pull); - ++pulling; lock_a.lock (); + ++pulling; frontier_pulls.pop_front (); } } diff --git a/nano/node/bootstrap/bootstrap_attempt.hpp b/nano/node/bootstrap/bootstrap_attempt.hpp index 4a7b6d40ad..732c9e1e97 100644 --- a/nano/node/bootstrap/bootstrap_attempt.hpp +++ b/nano/node/bootstrap/bootstrap_attempt.hpp @@ -20,6 +20,8 @@ class bootstrap_attempt : public std::enable_shared_from_this virtual void run () = 0; virtual void stop (); bool still_pulling (); + void pull_started (); + void pull_finished (); bool should_log (); std::string mode_text (); virtual void restart_condition (); diff --git a/nano/node/bootstrap/bootstrap_bulk_pull.cpp b/nano/node/bootstrap/bootstrap_bulk_pull.cpp index fc050471a3..58cc180887 100644 --- a/nano/node/bootstrap/bootstrap_bulk_pull.cpp +++ b/nano/node/bootstrap/bootstrap_bulk_pull.cpp @@ -50,8 +50,7 @@ nano::bulk_pull_client::~bulk_pull_client () { connection->node->bootstrap_initiator.cache.remove (pull); } - --attempt->pulling; - attempt->condition.notify_all (); + attempt->pull_finished (); } void nano::bulk_pull_client::request () @@ -292,8 +291,7 @@ pull_blocks (0) nano::bulk_pull_account_client::~bulk_pull_account_client () { - --attempt->pulling; - attempt->condition.notify_all (); + attempt->pull_finished (); } void nano::bulk_pull_account_client::request () diff --git a/nano/node/bootstrap/bootstrap_connections.cpp b/nano/node/bootstrap/bootstrap_connections.cpp index 30cccef0a3..fea668d75a 100644 --- a/nano/node/bootstrap/bootstrap_connections.cpp +++ b/nano/node/bootstrap/bootstrap_connections.cpp @@ -293,7 +293,10 @@ void nano::bootstrap_connections::populate_connections (bool repeat) } else if (connections_count == 0) { - new_connections_empty = true; + { + nano::lock_guard lock (mutex); + new_connections_empty = true; + } condition.notify_all (); } } @@ -347,8 +350,7 @@ void nano::bootstrap_connections::request_pull (nano::unique_lock & // Check if lazy pull is obsolete (head was processed or head is 0 for destinations requests) if (attempt_l != nullptr && attempt_l->mode == nano::bootstrap_mode::lazy && !pull.head.is_zero () && attempt_l->lazy_processed_or_exists (pull.head)) { - --attempt_l->pulling; - attempt_l->condition.notify_all (); + attempt_l->pull_finished (); attempt_l = nullptr; } } @@ -400,8 +402,7 @@ void nano::bootstrap_connections::requeue_pull (nano::pull_info const & pull_a, nano::lock_guard lock (mutex); pulls.push_front (pull); } - ++attempt_l->pulling; - attempt_l->condition.notify_all (); + attempt_l->pull_started (); condition.notify_all (); } else if (attempt_l->mode == nano::bootstrap_mode::lazy && (pull.retry_limit == std::numeric_limits::max () || pull.attempts <= pull.retry_limit + (pull.processed / node.network_params.bootstrap.lazy_max_pull_blocks))) @@ -413,8 +414,7 @@ void nano::bootstrap_connections::requeue_pull (nano::pull_info const & pull_a, nano::lock_guard lock (mutex); pulls.push_back (pull); } - ++attempt_l->pulling; - attempt_l->condition.notify_all (); + attempt_l->pull_started (); condition.notify_all (); } } From fc206410eda7c4efd2b9f57438e426ad91601ae8 Mon Sep 17 00:00:00 2001 From: Guilherme Lawless Date: Wed, 8 Apr 2020 08:42:52 +0100 Subject: [PATCH 3/5] Prevent reconnecting to excluded peers with sufficient score. (#2694) --- nano/core_test/network.cpp | 56 +++++++++++++++++++---- nano/core_test/node_telemetry.cpp | 18 ++++++-- nano/core_test/toml.cpp | 3 ++ nano/lib/stats.cpp | 2 + nano/lib/stats.hpp | 1 + nano/node/bootstrap/bootstrap_attempt.cpp | 5 ++ nano/node/bootstrap/bootstrap_server.cpp | 11 ++++- nano/node/logging.cpp | 8 ++++ nano/node/logging.hpp | 2 + nano/node/peer_exclusion.cpp | 9 ++-- nano/node/peer_exclusion.hpp | 7 ++- nano/node/telemetry.cpp | 3 ++ nano/node/transport/tcp.cpp | 51 +++++++++++---------- 13 files changed, 132 insertions(+), 44 deletions(-) diff --git a/nano/core_test/network.cpp b/nano/core_test/network.cpp index a784f3fabc..37784e0111 100644 --- a/nano/core_test/network.cpp +++ b/nano/core_test/network.cpp @@ -1049,10 +1049,9 @@ TEST (peer_exclusion, validate) nano::peer_exclusion excluded_peers; size_t fake_peers_count = 10; auto max_size = excluded_peers.limited_size (fake_peers_count); - auto address (boost::asio::ip::address_v6::loopback ()); for (auto i = 0; i < max_size + 2; ++i) { - nano::tcp_endpoint endpoint (address, i); + nano::tcp_endpoint endpoint (boost::asio::ip::address_v6::v4_mapped (boost::asio::ip::address_v4 (i)), 0); ASSERT_FALSE (excluded_peers.check (endpoint)); ASSERT_EQ (1, excluded_peers.add (endpoint, fake_peers_count)); ASSERT_FALSE (excluded_peers.check (endpoint)); @@ -1060,21 +1059,22 @@ TEST (peer_exclusion, validate) // The oldest one must have been removed ASSERT_EQ (max_size + 1, excluded_peers.size ()); auto & peers_by_endpoint (excluded_peers.peers.get ()); - ASSERT_EQ (peers_by_endpoint.end (), peers_by_endpoint.find (nano::tcp_endpoint (address, 0))); + nano::tcp_endpoint oldest (boost::asio::ip::address_v6::v4_mapped (boost::asio::ip::address_v4 (0x0)), 0); + ASSERT_EQ (peers_by_endpoint.end (), peers_by_endpoint.find (oldest.address ())); auto to_seconds = [](std::chrono::steady_clock::time_point const & timepoint) { return std::chrono::duration_cast (timepoint.time_since_epoch ()).count (); }; - nano::tcp_endpoint first (address, 1); - ASSERT_NE (peers_by_endpoint.end (), peers_by_endpoint.find (first)); - nano::tcp_endpoint second (address, 2); + nano::tcp_endpoint first (boost::asio::ip::address_v6::v4_mapped (boost::asio::ip::address_v4 (0x1)), 0); + ASSERT_NE (peers_by_endpoint.end (), peers_by_endpoint.find (first.address ())); + nano::tcp_endpoint second (boost::asio::ip::address_v6::v4_mapped (boost::asio::ip::address_v4 (0x2)), 0); ASSERT_EQ (false, excluded_peers.check (second)); - ASSERT_NEAR (to_seconds (std::chrono::steady_clock::now () + excluded_peers.exclude_time_hours), to_seconds (peers_by_endpoint.find (second)->exclude_until), 2); + ASSERT_NEAR (to_seconds (std::chrono::steady_clock::now () + excluded_peers.exclude_time_hours), to_seconds (peers_by_endpoint.find (second.address ())->exclude_until), 2); ASSERT_EQ (2, excluded_peers.add (second, fake_peers_count)); - ASSERT_EQ (peers_by_endpoint.end (), peers_by_endpoint.find (first)); - ASSERT_NEAR (to_seconds (std::chrono::steady_clock::now () + excluded_peers.exclude_time_hours), to_seconds (peers_by_endpoint.find (second)->exclude_until), 2); + ASSERT_EQ (peers_by_endpoint.end (), peers_by_endpoint.find (first.address ())); + ASSERT_NEAR (to_seconds (std::chrono::steady_clock::now () + excluded_peers.exclude_time_hours), to_seconds (peers_by_endpoint.find (second.address ())->exclude_until), 2); ASSERT_EQ (3, excluded_peers.add (second, fake_peers_count)); - ASSERT_NEAR (to_seconds (std::chrono::steady_clock::now () + excluded_peers.exclude_time_hours * 3 * 2), to_seconds (peers_by_endpoint.find (second)->exclude_until), 2); + ASSERT_NEAR (to_seconds (std::chrono::steady_clock::now () + excluded_peers.exclude_time_hours * 3 * 2), to_seconds (peers_by_endpoint.find (second.address ())->exclude_until), 2); ASSERT_EQ (max_size, excluded_peers.size ()); // Clear many entries if there are a low number of peers @@ -1094,3 +1094,39 @@ TEST (peer_exclusion, validate) ASSERT_EQ (sizeof (decltype (excluded_peers.peers)::value_type), child_info.sizeof_element); } } + +TEST (network, tcp_no_connect_excluded_peers) +{ + nano::system system (1); + auto node0 (system.nodes[0]); + ASSERT_EQ (0, node0->network.size ()); + auto node1 (std::make_shared (system.io_ctx, nano::get_available_port (), nano::unique_path (), system.alarm, system.logging, system.work)); + node1->start (); + system.nodes.push_back (node1); + auto endpoint1 (node1->network.endpoint ()); + auto endpoint1_tcp (nano::transport::map_endpoint_to_tcp (endpoint1)); + while (!node0->network.excluded_peers.check (endpoint1_tcp)) + { + node0->network.excluded_peers.add (endpoint1_tcp, 1); + } + ASSERT_EQ (0, node0->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_excluded)); + node1->network.merge_peer (node0->network.endpoint ()); + ASSERT_TIMELY (5s, node0->stats.count (nano::stat::type::tcp, nano::stat::detail::tcp_excluded) >= 1); + ASSERT_EQ (nullptr, node0->network.find_channel (endpoint1)); + + // Should not actively reachout to excluded peers + ASSERT_TRUE (node0->network.reachout (endpoint1, true)); + + // Erasing from excluded peers should allow a connection + node0->network.excluded_peers.remove (endpoint1_tcp); + ASSERT_FALSE (node0->network.excluded_peers.check (endpoint1_tcp)); + + // Manually cleanup previous attempt + node1->network.cleanup (std::chrono::steady_clock::now ()); + node1->network.syn_cookies.purge (std::chrono::steady_clock::now ()); + + // Ensure a successful connection + ASSERT_EQ (0, node0->network.size ()); + node1->network.merge_peer (node0->network.endpoint ()); + ASSERT_TIMELY (5s, node0->network.size () == 1); +} diff --git a/nano/core_test/node_telemetry.cpp b/nano/core_test/node_telemetry.cpp index d4eed98945..8a2eab219a 100644 --- a/nano/core_test/node_telemetry.cpp +++ b/nano/core_test/node_telemetry.cpp @@ -718,6 +718,8 @@ TEST (node_telemetry, disable_metrics) } } +namespace nano +{ TEST (node_telemetry, remove_peer_different_genesis) { nano::system system (1); @@ -738,6 +740,10 @@ TEST (node_telemetry, remove_peer_different_genesis) ASSERT_EQ (node0->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::out), 1); ASSERT_EQ (node1->stats.count (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::out), 1); + system.poll_until_true (3s, [&node0, address = node1->network.endpoint ().address ()]() -> bool { + nano::lock_guard guard (node0->network.excluded_peers.mutex); + return node0->network.excluded_peers.peers.get ().count (address); + }); } TEST (node_telemetry, remove_peer_different_genesis_udp) @@ -763,10 +769,12 @@ TEST (node_telemetry, remove_peer_different_genesis_udp) ASSERT_EQ (0, node0->network.size ()); ASSERT_EQ (0, node1->network.size ()); + system.poll_until_true (3s, [&node0, address = node1->network.endpoint ().address ()]() -> bool { + nano::lock_guard guard (node0->network.excluded_peers.mutex); + return node0->network.excluded_peers.peers.get ().count (address); + }); } -namespace nano -{ TEST (node_telemetry, remove_peer_invalid_signature) { nano::system system; @@ -788,5 +796,9 @@ TEST (node_telemetry, remove_peer_invalid_signature) node->network.process_message (telemetry_ack, channel); ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::telemetry, nano::stat::detail::invalid_signature) > 0); + system.poll_until_true (3s, [&node, address = channel->get_endpoint ().address ()]() -> bool { + nano::lock_guard guard (node->network.excluded_peers.mutex); + return node->network.excluded_peers.peers.get ().count (address); + }); +} } -} \ No newline at end of file diff --git a/nano/core_test/toml.cpp b/nano/core_test/toml.cpp index 54367bf784..2f91693941 100644 --- a/nano/core_test/toml.cpp +++ b/nano/core_test/toml.cpp @@ -203,6 +203,7 @@ TEST (toml, daemon_config_deserialize_defaults) ASSERT_EQ (conf.node.logging.network_timeout_logging_value, defaults.node.logging.network_timeout_logging_value); ASSERT_EQ (conf.node.logging.node_lifetime_tracing_value, defaults.node.logging.node_lifetime_tracing_value); ASSERT_EQ (conf.node.logging.network_telemetry_logging_value, defaults.node.logging.network_telemetry_logging_value); + ASSERT_EQ (conf.node.logging.network_rejected_logging_value, defaults.node.logging.network_rejected_logging_value); ASSERT_EQ (conf.node.logging.rotation_size, defaults.node.logging.rotation_size); ASSERT_EQ (conf.node.logging.single_line_record_value, defaults.node.logging.single_line_record_value); ASSERT_EQ (conf.node.logging.stable_log_filename, defaults.node.logging.stable_log_filename); @@ -471,6 +472,7 @@ TEST (toml, daemon_config_deserialize_no_defaults) network_message = true network_node_id_handshake = true network_telemetry_logging = true + network_rejected_logging = true network_packet = true network_publish = true network_timeout = true @@ -610,6 +612,7 @@ TEST (toml, daemon_config_deserialize_no_defaults) ASSERT_NE (conf.node.logging.network_message_logging_value, defaults.node.logging.network_message_logging_value); ASSERT_NE (conf.node.logging.network_node_id_handshake_logging_value, defaults.node.logging.network_node_id_handshake_logging_value); ASSERT_NE (conf.node.logging.network_telemetry_logging_value, defaults.node.logging.network_telemetry_logging_value); + ASSERT_NE (conf.node.logging.network_rejected_logging_value, defaults.node.logging.network_rejected_logging_value); ASSERT_NE (conf.node.logging.network_packet_logging_value, defaults.node.logging.network_packet_logging_value); ASSERT_NE (conf.node.logging.network_publish_logging_value, defaults.node.logging.network_publish_logging_value); ASSERT_NE (conf.node.logging.network_timeout_logging_value, defaults.node.logging.network_timeout_logging_value); diff --git a/nano/lib/stats.cpp b/nano/lib/stats.cpp index c039f42066..c51d597731 100644 --- a/nano/lib/stats.cpp +++ b/nano/lib/stats.cpp @@ -628,6 +628,8 @@ std::string nano::stat::detail_to_string (uint32_t key) case nano::stat::detail::tcp_write_drop: res = "tcp_write_drop"; break; + case nano::stat::detail::tcp_excluded: + res = "tcp_excluded"; case nano::stat::detail::unreachable_host: res = "unreachable_host"; break; diff --git a/nano/lib/stats.hpp b/nano/lib/stats.hpp index a76aef5df1..2dff45ae5e 100644 --- a/nano/lib/stats.hpp +++ b/nano/lib/stats.hpp @@ -294,6 +294,7 @@ class stat final tcp_accept_success, tcp_accept_failure, tcp_write_drop, + tcp_excluded, // ipc invocations, diff --git a/nano/node/bootstrap/bootstrap_attempt.cpp b/nano/node/bootstrap/bootstrap_attempt.cpp index d27ce94fa3..cb2522c89f 100644 --- a/nano/node/bootstrap/bootstrap_attempt.cpp +++ b/nano/node/bootstrap/bootstrap_attempt.cpp @@ -339,6 +339,11 @@ void nano::bootstrap_attempt_legacy::attempt_restart_check (nano::unique_lock= nano::peer_exclusion::score_limit) { node->logger.always_log (boost::str (boost::format ("Adding peer %1% to excluded peers list with score %2% after %3% seconds bootstrap attempt") % endpoint_frontier_request % score % std::chrono::duration_cast (std::chrono::steady_clock::now () - attempt_start).count ())); + auto channel = node->network.find_channel (nano::transport::map_tcp_to_endpoint (endpoint_frontier_request)); + if (channel != nullptr) + { + node->network.erase (*channel); + } } lock_a.unlock (); stop (); diff --git a/nano/node/bootstrap/bootstrap_server.cpp b/nano/node/bootstrap/bootstrap_server.cpp index d4f98f8868..2da045e538 100644 --- a/nano/node/bootstrap/bootstrap_server.cpp +++ b/nano/node/bootstrap/bootstrap_server.cpp @@ -64,12 +64,21 @@ size_t nano::bootstrap_listener::connection_count () void nano::bootstrap_listener::accept_action (boost::system::error_code const & ec, std::shared_ptr socket_a) { - auto connection (std::make_shared (socket_a, node.shared ())); + if (!node.network.excluded_peers.check (socket_a->remote_endpoint ())) { + auto connection (std::make_shared (socket_a, node.shared ())); nano::lock_guard lock (mutex); connections[connection.get ()] = connection; connection->receive (); } + else + { + node.stats.inc (nano::stat::type::tcp, nano::stat::detail::tcp_excluded); + if (node.config.logging.network_rejected_logging ()) + { + node.logger.try_log ("Rejected connection from excluded peer ", socket_a->remote_endpoint ()); + } + } } boost::asio::ip::tcp::endpoint nano::bootstrap_listener::endpoint () diff --git a/nano/node/logging.cpp b/nano/node/logging.cpp index 66bf4e1d23..8934e2028b 100644 --- a/nano/node/logging.cpp +++ b/nano/node/logging.cpp @@ -134,6 +134,7 @@ nano::error nano::logging::serialize_toml (nano::tomlconfig & toml) const toml.put ("network_keepalive", network_keepalive_logging_value, "Log keepalive related messages.\ntype:bool"); toml.put ("network_node_id_handshake", network_node_id_handshake_logging_value, "Log node-id handshake related messages.\ntype:bool"); toml.put ("network_telemetry", network_telemetry_logging_value, "Log telemetry related messages.\ntype:bool"); + toml.put ("network_rejected", network_rejected_logging_value, "Log message when a connection is rejected.\ntype:bool"); toml.put ("node_lifetime_tracing", node_lifetime_tracing_value, "Log node startup and shutdown messages.\ntype:bool"); toml.put ("insufficient_work", insufficient_work_logging_value, "Log if insufficient work is detected.\ntype:bool"); toml.put ("log_ipc", log_ipc_value, "Log IPC related activity.\ntype:bool"); @@ -166,6 +167,7 @@ nano::error nano::logging::deserialize_toml (nano::tomlconfig & toml) toml.get ("network_keepalive", network_keepalive_logging_value); toml.get ("network_node_id_handshake", network_node_id_handshake_logging_value); toml.get ("network_telemetry_logging", network_telemetry_logging_value); + toml.get ("network_rejected_logging", network_rejected_logging_value); toml.get ("node_lifetime_tracing", node_lifetime_tracing_value); toml.get ("insufficient_work", insufficient_work_logging_value); toml.get ("log_ipc", log_ipc_value); @@ -201,6 +203,7 @@ nano::error nano::logging::serialize_json (nano::jsonconfig & json) const json.put ("network_keepalive", network_keepalive_logging_value); json.put ("network_node_id_handshake", network_node_id_handshake_logging_value); json.put ("network_telemetry_logging", network_telemetry_logging_value); + json.put ("network_rejected_logging", network_rejected_logging_value); json.put ("node_lifetime_tracing", node_lifetime_tracing_value); json.put ("insufficient_work", insufficient_work_logging_value); json.put ("log_ipc", log_ipc_value); @@ -358,6 +361,11 @@ bool nano::logging::network_telemetry_logging () const return network_logging () && network_telemetry_logging_value; } +bool nano::logging::network_rejected_logging () const +{ + return network_logging () && network_rejected_logging_value; +} + bool nano::logging::node_lifetime_tracing () const { return node_lifetime_tracing_value; diff --git a/nano/node/logging.hpp b/nano/node/logging.hpp index e42a7aba53..c52290b08c 100644 --- a/nano/node/logging.hpp +++ b/nano/node/logging.hpp @@ -53,6 +53,7 @@ class logging final bool network_keepalive_logging () const; bool network_node_id_handshake_logging () const; bool network_telemetry_logging () const; + bool network_rejected_logging () const; bool node_lifetime_tracing () const; bool insufficient_work_logging () const; bool upnp_details_logging () const; @@ -77,6 +78,7 @@ class logging final bool network_keepalive_logging_value{ false }; bool network_node_id_handshake_logging_value{ false }; bool network_telemetry_logging_value{ false }; + bool network_rejected_logging_value{ false }; bool node_lifetime_tracing_value{ false }; bool insufficient_work_logging_value{ true }; bool log_ipc_value{ true }; diff --git a/nano/node/peer_exclusion.cpp b/nano/node/peer_exclusion.cpp index c43f15625e..2eba6927af 100644 --- a/nano/node/peer_exclusion.cpp +++ b/nano/node/peer_exclusion.cpp @@ -17,11 +17,12 @@ uint64_t nano::peer_exclusion::add (nano::tcp_endpoint const & endpoint_a, size_ } debug_assert (peers.size () <= size_max); auto & peers_by_endpoint (peers.get ()); - auto existing (peers_by_endpoint.find (endpoint_a)); + auto address = endpoint_a.address (); + auto existing (peers_by_endpoint.find (address)); if (existing == peers_by_endpoint.end ()) { // Insert new endpoint - auto inserted (peers.emplace (peer_exclusion::item{ std::chrono::steady_clock::steady_clock::now () + exclude_time_hours, endpoint_a, 1 })); + auto inserted (peers.emplace (peer_exclusion::item{ std::chrono::steady_clock::steady_clock::now () + exclude_time_hours, address, 1 })); (void)inserted; debug_assert (inserted.second); result = 1; @@ -50,7 +51,7 @@ bool nano::peer_exclusion::check (nano::tcp_endpoint const & endpoint_a) bool excluded (false); nano::lock_guard guard (mutex); auto & peers_by_endpoint (peers.get ()); - auto existing (peers_by_endpoint.find (endpoint_a)); + auto existing (peers_by_endpoint.find (endpoint_a.address ())); if (existing != peers_by_endpoint.end () && existing->score >= score_limit) { if (existing->exclude_until > std::chrono::steady_clock::now ()) @@ -68,7 +69,7 @@ bool nano::peer_exclusion::check (nano::tcp_endpoint const & endpoint_a) void nano::peer_exclusion::remove (nano::tcp_endpoint const & endpoint_a) { nano::lock_guard guard (mutex); - peers.get ().erase (endpoint_a); + peers.get ().erase (endpoint_a.address ()); } size_t nano::peer_exclusion::limited_size (size_t const network_peers_count_a) const diff --git a/nano/node/peer_exclusion.hpp b/nano/node/peer_exclusion.hpp index e7013d39d9..97b09d2f0a 100644 --- a/nano/node/peer_exclusion.hpp +++ b/nano/node/peer_exclusion.hpp @@ -16,7 +16,7 @@ class peer_exclusion final public: item () = delete; std::chrono::steady_clock::time_point exclude_until; - nano::tcp_endpoint endpoint; + decltype (std::declval ().address ()) address; uint64_t score; }; @@ -32,7 +32,7 @@ class peer_exclusion final mi::ordered_non_unique, mi::member>, mi::hashed_unique, - mi::member>>>; + mi::member>>>; // clang-format on private: @@ -52,6 +52,9 @@ class peer_exclusion final size_t limited_size (size_t const) const; size_t size () const; + friend class node_telemetry_remove_peer_different_genesis_Test; + friend class node_telemetry_remove_peer_different_genesis_udp_Test; + friend class node_telemetry_remove_peer_invalid_signature_Test; friend class peer_exclusion_validate_Test; }; std::unique_ptr collect_container_info (peer_exclusion const & excluded_peers, const std::string & name); diff --git a/nano/node/telemetry.cpp b/nano/node/telemetry.cpp index 8ed405e8d0..1015557a62 100644 --- a/nano/node/telemetry.cpp +++ b/nano/node/telemetry.cpp @@ -110,6 +110,9 @@ bool nano::telemetry::verify_message (nano::telemetry_ack const & message_a, nan if (remove_channel) { + // Add to peer exclusion list + network.excluded_peers.add (channel_a.get_tcp_endpoint (), network.size ()); + // Disconnect from peer with incorrect telemetry data network.erase (channel_a); } diff --git a/nano/node/transport/tcp.cpp b/nano/node/transport/tcp.cpp index f38343d98f..9837e70341 100644 --- a/nano/node/transport/tcp.cpp +++ b/nano/node/transport/tcp.cpp @@ -275,31 +275,34 @@ void nano::transport::tcp_channels::process_message (nano::message const & messa { node.network.process_message (message_a, channel); } - else if (!node_id_a.is_zero ()) + else if (!node.network.excluded_peers.check (endpoint_a)) { - // Add temporary channel - socket_a->set_writer_concurrency (nano::socket::concurrency::multi_writer); - auto temporary_channel (std::make_shared (node, socket_a)); - debug_assert (endpoint_a == temporary_channel->get_tcp_endpoint ()); - temporary_channel->set_node_id (node_id_a); - temporary_channel->set_network_version (message_a.header.version_using); - temporary_channel->set_last_packet_received (std::chrono::steady_clock::now ()); - temporary_channel->set_last_packet_sent (std::chrono::steady_clock::now ()); - temporary_channel->temporary = true; - debug_assert (type_a == nano::bootstrap_server_type::realtime || type_a == nano::bootstrap_server_type::realtime_response_server); - // Don't insert temporary channels for response_server - if (type_a == nano::bootstrap_server_type::realtime) + if (!node_id_a.is_zero ()) { - insert (temporary_channel, socket_a, nullptr); + // Add temporary channel + socket_a->set_writer_concurrency (nano::socket::concurrency::multi_writer); + auto temporary_channel (std::make_shared (node, socket_a)); + debug_assert (endpoint_a == temporary_channel->get_tcp_endpoint ()); + temporary_channel->set_node_id (node_id_a); + temporary_channel->set_network_version (message_a.header.version_using); + temporary_channel->set_last_packet_received (std::chrono::steady_clock::now ()); + temporary_channel->set_last_packet_sent (std::chrono::steady_clock::now ()); + temporary_channel->temporary = true; + debug_assert (type_a == nano::bootstrap_server_type::realtime || type_a == nano::bootstrap_server_type::realtime_response_server); + // Don't insert temporary channels for response_server + if (type_a == nano::bootstrap_server_type::realtime) + { + insert (temporary_channel, socket_a, nullptr); + } + node.network.process_message (message_a, temporary_channel); + } + else + { + // Initial node_id_handshake request without node ID + debug_assert (message_a.header.type == nano::message_type::node_id_handshake); + debug_assert (type_a == nano::bootstrap_server_type::undefined); + node.stats.inc (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in); } - node.network.process_message (message_a, temporary_channel); - } - else - { - // Initial node_id_handshake request without node ID - debug_assert (message_a.header.type == nano::message_type::node_id_handshake); - debug_assert (type_a == nano::bootstrap_server_type::undefined); - node.stats.inc (nano::stat::type::message, nano::stat::detail::node_id_handshake, nano::stat::dir::in); } } } @@ -367,7 +370,7 @@ bool nano::transport::tcp_channels::reachout (nano::endpoint const & endpoint_a) { auto tcp_endpoint (nano::transport::map_endpoint_to_tcp (endpoint_a)); // Don't overload single IP - bool error = max_ip_connections (tcp_endpoint); + bool error = node.network.excluded_peers.check (tcp_endpoint) || max_ip_connections (tcp_endpoint); if (!error && !node.flags.disable_tcp_realtime) { // Don't keepalive to nodes that already sent us something @@ -440,7 +443,7 @@ void nano::transport::tcp_channels::ongoing_keepalive () for (auto i (0); i <= random_count; ++i) { auto tcp_endpoint (node.network.udp_channels.bootstrap_peer (node.network_params.protocol.protocol_version_min)); - if (tcp_endpoint != invalid_endpoint && find_channel (tcp_endpoint) == nullptr) + if (tcp_endpoint != invalid_endpoint && find_channel (tcp_endpoint) == nullptr && !node.network.excluded_peers.check (tcp_endpoint)) { start_tcp (nano::transport::map_tcp_to_endpoint (tcp_endpoint)); } From 7e51c04e52d62dc0819583e0891d910af45501fa Mon Sep 17 00:00:00 2001 From: Wesley Shillingford Date: Wed, 8 Apr 2020 09:06:22 +0100 Subject: [PATCH 4/5] Incorrect cemented count during conf height algo transition (#2664) * Incorrect cemented count during transition in some circumstances * Better test name * Update test to use new construct after merge --- nano/core_test/confirmation_height.cpp | 14 +++- nano/node/confirmation_height_processor.cpp | 4 +- nano/node/confirmation_height_processor.hpp | 1 + nano/node/confirmation_height_unbounded.hpp | 1 + nano/slow_test/node.cpp | 91 +++++++++++++++++++++ 5 files changed, 109 insertions(+), 2 deletions(-) diff --git a/nano/core_test/confirmation_height.cpp b/nano/core_test/confirmation_height.cpp index 02b6ca642c..7f6b073a15 100644 --- a/nano/core_test/confirmation_height.cpp +++ b/nano/core_test/confirmation_height.cpp @@ -72,6 +72,7 @@ TEST (confirmation_height, single) ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); ASSERT_EQ (1, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (2, node->ledger.cache.cemented_count); ASSERT_EQ (0, node->active.election_winner_details_size ()); } @@ -215,6 +216,7 @@ TEST (confirmation_height, multiple_accounts) ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); ASSERT_EQ (10, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (11, node->ledger.cache.cemented_count); ASSERT_EQ (0, node->active.election_winner_details_size ()); }; @@ -292,6 +294,7 @@ TEST (confirmation_height, gap_bootstrap) ASSERT_EQ (0, node1.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); ASSERT_EQ (0, node1.stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); ASSERT_EQ (0, node1.stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (1, node1.ledger.cache.cemented_count); ASSERT_EQ (0, node1.active.election_winner_details_size ()); }; @@ -378,6 +381,7 @@ TEST (confirmation_height, gap_live) ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); ASSERT_EQ (6, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (7, node->ledger.cache.cemented_count); ASSERT_EQ (0, node->active.election_winner_details_size ()); }; @@ -753,6 +757,7 @@ TEST (confirmation_height, observers) ASSERT_EQ (1, node1->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); ASSERT_EQ (1, node1->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); ASSERT_EQ (1, node1->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (2, node1->ledger.cache.cemented_count); ASSERT_EQ (0, node1->active.election_winner_details_size ()); }; @@ -818,12 +823,14 @@ TEST (confirmation_height, modified_chain) ASSERT_EQ (1, confirmation_height_info.height); ASSERT_EQ (nano::genesis_hash, confirmation_height_info.frontier); ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::invalid_block, nano::stat::dir::in)); + ASSERT_EQ (1, node->ledger.cache.cemented_count); } else { // A non-existent block is cemented, expected given these conditions but is of course incorrect. ASSERT_EQ (2, confirmation_height_info.height); ASSERT_EQ (send->hash (), confirmation_height_info.frontier); + ASSERT_EQ (2, node->ledger.cache.cemented_count); } ASSERT_EQ (0, node->active.election_winner_details_size ()); @@ -871,6 +878,7 @@ TEST (confirmation_height, pending_observer_callbacks) ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (3, node->ledger.cache.cemented_count); ASSERT_EQ (0, node->active.election_winner_details_size ()); }; @@ -1172,7 +1180,7 @@ TEST (confirmation_height, callback_confirmed_history) ASSERT_EQ (1, node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_inactive, nano::stat::dir::out)); ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); - + ASSERT_EQ (3, node->ledger.cache.cemented_count); ASSERT_EQ (0, node->active.election_winner_details_size ()); }; @@ -1233,6 +1241,7 @@ TEST (confirmation_height, dependent_election) ASSERT_EQ (1, node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_inactive, nano::stat::dir::out)); ASSERT_EQ (3, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); ASSERT_EQ (3, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (4, node->ledger.cache.cemented_count); ASSERT_EQ (0, node->active.election_winner_details_size ()); }; @@ -1314,6 +1323,7 @@ TEST (confirmation_height, cemented_gap_below_receive) ASSERT_EQ (9, node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_inactive, nano::stat::dir::out)); ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (11, node->ledger.cache.cemented_count); ASSERT_EQ (0, node->active.election_winner_details_size ()); // Check that the order of callbacks is correct @@ -1406,6 +1416,7 @@ TEST (confirmation_height, cemented_gap_below_no_cache) ASSERT_EQ (5, node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_inactive, nano::stat::dir::out)); ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (7, node->ledger.cache.cemented_count); }; test_mode (nano::confirmation_height_mode::bounded); @@ -1486,6 +1497,7 @@ TEST (confirmation_height, election_winner_details_clearing) ASSERT_EQ (2, node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_active_quorum, nano::stat::dir::out)); ASSERT_EQ (3, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); ASSERT_EQ (3, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in)); + ASSERT_EQ (4, node->ledger.cache.cemented_count); }; test_mode (nano::confirmation_height_mode::bounded); diff --git a/nano/node/confirmation_height_processor.cpp b/nano/node/confirmation_height_processor.cpp index cad2739572..8b9366fc4d 100644 --- a/nano/node/confirmation_height_processor.cpp +++ b/nano/node/confirmation_height_processor.cpp @@ -66,7 +66,9 @@ void nano::confirmation_height_processor::run (confirmation_height_mode mode_a) auto blocks_within_automatic_unbounded_selection = (ledger.cache.block_count < num_blocks_to_use_unbounded || ledger.cache.block_count - num_blocks_to_use_unbounded < ledger.cache.cemented_count); // Don't want to mix up pending writes across different processors - if (mode_a == confirmation_height_mode::unbounded || (mode_a == confirmation_height_mode::automatic && blocks_within_automatic_unbounded_selection && confirmation_height_bounded_processor.pending_empty ())) + auto valid_unbounded = (mode_a == confirmation_height_mode::automatic && blocks_within_automatic_unbounded_selection && confirmation_height_bounded_processor.pending_empty ()); + auto force_unbounded = (!confirmation_height_unbounded_processor.pending_empty () || mode_a == confirmation_height_mode::unbounded); + if (force_unbounded || valid_unbounded) { debug_assert (confirmation_height_bounded_processor.pending_empty ()); if (confirmation_height_unbounded_processor.pending_empty ()) diff --git a/nano/node/confirmation_height_processor.hpp b/nano/node/confirmation_height_processor.hpp index 1763b0fc43..f7fa090e73 100644 --- a/nano/node/confirmation_height_processor.hpp +++ b/nano/node/confirmation_height_processor.hpp @@ -69,6 +69,7 @@ class confirmation_height_processor final friend class confirmation_height_pending_observer_callbacks_Test; friend class confirmation_height_dependent_election_Test; friend class confirmation_height_dependent_election_after_already_cemented_Test; + friend class confirmation_height_dynamic_algorithm_no_transition_while_pending_Test; }; std::unique_ptr collect_container_info (confirmation_height_processor &, const std::string &); diff --git a/nano/node/confirmation_height_unbounded.hpp b/nano/node/confirmation_height_unbounded.hpp index c95059bb3d..a233d1716f 100644 --- a/nano/node/confirmation_height_unbounded.hpp +++ b/nano/node/confirmation_height_unbounded.hpp @@ -100,6 +100,7 @@ class confirmation_height_unbounded final std::function notify_block_already_cemented_observers_callback; std::function awaiting_processing_size_callback; + friend class confirmation_height_dynamic_algorithm_no_transition_while_pending_Test; friend std::unique_ptr collect_container_info (confirmation_height_unbounded &, const std::string & name_a); }; diff --git a/nano/slow_test/node.cpp b/nano/slow_test/node.cpp index 1532306a35..face20e4ad 100644 --- a/nano/slow_test/node.cpp +++ b/nano/slow_test/node.cpp @@ -744,6 +744,97 @@ TEST (confirmation_height, dynamic_algorithm) namespace nano { +/* + * This tests an issue of incorrect cemented block counts during the transition of conf height algorithms + * The scenario was as follows: + * - There is at least 1 pending write entry in the unbounded conf height processor + * - 0 blocks currently awaiting processing in the main conf height processor class + * - A block was confirmed when hit the chain in the pending write above but was not a block higher than it. + * - It must be in `confirmation_height_processor::pause ()` function so that `pause` is set (and the difference between the number + * of blocks uncemented is > unbounded_cutoff so that it hits the bounded processor), the main `run` loop on the conf height processor is iterated. + * + * This cause unbounded pending entries not to be written, and then the bounded processor would write them, causing some inconsistencies. +*/ +TEST (confirmation_height, dynamic_algorithm_no_transition_while_pending) +{ + // Repeat in case of intermittent issues not replicating the issue talked about above. + for (auto _ = 0; _ < 3; ++_) + { + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + + auto latest_genesis = node->latest (nano::test_genesis_key.pub); + std::vector> state_blocks; + auto const num_blocks = nano::confirmation_height::unbounded_cutoff - 2; + + auto add_block_to_genesis_chain = [&](nano::write_transaction & transaction) { + static int num = 0; + auto send (std::make_shared (nano::test_genesis_key.pub, latest_genesis, nano::test_genesis_key.pub, nano::genesis_amount - num - 1, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest_genesis))); + latest_genesis = send->hash (); + state_blocks.push_back (send); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code); + ++num; + }; + + for (auto i = 0; i < num_blocks; ++i) + { + auto transaction = node->store.tx_begin_write (); + add_block_to_genesis_chain (transaction); + } + + { + auto write_guard = node->write_database_queue.wait (nano::writer::testing); + // To limit any data races we are not calling node.block_confirm, + // the relevant code is replicated here (implementation detail): + node->confirmation_height_processor.pause (); + node->confirmation_height_processor.add (state_blocks.back ()->hash ()); + node->confirmation_height_processor.unpause (); + + nano::timer<> timer; + timer.start (); + while (node->confirmation_height_processor.current ().is_zero ()) + { + ASSERT_LT (timer.since_start (), 2s); + } + + // Pausing prevents any writes in the outer while loop in the confirmaiton height processor (implementation detail) + node->confirmation_height_processor.pause (); + + timer.restart (); + while (node->confirmation_height_processor.confirmation_height_unbounded_processor.pending_writes_size == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + + { + // Make it so that the number of blocks exceed the unbounded cutoff would go into the bounded processor (but shouldn't due to unbounded pending writes) + auto transaction = node->store.tx_begin_write (); + add_block_to_genesis_chain (transaction); + add_block_to_genesis_chain (transaction); + } + // Make sure this is at a height lower than the block in the add () call above + node->confirmation_height_processor.add (state_blocks.front ()->hash ()); + node->confirmation_height_processor.unpause (); + } + + system.deadline_set (10s); + while (node->confirmation_height_processor.awaiting_processing_size () != 0 || !node->confirmation_height_processor.current ().is_zero ()) + { + ASSERT_NO_ERROR (system.poll ()); + } + + ASSERT_EQ (node->ledger.cache.cemented_count, num_blocks + 1); + ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in), num_blocks); + ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_bounded, nano::stat::dir::in), 0); + ASSERT_EQ (node->ledger.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_unbounded, nano::stat::dir::in), num_blocks); + ASSERT_EQ (node->active.election_winner_details_size (), 0); + } +} + // Can take up to 1 hour TEST (confirmation_height, prioritize_frontiers_overwrite) { From 544d3295eb83f51d49ea7a224bcb5d271bd2d870 Mon Sep 17 00:00:00 2001 From: Sergey Kroshnin Date: Wed, 8 Apr 2020 20:19:31 +0300 Subject: [PATCH 5/5] Active difficulty normalization (#2691) * use multiplier instead of difficulty in conflict_info as base * normalize multiplier for different epochs & blocks types based on sideband information `normalized = (multiplier + (ratio - 1)) / ratio;` * use adjusted & active multipliers instead of difficulties --- nano/core_test/active_transactions.cpp | 252 +++++++++++-------------- nano/core_test/conflicts.cpp | 39 ++-- nano/core_test/election.cpp | 1 + nano/core_test/node.cpp | 6 +- nano/core_test/vote_processor.cpp | 2 + nano/core_test/wallet.cpp | 53 ++++-- nano/core_test/websocket.cpp | 4 +- nano/lib/work.cpp | 45 +++++ nano/lib/work.hpp | 3 + nano/node/active_transactions.cpp | 134 ++++++++----- nano/node/active_transactions.hpp | 21 ++- nano/node/json_handler.cpp | 10 +- nano/node/wallet.cpp | 4 +- nano/rpc_test/rpc.cpp | 14 +- 14 files changed, 331 insertions(+), 257 deletions(-) diff --git a/nano/core_test/active_transactions.cpp b/nano/core_test/active_transactions.cpp index 249eb94195..abbcf01103 100644 --- a/nano/core_test/active_transactions.cpp +++ b/nano/core_test/active_transactions.cpp @@ -98,7 +98,7 @@ TEST (active_transactions, confirm_frontier) } } -TEST (active_transactions, adjusted_difficulty_priority) +TEST (active_transactions, adjusted_multiplier_priority) { nano::system system; nano::node_config node_config (nano::get_available_port (), system.logging); @@ -124,11 +124,11 @@ TEST (active_transactions, adjusted_difficulty_priority) // Check adjusted difficulty { nano::lock_guard active_guard (node1.active.mutex); - node1.active.update_adjusted_difficulty (); + node1.active.update_adjusted_multiplier (); ASSERT_EQ (node1.active.roots.get<1> ().begin ()->election->status.winner->hash (), send1->hash ()); - ASSERT_LT (node1.active.roots.find (send2->qualified_root ())->adjusted_difficulty, node1.active.roots.find (send1->qualified_root ())->adjusted_difficulty); - ASSERT_LT (node1.active.roots.find (open1->qualified_root ())->adjusted_difficulty, node1.active.roots.find (send1->qualified_root ())->adjusted_difficulty); - ASSERT_LT (node1.active.roots.find (open2->qualified_root ())->adjusted_difficulty, node1.active.roots.find (send2->qualified_root ())->adjusted_difficulty); + ASSERT_LT (node1.active.roots.find (send2->qualified_root ())->adjusted_multiplier, node1.active.roots.find (send1->qualified_root ())->adjusted_multiplier); + ASSERT_LT (node1.active.roots.find (open1->qualified_root ())->adjusted_multiplier, node1.active.roots.find (send1->qualified_root ())->adjusted_multiplier); + ASSERT_LT (node1.active.roots.find (open2->qualified_root ())->adjusted_multiplier, node1.active.roots.find (send2->qualified_root ())->adjusted_multiplier); } // Confirm elections @@ -171,130 +171,20 @@ TEST (active_transactions, adjusted_difficulty_priority) // Check adjusted difficulty nano::lock_guard lock (node1.active.mutex); - node1.active.update_adjusted_difficulty (); - uint64_t last_adjusted (0); + node1.active.update_adjusted_multiplier (); + double last_adjusted (0.0); for (auto i (node1.active.roots.get<1> ().begin ()), n (node1.active.roots.get<1> ().end ()); i != n; ++i) { //first root has nothing to compare - if (last_adjusted != 0) + if (last_adjusted != 0.0) { - ASSERT_LT (i->adjusted_difficulty, last_adjusted); + ASSERT_LE (i->adjusted_multiplier, last_adjusted); } - last_adjusted = i->adjusted_difficulty; - } - ASSERT_LT (node1.active.roots.find (send4->qualified_root ())->adjusted_difficulty, node1.active.roots.find (send3->qualified_root ())->adjusted_difficulty); - ASSERT_LT (node1.active.roots.find (send6->qualified_root ())->adjusted_difficulty, node1.active.roots.find (send5->qualified_root ())->adjusted_difficulty); - ASSERT_LT (node1.active.roots.find (send8->qualified_root ())->adjusted_difficulty, node1.active.roots.find (send7->qualified_root ())->adjusted_difficulty); -} - -TEST (active_transactions, adjusted_difficulty_overflow_max) -{ - nano::system system; - nano::node_config node_config (nano::get_available_port (), system.logging); - node_config.enable_voting = false; - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto & node1 = *system.add_node (node_config); - nano::genesis genesis; - nano::keypair key1, key2; - - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 10 * nano::xrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 20 * nano::xrb_ratio, key2.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); - auto open1 (std::make_shared (key1.pub, 0, key1.pub, 10 * nano::xrb_ratio, send1->hash (), key1.prv, key1.pub, *system.work.generate (key1.pub))); - auto open2 (std::make_shared (key2.pub, 0, key2.pub, 10 * nano::xrb_ratio, send2->hash (), key2.prv, key2.pub, *system.work.generate (key2.pub))); - node1.process_active (send1); // genesis - node1.process_active (send2); // genesis - node1.process_active (open1); // key1 - node1.process_active (open2); // key2 - system.deadline_set (10s); - while (node1.active.size () != 4) - { - ASSERT_NO_ERROR (system.poll ()); - } - - { - nano::lock_guard active_guard (node1.active.mutex); - // Update difficulty to maximum - auto send1_root (node1.active.roots.find (send1->qualified_root ())); - auto send2_root (node1.active.roots.find (send2->qualified_root ())); - auto open1_root (node1.active.roots.find (open1->qualified_root ())); - auto open2_root (node1.active.roots.find (open2->qualified_root ())); - auto modify_difficulty = [& roots = node1.active.roots](auto & existing_root) { - roots.modify (existing_root, [](nano::conflict_info & info_a) { - info_a.difficulty = std::numeric_limits::max (); - }); - }; - modify_difficulty (send1_root); - modify_difficulty (send2_root); - modify_difficulty (open1_root); - modify_difficulty (open2_root); - node1.active.add_adjust_difficulty (send2->hash ()); - node1.active.update_adjusted_difficulty (); - // Test overflow - ASSERT_EQ (node1.active.roots.get<1> ().begin ()->election->status.winner->hash (), send1->hash ()); - ASSERT_EQ (send1_root->adjusted_difficulty, std::numeric_limits::max ()); - ASSERT_LT (send2_root->adjusted_difficulty, send1_root->adjusted_difficulty); - ASSERT_LT (open1_root->adjusted_difficulty, send1_root->adjusted_difficulty); - ASSERT_LT (open2_root->adjusted_difficulty, send2_root->adjusted_difficulty); - } -} - -TEST (active_transactions, adjusted_difficulty_overflow_min) -{ - nano::system system; - nano::node_config node_config (nano::get_available_port (), system.logging); - node_config.enable_voting = false; - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto & node1 = *system.add_node (node_config); - nano::genesis genesis; - nano::keypair key1, key2, key3; - - auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 10 * nano::xrb_ratio, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); - auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 20 * nano::xrb_ratio, key2.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); - auto open1 (std::make_shared (key1.pub, 0, key1.pub, 10 * nano::xrb_ratio, send1->hash (), key1.prv, key1.pub, *system.work.generate (key1.pub))); - auto open2 (std::make_shared (key2.pub, 0, key2.pub, 10 * nano::xrb_ratio, send2->hash (), key2.prv, key2.pub, *system.work.generate (key2.pub))); - auto send3 (std::make_shared (key2.pub, open2->hash (), key2.pub, 9 * nano::xrb_ratio, key3.pub, key2.prv, key2.pub, *system.work.generate (open2->hash ()))); - node1.process_active (send1); // genesis - node1.process_active (send2); // genesis - node1.process_active (open1); // key1 - node1.process_active (open2); // key2 - node1.process_active (send3); // key2 - system.deadline_set (10s); - while (node1.active.size () != 5) - { - ASSERT_NO_ERROR (system.poll ()); - } - - { - nano::lock_guard active_guard (node1.active.mutex); - // Update difficulty to minimum - auto send1_root (node1.active.roots.find (send1->qualified_root ())); - auto send2_root (node1.active.roots.find (send2->qualified_root ())); - auto open1_root (node1.active.roots.find (open1->qualified_root ())); - auto open2_root (node1.active.roots.find (open2->qualified_root ())); - auto send3_root (node1.active.roots.find (send3->qualified_root ())); - auto modify_difficulty = [& roots = node1.active.roots](auto & existing_root) { - roots.modify (existing_root, [](nano::conflict_info & info_a) { - info_a.difficulty = std::numeric_limits::min () + 1; - }); - }; - modify_difficulty (send1_root); - modify_difficulty (send2_root); - modify_difficulty (open1_root); - modify_difficulty (open2_root); - modify_difficulty (send3_root); - node1.active.add_adjust_difficulty (send1->hash ()); - node1.active.update_adjusted_difficulty (); - // Test overflow - ASSERT_EQ (node1.active.roots.get<1> ().begin ()->election->status.winner->hash (), send1->hash ()); - ASSERT_EQ (send1_root->adjusted_difficulty, std::numeric_limits::min () + 3); - ASSERT_LT (send2_root->adjusted_difficulty, send1_root->adjusted_difficulty); - ASSERT_LT (open1_root->adjusted_difficulty, send1_root->adjusted_difficulty); - ASSERT_LT (open2_root->adjusted_difficulty, send2_root->adjusted_difficulty); - ASSERT_LT (send3_root->adjusted_difficulty, open2_root->adjusted_difficulty); - ASSERT_EQ (send3_root->adjusted_difficulty, std::numeric_limits::min ()); - // Clear roots with too low difficulty to prevent issues - node1.active.roots.clear (); + last_adjusted = i->adjusted_multiplier; } + ASSERT_LT (node1.active.roots.find (send4->qualified_root ())->adjusted_multiplier, node1.active.roots.find (send3->qualified_root ())->adjusted_multiplier); + ASSERT_LT (node1.active.roots.find (send6->qualified_root ())->adjusted_multiplier, node1.active.roots.find (send5->qualified_root ())->adjusted_multiplier); + ASSERT_LT (node1.active.roots.find (send8->qualified_root ())->adjusted_multiplier, node1.active.roots.find (send7->qualified_root ())->adjusted_multiplier); } TEST (active_transactions, keep_local) @@ -366,8 +256,8 @@ TEST (active_transactions, prioritize_chains) auto send5 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 20 * nano::xrb_ratio, key2.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); auto send6 (std::make_shared (nano::test_genesis_key.pub, send5->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 30 * nano::xrb_ratio, key3.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send5->hash ()))); auto open2 (std::make_shared (key2.pub, 0, key2.pub, 10 * nano::xrb_ratio, send5->hash (), key2.prv, key2.pub, *system.work.generate (key2.pub, nano::difficulty::from_multiplier (50., node1.network_params.network.publish_thresholds.base)))); - auto difficulty1 (open2->difficulty ()); - auto difficulty2 (send6->difficulty ()); + auto multiplier1 (nano::normalized_multiplier (nano::difficulty::to_multiplier (open2->difficulty (), nano::work_threshold (open2->work_version (), nano::block_details (nano::epoch::epoch_0, false, true, false))), node1.network_params.network.publish_thresholds.epoch_1)); + auto multiplier2 (nano::normalized_multiplier (nano::difficulty::to_multiplier (send6->difficulty (), nano::work_threshold (open2->work_version (), nano::block_details (nano::epoch::epoch_0, true, false, false))), node1.network_params.network.publish_thresholds.epoch_1)); node1.process_active (send1); node1.process_active (open1); @@ -406,11 +296,11 @@ TEST (active_transactions, prioritize_chains) size_t seen (0); { nano::lock_guard active_guard (node1.active.mutex); - node1.active.update_adjusted_difficulty (); + node1.active.update_adjusted_multiplier (); auto it (node1.active.roots.get<1> ().begin ()); while (!node1.active.roots.empty () && it != node1.active.roots.get<1> ().end ()) { - if (it->difficulty == (difficulty1 || difficulty2)) + if (it->multiplier == (multiplier1 || multiplier2)) { seen++; } @@ -587,8 +477,10 @@ TEST (active_transactions, update_difficulty) // Generate blocks & start elections auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 100, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (genesis.hash ()))); auto difficulty1 (send1->difficulty ()); + auto multiplier1 (nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty1, nano::work_threshold (send1->work_version (), nano::block_details (nano::epoch::epoch_0, true, false, false))), node1.network_params.network.publish_thresholds.epoch_1)); auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 200, key1.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); auto difficulty2 (send2->difficulty ()); + auto multiplier2 (nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty2, nano::work_threshold (send2->work_version (), nano::block_details (nano::epoch::epoch_0, true, false, false))), node1.network_params.network.publish_thresholds.epoch_1)); node1.process_active (send1); node1.process_active (send2); node1.block_processor.flush (); @@ -628,11 +520,11 @@ TEST (active_transactions, update_difficulty) ASSERT_NE (existing3, node2.active.roots.end ()); auto const existing4 (node2.active.roots.find (send2->qualified_root ())); ASSERT_NE (existing4, node2.active.roots.end ()); - auto updated1 = existing1->difficulty > difficulty1; - auto updated2 = existing2->difficulty > difficulty2; - auto propogated1 = existing3->difficulty > difficulty1; - auto propogated2 = existing4->difficulty > difficulty2; - done = updated1 && updated2 && propogated1 && propogated2; + auto updated1 = existing1->multiplier > multiplier1; + auto updated2 = existing2->multiplier > multiplier2; + auto propagated1 = existing3->multiplier > multiplier1; + auto propagated2 = existing4->multiplier > multiplier2; + done = updated1 && updated2 && propagated1 && propagated2; } ASSERT_NO_ERROR (system.poll ()); } @@ -789,6 +681,7 @@ TEST (active_transactions, dropped_cleanup) nano::genesis genesis; auto block = genesis.open; + block->sideband_set (nano::block_sideband (nano::genesis_account, 0, nano::genesis_amount, 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false, false, false)); // Add to network filter to ensure proper cleanup after the election is dropped std::vector block_bytes; @@ -877,42 +770,111 @@ TEST (active_transactions, insertion_prioritization) std::vector> blocks{ send1, send2, send3, send4, send5, send6, send7 }; std::sort (blocks.begin (), blocks.end (), [](auto const & blockl, auto const & blockr) { return blockl->difficulty () > blockr->difficulty (); }); - auto update_active_difficulty = [&node] { + auto update_active_multiplier = [&node] { nano::unique_lock lock (node.active.mutex); - node.active.update_active_difficulty (lock); + node.active.update_active_multiplier (lock); }; ASSERT_TRUE (node.active.insert (blocks[2]).election->prioritized ()); - update_active_difficulty (); + update_active_multiplier (); ASSERT_FALSE (node.active.insert (blocks[3]).election->prioritized ()); - update_active_difficulty (); + update_active_multiplier (); ASSERT_TRUE (node.active.insert (blocks[1]).election->prioritized ()); - update_active_difficulty (); + update_active_multiplier (); ASSERT_FALSE (node.active.insert (blocks[4]).election->prioritized ()); - update_active_difficulty (); + update_active_multiplier (); ASSERT_TRUE (node.active.insert (blocks[0]).election->prioritized ()); - update_active_difficulty (); + update_active_multiplier (); ASSERT_FALSE (node.active.insert (blocks[5]).election->prioritized ()); - update_active_difficulty (); + update_active_multiplier (); ASSERT_FALSE (node.active.insert (blocks[6]).election->prioritized ()); } -TEST (active_difficulty, less_than_one) +TEST (active_multiplier, less_than_one) { nano::system system (1); auto & node (*system.nodes[0]); nano::unique_lock lock (node.active.mutex); auto base_active_difficulty = node.network_params.network.publish_thresholds.epoch_1; + auto base_active_multiplier = 1.0; auto min_active_difficulty = node.network_params.network.publish_thresholds.entry; auto min_multiplier = nano::difficulty::to_multiplier (min_active_difficulty, base_active_difficulty); - ASSERT_EQ (node.active.trended_active_difficulty, base_active_difficulty); + ASSERT_EQ (node.active.trended_active_multiplier, base_active_multiplier); for (int i = 0; i < node.active.multipliers_cb.size () - 1; ++i) { node.active.multipliers_cb.push_front (min_multiplier); } auto sum (std::accumulate (node.active.multipliers_cb.begin (), node.active.multipliers_cb.end (), double(0))); - auto difficulty = nano::difficulty::from_multiplier (sum / node.active.multipliers_cb.size (), node.network_params.network.publish_thresholds.epoch_1); + auto multiplier = sum / node.active.multipliers_cb.size (); node.active.multipliers_cb.push_front (min_multiplier); - node.active.update_active_difficulty (lock); - ASSERT_EQ (node.active.trended_active_difficulty, difficulty); + node.active.update_active_multiplier (lock); + ASSERT_EQ (node.active.trended_active_multiplier, multiplier); +} + +TEST (active_multiplier, normalization) +{ + nano::system system (1); + auto & node (*system.nodes[0]); + // Check normalization for epoch 1 + double multiplier1 (1.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier1, node.network_params.network.publish_thresholds.epoch_1), nano::difficulty::from_multiplier (1.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier1 (nano::normalized_multiplier (multiplier1, node.network_params.network.publish_thresholds.epoch_1)); + ASSERT_NEAR (1.0, norm_multiplier1, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier1, node.network_params.network.publish_thresholds.epoch_1), multiplier1, 1e-10); + double multiplier2 (5.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier2, node.network_params.network.publish_thresholds.epoch_1), nano::difficulty::from_multiplier (1.5, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier2 (nano::normalized_multiplier (multiplier2, node.network_params.network.publish_thresholds.epoch_1)); + ASSERT_NEAR (1.5, norm_multiplier2, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier2, node.network_params.network.publish_thresholds.epoch_1), multiplier2, 1e-10); + double multiplier3 (9.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier3, node.network_params.network.publish_thresholds.epoch_1), nano::difficulty::from_multiplier (2.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier3 (nano::normalized_multiplier (multiplier3, node.network_params.network.publish_thresholds.epoch_1)); + ASSERT_NEAR (2.0, norm_multiplier3, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier3, node.network_params.network.publish_thresholds.epoch_1), multiplier3, 1e-10); + double multiplier4 (17.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier4, node.network_params.network.publish_thresholds.epoch_1), nano::difficulty::from_multiplier (3.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier4 (nano::normalized_multiplier (multiplier4, node.network_params.network.publish_thresholds.epoch_1)); + ASSERT_NEAR (3.0, norm_multiplier4, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier4, node.network_params.network.publish_thresholds.epoch_1), multiplier4, 1e-10); + double multiplier5 (25.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier5, node.network_params.network.publish_thresholds.epoch_1), nano::difficulty::from_multiplier (4.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier5 (nano::normalized_multiplier (multiplier5, node.network_params.network.publish_thresholds.epoch_1)); + ASSERT_NEAR (4.0, norm_multiplier5, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier5, node.network_params.network.publish_thresholds.epoch_1), multiplier5, 1e-10); + double multiplier6 (57.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier6, node.network_params.network.publish_thresholds.epoch_1), nano::difficulty::from_multiplier (8.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier6 (nano::normalized_multiplier (multiplier6, node.network_params.network.publish_thresholds.epoch_1)); + ASSERT_NEAR (8.0, norm_multiplier6, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier6, node.network_params.network.publish_thresholds.epoch_1), multiplier6, 1e-10); + // Check normalization for epoch 2 receive + double multiplier10 (1.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier10, node.network_params.network.publish_thresholds.epoch_2_receive), nano::difficulty::from_multiplier (1.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier10 (nano::normalized_multiplier (multiplier10, node.network_params.network.publish_thresholds.epoch_2_receive)); + ASSERT_NEAR (1.0, norm_multiplier10, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier10, node.network_params.network.publish_thresholds.epoch_2_receive), multiplier10, 1e-10); + double multiplier11 (33.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier11, node.network_params.network.publish_thresholds.epoch_2_receive), nano::difficulty::from_multiplier (1.5, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier11 (nano::normalized_multiplier (multiplier11, node.network_params.network.publish_thresholds.epoch_2_receive)); + ASSERT_NEAR (1.5, norm_multiplier11, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier11, node.network_params.network.publish_thresholds.epoch_2_receive), multiplier11, 1e-10); + double multiplier12 (65.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier12, node.network_params.network.publish_thresholds.epoch_2_receive), nano::difficulty::from_multiplier (2.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier12 (nano::normalized_multiplier (multiplier12, node.network_params.network.publish_thresholds.epoch_2_receive)); + ASSERT_NEAR (2.0, norm_multiplier12, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier12, node.network_params.network.publish_thresholds.epoch_2_receive), multiplier12, 1e-10); + double multiplier13 (129.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier13, node.network_params.network.publish_thresholds.epoch_2_receive), nano::difficulty::from_multiplier (3.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier13 (nano::normalized_multiplier (multiplier13, node.network_params.network.publish_thresholds.epoch_2_receive)); + ASSERT_NEAR (3.0, norm_multiplier13, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier13, node.network_params.network.publish_thresholds.epoch_2_receive), multiplier13, 1e-10); + double multiplier14 (193.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier14, node.network_params.network.publish_thresholds.epoch_2_receive), nano::difficulty::from_multiplier (4.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier14 (nano::normalized_multiplier (multiplier14, node.network_params.network.publish_thresholds.epoch_2_receive)); + ASSERT_NEAR (4.0, norm_multiplier14, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier14, node.network_params.network.publish_thresholds.epoch_2_receive), multiplier14, 1e-10); + double multiplier15 (961.0); + ASSERT_LT (nano::difficulty::from_multiplier (multiplier15, node.network_params.network.publish_thresholds.epoch_2_receive), nano::difficulty::from_multiplier (16.0, node.network_params.network.publish_thresholds.epoch_2)); + auto norm_multiplier15 (nano::normalized_multiplier (multiplier15, node.network_params.network.publish_thresholds.epoch_2_receive)); + ASSERT_NEAR (16.0, norm_multiplier15, 1e-10); + ASSERT_NEAR (nano::denormalized_multiplier (norm_multiplier15, node.network_params.network.publish_thresholds.epoch_2_receive), multiplier15, 1e-10); } diff --git a/nano/core_test/conflicts.cpp b/nano/core_test/conflicts.cpp index 082acd51e8..a55b010974 100644 --- a/nano/core_test/conflicts.cpp +++ b/nano/core_test/conflicts.cpp @@ -166,6 +166,7 @@ TEST (conflicts, reprioritize) auto send1 (std::make_shared (genesis.hash (), key1.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*send1); auto difficulty1 (send1->difficulty ()); + auto multiplier1 (nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty1, nano::work_threshold (send1->work_version (), nano::block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */))), node1.network_params.network.publish_thresholds.epoch_1)); nano::send_block send1_copy (*send1); node1.process_active (send1); node1.block_processor.flush (); @@ -173,17 +174,18 @@ TEST (conflicts, reprioritize) nano::lock_guard guard (node1.active.mutex); auto existing1 (node1.active.roots.find (send1->qualified_root ())); ASSERT_NE (node1.active.roots.end (), existing1); - ASSERT_EQ (difficulty1, existing1->difficulty); + ASSERT_EQ (multiplier1, existing1->multiplier); } node1.work_generate_blocking (send1_copy, difficulty1); auto difficulty2 (send1_copy.difficulty ()); + auto multiplier2 (nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty2, nano::work_threshold (send1_copy.work_version (), nano::block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */))), node1.network_params.network.publish_thresholds.epoch_1)); node1.process_active (std::make_shared (send1_copy)); node1.block_processor.flush (); { nano::lock_guard guard (node1.active.mutex); auto existing2 (node1.active.roots.find (send1->qualified_root ())); ASSERT_NE (node1.active.roots.end (), existing2); - ASSERT_EQ (difficulty2, existing2->difficulty); + ASSERT_EQ (multiplier2, existing2->multiplier); } } @@ -214,7 +216,7 @@ TEST (conflicts, dependency) } } -TEST (conflicts, adjusted_difficulty) +TEST (conflicts, adjusted_multiplier) { nano::system system (1); auto & node1 (*system.nodes[0]); @@ -250,35 +252,35 @@ TEST (conflicts, adjusted_difficulty) { ASSERT_NO_ERROR (system.poll ()); } - std::unordered_map adjusted_difficulties; + std::unordered_map adjusted_multipliers; { nano::lock_guard guard (node1.active.mutex); - node1.active.update_adjusted_difficulty (); + node1.active.update_adjusted_multiplier (); ASSERT_EQ (node1.active.roots.get<1> ().begin ()->election->status.winner->hash (), send1->hash ()); for (auto i (node1.active.roots.get<1> ().begin ()), n (node1.active.roots.get<1> ().end ()); i != n; ++i) { - adjusted_difficulties.insert (std::make_pair (i->election->status.winner->hash (), i->adjusted_difficulty)); + adjusted_multipliers.insert (std::make_pair (i->election->status.winner->hash (), i->adjusted_multiplier)); } } // genesis - ASSERT_GT (adjusted_difficulties.find (send1->hash ())->second, adjusted_difficulties.find (send2->hash ())->second); - ASSERT_GT (adjusted_difficulties.find (send2->hash ())->second, adjusted_difficulties.find (receive1->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (send1->hash ())->second, adjusted_multipliers.find (send2->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (send2->hash ())->second, adjusted_multipliers.find (receive1->hash ())->second); // key1 - ASSERT_GT (adjusted_difficulties.find (send1->hash ())->second, adjusted_difficulties.find (open1->hash ())->second); - ASSERT_GT (adjusted_difficulties.find (open1->hash ())->second, adjusted_difficulties.find (send3->hash ())->second); - ASSERT_GT (adjusted_difficulties.find (send3->hash ())->second, adjusted_difficulties.find (send4->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (send1->hash ())->second, adjusted_multipliers.find (open1->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (open1->hash ())->second, adjusted_multipliers.find (send3->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (send3->hash ())->second, adjusted_multipliers.find (send4->hash ())->second); //key2 - ASSERT_GT (adjusted_difficulties.find (send3->hash ())->second, adjusted_difficulties.find (receive2->hash ())->second); - ASSERT_GT (adjusted_difficulties.find (open_epoch1->hash ())->second, adjusted_difficulties.find (receive2->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (send3->hash ())->second, adjusted_multipliers.find (receive2->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (open_epoch1->hash ())->second, adjusted_multipliers.find (receive2->hash ())->second); // key3 - ASSERT_GT (adjusted_difficulties.find (send4->hash ())->second, adjusted_difficulties.find (open2->hash ())->second); - ASSERT_GT (adjusted_difficulties.find (open2->hash ())->second, adjusted_difficulties.find (change1->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (send4->hash ())->second, adjusted_multipliers.find (open2->hash ())->second); + ASSERT_GT (adjusted_multipliers.find (open2->hash ())->second, adjusted_multipliers.find (change1->hash ())->second); // Independent elections can have higher difficulty than adjusted tree nano::keypair key4; auto send5 (std::make_shared (key3.pub, change1->hash (), nano::test_genesis_key.pub, 0, key4.pub, key3.prv, key3.pub, *system.work.generate (change1->hash ()))); // Pending for open epoch block node1.process_active (send5); - auto open_epoch2 (std::make_shared (key4.pub, 0, 0, 0, node1.ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (key4.pub, adjusted_difficulties.find (send1->hash ())->second))); - ASSERT_GT (open_epoch2->difficulty (), adjusted_difficulties.find (send1->hash ())->second); + auto open_epoch2 (std::make_shared (key4.pub, 0, 0, 0, node1.ledger.epoch_link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (key4.pub, nano::difficulty::from_multiplier ((adjusted_multipliers.find (send1->hash ())->second), node1.network_params.network.publish_thresholds.base)))); + ASSERT_GT (open_epoch2->difficulty (), nano::difficulty::from_multiplier ((adjusted_multipliers.find (send1->hash ())->second), node1.network_params.network.publish_thresholds.base)); node1.process_active (open_epoch2); node1.block_processor.flush (); system.deadline_set (3s); @@ -288,7 +290,8 @@ TEST (conflicts, adjusted_difficulty) } { nano::lock_guard guard (node1.active.mutex); - node1.active.update_adjusted_difficulty (); + node1.active.update_adjusted_multiplier (); + ASSERT_EQ (node1.active.roots.size (), 12); ASSERT_EQ (node1.active.roots.get<1> ().begin ()->election->status.winner->hash (), open_epoch2->hash ()); } } diff --git a/nano/core_test/election.cpp b/nano/core_test/election.cpp index 21da4b0e5e..cb5e7cce88 100644 --- a/nano/core_test/election.cpp +++ b/nano/core_test/election.cpp @@ -9,6 +9,7 @@ TEST (election, construction) nano::system system (1); nano::genesis genesis; auto & node = *system.nodes[0]; + genesis.open->sideband_set (nano::block_sideband (nano::genesis_account, 0, nano::genesis_amount, 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false, false, false)); auto election = node.active.insert (genesis.open).election; ASSERT_TRUE (election->idle ()); election->transition_active (); diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index ae8e0c8809..a172b2246a 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -3727,7 +3727,7 @@ TEST (node, aggressive_flooding) ASSERT_EQ (1 + 2 * nodes_wallets.size () + 2, node1.ledger.cache.block_count); } -TEST (active_difficulty, recalculate_work) +TEST (active_multiplier, recalculate_work) { nano::system system; nano::node_config node_config (nano::get_available_port (), system.logging); @@ -3756,9 +3756,9 @@ TEST (active_difficulty, recalculate_work) } node1.work_generate_blocking (*send1); node1.process_active (send1); - node1.active.update_active_difficulty (lock); + node1.active.update_active_multiplier (lock); sum = std::accumulate (node1.active.multipliers_cb.begin (), node1.active.multipliers_cb.end (), double(0)); - ASSERT_EQ (node1.active.trended_active_difficulty, nano::difficulty::from_multiplier (sum / node1.active.multipliers_cb.size (), node1.network_params.network.publish_thresholds.epoch_1)); + ASSERT_EQ (node1.active.trended_active_multiplier, sum / node1.active.multipliers_cb.size ()); lock.unlock (); } diff --git a/nano/core_test/vote_processor.cpp b/nano/core_test/vote_processor.cpp index 45b0f5d2e4..02491fb088 100644 --- a/nano/core_test/vote_processor.cpp +++ b/nano/core_test/vote_processor.cpp @@ -28,6 +28,7 @@ TEST (vote_processor, codes) ASSERT_EQ (nano::vote_code::indeterminate, node.vote_processor.vote_blocking (vote, channel)); // First vote from an account for an ongoing election + genesis.open->sideband_set (nano::block_sideband (nano::genesis_account, 0, nano::genesis_amount, 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false, false, false)); ASSERT_TRUE (node.active.insert (genesis.open).inserted); ASSERT_EQ (nano::vote_code::vote, node.vote_processor.vote_blocking (vote, channel)); @@ -76,6 +77,7 @@ TEST (vote_processor, invalid_signature) vote_invalid->signature.bytes[0] ^= 1; auto channel (std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); + genesis.open->sideband_set (nano::block_sideband (nano::genesis_account, 0, nano::genesis_amount, 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false, false, false)); auto election (node.active.insert (genesis.open)); ASSERT_TRUE (election.election && election.inserted); ASSERT_EQ (1, election.election->last_votes.size ()); diff --git a/nano/core_test/wallet.cpp b/nano/core_test/wallet.cpp index 3cad3d5888..254b869565 100644 --- a/nano/core_test/wallet.cpp +++ b/nano/core_test/wallet.cpp @@ -1173,15 +1173,17 @@ TEST (wallet, work_watcher_update) nano::keypair key; auto const block1 (wallet.send_action (nano::test_genesis_key.pub, key.pub, 100)); auto difficulty1 (block1->difficulty ()); + auto multiplier1 (nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty1, nano::work_threshold (block1->work_version (), nano::block_details (nano::epoch::epoch_0, true, false, false))), node.network_params.network.publish_thresholds.epoch_1)); auto const block2 (wallet.send_action (nano::test_genesis_key.pub, key.pub, 200)); auto difficulty2 (block2->difficulty ()); - uint64_t updated_difficulty1{ difficulty1 }, updated_difficulty2{ difficulty2 }; + auto multiplier2 (nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty2, nano::work_threshold (block2->work_version (), nano::block_details (nano::epoch::epoch_0, true, false, false))), node.network_params.network.publish_thresholds.epoch_1)); + double updated_multiplier1{ multiplier1 }, updated_multiplier2{ multiplier2 }, target_multiplier{ std::max (multiplier1, multiplier2) + 1e-6 }; { nano::lock_guard guard (node.active.mutex); - node.active.trended_active_difficulty = std::max (difficulty1, difficulty2) + 1; + node.active.trended_active_multiplier = target_multiplier; } system.deadline_set (20s); - while (updated_difficulty1 == difficulty1 || updated_difficulty2 == difficulty2) + while (updated_multiplier1 == multiplier1 || updated_multiplier2 == multiplier2) { { nano::lock_guard guard (node.active.mutex); @@ -1189,19 +1191,19 @@ TEST (wallet, work_watcher_update) auto const existing (node.active.roots.find (block1->qualified_root ())); //if existing is junk the block has been confirmed already ASSERT_NE (existing, node.active.roots.end ()); - updated_difficulty1 = existing->difficulty; + updated_multiplier1 = existing->multiplier; } { auto const existing (node.active.roots.find (block2->qualified_root ())); //if existing is junk the block has been confirmed already ASSERT_NE (existing, node.active.roots.end ()); - updated_difficulty2 = existing->difficulty; + updated_multiplier2 = existing->multiplier; } } ASSERT_NO_ERROR (system.poll ()); } - ASSERT_GT (updated_difficulty1, difficulty1); - ASSERT_GT (updated_difficulty2, difficulty2); + ASSERT_GT (updated_multiplier1, multiplier1); + ASSERT_GT (updated_multiplier2, multiplier2); } TEST (wallet, work_watcher_generation_disabled) @@ -1220,8 +1222,8 @@ TEST (wallet, work_watcher_generation_disabled) node.wallets.watcher->add (block); ASSERT_FALSE (node.process_local (block).code != nano::process_result::progress); ASSERT_TRUE (node.wallets.watcher->is_watched (block->qualified_root ())); - auto multiplier = nano::difficulty::to_multiplier (difficulty, nano::work_threshold_base (block->work_version ())); - uint64_t updated_difficulty{ difficulty }; + auto multiplier = nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty, nano::work_threshold (block->work_version (), nano::block_details (nano::epoch::epoch_0, true, false, false))), node.network_params.network.publish_thresholds.epoch_1); + double updated_multiplier{ multiplier }; { nano::unique_lock lock (node.active.mutex); // Prevent active difficulty repopulating multipliers @@ -1231,7 +1233,7 @@ TEST (wallet, work_watcher_generation_disabled) { node.active.multipliers_cb.push_back (multiplier * (1.5 + i / 100.)); } - node.active.update_active_difficulty (lock); + node.active.update_active_multiplier (lock); } std::this_thread::sleep_for (5s); @@ -1240,9 +1242,9 @@ TEST (wallet, work_watcher_generation_disabled) auto const existing (node.active.roots.find (block->qualified_root ())); //if existing is junk the block has been confirmed already ASSERT_NE (existing, node.active.roots.end ()); - updated_difficulty = existing->difficulty; + updated_multiplier = existing->multiplier; } - ASSERT_EQ (updated_difficulty, difficulty); + ASSERT_EQ (updated_multiplier, multiplier); ASSERT_TRUE (node.distributed_work.items.empty ()); } @@ -1291,7 +1293,7 @@ TEST (wallet, work_watcher_cancel) { node.active.multipliers_cb.push_back (node.config.max_work_generate_multiplier); } - node.active.update_active_difficulty (lock); + node.active.update_active_multiplier (lock); } // Wait for work generation to start system.deadline_set (5s); @@ -1322,6 +1324,7 @@ TEST (wallet, work_watcher_cancel) TEST (wallet, limited_difficulty) { nano::system system; + nano::genesis genesis; nano::node_config node_config (nano::get_available_port (), system.logging); node_config.max_work_generate_difficulty = nano::network_constants ().publish_thresholds.base; nano::node_flags node_flags; @@ -1336,9 +1339,9 @@ TEST (wallet, limited_difficulty) { // Force active difficulty to an impossibly high value nano::lock_guard guard (node.active.mutex); - node.active.trended_active_difficulty = std::numeric_limits::max (); + node.active.trended_active_multiplier = 1024 * 1024 * 1024; } - ASSERT_EQ (node_config.max_work_generate_difficulty, node.active.limited_active_difficulty ()); + ASSERT_EQ (node_config.max_work_generate_difficulty, node.active.limited_active_difficulty (*genesis.open)); auto send = wallet.send_action (nano::test_genesis_key.pub, nano::keypair ().pub, 1, 1); ASSERT_NE (nullptr, send); } @@ -1386,8 +1389,10 @@ TEST (wallet, epoch_2_receive_propagation) auto const max_tries = 20; while (++tries < max_tries) { - nano::system system (1); - auto & node (*system.nodes[0]); + nano::system system; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node (*system.add_node (node_flags)); auto & wallet (*system.wallet (0)); // Upgrade the genesis account to epoch 1 @@ -1414,6 +1419,10 @@ TEST (wallet, epoch_2_receive_propagation) ASSERT_NE (nullptr, send2); // Receiving should use the lower difficulty + { + nano::lock_guard guard (node.active.mutex); + node.active.trended_active_multiplier = 1.0; + } auto receive2 = wallet.receive_action (*send2, key.pub, amount, 1); ASSERT_NE (nullptr, receive2); if (receive2->difficulty () < node.network_params.network.publish_thresholds.base) @@ -1434,8 +1443,10 @@ TEST (wallet, epoch_2_receive_unopened) auto const max_tries = 20; while (++tries < max_tries) { - nano::system system (1); - auto & node (*system.nodes[0]); + nano::system system; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node (*system.add_node (node_flags)); auto & wallet (*system.wallet (0)); // Upgrade the genesis account to epoch 1 @@ -1457,6 +1468,10 @@ TEST (wallet, epoch_2_receive_unopened) wallet.insert_adhoc (key.prv, false); // Receiving should use the lower difficulty + { + nano::lock_guard guard (node.active.mutex); + node.active.trended_active_multiplier = 1.0; + } auto receive1 = wallet.receive_action (*send1, key.pub, amount, 1); ASSERT_NE (nullptr, receive1); if (receive1->difficulty () < node.network_params.network.publish_thresholds.base) diff --git a/nano/core_test/websocket.cpp b/nano/core_test/websocket.cpp index 10d894e094..b4b0440e31 100644 --- a/nano/core_test/websocket.cpp +++ b/nano/core_test/websocket.cpp @@ -52,7 +52,7 @@ TEST (websocket, subscription_edge) } } -// Test client subscribing to changes in active_difficulty +// Test client subscribing to changes in active_multiplier TEST (websocket, active_difficulty) { nano::system system; @@ -80,7 +80,7 @@ TEST (websocket, active_difficulty) ASSERT_NO_ERROR (system.poll ()); } - // Fake history records to force trended_active_difficulty change + // Fake history records to force trended_active_multiplier change { nano::unique_lock lock (node1->active.mutex); node1->active.multipliers_cb.push_front (10.); diff --git a/nano/lib/work.cpp b/nano/lib/work.cpp index 2efb70ae47..8ef291a9db 100644 --- a/nano/lib/work.cpp +++ b/nano/lib/work.cpp @@ -145,6 +145,51 @@ uint64_t nano::work_v1::value (nano::root const & root_a, uint64_t work_a) } #endif +double nano::normalized_multiplier (double const multiplier_a, uint64_t const threshold_a) +{ + static nano::network_constants network_constants; + debug_assert (multiplier_a >= 1); + auto multiplier (multiplier_a); + /* Normalization rules + ratio = multiplier of max work threshold (send epoch 2) from given threshold + i.e. max = 0xfe00000000000000, given = 0xf000000000000000, ratio = 8.0 + normalized = (multiplier + (ratio - 1)) / ratio; + Epoch 1 + multiplier | normalized + 1.0 | 1.0 + 9.0 | 2.0 + 25.0 | 4.0 + Epoch 2 (receive / epoch subtypes) + multiplier | normalized + 1.0 | 1.0 + 65.0 | 2.0 + 241.0 | 4.0 + */ + if (threshold_a == network_constants.publish_thresholds.epoch_1 || threshold_a == network_constants.publish_thresholds.epoch_2_receive) + { + auto ratio (nano::difficulty::to_multiplier (network_constants.publish_thresholds.epoch_2, threshold_a)); + debug_assert (ratio >= 1); + multiplier = (multiplier + (ratio - 1.0)) / ratio; + debug_assert (multiplier >= 1); + } + return multiplier; +} + +double nano::denormalized_multiplier (double const multiplier_a, uint64_t const threshold_a) +{ + static nano::network_constants network_constants; + debug_assert (multiplier_a >= 1); + auto multiplier (multiplier_a); + if (threshold_a == network_constants.publish_thresholds.epoch_1 || threshold_a == network_constants.publish_thresholds.epoch_2_receive) + { + auto ratio (nano::difficulty::to_multiplier (network_constants.publish_thresholds.epoch_2, threshold_a)); + debug_assert (ratio >= 1); + multiplier = multiplier * ratio + 1.0 - ratio; + debug_assert (multiplier >= 1); + } + return multiplier; +} + nano::work_pool::work_pool (unsigned max_threads_a, std::chrono::nanoseconds pow_rate_limiter_a, std::function (nano::work_version const, nano::root const &, uint64_t, std::atomic &)> opencl_a) : ticket (0), done (false), diff --git a/nano/lib/work.hpp b/nano/lib/work.hpp index eec1b62769..f65261cfa3 100644 --- a/nano/lib/work.hpp +++ b/nano/lib/work.hpp @@ -39,6 +39,9 @@ namespace work_v1 uint64_t threshold_entry (); uint64_t threshold (nano::block_details const); } + +double normalized_multiplier (double const, uint64_t const); +double denormalized_multiplier (double const, uint64_t const); class opencl_work; class work_item final { diff --git a/nano/node/active_transactions.cpp b/nano/node/active_transactions.cpp index 8245225030..cfde67b672 100644 --- a/nano/node/active_transactions.cpp +++ b/nano/node/active_transactions.cpp @@ -19,7 +19,7 @@ confirmation_height_processor (confirmation_height_processor_a), generator (node_a.config, node_a.store, node_a.wallets, node_a.vote_processor, node_a.votes_cache, node_a.network), node (node_a), multipliers_cb (20, 1.), -trended_active_difficulty (node_a.network_params.network.publish_thresholds.epoch_1), +trended_active_multiplier (1.0), check_all_elections_period (node_a.network_params.network.is_test_network () ? 10ms : 5s), election_time_to_live (node_a.network_params.network.is_test_network () ? 0s : 2s), prioritized_cutoff (std::max (1, node_a.config.active_elections_size / 10)), @@ -300,10 +300,10 @@ void nano::active_transactions::request_loop () // Account for the time spent in request_confirm by defining the wakeup point beforehand const auto wakeup_l (std::chrono::steady_clock::now () + std::chrono::milliseconds (node.network_params.network.request_interval_ms)); - update_adjusted_difficulty (); - // frontiers_confirmation should be above update_active_difficulty to ensure new sorted roots are updated + update_adjusted_multiplier (); + // frontiers_confirmation should be above update_active_multiplier to ensure new sorted roots are updated frontiers_confirmation (lock); - update_active_difficulty (lock); + update_active_multiplier (lock); request_confirm (lock); // Sleep until all broadcasts are done, plus the remaining loop time @@ -504,9 +504,10 @@ nano::election_insertion_result nano::active_transactions::insert_impl (std::sha result.inserted = true; auto hash (block_a->hash ()); auto difficulty (block_a->difficulty ()); - bool prioritized = roots.size () < prioritized_cutoff || difficulty > last_prioritized_difficulty.value_or (0); + double multiplier (normalized_multiplier (*block_a)); + bool prioritized = roots.size () < prioritized_cutoff || multiplier > last_prioritized_multiplier.value_or (0); result.election = nano::make_shared (node, block_a, confirmation_action_a, prioritized); - roots.get ().emplace (nano::conflict_info{ root, difficulty, difficulty, result.election }); + roots.get ().emplace (nano::conflict_info{ root, multiplier, multiplier, result.election }); blocks.emplace (hash, result.election); add_adjust_difficulty (hash); result.election->insert_inactive_votes_cache (hash); @@ -630,15 +631,15 @@ void nano::active_transactions::update_difficulty (std::shared_ptr auto existing_election (roots.get ().find (block_a->qualified_root ())); if (existing_election != roots.get ().end ()) { - auto difficulty (block_a->difficulty ()); - if (difficulty > existing_election->difficulty) + double multiplier (normalized_multiplier (*block_a, existing_election->election->blocks)); + if (multiplier > existing_election->multiplier) { if (node.config.logging.active_update_logging ()) { - node.logger.try_log (boost::str (boost::format ("Block %1% was updated from difficulty %2% to %3%") % block_a->hash ().to_string () % nano::to_string_hex (existing_election->difficulty) % nano::to_string_hex (difficulty))); + node.logger.try_log (boost::str (boost::format ("Block %1% was updated from multiplier %2% to %3%") % block_a->hash ().to_string () % existing_election->multiplier % multiplier)); } - roots.get ().modify (existing_election, [difficulty](nano::conflict_info & info_a) { - info_a.difficulty = difficulty; + roots.get ().modify (existing_election, [multiplier](nano::conflict_info & info_a) { + info_a.multiplier = multiplier; }); existing_election->election->publish (block_a); add_adjust_difficulty (block_a->hash ()); @@ -646,13 +647,44 @@ void nano::active_transactions::update_difficulty (std::shared_ptr } } +double nano::active_transactions::normalized_multiplier (nano::block const & block_a, std::unordered_map> const & blocks_a) +{ + auto difficulty (block_a.difficulty ()); + uint64_t threshold (0); + bool sideband_not_found (false); + if (block_a.has_sideband ()) + { + threshold = nano::work_threshold (block_a.work_version (), block_a.sideband ().details); + } + else + { + auto find_block (blocks_a.find (block_a.hash ())); + if (find_block != blocks_a.end () && find_block->second->has_sideband ()) + { + threshold = nano::work_threshold (block_a.work_version (), find_block->second->sideband ().details); + } + else + { + threshold = nano::work_threshold_base (block_a.work_version ()); + sideband_not_found = true; + } + } + double multiplier (nano::difficulty::to_multiplier (difficulty, threshold)); + debug_assert (multiplier >= 1 || sideband_not_found); + if (multiplier >= 1) + { + multiplier = nano::normalized_multiplier (multiplier, threshold); + } + return multiplier; +} + void nano::active_transactions::add_adjust_difficulty (nano::block_hash const & hash_a) { debug_assert (!mutex.try_lock ()); adjust_difficulty_list.push_back (hash_a); } -void nano::active_transactions::update_adjusted_difficulty () +void nano::active_transactions::update_adjusted_multiplier () { debug_assert (!mutex.try_lock ()); std::unordered_set processed_blocks; @@ -700,7 +732,7 @@ void nano::active_transactions::update_adjusted_difficulty () auto existing_root (roots.get ().find (root)); if (existing_root != roots.get ().end ()) { - sum += nano::difficulty::to_multiplier (existing_root->difficulty, node.network_params.network.publish_thresholds.epoch_1); + sum += existing_root->multiplier; elections_list.emplace_back (root, level); if (level > highest_level) { @@ -717,32 +749,19 @@ void nano::active_transactions::update_adjusted_difficulty () } if (!elections_list.empty ()) { - double multiplier = sum / elections_list.size (); - uint64_t average = nano::difficulty::from_multiplier (multiplier, node.network_params.network.publish_thresholds.epoch_1); - // Prevent overflow - int64_t limiter (0); - if (std::numeric_limits::max () - average < static_cast (highest_level)) - { - // Highest adjusted difficulty value should be std::numeric_limits::max () - limiter = std::numeric_limits::max () - average + highest_level; - debug_assert (std::numeric_limits::max () == average + highest_level - limiter); - } - else if (average < std::numeric_limits::min () - lowest_level) - { - // Lowest adjusted difficulty value should be std::numeric_limits::min () - limiter = std::numeric_limits::min () - average + lowest_level; - debug_assert (std::numeric_limits::min () == average + lowest_level - limiter); - } + double avg_multiplier = sum / elections_list.size (); + double min_unit = 32.0 * avg_multiplier * std::numeric_limits::epsilon (); + debug_assert (min_unit > 0); - // Set adjusted difficulty + // Set adjusted multiplier for (auto & item : elections_list) { auto existing_root (roots.get ().find (item.first)); - uint64_t difficulty_a = average + item.second - limiter; - if (existing_root->adjusted_difficulty != difficulty_a) + double multiplier_a = avg_multiplier + (double)item.second * min_unit; + if (existing_root->adjusted_multiplier != multiplier_a) { - roots.get ().modify (existing_root, [difficulty_a](nano::conflict_info & info_a) { - info_a.adjusted_difficulty = difficulty_a; + roots.get ().modify (existing_root, [multiplier_a](nano::conflict_info & info_a) { + info_a.adjusted_multiplier = multiplier_a; }); } } @@ -750,52 +769,73 @@ void nano::active_transactions::update_adjusted_difficulty () } } -void nano::active_transactions::update_active_difficulty (nano::unique_lock & lock_a) +void nano::active_transactions::update_active_multiplier (nano::unique_lock & lock_a) { debug_assert (!mutex.try_lock ()); - last_prioritized_difficulty.reset (); + last_prioritized_multiplier.reset (); double multiplier (1.); // Heurestic to filter out non-saturated network and frontier confirmation if (roots.size () > prioritized_cutoff / 2 || (node.network_params.network.is_test_network () && !roots.empty ())) { auto & sorted_roots = roots.get (); - std::vector prioritized; + std::vector prioritized; prioritized.reserve (std::min (sorted_roots.size (), prioritized_cutoff)); for (auto it (sorted_roots.begin ()), end (sorted_roots.end ()); it != end && prioritized.size () < prioritized_cutoff; ++it) { if (!it->election->confirmed ()) { - prioritized.push_back (it->adjusted_difficulty); + prioritized.push_back (it->adjusted_multiplier); } } if (prioritized.size () > 10 || (node.network_params.network.is_test_network () && !prioritized.empty ())) { - multiplier = nano::difficulty::to_multiplier (prioritized[prioritized.size () / 2], node.network_params.network.publish_thresholds.epoch_1); + multiplier = prioritized[prioritized.size () / 2]; } if (!prioritized.empty ()) { - last_prioritized_difficulty = prioritized.back (); + last_prioritized_multiplier = prioritized.back (); } } debug_assert (multiplier >= nano::difficulty::to_multiplier (node.network_params.network.publish_thresholds.entry, node.network_params.network.publish_thresholds.epoch_1)); multipliers_cb.push_front (multiplier); auto sum (std::accumulate (multipliers_cb.begin (), multipliers_cb.end (), double(0))); - auto difficulty = nano::difficulty::from_multiplier (sum / multipliers_cb.size (), node.network_params.network.publish_thresholds.epoch_1); + double avg_multiplier (sum / multipliers_cb.size ()); + auto difficulty = nano::difficulty::from_multiplier (avg_multiplier, node.default_difficulty (nano::work_version::work_1)); debug_assert (difficulty >= node.network_params.network.publish_thresholds.entry); - trended_active_difficulty = difficulty; - node.observers.difficulty.notify (trended_active_difficulty); + trended_active_multiplier = avg_multiplier; + node.observers.difficulty.notify (difficulty); } uint64_t nano::active_transactions::active_difficulty () { - nano::lock_guard lock (mutex); - return trended_active_difficulty; + return nano::difficulty::from_multiplier (active_multiplier (), node.default_difficulty (nano::work_version::work_1)); +} + +uint64_t nano::active_transactions::limited_active_difficulty (nano::block const & block_a) +{ + uint64_t threshold (0); + if (block_a.has_sideband ()) + { + threshold = nano::work_threshold (block_a.work_version (), block_a.sideband ().details); + } + else + { + threshold = node.default_difficulty (block_a.work_version ()); + } + return limited_active_difficulty (threshold); } -uint64_t nano::active_transactions::limited_active_difficulty () +uint64_t nano::active_transactions::limited_active_difficulty (uint64_t const threshold_a) { - return std::min (active_difficulty (), node.config.max_work_generate_difficulty); + auto difficulty (nano::difficulty::from_multiplier (nano::denormalized_multiplier (active_multiplier (), threshold_a), threshold_a)); + return std::min (difficulty, node.config.max_work_generate_difficulty); +} + +double nano::active_transactions::active_multiplier () +{ + nano::lock_guard lock (mutex); + return trended_active_multiplier; } // List of active blocks in elections diff --git a/nano/node/active_transactions.hpp b/nano/node/active_transactions.hpp index 5c55c469c0..26d5d57aa3 100644 --- a/nano/node/active_transactions.hpp +++ b/nano/node/active_transactions.hpp @@ -35,8 +35,8 @@ class conflict_info final { public: nano::qualified_root root; - uint64_t difficulty; - uint64_t adjusted_difficulty; + double multiplier; + double adjusted_multiplier; std::shared_ptr election; }; @@ -103,11 +103,14 @@ class active_transactions final bool active (nano::qualified_root const &); std::shared_ptr election (nano::qualified_root const &) const; void update_difficulty (std::shared_ptr); + double normalized_multiplier (nano::block const &, std::unordered_map> const & = {}); void add_adjust_difficulty (nano::block_hash const &); - void update_adjusted_difficulty (); - void update_active_difficulty (nano::unique_lock &); + void update_adjusted_multiplier (); + void update_active_multiplier (nano::unique_lock &); uint64_t active_difficulty (); - uint64_t limited_active_difficulty (); + uint64_t limited_active_difficulty (nano::block const &); + uint64_t limited_active_difficulty (uint64_t const); + double active_multiplier (); std::deque> list_blocks (); void erase (nano::block const &); bool empty (); @@ -123,11 +126,11 @@ class active_transactions final mi::hashed_unique, mi::member>, mi::ordered_non_unique, - mi::member, - std::greater>>> + mi::member, + std::greater>>> roots; // clang-format on - boost::optional last_prioritized_difficulty{ boost::none }; + boost::optional last_prioritized_multiplier{ boost::none }; std::unordered_map> blocks; std::deque list_recently_cemented (); std::deque recently_cemented; @@ -141,7 +144,7 @@ class active_transactions final nano::node & node; mutable std::mutex mutex; boost::circular_buffer multipliers_cb; - uint64_t trended_active_difficulty; + double trended_active_multiplier; size_t priority_cementable_frontiers_size (); size_t priority_wallet_cementable_frontiers_size (); boost::circular_buffer difficulty_trend (); diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index 4b89e6666b..42b5c325dd 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -923,11 +923,11 @@ void nano::json_handler::accounts_pending () void nano::json_handler::active_difficulty () { auto include_trend (request.get ("include_trend", false)); - response_l.put ("network_minimum", nano::to_string_hex (node.network_params.network.publish_thresholds.epoch_1)); - auto difficulty_active = node.active.active_difficulty (); - response_l.put ("network_current", nano::to_string_hex (difficulty_active)); - auto multiplier = nano::difficulty::to_multiplier (difficulty_active, node.network_params.network.publish_thresholds.epoch_1); - response_l.put ("multiplier", nano::to_string (multiplier)); + auto multiplier_active = node.active.active_multiplier (); + auto default_difficulty (node.default_difficulty (nano::work_version::work_1)); + response_l.put ("network_minimum", nano::to_string_hex (default_difficulty)); + response_l.put ("network_current", nano::to_string_hex (nano::difficulty::from_multiplier (multiplier_active, default_difficulty))); + response_l.put ("multiplier", multiplier_active); if (include_trend) { boost::property_tree::ptree trend_entry_l; diff --git a/nano/node/wallet.cpp b/nano/node/wallet.cpp index 8cc6c4c578..b41c4d56fa 100644 --- a/nano/node/wallet.cpp +++ b/nano/node/wallet.cpp @@ -1153,7 +1153,7 @@ bool nano::wallet::action_complete (std::shared_ptr const & block_a { wallets.node.logger.try_log (boost::str (boost::format ("Cached or provided work for block %1% account %2% is invalid, regenerating") % block_a->hash ().to_string () % account_a.to_account ())); debug_assert (required_difficulty <= wallets.node.config.max_work_generate_difficulty); - auto target_difficulty = std::max (required_difficulty, wallets.node.active.limited_active_difficulty ()); + auto target_difficulty = std::max (required_difficulty, wallets.node.active.limited_active_difficulty (required_difficulty)); error = !wallets.node.work_generate_blocking (*block_a, target_difficulty).is_initialized (); } if (!error) @@ -1466,7 +1466,7 @@ void nano::work_watcher::watching (nano::qualified_root const & root_a, std::sha if (watcher_l->watched.find (root_a) != watcher_l->watched.end ()) // not yet confirmed or cancelled { lock.unlock (); - auto active_difficulty (watcher_l->node.active.limited_active_difficulty ()); + auto active_difficulty (watcher_l->node.active.limited_active_difficulty (*block_a)); /* * Work watcher should still watch blocks even without work generation, although no rework is done * Functionality may be added in the future that does not require updating work diff --git a/nano/rpc_test/rpc.cpp b/nano/rpc_test/rpc.cpp index 7e92190ec8..7f89a53106 100644 --- a/nano/rpc_test/rpc.cpp +++ b/nano/rpc_test/rpc.cpp @@ -1861,7 +1861,7 @@ TEST (rpc, process_block_with_work_watcher) auto latest (node1.latest (nano::test_genesis_key.pub)); auto send (std::make_shared (nano::test_genesis_key.pub, latest, nano::test_genesis_key.pub, nano::genesis_amount - 100, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest))); auto difficulty1 (send->difficulty ()); - auto multiplier1 = nano::difficulty::to_multiplier (difficulty1, node1.active.active_difficulty ()); + auto multiplier1 = nano::normalized_multiplier (nano::difficulty::to_multiplier (difficulty1, node1.network_params.network.publish_thresholds.epoch_1), node1.network_params.network.publish_thresholds.epoch_1); nano::node_rpc_config node_rpc_config; nano::ipc::ipc_server ipc_server (node1, node_rpc_config); nano::rpc_config rpc_config (nano::get_available_port (), true); @@ -1889,7 +1889,7 @@ TEST (rpc, process_block_with_work_watcher) } system.deadline_set (10s); auto updated (false); - uint64_t updated_difficulty; + double updated_multiplier; while (!updated) { nano::unique_lock lock (node1.active.mutex); @@ -1898,16 +1898,16 @@ TEST (rpc, process_block_with_work_watcher) { node1.active.multipliers_cb.push_back (multiplier1 * (1 + i / 100.)); } - node1.active.update_active_difficulty (lock); + node1.active.update_active_multiplier (lock); auto const existing (node1.active.roots.find (send->qualified_root ())); //if existing is junk the block has been confirmed already ASSERT_NE (existing, node1.active.roots.end ()); - updated = existing->difficulty != difficulty1; - updated_difficulty = existing->difficulty; + updated = existing->multiplier != multiplier1; + updated_multiplier = existing->multiplier; lock.unlock (); ASSERT_NO_ERROR (system.poll ()); } - ASSERT_GT (updated_difficulty, difficulty1); + ASSERT_GT (updated_multiplier, multiplier1); // Try without enable_control which watch_work requires if set to true { @@ -7507,7 +7507,7 @@ TEST (rpc, active_difficulty) node->active.multipliers_cb.push_front (1.5); node->active.multipliers_cb.push_front (4.2); // Also pushes 1.0 to the front of multipliers_cb - node->active.update_active_difficulty (lock); + node->active.update_active_multiplier (lock); lock.unlock (); auto trend_size (node->active.multipliers_cb.size ()); ASSERT_NE (0, trend_size);