Skip to content

Commit

Permalink
Add cemented frontier successor confirmation in request loop (#2885)
Browse files Browse the repository at this point in the history
Adapted for V21.2 patch
  • Loading branch information
guilhermelawless committed Sep 2, 2020
1 parent 7ee6a6a commit ac7e283
Show file tree
Hide file tree
Showing 12 changed files with 595 additions and 112 deletions.
154 changes: 154 additions & 0 deletions nano/core_test/active_transactions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1245,3 +1245,157 @@ TEST (active_transactions, difficulty_update_observer)
});
ASSERT_TIMELY (3s, update_received);
}

namespace nano
{
TEST (active_transactions, pessimistic_elections)
{
nano::system system;
nano::node_flags flags;
nano::node_config config (nano::get_available_port (), system.logging);
config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto & node = *system.add_node (config, flags);

nano::keypair key;
nano::state_block_builder builder;
std::shared_ptr<nano::block> send = builder.make_block ()
.account (nano::test_genesis_key.pub)
.previous (nano::genesis_hash)
.representative (nano::test_genesis_key.pub)
.link (nano::test_genesis_key.pub)
.balance (nano::genesis_amount - 1)
.sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub)
.work (*system.work.generate (nano::genesis_hash))
.build ();

ASSERT_EQ (nano::process_result::progress, node.process (*send).code);

auto send2 = builder.make_block ()
.account (nano::test_genesis_key.pub)
.previous (send->hash ())
.representative (nano::test_genesis_key.pub)
.link (key.pub)
.balance (nano::genesis_amount - 2)
.sign (nano::test_genesis_key.prv, nano::test_genesis_key.pub)
.work (*system.work.generate (send->hash ()))
.build ();

ASSERT_EQ (nano::process_result::progress, node.process (*send2).code);

std::shared_ptr<nano::block> open = builder.make_block ()
.account (key.pub)
.previous (0)
.representative (key.pub)
.link (send2->hash ())
.balance (1)
.sign (key.prv, key.pub)
.work (*system.work.generate (key.pub))
.build ();

ASSERT_EQ (nano::process_result::progress, node.process (*open).code);

// This should only cement the first block in genesis account
uint64_t election_count = 0;
// Make dummy election with winner.
{
nano::lock_guard<std::mutex> guard (node.active.mutex);
nano::election election1 (
node, send, [](auto const & block) {}, false, nano::election_behavior::normal);
nano::election election2 (
node, open, [](auto const & block) {}, false, nano::election_behavior::normal);
node.active.add_expired_optimistic_election (election1);
node.active.add_expired_optimistic_election (election2);
}
node.active.confirm_expired_frontiers_pessimistically (node.store.tx_begin_read (), 100, election_count);
ASSERT_EQ (1, election_count);
ASSERT_EQ (2, node.active.expired_optimistic_election_infos.size ());
ASSERT_EQ (2, node.active.expired_optimistic_election_infos.size ());
auto election_started_it = node.active.expired_optimistic_election_infos.get<nano::active_transactions::tag_election_started> ().begin ();
ASSERT_EQ (election_started_it->account, nano::genesis_account);
ASSERT_EQ (election_started_it->election_started, true);
ASSERT_EQ ((++election_started_it)->election_started, false);

// No new elections should get started yet
node.active.confirm_expired_frontiers_pessimistically (node.store.tx_begin_read (), 100, election_count);
ASSERT_EQ (1, election_count);
ASSERT_EQ (2, node.active.expired_optimistic_election_infos.size ());
ASSERT_EQ (node.active.expired_optimistic_election_infos_size, node.active.expired_optimistic_election_infos.size ());

{
ASSERT_EQ (1, node.active.size ());
auto election = node.active.election (send->qualified_root ());
ASSERT_NE (nullptr, election);
nano::lock_guard<std::mutex> guard (node.active.mutex);
election->confirm_once ();
}

ASSERT_TIMELY (3s, node.block_confirmed (send->hash ()) && !node.confirmation_height_processor.is_processing_block (send->hash ()));

nano::confirmation_height_info genesis_confirmation_height_info;
nano::confirmation_height_info key1_confirmation_height_info;
{
auto transaction = node.store.tx_begin_read ();
node.store.confirmation_height_get (transaction, nano::genesis_account, genesis_confirmation_height_info);
ASSERT_EQ (2, genesis_confirmation_height_info.height);
node.store.confirmation_height_get (transaction, key.pub, key1_confirmation_height_info);
ASSERT_EQ (0, key1_confirmation_height_info.height);
}

// Activation of cemented frontier successor should get started after the first pessimistic block is confirmed
ASSERT_TIMELY (10s, node.active.active (send->qualified_root ()));

node.active.confirm_expired_frontiers_pessimistically (node.store.tx_begin_read (), 100, election_count);
ASSERT_EQ (1, election_count);
ASSERT_EQ (2, node.active.expired_optimistic_election_infos.size ());

// Confirm it
{
auto election = node.active.election (send2->qualified_root ());
ASSERT_NE (nullptr, election);
nano::lock_guard<std::mutex> guard (node.active.mutex);
election->confirm_once ();
}

ASSERT_TIMELY (3s, node.block_confirmed (send2->hash ()));

{
auto transaction = node.store.tx_begin_read ();
node.store.confirmation_height_get (transaction, nano::genesis_account, genesis_confirmation_height_info);
ASSERT_EQ (3, genesis_confirmation_height_info.height);
node.store.confirmation_height_get (transaction, key.pub, key1_confirmation_height_info);
ASSERT_EQ (0, key1_confirmation_height_info.height);
}

// Wait until activation of destination account is done.
ASSERT_TIMELY (10s, node.active.active (send2->qualified_root ()));

// Election count should not increase, but the elections should be marked as started for that account afterwards
ASSERT_EQ (election_started_it->election_started, false);
node.active.confirm_expired_frontiers_pessimistically (node.store.tx_begin_read (), 100, election_count);
ASSERT_EQ (1, election_count);
ASSERT_EQ (2, node.active.expired_optimistic_election_infos.size ());
node.active.confirm_expired_frontiers_pessimistically (node.store.tx_begin_read (), 100, election_count);

{
auto election = node.active.election (open->qualified_root ());
ASSERT_NE (nullptr, election);
nano::lock_guard<std::mutex> guard (node.active.mutex);
election->confirm_once ();
}

ASSERT_TIMELY (3s, node.block_confirmed (open->hash ()));

{
auto transaction = node.store.tx_begin_read ();
node.store.confirmation_height_get (transaction, nano::genesis_account, genesis_confirmation_height_info);
ASSERT_EQ (3, genesis_confirmation_height_info.height);
node.store.confirmation_height_get (transaction, key.pub, key1_confirmation_height_info);
ASSERT_EQ (1, key1_confirmation_height_info.height);
}

// Sanity check that calling it again on a fully cemented chain has no adverse effects.
node.active.confirm_expired_frontiers_pessimistically (node.store.tx_begin_read (), 100, election_count);
ASSERT_EQ (1, election_count);
ASSERT_EQ (2, node.active.expired_optimistic_election_infos.size ());
}
}
10 changes: 5 additions & 5 deletions nano/core_test/confirmation_solicitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ TEST (confirmation_solicitor, batches)
nano::lock_guard<std::mutex> guard (node2.active.mutex);
for (size_t i (0); i < nano::network::confirm_req_hashes_max; ++i)
{
auto election (std::make_shared<nano::election> (node2, send, nullptr, false));
auto election (std::make_shared<nano::election> (node2, send, nullptr, false, nano::election_behavior::normal));
ASSERT_FALSE (solicitor.add (*election));
}
ASSERT_EQ (1, solicitor.max_confirm_req_batches);
// Reached the maximum amount of requests for the channel
auto election (std::make_shared<nano::election> (node2, send, nullptr, false));
auto election (std::make_shared<nano::election> (node2, send, nullptr, false, nano::election_behavior::normal));
ASSERT_TRUE (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));
Expand Down Expand Up @@ -75,7 +75,7 @@ TEST (confirmation_solicitor, different_hash)
send->sideband_set ({});
{
nano::lock_guard<std::mutex> guard (node2.active.mutex);
auto election (std::make_shared<nano::election> (node2, send, nullptr, false));
auto election (std::make_shared<nano::election> (node2, send, nullptr, false, nano::election_behavior::normal));
// Add a vote for something else, not the winner
election->last_votes[representative.account] = { std::chrono::steady_clock::now (), 1, 1 };
// Ensure the request and broadcast goes through
Expand Down Expand Up @@ -114,7 +114,7 @@ TEST (confirmation_solicitor, bypass_max_requests_cap)
send->sideband_set ({});
{
nano::lock_guard<std::mutex> guard (node2.active.mutex);
auto election (std::make_shared<nano::election> (node2, send, nullptr, false));
auto election (std::make_shared<nano::election> (node2, send, nullptr, false, nano::election_behavior::normal));
// Add a vote for something else, not the winner
for (auto const & rep : representatives)
{
Expand All @@ -130,7 +130,7 @@ TEST (confirmation_solicitor, bypass_max_requests_cap)
solicitor.prepare (representatives);
{
nano::lock_guard<std::mutex> guard (node2.active.mutex);
auto election (std::make_shared<nano::election> (node2, send, nullptr, false));
auto election (std::make_shared<nano::election> (node2, send, nullptr, false, nano::election_behavior::normal));
// Erase all votes
election->last_votes.clear ();
ASSERT_FALSE (solicitor.add (*election));
Expand Down
69 changes: 67 additions & 2 deletions nano/core_test/frontiers_confirmation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ TEST (frontiers_confirmation, prioritize_frontiers)
node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1));
ASSERT_TRUE (priority_orders_match (node->active.priority_wallet_cementable_frontiers, std::array<nano::account, num_accounts>{ key3.pub, nano::genesis_account, key4.pub, key1.pub, key2.pub }));
uint64_t election_count = 0;
node->active.confirm_prioritized_frontiers (transaction);
node->active.confirm_prioritized_frontiers (transaction, 100, election_count);

// Check that the active transactions roots contains the frontiers
ASSERT_TIMELY (10s, node->active.size () == num_accounts);
Expand All @@ -143,6 +143,72 @@ TEST (frontiers_confirmation, prioritize_frontiers)
}
}

