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:
parent
aaf0630edd
commit
58f4d8ea8e
16 changed files with 204 additions and 172 deletions
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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 } };
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 ();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -765,6 +765,7 @@ void nano::node::start ()
|
|||
port_mapping.start ();
|
||||
}
|
||||
wallets.start ();
|
||||
active.start ();
|
||||
generator.start ();
|
||||
final_generator.start ();
|
||||
backlog.start ();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue