From 2b658acb4b1071b858c64473f9d700fa0e43b806 Mon Sep 17 00:00:00 2001 From: Guilherme Lawless Date: Mon, 23 Mar 2020 10:38:31 +0000 Subject: [PATCH] Delay voting for non-priority elections under saturation (#2666) * Delay voting for non-priority elections under saturation This is part of https://github.com/nanocurrency/nano-node/pull/2440 . Using a similar strategy of saving the last prioritized difficulty during `update_active_difficulty()`, the result of inserting an election now includes another boolean, hinting that the election may not be a priority due to a large number of active elections and low difficulty of the inserted block. Active difficulty is now calculated only from these prioritized elections. The cutoff is defined at 10% of thhe `active_elections_size` config which is now 50k. During `request_confirm`, the top elections are prioritized. When an election is prioritized, a vote is generated for the current winner. * Make sure to have at least one element when updating last_prioritized_difficulty --- nano/core_test/active_transactions.cpp | 47 ++++++++++++- nano/core_test/conflicts.cpp | 16 ++--- nano/core_test/election.cpp | 2 +- nano/core_test/ledger.cpp | 92 +++++++++++++------------- nano/core_test/node.cpp | 4 +- nano/core_test/vote_processor.cpp | 10 +-- nano/node/active_transactions.cpp | 73 +++++++++++++------- nano/node/active_transactions.hpp | 17 ++++- nano/node/blockprocessor.cpp | 6 +- nano/node/election.cpp | 8 +-- nano/node/node.cpp | 10 +-- 11 files changed, 182 insertions(+), 103 deletions(-) diff --git a/nano/core_test/active_transactions.cpp b/nano/core_test/active_transactions.cpp index 5be20606..42e50d04 100644 --- a/nano/core_test/active_transactions.cpp +++ b/nano/core_test/active_transactions.cpp @@ -797,7 +797,7 @@ TEST (active_transactions, dropped_cleanup) ASSERT_FALSE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ())); ASSERT_TRUE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ())); - auto election (node.active.insert (block).first); + auto election (node.active.insert (block).election); ASSERT_NE (nullptr, election); // Not yet removed @@ -843,7 +843,7 @@ TEST (active_transactions, confirmation_consistency) system.deadline_set (5s); while (!node.ledger.block_confirmed (node.store.tx_begin_read (), block->hash ())) { - ASSERT_FALSE (node.active.insert (block).second); + ASSERT_FALSE (node.active.insert (block).inserted); ASSERT_NO_ERROR (system.poll (5ms)); } nano::lock_guard guard (node.active.mutex); @@ -852,4 +852,45 @@ TEST (active_transactions, confirmation_consistency) ASSERT_EQ (i + 1, node.active.recently_cemented.size ()); } } -} \ No newline at end of file +} + +TEST (active_transactions, insertion_prioritization) +{ + nano::system system; + nano::node_config node_config (nano::get_available_port (), system.logging); + // 10% of elections (1) are prioritized + node_config.active_elections_size = 10; + nano::node_flags node_flags; + node_flags.disable_request_loop = true; + auto & node = *system.add_node (node_config, node_flags); + auto send1 (std::make_shared (nano::test_genesis_key.pub, nano::genesis_hash, nano::test_genesis_key.pub, nano::genesis_amount - 10 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (nano::genesis_hash))); + auto send2 (std::make_shared (nano::test_genesis_key.pub, send1->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 20 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send1->hash ()))); + auto send3 (std::make_shared (nano::test_genesis_key.pub, send2->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 30 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send2->hash ()))); + auto send4 (std::make_shared (nano::test_genesis_key.pub, send3->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 40 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send3->hash ()))); + auto send5 (std::make_shared (nano::test_genesis_key.pub, send4->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 50 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send4->hash ()))); + auto send6 (std::make_shared (nano::test_genesis_key.pub, send5->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 60 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send5->hash ()))); + auto send7 (std::make_shared (nano::test_genesis_key.pub, send6->hash (), nano::test_genesis_key.pub, nano::genesis_amount - 70 * nano::xrb_ratio, nano::public_key (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (send6->hash ()))); + + // Sort by difficulty, descending + std::vector> blocks{ send1, send2, send3, send4, send5, send6, send7 }; + std::sort (blocks.begin (), blocks.end (), [](auto const & blockl, auto const & blockr) { return blockl->difficulty () > blockr->difficulty (); }); + + auto update_active_difficulty = [&node] { + nano::unique_lock lock (node.active.mutex); + node.active.update_active_difficulty (lock); + }; + + ASSERT_TRUE (node.active.insert (blocks[2]).prioritized); + update_active_difficulty (); + ASSERT_FALSE (node.active.insert (blocks[3]).prioritized); + update_active_difficulty (); + ASSERT_TRUE (node.active.insert (blocks[1]).prioritized); + update_active_difficulty (); + ASSERT_FALSE (node.active.insert (blocks[4]).prioritized); + update_active_difficulty (); + ASSERT_TRUE (node.active.insert (blocks[0]).prioritized); + update_active_difficulty (); + ASSERT_FALSE (node.active.insert (blocks[5]).prioritized); + update_active_difficulty (); + ASSERT_FALSE (node.active.insert (blocks[6]).prioritized); +} diff --git a/nano/core_test/conflicts.cpp b/nano/core_test/conflicts.cpp index 1cf633a3..f46889a9 100644 --- a/nano/core_test/conflicts.cpp +++ b/nano/core_test/conflicts.cpp @@ -22,8 +22,8 @@ TEST (conflicts, start_stop) ASSERT_EQ (1, node1.active.size ()); { nano::lock_guard guard (node1.active.mutex); - ASSERT_NE (nullptr, election1.first); - ASSERT_EQ (1, election1.first->last_votes.size ()); + ASSERT_NE (nullptr, election1.election); + ASSERT_EQ (1, election1.election->last_votes.size ()); } } @@ -46,9 +46,9 @@ TEST (conflicts, add_existing) ASSERT_EQ (1, node1.active.size ()); { nano::lock_guard guard (node1.active.mutex); - ASSERT_NE (nullptr, election1.first); - ASSERT_EQ (2, election1.first->last_votes.size ()); - ASSERT_NE (election1.first->last_votes.end (), election1.first->last_votes.find (key2.pub)); + ASSERT_NE (nullptr, election1.election); + ASSERT_EQ (2, election1.election->last_votes.size ()); + ASSERT_NE (election1.election->last_votes.end (), election1.election->last_votes.find (key2.pub)); } } @@ -208,9 +208,9 @@ TEST (conflicts, dependency) // Check dependency for send block { nano::lock_guard guard (node1->active.mutex); - ASSERT_NE (nullptr, election1.first); - ASSERT_EQ (1, election1.first->dependent_blocks.size ()); - ASSERT_NE (election1.first->dependent_blocks.end (), election1.first->dependent_blocks.find (state_open1->hash ())); + ASSERT_NE (nullptr, election1.election); + ASSERT_EQ (1, election1.election->dependent_blocks.size ()); + ASSERT_NE (election1.election->dependent_blocks.end (), election1.election->dependent_blocks.find (state_open1->hash ())); } } diff --git a/nano/core_test/election.cpp b/nano/core_test/election.cpp index 8ccc8af4..21da4b0e 100644 --- a/nano/core_test/election.cpp +++ b/nano/core_test/election.cpp @@ -9,7 +9,7 @@ TEST (election, construction) nano::system system (1); nano::genesis genesis; auto & node = *system.nodes[0]; - auto election = node.active.insert (genesis.open).first; + auto election = node.active.insert (genesis.open).election; ASSERT_TRUE (election->idle ()); election->transition_active (); ASSERT_FALSE (election->idle ()); diff --git a/nano/core_test/ledger.cpp b/nano/core_test/ledger.cpp index 08e0b6ad..3b8eb22c 100644 --- a/nano/core_test/ledger.cpp +++ b/nano/core_test/ledger.cpp @@ -768,7 +768,7 @@ TEST (votes, check_signature) auto election1 = node1.active.insert (send1); { nano::lock_guard lock (node1.active.mutex); - ASSERT_EQ (1, election1.first->last_votes.size ()); + ASSERT_EQ (1, election1.election->last_votes.size ()); } auto vote1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send1)); vote1->signature.bytes[0] ^= 1; @@ -790,18 +790,18 @@ TEST (votes, add_one) ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *send1).code); auto election1 = node1.active.insert (send1); nano::unique_lock lock (node1.active.mutex); - ASSERT_EQ (1, election1.first->last_votes.size ()); + ASSERT_EQ (1, election1.election->last_votes.size ()); lock.unlock (); 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::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)); - ASSERT_NE (election1.first->last_votes.end (), existing1); + ASSERT_EQ (2, election1.election->last_votes.size ()); + auto existing1 (election1.election->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_NE (election1.election->last_votes.end (), existing1); ASSERT_EQ (send1->hash (), existing1->second.hash); - auto winner (*election1.first->tally ().begin ()); + auto winner (*election1.election->tally ().begin ()); ASSERT_EQ (*send1, *winner.second); ASSERT_EQ (nano::genesis_amount - 100, winner.first); } @@ -826,12 +826,12 @@ TEST (votes, add_two) 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)); lock.lock (); - ASSERT_EQ (3, election1.first->last_votes.size ()); - ASSERT_NE (election1.first->last_votes.end (), election1.first->last_votes.find (nano::test_genesis_key.pub)); - ASSERT_EQ (send1->hash (), election1.first->last_votes[nano::test_genesis_key.pub].hash); - ASSERT_NE (election1.first->last_votes.end (), election1.first->last_votes.find (key2.pub)); - ASSERT_EQ (send2->hash (), election1.first->last_votes[key2.pub].hash); - auto winner (*election1.first->tally ().begin ()); + ASSERT_EQ (3, election1.election->last_votes.size ()); + ASSERT_NE (election1.election->last_votes.end (), election1.election->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_EQ (send1->hash (), election1.election->last_votes[nano::test_genesis_key.pub].hash); + ASSERT_NE (election1.election->last_votes.end (), election1.election->last_votes.find (key2.pub)); + ASSERT_EQ (send2->hash (), election1.election->last_votes[key2.pub].hash); + auto winner (*election1.election->tally ().begin ()); ASSERT_EQ (*send1, *winner.second); } @@ -857,30 +857,30 @@ TEST (votes, add_existing) // Block is already processed from vote ASSERT_TRUE (node1.active.publish (send1)); nano::unique_lock lock (node1.active.mutex); - ASSERT_EQ (1, election1.first->last_votes[nano::test_genesis_key.pub].sequence); + ASSERT_EQ (1, election1.election->last_votes[nano::test_genesis_key.pub].sequence); nano::keypair key2; auto send2 (std::make_shared (genesis.hash (), key2.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); node1.work_generate_blocking (*send2); auto vote2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 2, send2)); // Pretend we've waited the timeout - election1.first->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20); + election1.election->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20); lock.unlock (); ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2)); ASSERT_FALSE (node1.active.publish (send2)); lock.lock (); - ASSERT_EQ (2, election1.first->last_votes[nano::test_genesis_key.pub].sequence); + ASSERT_EQ (2, election1.election->last_votes[nano::test_genesis_key.pub].sequence); // Also resend the old vote, and see if we respect the sequence number - election1.first->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20); + election1.election->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20); lock.unlock (); ASSERT_EQ (nano::vote_code::replay, node1.active.vote (vote1)); lock.lock (); - ASSERT_EQ (2, election1.first->last_votes[nano::test_genesis_key.pub].sequence); - ASSERT_EQ (2, election1.first->last_votes.size ()); - ASSERT_NE (election1.first->last_votes.end (), election1.first->last_votes.find (nano::test_genesis_key.pub)); - ASSERT_EQ (send2->hash (), election1.first->last_votes[nano::test_genesis_key.pub].hash); + ASSERT_EQ (2, election1.election->last_votes[nano::test_genesis_key.pub].sequence); + ASSERT_EQ (2, election1.election->last_votes.size ()); + ASSERT_NE (election1.election->last_votes.end (), election1.election->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_EQ (send2->hash (), election1.election->last_votes[nano::test_genesis_key.pub].hash); { auto transaction (node1.store.tx_begin_read ()); - auto winner (*election1.first->tally ().begin ()); + auto winner (*election1.election->tally ().begin ()); ASSERT_EQ (*send2, *winner.second); } } @@ -906,14 +906,14 @@ TEST (votes, add_old) auto vote2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send2)); { nano::lock_guard lock (node1.active.mutex); - election1.first->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20); + election1.election->last_votes[nano::test_genesis_key.pub].time = std::chrono::steady_clock::now () - std::chrono::seconds (20); } node1.vote_processor.vote_blocking (vote2, channel); - ASSERT_EQ (2, election1.first->last_votes_size ()); + ASSERT_EQ (2, election1.election->last_votes_size ()); nano::lock_guard lock (node1.active.mutex); - ASSERT_NE (election1.first->last_votes.end (), election1.first->last_votes.find (nano::test_genesis_key.pub)); - ASSERT_EQ (send1->hash (), election1.first->last_votes[nano::test_genesis_key.pub].hash); - auto winner (*election1.first->tally ().begin ()); + ASSERT_NE (election1.election->last_votes.end (), election1.election->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_EQ (send1->hash (), election1.election->last_votes[nano::test_genesis_key.pub].hash); + auto winner (*election1.election->tally ().begin ()); ASSERT_EQ (*send1, *winner.second); } @@ -933,27 +933,27 @@ TEST (votes, add_old_different_account) ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, *send2).code); auto election1 = node1.active.insert (send1); auto election2 = node1.active.insert (send2); - ASSERT_EQ (1, election1.first->last_votes_size ()); - ASSERT_EQ (1, election2.first->last_votes_size ()); + ASSERT_EQ (1, election1.election->last_votes_size ()); + ASSERT_EQ (1, election2.election->last_votes_size ()); auto vote1 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 2, send1)); auto channel (std::make_shared (node1.network.udp_channels, node1.network.endpoint (), node1.network_params.protocol.protocol_version)); auto vote_result1 (node1.vote_processor.vote_blocking (vote1, channel)); ASSERT_EQ (nano::vote_code::vote, vote_result1); - ASSERT_EQ (2, election1.first->last_votes_size ()); - ASSERT_EQ (1, election2.first->last_votes_size ()); + ASSERT_EQ (2, election1.election->last_votes_size ()); + ASSERT_EQ (1, election2.election->last_votes_size ()); auto vote2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send2)); auto vote_result2 (node1.vote_processor.vote_blocking (vote2, channel)); ASSERT_EQ (nano::vote_code::vote, vote_result2); - ASSERT_EQ (2, election1.first->last_votes_size ()); - ASSERT_EQ (2, election2.first->last_votes_size ()); + ASSERT_EQ (2, election1.election->last_votes_size ()); + ASSERT_EQ (2, election2.election->last_votes_size ()); nano::unique_lock lock (node1.active.mutex); - ASSERT_NE (election1.first->last_votes.end (), election1.first->last_votes.find (nano::test_genesis_key.pub)); - ASSERT_NE (election2.first->last_votes.end (), election2.first->last_votes.find (nano::test_genesis_key.pub)); - ASSERT_EQ (send1->hash (), election1.first->last_votes[nano::test_genesis_key.pub].hash); - ASSERT_EQ (send2->hash (), election2.first->last_votes[nano::test_genesis_key.pub].hash); - auto winner1 (*election1.first->tally ().begin ()); + ASSERT_NE (election1.election->last_votes.end (), election1.election->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_NE (election2.election->last_votes.end (), election2.election->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_EQ (send1->hash (), election1.election->last_votes[nano::test_genesis_key.pub].hash); + ASSERT_EQ (send2->hash (), election2.election->last_votes[nano::test_genesis_key.pub].hash); + auto winner1 (*election1.election->tally ().begin ()); ASSERT_EQ (*send1, *winner1.second); - auto winner2 (*election2.first->tally ().begin ()); + auto winner2 (*election2.election->tally ().begin ()); ASSERT_EQ (*send2, *winner2.second); } @@ -978,10 +978,10 @@ TEST (votes, add_cooldown) auto vote2 (std::make_shared (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 2, send2)); node1.vote_processor.vote_blocking (vote2, channel); nano::unique_lock lock (node1.active.mutex); - ASSERT_EQ (2, election1.first->last_votes.size ()); - ASSERT_NE (election1.first->last_votes.end (), election1.first->last_votes.find (nano::test_genesis_key.pub)); - ASSERT_EQ (send1->hash (), election1.first->last_votes[nano::test_genesis_key.pub].hash); - auto winner (*election1.first->tally ().begin ()); + ASSERT_EQ (2, election1.election->last_votes.size ()); + ASSERT_NE (election1.election->last_votes.end (), election1.election->last_votes.find (nano::test_genesis_key.pub)); + ASSERT_EQ (send1->hash (), election1.election->last_votes[nano::test_genesis_key.pub].hash); + auto winner (*election1.election->tally ().begin ()); ASSERT_EQ (*send1, *winner.second); } @@ -2656,10 +2656,10 @@ TEST (ledger, block_hash_account_conflict) auto election3 = node1.active.insert (send2); auto election4 = node1.active.insert (open_epoch1); nano::lock_guard lock (node1.active.mutex); - auto winner1 (*election1.first->tally ().begin ()); - auto winner2 (*election2.first->tally ().begin ()); - auto winner3 (*election3.first->tally ().begin ()); - auto winner4 (*election4.first->tally ().begin ()); + auto winner1 (*election1.election->tally ().begin ()); + auto winner2 (*election2.election->tally ().begin ()); + auto winner3 (*election3.election->tally ().begin ()); + auto winner4 (*election4.election->tally ().begin ()); ASSERT_EQ (*send1, *winner1.second); ASSERT_EQ (*receive1, *winner2.second); ASSERT_EQ (*send2, *winner3.second); diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index a4e34de2..68083adf 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -1840,12 +1840,12 @@ TEST (node, rep_self_vote) node0->block_processor.generator.add (block0->hash ()); system.deadline_set (1s); // Wait until representatives are activated & make vote - while (election1.first->last_votes_size () != 3) + while (election1.election->last_votes_size () != 3) { ASSERT_NO_ERROR (system.poll ()); } nano::unique_lock lock (active.mutex); - auto & rep_votes (election1.first->last_votes); + auto & rep_votes (election1.election->last_votes); ASSERT_NE (rep_votes.end (), rep_votes.find (nano::test_genesis_key.pub)); ASSERT_NE (rep_votes.end (), rep_votes.find (rep_big.pub)); } diff --git a/nano/core_test/vote_processor.cpp b/nano/core_test/vote_processor.cpp index 2ef8d9d9..45b0f5d2 100644 --- a/nano/core_test/vote_processor.cpp +++ b/nano/core_test/vote_processor.cpp @@ -28,7 +28,7 @@ TEST (vote_processor, codes) ASSERT_EQ (nano::vote_code::indeterminate, node.vote_processor.vote_blocking (vote, channel)); // First vote from an account for an ongoing election - ASSERT_TRUE (node.active.insert (genesis.open).second); + ASSERT_TRUE (node.active.insert (genesis.open).inserted); ASSERT_EQ (nano::vote_code::vote, node.vote_processor.vote_blocking (vote, channel)); // Processing the same vote is a replay @@ -77,14 +77,14 @@ TEST (vote_processor, invalid_signature) auto channel (std::make_shared (node.network.udp_channels, node.network.endpoint (), node.network_params.protocol.protocol_version)); auto election (node.active.insert (genesis.open)); - ASSERT_TRUE (election.first && election.second); - ASSERT_EQ (1, election.first->last_votes.size ()); + ASSERT_TRUE (election.election && election.inserted); + ASSERT_EQ (1, election.election->last_votes.size ()); node.vote_processor.vote (vote_invalid, channel); node.vote_processor.flush (); - ASSERT_EQ (1, election.first->last_votes.size ()); + ASSERT_EQ (1, election.election->last_votes.size ()); node.vote_processor.vote (vote, channel); node.vote_processor.flush (); - ASSERT_EQ (2, election.first->last_votes.size ()); + ASSERT_EQ (2, election.election->last_votes.size ()); } TEST (vote_processor, no_capacity) diff --git a/nano/node/active_transactions.cpp b/nano/node/active_transactions.cpp index 993c04e3..75f47060 100644 --- a/nano/node/active_transactions.cpp +++ b/nano/node/active_transactions.cpp @@ -21,6 +21,7 @@ multipliers_cb (20, 1.), trended_active_difficulty (node_a.network_params.network.publish_threshold), check_all_elections_period (node_a.network_params.network.is_test_network () ? 10ms : 5s), election_time_to_live (node_a.network_params.network.is_test_network () ? 0s : 2s), +prioritized_cutoff (std::max (1, node_a.config.active_elections_size / 10)), thread ([this]() { nano::thread_role::set (nano::thread_role::name::request_loop); request_loop (); @@ -101,12 +102,12 @@ void nano::active_transactions::search_frontiers (nano::transaction const & tran { auto block (this->node.store.block_get (transaction_a, info.head)); auto election = this->insert (block); - if (election.second) + if (election.inserted) { - election.first->transition_active (); + election.election->transition_active (); ++elections_count; // Calculate votes for local representatives - if (representative) + if (election.prioritized && representative) { this->node.block_processor.generator.add (info.head); } @@ -230,10 +231,12 @@ void nano::active_transactions::request_confirm (nano::unique_lock & nano::confirmation_solicitor solicitor (node.network, node.network_params.network); solicitor.prepare (node.rep_crawler.principal_representatives (std::numeric_limits::max (), node.network_params.protocol.tcp_realtime_protocol_version_min)); + bool const representative_l (node.config.enable_voting && node.wallets.rep_counts ().voting > 0); + std::vector hashes_generation_l; auto & sorted_roots_l (roots.get ()); auto const election_ttl_cutoff_l (std::chrono::steady_clock::now () - election_time_to_live); bool const check_all_elections_l (std::chrono::steady_clock::now () - last_check_all_elections > check_all_elections_period); - size_t const this_loop_target_l (check_all_elections_l ? sorted_roots_l.size () : node.config.active_elections_size / 10); + size_t const this_loop_target_l (check_all_elections_l ? sorted_roots_l.size () : prioritized_cutoff); size_t unconfirmed_count_l (0); nano::timer elapsed (nano::timer_state::started); @@ -247,7 +250,19 @@ void nano::active_transactions::request_confirm (nano::unique_lock & for (auto i = sorted_roots_l.begin (), n = sorted_roots_l.end (); i != n && unconfirmed_count_l < this_loop_target_l;) { auto & election_l (i->election); - unconfirmed_count_l += !election_l->confirmed (); + bool const confirmed_l (election_l->confirmed ()); + + // Queue vote generation for newly prioritized elections + if (!i->prioritized && unconfirmed_count_l < prioritized_cutoff) + { + sorted_roots_l.modify (i, [](nano::conflict_info & info_a) { info_a.prioritized = true; }); + if (representative_l && !confirmed_l) + { + hashes_generation_l.push_back (election_l->status.winner->hash ()); + } + } + + unconfirmed_count_l += !confirmed_l; 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)) { @@ -261,13 +276,17 @@ void nano::active_transactions::request_confirm (nano::unique_lock & } lock_a.unlock (); solicitor.flush (); + for (auto const & hash_l : hashes_generation_l) + { + node.block_processor.generator.add (hash_l); + } lock_a.lock (); // This is updated after the loop to ensure slow machines don't do the full check often if (check_all_elections_l) { last_check_all_elections = std::chrono::steady_clock::now (); - if (node.config.logging.timing_logging () && this_loop_target_l > node.config.active_elections_size / 10) + if (node.config.logging.timing_logging () && this_loop_target_l > prioritized_cutoff) { node.logger.try_log (boost::str (boost::format ("Processed %1% elections (%2% were already confirmed) in %3% %4%") % this_loop_target_l % (this_loop_target_l - unconfirmed_count_l) % elapsed.value ().count () % elapsed.unit ())); } @@ -479,9 +498,9 @@ void nano::active_transactions::stop () roots.clear (); } -std::pair, bool> nano::active_transactions::insert_impl (std::shared_ptr block_a, std::function)> const & confirmation_action_a) +nano::election_insertion_result nano::active_transactions::insert_impl (std::shared_ptr block_a, std::function)> const & confirmation_action_a) { - std::pair, bool> result = { nullptr, false }; + nano::election_insertion_result result; if (!stopped) { auto root (block_a->qualified_root ()); @@ -490,25 +509,26 @@ std::pair, bool> nano::active_transactions::inse { if (recently_confirmed.get ().find (root) == recently_confirmed.get ().end ()) { - result.second = true; + result.inserted = true; auto hash (block_a->hash ()); - result.first = nano::make_shared (node, block_a, confirmation_action_a); + result.election = 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); + result.prioritized = roots.size () < prioritized_cutoff || difficulty > last_prioritized_difficulty.value_or (0); + roots.get ().emplace (nano::conflict_info{ root, difficulty, difficulty, result.election, result.prioritized }); + blocks.emplace (hash, result.election); add_adjust_difficulty (hash); - result.first->insert_inactive_votes_cache (hash); + result.election->insert_inactive_votes_cache (hash); } } else { - result.first = existing->election; + result.election = existing->election; } } return result; } -std::pair, bool> nano::active_transactions::insert (std::shared_ptr block_a, std::function)> const & confirmation_action_a) +nano::election_insertion_result 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, confirmation_action_a); @@ -741,23 +761,28 @@ void nano::active_transactions::update_adjusted_difficulty () void nano::active_transactions::update_active_difficulty (nano::unique_lock & lock_a) { debug_assert (!mutex.try_lock ()); + last_prioritized_difficulty.reset (); double multiplier (1.); - if (!roots.empty ()) + // Heurestic to filter out non-saturated network and frontier confirmation + if (roots.size () > prioritized_cutoff / 2 || (node.network_params.network.is_test_network () && !roots.empty ())) { auto & sorted_roots = roots.get (); - std::vector active_root_difficulties; - active_root_difficulties.reserve (std::min (sorted_roots.size (), node.config.active_elections_size)); - size_t count (0); - for (auto it (sorted_roots.begin ()), end (sorted_roots.end ()); it != end && count++ < node.config.active_elections_size; ++it) + std::vector prioritized; + prioritized.reserve (std::min (sorted_roots.size (), prioritized_cutoff)); + for (auto it (sorted_roots.begin ()), end (sorted_roots.end ()); it != end && prioritized.size () < prioritized_cutoff; ++it) { - if (!it->election->confirmed () && !it->election->idle ()) + if (!it->election->confirmed ()) { - active_root_difficulties.push_back (it->adjusted_difficulty); + prioritized.push_back (it->adjusted_difficulty); } } - if (active_root_difficulties.size () > 10 || (!active_root_difficulties.empty () && node.network_params.network.is_test_network ())) + if (prioritized.size () > 10 || (node.network_params.network.is_test_network () && !prioritized.empty ())) { - multiplier = nano::difficulty::to_multiplier (active_root_difficulties[active_root_difficulties.size () / 2], node.network_params.network.publish_threshold); + multiplier = nano::difficulty::to_multiplier (prioritized[prioritized.size () / 2], node.network_params.network.publish_threshold); + } + if (!prioritized.empty ()) + { + last_prioritized_difficulty = prioritized.back (); } } debug_assert (multiplier >= 1); diff --git a/nano/node/active_transactions.hpp b/nano/node/active_transactions.hpp index 07361499..b6071e16 100644 --- a/nano/node/active_transactions.hpp +++ b/nano/node/active_transactions.hpp @@ -37,6 +37,7 @@ public: uint64_t difficulty; uint64_t adjusted_difficulty; std::shared_ptr election; + bool prioritized; }; class cementable_account final @@ -64,6 +65,14 @@ public: bool confirmed{ false }; // Did item reach votes quorum? (minimum config value) }; +class election_insertion_result final +{ +public: + std::shared_ptr election; + bool inserted{ false }; + bool prioritized{ false }; +}; + // Core class for determining consensus // Holds all active blocks i.e. recently added blocks that need confirmation class active_transactions final @@ -86,7 +95,7 @@ public: // 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, std::function)> const & = [](std::shared_ptr) {}); + nano::election_insertion_result 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); @@ -119,6 +128,7 @@ public: std::greater>>> roots; // clang-format on + boost::optional last_prioritized_difficulty{ boost::none }; std::unordered_map> blocks; std::deque list_recently_cemented (); std::deque recently_cemented; @@ -146,7 +156,7 @@ private: // Call action with confirmed block, may be different than what we started with // clang-format off - std::pair, bool> insert_impl (std::shared_ptr, std::function)> const & = [](std::shared_ptr) {}); + nano::election_insertion_result insert_impl (std::shared_ptr, std::function)> const & = [](std::shared_ptr) {}); // clang-format on void request_loop (); void search_frontiers (nano::transaction const &); @@ -164,6 +174,9 @@ private: // Maximum time an election can be kept active if it is extending the container std::chrono::seconds const election_time_to_live; + // Elections above this position in the queue are prioritized + size_t const prioritized_cutoff; + static size_t constexpr recently_confirmed_size{ 65536 }; using recent_confirmation = std::pair; // clang-format off diff --git a/nano/node/blockprocessor.cpp b/nano/node/blockprocessor.cpp index 198e89c5..b01d3a37 100644 --- a/nano/node/blockprocessor.cpp +++ b/nano/node/blockprocessor.cpp @@ -271,9 +271,9 @@ void nano::block_processor::process_live (nano::block_hash const & hash_a, std:: // Start collecting quorum on block auto election = node.active.insert (block_a); - if (election.second) + if (election.inserted) { - election.first->transition_passive (); + election.election->transition_passive (); } // Announce block contents to the network @@ -285,7 +285,7 @@ void nano::block_processor::process_live (nano::block_hash const & hash_a, std:: { node.network.flood_block (block_a, nano::buffer_drop_policy::no_limiter_drop); } - if (node.config.enable_voting && node.wallets.rep_counts ().voting > 0) + if (election.prioritized && node.config.enable_voting && node.wallets.rep_counts ().voting > 0) { // Announce our weighted vote to the network generator.add (hash_a); diff --git a/nano/node/election.cpp b/nano/node/election.cpp index 97947176..e9b776e7 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -207,9 +207,9 @@ void nano::election::activate_dependencies () 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) + if (election.inserted) { - election.first->transition_active (); + election.election->transition_active (); escalated_l = true; } } @@ -225,9 +225,9 @@ void nano::election::activate_dependencies () 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) + if (election.inserted) { - election.first->transition_active (); + election.election->transition_active (); escalated_l = true; } } diff --git a/nano/node/node.cpp b/nano/node/node.cpp index 6bce6f35..bdf4605b 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -556,10 +556,10 @@ void nano::node::process_fork (nano::transaction const & transaction_a, std::sha } } }); - if (election.second) + if (election.inserted) { 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 ())); - election.first->transition_active (); + election.election->transition_active (); } } } @@ -1095,12 +1095,12 @@ void nano::node::add_initial_peers () void nano::node::block_confirm (std::shared_ptr block_a) { auto election = active.insert (block_a); - if (election.second) + if (election.inserted) { - election.first->transition_active (); + election.election->transition_active (); } // Calculate votes for local representatives - if (config.enable_voting && wallets.rep_counts ().voting > 0 && active.active (*block_a)) + if (election.prioritized && config.enable_voting && wallets.rep_counts ().voting > 0 && active.active (*block_a)) { block_processor.generator.add (block_a->hash ()); }