TEST (frontiers_confirmation, prioritize_frontiers_max_optimistic_elections)
{
nano::system system;
// Prevent frontiers being confirmed as it will affect the priorization checking
nano::node_config node_config (nano::get_available_port (), system.logging);
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto node = system.add_node (node_config);

node->ledger.cache.cemented_count = node->ledger.bootstrap_weight_max_blocks - 1;
auto max_optimistic_election_count_under_hardcoded_weight = node->active.max_optimistic ();
node->ledger.cache.cemented_count = node->ledger.bootstrap_weight_max_blocks;
auto max_optimistic_election_count = node->active.max_optimistic ();
ASSERT_GT (max_optimistic_election_count_under_hardcoded_weight, max_optimistic_election_count);

for (auto i = 0; i < max_optimistic_election_count * 2; ++i)
{
auto transaction = node->store.tx_begin_write ();
auto latest = node->latest (nano::genesis_account);
nano::keypair key;
nano::send_block send (latest, key.pub, node->config.online_weight_minimum.number () + 10000, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *system.work.generate (latest));
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code);
nano::open_block open (send.hash (), nano::genesis_account, key.pub, key.prv, key.pub, *system.work.generate (key.pub));
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open).code);
}

{
nano::unique_lock<std::mutex> lk (node->active.mutex);
node->active.frontiers_confirmation (lk);
}

