From 5dac531c911b0e19e2c615397fa38e94a9b34ecd Mon Sep 17 00:00:00 2001 From: clemahieu Date: Mon, 2 Mar 2020 10:36:13 +0100 Subject: [PATCH] Election state refactor (#2535) Converting the election class into a state machine instead of working on rebroadcast iterations. --- nano/core_test/CMakeLists.txt | 1 + nano/core_test/active_transactions.cpp | 152 ++++++++++- nano/core_test/confirmation_height.cpp | 18 +- nano/core_test/confirmation_solicitor.cpp | 4 +- nano/core_test/election.cpp | 18 ++ nano/core_test/ledger.cpp | 2 +- nano/core_test/node.cpp | 2 - nano/node/active_transactions.cpp | 162 +++--------- nano/node/active_transactions.hpp | 21 +- nano/node/blockprocessor.cpp | 6 +- nano/node/election.cpp | 305 +++++++++++++++++++--- nano/node/election.hpp | 59 ++++- nano/node/json_handler.cpp | 2 +- nano/node/node.cpp | 49 ++-- nano/node/repcrawler.cpp | 6 - nano/node/repcrawler.hpp | 3 - 16 files changed, 549 insertions(+), 261 deletions(-) create mode 100644 nano/core_test/election.cpp diff --git a/nano/core_test/CMakeLists.txt b/nano/core_test/CMakeLists.txt index 947d5dff0d..06aac192f2 100644 --- a/nano/core_test/CMakeLists.txt +++ b/nano/core_test/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable (core_test conflicts.cpp difficulty.cpp distributed_work.cpp + election.cpp entry.cpp epochs.cpp gap_cache.cpp diff --git a/nano/core_test/active_transactions.cpp b/nano/core_test/active_transactions.cpp index 15b74bc141..b9b6379f4f 100644 --- a/nano/core_test/active_transactions.cpp +++ b/nano/core_test/active_transactions.cpp @@ -7,7 +7,7 @@ using namespace std::chrono_literals; -TEST (active_transactions, confirm_one) +TEST (active_transactions, confirm_active) { nano::system system (1); auto & node1 = *system.nodes[0]; @@ -15,7 +15,7 @@ TEST (active_transactions, confirm_one) system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::public_key (), node1.config.receive_minimum.number ())); system.deadline_set (5s); - while (!node1.active.empty () && !node1.block_confirmed_or_being_confirmed (node1.store.tx_begin_read (), send->hash ())) + while (!node1.active.empty () || !node1.block_confirmed_or_being_confirmed (node1.store.tx_begin_read (), send->hash ())) { ASSERT_NO_ERROR (system.poll ()); } @@ -27,12 +27,58 @@ TEST (active_transactions, confirm_one) node1.network.flood_block (send, nano::buffer_drop_policy::no_limiter_drop); ASSERT_NO_ERROR (system.poll ()); } - while (node2.ledger.cache.cemented_count < 2) + while (node2.ledger.cache.cemented_count < 2 || !node2.active.empty ()) { ASSERT_NO_ERROR (system.poll ()); } } +TEST (active_transactions, confirm_frontier) +{ + nano::system system (1); + auto & node1 = *system.nodes[0]; + // Send and vote for a block before peering with node2 + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::public_key (), node1.config.receive_minimum.number ())); + system.deadline_set (5s); + while (!node1.active.empty () || !node1.block_confirmed_or_being_confirmed (node1.store.tx_begin_read (), send->hash ())) + { + ASSERT_NO_ERROR (system.poll ()); + } + auto & node2 = *system.add_node (nano::node_config (nano::get_available_port (), system.logging)); + ASSERT_EQ (nano::process_result::progress, node2.process (*send).code); + system.deadline_set (5s); + while (node2.ledger.cache.cemented_count < 2 || !node2.active.empty ()) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (active_transactions, confirm_dependent) +{ + nano::system system; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node1 = *system.add_node (node_flags); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::public_key (), node1.config.receive_minimum.number ())); + auto send2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::public_key (), node1.config.receive_minimum.number ())); + auto send3 (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::public_key (), node1.config.receive_minimum.number ())); + nano::node_config node_config; + node_config.peering_port = nano::get_available_port (); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node2 = *system.add_node (node_config); + node2.process_local (send1); + node2.process_local (send2); + node2.process_active (send3); + system.deadline_set (5s); + while (!node2.active.empty ()) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (4, node2.ledger.cache.cemented_count); +} + TEST (active_transactions, adjusted_difficulty_priority) { nano::system system; @@ -76,7 +122,7 @@ TEST (active_transactions, adjusted_difficulty_priority) } } system.deadline_set (10s); - while (node1.ledger.cache.cemented_count < 5) + while (node1.ledger.cache.cemented_count < 5 || !node1.active.empty ()) { ASSERT_NO_ERROR (system.poll ()); } @@ -400,7 +446,7 @@ TEST (active_transactions, inactive_votes_cache_fork) while (!confirmed) { auto transaction (node.store.tx_begin_read ()); - confirmed = node.block (send1->hash ()) != nullptr && node.ledger.block_confirmed (transaction, send1->hash ()); + confirmed = node.block (send1->hash ()) != nullptr && node.ledger.block_confirmed (transaction, send1->hash ()) && node.active.empty (); ASSERT_NO_ERROR (system.poll ()); } ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached)); @@ -557,9 +603,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 updated = (existing1->difficulty > difficulty1) && (existing2->difficulty > difficulty2); - auto propogated = (existing3->difficulty > difficulty1) && (existing4->difficulty > difficulty2); - done = updated && propogated; + 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; } ASSERT_NO_ERROR (system.poll ()); } @@ -583,15 +631,28 @@ TEST (active_transactions, vote_replays) node.process_active (open1); node.block_processor.flush (); ASSERT_EQ (2, node.active.size ()); - // First vote is not a replay and confirms the election, second vote should be indeterminate since the election no longer exists + // First vote is not a replay and confirms the election, second vote should be a replay since the election has confirmed but not yet removed auto vote_send1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, send1)); ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote_send1)); - ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (2, node.active.size ()); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_send1)); + // Wait until the election is removed, at which point the vote should be indeterminate + system.deadline_set (3s); + while (node.active.size () != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_send1)); // Open new account auto vote_open1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, open1)); ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote_open1)); - ASSERT_TRUE (node.active.empty ()); + ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_open1)); + system.deadline_set (3s); + while (!node.active.empty ()) + { + ASSERT_NO_ERROR (system.poll ()); + } ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_open1)); ASSERT_EQ (nano::Gxrb_ratio, node.ledger.weight (key.pub)); @@ -607,7 +668,76 @@ TEST (active_transactions, vote_replays) ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote2_send2)); ASSERT_EQ (1, node.active.size ()); ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote1_send2)); + ASSERT_EQ (1, node.active.size ()); + ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote1_send2)); + while (!node.active.empty ()) + { + ASSERT_NO_ERROR (system.poll ()); + } ASSERT_EQ (0, node.active.size ()); ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote1_send2)); ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote2_send2)); } + +TEST (active_transactions, activate_dependencies) +{ + // Ensure that we attempt to backtrack if an election isn't getting confirmed and there are more uncemented blocks to start elections for + nano::system system; + nano::node_config config (nano::get_available_port (), system.logging); + config.enable_voting = true; + nano::node_flags flags; + flags.disable_bootstrap_listener = true; + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node1 (system.add_node (config, flags)); + config.peering_port = nano::get_available_port (); + auto node2 (system.add_node (config, flags)); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::genesis genesis; + nano::block_builder builder; + system.deadline_set (std::chrono::seconds (15)); + std::shared_ptr block0 = builder.state () + .account (nano::test_genesis_key.pub) + .previous (genesis.hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - nano::Gxrb_ratio) + .link (0) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (node1->work_generate_blocking (genesis.hash ()).value ()) + .build (); + // Establish a representative + node2->process_active (block0); + node2->block_processor.flush (); + while (node1->block (block0->hash ()) == nullptr) + { + ASSERT_NO_ERROR (system.poll ()); + } + auto block1 = builder.state () + .account (nano::test_genesis_key.pub) + .previous (block0->hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - nano::Gxrb_ratio) + .link (0) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (node1->work_generate_blocking (block0->hash ()).value ()) + .build (); + { + auto transaction = node2->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node2->ledger.process (transaction, *block1).code); + } + std::shared_ptr block2 = builder.state () + .account (nano::test_genesis_key.pub) + .previous (block1->hash ()) + .representative (nano::test_genesis_key.pub) + .balance (nano::genesis_amount - 2 * nano::Gxrb_ratio) + .link (0) + .sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub) + .work (node1->work_generate_blocking (block1->hash ()).value ()) + .build (); + node2->process_active (block2); + node2->block_processor.flush (); + while (node1->block (block2->hash ()) == nullptr) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_NE (nullptr, node1->block (block2->hash ())); +} diff --git a/nano/core_test/confirmation_height.cpp b/nano/core_test/confirmation_height.cpp index 714694648a..33830efffa 100644 --- a/nano/core_test/confirmation_height.cpp +++ b/nano/core_test/confirmation_height.cpp @@ -1187,13 +1187,8 @@ TEST (confirmation_height, dependent_election) add_callback_stats (*node); - // Wait until it has been processed + // Start an election and vote, should confirm the block node->block_confirm (send2); - system.deadline_set (10s); - while (node->active.size () > 0) - { - ASSERT_NO_ERROR (system.poll ()); - } { // The write guard prevents the confirmation height processor doing any writes. @@ -1426,17 +1421,6 @@ TEST (confirmation_height, election_winner_details_clearing) add_callback_stats (*node); node->block_confirm (send1); - system.deadline_set (10s); - while (node->active.size () > 0) - { - ASSERT_NO_ERROR (system.poll ()); - } - - ASSERT_EQ (0, node->active.list_confirmed ().size ()); - { - nano::lock_guard guard (node->active.mutex); - ASSERT_EQ (0, node->active.blocks.size ()); - } system.deadline_set (10s); while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 2) diff --git a/nano/core_test/confirmation_solicitor.cpp b/nano/core_test/confirmation_solicitor.cpp index 8be720b051..d2595da088 100644 --- a/nano/core_test/confirmation_solicitor.cpp +++ b/nano/core_test/confirmation_solicitor.cpp @@ -34,12 +34,12 @@ TEST (confirmation_solicitor, batches) auto send (std::make_shared (nano::genesis_hash, nano::keypair ().pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (nano::genesis_hash))); for (size_t i (0); i < nano::network::confirm_req_hashes_max; ++i) { - auto election (std::make_shared (node2, send, false, nullptr)); + auto election (std::make_shared (node2, send, nullptr)); ASSERT_FALSE (node2.active.solicitor.add (*election)); } ASSERT_EQ (1, node2.active.solicitor.max_confirm_req_batches); // Reached the maximum amount of requests for the channel - auto election (std::make_shared (node2, send, false, nullptr)); + auto election (std::make_shared (node2, send, nullptr)); ASSERT_TRUE (node2.active.solicitor.add (*election)); // Broadcasting should be immediate ASSERT_EQ (0, node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::out)); diff --git a/nano/core_test/election.cpp b/nano/core_test/election.cpp new file mode 100644 index 0000000000..8ccc8af4d2 --- /dev/null +++ b/nano/core_test/election.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +#include + +TEST (election, construction) +{ + nano::system system (1); + nano::genesis genesis; + auto & node = *system.nodes[0]; + auto election = node.active.insert (genesis.open).first; + ASSERT_TRUE (election->idle ()); + election->transition_active (); + ASSERT_FALSE (election->idle ()); + election->transition_passive (); + ASSERT_FALSE (election->idle ()); +} diff --git a/nano/core_test/ledger.cpp b/nano/core_test/ledger.cpp index ed438fa646..b71139c6a0 100644 --- a/nano/core_test/ledger.cpp +++ b/nano/core_test/ledger.cpp @@ -768,7 +768,7 @@ TEST (votes, add_one) auto vote1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send1)); ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1)); auto vote2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 2, send1)); - ASSERT_EQ (nano::vote_code::indeterminate, node1.active.vote (vote2)); + ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2)); lock.lock (); ASSERT_EQ (2, election1.first->last_votes.size ()); auto existing1 (election1.first->last_votes.find (nano::test_genesis_key.pub)); diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index b13f0d812d..eab6c6ef7f 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -2134,7 +2134,6 @@ TEST (node, rep_weight) auto vote0 = std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, genesis.open); auto vote1 = std::make_shared (keypair1.pub, keypair1.prv, 0, genesis.open); auto vote2 = std::make_shared (keypair2.pub, keypair2.prv, 0, genesis.open); - node.rep_crawler.add (genesis.open->hash ()); node.rep_crawler.response (channel0, vote0); node.rep_crawler.response (channel1, vote1); node.rep_crawler.response (channel2, vote2); @@ -2217,7 +2216,6 @@ TEST (node, rep_remove) nano::amount amount100 (100); node.network.udp_channels.insert (endpoint0, node.network_params.protocol.protocol_version); auto vote1 = std::make_shared (keypair1.pub, keypair1.prv, 0, genesis.open); - node.rep_crawler.add (genesis.hash ()); node.rep_crawler.response (channel0, vote1); system.deadline_set (5s); while (node.rep_crawler.representative_count () != 1) diff --git a/nano/node/active_transactions.cpp b/nano/node/active_transactions.cpp index 7e9923d589..fd80025924 100644 --- a/nano/node/active_transactions.cpp +++ b/nano/node/active_transactions.cpp @@ -17,12 +17,7 @@ node (node_a), multipliers_cb (20, 1.), trended_active_difficulty (node_a.network_params.network.publish_threshold), solicitor (node_a.network, node_a.network_params.network), -long_election_threshold (node_a.network_params.network.is_test_network () ? 2s : 24s), -election_request_delay (node_a.network_params.network.is_test_network () ? 0s : 1s), -election_time_to_live (node_a.network_params.network.is_test_network () ? 0s : 10s), -min_time_between_requests (node_a.network_params.network.is_test_network () ? 25ms : 3s), -min_time_between_floods (node_a.network_params.network.is_test_network () ? 50ms : 6s), -min_request_count_flood (node_a.network_params.network.is_test_network () ? 0 : 2), +election_time_to_live (node_a.network_params.network.is_test_network () ? 0s : 2s), thread ([this]() { nano::thread_role::set (nano::thread_role::name::request_loop); request_loop (); @@ -38,8 +33,6 @@ thread ([this]() { this->block_already_cemented_callback (hash_a); }); - debug_assert (min_time_between_requests > std::chrono::milliseconds (node.network_params.network.request_interval_ms)); - debug_assert (min_time_between_floods > std::chrono::milliseconds (node.network_params.network.request_interval_ms)); nano::unique_lock lock (mutex); condition.wait (lock, [& started = started] { return started; }); } @@ -104,8 +97,10 @@ void nano::active_transactions::search_frontiers (nano::transaction const & tran if (info.block_count > confirmation_height_info.height && !this->confirmation_height_processor.is_processing_block (info.head)) { auto block (this->node.store.block_get (transaction_a, info.head)); - if (this->insert (block, true).first) + auto election = this->insert (block); + if (election.second) { + election.first->transition_active (); ++elections_count; // Calculate votes for local representatives if (representative) @@ -163,7 +158,7 @@ void nano::active_transactions::block_cemented_callback (std::shared_ptr lk (mutex); - if (election->confirmed () && !election->stopped && election->status.winner->hash () == hash) + if (election->confirmed () && election->status.winner->hash () == hash) { add_confirmed (election->status, block_a->qualified_root ()); lk.unlock (); @@ -206,60 +201,10 @@ void nano::active_transactions::block_already_cemented_callback (nano::block_has election_winner_details.erase (hash_a); } -void nano::active_transactions::election_escalate (std::shared_ptr & election_l, nano::transaction const & transaction_l, size_t const & roots_size_l) -{ - constexpr unsigned high_confirmation_request_count{ 128 }; - // Log votes for very long unconfirmed elections - if (election_l->confirmation_request_count % (4 * high_confirmation_request_count) == 1) - { - auto tally_l (election_l->tally ()); - election_l->log_votes (tally_l); - } - /* - * Escalation for long unconfirmed elections - * Start new elections for previous block & source if there are less than 100 active elections - */ - if (election_l->confirmation_request_count % high_confirmation_request_count == 1 && roots_size_l < 100 && !node.network_params.network.is_test_network ()) - { - bool escalated_l (false); - std::shared_ptr previous_l; - auto previous_hash_l (election_l->status.winner->previous ()); - if (!previous_hash_l.is_zero ()) - { - previous_l = node.store.block_get (transaction_l, previous_hash_l); - if (previous_l != nullptr && blocks.find (previous_hash_l) == blocks.end () && !node.block_confirmed_or_being_confirmed (transaction_l, previous_hash_l)) - { - insert_impl (std::move (previous_l), true); - escalated_l = true; - } - } - /* If previous block not existing/not commited yet, block_source can cause segfault for state blocks - So source check can be done only if previous != nullptr or previous is 0 (open account) */ - if (previous_hash_l.is_zero () || previous_l != nullptr) - { - auto source_hash_l (node.ledger.block_source (transaction_l, *election_l->status.winner)); - if (!source_hash_l.is_zero () && source_hash_l != previous_hash_l && blocks.find (source_hash_l) == blocks.end ()) - { - auto source_l (node.store.block_get (transaction_l, source_hash_l)); - if (source_l != nullptr && !node.block_confirmed_or_being_confirmed (transaction_l, source_hash_l)) - { - insert_impl (std::move (source_l), true); - escalated_l = true; - } - } - } - if (escalated_l) - { - election_l->update_dependent (); - } - } -} - void nano::active_transactions::request_confirm (nano::unique_lock & lock_a) { debug_assert (!mutex.try_lock ()); auto transaction_l (node.store.tx_begin_read ()); - std::unordered_set inactive_l; /* * Confirm frontiers when there aren't many confirmations already pending and node finished initial bootstrap * In auto mode start confirm only if node contains almost principal representative (half of required for principal weight) @@ -283,19 +228,9 @@ void nano::active_transactions::request_confirm (nano::unique_lock & solicitor.prepare (node.rep_crawler.representatives (node.network_params.protocol.tcp_realtime_protocol_version_min)); lock_a.lock (); - auto const now (std::chrono::steady_clock::now ()); - // Any new election started from process_live only gets requests after at least 1 second - auto cutoff_l (now - election_request_delay); - // Elections taking too long get escalated - auto long_election_cutoff_l (now - long_election_threshold); - // The lowest PoW difficulty elections have a maximum time to live if they are beyond the soft threshold size for the container - auto election_ttl_cutoff_l (now - election_time_to_live); - // Rate-limitting floods - auto const flood_cutoff (now - min_time_between_floods); - // Rate-limitting confirmation requests - auto const request_cutoff (now - min_time_between_requests); - + auto election_ttl_cutoff_l (std::chrono::steady_clock::now () - election_time_to_live); auto roots_size_l (roots.size ()); + bool saturated_l (roots_size_l > node.config.active_elections_size / 2); auto & sorted_roots_l = roots.get (); size_t count_l{ 0 }; @@ -306,60 +241,22 @@ void nano::active_transactions::request_confirm (nano::unique_lock & * Elections extending the soft config.active_elections_size limit are flushed after a certain time-to-live cutoff * Flushed elections are later re-activated via frontier confirmation */ - for (auto i = sorted_roots_l.begin (), n = sorted_roots_l.end (); i != n; ++i, ++count_l) + for (auto i = sorted_roots_l.begin (), n = sorted_roots_l.end (); i != n; ++count_l) { - auto election_l (i->election); - auto root_l (i->root); - if (election_l->confirmed () || (election_l->confirmation_request_count != 0 && !node.ledger.could_fit (transaction_l, *election_l->status.winner))) + auto & election_l (i->election); + if ((count_l >= node.config.active_elections_size && election_l->election_start < election_ttl_cutoff_l && !node.wallets.watcher->is_watched (i->root)) || election_l->transition_time (saturated_l)) { - election_l->stop (); + election_l->clear_blocks (); + i = sorted_roots_l.erase (i); } - // Erase finished elections - if ((election_l->stopped)) - { - inactive_l.insert (root_l); - } - // Drop elections - else if (count_l >= node.config.active_elections_size && election_l->election_start < election_ttl_cutoff_l && !node.wallets.watcher->is_watched (root_l)) + else { - election_l->stop (); - inactive_l.insert (root_l); - } - // Attempt obtaining votes - else if (election_l->skip_delay || election_l->election_start < cutoff_l) - { - // Broadcast the winner when elections are taking longer to confirm - if (election_l->confirmation_request_count >= min_request_count_flood && election_l->last_broadcast < flood_cutoff && !solicitor.broadcast (*election_l)) - { - election_l->last_broadcast = now; - } - // Rate-limited requests for confirmation - else if (election_l->last_request < request_cutoff && !solicitor.add (*election_l)) - { - ++election_l->confirmation_request_count; - election_l->last_request = now; - } - // Escalate long election after a certain time and number of requests performed - if (election_l->confirmation_request_count > 4 && election_l->election_start < long_election_cutoff_l) - { - election_escalate (election_l, transaction_l, roots_size_l); - } + ++i; } } lock_a.unlock (); solicitor.flush (); lock_a.lock (); - // Erase inactive elections - for (auto i (inactive_l.begin ()), n (inactive_l.end ()); i != n; ++i) - { - auto root_it (roots.get ().find (*i)); - if (root_it != roots.get ().end ()) - { - root_it->election->clear_blocks (); - root_it->election->clear_dependent (); - roots.get ().erase (root_it); - } - } } void nano::active_transactions::request_loop () @@ -566,7 +463,7 @@ void nano::active_transactions::stop () roots.clear (); } -std::pair, bool> nano::active_transactions::insert_impl (std::shared_ptr block_a, bool const skip_delay_a, std::function)> const & confirmation_action_a) +std::pair, bool> nano::active_transactions::insert_impl (std::shared_ptr block_a, std::function)> const & confirmation_action_a) { std::pair, bool> result = { nullptr, false }; if (!stopped) @@ -579,7 +476,7 @@ std::pair, bool> nano::active_transactions::inse { result.second = true; auto hash (block_a->hash ()); - result.first = nano::make_shared (node, block_a, skip_delay_a, confirmation_action_a); + result.first = nano::make_shared (node, block_a, confirmation_action_a); auto difficulty (block_a->difficulty ()); roots.get ().emplace (nano::conflict_info{ root, difficulty, difficulty, result.first }); blocks.emplace (hash, result.first); @@ -595,10 +492,10 @@ std::pair, bool> nano::active_transactions::inse return result; } -std::pair, bool> nano::active_transactions::insert (std::shared_ptr block_a, bool const skip_delay_a, std::function)> const & confirmation_action_a) +std::pair, bool> nano::active_transactions::insert (std::shared_ptr block_a, std::function)> const & confirmation_action_a) { nano::lock_guard lock (mutex); - return insert_impl (block_a, skip_delay_a, confirmation_action_a); + return insert_impl (block_a, confirmation_action_a); } // Validate a vote and apply it to the current election if one exists @@ -671,6 +568,18 @@ bool nano::active_transactions::active (nano::block const & block_a) return active (block_a.qualified_root ()); } +std::shared_ptr nano::active_transactions::election (nano::qualified_root const & root_a) const +{ + std::shared_ptr result; + nano::lock_guard lock (mutex); + auto existing = roots.get ().find (root_a); + if (existing != roots.get ().end ()) + { + result = existing->election; + } + return result; +} + void nano::active_transactions::update_difficulty (std::shared_ptr block_a) { nano::unique_lock lock (mutex); @@ -711,7 +620,7 @@ void nano::active_transactions::adjust_difficulty (nano::block_hash const & hash if (processed_blocks.find (hash) == processed_blocks.end ()) { auto existing (blocks.find (hash)); - if (existing != blocks.end () && !existing->second->confirmed () && !existing->second->stopped && existing->second->status.winner->hash () == hash) + if (existing != blocks.end () && !existing->second->confirmed () && existing->second->status.winner->hash () == hash) { auto previous (existing->second->status.winner->previous ()); if (!previous.is_zero ()) @@ -793,10 +702,9 @@ void nano::active_transactions::update_active_difficulty (nano::unique_lock active_root_difficulties; active_root_difficulties.reserve (std::min (sorted_roots.size (), node.config.active_elections_size)); size_t count (0); - auto cutoff (std::chrono::steady_clock::now () - election_request_delay - 1s); for (auto it (sorted_roots.begin ()), end (sorted_roots.end ()); it != end && count++ < node.config.active_elections_size; ++it) { - if (!it->election->confirmed () && !it->election->stopped && it->election->election_start < cutoff) + if (!it->election->confirmed () && !it->election->idle ()) { active_root_difficulties.push_back (it->adjusted_difficulty); } @@ -865,9 +773,7 @@ void nano::active_transactions::erase (nano::block const & block_a) auto root_it (roots.get ().find (block_a.qualified_root ())); if (root_it != roots.get ().end ()) { - root_it->election->stop (); root_it->election->clear_blocks (); - root_it->election->clear_dependent (); roots.get ().erase (root_it); node.logger.try_log (boost::str (boost::format ("Election erased for block block %1% root %2%") % block_a.hash ().to_string () % block_a.root ().to_string ())); } @@ -894,7 +800,7 @@ bool nano::active_transactions::publish (std::shared_ptr block_a) { auto election (existing->election); result = election->publish (block_a); - if (!result && !election->confirmed ()) + if (!result) { blocks.emplace (block_a->hash (), election); } @@ -910,7 +816,7 @@ boost::optional nano::active_transactions::confirm_b auto existing (blocks.find (hash)); if (existing != blocks.end ()) { - if (!existing->second->confirmed () && !existing->second->stopped && existing->second->status.winner->hash () == hash) + if (!existing->second->confirmed () && existing->second->status.winner->hash () == hash) { existing->second->confirm_once (nano::election_status_type::active_confirmation_height); return nano::election_status_type::active_confirmation_height; diff --git a/nano/node/active_transactions.hpp b/nano/node/active_transactions.hpp index d1c68f45db..22c2af6d86 100644 --- a/nano/node/active_transactions.hpp +++ b/nano/node/active_transactions.hpp @@ -74,6 +74,8 @@ class inactive_cache_information final // Holds all active blocks i.e. recently added blocks that need confirmation class active_transactions final { + friend class nano::election; + // clang-format off class tag_account {}; class tag_difficulty {}; @@ -90,13 +92,14 @@ class active_transactions final // Start an election for a block // Call action with confirmed block, may be different than what we started with // clang-format off - std::pair, bool> insert (std::shared_ptr, bool const = false, std::function)> const & = [](std::shared_ptr) {}); + std::pair, bool> insert (std::shared_ptr, std::function)> const & = [](std::shared_ptr) {}); // clang-format on // Distinguishes replay votes, cannot be determined if the block is not in any election nano::vote_code vote (std::shared_ptr); // Is the root of this block in the roots container bool active (nano::block const &); bool active (nano::qualified_root const &); + std::shared_ptr election (nano::qualified_root const &) const; void update_difficulty (std::shared_ptr); void adjust_difficulty (nano::block_hash const &); void update_active_difficulty (nano::unique_lock &); @@ -130,7 +133,7 @@ class active_transactions final void erase_inactive_votes_cache (nano::block_hash const &); nano::confirmation_height_processor & confirmation_height_processor; nano::node & node; - std::mutex mutex; + mutable std::mutex mutex; boost::circular_buffer multipliers_cb; uint64_t trended_active_difficulty; size_t priority_cementable_frontiers_size (); @@ -147,11 +150,10 @@ class active_transactions final // Call action with confirmed block, may be different than what we started with // clang-format off - std::pair, bool> insert_impl (std::shared_ptr, bool const = false, std::function)> const & = [](std::shared_ptr) {}); + std::pair, bool> insert_impl (std::shared_ptr, std::function)> const & = [](std::shared_ptr) {}); // clang-format on void request_loop (); void search_frontiers (nano::transaction const &); - void election_escalate (std::shared_ptr &, nano::transaction const &, size_t const &); void request_confirm (nano::unique_lock &); nano::account next_frontier_account{ 0 }; std::chrono::steady_clock::time_point next_frontier_check{ std::chrono::steady_clock::now () }; @@ -159,18 +161,8 @@ class active_transactions final bool started{ false }; std::atomic stopped{ false }; - // Minimum time an election must be active before escalation - std::chrono::seconds const long_election_threshold; - // Delay until requesting confirmation for an election - std::chrono::milliseconds const election_request_delay; // Maximum time an election can be kept active if it is extending the container std::chrono::seconds const election_time_to_live; - // Minimum time between confirmation requests for an election - std::chrono::milliseconds const min_time_between_requests; - // Minimum time between broadcasts of the current winner of an election, as a backup to requesting confirmations - std::chrono::milliseconds const min_time_between_floods; - // Minimum election request count to start broadcasting blocks, as a backup to requesting confirmations - size_t const min_request_count_flood; // clang-format off boost::multi_index_container const &, nano::block_hash const &, bool &); - static size_t constexpr dropped_elections_cache_max{ 32 * 1024 }; boost::thread thread; friend class confirmation_height_prioritize_frontiers_Test; diff --git a/nano/node/blockprocessor.cpp b/nano/node/blockprocessor.cpp index dbdb3f6bd5..c7f0d9e9dc 100644 --- a/nano/node/blockprocessor.cpp +++ b/nano/node/blockprocessor.cpp @@ -372,7 +372,11 @@ void nano::block_processor::process_live (nano::block_hash const & hash_a, std:: } // Start collecting quorum on block - node.active.insert (block_a, false); + auto election = node.active.insert (block_a); + if (election.second) + { + election.first->transition_passive (); + } // Announce block contents to the network if (initial_publish_a) diff --git a/nano/node/election.cpp b/nano/node/election.cpp index bda1bdb33b..5e52ed6d5b 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -3,20 +3,29 @@ #include +using namespace std::chrono; + +int constexpr nano::election::passive_duration_factor; +int constexpr nano::election::active_duration_factor; +int constexpr nano::election::confirmed_duration_factor; +int constexpr nano::election::confirmed_duration_factor_saturated; + +std::chrono::milliseconds nano::election::base_latency () const +{ + return node.network_params.network.is_test_network () ? 25ms : 1000ms; +} + nano::election_vote_result::election_vote_result (bool replay_a, bool processed_a) { replay = replay_a; processed = processed_a; } -nano::election::election (nano::node & node_a, std::shared_ptr block_a, bool const skip_delay_a, std::function)> const & confirmation_action_a) : +nano::election::election (nano::node & node_a, std::shared_ptr block_a, std::function)> const & confirmation_action_a) : confirmation_action (confirmation_action_a), -confirmed_m (false), +state_start (std::chrono::steady_clock::now ()), node (node_a), -election_start (std::chrono::steady_clock::now ()), -status ({ block_a, 0, std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, 1, 0, nano::election_status_type::ongoing }), -skip_delay (skip_delay_a), -stopped (false) +status ({ block_a, 0, std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), 0, 1, 0, nano::election_status_type::ongoing }) { last_votes.emplace (node.network_params.random.not_an_account, nano::vote_info{ std::chrono::steady_clock::now (), 0, block_a->hash () }); blocks.emplace (block_a->hash (), block_a); @@ -26,7 +35,7 @@ stopped (false) void nano::election::confirm_once (nano::election_status_type type_a) { debug_assert (!node.active.mutex.try_lock ()); - if (!confirmed_m.exchange (true)) + if (state_m.exchange (nano::election::state_t::confirmed) != nano::election::state_t::confirmed) { status.election_end = std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()); status.election_duration = std::chrono::duration_cast (std::chrono::steady_clock::now () - election_start); @@ -48,30 +57,254 @@ void nano::election::confirm_once (nano::election_status_type type_a) node_l->process_confirmed (status_l, this_l); confirmation_action_l (status_l.winner); }); - clear_blocks (); - clear_dependent (); - node.active.roots.erase (status.winner->qualified_root ()); } } -void nano::election::stop () +bool nano::election::valid_change (nano::election::state_t expected_a, nano::election::state_t desired_a) const { - debug_assert (!node.active.mutex.try_lock ()); - if (!stopped && !confirmed ()) + bool result = false; + switch (expected_a) { - stopped = true; - status.election_end = std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()); - status.election_duration = std::chrono::duration_cast (std::chrono::steady_clock::now () - election_start); - status.confirmation_request_count = confirmation_request_count; - status.block_count = nano::narrow_cast (blocks.size ()); - status.voter_count = nano::narrow_cast (last_votes.size ()); - status.type = nano::election_status_type::stopped; + case nano::election::state_t::idle: + switch (desired_a) + { + case nano::election::state_t::passive: + case nano::election::state_t::active: + result = true; + break; + default: + break; + } + break; + case nano::election::state_t::passive: + switch (desired_a) + { + case nano::election::state_t::idle: + case nano::election::state_t::active: + case nano::election::state_t::confirmed: + case nano::election::state_t::expired_unconfirmed: + result = true; + break; + default: + break; + } + break; + case nano::election::state_t::active: + switch (desired_a) + { + case nano::election::state_t::idle: + case nano::election::state_t::backtracking: + case nano::election::state_t::confirmed: + case nano::election::state_t::expired_unconfirmed: + result = true; + break; + default: + break; + } + case nano::election::state_t::backtracking: + switch (desired_a) + { + case nano::election::state_t::idle: + case nano::election::state_t::confirmed: + case nano::election::state_t::expired_unconfirmed: + result = true; + break; + default: + break; + } + case nano::election::state_t::confirmed: + switch (desired_a) + { + case nano::election::state_t::expired_confirmed: + result = true; + break; + default: + break; + } + case nano::election::state_t::expired_unconfirmed: + break; + case nano::election::state_t::expired_confirmed: + break; + } + return result; +} + +bool nano::election::state_change (nano::election::state_t expected_a, nano::election::state_t desired_a) +{ + debug_assert (!timepoints_mutex.try_lock ()); + bool result = true; + if (valid_change (expected_a, desired_a)) + { + if (state_m.compare_exchange_strong (expected_a, desired_a)) + { + state_start = std::chrono::steady_clock::now (); + result = false; + } + } + else + { + debug_assert (false); + } + return result; +} + +void nano::election::send_confirm_req () +{ + if (last_req + std::chrono::seconds (15) < std::chrono::steady_clock::now ()) + { + if (!node.active.solicitor.add (*this)) + { + last_req = std::chrono::steady_clock::now (); + } } } -bool nano::election::confirmed () +void nano::election::transition_passive () +{ + nano::lock_guard guard (timepoints_mutex); + transition_passive_impl (); +} + +void nano::election::transition_passive_impl () +{ + state_change (nano::election::state_t::idle, nano::election::state_t::passive); +} + +void nano::election::transition_active () +{ + nano::lock_guard guard (timepoints_mutex); + transition_active_impl (); +} + +void nano::election::transition_active_impl () { - return confirmed_m; + if (!state_change (nano::election::state_t::idle, nano::election::state_t::active)) + { + if (base_latency () * 5 < std::chrono::steady_clock::now () - last_block) + { + last_block = std::chrono::steady_clock::now (); + node.network.flood_block (status.winner); + } + } +} + +bool nano::election::idle () const +{ + return state_m == nano::election::state_t::idle; +} + +bool nano::election::confirmed () const +{ + return state_m == nano::election::state_t::confirmed || state_m == nano::election::state_t::expired_confirmed; +} + +void nano::election::activate_dependencies () +{ + auto transaction = node.store.tx_begin_read (); + bool escalated_l (false); + std::shared_ptr previous_l; + auto previous_hash_l (status.winner->previous ()); + if (!previous_hash_l.is_zero () && node.active.blocks.find (previous_hash_l) == node.active.blocks.end ()) + { + previous_l = node.store.block_get (transaction, previous_hash_l); + if (previous_l != nullptr && !node.block_confirmed_or_being_confirmed (transaction, previous_hash_l)) + { + auto election = node.active.insert_impl (previous_l); + if (election.second) + { + election.first->transition_active (); + escalated_l = true; + } + } + } + /* If previous block not existing/not commited yet, block_source can cause segfault for state blocks + So source check can be done only if previous != nullptr or previous is 0 (open account) */ + if (previous_hash_l.is_zero () || previous_l != nullptr) + { + auto source_hash_l (node.ledger.block_source (transaction, *status.winner)); + if (!source_hash_l.is_zero () && source_hash_l != previous_hash_l && node.active.blocks.find (source_hash_l) == node.active.blocks.end ()) + { + auto source_l (node.store.block_get (transaction, source_hash_l)); + if (source_l != nullptr && !node.block_confirmed_or_being_confirmed (transaction, source_hash_l)) + { + auto election = node.active.insert_impl (source_l); + if (election.second) + { + election.first->transition_active (); + escalated_l = true; + } + } + } + } + if (escalated_l) + { + update_dependent (); + } +} + +void nano::election::broadcast_block () +{ + if (base_latency () * 5 < std::chrono::steady_clock::now () - last_block) + { + if (!node.active.solicitor.broadcast (*this)) + { + last_block = std::chrono::steady_clock::now (); + } + } +} + +bool nano::election::transition_time (bool const saturated_a) +{ + debug_assert (!node.active.mutex.try_lock ()); + nano::unique_lock lock (timepoints_mutex); + bool result = false; + switch (state_m) + { + case nano::election::state_t::idle: + break; + case nano::election::state_t::passive: + { + if (base_latency () * passive_duration_factor < std::chrono::steady_clock::now () - state_start) + { + state_change (nano::election::state_t::passive, nano::election::state_t::active); + } + break; + } + case nano::election::state_t::active: + broadcast_block (); + send_confirm_req (); + if (base_latency () * active_duration_factor < std::chrono::steady_clock::now () - state_start) + { + state_change (nano::election::state_t::active, nano::election::state_t::backtracking); + lock.unlock (); + activate_dependencies (); + } + break; + case nano::election::state_t::backtracking: + broadcast_block (); + send_confirm_req (); + break; + case nano::election::state_t::confirmed: + if (base_latency () * (saturated_a ? confirmed_duration_factor_saturated : confirmed_duration_factor) < std::chrono::steady_clock::now () - state_start) + { + result = true; + state_change (nano::election::state_t::confirmed, nano::election::state_t::expired_confirmed); + } + break; + case nano::election::state_t::expired_unconfirmed: + case nano::election::state_t::expired_confirmed: + debug_assert (false); + break; + } + // Note: lock (timepoints_mutex) is at an unknown state here - possibly unlocked before activate_dependencies + if (!confirmed () && std::chrono::minutes (5) < std::chrono::steady_clock::now () - election_start) + { + result = true; + state_change (state_m.load (), nano::election::state_t::expired_unconfirmed); + status.type = nano::election_status_type::stopped; + log_votes (tally ()); + } + return result; } bool nano::election::have_quorum (nano::tally_t const & tally_a, nano::uint128_t tally_sum) const @@ -189,10 +422,10 @@ nano::election_vote_result nano::election::vote (nano::account rep, uint64_t seq } else { - auto last_vote (last_vote_it->second); - if (last_vote.sequence < sequence || (last_vote.sequence == sequence && last_vote.hash < block_hash)) + auto last_vote_l (last_vote_it->second); + if (last_vote_l.sequence < sequence || (last_vote_l.sequence == sequence && last_vote_l.hash < block_hash)) { - if (last_vote.time <= std::chrono::steady_clock::now () - std::chrono::seconds (cooldown)) + if (last_vote_l.time <= std::chrono::steady_clock::now () - std::chrono::seconds (cooldown)) { should_process = true; } @@ -217,8 +450,9 @@ nano::election_vote_result nano::election::vote (nano::account rep, uint64_t seq bool nano::election::publish (std::shared_ptr block_a) { - auto result (false); - if (blocks.size () >= 10) + // Do not insert new blocks if already confirmed + auto result (confirmed ()); + if (!result && blocks.size () >= 10) { if (last_tally[block_a->hash ()] < node.online_reps.online_stake () / 10) { @@ -276,7 +510,7 @@ void nano::election::update_dependent () for (auto & block_search : blocks_search) { auto existing (node.active.blocks.find (block_search)); - if (existing != node.active.blocks.end () && !existing->second->confirmed () && !existing->second->stopped) + if (existing != node.active.blocks.end () && !existing->second->confirmed ()) { if (existing->second->dependent_blocks.find (hash) == existing->second->dependent_blocks.end ()) { @@ -286,27 +520,18 @@ void nano::election::update_dependent () } } -void nano::election::clear_dependent () -{ - for (auto & dependent_block : dependent_blocks) - { - node.active.adjust_difficulty (dependent_block); - } -} - void nano::election::clear_blocks () { auto winner_hash (status.winner->hash ()); - for (auto & block : blocks) + for (auto const & block : blocks) { auto & hash (block.first); auto erased (node.active.blocks.erase (hash)); (void)erased; - // clear_blocks () can be called in active_transactions::publish () before blocks insertion if election was confirmed - debug_assert (erased == 1 || confirmed ()); + debug_assert (erased == 1); node.active.erase_inactive_votes_cache (hash); // Notify observers about dropped elections & blocks lost confirmed elections - if (stopped || hash != winner_hash) + if (!confirmed () || hash != winner_hash) { node.observers.active_stopped.notify (hash); } diff --git a/nano/node/election.hpp b/nano/node/election.hpp index cfa76733a6..3205345f67 100644 --- a/nano/node/election.hpp +++ b/nano/node/election.hpp @@ -50,11 +50,42 @@ class election_vote_result final }; class election final : public std::enable_shared_from_this { + // Minimum time between broadcasts of the current winner of an election, as a backup to requesting confirmations + std::chrono::milliseconds base_latency () const; std::function)> confirmation_action; - std::atomic confirmed_m; + +private: // State management + enum class state_t + { + idle, + passive, + active, + backtracking, + confirmed, + expired_confirmed, + expired_unconfirmed + }; + static int constexpr passive_duration_factor = 5; + static int constexpr active_duration_factor = 20; + static int constexpr confirmed_duration_factor = 10; + static int constexpr confirmed_duration_factor_saturated = 1; + std::atomic state_m = { state_t::idle }; + + // Protects state_start, last_vote and last_block + std::mutex timepoints_mutex; + std::chrono::steady_clock::time_point state_start = { std::chrono::steady_clock::now () }; + std::chrono::steady_clock::time_point last_vote = { std::chrono::steady_clock::time_point () }; + std::chrono::steady_clock::time_point last_block = { std::chrono::steady_clock::time_point () }; + std::chrono::steady_clock::time_point last_req = { std::chrono::steady_clock::time_point () }; + + bool valid_change (nano::election::state_t, nano::election::state_t) const; + bool state_change (nano::election::state_t, nano::election::state_t); + void broadcast_block (); + void send_confirm_req (); + void activate_dependencies (); public: - election (nano::node &, std::shared_ptr, bool const, std::function)> const &); + election (nano::node &, std::shared_ptr, std::function)> const &); nano::election_vote_result vote (nano::account, uint64_t, nano::block_hash); nano::tally_t tally (); // Check if we have vote quorum @@ -66,22 +97,28 @@ class election final : public std::enable_shared_from_this bool publish (std::shared_ptr block_a); size_t last_votes_size (); void update_dependent (); - void clear_dependent (); void clear_blocks (); void insert_inactive_votes_cache (nano::block_hash const &); - void stop (); - bool confirmed (); + +public: // State transitions + bool transition_time (bool const saturated); + void transition_passive (); + void transition_active (); + +private: + void transition_passive_impl (); + void transition_active_impl (); + +public: + bool idle () const; + bool confirmed () const; nano::node & node; std::unordered_map last_votes; std::unordered_map> blocks; - std::chrono::steady_clock::time_point election_start; + std::chrono::steady_clock::time_point election_start = { std::chrono::steady_clock::now () }; nano::election_status status; - bool skip_delay; - bool stopped; - std::unordered_map last_tally; unsigned confirmation_request_count{ 0 }; - std::chrono::steady_clock::time_point last_broadcast; - std::chrono::steady_clock::time_point last_request; + std::unordered_map last_tally; std::unordered_set dependent_blocks; std::chrono::seconds late_blocks_delay{ 5 }; }; diff --git a/nano/node/json_handler.cpp b/nano/node/json_handler.cpp index a163925a77..34a3231ed2 100644 --- a/nano/node/json_handler.cpp +++ b/nano/node/json_handler.cpp @@ -1766,7 +1766,7 @@ void nano::json_handler::confirmation_active () nano::lock_guard lock (node.active.mutex); for (auto i (node.active.roots.begin ()), n (node.active.roots.end ()); i != n; ++i) { - if (i->election->confirmation_request_count >= announcements && !i->election->confirmed () && !i->election->stopped) + if (i->election->confirmation_request_count >= announcements && !i->election->confirmed ()) { boost::property_tree::ptree entry; entry.put ("", i->root.to_string ()); diff --git a/nano/node/node.cpp b/nano/node/node.cpp index f6381fd4db..84811903cf 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -549,29 +549,29 @@ void nano::node::process_fork (nano::transaction const & transaction_a, std::sha if (ledger_block && !block_confirmed_or_being_confirmed (transaction_a, ledger_block->hash ())) { std::weak_ptr this_w (shared_from_this ()); - if (active.insert (ledger_block, false, [this_w, root](std::shared_ptr) { - if (auto this_l = this_w.lock ()) - { - auto attempt (this_l->bootstrap_initiator.current_attempt ()); - if (attempt && attempt->mode == nano::bootstrap_mode::legacy) - { - auto transaction (this_l->store.tx_begin_read ()); - auto account (this_l->ledger.store.frontier_get (transaction, root)); - if (!account.is_zero ()) - { - this_l->bootstrap_initiator.connections->requeue_pull (nano::pull_info (account, root, root, attempt->incremental_id)); - } - else if (this_l->ledger.store.account_exists (transaction, root)) - { - this_l->bootstrap_initiator.connections->requeue_pull (nano::pull_info (root, nano::block_hash (0), nano::block_hash (0), attempt->incremental_id)); - } - } - } - }) - .first) + auto election = active.insert (ledger_block, [this_w, root](std::shared_ptr) { + if (auto this_l = this_w.lock ()) + { + auto attempt (this_l->bootstrap_initiator.current_attempt ()); + if (attempt && attempt->mode == nano::bootstrap_mode::legacy) + { + auto transaction (this_l->store.tx_begin_read ()); + auto account (this_l->ledger.store.frontier_get (transaction, root)); + if (!account.is_zero ()) + { + this_l->bootstrap_initiator.connections->requeue_pull (nano::pull_info (account, root, root, attempt->incremental_id)); + } + else if (this_l->ledger.store.account_exists (transaction, root)) + { + this_l->bootstrap_initiator.connections->requeue_pull (nano::pull_info (root, nano::block_hash (0), nano::block_hash (0), attempt->incremental_id)); + } + } + } + }); + if (election.second) { logger.always_log (boost::str (boost::format ("Resolving fork between our block: %1% and block %2% both with root %3%") % ledger_block->hash ().to_string () % block_a->hash ().to_string () % block_a->root ().to_string ())); - network.broadcast_confirm_req (ledger_block); + election.first->transition_active (); } } } @@ -1101,8 +1101,11 @@ void nano::node::add_initial_peers () void nano::node::block_confirm (std::shared_ptr block_a) { - active.insert (block_a, false); - network.broadcast_confirm_req (block_a); + auto election = active.insert (block_a); + if (election.second) + { + election.first->transition_active (); + } // Calculate votes for local representatives if (config.enable_voting && wallets.rep_counts ().voting > 0 && active.active (*block_a)) { diff --git a/nano/node/repcrawler.cpp b/nano/node/repcrawler.cpp index 5e458d0df0..d1af4a7663 100644 --- a/nano/node/repcrawler.cpp +++ b/nano/node/repcrawler.cpp @@ -14,12 +14,6 @@ node (node_a) } } -void nano::rep_crawler::add (nano::block_hash const & hash_a) -{ - nano::lock_guard lock (active_mutex); - active.insert (hash_a); -} - void nano::rep_crawler::remove (nano::block_hash const & hash_a) { nano::lock_guard lock (active_mutex); diff --git a/nano/node/repcrawler.hpp b/nano/node/repcrawler.hpp index 3421d28faf..0071584f53 100644 --- a/nano/node/repcrawler.hpp +++ b/nano/node/repcrawler.hpp @@ -80,9 +80,6 @@ class rep_crawler final /** Start crawling */ void start (); - /** Add block hash to list of active rep queries */ - void add (nano::block_hash const &); - /** Remove block hash from list of active rep queries */ void remove (nano::block_hash const &);