diff --git a/nano/core_test/active_transactions.cpp b/nano/core_test/active_transactions.cpp index 2a63a424..887eb52d 100644 --- a/nano/core_test/active_transactions.cpp +++ b/nano/core_test/active_transactions.cpp @@ -628,7 +628,7 @@ TEST (active_transactions, dropped_cleanup) // Not yet removed ASSERT_TRUE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ())); - ASSERT_EQ (1, node.active.blocks.count (nano::dev::genesis->hash ())); + ASSERT_TRUE (node.active.active (nano::dev::genesis->hash ())); // Now simulate dropping the election ASSERT_FALSE (election->confirmed ()); @@ -641,7 +641,7 @@ TEST (active_transactions, dropped_cleanup) ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::election_drop_all)); // Block cleared from active - ASSERT_EQ (0, node.active.blocks.count (nano::dev::genesis->hash ())); + ASSERT_FALSE (node.active.active (nano::dev::genesis->hash ())); // Repeat test for a confirmed election ASSERT_TRUE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ())); @@ -660,7 +660,7 @@ TEST (active_transactions, dropped_cleanup) ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::election_drop_all)); // Block cleared from active - ASSERT_EQ (0, node.active.blocks.count (nano::dev::genesis->hash ())); + ASSERT_FALSE (node.active.active (nano::dev::genesis->hash ())); } TEST (active_transactions, republish_winner) diff --git a/nano/core_test/bootstrap.cpp b/nano/core_test/bootstrap.cpp index 66e38324..089e05b4 100644 --- a/nano/core_test/bootstrap.cpp +++ b/nano/core_test/bootstrap.cpp @@ -933,6 +933,7 @@ TEST (bootstrap_processor, lazy_hash_pruning) // Processing chain to prune for node1 config.peering_port = nano::test::get_available_port (); auto node1 (std::make_shared (system.io_ctx, nano::unique_path (), config, system.work, node_flags, 1)); + node1->start (); node1->process_active (send1); node1->process_active (receive1); node1->process_active (change1); diff --git a/nano/core_test/confirmation_height.cpp b/nano/core_test/confirmation_height.cpp index 5ac746db..dc52bf8f 100644 --- a/nano/core_test/confirmation_height.cpp +++ b/nano/core_test/confirmation_height.cpp @@ -1561,10 +1561,7 @@ TEST (confirmation_height, callback_confirmed_history) election->force_confirm (); ASSERT_TIMELY (10s, node->active.size () == 0); ASSERT_EQ (0, node->active.recently_cemented.list ().size ()); - { - nano::lock_guard guard (node->active.mutex); - ASSERT_EQ (0, node->active.blocks.size ()); - } + ASSERT_TRUE (node->active.empty ()); auto transaction = node->store.tx_begin_read (); ASSERT_FALSE (node->ledger.block_confirmed (transaction, send->hash ())); @@ -1584,7 +1581,7 @@ TEST (confirmation_height, callback_confirmed_history) ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out) == 1); ASSERT_EQ (1, node->active.recently_cemented.list ().size ()); - ASSERT_EQ (0, node->active.blocks.size ()); + ASSERT_TRUE (node->active.empty ()); // Confirm the callback is not called under this circumstance ASSERT_EQ (2, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); diff --git a/nano/core_test/election.cpp b/nano/core_test/election.cpp index c6d128a7..22b2673e 100644 --- a/nano/core_test/election.cpp +++ b/nano/core_test/election.cpp @@ -257,3 +257,42 @@ TEST (election, quorum_minimum_update_weight_before_quorum_checks) ASSERT_NE (nullptr, node1.block (send1->hash ())); } } + +TEST (election, continuous_voting) +{ + nano::test::system system{}; + auto & node1 = *system.add_node (); + system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); + + // We want genesis to have just enough voting weight to be a principal rep, but not enough to confirm blocks on their own + nano::keypair key1{}; + nano::send_block_builder builder{}; + auto send1 = builder.make_block () + .previous (nano::dev::genesis->hash ()) + .destination (key1.pub) + .balance (node1.balance (nano::dev::genesis_key.pub) / 10 * 1) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*system.work.generate (nano::dev::genesis->hash ())) + .build_shared (); + + ASSERT_TRUE (nano::test::process (node1, { send1 })); + ASSERT_TIMELY (5s, nano::test::confirm (node1, { send1 })); + ASSERT_TIMELY (5s, nano::test::confirmed (node1, { send1 })); + + node1.stats.clear (); + + // Create a block that should be staying in AEC but not get confirmed + auto send2 = builder.make_block () + .previous (send1->hash ()) + .destination (key1.pub) + .balance (node1.balance (nano::dev::genesis_key.pub) - 1) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*system.work.generate (send1->hash ())) + .build_shared (); + + ASSERT_TRUE (nano::test::process (node1, { send2 })); + ASSERT_TIMELY (5s, node1.active.active (*send2)); + + // Ensure votes are generated in continuous manner + ASSERT_TIMELY_EQ (5s, node1.stats.count (nano::stat::type::election, nano::stat::detail::generate_vote), 5); +} \ No newline at end of file diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index 95833fd4..aab42e45 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -455,11 +455,8 @@ TEST (node, search_receivable_confirmed) system.wallet (0)->insert_adhoc (key2.prv); ASSERT_FALSE (system.wallet (0)->search_receivable (system.wallet (0)->wallets.tx_begin_read ())); { - nano::lock_guard guard (node->active.mutex); - auto existing1 (node->active.blocks.find (send1->hash ())); - ASSERT_EQ (node->active.blocks.end (), existing1); - auto existing2 (node->active.blocks.find (send2->hash ())); - ASSERT_EQ (node->active.blocks.end (), existing2); + ASSERT_FALSE (node->active.active (send1->hash ())); + ASSERT_FALSE (node->active.active (send2->hash ())); } ASSERT_TIMELY (10s, node->balance (key2.pub) == 2 * node->config.receive_minimum.number ()); } @@ -3907,11 +3904,10 @@ TEST (node, dependency_graph) ASSERT_NO_ERROR (system.poll_until_true (15s, [&] { // Not many blocks should be active simultaneously EXPECT_LT (node.active.size (), 6); - nano::lock_guard guard (node.active.mutex); // Ensure that active blocks have their ancestors confirmed auto error = std::any_of (dependency_graph.cbegin (), dependency_graph.cend (), [&] (auto entry) { - if (node.active.blocks.count (entry.first)) + if (node.active.active (entry.first)) { for (auto ancestor : entry.second) { diff --git a/nano/core_test/voting.cpp b/nano/core_test/voting.cpp index 282ff91e..4bc7e2a4 100644 --- a/nano/core_test/voting.cpp +++ b/nano/core_test/voting.cpp @@ -103,22 +103,6 @@ TEST (vote_generator, multiple_representatives) } } -TEST (vote_generator, session) -{ - nano::test::system system (1); - auto node (system.nodes[0]); - system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); - nano::vote_generator_session generator_session (node->generator); - boost::thread thread ([node, &generator_session] () { - nano::thread_role::set (nano::thread_role::name::request_loop); - generator_session.add (nano::dev::genesis->account (), nano::dev::genesis->hash ()); - ASSERT_EQ (0, node->stats.count (nano::stat::type::vote, nano::stat::detail::vote_indeterminate)); - generator_session.flush (); - }); - thread.join (); - ASSERT_TIMELY (2s, 1 == node->stats.count (nano::stat::type::vote, nano::stat::detail::vote_indeterminate)); -} - TEST (vote_spacing, basic) { nano::vote_spacing spacing{ std::chrono::milliseconds{ 100 } }; diff --git a/nano/lib/config.hpp b/nano/lib/config.hpp index 45713fc7..c51de572 100644 --- a/nano/lib/config.hpp +++ b/nano/lib/config.hpp @@ -211,7 +211,8 @@ public: max_peers_per_ip (default_max_peers_per_ip), max_peers_per_subnetwork (default_max_peers_per_ip * 4), ipv6_subnetwork_prefix_for_limiting (64), // Equivalent to network prefix /64. - peer_dump_interval (std::chrono::seconds (5 * 60)) + peer_dump_interval (std::chrono::seconds (5 * 60)), + vote_broadcast_interval (1000) { if (is_live_network ()) { @@ -243,6 +244,7 @@ public: max_peers_per_ip = 20; max_peers_per_subnetwork = max_peers_per_ip * 4; peer_dump_interval = std::chrono::seconds (1); + vote_broadcast_interval = 100; } } @@ -282,6 +284,10 @@ public: size_t max_peers_per_subnetwork; size_t ipv6_subnetwork_prefix_for_limiting; std::chrono::seconds peer_dump_interval; + /** Time to wait before vote rebroadcasts for active elections, this is doubled for each broadcast, up to `max_vote_broadcast_interval` (milliseconds) */ + uint64_t vote_broadcast_interval; + /** Maximum interval for vote broadcasts for active elections (milliseconds) */ + static uint64_t constexpr max_vote_broadcast_interval{ 16 * 1000 }; /** Returns the network this object contains values for */ nano::networks network () const diff --git a/nano/lib/stats.cpp b/nano/lib/stats.cpp index c7e1bfce..4c4ea419 100644 --- a/nano/lib/stats.cpp +++ b/nano/lib/stats.cpp @@ -554,11 +554,8 @@ std::string nano::stat::type_to_string (stat::type type) case nano::stat::type::bootstrap_server: res = "bootstrap_server"; break; - case nano::stat::type::bootstrap_server_requests: - res = "bootstrap_server_requests"; - break; - case nano::stat::type::bootstrap_server_responses: - res = "bootstrap_server_responses"; + case nano::stat::type::active: + res = "active"; break; } return res; @@ -572,6 +569,9 @@ std::string nano::stat::detail_to_string (stat::detail detail) case nano::stat::detail::all: res = "all"; break; + case nano::stat::detail::loop: + res = "loop"; + break; case nano::stat::detail::queue: res = "queue"; break; @@ -823,6 +823,15 @@ std::string nano::stat::detail_to_string (stat::detail detail) case nano::stat::detail::election_hinted_drop: res = "election_hinted_drop"; break; + case nano::stat::detail::generate_vote: + res = "generate_vote"; + break; + case nano::stat::detail::generate_vote_normal: + res = "generate_vote_normal"; + break; + case nano::stat::detail::generate_vote_final: + res = "generate_vote_final"; + break; case nano::stat::detail::blocking: res = "blocking"; break; diff --git a/nano/lib/stats.hpp b/nano/lib/stats.hpp index aa462a1e..251456ab 100644 --- a/nano/lib/stats.hpp +++ b/nano/lib/stats.hpp @@ -248,8 +248,7 @@ public: hinting, blockprocessor, bootstrap_server, - bootstrap_server_requests, - bootstrap_server_responses, + active, }; /** Optional detail type */ @@ -257,6 +256,9 @@ public: { all = 0, + // common + loop, + // processing queue queue, overfill, @@ -358,6 +360,9 @@ public: election_hinted_started, election_hinted_confirmed, election_hinted_drop, + generate_vote, + generate_vote_normal, + generate_vote_final, // udp blocking, diff --git a/nano/node/active_transactions.cpp b/nano/node/active_transactions.cpp index eb4de130..5088df0c 100644 --- a/nano/node/active_transactions.cpp +++ b/nano/node/active_transactions.cpp @@ -20,11 +20,7 @@ nano::active_transactions::active_transactions (nano::node & node_a, nano::confi node{ node_a }, recently_confirmed{ 65536 }, recently_cemented{ node.config.confirmation_history_size }, - election_time_to_live{ node_a.network_params.network.is_dev_network () ? 0s : 2s }, - thread ([this] () { - nano::thread_role::set (nano::thread_role::name::request_loop); - request_loop (); - }) + election_time_to_live{ node_a.network_params.network.is_dev_network () ? 0s : 2s } { // Register a callback which will get called after a block is cemented confirmation_height_processor.add_cemented_observer ([this] (std::shared_ptr const & callback_block_a) { @@ -35,14 +31,33 @@ nano::active_transactions::active_transactions (nano::node & node_a, nano::confi confirmation_height_processor.add_block_already_cemented_observer ([this] (nano::block_hash const & hash_a) { this->block_already_cemented_callback (hash_a); }); - - nano::unique_lock lock (mutex); - condition.wait (lock, [&started = started] { return started; }); } nano::active_transactions::~active_transactions () { - stop (); + // Thread must be stopped before destruction + debug_assert (!thread.joinable ()); +} + +void nano::active_transactions::start () +{ + debug_assert (!thread.joinable ()); + + thread = std::thread ([this] () { + nano::thread_role::set (nano::thread_role::name::request_loop); + request_loop (); + }); +} + +void nano::active_transactions::stop () +{ + stopped = true; + condition.notify_all (); + if (thread.joinable ()) + { + thread.join (); + } + clear (); } void nano::active_transactions::block_cemented_callback (std::shared_ptr const & block_a) @@ -200,8 +215,6 @@ void nano::active_transactions::request_confirm (nano::unique_lock nano::confirmation_solicitor solicitor (node.network, node.config); solicitor.prepare (node.rep_crawler.principal_representatives (std::numeric_limits::max ())); - nano::vote_generator_session generator_session (node.generator); - nano::vote_generator_session final_generator_session (node.final_generator); std::size_t unconfirmed_count_l (0); nano::timer elapsed (nano::timer_state::started); @@ -230,8 +243,6 @@ void nano::active_transactions::request_confirm (nano::unique_lock } solicitor.flush (); - generator_session.flush (); - final_generator_session.flush (); lock_a.lock (); if (node.config.logging.timing_logging ()) @@ -324,20 +335,12 @@ std::vector> nano::active_transactions::list_act void nano::active_transactions::request_loop () { nano::unique_lock lock (mutex); - started = true; - lock.unlock (); - condition.notify_all (); - - // The wallets and active_transactions objects are mutually dependent, so we need a fully - // constructed node before proceeding. - this->node.node_initialized_latch.wait (); - - lock.lock (); - while (!stopped && !node.flags.disable_request_loop) { auto const stamp_l = std::chrono::steady_clock::now (); + node.stats.inc (nano::stat::type::active, nano::stat::detail::loop); + request_confirm (lock); debug_assert (lock.owns_lock ()); @@ -350,24 +353,6 @@ void nano::active_transactions::request_loop () } } -void nano::active_transactions::stop () -{ - nano::unique_lock lock (mutex); - if (!started) - { - condition.wait (lock, [&started = started] { return started; }); - } - stopped = true; - lock.unlock (); - condition.notify_all (); - if (thread.joinable ()) - { - thread.join (); - } - lock.lock (); - roots.clear (); -} - nano::election_insertion_result nano::active_transactions::insert_impl (nano::unique_lock & lock_a, std::shared_ptr const & block_a, nano::election_behavior election_behavior_a, std::function const &)> const & confirmation_action_a) { debug_assert (lock_a.owns_lock ()); @@ -419,7 +404,7 @@ nano::election_insertion_result nano::active_transactions::insert_impl (nano::un // Votes are generated for inserted or ongoing elections if (result.election) { - result.election->generate_votes (); + result.election->broadcast_vote (); } } return result; @@ -505,19 +490,19 @@ nano::vote_code nano::active_transactions::vote (std::shared_ptr con return result; } -bool nano::active_transactions::active (nano::qualified_root const & root_a) +bool nano::active_transactions::active (nano::qualified_root const & root_a) const { nano::lock_guard lock (mutex); return roots.get ().find (root_a) != roots.get ().end (); } -bool nano::active_transactions::active (nano::block const & block_a) +bool nano::active_transactions::active (nano::block const & block_a) const { nano::lock_guard guard (mutex); return roots.get ().find (block_a.qualified_root ()) != roots.get ().end () && blocks.find (block_a.hash ()) != blocks.end (); } -bool nano::active_transactions::active (const nano::block_hash & hash) +bool nano::active_transactions::active (const nano::block_hash & hash) const { nano::lock_guard guard{ mutex }; return blocks.find (hash) != blocks.end (); @@ -582,13 +567,13 @@ void nano::active_transactions::erase_oldest () } } -bool nano::active_transactions::empty () +bool nano::active_transactions::empty () const { nano::lock_guard lock (mutex); return roots.empty (); } -std::size_t nano::active_transactions::size () +std::size_t nano::active_transactions::size () const { nano::lock_guard lock (mutex); return roots.size (); diff --git a/nano/node/active_transactions.hpp b/nano/node/active_transactions.hpp index a0a2591e..c98ab351 100644 --- a/nano/node/active_transactions.hpp +++ b/nano/node/active_transactions.hpp @@ -111,10 +111,13 @@ public: bool inserted{ false }; }; -// Core class for determining consensus -// Holds all active blocks i.e. recently added blocks that need confirmation +/** + * Core class for determining consensus + * Holds all active blocks i.e. recently added blocks that need confirmation + */ class active_transactions final { +private: // Elections class conflict_info final { public: @@ -131,10 +134,7 @@ class active_transactions final class tag_uncemented {}; class tag_arrival {}; class tag_hash {}; - // clang-format on -public: - // clang-format off using ordered_roots = boost::multi_index_container>, @@ -143,11 +143,16 @@ public: >>; // clang-format on ordered_roots roots; + std::unordered_map> blocks; +public: explicit active_transactions (nano::node &, nano::confirmation_height_processor &); ~active_transactions (); - /* + void start (); + void stop (); + + /** * Starts new election with hinted behavior type * Hinted elections have shorter timespan and only can take up limited space inside active elections container */ @@ -155,12 +160,12 @@ public: // Distinguishes replay votes, cannot be determined if the block is not in any election nano::vote_code vote (std::shared_ptr const &); // Is the root of this block in the roots container - bool active (nano::block const &); - bool active (nano::qualified_root const &); - /* + bool active (nano::block const &) const; + bool active (nano::qualified_root const &) const; + /** * Is the block hash present in any active election */ - bool active (nano::block_hash const &); + bool active (nano::block_hash const &) const; std::shared_ptr election (nano::qualified_root const &) const; std::shared_ptr winner (nano::block_hash const &) const; // Returns a list of elections sorted by difficulty @@ -168,49 +173,35 @@ public: void erase (nano::block const &); void erase_hash (nano::block_hash const &); void erase_oldest (); - bool empty (); - std::size_t size (); - void stop (); + bool empty () const; + std::size_t size () const; bool publish (std::shared_ptr const &); boost::optional confirm_block (nano::transaction const &, std::shared_ptr const &); void block_cemented_callback (std::shared_ptr const &); void block_already_cemented_callback (nano::block_hash const &); - /* + /** * Maximum number of all elections that should be present in this container. * This is only a soft limit, it is possible for this container to exceed this count. */ int64_t limit () const; - /* + /** * Maximum number of hinted elections that should be present in this container. */ int64_t hinted_limit () const; int64_t vacancy () const; - /* + /** * How many election slots are available for hinted elections. * The limit of AEC taken up by hinted elections is controlled by `node_config::active_elections_hinted_limit_percentage` */ int64_t vacancy_hinted () const; std::function vacancy_update{ [] () {} }; - std::unordered_map> blocks; - - nano::election_scheduler & scheduler; - nano::confirmation_height_processor & confirmation_height_processor; - nano::node & node; - mutable nano::mutex mutex{ mutex_identifier (mutexes::active) }; std::size_t election_winner_details_size (); void add_election_winner_details (nano::block_hash const &, std::shared_ptr const &); void remove_election_winner_details (nano::block_hash const &); - recently_confirmed_cache recently_confirmed; - recently_cemented_cache recently_cemented; - private: - nano::mutex election_winner_details_mutex{ mutex_identifier (mutexes::election_winner_details) }; - - std::unordered_map> election_winner_details; - // Call action with confirmed block, may be different than what we started with nano::election_insertion_result insert_impl (nano::unique_lock &, std::shared_ptr const &, nano::election_behavior = nano::election_behavior::normal, std::function const &)> const & = nullptr); void request_loop (); @@ -220,23 +211,37 @@ private: void cleanup_election (nano::unique_lock & lock_a, std::shared_ptr); // Returns a list of elections sorted by difficulty, mutex must be locked std::vector> list_active_impl (std::size_t) const; - - /* + /** * Checks if vote passes minimum representative weight threshold and adds it to inactive vote cache * TODO: Should be moved to `vote_cache` class */ - void add_inactive_vote_cache (nano::block_hash const & hash, std::shared_ptr const vote); + void add_inactive_vote_cache (nano::block_hash const & hash, std::shared_ptr vote); - nano::condition_variable condition; - bool started{ false }; - std::atomic stopped{ false }; +private: // Dependencies + nano::election_scheduler & scheduler; + nano::confirmation_height_processor & confirmation_height_processor; + nano::node & node; + +public: + recently_confirmed_cache recently_confirmed; + recently_cemented_cache recently_cemented; + + // TODO: This mutex is currently public because many tests access it + // TODO: This is bad. Remove the need to explicitly lock this from any code outside of this class + mutable nano::mutex mutex{ mutex_identifier (mutexes::active) }; + +private: + nano::mutex election_winner_details_mutex{ mutex_identifier (mutexes::election_winner_details) }; + std::unordered_map> election_winner_details; // Maximum time an election can be kept active if it is extending the container std::chrono::seconds const election_time_to_live; int active_hinted_elections_count{ 0 }; - boost::thread thread; + nano::condition_variable condition; + std::atomic stopped{ false }; + std::thread thread; friend class election; friend class election_scheduler; diff --git a/nano/node/election.cpp b/nano/node/election.cpp index fb47ab6b..0948776d 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -21,19 +21,16 @@ nano::election_vote_result::election_vote_result (bool replay_a, bool processed_ nano::election::election (nano::node & node_a, std::shared_ptr const & block_a, std::function const &)> const & confirmation_action_a, std::function const & live_vote_action_a, nano::election_behavior election_behavior_a) : confirmation_action (confirmation_action_a), live_vote_action (live_vote_action_a), - behavior (election_behavior_a), node (node_a), + behavior (election_behavior_a), status ({ block_a, 0, 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), root (block_a->root ()), - qualified_root (block_a->qualified_root ()) + qualified_root (block_a->qualified_root ()), + vote_broadcast_interval{ node.config.network_params.network.vote_broadcast_interval } { last_votes.emplace (nano::account::null (), nano::vote_info{ std::chrono::steady_clock::now (), 0, block_a->hash () }); last_blocks.emplace (block_a->hash (), block_a); - if (node.config.enable_voting && node.wallets.reps ().voting > 0) - { - node.generator.add (root, block_a->hash ()); - } } void nano::election::confirm_once (nano::unique_lock & lock_a, nano::election_status_type type_a) @@ -168,6 +165,19 @@ void nano::election::broadcast_block (nano::confirmation_solicitor & solicitor_a } } +void nano::election::broadcast_vote () +{ + debug_assert (vote_broadcast_interval > 0); + + nano::unique_lock lock{ mutex }; + if (last_vote + std::chrono::milliseconds (vote_broadcast_interval) < std::chrono::steady_clock::now ()) + { + broadcast_vote_impl (); + last_vote = std::chrono::steady_clock::now (); + vote_broadcast_interval = std::min (vote_broadcast_interval * 2, node.config.network_params.network.max_vote_broadcast_interval); + } +} + bool nano::election::transition_time (nano::confirmation_solicitor & solicitor_a) { bool result = false; @@ -180,6 +190,7 @@ bool nano::election::transition_time (nano::confirmation_solicitor & solicitor_a } break; case nano::election::state_t::active: + broadcast_vote (); broadcast_block (solicitor_a); send_confirm_req (solicitor_a); break; @@ -476,21 +487,23 @@ std::shared_ptr nano::election::winner () const return status.winner; } -void nano::election::generate_votes () const +void nano::election::broadcast_vote_impl () { + debug_assert (!mutex.try_lock ()); + if (node.config.enable_voting && node.wallets.reps ().voting > 0) { - nano::unique_lock lock (mutex); + node.stats.inc (nano::stat::type::election, nano::stat::detail::generate_vote); + if (confirmed () || have_quorum (tally_impl ())) { - auto hash = status.winner->hash (); - lock.unlock (); - node.final_generator.add (root, hash); - lock.lock (); + node.stats.inc (nano::stat::type::election, nano::stat::detail::generate_vote_final); + node.final_generator.add (root, status.winner->hash ()); // Broadcasts vote to the network } else { - node.generator.add (root, status.winner->hash ()); + node.stats.inc (nano::stat::type::election, nano::stat::detail::generate_vote_normal); + node.generator.add (root, status.winner->hash ()); // Broadcasts vote to the network } } } diff --git a/nano/node/election.hpp b/nano/node/election.hpp index 03c875f0..f8e4bcb0 100644 --- a/nano/node/election.hpp +++ b/nano/node/election.hpp @@ -15,7 +15,7 @@ class channel; class confirmation_solicitor; class inactive_cache_information; class node; -class vote_generator_session; + class vote_info final { public: @@ -23,6 +23,7 @@ public: uint64_t timestamp; nano::block_hash hash; }; + class vote_with_weight_info final { public: @@ -32,6 +33,7 @@ public: nano::block_hash hash; nano::uint128_t weight; }; + class election_vote_result final { public: @@ -40,17 +42,20 @@ public: bool replay{ false }; bool processed{ false }; }; + enum class election_behavior { normal, hinted }; + struct election_extended_status final { nano::election_status status; std::unordered_map votes; nano::tally_t tally; }; + class election final : public std::enable_shared_from_this { public: @@ -75,6 +80,7 @@ private: // State management expired_confirmed, expired_unconfirmed }; + static unsigned constexpr passive_duration_factor = 5; static unsigned constexpr active_request_count_min = 2; std::atomic state_m = { state_t::passive }; @@ -84,7 +90,9 @@ private: // State management // These are modified while not holding the mutex from transition_time only std::chrono::steady_clock::time_point last_block = { std::chrono::steady_clock::now () }; - std::chrono::steady_clock::time_point last_req = { std::chrono::steady_clock::time_point () }; + std::chrono::steady_clock::time_point last_req = {}; + /** The last time vote for this election was generated */ + std::chrono::steady_clock::time_point last_vote = {}; bool valid_change (nano::election::state_t, nano::election::state_t) const; bool state_change (nano::election::state_t, nano::election::state_t); @@ -120,6 +128,15 @@ public: // Interface // Confirm this block if quorum is met void confirm_if_quorum (nano::unique_lock &); + /** + * Broadcasts vote for the current winner of this election + * Checks if sufficient amount of time (`vote_generation_interval`) passed since the last vote generation + */ + void broadcast_vote (); + +private: // Dependencies + nano::node & node; + public: // Information uint64_t const height; nano::root const root; @@ -132,8 +149,11 @@ private: void confirm_once (nano::unique_lock & lock_a, nano::election_status_type = nano::election_status_type::active_confirmed_quorum); void broadcast_block (nano::confirmation_solicitor &); void send_confirm_req (nano::confirmation_solicitor &); - // Calculate votes for local representatives - void generate_votes () const; + /** + * Broadcast vote for current election winner. Generates final vote if reached quorum or already confirmed + * Requires mutex lock + */ + void broadcast_vote_impl (); void remove_votes (nano::block_hash const &); void remove_block (nano::block_hash const &); bool replace_by_weight (nano::unique_lock & lock_a, nano::block_hash const &); @@ -153,9 +173,12 @@ private: nano::election_behavior const behavior{ nano::election_behavior::normal }; std::chrono::steady_clock::time_point const election_start = { std::chrono::steady_clock::now () }; - nano::node & node; + /** Time to wait before next vote broadcast for current winner, doubles for each broadcast, up to `max_vote_broadcast_interval` */ + uint64_t vote_broadcast_interval; + mutable nano::mutex mutex; +private: // Constants static std::size_t constexpr max_blocks{ 10 }; friend class active_transactions; diff --git a/nano/node/node.cpp b/nano/node/node.cpp index 8bc559e4..4634f518 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -765,6 +765,7 @@ void nano::node::start () port_mapping.start (); } wallets.start (); + active.start (); generator.start (); final_generator.start (); backlog.start (); diff --git a/nano/node/voting.cpp b/nano/node/voting.cpp index 4bb6ccfe..400230ec 100644 --- a/nano/node/voting.cpp +++ b/nano/node/voting.cpp @@ -444,26 +444,6 @@ void nano::vote_generator::run () } } -nano::vote_generator_session::vote_generator_session (nano::vote_generator & vote_generator_a) : - generator (vote_generator_a) -{ -} - -void nano::vote_generator_session::add (nano::root const & root_a, nano::block_hash const & hash_a) -{ - debug_assert (nano::thread_role::get () == nano::thread_role::name::request_loop); - items.emplace_back (root_a, hash_a); -} - -void nano::vote_generator_session::flush () -{ - debug_assert (nano::thread_role::get () == nano::thread_role::name::request_loop); - for (auto const & [root, hash] : items) - { - generator.add (root, hash); - } -} - std::unique_ptr nano::collect_container_info (nano::vote_generator & vote_generator, std::string const & name) { std::size_t candidates_count = 0; diff --git a/nano/node/voting.hpp b/nano/node/voting.hpp index 492577bb..53928b08 100644 --- a/nano/node/voting.hpp +++ b/nano/node/voting.hpp @@ -177,16 +177,4 @@ private: }; std::unique_ptr collect_container_info (vote_generator & generator, std::string const & name); - -class vote_generator_session final -{ -public: - explicit vote_generator_session (vote_generator & vote_generator_a); - void add (nano::root const &, nano::block_hash const &); - void flush (); - -private: - nano::vote_generator & generator; - std::vector> items; -}; }