ASSERT_EQ (max_optimistic_election_count, node->active.roots.size ());

nano::account next_frontier_account{ 2 };
node->active.next_frontier_account = next_frontier_account;

// Call frontiers confirmation again and confirm that next_frontier_account hasn't changed
{
nano::unique_lock<std::mutex> lk (node->active.mutex);
node->active.frontiers_confirmation (lk);
}

ASSERT_EQ (max_optimistic_election_count, node->active.roots.size ());
ASSERT_EQ (next_frontier_account, node->active.next_frontier_account);
}

TEST (frontiers_confirmation, expired_optimistic_elections_removal)
{
nano::system system;
nano::node_config node_config (nano::get_available_port (), system.logging);
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto node = system.add_node (node_config);

// This should be removed on the next prioritization call
node->active.expired_optimistic_election_infos.emplace (std::chrono::steady_clock::now () - (node->active.expired_optimistic_election_info_cutoff + 1min), nano::account (1));
ASSERT_EQ (1, node->active.expired_optimistic_election_infos.size ());
node->active.prioritize_frontiers_for_confirmation (node->store.tx_begin_read (), 0s, 0s);
ASSERT_EQ (0, node->active.expired_optimistic_election_infos.size ());

// This should not be removed on the next prioritization call
node->active.expired_optimistic_election_infos.emplace (std::chrono::steady_clock::now () - (node->active.expired_optimistic_election_info_cutoff - 1min), nano::account (1));
ASSERT_EQ (1, node->active.expired_optimistic_election_infos.size ());
node->active.prioritize_frontiers_for_confirmation (node->store.tx_begin_read (), 0s, 0s);
ASSERT_EQ (1, node->active.expired_optimistic_election_infos.size ());
}
}

