Election state refactor (#2535)
Converting the election class into a state machine instead of working on rebroadcast iterations.
This commit is contained in:
parent
0fa01519c0
commit
5dac531c91
16 changed files with 551 additions and 263 deletions
|
@ -13,6 +13,7 @@ add_executable (core_test
|
|||
conflicts.cpp
|
||||
difficulty.cpp
|
||||
distributed_work.cpp
|
||||
election.cpp
|
||||
entry.cpp
|
||||
epochs.cpp
|
||||
gap_cache.cpp
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
TEST (active_transactions, confirm_one)
|
||||
TEST (active_transactions, confirm_active)
|
||||
{
|
||||
nano::system system (1);
|
||||
auto & node1 = *system.nodes[0];
|
||||
|
@ -15,7 +15,7 @@ TEST (active_transactions, confirm_one)
|
|||
system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv);
|
||||
auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::public_key (), node1.config.receive_minimum.number ()));
|
||||
system.deadline_set (5s);
|
||||
while (!node1.active.empty () && !node1.block_confirmed_or_being_confirmed (node1.store.tx_begin_read (), send->hash ()))
|
||||
while (!node1.active.empty () || !node1.block_confirmed_or_being_confirmed (node1.store.tx_begin_read (), send->hash ()))
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
|
@ -27,12 +27,58 @@ TEST (active_transactions, confirm_one)
|
|||
node1.network.flood_block (send, nano::buffer_drop_policy::no_limiter_drop);
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
while (node2.ledger.cache.cemented_count < 2)
|
||||
while (node2.ledger.cache.cemented_count < 2 || !node2.active.empty ())
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
}
|
||||
|
||||
TEST (active_transactions, confirm_frontier)
|
||||
{
|
||||
nano::system system (1);
|
||||
auto & node1 = *system.nodes[0];
|
||||
// Send and vote for a block before peering with node2
|
||||
system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv);
|
||||
auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::public_key (), node1.config.receive_minimum.number ()));
|
||||
system.deadline_set (5s);
|
||||
while (!node1.active.empty () || !node1.block_confirmed_or_being_confirmed (node1.store.tx_begin_read (), send->hash ()))
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
auto & node2 = *system.add_node (nano::node_config (nano::get_available_port (), system.logging));
|
||||
ASSERT_EQ (nano::process_result::progress, node2.process (*send).code);
|
||||
system.deadline_set (5s);
|
||||
while (node2.ledger.cache.cemented_count < 2 || !node2.active.empty ())
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
}
|
||||
|
||||
TEST (active_transactions, confirm_dependent)
|
||||
{
|
||||
nano::system system;
|
||||
nano::node_flags node_flags;
|
||||
node_flags.disable_request_loop = true;
|
||||
auto & node1 = *system.add_node (node_flags);
|
||||
system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv);
|
||||
auto send1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::public_key (), node1.config.receive_minimum.number ()));
|
||||
auto send2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::public_key (), node1.config.receive_minimum.number ()));
|
||||
auto send3 (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::public_key (), node1.config.receive_minimum.number ()));
|
||||
nano::node_config node_config;
|
||||
node_config.peering_port = nano::get_available_port ();
|
||||
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
auto & node2 = *system.add_node (node_config);
|
||||
node2.process_local (send1);
|
||||
node2.process_local (send2);
|
||||
node2.process_active (send3);
|
||||
system.deadline_set (5s);
|
||||
while (!node2.active.empty ())
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
ASSERT_EQ (4, node2.ledger.cache.cemented_count);
|
||||
}
|
||||
|
||||
TEST (active_transactions, adjusted_difficulty_priority)
|
||||
{
|
||||
nano::system system;
|
||||
|
@ -76,7 +122,7 @@ TEST (active_transactions, adjusted_difficulty_priority)
|
|||
}
|
||||
}
|
||||
system.deadline_set (10s);
|
||||
while (node1.ledger.cache.cemented_count < 5)
|
||||
while (node1.ledger.cache.cemented_count < 5 || !node1.active.empty ())
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
|
@ -400,7 +446,7 @@ TEST (active_transactions, inactive_votes_cache_fork)
|
|||
while (!confirmed)
|
||||
{
|
||||
auto transaction (node.store.tx_begin_read ());
|
||||
confirmed = node.block (send1->hash ()) != nullptr && node.ledger.block_confirmed (transaction, send1->hash ());
|
||||
confirmed = node.block (send1->hash ()) != nullptr && node.ledger.block_confirmed (transaction, send1->hash ()) && node.active.empty ();
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached));
|
||||
|
@ -557,9 +603,11 @@ TEST (active_transactions, update_difficulty)
|
|||
ASSERT_NE (existing3, node2.active.roots.end ());
|
||||
auto const existing4 (node2.active.roots.find (send2->qualified_root ()));
|
||||
ASSERT_NE (existing4, node2.active.roots.end ());
|
||||
auto updated = (existing1->difficulty > difficulty1) && (existing2->difficulty > difficulty2);
|
||||
auto propogated = (existing3->difficulty > difficulty1) && (existing4->difficulty > difficulty2);
|
||||
done = updated && propogated;
|
||||
auto updated1 = existing1->difficulty > difficulty1;
|
||||
auto updated2 = existing2->difficulty > difficulty2;
|
||||
auto propogated1 = existing3->difficulty > difficulty1;
|
||||
auto propogated2 = existing4->difficulty > difficulty2;
|
||||
done = updated1 && updated2 && propogated1 && propogated2;
|
||||
}
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
|
@ -583,15 +631,28 @@ TEST (active_transactions, vote_replays)
|
|||
node.process_active (open1);
|
||||
node.block_processor.flush ();
|
||||
ASSERT_EQ (2, node.active.size ());
|
||||
// First vote is not a replay and confirms the election, second vote should be indeterminate since the election no longer exists
|
||||
// First vote is not a replay and confirms the election, second vote should be a replay since the election has confirmed but not yet removed
|
||||
auto vote_send1 (std::make_shared<nano::vote> (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, send1));
|
||||
ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote_send1));
|
||||
ASSERT_EQ (1, node.active.size ());
|
||||
ASSERT_EQ (2, node.active.size ());
|
||||
ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_send1));
|
||||
// Wait until the election is removed, at which point the vote should be indeterminate
|
||||
system.deadline_set (3s);
|
||||
while (node.active.size () != 1)
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_send1));
|
||||
// Open new account
|
||||
auto vote_open1 (std::make_shared<nano::vote> (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, open1));
|
||||
ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote_open1));
|
||||
ASSERT_TRUE (node.active.empty ());
|
||||
ASSERT_EQ (1, node.active.size ());
|
||||
ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_open1));
|
||||
system.deadline_set (3s);
|
||||
while (!node.active.empty ())
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_open1));
|
||||
ASSERT_EQ (nano::Gxrb_ratio, node.ledger.weight (key.pub));
|
||||
|
||||
|
@ -607,7 +668,76 @@ TEST (active_transactions, vote_replays)
|
|||
ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote2_send2));
|
||||
ASSERT_EQ (1, node.active.size ());
|
||||
ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote1_send2));
|
||||
ASSERT_EQ (1, node.active.size ());
|
||||
ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote1_send2));
|
||||
while (!node.active.empty ())
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
ASSERT_EQ (0, node.active.size ());
|
||||
ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote1_send2));
|
||||
ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote2_send2));
|
||||
}
|
||||
|
||||
TEST (active_transactions, activate_dependencies)
|
||||
{
|
||||
// Ensure that we attempt to backtrack if an election isn't getting confirmed and there are more uncemented blocks to start elections for
|
||||
nano::system system;
|
||||
nano::node_config config (nano::get_available_port (), system.logging);
|
||||
config.enable_voting = true;
|
||||
nano::node_flags flags;
|
||||
flags.disable_bootstrap_listener = true;
|
||||
config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
||||
auto node1 (system.add_node (config, flags));
|
||||
config.peering_port = nano::get_available_port ();
|
||||
auto node2 (system.add_node (config, flags));
|
||||
system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv);
|
||||
nano::genesis genesis;
|
||||
nano::block_builder builder;
|
||||
system.deadline_set (std::chrono::seconds (15));
|
||||
std::shared_ptr<nano::block> block0 = builder.state ()
|
||||
.account (nano::test_genesis_key.pub)
|
||||
.previous (genesis.hash ())
|
||||
.representative (nano::test_genesis_key.pub)
|
||||
.balance (nano::genesis_amount - nano::Gxrb_ratio)
|
||||
.link (0)
|
||||
.sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub)
|
||||
.work (node1->work_generate_blocking (genesis.hash ()).value ())
|
||||
.build ();
|
||||
// Establish a representative
|
||||
node2->process_active (block0);
|
||||
node2->block_processor.flush ();
|
||||
while (node1->block (block0->hash ()) == nullptr)
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
auto block1 = builder.state ()
|
||||
.account (nano::test_genesis_key.pub)
|
||||
.previous (block0->hash ())
|
||||
.representative (nano::test_genesis_key.pub)
|
||||
.balance (nano::genesis_amount - nano::Gxrb_ratio)
|
||||
.link (0)
|
||||
.sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub)
|
||||
.work (node1->work_generate_blocking (block0->hash ()).value ())
|
||||
.build ();
|
||||
{
|
||||
auto transaction = node2->store.tx_begin_write ();
|
||||
ASSERT_EQ (nano::process_result::progress, node2->ledger.process (transaction, *block1).code);
|
||||
}
|
||||
std::shared_ptr<nano::block> block2 = builder.state ()
|
||||
.account (nano::test_genesis_key.pub)
|
||||
.previous (block1->hash ())
|
||||
.representative (nano::test_genesis_key.pub)
|
||||
.balance (nano::genesis_amount - 2 * nano::Gxrb_ratio)
|
||||
.link (0)
|
||||
.sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub)
|
||||
.work (node1->work_generate_blocking (block1->hash ()).value ())
|
||||
.build ();
|
||||
node2->process_active (block2);
|
||||
node2->block_processor.flush ();
|
||||
while (node1->block (block2->hash ()) == nullptr)
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
ASSERT_NE (nullptr, node1->block (block2->hash ()));
|
||||
}
|
||||
|
|
|
@ -1187,13 +1187,8 @@ TEST (confirmation_height, dependent_election)
|
|||
|
||||
add_callback_stats (*node);
|
||||
|
||||
// Wait until it has been processed
|
||||
// Start an election and vote, should confirm the block
|
||||
node->block_confirm (send2);
|
||||
system.deadline_set (10s);
|
||||
while (node->active.size () > 0)
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
|
||||
{
|
||||
// The write guard prevents the confirmation height processor doing any writes.
|
||||
|
@ -1426,17 +1421,6 @@ TEST (confirmation_height, election_winner_details_clearing)
|
|||
add_callback_stats (*node);
|
||||
|
||||
node->block_confirm (send1);
|
||||
system.deadline_set (10s);
|
||||
while (node->active.size () > 0)
|
||||
{
|
||||
ASSERT_NO_ERROR (system.poll ());
|
||||
}
|
||||
|
||||
ASSERT_EQ (0, node->active.list_confirmed ().size ());
|
||||
{
|
||||
nano::lock_guard<std::mutex> guard (node->active.mutex);
|
||||
ASSERT_EQ (0, node->active.blocks.size ());
|
||||
}
|
||||
|
||||
system.deadline_set (10s);
|
||||
while (node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) != 2)
|
||||
|
|
|
@ -34,12 +34,12 @@ TEST (confirmation_solicitor, batches)
|
|||
auto send (std::make_shared<nano::send_block> (nano::genesis_hash, nano::keypair ().pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (nano::genesis_hash)));
|
||||
for (size_t i (0); i < nano::network::confirm_req_hashes_max; ++i)
|
||||
{
|
||||
auto election (std::make_shared<nano::election> (node2, send, false, nullptr));
|
||||
auto election (std::make_shared<nano::election> (node2, send, nullptr));
|
||||
ASSERT_FALSE (node2.active.solicitor.add (*election));
|
||||
}
|
||||
ASSERT_EQ (1, node2.active.solicitor.max_confirm_req_batches);
|
||||
// Reached the maximum amount of requests for the channel
|
||||
auto election (std::make_shared<nano::election> (node2, send, false, nullptr));
|
||||
auto election (std::make_shared<nano::election> (node2, send, nullptr));
|
||||
ASSERT_TRUE (node2.active.solicitor.add (*election));
|
||||
// Broadcasting should be immediate
|
||||
ASSERT_EQ (0, node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::out));
|
||||
|
|
18
nano/core_test/election.cpp
Normal file
18
nano/core_test/election.cpp
Normal file
|
@ -0,0 +1,18 @@
|
|||
#include <nano/core_test/testutil.hpp>
|
||||
#include <nano/node/election.hpp>
|
||||
#include <nano/node/testing.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST (election, construction)
|
||||
{
|
||||
nano::system system (1);
|
||||
nano::genesis genesis;
|
||||
auto & node = *system.nodes[0];
|
||||
auto election = node.active.insert (genesis.open).first;
|
||||
ASSERT_TRUE (election->idle ());
|
||||
election->transition_active ();
|
||||
ASSERT_FALSE (election->idle ());
|
||||
election->transition_passive ();
|
||||
ASSERT_FALSE (election->idle ());
|
||||
}
|
|
@ -768,7 +768,7 @@ TEST (votes, add_one)
|
|||
auto vote1 (std::make_shared<nano::vote> (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 1, send1));
|
||||
ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote1));
|
||||
auto vote2 (std::make_shared<nano::vote> (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 2, send1));
|
||||
ASSERT_EQ (nano::vote_code::indeterminate, node1.active.vote (vote2));
|
||||
ASSERT_EQ (nano::vote_code::vote, node1.active.vote (vote2));
|
||||
lock.lock ();
|
||||
ASSERT_EQ (2, election1.first->last_votes.size ());
|
||||
auto existing1 (election1.first->last_votes.find (nano::test_genesis_key.pub));
|
||||
|
|
|
@ -2134,7 +2134,6 @@ TEST (node, rep_weight)
|
|||
auto vote0 = std::make_shared<nano::vote> (nano::test_genesis_key.pub, nano::test_genesis_key.prv, 0, genesis.open);
|
||||
auto vote1 = std::make_shared<nano::vote> (keypair1.pub, keypair1.prv, 0, genesis.open);
|
||||
auto vote2 = std::make_shared<nano::vote> (keypair2.pub, keypair2.prv, 0, genesis.open);
|
||||
node.rep_crawler.add (genesis.open->hash ());
|
||||
node.rep_crawler.response (channel0, vote0);
|
||||
node.rep_crawler.response (channel1, vote1);
|
||||
node.rep_crawler.response (channel2, vote2);
|
||||
|
@ -2217,7 +2216,6 @@ TEST (node, rep_remove)
|
|||
nano::amount amount100 (100);
|
||||
node.network.udp_channels.insert (endpoint0, node.network_params.protocol.protocol_version);
|
||||
auto vote1 = std::make_shared<nano::vote> (keypair1.pub, keypair1.prv, 0, genesis.open);
|
||||
node.rep_crawler.add (genesis.hash ());
|
||||
node.rep_crawler.response (channel0, vote1);
|
||||
system.deadline_set (5s);
|
||||
while (node.rep_crawler.representative_count () != 1)
|
||||
|
|
|
@ -17,12 +17,7 @@ node (node_a),
|
|||
multipliers_cb (20, 1.),
|
||||
trended_active_difficulty (node_a.network_params.network.publish_threshold),
|
||||
solicitor (node_a.network, node_a.network_params.network),
|
||||
long_election_threshold (node_a.network_params.network.is_test_network () ? 2s : 24s),
|
||||
election_request_delay (node_a.network_params.network.is_test_network () ? 0s : 1s),
|
||||
election_time_to_live (node_a.network_params.network.is_test_network () ? 0s : 10s),
|
||||
min_time_between_requests (node_a.network_params.network.is_test_network () ? 25ms : 3s),
|
||||
min_time_between_floods (node_a.network_params.network.is_test_network () ? 50ms : 6s),
|
||||
min_request_count_flood (node_a.network_params.network.is_test_network () ? 0 : 2),
|
||||
election_time_to_live (node_a.network_params.network.is_test_network () ? 0s : 2s),
|
||||
thread ([this]() {
|
||||
nano::thread_role::set (nano::thread_role::name::request_loop);
|
||||
request_loop ();
|
||||
|
@ -38,8 +33,6 @@ thread ([this]() {
|
|||
this->block_already_cemented_callback (hash_a);
|
||||
});
|
||||
|
||||
debug_assert (min_time_between_requests > std::chrono::milliseconds (node.network_params.network.request_interval_ms));
|
||||
debug_assert (min_time_between_floods > std::chrono::milliseconds (node.network_params.network.request_interval_ms));
|
||||
nano::unique_lock<std::mutex> lock (mutex);
|
||||
condition.wait (lock, [& started = started] { return started; });
|
||||
}
|
||||
|
@ -104,8 +97,10 @@ void nano::active_transactions::search_frontiers (nano::transaction const & tran
|
|||
if (info.block_count > confirmation_height_info.height && !this->confirmation_height_processor.is_processing_block (info.head))
|
||||
{
|
||||
auto block (this->node.store.block_get (transaction_a, info.head));
|
||||
if (this->insert (block, true).first)
|
||||
auto election = this->insert (block);
|
||||
if (election.second)
|
||||
{
|
||||
election.first->transition_active ();
|
||||
++elections_count;
|
||||
// Calculate votes for local representatives
|
||||
if (representative)
|
||||
|
@ -163,7 +158,7 @@ void nano::active_transactions::block_cemented_callback (std::shared_ptr<nano::b
|
|||
// Make sure mutex is held before election usage so we know that confirm_once has
|
||||
// finished removing the root from active to avoid any data race.
|
||||
nano::unique_lock<std::mutex> lk (mutex);
|
||||
if (election->confirmed () && !election->stopped && election->status.winner->hash () == hash)
|
||||
if (election->confirmed () && election->status.winner->hash () == hash)
|
||||
{
|
||||
add_confirmed (election->status, block_a->qualified_root ());
|
||||
lk.unlock ();
|
||||
|
@ -206,60 +201,10 @@ void nano::active_transactions::block_already_cemented_callback (nano::block_has
|
|||
election_winner_details.erase (hash_a);
|
||||
}
|
||||
|
||||
void nano::active_transactions::election_escalate (std::shared_ptr<nano::election> & election_l, nano::transaction const & transaction_l, size_t const & roots_size_l)
|
||||
{
|
||||
constexpr unsigned high_confirmation_request_count{ 128 };
|
||||
// Log votes for very long unconfirmed elections
|
||||
if (election_l->confirmation_request_count % (4 * high_confirmation_request_count) == 1)
|
||||
{
|
||||
auto tally_l (election_l->tally ());
|
||||
election_l->log_votes (tally_l);
|
||||
}
|
||||
/*
|
||||
* Escalation for long unconfirmed elections
|
||||
* Start new elections for previous block & source if there are less than 100 active elections
|
||||
*/
|
||||
if (election_l->confirmation_request_count % high_confirmation_request_count == 1 && roots_size_l < 100 && !node.network_params.network.is_test_network ())
|
||||
{
|
||||
bool escalated_l (false);
|
||||
std::shared_ptr<nano::block> previous_l;
|
||||
auto previous_hash_l (election_l->status.winner->previous ());
|
||||
if (!previous_hash_l.is_zero ())
|
||||
{
|
||||
previous_l = node.store.block_get (transaction_l, previous_hash_l);
|
||||
if (previous_l != nullptr && blocks.find (previous_hash_l) == blocks.end () && !node.block_confirmed_or_being_confirmed (transaction_l, previous_hash_l))
|
||||
{
|
||||
insert_impl (std::move (previous_l), true);
|
||||
escalated_l = true;
|
||||
}
|
||||
}
|
||||
/* If previous block not existing/not commited yet, block_source can cause segfault for state blocks
|
||||
So source check can be done only if previous != nullptr or previous is 0 (open account) */
|
||||
if (previous_hash_l.is_zero () || previous_l != nullptr)
|
||||
{
|
||||
auto source_hash_l (node.ledger.block_source (transaction_l, *election_l->status.winner));
|
||||
if (!source_hash_l.is_zero () && source_hash_l != previous_hash_l && blocks.find (source_hash_l) == blocks.end ())
|
||||
{
|
||||
auto source_l (node.store.block_get (transaction_l, source_hash_l));
|
||||
if (source_l != nullptr && !node.block_confirmed_or_being_confirmed (transaction_l, source_hash_l))
|
||||
{
|
||||
insert_impl (std::move (source_l), true);
|
||||
escalated_l = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (escalated_l)
|
||||
{
|
||||
election_l->update_dependent ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void nano::active_transactions::request_confirm (nano::unique_lock<std::mutex> & lock_a)
|
||||
{
|
||||
debug_assert (!mutex.try_lock ());
|
||||
auto transaction_l (node.store.tx_begin_read ());
|
||||
std::unordered_set<nano::qualified_root> inactive_l;
|
||||
/*
|
||||
* Confirm frontiers when there aren't many confirmations already pending and node finished initial bootstrap
|
||||
* In auto mode start confirm only if node contains almost principal representative (half of required for principal weight)
|
||||
|
@ -283,19 +228,9 @@ void nano::active_transactions::request_confirm (nano::unique_lock<std::mutex> &
|
|||
solicitor.prepare (node.rep_crawler.representatives (node.network_params.protocol.tcp_realtime_protocol_version_min));
|
||||
lock_a.lock ();
|
||||
|
||||
auto const now (std::chrono::steady_clock::now ());
|
||||
// Any new election started from process_live only gets requests after at least 1 second
|
||||
auto cutoff_l (now - election_request_delay);
|
||||
// Elections taking too long get escalated
|
||||
auto long_election_cutoff_l (now - long_election_threshold);
|
||||
// The lowest PoW difficulty elections have a maximum time to live if they are beyond the soft threshold size for the container
|
||||
auto election_ttl_cutoff_l (now - election_time_to_live);
|
||||
// Rate-limitting floods
|
||||
auto const flood_cutoff (now - min_time_between_floods);
|
||||
// Rate-limitting confirmation requests
|
||||
auto const request_cutoff (now - min_time_between_requests);
|
||||
|
||||
auto election_ttl_cutoff_l (std::chrono::steady_clock::now () - election_time_to_live);
|
||||
auto roots_size_l (roots.size ());
|
||||
bool saturated_l (roots_size_l > node.config.active_elections_size / 2);
|
||||
auto & sorted_roots_l = roots.get<tag_difficulty> ();
|
||||
size_t count_l{ 0 };
|
||||
|
||||
|
@ -306,60 +241,22 @@ void nano::active_transactions::request_confirm (nano::unique_lock<std::mutex> &
|
|||
* Elections extending the soft config.active_elections_size limit are flushed after a certain time-to-live cutoff
|
||||
* Flushed elections are later re-activated via frontier confirmation
|
||||
*/
|
||||
for (auto i = sorted_roots_l.begin (), n = sorted_roots_l.end (); i != n; ++i, ++count_l)
|
||||
for (auto i = sorted_roots_l.begin (), n = sorted_roots_l.end (); i != n; ++count_l)
|
||||
{
|
||||
auto election_l (i->election);
|
||||
auto root_l (i->root);
|
||||
if (election_l->confirmed () || (election_l->confirmation_request_count != 0 && !node.ledger.could_fit (transaction_l, *election_l->status.winner)))
|
||||
auto & election_l (i->election);
|
||||
if ((count_l >= node.config.active_elections_size && election_l->election_start < election_ttl_cutoff_l && !node.wallets.watcher->is_watched (i->root)) || election_l->transition_time (saturated_l))
|
||||
{
|
||||
election_l->stop ();
|
||||
election_l->clear_blocks ();
|
||||
i = sorted_roots_l.erase (i);
|
||||
}
|
||||
// Erase finished elections
|
||||
if ((election_l->stopped))
|
||||
else
|
||||
{
|
||||
inactive_l.insert (root_l);
|
||||
}
|
||||
// Drop elections
|
||||
else if (count_l >= node.config.active_elections_size && election_l->election_start < election_ttl_cutoff_l && !node.wallets.watcher->is_watched (root_l))
|
||||
{
|
||||
election_l->stop ();
|
||||
inactive_l.insert (root_l);
|
||||
}
|
||||
// Attempt obtaining votes
|
||||
else if (election_l->skip_delay || election_l->election_start < cutoff_l)
|
||||
{
|
||||
// Broadcast the winner when elections are taking longer to confirm
|
||||
if (election_l->confirmation_request_count >= min_request_count_flood && election_l->last_broadcast < flood_cutoff && !solicitor.broadcast (*election_l))
|
||||
{
|
||||
election_l->last_broadcast = now;
|
||||
}
|
||||
// Rate-limited requests for confirmation
|
||||
else if (election_l->last_request < request_cutoff && !solicitor.add (*election_l))
|
||||
{
|
||||
++election_l->confirmation_request_count;
|
||||
election_l->last_request = now;
|
||||
}
|
||||
// Escalate long election after a certain time and number of requests performed
|
||||
if (election_l->confirmation_request_count > 4 && election_l->election_start < long_election_cutoff_l)
|
||||
{
|
||||
election_escalate (election_l, transaction_l, roots_size_l);
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
lock_a.unlock ();
|
||||
solicitor.flush ();
|
||||
lock_a.lock ();
|
||||
// Erase inactive elections
|
||||
for (auto i (inactive_l.begin ()), n (inactive_l.end ()); i != n; ++i)
|
||||
{
|
||||
auto root_it (roots.get<tag_root> ().find (*i));
|
||||
if (root_it != roots.get<tag_root> ().end ())
|
||||
{
|
||||
root_it->election->clear_blocks ();
|
||||
root_it->election->clear_dependent ();
|
||||
roots.get<tag_root> ().erase (root_it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void nano::active_transactions::request_loop ()
|
||||
|
@ -566,7 +463,7 @@ void nano::active_transactions::stop ()
|
|||
roots.clear ();
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<nano::election>, bool> nano::active_transactions::insert_impl (std::shared_ptr<nano::block> block_a, bool const skip_delay_a, std::function<void(std::shared_ptr<nano::block>)> const & confirmation_action_a)
|
||||
std::pair<std::shared_ptr<nano::election>, bool> nano::active_transactions::insert_impl (std::shared_ptr<nano::block> block_a, std::function<void(std::shared_ptr<nano::block>)> const & confirmation_action_a)
|
||||
{
|
||||
std::pair<std::shared_ptr<nano::election>, bool> result = { nullptr, false };
|
||||
if (!stopped)
|
||||
|
@ -579,7 +476,7 @@ std::pair<std::shared_ptr<nano::election>, bool> nano::active_transactions::inse
|
|||
{
|
||||
result.second = true;
|
||||
auto hash (block_a->hash ());
|
||||
result.first = nano::make_shared<nano::election> (node, block_a, skip_delay_a, confirmation_action_a);
|
||||
result.first = nano::make_shared<nano::election> (node, block_a, confirmation_action_a);
|
||||
auto difficulty (block_a->difficulty ());
|
||||
roots.get<tag_root> ().emplace (nano::conflict_info{ root, difficulty, difficulty, result.first });
|
||||
blocks.emplace (hash, result.first);
|
||||
|
@ -595,10 +492,10 @@ std::pair<std::shared_ptr<nano::election>, bool> nano::active_transactions::inse
|
|||
return result;
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<nano::election>, bool> nano::active_transactions::insert (std::shared_ptr<nano::block> block_a, bool const skip_delay_a, std::function<void(std::shared_ptr<nano::block>)> const & confirmation_action_a)
|
||||
std::pair<std::shared_ptr<nano::election>, bool> nano::active_transactions::insert (std::shared_ptr<nano::block> block_a, std::function<void(std::shared_ptr<nano::block>)> const & confirmation_action_a)
|
||||
{
|
||||
nano::lock_guard<std::mutex> lock (mutex);
|
||||
return insert_impl (block_a, skip_delay_a, confirmation_action_a);
|
||||
return insert_impl (block_a, confirmation_action_a);
|
||||
}
|
||||
|
||||
// Validate a vote and apply it to the current election if one exists
|
||||
|
@ -671,6 +568,18 @@ bool nano::active_transactions::active (nano::block const & block_a)
|
|||
return active (block_a.qualified_root ());
|
||||
}
|
||||
|
||||
std::shared_ptr<nano::election> nano::active_transactions::election (nano::qualified_root const & root_a) const
|
||||
{
|
||||
std::shared_ptr<nano::election> result;
|
||||
nano::lock_guard<std::mutex> lock (mutex);
|
||||
auto existing = roots.get<tag_root> ().find (root_a);
|
||||
if (existing != roots.get<tag_root> ().end ())
|
||||
{
|
||||
result = existing->election;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void nano::active_transactions::update_difficulty (std::shared_ptr<nano::block> block_a)
|
||||
{
|
||||
nano::unique_lock<std::mutex> lock (mutex);
|
||||
|
@ -711,7 +620,7 @@ void nano::active_transactions::adjust_difficulty (nano::block_hash const & hash
|
|||
if (processed_blocks.find (hash) == processed_blocks.end ())
|
||||
{
|
||||
auto existing (blocks.find (hash));
|
||||
if (existing != blocks.end () && !existing->second->confirmed () && !existing->second->stopped && existing->second->status.winner->hash () == hash)
|
||||
if (existing != blocks.end () && !existing->second->confirmed () && existing->second->status.winner->hash () == hash)
|
||||
{
|
||||
auto previous (existing->second->status.winner->previous ());
|
||||
if (!previous.is_zero ())
|
||||
|
@ -793,10 +702,9 @@ void nano::active_transactions::update_active_difficulty (nano::unique_lock<std:
|
|||
std::vector<uint64_t> active_root_difficulties;
|
||||
active_root_difficulties.reserve (std::min (sorted_roots.size (), node.config.active_elections_size));
|
||||
size_t count (0);
|
||||
auto cutoff (std::chrono::steady_clock::now () - election_request_delay - 1s);
|
||||
for (auto it (sorted_roots.begin ()), end (sorted_roots.end ()); it != end && count++ < node.config.active_elections_size; ++it)
|
||||
{
|
||||
if (!it->election->confirmed () && !it->election->stopped && it->election->election_start < cutoff)
|
||||
if (!it->election->confirmed () && !it->election->idle ())
|
||||
{
|
||||
active_root_difficulties.push_back (it->adjusted_difficulty);
|
||||
}
|
||||
|
@ -865,9 +773,7 @@ void nano::active_transactions::erase (nano::block const & block_a)
|
|||
auto root_it (roots.get<tag_root> ().find (block_a.qualified_root ()));
|
||||
if (root_it != roots.get<tag_root> ().end ())
|
||||
{
|
||||
root_it->election->stop ();
|
||||
root_it->election->clear_blocks ();
|
||||
root_it->election->clear_dependent ();
|
||||
roots.get<tag_root> ().erase (root_it);
|
||||
node.logger.try_log (boost::str (boost::format ("Election erased for block block %1% root %2%") % block_a.hash ().to_string () % block_a.root ().to_string ()));
|
||||
}
|
||||
|
@ -894,7 +800,7 @@ bool nano::active_transactions::publish (std::shared_ptr<nano::block> block_a)
|
|||
{
|
||||
auto election (existing->election);
|
||||
result = election->publish (block_a);
|
||||
if (!result && !election->confirmed ())
|
||||
if (!result)
|
||||
{
|
||||
blocks.emplace (block_a->hash (), election);
|
||||
}
|
||||
|
@ -910,7 +816,7 @@ boost::optional<nano::election_status_type> nano::active_transactions::confirm_b
|
|||
auto existing (blocks.find (hash));
|
||||
if (existing != blocks.end ())
|
||||
{
|
||||
if (!existing->second->confirmed () && !existing->second->stopped && existing->second->status.winner->hash () == hash)
|
||||
if (!existing->second->confirmed () && existing->second->status.winner->hash () == hash)
|
||||
{
|
||||
existing->second->confirm_once (nano::election_status_type::active_confirmation_height);
|
||||
return nano::election_status_type::active_confirmation_height;
|
||||
|
|
|
@ -74,6 +74,8 @@ public:
|
|||
// Holds all active blocks i.e. recently added blocks that need confirmation
|
||||
class active_transactions final
|
||||
{
|
||||
friend class nano::election;
|
||||
|
||||
// clang-format off
|
||||
class tag_account {};
|
||||
class tag_difficulty {};
|
||||
|
@ -90,13 +92,14 @@ public:
|
|||
// Start an election for a block
|
||||
// Call action with confirmed block, may be different than what we started with
|
||||
// clang-format off
|
||||
std::pair<std::shared_ptr<nano::election>, bool> insert (std::shared_ptr<nano::block>, bool const = false, std::function<void(std::shared_ptr<nano::block>)> const & = [](std::shared_ptr<nano::block>) {});
|
||||
std::pair<std::shared_ptr<nano::election>, bool> insert (std::shared_ptr<nano::block>, std::function<void(std::shared_ptr<nano::block>)> const & = [](std::shared_ptr<nano::block>) {});
|
||||
// clang-format on
|
||||
// Distinguishes replay votes, cannot be determined if the block is not in any election
|
||||
nano::vote_code vote (std::shared_ptr<nano::vote>);
|
||||
// Is the root of this block in the roots container
|
||||
bool active (nano::block const &);
|
||||
bool active (nano::qualified_root const &);
|
||||
std::shared_ptr<nano::election> election (nano::qualified_root const &) const;
|
||||
void update_difficulty (std::shared_ptr<nano::block>);
|
||||
void adjust_difficulty (nano::block_hash const &);
|
||||
void update_active_difficulty (nano::unique_lock<std::mutex> &);
|
||||
|
@ -130,7 +133,7 @@ public:
|
|||
void erase_inactive_votes_cache (nano::block_hash const &);
|
||||
nano::confirmation_height_processor & confirmation_height_processor;
|
||||
nano::node & node;
|
||||
std::mutex mutex;
|
||||
mutable std::mutex mutex;
|
||||
boost::circular_buffer<double> multipliers_cb;
|
||||
uint64_t trended_active_difficulty;
|
||||
size_t priority_cementable_frontiers_size ();
|
||||
|
@ -147,11 +150,10 @@ private:
|
|||
|
||||
// Call action with confirmed block, may be different than what we started with
|
||||
// clang-format off
|
||||
std::pair<std::shared_ptr<nano::election>, bool> insert_impl (std::shared_ptr<nano::block>, bool const = false, std::function<void(std::shared_ptr<nano::block>)> const & = [](std::shared_ptr<nano::block>) {});
|
||||
std::pair<std::shared_ptr<nano::election>, bool> insert_impl (std::shared_ptr<nano::block>, std::function<void(std::shared_ptr<nano::block>)> const & = [](std::shared_ptr<nano::block>) {});
|
||||
// clang-format on
|
||||
void request_loop ();
|
||||
void search_frontiers (nano::transaction const &);
|
||||
void election_escalate (std::shared_ptr<nano::election> &, nano::transaction const &, size_t const &);
|
||||
void request_confirm (nano::unique_lock<std::mutex> &);
|
||||
nano::account next_frontier_account{ 0 };
|
||||
std::chrono::steady_clock::time_point next_frontier_check{ std::chrono::steady_clock::now () };
|
||||
|
@ -159,18 +161,8 @@ private:
|
|||
bool started{ false };
|
||||
std::atomic<bool> stopped{ false };
|
||||
|
||||
// Minimum time an election must be active before escalation
|
||||
std::chrono::seconds const long_election_threshold;
|
||||
// Delay until requesting confirmation for an election
|
||||
std::chrono::milliseconds const election_request_delay;
|
||||
// Maximum time an election can be kept active if it is extending the container
|
||||
std::chrono::seconds const election_time_to_live;
|
||||
// Minimum time between confirmation requests for an election
|
||||
std::chrono::milliseconds const min_time_between_requests;
|
||||
// Minimum time between broadcasts of the current winner of an election, as a backup to requesting confirmations
|
||||
std::chrono::milliseconds const min_time_between_floods;
|
||||
// Minimum election request count to start broadcasting blocks, as a backup to requesting confirmations
|
||||
size_t const min_request_count_flood;
|
||||
|
||||
// clang-format off
|
||||
boost::multi_index_container<nano::qualified_root,
|
||||
|
@ -206,7 +198,6 @@ private:
|
|||
ordered_cache inactive_votes_cache;
|
||||
// clang-format on
|
||||
bool inactive_votes_bootstrap_check (std::vector<nano::account> const &, nano::block_hash const &, bool &);
|
||||
static size_t constexpr dropped_elections_cache_max{ 32 * 1024 };
|
||||
boost::thread thread;
|
||||
|
||||
friend class confirmation_height_prioritize_frontiers_Test;
|
||||
|
|
|
@ -372,7 +372,11 @@ void nano::block_processor::process_live (nano::block_hash const & hash_a, std::
|
|||
}
|
||||
|
||||
// Start collecting quorum on block
|
||||
node.active.insert (block_a, false);
|
||||
auto election = node.active.insert (block_a);
|
||||
if (election.second)
|
||||
{
|
||||
election.first->transition_passive ();
|
||||
}
|
||||
|
||||
// Announce block contents to the network
|
||||
if (initial_publish_a)
|
||||
|
|
|
@ -3,20 +3,29 @@
|
|||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
int constexpr nano::election::passive_duration_factor;
|
||||
int constexpr nano::election::active_duration_factor;
|
||||
int constexpr nano::election::confirmed_duration_factor;
|
||||
int constexpr nano::election::confirmed_duration_factor_saturated;
|
||||
|
||||
std::chrono::milliseconds nano::election::base_latency () const
|
||||
{
|
||||
return node.network_params.network.is_test_network () ? 25ms : 1000ms;
|
||||
}
|
||||
|
||||
nano::election_vote_result::election_vote_result (bool replay_a, bool processed_a)
|
||||
{
|
||||
replay = replay_a;
|
||||
processed = processed_a;
|
||||
}
|
||||
|
||||
nano::election::election (nano::node & node_a, std::shared_ptr<nano::block> block_a, bool const skip_delay_a, std::function<void(std::shared_ptr<nano::block>)> const & confirmation_action_a) :
|
||||
nano::election::election (nano::node & node_a, std::shared_ptr<nano::block> block_a, std::function<void(std::shared_ptr<nano::block>)> const & confirmation_action_a) :
|
||||
confirmation_action (confirmation_action_a),
|
||||
confirmed_m (false),
|
||||
state_start (std::chrono::steady_clock::now ()),
|
||||
node (node_a),
|
||||
election_start (std::chrono::steady_clock::now ()),
|
||||
status ({ block_a, 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 }),
|
||||
skip_delay (skip_delay_a),
|
||||
stopped (false)
|
||||
status ({ block_a, 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 })
|
||||
{
|
||||
last_votes.emplace (node.network_params.random.not_an_account, nano::vote_info{ std::chrono::steady_clock::now (), 0, block_a->hash () });
|
||||
blocks.emplace (block_a->hash (), block_a);
|
||||
|
@ -26,7 +35,7 @@ stopped (false)
|
|||
void nano::election::confirm_once (nano::election_status_type type_a)
|
||||
{
|
||||
debug_assert (!node.active.mutex.try_lock ());
|
||||
if (!confirmed_m.exchange (true))
|
||||
if (state_m.exchange (nano::election::state_t::confirmed) != nano::election::state_t::confirmed)
|
||||
{
|
||||
status.election_end = std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::system_clock::now ().time_since_epoch ());
|
||||
status.election_duration = std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::steady_clock::now () - election_start);
|
||||
|
@ -48,30 +57,254 @@ void nano::election::confirm_once (nano::election_status_type type_a)
|
|||
node_l->process_confirmed (status_l, this_l);
|
||||
confirmation_action_l (status_l.winner);
|
||||
});
|
||||
clear_blocks ();
|
||||
clear_dependent ();
|
||||
node.active.roots.erase (status.winner->qualified_root ());
|
||||
}
|
||||
}
|
||||
|
||||
void nano::election::stop ()
|
||||
bool nano::election::valid_change (nano::election::state_t expected_a, nano::election::state_t desired_a) const
|
||||
{
|
||||
bool result = false;
|
||||
switch (expected_a)
|
||||
{
|
||||
case nano::election::state_t::idle:
|
||||
switch (desired_a)
|
||||
{
|
||||
case nano::election::state_t::passive:
|
||||
case nano::election::state_t::active:
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case nano::election::state_t::passive:
|
||||
switch (desired_a)
|
||||
{
|
||||
case nano::election::state_t::idle:
|
||||
case nano::election::state_t::active:
|
||||
case nano::election::state_t::confirmed:
|
||||
case nano::election::state_t::expired_unconfirmed:
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case nano::election::state_t::active:
|
||||
switch (desired_a)
|
||||
{
|
||||
case nano::election::state_t::idle:
|
||||
case nano::election::state_t::backtracking:
|
||||
case nano::election::state_t::confirmed:
|
||||
case nano::election::state_t::expired_unconfirmed:
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case nano::election::state_t::backtracking:
|
||||
switch (desired_a)
|
||||
{
|
||||
case nano::election::state_t::idle:
|
||||
case nano::election::state_t::confirmed:
|
||||
case nano::election::state_t::expired_unconfirmed:
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case nano::election::state_t::confirmed:
|
||||
switch (desired_a)
|
||||
{
|
||||
case nano::election::state_t::expired_confirmed:
|
||||
result = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case nano::election::state_t::expired_unconfirmed:
|
||||
break;
|
||||
case nano::election::state_t::expired_confirmed:
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool nano::election::state_change (nano::election::state_t expected_a, nano::election::state_t desired_a)
|
||||
{
|
||||
debug_assert (!timepoints_mutex.try_lock ());
|
||||
bool result = true;
|
||||
if (valid_change (expected_a, desired_a))
|
||||
{
|
||||
if (state_m.compare_exchange_strong (expected_a, desired_a))
|
||||
{
|
||||
state_start = std::chrono::steady_clock::now ();
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
debug_assert (false);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void nano::election::send_confirm_req ()
|
||||
{
|
||||
if (last_req + std::chrono::seconds (15) < std::chrono::steady_clock::now ())
|
||||
{
|
||||
if (!node.active.solicitor.add (*this))
|
||||
{
|
||||
last_req = std::chrono::steady_clock::now ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void nano::election::transition_passive ()
|
||||
{
|
||||
nano::lock_guard<std::mutex> guard (timepoints_mutex);
|
||||
transition_passive_impl ();
|
||||
}
|
||||
|
||||
void nano::election::transition_passive_impl ()
|
||||
{
|
||||
state_change (nano::election::state_t::idle, nano::election::state_t::passive);
|
||||
}
|
||||
|
||||
void nano::election::transition_active ()
|
||||
{
|
||||
nano::lock_guard<std::mutex> guard (timepoints_mutex);
|
||||
transition_active_impl ();
|
||||
}
|
||||
|
||||
void nano::election::transition_active_impl ()
|
||||
{
|
||||
if (!state_change (nano::election::state_t::idle, nano::election::state_t::active))
|
||||
{
|
||||
if (base_latency () * 5 < std::chrono::steady_clock::now () - last_block)
|
||||
{
|
||||
last_block = std::chrono::steady_clock::now ();
|
||||
node.network.flood_block (status.winner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool nano::election::idle () const
|
||||
{
|
||||
return state_m == nano::election::state_t::idle;
|
||||
}
|
||||
|
||||
bool nano::election::confirmed () const
|
||||
{
|
||||
return state_m == nano::election::state_t::confirmed || state_m == nano::election::state_t::expired_confirmed;
|
||||
}
|
||||
|
||||
void nano::election::activate_dependencies ()
|
||||
{
|
||||
auto transaction = node.store.tx_begin_read ();
|
||||
bool escalated_l (false);
|
||||
std::shared_ptr<nano::block> previous_l;
|
||||
auto previous_hash_l (status.winner->previous ());
|
||||
if (!previous_hash_l.is_zero () && node.active.blocks.find (previous_hash_l) == node.active.blocks.end ())
|
||||
{
|
||||
previous_l = node.store.block_get (transaction, previous_hash_l);
|
||||
if (previous_l != nullptr && !node.block_confirmed_or_being_confirmed (transaction, previous_hash_l))
|
||||
{
|
||||
auto election = node.active.insert_impl (previous_l);
|
||||
if (election.second)
|
||||
{
|
||||
election.first->transition_active ();
|
||||
escalated_l = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* If previous block not existing/not commited yet, block_source can cause segfault for state blocks
|
||||
So source check can be done only if previous != nullptr or previous is 0 (open account) */
|
||||
if (previous_hash_l.is_zero () || previous_l != nullptr)
|
||||
{
|
||||
auto source_hash_l (node.ledger.block_source (transaction, *status.winner));
|
||||
if (!source_hash_l.is_zero () && source_hash_l != previous_hash_l && node.active.blocks.find (source_hash_l) == node.active.blocks.end ())
|
||||
{
|
||||
auto source_l (node.store.block_get (transaction, source_hash_l));
|
||||
if (source_l != nullptr && !node.block_confirmed_or_being_confirmed (transaction, source_hash_l))
|
||||
{
|
||||
auto election = node.active.insert_impl (source_l);
|
||||
if (election.second)
|
||||
{
|
||||
election.first->transition_active ();
|
||||
escalated_l = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (escalated_l)
|
||||
{
|
||||
update_dependent ();
|
||||
}
|
||||
}
|
||||
|
||||
void nano::election::broadcast_block ()
|
||||
{
|
||||
if (base_latency () * 5 < std::chrono::steady_clock::now () - last_block)
|
||||
{
|
||||
if (!node.active.solicitor.broadcast (*this))
|
||||
{
|
||||
last_block = std::chrono::steady_clock::now ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool nano::election::transition_time (bool const saturated_a)
|
||||
{
|
||||
debug_assert (!node.active.mutex.try_lock ());
|
||||
if (!stopped && !confirmed ())
|
||||
nano::unique_lock<std::mutex> lock (timepoints_mutex);
|
||||
bool result = false;
|
||||
switch (state_m)
|
||||
{
|
||||
stopped = true;
|
||||
status.election_end = std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::system_clock::now ().time_since_epoch ());
|
||||
status.election_duration = std::chrono::duration_cast<std::chrono::milliseconds> (std::chrono::steady_clock::now () - election_start);
|
||||
status.confirmation_request_count = confirmation_request_count;
|
||||
status.block_count = nano::narrow_cast<decltype (status.block_count)> (blocks.size ());
|
||||
status.voter_count = nano::narrow_cast<decltype (status.voter_count)> (last_votes.size ());
|
||||
status.type = nano::election_status_type::stopped;
|
||||
case nano::election::state_t::idle:
|
||||
break;
|
||||
case nano::election::state_t::passive:
|
||||
{
|
||||
if (base_latency () * passive_duration_factor < std::chrono::steady_clock::now () - state_start)
|
||||
{
|
||||
state_change (nano::election::state_t::passive, nano::election::state_t::active);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case nano::election::state_t::active:
|
||||
broadcast_block ();
|
||||
send_confirm_req ();
|
||||
if (base_latency () * active_duration_factor < std::chrono::steady_clock::now () - state_start)
|
||||
{
|
||||
state_change (nano::election::state_t::active, nano::election::state_t::backtracking);
|
||||
lock.unlock ();
|
||||
activate_dependencies ();
|
||||
}
|
||||
break;
|
||||
case nano::election::state_t::backtracking:
|
||||
broadcast_block ();
|
||||
send_confirm_req ();
|
||||
break;
|
||||
case nano::election::state_t::confirmed:
|
||||
if (base_latency () * (saturated_a ? confirmed_duration_factor_saturated : confirmed_duration_factor) < std::chrono::steady_clock::now () - state_start)
|
||||
{
|
||||
result = true;
|
||||
state_change (nano::election::state_t::confirmed, nano::election::state_t::expired_confirmed);
|
||||
}
|
||||
break;
|
||||
case nano::election::state_t::expired_unconfirmed:
|
||||
case nano::election::state_t::expired_confirmed:
|
||||
debug_assert (false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool nano::election::confirmed ()
|
||||
{
|
||||
return confirmed_m;
|
||||
// Note: lock (timepoints_mutex) is at an unknown state here - possibly unlocked before activate_dependencies
|
||||
if (!confirmed () && std::chrono::minutes (5) < std::chrono::steady_clock::now () - election_start)
|
||||
{
|
||||
result = true;
|
||||
state_change (state_m.load (), nano::election::state_t::expired_unconfirmed);
|
||||
status.type = nano::election_status_type::stopped;
|
||||
log_votes (tally ());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool nano::election::have_quorum (nano::tally_t const & tally_a, nano::uint128_t tally_sum) const
|
||||
|
@ -189,10 +422,10 @@ nano::election_vote_result nano::election::vote (nano::account rep, uint64_t seq
|
|||
}
|
||||
else
|
||||
{
|
||||
auto last_vote (last_vote_it->second);
|
||||
if (last_vote.sequence < sequence || (last_vote.sequence == sequence && last_vote.hash < block_hash))
|
||||
auto last_vote_l (last_vote_it->second);
|
||||
if (last_vote_l.sequence < sequence || (last_vote_l.sequence == sequence && last_vote_l.hash < block_hash))
|
||||
{
|
||||
if (last_vote.time <= std::chrono::steady_clock::now () - std::chrono::seconds (cooldown))
|
||||
if (last_vote_l.time <= std::chrono::steady_clock::now () - std::chrono::seconds (cooldown))
|
||||
{
|
||||
should_process = true;
|
||||
}
|
||||
|
@ -217,8 +450,9 @@ nano::election_vote_result nano::election::vote (nano::account rep, uint64_t seq
|
|||
|
||||
bool nano::election::publish (std::shared_ptr<nano::block> block_a)
|
||||
{
|
||||
auto result (false);
|
||||
if (blocks.size () >= 10)
|
||||
// Do not insert new blocks if already confirmed
|
||||
auto result (confirmed ());
|
||||
if (!result && blocks.size () >= 10)
|
||||
{
|
||||
if (last_tally[block_a->hash ()] < node.online_reps.online_stake () / 10)
|
||||
{
|
||||
|
@ -276,7 +510,7 @@ void nano::election::update_dependent ()
|
|||
for (auto & block_search : blocks_search)
|
||||
{
|
||||
auto existing (node.active.blocks.find (block_search));
|
||||
if (existing != node.active.blocks.end () && !existing->second->confirmed () && !existing->second->stopped)
|
||||
if (existing != node.active.blocks.end () && !existing->second->confirmed ())
|
||||
{
|
||||
if (existing->second->dependent_blocks.find (hash) == existing->second->dependent_blocks.end ())
|
||||
{
|
||||
|
@ -286,27 +520,18 @@ void nano::election::update_dependent ()
|
|||
}
|
||||
}
|
||||
|
||||
void nano::election::clear_dependent ()
|
||||
{
|
||||
for (auto & dependent_block : dependent_blocks)
|
||||
{
|
||||
node.active.adjust_difficulty (dependent_block);
|
||||
}
|
||||
}
|
||||
|
||||
void nano::election::clear_blocks ()
|
||||
{
|
||||
auto winner_hash (status.winner->hash ());
|
||||
for (auto & block : blocks)
|
||||
for (auto const & block : blocks)
|
||||
{
|
||||
auto & hash (block.first);
|
||||
auto erased (node.active.blocks.erase (hash));
|
||||
(void)erased;
|
||||
// clear_blocks () can be called in active_transactions::publish () before blocks insertion if election was confirmed
|
||||
debug_assert (erased == 1 || confirmed ());
|
||||
debug_assert (erased == 1);
|
||||
node.active.erase_inactive_votes_cache (hash);
|
||||
// Notify observers about dropped elections & blocks lost confirmed elections
|
||||
if (stopped || hash != winner_hash)
|
||||
if (!confirmed () || hash != winner_hash)
|
||||
{
|
||||
node.observers.active_stopped.notify (hash);
|
||||
}
|
||||
|
|
|
@ -50,11 +50,42 @@ public:
|
|||
};
|
||||
class election final : public std::enable_shared_from_this<nano::election>
|
||||
{
|
||||
// Minimum time between broadcasts of the current winner of an election, as a backup to requesting confirmations
|
||||
std::chrono::milliseconds base_latency () const;
|
||||
std::function<void(std::shared_ptr<nano::block>)> confirmation_action;
|
||||
std::atomic<bool> confirmed_m;
|
||||
|
||||
private: // State management
|
||||
enum class state_t
|
||||
{
|
||||
idle,
|
||||
passive,
|
||||
active,
|
||||
backtracking,
|
||||
confirmed,
|
||||
expired_confirmed,
|
||||
expired_unconfirmed
|
||||
};
|
||||
static int constexpr passive_duration_factor = 5;
|
||||
static int constexpr active_duration_factor = 20;
|
||||
static int constexpr confirmed_duration_factor = 10;
|
||||
static int constexpr confirmed_duration_factor_saturated = 1;
|
||||
std::atomic<nano::election::state_t> state_m = { state_t::idle };
|
||||
|
||||
// Protects state_start, last_vote and last_block
|
||||
std::mutex timepoints_mutex;
|
||||
std::chrono::steady_clock::time_point state_start = { std::chrono::steady_clock::now () };
|
||||
std::chrono::steady_clock::time_point last_vote = { std::chrono::steady_clock::time_point () };
|
||||
std::chrono::steady_clock::time_point last_block = { std::chrono::steady_clock::time_point () };
|
||||
std::chrono::steady_clock::time_point last_req = { std::chrono::steady_clock::time_point () };
|
||||
|
||||
bool valid_change (nano::election::state_t, nano::election::state_t) const;
|
||||
bool state_change (nano::election::state_t, nano::election::state_t);
|
||||
void broadcast_block ();
|
||||
void send_confirm_req ();
|
||||
void activate_dependencies ();
|
||||
|
||||
public:
|
||||
election (nano::node &, std::shared_ptr<nano::block>, bool const, std::function<void(std::shared_ptr<nano::block>)> const &);
|
||||
election (nano::node &, std::shared_ptr<nano::block>, std::function<void(std::shared_ptr<nano::block>)> const &);
|
||||
nano::election_vote_result vote (nano::account, uint64_t, nano::block_hash);
|
||||
nano::tally_t tally ();
|
||||
// Check if we have vote quorum
|
||||
|
@ -66,22 +97,28 @@ public:
|
|||
bool publish (std::shared_ptr<nano::block> block_a);
|
||||
size_t last_votes_size ();
|
||||
void update_dependent ();
|
||||
void clear_dependent ();
|
||||
void clear_blocks ();
|
||||
void insert_inactive_votes_cache (nano::block_hash const &);
|
||||
void stop ();
|
||||
bool confirmed ();
|
||||
|
||||
public: // State transitions
|
||||
bool transition_time (bool const saturated);
|
||||
void transition_passive ();
|
||||
void transition_active ();
|
||||
|
||||
private:
|
||||
void transition_passive_impl ();
|
||||
void transition_active_impl ();
|
||||
|
||||
public:
|
||||
bool idle () const;
|
||||
bool confirmed () const;
|
||||
nano::node & node;
|
||||
std::unordered_map<nano::account, nano::vote_info> last_votes;
|
||||
std::unordered_map<nano::block_hash, std::shared_ptr<nano::block>> blocks;
|
||||
std::chrono::steady_clock::time_point election_start;
|
||||
std::chrono::steady_clock::time_point election_start = { std::chrono::steady_clock::now () };
|
||||
nano::election_status status;
|
||||
bool skip_delay;
|
||||
bool stopped;
|
||||
std::unordered_map<nano::block_hash, nano::uint128_t> last_tally;
|
||||
unsigned confirmation_request_count{ 0 };
|
||||
std::chrono::steady_clock::time_point last_broadcast;
|
||||
std::chrono::steady_clock::time_point last_request;
|
||||
std::unordered_map<nano::block_hash, nano::uint128_t> last_tally;
|
||||
std::unordered_set<nano::block_hash> dependent_blocks;
|
||||
std::chrono::seconds late_blocks_delay{ 5 };
|
||||
};
|
||||
|
|
|
@ -1766,7 +1766,7 @@ void nano::json_handler::confirmation_active ()
|
|||
nano::lock_guard<std::mutex> lock (node.active.mutex);
|
||||
for (auto i (node.active.roots.begin ()), n (node.active.roots.end ()); i != n; ++i)
|
||||
{
|
||||
if (i->election->confirmation_request_count >= announcements && !i->election->confirmed () && !i->election->stopped)
|
||||
if (i->election->confirmation_request_count >= announcements && !i->election->confirmed ())
|
||||
{
|
||||
boost::property_tree::ptree entry;
|
||||
entry.put ("", i->root.to_string ());
|
||||
|
|
|
@ -549,29 +549,29 @@ void nano::node::process_fork (nano::transaction const & transaction_a, std::sha
|
|||
if (ledger_block && !block_confirmed_or_being_confirmed (transaction_a, ledger_block->hash ()))
|
||||
{
|
||||
std::weak_ptr<nano::node> this_w (shared_from_this ());
|
||||
if (active.insert (ledger_block, false, [this_w, root](std::shared_ptr<nano::block>) {
|
||||
if (auto this_l = this_w.lock ())
|
||||
{
|
||||
auto attempt (this_l->bootstrap_initiator.current_attempt ());
|
||||
if (attempt && attempt->mode == nano::bootstrap_mode::legacy)
|
||||
{
|
||||
auto transaction (this_l->store.tx_begin_read ());
|
||||
auto account (this_l->ledger.store.frontier_get (transaction, root));
|
||||
if (!account.is_zero ())
|
||||
{
|
||||
this_l->bootstrap_initiator.connections->requeue_pull (nano::pull_info (account, root, root, attempt->incremental_id));
|
||||
}
|
||||
else if (this_l->ledger.store.account_exists (transaction, root))
|
||||
{
|
||||
this_l->bootstrap_initiator.connections->requeue_pull (nano::pull_info (root, nano::block_hash (0), nano::block_hash (0), attempt->incremental_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.first)
|
||||
auto election = active.insert (ledger_block, [this_w, root](std::shared_ptr<nano::block>) {
|
||||
if (auto this_l = this_w.lock ())
|
||||
{
|
||||
auto attempt (this_l->bootstrap_initiator.current_attempt ());
|
||||
if (attempt && attempt->mode == nano::bootstrap_mode::legacy)
|
||||
{
|
||||
auto transaction (this_l->store.tx_begin_read ());
|
||||
auto account (this_l->ledger.store.frontier_get (transaction, root));
|
||||
if (!account.is_zero ())
|
||||
{
|
||||
this_l->bootstrap_initiator.connections->requeue_pull (nano::pull_info (account, root, root, attempt->incremental_id));
|
||||
}
|
||||
else if (this_l->ledger.store.account_exists (transaction, root))
|
||||
{
|
||||
this_l->bootstrap_initiator.connections->requeue_pull (nano::pull_info (root, nano::block_hash (0), nano::block_hash (0), attempt->incremental_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (election.second)
|
||||
{
|
||||
logger.always_log (boost::str (boost::format ("Resolving fork between our block: %1% and block %2% both with root %3%") % ledger_block->hash ().to_string () % block_a->hash ().to_string () % block_a->root ().to_string ()));
|
||||
network.broadcast_confirm_req (ledger_block);
|
||||
election.first->transition_active ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1101,8 +1101,11 @@ void nano::node::add_initial_peers ()
|
|||
|
||||
void nano::node::block_confirm (std::shared_ptr<nano::block> block_a)
|
||||
{
|
||||
active.insert (block_a, false);
|
||||
network.broadcast_confirm_req (block_a);
|
||||
auto election = active.insert (block_a);
|
||||
if (election.second)
|
||||
{
|
||||
election.first->transition_active ();
|
||||
}
|
||||
// Calculate votes for local representatives
|
||||
if (config.enable_voting && wallets.rep_counts ().voting > 0 && active.active (*block_a))
|
||||
{
|
||||
|
|
|
@ -14,12 +14,6 @@ node (node_a)
|
|||
}
|
||||
}
|
||||
|
||||
void nano::rep_crawler::add (nano::block_hash const & hash_a)
|
||||
{
|
||||
nano::lock_guard<std::mutex> lock (active_mutex);
|
||||
active.insert (hash_a);
|
||||
}
|
||||
|
||||
void nano::rep_crawler::remove (nano::block_hash const & hash_a)
|
||||
{
|
||||
nano::lock_guard<std::mutex> lock (active_mutex);
|
||||
|
|
|
@ -80,9 +80,6 @@ public:
|
|||
/** Start crawling */
|
||||
void start ();
|
||||
|
||||
/** Add block hash to list of active rep queries */
|
||||
void add (nano::block_hash const &);
|
||||
|
||||
/** Remove block hash from list of active rep queries */
|
||||
void remove (nano::block_hash const &);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue