Continuous vote generation for active elections (#3996)

* Clean up `active_transactions`

* Remove unused `vote_generator_session`

* Generate votes for active elections in fixed intervals

* Exponentially increase vote broadcast interval

* Small cleanup of `election`

* Move `vote_broadcast_interval` into network config

* Add test for continuous voting

* Formatting
This commit is contained in:
Piotr Wójcik 2022-11-14 13:44:31 +01:00 committed by GitHub
commit 58f4d8ea8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 204 additions and 172 deletions

View file

@ -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)

View file

@ -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<nano::node> (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);

View file

@ -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<nano::mutex> 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));

View file

@ -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);
}

View file

@ -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<nano::mutex> 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<nano::mutex> 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)
{

View file

@ -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 } };

View file

@ -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

View file

@ -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;

View file

@ -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,

View file

@ -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<nano::block> 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<nano::mutex> 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<nano::block> const & block_a)
@ -200,8 +215,6 @@ void nano::active_transactions::request_confirm (nano::unique_lock<nano::mutex>
nano::confirmation_solicitor solicitor (node.network, node.config);
solicitor.prepare (node.rep_crawler.principal_representatives (std::numeric_limits<std::size_t>::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<std::chrono::milliseconds> elapsed (nano::timer_state::started);
@ -230,8 +243,6 @@ void nano::active_transactions::request_confirm (nano::unique_lock<nano::mutex>
}
solicitor.flush ();
generator_session.flush ();
final_generator_session.flush ();
lock_a.lock ();
if (node.config.logging.timing_logging ())
@ -324,20 +335,12 @@ std::vector<std::shared_ptr<nano::election>> nano::active_transactions::list_act
void nano::active_transactions::request_loop ()
{
nano::unique_lock<nano::mutex> 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<nano::mutex> 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<nano::mutex> & lock_a, std::shared_ptr<nano::block> const & block_a, nano::election_behavior election_behavior_a, std::function<void (std::shared_ptr<nano::block> 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<nano::vote> 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<nano::mutex> lock (mutex);
return roots.get<tag_root> ().find (root_a) != roots.get<tag_root> ().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<nano::mutex> guard (mutex);
return roots.get<tag_root> ().find (block_a.qualified_root ()) != roots.get<tag_root> ().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<nano::mutex> 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<nano::mutex> lock (mutex);
return roots.empty ();
}
std::size_t nano::active_transactions::size ()
std::size_t nano::active_transactions::size () const
{
nano::lock_guard<nano::mutex> lock (mutex);
return roots.size ();

View file

@ -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<conflict_info,
mi::indexed_by<
mi::sequenced<mi::tag<tag_sequenced>>,
@ -143,11 +143,16 @@ public:
>>;
// clang-format on
ordered_roots roots;
std::unordered_map<nano::block_hash, std::shared_ptr<nano::election>> 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<nano::vote> 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<nano::election> election (nano::qualified_root const &) const;
std::shared_ptr<nano::block> 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<nano::block> const &);
boost::optional<nano::election_status_type> confirm_block (nano::transaction const &, std::shared_ptr<nano::block> const &);
void block_cemented_callback (std::shared_ptr<nano::block> 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<void ()> vacancy_update{ [] () {} };
std::unordered_map<nano::block_hash, std::shared_ptr<nano::election>> 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<nano::election> 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<nano::block_hash, std::shared_ptr<nano::election>> 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<nano::mutex> &, std::shared_ptr<nano::block> const &, nano::election_behavior = nano::election_behavior::normal, std::function<void (std::shared_ptr<nano::block> const &)> const & = nullptr);
void request_loop ();
@ -220,23 +211,37 @@ private:
void cleanup_election (nano::unique_lock<nano::mutex> & lock_a, std::shared_ptr<nano::election>);
// Returns a list of elections sorted by difficulty, mutex must be locked
std::vector<std::shared_ptr<nano::election>> 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<nano::vote> const vote);
void add_inactive_vote_cache (nano::block_hash const & hash, std::shared_ptr<nano::vote> vote);
nano::condition_variable condition;
bool started{ false };
std::atomic<bool> 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<nano::block_hash, std::shared_ptr<nano::election>> 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<bool> stopped{ false };
std::thread thread;
friend class election;
friend class election_scheduler;

View file

@ -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<nano::block> const & block_a, std::function<void (std::shared_ptr<nano::block> const &)> const & confirmation_action_a, std::function<void (nano::account const &)> 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::milliseconds> (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values<std::chrono::milliseconds>::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<nano::mutex> & 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<nano::mutex> 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::block> 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<nano::mutex> 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
}
}
}

View file

@ -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<nano::account, nano::vote_info> votes;
nano::tally_t tally;
};
class election final : public std::enable_shared_from_this<nano::election>
{
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<nano::election::state_t> 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<nano::mutex> &);
/**
* 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<nano::mutex> & 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<nano::mutex> & 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;

View file

@ -765,6 +765,7 @@ void nano::node::start ()
port_mapping.start ();
}
wallets.start ();
active.start ();
generator.start ();
final_generator.start ();
backlog.start ();

View file

@ -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::container_info_component> nano::collect_container_info (nano::vote_generator & vote_generator, std::string const & name)
{
std::size_t candidates_count = 0;

View file

@ -177,16 +177,4 @@ private:
};
std::unique_ptr<container_info_component> 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<std::pair<nano::root, nano::block_hash>> items;
};
}