TEST (frontiers_confirmation, mode)
{
nano::genesis genesis;
Expand Down Expand Up @@ -190,4 +256,3 @@ TEST (frontiers_confirmation, mode)
ASSERT_EQ (0, node->active.size ());
}
}
}
21 changes: 7 additions & 14 deletions nano/core_test/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4129,14 +4129,16 @@ TEST (node, dependency_graph)
ASSERT_TIMELY (5s, node.active.empty ());
}

// Confirm a complex dependency graph starting from a frontier
TEST (node, DISABLED_dependency_graph_frontier)
// Confirm a complex dependency graph. Uses frontiers confirmation which will fail to
// confirm a frontier optimistically then fallback to pessimistic confirmation.
TEST (node, dependency_graph_frontier)
{
nano::system system;
nano::node_config config (nano::get_available_port (), system.logging);
config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto & node1 = *system.add_node (config);
config.peering_port = nano::get_available_port ();
config.frontiers_confirmation = nano::frontiers_confirmation_mode::always;
auto & node2 = *system.add_node (config);

nano::state_block_builder builder;
Expand Down Expand Up @@ -4284,23 +4286,14 @@ TEST (node, DISABLED_dependency_graph_frontier)
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *key3_epoch).code);
}

ASSERT_TRUE (node1.active.empty () && node2.active.empty ());

// node1 can vote, but only on the first block
system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv);

// activate the graph frontier
// node2 activates dependencies in sequence until it reaches the first block
node2.block_confirm (node2.block (key3_epoch->hash ()));

// Eventually the first block in the graph gets activated and confirmed via node1
ASSERT_TIMELY (15s, node2.block_confirmed (gen_send1->hash ()));

// Activate the first block in node1, allowing it to confirm all blocks for both nodes
ASSERT_TIMELY (10s, node2.active.active (gen_send1->qualified_root ()));
node1.block_confirm (gen_send1);

ASSERT_TIMELY (15s, node1.ledger.cache.cemented_count == node1.ledger.cache.block_count);
ASSERT_TIMELY (5s, node2.ledger.cache.cemented_count == node2.ledger.cache.block_count);
ASSERT_TIMELY (5s, node1.active.empty () && node2.active.empty ());
ASSERT_TIMELY (15s, node2.ledger.cache.cemented_count == node2.ledger.cache.block_count);
}

namespace nano
Expand Down
Loading

0 comments on commit ac7e283

Please sign in to comment.