diff --git a/nano/core_test/active_transactions.cpp b/nano/core_test/active_transactions.cpp index a1f967ff..cb7f355c 100644 --- a/nano/core_test/active_transactions.cpp +++ b/nano/core_test/active_transactions.cpp @@ -1447,3 +1447,157 @@ TEST (active_transactions, difficulty_update_observer) }); ASSERT_TIMELY (3s, update_received); } + +namespace nano +{ +TEST (active_transactions, pessimistic_elections) +{ + nano::system system; + nano::node_flags flags; + nano::node_config config (nano::get_available_port (), system.logging); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto & node = *system.add_node (config, flags); + + nano::keypair key; + nano::state_block_builder builder; + auto send = builder.make_block () + .account (nano::dev_genesis_key.pub) + .previous (nano::genesis_hash) + .representative (nano::dev_genesis_key.pub) + .link (nano::dev_genesis_key.pub) + .balance (nano::genesis_amount - 1) + .sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub) + .work (*system.work.generate (nano::genesis_hash)) + .build_shared (); + + ASSERT_EQ (nano::process_result::progress, node.process (*send).code); + + auto send2 = builder.make_block () + .account (nano::dev_genesis_key.pub) + .previous (send->hash ()) + .representative (nano::dev_genesis_key.pub) + .link (key.pub) + .balance (nano::genesis_amount - 2) + .sign (nano::dev_genesis_key.prv, nano::dev_genesis_key.pub) + .work (*system.work.generate (send->hash ())) + .build (); + + ASSERT_EQ (nano::process_result::progress, node.process (*send2).code); + + auto open = builder.make_block () + .account (key.pub) + .previous (0) + .representative (key.pub) + .link (send2->hash ()) + .balance (1) + .sign (key.prv, key.pub) + .work (*system.work.generate (key.pub)) + .build_shared (); + + ASSERT_EQ (nano::process_result::progress, node.process (*open).code); + + // This should only cement the first block in genesis account + uint64_t election_count = 0; + // Make dummy election with winner. + { + nano::lock_guard guard (node.active.mutex); + nano::election election1 ( + node, send, [](auto const & block) {}, false, nano::election_behavior::normal); + nano::election election2 ( + node, open, [](auto const & block) {}, false, nano::election_behavior::normal); + node.active.add_expired_optimistic_election (election1); + node.active.add_expired_optimistic_election (election2); + } + node.active.confirm_expired_frontiers_pessimistically (node.store.tx_begin_read (), 100, election_count); + ASSERT_EQ (1, election_count); + ASSERT_EQ (2, node.active.expired_optimistic_election_infos.size ()); + ASSERT_EQ (2, node.active.expired_optimistic_election_infos.size ()); + auto election_started_it = node.active.expired_optimistic_election_infos.get ().begin (); + ASSERT_EQ (election_started_it->account, nano::genesis_account); + ASSERT_EQ (election_started_it->election_started, true); + ASSERT_EQ ((++election_started_it)->election_started, false); + + // No new elections should get started yet + node.active.confirm_expired_frontiers_pessimistically (node.store.tx_begin_read (), 100, election_count); + ASSERT_EQ (1, election_count); + ASSERT_EQ (2, node.active.expired_optimistic_election_infos.size ()); + ASSERT_EQ (node.active.expired_optimistic_election_infos_size, node.active.expired_optimistic_election_infos.size ()); + + { + ASSERT_EQ (1, node.active.size ()); + auto election = node.active.election (send->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node.active.mutex); + election->confirm_once (); + } + + ASSERT_TIMELY (3s, node.block_confirmed (send->hash ()) && !node.confirmation_height_processor.is_processing_added_block (send->hash ())); + + nano::confirmation_height_info genesis_confirmation_height_info; + nano::confirmation_height_info key1_confirmation_height_info; + { + auto transaction = node.store.tx_begin_read (); + node.store.confirmation_height_get (transaction, nano::genesis_account, genesis_confirmation_height_info); + ASSERT_EQ (2, genesis_confirmation_height_info.height); + node.store.confirmation_height_get (transaction, key.pub, key1_confirmation_height_info); + ASSERT_EQ (0, key1_confirmation_height_info.height); + } + + // Activation of cemented frontier successor should get started after the first pessimistic block is confirmed + ASSERT_TIMELY (10s, node.active.active (send->qualified_root ())); + + node.active.confirm_expired_frontiers_pessimistically (node.store.tx_begin_read (), 100, election_count); + ASSERT_EQ (1, election_count); + ASSERT_EQ (2, node.active.expired_optimistic_election_infos.size ()); + + // Confirm it + { + auto election = node.active.election (send2->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node.active.mutex); + election->confirm_once (); + } + + ASSERT_TIMELY (3s, node.block_confirmed (send2->hash ())); + + { + auto transaction = node.store.tx_begin_read (); + node.store.confirmation_height_get (transaction, nano::genesis_account, genesis_confirmation_height_info); + ASSERT_EQ (3, genesis_confirmation_height_info.height); + node.store.confirmation_height_get (transaction, key.pub, key1_confirmation_height_info); + ASSERT_EQ (0, key1_confirmation_height_info.height); + } + + // Wait until activation of destination account is done. + ASSERT_TIMELY (10s, node.active.active (send2->qualified_root ())); + + // Election count should not increase, but the elections should be marked as started for that account afterwards + ASSERT_EQ (election_started_it->election_started, false); + node.active.confirm_expired_frontiers_pessimistically (node.store.tx_begin_read (), 100, election_count); + ASSERT_EQ (1, election_count); + ASSERT_EQ (2, node.active.expired_optimistic_election_infos.size ()); + node.active.confirm_expired_frontiers_pessimistically (node.store.tx_begin_read (), 100, election_count); + + { + auto election = node.active.election (open->qualified_root ()); + ASSERT_NE (nullptr, election); + nano::lock_guard guard (node.active.mutex); + election->confirm_once (); + } + + ASSERT_TIMELY (3s, node.block_confirmed (open->hash ())); + + { + auto transaction = node.store.tx_begin_read (); + node.store.confirmation_height_get (transaction, nano::genesis_account, genesis_confirmation_height_info); + ASSERT_EQ (3, genesis_confirmation_height_info.height); + node.store.confirmation_height_get (transaction, key.pub, key1_confirmation_height_info); + ASSERT_EQ (1, key1_confirmation_height_info.height); + } + + // Sanity check that calling it again on a fully cemented chain has no adverse effects. + node.active.confirm_expired_frontiers_pessimistically (node.store.tx_begin_read (), 100, election_count); + ASSERT_EQ (1, election_count); + ASSERT_EQ (2, node.active.expired_optimistic_election_infos.size ()); +} +} diff --git a/nano/core_test/confirmation_solicitor.cpp b/nano/core_test/confirmation_solicitor.cpp index 260b39b8..ed041530 100644 --- a/nano/core_test/confirmation_solicitor.cpp +++ b/nano/core_test/confirmation_solicitor.cpp @@ -34,12 +34,12 @@ TEST (confirmation_solicitor, batches) nano::lock_guard guard (node2.active.mutex); for (size_t i (0); i < nano::network::confirm_req_hashes_max; ++i) { - auto election (std::make_shared (node2, send, nullptr, false)); + auto election (std::make_shared (node2, send, nullptr, false, nano::election_behavior::normal)); ASSERT_FALSE (solicitor.add (*election)); } ASSERT_EQ (1, solicitor.max_confirm_req_batches); // Reached the maximum amount of requests for the channel - auto election (std::make_shared (node2, send, nullptr, false)); + auto election (std::make_shared (node2, send, nullptr, false, nano::election_behavior::normal)); ASSERT_TRUE (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)); @@ -75,7 +75,7 @@ TEST (confirmation_solicitor, different_hash) send->sideband_set ({}); { nano::lock_guard guard (node2.active.mutex); - auto election (std::make_shared (node2, send, nullptr, false)); + auto election (std::make_shared (node2, send, nullptr, false, nano::election_behavior::normal)); // Add a vote for something else, not the winner election->last_votes[representative.account] = { std::chrono::steady_clock::now (), 1, 1 }; // Ensure the request and broadcast goes through @@ -114,7 +114,7 @@ TEST (confirmation_solicitor, bypass_max_requests_cap) send->sideband_set ({}); { nano::lock_guard guard (node2.active.mutex); - auto election (std::make_shared (node2, send, nullptr, false)); + auto election (std::make_shared (node2, send, nullptr, false, nano::election_behavior::normal)); // Add a vote for something else, not the winner for (auto const & rep : representatives) { @@ -130,7 +130,7 @@ TEST (confirmation_solicitor, bypass_max_requests_cap) solicitor.prepare (representatives); { nano::lock_guard guard (node2.active.mutex); - auto election (std::make_shared (node2, send, nullptr, false)); + auto election (std::make_shared (node2, send, nullptr, false, nano::election_behavior::normal)); // Erase all votes election->last_votes.clear (); ASSERT_FALSE (solicitor.add (*election)); diff --git a/nano/core_test/frontiers_confirmation.cpp b/nano/core_test/frontiers_confirmation.cpp index b87b2146..2779aa69 100644 --- a/nano/core_test/frontiers_confirmation.cpp +++ b/nano/core_test/frontiers_confirmation.cpp @@ -130,7 +130,7 @@ TEST (frontiers_confirmation, prioritize_frontiers) node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); ASSERT_TRUE (priority_orders_match (node->active.priority_wallet_cementable_frontiers, std::array{ key3.pub, nano::genesis_account, key4.pub, key1.pub, key2.pub })); uint64_t election_count = 0; - node->active.confirm_prioritized_frontiers (transaction); + node->active.confirm_prioritized_frontiers (transaction, 100, election_count); // Check that the active transactions roots contains the frontiers ASSERT_TIMELY (10s, node->active.size () == num_accounts); @@ -143,6 +143,72 @@ TEST (frontiers_confirmation, prioritize_frontiers) } } +TEST (frontiers_confirmation, prioritize_frontiers_max_optimistic_elections) +{ + nano::system system; + // Prevent frontiers being confirmed as it will affect the priorization checking + 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); + + node->ledger.cache.cemented_count = node->ledger.bootstrap_weight_max_blocks - 1; + auto max_optimistic_election_count_under_hardcoded_weight = node->active.max_optimistic (); + node->ledger.cache.cemented_count = node->ledger.bootstrap_weight_max_blocks; + auto max_optimistic_election_count = node->active.max_optimistic (); + ASSERT_GT (max_optimistic_election_count_under_hardcoded_weight, max_optimistic_election_count); + + for (auto i = 0; i < max_optimistic_election_count * 2; ++i) + { + auto transaction = node->store.tx_begin_write (); + auto latest = node->latest (nano::genesis_account); + nano::keypair key; + nano::send_block send (latest, key.pub, node->config.online_weight_minimum.number () + 10000, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *system.work.generate (latest)); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + nano::open_block open (send.hash (), nano::genesis_account, key.pub, key.prv, key.pub, *system.work.generate (key.pub)); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open).code); + } + + { + nano::unique_lock lk (node->active.mutex); + node->active.frontiers_confirmation (lk); + } + + ASSERT_EQ (max_optimistic_election_count, node->active.roots.size ()); + + nano::account next_frontier_account{ 2 }; + node->active.next_frontier_account = next_frontier_account; + + // Call frontiers confirmation again and confirm that next_frontier_account hasn't changed + { + nano::unique_lock lk (node->active.mutex); + node->active.frontiers_confirmation (lk); + } + + ASSERT_EQ (max_optimistic_election_count, node->active.roots.size ()); + ASSERT_EQ (next_frontier_account, node->active.next_frontier_account); +} + +TEST (frontiers_confirmation, expired_optimistic_elections_removal) +{ + 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); + + // This should be removed on the next prioritization call + node->active.expired_optimistic_election_infos.emplace (std::chrono::steady_clock::now () - (node->active.expired_optimistic_election_info_cutoff + 1min), nano::account (1)); + ASSERT_EQ (1, node->active.expired_optimistic_election_infos.size ()); + node->active.prioritize_frontiers_for_confirmation (node->store.tx_begin_read (), 0s, 0s); + ASSERT_EQ (0, node->active.expired_optimistic_election_infos.size ()); + + // This should not be removed on the next prioritization call + node->active.expired_optimistic_election_infos.emplace (std::chrono::steady_clock::now () - (node->active.expired_optimistic_election_info_cutoff - 1min), nano::account (1)); + ASSERT_EQ (1, node->active.expired_optimistic_election_infos.size ()); + node->active.prioritize_frontiers_for_confirmation (node->store.tx_begin_read (), 0s, 0s); + ASSERT_EQ (1, node->active.expired_optimistic_election_infos.size ()); +} +} + TEST (frontiers_confirmation, mode) { nano::genesis genesis; @@ -190,4 +256,3 @@ TEST (frontiers_confirmation, mode) ASSERT_EQ (0, node->active.size ()); } } -} diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index e942e8ea..6cbc2d14 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -4205,14 +4205,16 @@ TEST (node, dependency_graph) ASSERT_TIMELY (5s, node.active.empty ()); } -// Confirm a complex dependency graph starting from a frontier -TEST (node, DISABLED_dependency_graph_frontier) +// Confirm a complex dependency graph. Uses frontiers confirmation which will fail to +// confirm a frontier optimistically then fallback to pessimistic confirmation. +TEST (node, dependency_graph_frontier) { nano::system system; nano::node_config config (nano::get_available_port (), system.logging); config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; auto & node1 = *system.add_node (config); config.peering_port = nano::get_available_port (); + config.frontiers_confirmation = nano::frontiers_confirmation_mode::always; auto & node2 = *system.add_node (config); nano::state_block_builder builder; @@ -4360,23 +4362,14 @@ TEST (node, DISABLED_dependency_graph_frontier) ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *key3_epoch).code); } - ASSERT_TRUE (node1.active.empty () && node2.active.empty ()); - // node1 can vote, but only on the first block system.wallet (0)->insert_adhoc (nano::dev_genesis_key.prv); - // activate the graph frontier - // node2 activates dependencies in sequence until it reaches the first block - node2.block_confirm (node2.block (key3_epoch->hash ())); - - // Eventually the first block in the graph gets activated and confirmed via node1 - ASSERT_TIMELY (15s, node2.block_confirmed (gen_send1->hash ())); - - // Activate the first block in node1, allowing it to confirm all blocks for both nodes + ASSERT_TIMELY (10s, node2.active.active (gen_send1->qualified_root ())); node1.block_confirm (gen_send1); + ASSERT_TIMELY (15s, node1.ledger.cache.cemented_count == node1.ledger.cache.block_count); - ASSERT_TIMELY (5s, node2.ledger.cache.cemented_count == node2.ledger.cache.block_count); - ASSERT_TIMELY (5s, node1.active.empty () && node2.active.empty ()); + ASSERT_TIMELY (15s, node2.ledger.cache.cemented_count == node2.ledger.cache.block_count); } namespace nano diff --git a/nano/node/active_transactions.cpp b/nano/node/active_transactions.cpp index 48f28bd5..88569744 100644 --- a/nano/node/active_transactions.cpp +++ b/nano/node/active_transactions.cpp @@ -16,6 +16,8 @@ using namespace std::chrono; size_t constexpr nano::active_transactions::max_active_elections_frontier_insertion; +constexpr std::chrono::minutes nano::active_transactions::expired_optimistic_election_info_cutoff; + nano::active_transactions::active_transactions (nano::node & node_a, nano::confirmation_height_processor & confirmation_height_processor_a) : recently_dropped (node_a.stats), confirmation_height_processor (confirmation_height_processor_a), @@ -50,9 +52,38 @@ nano::active_transactions::~active_transactions () stop (); } -void nano::active_transactions::confirm_prioritized_frontiers (nano::transaction const & transaction_a) +bool nano::active_transactions::insert_election_from_frontiers_confirmation (std::shared_ptr const & block_a, nano::account const & account_a, nano::uint128_t previous_balance_a, nano::election_behavior election_behavior_a) +{ + bool inserted{ false }; + nano::lock_guard guard (mutex); + if (roots.get ().find (block_a->qualified_root ()) == roots.get ().end ()) + { + std::function const &)> election_confirmation_cb; + if (election_behavior_a == nano::election_behavior::optimistic) + { + election_confirmation_cb = [this](std::shared_ptr const & block_a) { + --optimistic_elections_count; + }; + } + + auto insert_result = insert_impl (block_a, previous_balance_a, election_behavior_a, election_confirmation_cb); + inserted = insert_result.inserted; + if (inserted) + { + insert_result.election->transition_active (); + if (insert_result.election->optimistic ()) + { + ++optimistic_elections_count; + } + } + } + return inserted; +} + +nano::frontiers_confirmation_info nano::active_transactions::get_frontiers_confirmation_info () { // Limit maximum count of elections to start + nano::frontiers_confirmation_info frontiers_confirmation_info; auto rep_counts (node.wallets.reps ()); bool representative (node.config.enable_voting && rep_counts.voting > 0); bool half_princpal_representative (representative && rep_counts.half_principal > 0); @@ -73,15 +104,36 @@ void nano::active_transactions::confirm_prioritized_frontiers (nano::transaction { max_elections = max_active - roots_size; } + } + else + { + max_elections = 0; + } - size_t elections_count (0); - nano::unique_lock lk (mutex); - auto start_elections_for_prioritized_frontiers = [&transaction_a, &elections_count, max_elections, &lk, this](prioritize_num_uncemented & cementable_frontiers) { - while (!cementable_frontiers.empty () && !this->stopped && elections_count < max_elections) + return nano::frontiers_confirmation_info{ max_elections, agressive_mode }; +} + +void nano::active_transactions::set_next_frontier_check (bool agressive_mode_a) +{ + auto request_interval (std::chrono::milliseconds (node.network_params.network.request_interval_ms)); + auto rel_time_next_frontier_check = request_interval * (agressive_mode_a ? 20 : 60); + // Decrease check time for dev network + int dev_network_factor = node.network_params.network.is_dev_network () ? 1000 : 1; + + next_frontier_check = steady_clock::now () + (rel_time_next_frontier_check / dev_network_factor); +} + +void nano::active_transactions::confirm_prioritized_frontiers (nano::transaction const & transaction_a, uint64_t max_elections_a, uint64_t & elections_count_a) +{ + nano::unique_lock lk (mutex); + auto start_elections_for_prioritized_frontiers = [&transaction_a, &elections_count_a, max_elections_a, &lk, this](prioritize_num_uncemented & cementable_frontiers) { + while (!cementable_frontiers.empty () && !this->stopped && elections_count_a < max_elections_a && optimistic_elections_count < max_optimistic ()) + { + auto cementable_account_front_it = cementable_frontiers.get ().begin (); + auto cementable_account = *cementable_account_front_it; + cementable_frontiers.get ().erase (cementable_account_front_it); + if (expired_optimistic_election_infos.get ().count (cementable_account.account) == 0) { - auto cementable_account_front_it = cementable_frontiers.get ().begin (); - auto cementable_account = *cementable_account_front_it; - cementable_frontiers.get ().erase (cementable_account_front_it); lk.unlock (); nano::account_info info; auto error = this->node.store.account_get (transaction_a, cementable_account.account, info); @@ -97,33 +149,21 @@ void nano::active_transactions::confirm_prioritized_frontiers (nano::transaction { auto block (this->node.store.block_get (transaction_a, info.head)); auto previous_balance (this->node.ledger.balance (transaction_a, block->previous ())); - lk.lock (); - if (roots.get ().find (block->qualified_root ()) == roots.get ().end ()) + auto inserted_election = this->insert_election_from_frontiers_confirmation (block, cementable_account.account, previous_balance, nano::election_behavior::optimistic); + if (inserted_election) { - auto insert_result = this->insert_impl (block, previous_balance); - if (insert_result.inserted) - { - insert_result.election->transition_active (); - ++elections_count; - } + ++elections_count_a; } - lk.unlock (); } } } lk.lock (); } - }; - start_elections_for_prioritized_frontiers (priority_cementable_frontiers); - start_elections_for_prioritized_frontiers (priority_wallet_cementable_frontiers); + } + }; - auto request_interval (std::chrono::milliseconds (node.network_params.network.request_interval_ms)); - auto rel_time_next_frontier_check = request_interval * (agressive_mode ? 20 : 60); - // Decrease check time for dev network - int dev_network_factor = is_dev_network ? 1000 : 1; - - next_frontier_check = steady_clock::now () + (rel_time_next_frontier_check / dev_network_factor); - } + start_elections_for_prioritized_frontiers (priority_wallet_cementable_frontiers); + start_elections_for_prioritized_frontiers (priority_cementable_frontiers); } void nano::active_transactions::block_cemented_callback (std::shared_ptr const & block_a) @@ -276,6 +316,15 @@ void nano::active_transactions::request_confirm (nano::unique_lock & bool const overflow_l (unconfirmed_count_l > node.config.active_elections_size && election_l->election_start < election_ttl_cutoff_l && !node.wallets.watcher->is_watched (i->root)); if (overflow_l || election_l->transition_time (solicitor)) { + if (election_l->optimistic () && election_l->failed ()) + { + if (election_l->confirmation_request_count != 0) + { + add_expired_optimistic_election (*election_l); + } + --optimistic_elections_count; + } + election_l->cleanup (); i = sorted_roots_l.erase (i); } @@ -300,30 +349,156 @@ void nano::active_transactions::request_confirm (nano::unique_lock & } } +void nano::active_transactions::add_expired_optimistic_election (nano::election const & election_a) +{ + auto account = election_a.status.winner->account (); + if (account.is_zero ()) + { + account = election_a.status.winner->sideband ().account; + } + + auto it = expired_optimistic_election_infos.get ().find (account); + if (it != expired_optimistic_election_infos.get ().end ()) + { + expired_optimistic_election_infos.get ().modify (it, [](auto & expired_optimistic_election) { + expired_optimistic_election.expired_time = std::chrono::steady_clock::now (); + expired_optimistic_election.election_started = false; + }); + } + else + { + expired_optimistic_election_infos.emplace (std::chrono::steady_clock::now (), account); + } + + // Expire the oldest one if a maximum is reached + auto const max_expired_optimistic_election_infos = 10000; + if (expired_optimistic_election_infos.size () > max_expired_optimistic_election_infos) + { + expired_optimistic_election_infos.get ().erase (expired_optimistic_election_infos.get ().begin ()); + } + expired_optimistic_election_infos_size = expired_optimistic_election_infos.size (); +} + +unsigned nano::active_transactions::max_optimistic () +{ + return node.ledger.cache.cemented_count < node.ledger.bootstrap_weight_max_blocks ? std::numeric_limits::max () : 50u; +} + void nano::active_transactions::frontiers_confirmation (nano::unique_lock & lock_a) +{ + // Spend some time prioritizing accounts with the most uncemented blocks to reduce voting traffic + auto request_interval = std::chrono::milliseconds (node.network_params.network.request_interval_ms); + // Spend longer searching ledger accounts when there is a low amount of elections going on + auto low_active = roots.size () < 1000; + auto time_to_spend_prioritizing_ledger_accounts = request_interval / (low_active ? 20 : 100); + auto time_to_spend_prioritizing_wallet_accounts = request_interval / 250; + auto time_to_spend_confirming_pessimistic_accounts = time_to_spend_prioritizing_ledger_accounts; + lock_a.unlock (); + auto transaction = node.store.tx_begin_read (); + prioritize_frontiers_for_confirmation (transaction, node.network_params.network.is_dev_network () ? std::chrono::milliseconds (50) : time_to_spend_prioritizing_ledger_accounts, time_to_spend_prioritizing_wallet_accounts); + auto frontiers_confirmation_info = get_frontiers_confirmation_info (); + if (frontiers_confirmation_info.can_start_elections ()) + { + uint64_t elections_count (0); + confirm_prioritized_frontiers (transaction, frontiers_confirmation_info.max_elections, elections_count); + confirm_expired_frontiers_pessimistically (transaction, frontiers_confirmation_info.max_elections, elections_count); + set_next_frontier_check (frontiers_confirmation_info.aggressive_mode); + } + lock_a.lock (); +} + +/* + * This function takes the expired_optimistic_election_infos generated from failed elections from frontiers confirmations and starts + * confirming blocks at cemented height + 1 (cemented frontier successor) for an account only if all dependent blocks already + * confirmed. + */ +void nano::active_transactions::confirm_expired_frontiers_pessimistically (nano::transaction const & transaction_a, uint64_t max_elections_a, uint64_t & elections_count_a) +{ + auto i{ node.store.latest_begin (transaction_a, next_frontier_account) }; + auto n{ node.store.latest_end () }; + nano::timer timer (nano::timer_state::started); + nano::confirmation_height_info confirmation_height_info; + + // Loop through any expired optimistic elections which have not been started yet. This tag stores already started ones first + std::vector elections_started_for_account; + for (auto i = expired_optimistic_election_infos.get ().lower_bound (false); i != expired_optimistic_election_infos.get ().end ();) + { + if (stopped || elections_count_a >= max_elections_a) + { + break; + } + + auto const & account{ i->account }; + nano::account_info account_info; + bool should_delete{ true }; + if (!node.store.account_get (transaction_a, account, account_info) && !node.store.confirmation_height_get (transaction_a, account, confirmation_height_info)) + { + if (account_info.block_count > confirmation_height_info.height) + { + should_delete = false; + std::shared_ptr previous_block; + std::shared_ptr block; + if (confirmation_height_info.height == 0) + { + block = node.store.block_get (transaction_a, account_info.open_block); + } + else + { + previous_block = node.store.block_get (transaction_a, confirmation_height_info.frontier); + block = node.store.block_get (transaction_a, previous_block->sideband ().successor); + } + + if (block && !node.confirmation_height_processor.is_processing_block (block->hash ()) && node.ledger.dependents_confirmed (transaction_a, *block)) + { + nano::uint128_t previous_balance{ 0 }; + if (previous_block && previous_block->balance ().is_zero ()) + { + previous_balance = previous_block->sideband ().balance.number (); + } + + auto inserted_election = insert_election_from_frontiers_confirmation (block, account, previous_balance, nano::election_behavior::normal); + if (inserted_election) + { + ++elections_count_a; + } + elections_started_for_account.push_back (i->account); + } + } + } + + if (should_delete) + { + // This account is confirmed already or doesn't exist. + i = expired_optimistic_election_infos.get ().erase (i); + expired_optimistic_election_infos_size = expired_optimistic_election_infos.size (); + } + else + { + ++i; + } + } + + for (auto const & account : elections_started_for_account) + { + auto it = expired_optimistic_election_infos.get ().find (account); + debug_assert (it != expired_optimistic_election_infos.get ().end ()); + expired_optimistic_election_infos.get ().modify (it, [](auto & expired_optimistic_election_info_a) { + expired_optimistic_election_info_a.election_started = true; + }); + } +} + +bool nano::active_transactions::should_do_frontiers_confirmation () const { /* * Confirm frontiers when there aren't many confirmations already pending and node finished initial bootstrap - */ + */ auto pending_confirmation_height_size (confirmation_height_processor.awaiting_processing_size ()); auto bootstrap_weight_reached (node.ledger.cache.block_count >= node.ledger.bootstrap_weight_max_blocks); auto disabled_confirmation_mode = (node.config.frontiers_confirmation == nano::frontiers_confirmation_mode::disabled); auto conf_height_capacity_reached = pending_confirmation_height_size > confirmed_frontiers_max_pending_size; auto all_cemented = node.ledger.cache.block_count == node.ledger.cache.cemented_count; - if (!disabled_confirmation_mode && bootstrap_weight_reached && !conf_height_capacity_reached && !all_cemented) - { - // Spend some time prioritizing accounts with the most uncemented blocks to reduce voting traffic - auto request_interval = std::chrono::milliseconds (node.network_params.network.request_interval_ms); - // Spend longer searching ledger accounts when there is a low amount of elections going on - auto low_active = roots.size () < 1000; - auto time_spent_prioritizing_ledger_accounts = request_interval / (low_active ? 20 : 100); - auto time_spent_prioritizing_wallet_accounts = request_interval / 250; - lock_a.unlock (); - auto transaction = node.store.tx_begin_read (); - prioritize_frontiers_for_confirmation (transaction, node.network_params.network.is_dev_network () ? std::chrono::milliseconds (50) : time_spent_prioritizing_ledger_accounts, time_spent_prioritizing_wallet_accounts); - confirm_prioritized_frontiers (transaction); - lock_a.lock (); - } + return (!disabled_confirmation_mode && bootstrap_weight_reached && !conf_height_capacity_reached && !all_cemented); } void nano::active_transactions::request_loop () @@ -352,7 +527,10 @@ void nano::active_transactions::request_loop () const auto stamp_l = std::chrono::steady_clock::now (); // frontiers_confirmation should be above update_active_multiplier to ensure new sorted roots are updated - frontiers_confirmation (lock); + if (should_do_frontiers_confirmation ()) + { + frontiers_confirmation (lock); + } update_active_multiplier (lock); request_confirm (lock); @@ -365,11 +543,12 @@ void nano::active_transactions::request_loop () } } -void nano::active_transactions::prioritize_account_for_confirmation (nano::active_transactions::prioritize_num_uncemented & cementable_frontiers_a, size_t & cementable_frontiers_size_a, nano::account const & account_a, nano::account_info const & info_a, uint64_t confirmation_height) +bool nano::active_transactions::prioritize_account_for_confirmation (nano::active_transactions::prioritize_num_uncemented & cementable_frontiers_a, size_t & cementable_frontiers_size_a, nano::account const & account_a, nano::account_info const & info_a, uint64_t confirmation_height_a) { - if (info_a.block_count > confirmation_height && !confirmation_height_processor.is_processing_block (info_a.head)) + auto inserted_new{ false }; + if (info_a.block_count > confirmation_height_a && !confirmation_height_processor.is_processing_block (info_a.head)) { - auto num_uncemented = info_a.block_count - confirmation_height; + auto num_uncemented = info_a.block_count - confirmation_height_a; nano::lock_guard guard (mutex); auto it = cementable_frontiers_a.get ().find (account_a); if (it != cementable_frontiers_a.get ().end ()) @@ -399,11 +578,13 @@ void nano::active_transactions::prioritize_account_for_confirmation (nano::activ } else { + inserted_new = true; cementable_frontiers_a.get ().emplace (account_a, num_uncemented); } } cementable_frontiers_size_a = cementable_frontiers_a.size (); } + return inserted_new; } void nano::active_transactions::prioritize_frontiers_for_confirmation (nano::transaction const & transaction_a, std::chrono::milliseconds ledger_account_traversal_max_time_a, std::chrono::milliseconds wallet_account_traversal_max_time_a) @@ -418,8 +599,18 @@ void nano::active_transactions::prioritize_frontiers_for_confirmation (nano::tra priority_cementable_frontiers_size = priority_cementable_frontiers.size (); priority_wallet_cementable_frontiers_size = priority_wallet_cementable_frontiers.size (); } - nano::timer wallet_account_timer; - wallet_account_timer.start (); + + nano::timer wallet_account_timer (nano::timer_state::started); + // Remove any old expired optimistic elections so they are no longer excluded in subsequent checks + auto expired_cutoff_it (expired_optimistic_election_infos.get ().lower_bound (std::chrono::steady_clock::now () - expired_optimistic_election_info_cutoff)); + expired_optimistic_election_infos.get ().erase (expired_optimistic_election_infos.get ().begin (), expired_cutoff_it); + expired_optimistic_election_infos_size = expired_optimistic_election_infos.size (); + + auto num_new_inserted{ 0u }; + auto should_iterate = [this, &num_new_inserted]() { + auto max_optimistic_l = max_optimistic (); + return !stopped && (max_optimistic_l > optimistic_elections_count && max_optimistic_l - optimistic_elections_count > num_new_inserted); + }; if (!skip_wallets) { @@ -432,7 +623,7 @@ void nano::active_transactions::prioritize_frontiers_for_confirmation (nano::tra { skip_wallets = true; } - for (auto item_it = items.cbegin (); item_it != items.cend (); ++item_it) + for (auto item_it = items.cbegin (); item_it != items.cend () && should_iterate (); ++item_it) { // Skip this wallet if it has been traversed already while there are others still awaiting if (wallet_ids_already_iterated.find (item_it->first) != wallet_ids_already_iterated.end ()) @@ -449,10 +640,10 @@ void nano::active_transactions::prioritize_frontiers_for_confirmation (nano::tra auto i (wallet->store.begin (wallet_transaction, next_wallet_frontier_account)); auto n (wallet->store.end ()); nano::confirmation_height_info confirmation_height_info; - for (; i != n; ++i) + for (; i != n && should_iterate (); ++i) { auto const & account (i->first); - if (!node.store.account_get (transaction_a, account, info) && !node.store.confirmation_height_get (transaction_a, account, confirmation_height_info)) + if (expired_optimistic_election_infos.get ().count (account) == 0 && !node.store.account_get (transaction_a, account, info) && !node.store.confirmation_height_get (transaction_a, account, confirmation_height_info)) { // If it exists in normal priority collection delete from there. auto it = priority_cementable_frontiers.find (account); @@ -463,7 +654,11 @@ void nano::active_transactions::prioritize_frontiers_for_confirmation (nano::tra priority_cementable_frontiers_size = priority_cementable_frontiers.size (); } - prioritize_account_for_confirmation (priority_wallet_cementable_frontiers, priority_wallet_cementable_frontiers_size, account, info, confirmation_height_info.height); + auto insert_newed = prioritize_account_for_confirmation (priority_wallet_cementable_frontiers, priority_wallet_cementable_frontiers_size, account, info, confirmation_height_info.height); + if (insert_newed) + { + ++num_new_inserted; + } if (wallet_account_timer.since_start () >= wallet_account_traversal_max_time_a) { @@ -489,21 +684,23 @@ void nano::active_transactions::prioritize_frontiers_for_confirmation (nano::tra } } - nano::timer timer; - timer.start (); - + nano::timer timer (nano::timer_state::started); auto i (node.store.latest_begin (transaction_a, next_frontier_account)); auto n (node.store.latest_end ()); nano::confirmation_height_info confirmation_height_info; - for (; i != n && !stopped; ++i) + for (; i != n && should_iterate (); ++i) { auto const & account (i->first); auto const & info (i->second); if (priority_wallet_cementable_frontiers.find (account) == priority_wallet_cementable_frontiers.end ()) { - if (!node.store.confirmation_height_get (transaction_a, account, confirmation_height_info)) + if (expired_optimistic_election_infos.get ().count (account) == 0 && !node.store.confirmation_height_get (transaction_a, account, confirmation_height_info)) { - prioritize_account_for_confirmation (priority_cementable_frontiers, priority_cementable_frontiers_size, account, info, confirmation_height_info.height); + auto insert_newed = prioritize_account_for_confirmation (priority_cementable_frontiers, priority_cementable_frontiers_size, account, info, confirmation_height_info.height); + if (insert_newed) + { + ++num_new_inserted; + } } } next_frontier_account = account.number () + 1; @@ -541,7 +738,7 @@ void nano::active_transactions::stop () roots.clear (); } -nano::election_insertion_result nano::active_transactions::insert_impl (std::shared_ptr const & block_a, boost::optional const & previous_balance_a, std::function)> const & confirmation_action_a) +nano::election_insertion_result nano::active_transactions::insert_impl (std::shared_ptr const & block_a, boost::optional const & previous_balance_a, nano::election_behavior election_behavior_a, std::function)> const & confirmation_action_a) { debug_assert (block_a->has_sideband ()); nano::election_insertion_result result; @@ -568,7 +765,7 @@ nano::election_insertion_result nano::active_transactions::insert_impl (std::sha } 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); + result.election = nano::make_shared (node, block_a, confirmation_action_a, prioritized, election_behavior_a); roots.get ().emplace (nano::active_transactions::conflict_info{ root, multiplier, result.election, epoch, previous_balance }); blocks.emplace (hash, result.election); result.election->insert_inactive_votes_cache (hash); @@ -590,10 +787,10 @@ nano::election_insertion_result nano::active_transactions::insert_impl (std::sha return result; } -nano::election_insertion_result nano::active_transactions::insert (std::shared_ptr const & block_a, boost::optional const & previous_balance_a, std::function)> const & confirmation_action_a) +nano::election_insertion_result nano::active_transactions::insert (std::shared_ptr const & block_a, boost::optional const & previous_balance_a, nano::election_behavior election_behavior_a, std::function)> const & confirmation_action_a) { nano::lock_guard lock (mutex); - return insert_impl (block_a, previous_balance_a, confirmation_action_a); + return insert_impl (block_a, previous_balance_a, election_behavior_a, confirmation_action_a); } // Validate a vote and apply it to the current election if one exists @@ -1172,7 +1369,10 @@ nano::inactive_cache_status nano::active_transactions::inactive_votes_bootstrap_ auto block = node.store.block_get (transaction, hash_a); if (block && status.election_started && !previously_a.election_started && !node.block_confirmed_or_being_confirmed (transaction, hash_a)) { - insert_impl (block); + if (node.ledger.cache.cemented_count >= node.ledger.bootstrap_weight_max_blocks) + { + insert_impl (block); + } } else if (!block && status.bootstrap_started && !previously_a.bootstrap_started) { @@ -1193,6 +1393,17 @@ account (account_a), blocks_uncemented (blocks_uncemented_a) { } +nano::expired_optimistic_election_info::expired_optimistic_election_info (std::chrono::steady_clock::time_point expired_time_a, nano::account account_a) : +expired_time (expired_time_a), +account (account_a) +{ +} + +bool nano::frontiers_confirmation_info::can_start_elections () const +{ + return max_elections > 0; +} + std::unique_ptr nano::collect_container_info (active_transactions & active_transactions, const std::string & name) { size_t roots_count; @@ -1214,9 +1425,11 @@ std::unique_ptr nano::collect_container_info (ac composite->add_component (std::make_unique (container_info{ "election_winner_details", active_transactions.election_winner_details_size (), sizeof (decltype (active_transactions.election_winner_details)::value_type) })); composite->add_component (std::make_unique (container_info{ "recently_confirmed", recently_confirmed_count, sizeof (decltype (active_transactions.recently_confirmed)::value_type) })); composite->add_component (std::make_unique (container_info{ "recently_cemented", recently_cemented_count, sizeof (decltype (active_transactions.recently_cemented)::value_type) })); - composite->add_component (std::make_unique (container_info{ "priority_wallet_cementable_frontiers_count", active_transactions.priority_wallet_cementable_frontiers_size (), sizeof (nano::cementable_account) })); - composite->add_component (std::make_unique (container_info{ "priority_cementable_frontiers_count", active_transactions.priority_cementable_frontiers_size (), sizeof (nano::cementable_account) })); - composite->add_component (std::make_unique (container_info{ "inactive_votes_cache_count", active_transactions.inactive_votes_cache_size (), sizeof (nano::gap_information) })); + composite->add_component (std::make_unique (container_info{ "priority_wallet_cementable_frontiers", active_transactions.priority_wallet_cementable_frontiers_size (), sizeof (nano::cementable_account) })); + composite->add_component (std::make_unique (container_info{ "priority_cementable_frontiers", active_transactions.priority_cementable_frontiers_size (), sizeof (nano::cementable_account) })); + composite->add_component (std::make_unique (container_info{ "expired_optimistic_election_infos", active_transactions.expired_optimistic_election_infos_size, sizeof (decltype (active_transactions.expired_optimistic_election_infos)::value_type) })); + composite->add_component (std::make_unique (container_info{ "inactive_votes_cache", active_transactions.inactive_votes_cache_size (), sizeof (nano::gap_information) })); + composite->add_component (std::make_unique (container_info{ "optimistic_elections_count", active_transactions.optimistic_elections_count, 0 })); // This isn't an extra container, is just to expose the count easily composite->add_component (collect_container_info (active_transactions.generator, "generator")); return composite; } diff --git a/nano/node/active_transactions.hpp b/nano/node/active_transactions.hpp index f8175357..f07d9451 100644 --- a/nano/node/active_transactions.hpp +++ b/nano/node/active_transactions.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -74,6 +75,25 @@ public: } }; +class expired_optimistic_election_info final +{ +public: + expired_optimistic_election_info (std::chrono::steady_clock::time_point, nano::account); + + std::chrono::steady_clock::time_point expired_time; + nano::account account; + bool election_started{ false }; +}; + +class frontiers_confirmation_info +{ +public: + bool can_start_elections () const; + + size_t max_elections{ 0 }; + bool aggressive_mode{ false }; +}; + class dropped_elections final { public: @@ -132,6 +152,8 @@ class active_transactions final class tag_uncemented {}; class tag_arrival {}; class tag_hash {}; + class tag_expired_time {}; + class tag_election_started {}; // clang-format on public: @@ -152,7 +174,7 @@ public: // Start an election for a block // Call action with confirmed block, may be different than what we started with // clang-format off - nano::election_insertion_result insert (std::shared_ptr const &, boost::optional const & = boost::none, std::function)> const & = [](std::shared_ptr) {}); + nano::election_insertion_result insert (std::shared_ptr const &, boost::optional const & = boost::none, nano::election_behavior = nano::election_behavior::normal, std::function)> const & = nullptr); // clang-format on // Distinguishes replay votes, cannot be determined if the block is not in any election nano::vote_code vote (std::shared_ptr); @@ -216,7 +238,7 @@ private: // Call action with confirmed block, may be different than what we started with // clang-format off - nano::election_insertion_result insert_impl (std::shared_ptr const &, boost::optional const & = boost::none, std::function)> const & = [](std::shared_ptr) {}); + nano::election_insertion_result insert_impl (std::shared_ptr const &, boost::optional const & = boost::none, nano::election_behavior = nano::election_behavior::normal, std::function)> const & = nullptr); // clang-format on // Returns false if the election difficulty was updated bool update_difficulty_impl (roots_iterator const &, nano::block const &); @@ -254,23 +276,43 @@ private: mi::ordered_non_unique, mi::member, std::greater>>>; + + boost::multi_index_container, + mi::member>, + mi::hashed_unique, + mi::member>, + mi::ordered_non_unique, + mi::member, std::greater>>> + expired_optimistic_election_infos; // clang-format on + std::atomic expired_optimistic_election_infos_size{ 0 }; // Frontiers confirmation - void confirm_prioritized_frontiers (nano::transaction const &); + nano::frontiers_confirmation_info get_frontiers_confirmation_info (); + void confirm_prioritized_frontiers (nano::transaction const &, uint64_t, uint64_t &); + void confirm_expired_frontiers_pessimistically (nano::transaction const &, uint64_t, uint64_t &); void frontiers_confirmation (nano::unique_lock &); + bool insert_election_from_frontiers_confirmation (std::shared_ptr const &, nano::account const &, nano::uint128_t, nano::election_behavior); nano::account next_frontier_account{ 0 }; std::chrono::steady_clock::time_point next_frontier_check{ std::chrono::steady_clock::now () }; constexpr static size_t max_active_elections_frontier_insertion{ 1000 }; prioritize_num_uncemented priority_wallet_cementable_frontiers; prioritize_num_uncemented priority_cementable_frontiers; - void prioritize_frontiers_for_confirmation (nano::transaction const &, std::chrono::milliseconds, std::chrono::milliseconds); std::unordered_set wallet_ids_already_iterated; std::unordered_map next_wallet_id_accounts; bool skip_wallets{ false }; - void prioritize_account_for_confirmation (prioritize_num_uncemented &, size_t &, nano::account const &, nano::account_info const &, uint64_t); + std::atomic optimistic_elections_count{ 0 }; + void prioritize_frontiers_for_confirmation (nano::transaction const &, std::chrono::milliseconds, std::chrono::milliseconds); + bool prioritize_account_for_confirmation (prioritize_num_uncemented &, size_t &, nano::account const &, nano::account_info const &, uint64_t); + unsigned max_optimistic (); + void set_next_frontier_check (bool); + void add_expired_optimistic_election (nano::election const &); + bool should_do_frontiers_confirmation () const; static size_t constexpr max_priority_cementable_frontiers{ 100000 }; static size_t constexpr confirmed_frontiers_max_pending_size{ 10000 }; + static std::chrono::minutes constexpr expired_optimistic_election_info_cutoff{ 30 }; // clang-format off using ordered_cache = boost::multi_index_container collect_container_info (active_transactions & active_transactions, const std::string & name); diff --git a/nano/node/confirmation_height_processor.hpp b/nano/node/confirmation_height_processor.hpp index 8761bf44..eb48789a 100644 --- a/nano/node/confirmation_height_processor.hpp +++ b/nano/node/confirmation_height_processor.hpp @@ -94,6 +94,7 @@ private: friend class confirmation_height_long_chains_Test; friend class confirmation_height_many_accounts_single_confirmation_Test; friend class request_aggregator_cannot_vote_Test; + friend class active_transactions_pessimistic_elections_Test; }; std::unique_ptr collect_container_info (confirmation_height_processor &, const std::string &); diff --git a/nano/node/election.cpp b/nano/node/election.cpp index a3d07b9a..caca8225 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -22,9 +22,10 @@ nano::election_vote_result::election_vote_result (bool replay_a, bool processed_ processed = processed_a; } -nano::election::election (nano::node & node_a, std::shared_ptr block_a, std::function)> const & confirmation_action_a, bool prioritized_a) : +nano::election::election (nano::node & node_a, std::shared_ptr block_a, std::function)> const & confirmation_action_a, bool prioritized_a, nano::election_behavior election_behavior_a) : confirmation_action (confirmation_action_a), prioritized_m (prioritized_a), +election_behavior (election_behavior_a), node (node_a), 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 }), height (block_a->sideband ().height), @@ -52,9 +53,12 @@ void nano::election::confirm_once (nano::election_status_type type_a) auto confirmation_action_l (confirmation_action); node.active.election_winner_details.emplace (status.winner->hash (), shared_from_this ()); node.active.add_recently_confirmed (status_l.winner->qualified_root (), status_l.winner->hash ()); - node_l->process_confirmed (status_l); + node.process_confirmed (status_l); node.background ([node_l, status_l, confirmation_action_l]() { - confirmation_action_l (status_l.winner); + if (confirmation_action_l) + { + confirmation_action_l (status_l.winner); + } }); } } @@ -137,7 +141,7 @@ bool nano::election::state_change (nano::election::state_t expected_a, nano::ele void nano::election::send_confirm_req (nano::confirmation_solicitor & solicitor_a) { - if (base_latency () * 5 < std::chrono::steady_clock::now () - last_req) + if ((base_latency () * (optimistic () ? 10 : 5)) < (std::chrono::steady_clock::now () - last_req)) { if (!solicitor_a.add (*this)) { @@ -163,6 +167,11 @@ bool nano::election::confirmed () const return state_m == nano::election::state_t::confirmed || state_m == nano::election::state_t::expired_confirmed; } +bool nano::election::failed () const +{ + return state_m == nano::election::state_t::expired_unconfirmed; +} + void nano::election::broadcast_block (nano::confirmation_solicitor & solicitor_a) { if (base_latency () * 15 < std::chrono::steady_clock::now () - last_block) @@ -210,7 +219,9 @@ bool nano::election::transition_time (nano::confirmation_solicitor & solicitor_a debug_assert (false); break; } - if (!confirmed () && std::chrono::minutes (5) < std::chrono::steady_clock::now () - election_start) + auto optimistic_expiration_time = node.network_params.network.is_dev_network () ? 500 : 60 * 1000; + auto expire_time = std::chrono::milliseconds (optimistic () ? optimistic_expiration_time : 5 * 60 * 1000); + if (!confirmed () && expire_time < std::chrono::steady_clock::now () - election_start) { result = true; state_change (state_m.load (), nano::election::state_t::expired_unconfirmed); @@ -465,6 +476,11 @@ bool nano::election::prioritized () const return prioritized_m; } +bool nano::election::optimistic () const +{ + return election_behavior == nano::election_behavior::optimistic; +} + void nano::election::prioritize_election (nano::vote_generator_session & generator_session_a) { debug_assert (!node.active.mutex.try_lock ()); diff --git a/nano/node/election.hpp b/nano/node/election.hpp index 03937fcb..8bfeaa43 100644 --- a/nano/node/election.hpp +++ b/nano/node/election.hpp @@ -30,6 +30,12 @@ public: bool replay{ false }; bool processed{ false }; }; +enum class election_behavior +{ + normal, + optimistic +}; + 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 @@ -67,7 +73,7 @@ private: // State management std::atomic prioritized_m = { false }; public: - election (nano::node &, std::shared_ptr, std::function)> const &, bool); + election (nano::node &, std::shared_ptr, std::function)> const &, bool, nano::election_behavior); nano::election_vote_result vote (nano::account, uint64_t, nano::block_hash); nano::tally_t tally (); // Check if we have vote quorum @@ -80,6 +86,7 @@ public: size_t last_votes_size (); size_t insert_inactive_votes_cache (nano::block_hash const &); bool prioritized () const; + bool optimistic () const; void prioritize_election (nano::vote_generator_session &); // Erase all blocks from active and, if not confirmed, clear digests from network filters void cleanup (); @@ -93,6 +100,8 @@ private: public: bool confirmed () const; + bool failed () const; + nano::election_behavior election_behavior{ nano::election_behavior::normal }; nano::node & node; std::unordered_map last_votes; std::unordered_map> blocks; diff --git a/nano/node/node.cpp b/nano/node/node.cpp index 87cf377a..55f90d33 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -526,7 +526,7 @@ 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 ()) && (ledger.dependents_confirmed (transaction_a, *ledger_block) || modified_a < nano::seconds_since_epoch () - 300 || !block_arrival.recent (block_a->hash ()))) { std::weak_ptr this_w (shared_from_this ()); - auto election = active.insert (ledger_block, boost::none, [this_w, root, root_block_type = block_a->type ()](std::shared_ptr) { + auto election = active.insert (ledger_block, boost::none, nano::election_behavior::normal, [this_w, root, root_block_type = block_a->type ()](std::shared_ptr) { if (auto this_l = this_w.lock ()) { auto attempt (this_l->bootstrap_initiator.current_attempt ()); diff --git a/nano/secure/ledger.cpp b/nano/secure/ledger.cpp index 09037ab5..da80730b 100644 --- a/nano/secure/ledger.cpp +++ b/nano/secure/ledger.cpp @@ -1202,18 +1202,6 @@ bool nano::ledger::block_confirmed (nano::transaction const & transaction_a, nan return confirmed; } -bool nano::ledger::block_not_confirmed_or_not_exists (nano::block const & block_a) const -{ - bool result (true); - auto hash (block_a.hash ()); - auto transaction (store.tx_begin_read ()); - if (store.block_exists (transaction, hash)) - { - result = !block_confirmed (transaction, hash); - } - return result; -} - std::unique_ptr nano::collect_container_info (ledger & ledger, const std::string & name) { auto count = ledger.bootstrap_weights_size.load (); diff --git a/nano/secure/ledger.hpp b/nano/secure/ledger.hpp index 16ae0cdc..ed1fe4cf 100644 --- a/nano/secure/ledger.hpp +++ b/nano/secure/ledger.hpp @@ -26,7 +26,6 @@ public: std::shared_ptr successor (nano::transaction const &, nano::qualified_root const &); std::shared_ptr forked_block (nano::transaction const &, nano::block const &); bool block_confirmed (nano::transaction const & transaction_a, nano::block_hash const & hash_a) const; - bool block_not_confirmed_or_not_exists (nano::block const & block_a) const; nano::block_hash latest (nano::transaction const &, nano::account const &); nano::root latest_root (nano::transaction const &, nano::account const &); nano::block_hash representative (nano::transaction const &, nano::block_hash const &);