1535 lines
65 KiB
C++
1535 lines
65 KiB
C++
#include <nano/lib/jsonconfig.hpp>
|
|
#include <nano/node/election.hpp>
|
|
#include <nano/node/transport/inproc.hpp>
|
|
#include <nano/test_common/system.hpp>
|
|
#include <nano/test_common/testutil.hpp>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <numeric>
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
namespace nano
|
|
{
|
|
// Tests that an election can be confirmed as the result of a confirmation request
|
|
//
|
|
// Set-up:
|
|
// - node1 with:
|
|
// - enabled frontiers_confirmation (default) -> allows it to confirm blocks and subsequently generates votes
|
|
// - node2 with:
|
|
// - disabled rep crawler -> this inhibits node2 from learning that node1 is a rep
|
|
//
|
|
// Steps:
|
|
// - create a block (send1), process it locally (without creating an election for it) on node1
|
|
// - process send1 (as incoming from network -- process_active) on node2
|
|
// - expect that election has been started for send1 on node2, but no confirmation_requests are sent for it
|
|
// - stick genesis key into node1, then add node1 as a rep to node2's probable reps list
|
|
// - expect at least one confirmation_request for the election (having been sent to node1 -- which is a rep now)
|
|
// - expect a (non-final) vote to come back
|
|
// - expected confirmation_request count has increased -- two round trips for the election to get confirmed
|
|
// - expect election is confirmed
|
|
|
|
TEST (active_transactions, confirm_election_by_request)
|
|
{
|
|
nano::system system{};
|
|
auto & node1 = *system.add_node ();
|
|
|
|
nano::node_flags node_flags2{};
|
|
node_flags2.disable_rep_crawler = true;
|
|
auto & node2 = *system.add_node (node_flags2);
|
|
|
|
auto send1 = nano::state_block_builder{}.make_block ().account (nano::dev::genesis_key.pub).representative (nano::dev::genesis_key.pub).previous (nano::dev::genesis->hash ()).link (nano::public_key ()).balance (nano::dev::constants.genesis_amount - 100).sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub).work (*system.work.generate (nano::dev::genesis->hash ())).build_shared ();
|
|
|
|
// Process send1 locally on node1
|
|
ASSERT_EQ (nano::process_result::progress, node1.process (*send1).code);
|
|
|
|
// Start an election for send1 on node2
|
|
node2.process_active (send1);
|
|
std::shared_ptr<nano::election> election{};
|
|
ASSERT_TIMELY (5s, (election = node2.active.election (send1->qualified_root ())) != nullptr);
|
|
|
|
// Expect that node2 has nobody to send a confirmation_request to (no reps)
|
|
ASSERT_EQ (0, election->confirmation_request_count);
|
|
|
|
// Add key to node1
|
|
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
|
|
|
|
// Get random peer list (of size 1) from node2 -- so basically just node1
|
|
auto const peers = node2.network.random_set (1);
|
|
ASSERT_FALSE (peers.empty ());
|
|
|
|
// Add representative (node1) to disabled rep crawler of node2
|
|
{
|
|
nano::lock_guard<nano::mutex> guard (node2.rep_crawler.probable_reps_mutex);
|
|
node2.rep_crawler.probable_reps.emplace (nano::dev::genesis_key.pub, nano::dev::constants.genesis_amount, *peers.cbegin ());
|
|
}
|
|
|
|
// At least one confirmation request sent to the freshly inserted rep (node1)
|
|
std::size_t confirm_req_count{};
|
|
ASSERT_TIMELY (5s, (confirm_req_count = election->confirmation_request_count) > 0);
|
|
|
|
// Expect a (non-final) vote come back
|
|
ASSERT_TIMELY (5s, election->votes ().size () > 1);
|
|
|
|
// There need to be 2 round trips in order for the election to get confirmed
|
|
ASSERT_TIMELY (5s, election->confirmation_request_count > confirm_req_count);
|
|
|
|
// Expect election was confirmed
|
|
ASSERT_TIMELY (5s, election->confirmed ());
|
|
ASSERT_TIMELY (5s, node1.block_confirmed (send1->hash ()));
|
|
ASSERT_TIMELY (5s, node2.block_confirmed (send1->hash ()));
|
|
}
|
|
}
|
|
|
|
namespace nano
|
|
{
|
|
TEST (active_transactions, confirm_frontier)
|
|
{
|
|
nano::system system;
|
|
nano::node_flags node_flags;
|
|
node_flags.disable_request_loop = true;
|
|
// Voting node
|
|
auto & node1 = *system.add_node (node_flags);
|
|
nano::node_flags node_flags2;
|
|
// The rep crawler would otherwise request confirmations in order to find representatives
|
|
node_flags2.disable_rep_crawler = true;
|
|
auto & node2 = *system.add_node (node_flags2);
|
|
|
|
// Add key to node1
|
|
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
|
|
// Add representative to disabled rep crawler
|
|
auto peers (node2.network.random_set (1));
|
|
ASSERT_FALSE (peers.empty ());
|
|
{
|
|
nano::lock_guard<nano::mutex> guard (node2.rep_crawler.probable_reps_mutex);
|
|
node2.rep_crawler.probable_reps.emplace (nano::dev::genesis_key.pub, nano::dev::constants.genesis_amount, *peers.begin ());
|
|
}
|
|
|
|
nano::state_block_builder builder;
|
|
auto send = builder
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (nano::dev::genesis->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 100)
|
|
.link (nano::public_key ())
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
|
.build_shared ();
|
|
auto send_copy = builder.make_block ().from (*send).build_shared ();
|
|
ASSERT_EQ (nano::process_result::progress, node1.process (*send).code);
|
|
node1.confirmation_height_processor.add (send);
|
|
ASSERT_TIMELY (5s, node1.ledger.block_confirmed (node1.store.tx_begin_read (), send->hash ()));
|
|
ASSERT_EQ (nano::process_result::progress, node2.process (*send_copy).code);
|
|
ASSERT_TIMELY (5s, !node2.active.empty ());
|
|
// Save election to check request count afterwards
|
|
auto election2 = node2.active.election (send->qualified_root ());
|
|
ASSERT_NE (nullptr, election2);
|
|
ASSERT_TIMELY (5s, node2.ledger.cache.cemented_count == 2 && node2.active.empty ());
|
|
ASSERT_GT (election2->confirmation_request_count, 0u);
|
|
}
|
|
}
|
|
|
|
TEST (active_transactions, keep_local)
|
|
{
|
|
nano::system system{};
|
|
|
|
nano::node_config node_config{ nano::get_available_port (), system.logging };
|
|
node_config.enable_voting = false;
|
|
// Bound to 2, won't drop wallet created transactions, but good to test dropping remote
|
|
node_config.active_elections_size = 2;
|
|
// Disable frontier confirmation to allow the test to finish before
|
|
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
|
|
|
auto & node = *system.add_node (node_config);
|
|
auto & wallet (*system.wallet (0));
|
|
|
|
nano::keypair key1{};
|
|
nano::keypair key2{};
|
|
nano::keypair key3{};
|
|
nano::keypair key4{};
|
|
nano::keypair key5{};
|
|
nano::keypair key6{};
|
|
|
|
wallet.insert_adhoc (nano::dev::genesis_key.prv);
|
|
auto const send1 = wallet.send_action (nano::dev::genesis_key.pub, key1.pub, node.config.receive_minimum.number ());
|
|
auto const send2 = wallet.send_action (nano::dev::genesis_key.pub, key2.pub, node.config.receive_minimum.number ());
|
|
auto const send3 = wallet.send_action (nano::dev::genesis_key.pub, key3.pub, node.config.receive_minimum.number ());
|
|
auto const send4 = wallet.send_action (nano::dev::genesis_key.pub, key4.pub, node.config.receive_minimum.number ());
|
|
auto const send5 = wallet.send_action (nano::dev::genesis_key.pub, key5.pub, node.config.receive_minimum.number ());
|
|
auto const send6 = wallet.send_action (nano::dev::genesis_key.pub, key6.pub, node.config.receive_minimum.number ());
|
|
|
|
// force-confirm blocks
|
|
for (auto const & block : { send1, send2, send3, send4, send5, send6 })
|
|
{
|
|
std::shared_ptr<nano::election> election{};
|
|
ASSERT_TIMELY (5s, (election = node.active.election (block->qualified_root ())) != nullptr);
|
|
node.process_confirmed (nano::election_status{ block });
|
|
election->force_confirm ();
|
|
ASSERT_TIMELY (5s, node.block_confirmed (block->hash ()));
|
|
}
|
|
|
|
nano::state_block_builder builder{};
|
|
const auto receive1 = builder.make_block ()
|
|
.account (key1.pub)
|
|
.previous (0)
|
|
.representative (key1.pub)
|
|
.balance (node.config.receive_minimum.number ())
|
|
.link (send1->hash ())
|
|
.sign (key1.prv, key1.pub)
|
|
.work (*system.work.generate (key1.pub))
|
|
.build_shared ();
|
|
const auto receive2 = builder.make_block ()
|
|
.account (key2.pub)
|
|
.previous (0)
|
|
.representative (key2.pub)
|
|
.balance (node.config.receive_minimum.number ())
|
|
.link (send2->hash ())
|
|
.sign (key2.prv, key2.pub)
|
|
.work (*system.work.generate (key2.pub))
|
|
.build_shared ();
|
|
const auto receive3 = builder.make_block ()
|
|
.account (key3.pub)
|
|
.previous (0)
|
|
.representative (key3.pub)
|
|
.balance (node.config.receive_minimum.number ())
|
|
.link (send3->hash ())
|
|
.sign (key3.prv, key3.pub)
|
|
.work (*system.work.generate (key3.pub))
|
|
.build_shared ();
|
|
node.process_active (receive1);
|
|
node.process_active (receive2);
|
|
node.process_active (receive3);
|
|
|
|
/// bound elections, should drop after one loop
|
|
ASSERT_TIMELY (5s, node.active.size () == node_config.active_elections_size);
|
|
// ASSERT_EQ (1, node.scheduler.size ());
|
|
}
|
|
|
|
TEST (active_transactions, inactive_votes_cache)
|
|
{
|
|
nano::system system (1);
|
|
auto & node = *system.nodes[0];
|
|
nano::block_hash latest (node.latest (nano::dev::genesis_key.pub));
|
|
nano::keypair key;
|
|
auto send = nano::send_block_builder ()
|
|
.previous (latest)
|
|
.destination (key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 100)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest))
|
|
.build_shared ();
|
|
auto vote (std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::vote::timestamp_max, nano::vote::duration_max, std::vector<nano::block_hash> (1, send->hash ())));
|
|
node.vote_processor.vote (vote, std::make_shared<nano::transport::inproc::channel> (node, node));
|
|
ASSERT_TIMELY (5s, node.active.inactive_votes_cache_size () == 1);
|
|
node.process_active (send);
|
|
node.block_processor.flush ();
|
|
ASSERT_TIMELY (5s, node.ledger.block_confirmed (node.store.tx_begin_read (), send->hash ()));
|
|
ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached));
|
|
}
|
|
|
|
TEST (active_transactions, inactive_votes_cache_non_final)
|
|
{
|
|
nano::system system (1);
|
|
auto & node = *system.nodes[0];
|
|
nano::block_hash latest (node.latest (nano::dev::genesis_key.pub));
|
|
nano::keypair key;
|
|
auto send = nano::send_block_builder ()
|
|
.previous (latest)
|
|
.destination (key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 100)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest))
|
|
.build_shared ();
|
|
auto vote (std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, 0, 0, std::vector<nano::block_hash> (1, send->hash ()))); // Non-final vote
|
|
node.vote_processor.vote (vote, std::make_shared<nano::transport::inproc::channel> (node, node));
|
|
ASSERT_TIMELY (5s, node.active.inactive_votes_cache_size () == 1);
|
|
node.process_active (send);
|
|
node.block_processor.flush ();
|
|
ASSERT_TIMELY (5s, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached) == 1);
|
|
auto election = node.active.election (send->qualified_root ());
|
|
ASSERT_NE (nullptr, election);
|
|
ASSERT_FALSE (election->confirmed ());
|
|
ASSERT_EQ (nano::dev::constants.genesis_amount - 100, election->tally ().begin ()->first);
|
|
}
|
|
|
|
TEST (active_transactions, inactive_votes_cache_fork)
|
|
{
|
|
nano::system system{ 1 };
|
|
auto & node = *system.nodes[0];
|
|
|
|
auto const latest = node.latest (nano::dev::genesis_key.pub);
|
|
nano::keypair key{};
|
|
|
|
nano::send_block_builder builder{};
|
|
auto send1 = builder.make_block ()
|
|
.previous (latest)
|
|
.destination (key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 100)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest))
|
|
.build_shared ();
|
|
|
|
auto send2 = builder.make_block ()
|
|
.previous (latest)
|
|
.destination (key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 200)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest))
|
|
.build_shared ();
|
|
|
|
auto const vote = std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::vote::timestamp_max, nano::vote::duration_max, std::vector<nano::block_hash> (1, send1->hash ()));
|
|
node.vote_processor.vote (vote, std::make_shared<nano::transport::inproc::channel> (node, node));
|
|
ASSERT_TIMELY (5s, node.active.inactive_votes_cache_size () == 1);
|
|
|
|
node.process_active (send2);
|
|
|
|
std::shared_ptr<nano::election> election{};
|
|
ASSERT_TIMELY (5s, (election = node.active.election (send1->qualified_root ())) != nullptr);
|
|
|
|
node.process_active (send1);
|
|
ASSERT_TIMELY (5s, election->blocks ().size () == 2);
|
|
ASSERT_TIMELY (5s, node.block_confirmed (send1->hash ()));
|
|
ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached));
|
|
}
|
|
|
|
TEST (active_transactions, inactive_votes_cache_existing_vote)
|
|
{
|
|
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);
|
|
nano::block_hash latest (node.latest (nano::dev::genesis_key.pub));
|
|
nano::keypair key;
|
|
nano::block_builder builder;
|
|
auto send = builder.send ()
|
|
.previous (latest)
|
|
.destination (key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 100 * nano::Gxrb_ratio)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest))
|
|
.build_shared ();
|
|
auto open = builder.state ()
|
|
.account (key.pub)
|
|
.previous (0)
|
|
.representative (key.pub)
|
|
.balance (100 * nano::Gxrb_ratio)
|
|
.link (send->hash ())
|
|
.sign (key.prv, key.pub)
|
|
.work (*system.work.generate (key.pub))
|
|
.build_shared ();
|
|
node.process_active (send);
|
|
node.block_processor.add (open);
|
|
node.block_processor.flush ();
|
|
ASSERT_TIMELY (5s, node.active.size () == 1);
|
|
auto election (node.active.election (send->qualified_root ()));
|
|
ASSERT_NE (nullptr, election);
|
|
ASSERT_GT (node.weight (key.pub), node.minimum_principal_weight ());
|
|
// Insert vote
|
|
auto vote1 (std::make_shared<nano::vote> (key.pub, key.prv, nano::vote::timestamp_min * 1, 0, std::vector<nano::block_hash> (1, send->hash ())));
|
|
node.vote_processor.vote (vote1, std::make_shared<nano::transport::inproc::channel> (node, node));
|
|
ASSERT_TIMELY (5s, election->votes ().size () == 2);
|
|
ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_new));
|
|
auto last_vote1 (election->votes ()[key.pub]);
|
|
ASSERT_EQ (send->hash (), last_vote1.hash);
|
|
ASSERT_EQ (nano::vote::timestamp_min * 1, last_vote1.timestamp);
|
|
// Attempt to change vote with inactive_votes_cache
|
|
nano::unique_lock<nano::mutex> active_lock (node.active.mutex);
|
|
node.active.add_inactive_votes_cache (active_lock, send->hash (), key.pub, 0);
|
|
active_lock.unlock ();
|
|
const auto cache (node.active.find_inactive_votes_cache (send->hash ()));
|
|
active_lock.lock ();
|
|
ASSERT_EQ (1, cache.voters.size ());
|
|
cache.fill (election);
|
|
// Check that election data is not changed
|
|
ASSERT_EQ (2, election->votes ().size ());
|
|
auto last_vote2 (election->votes ()[key.pub]);
|
|
ASSERT_EQ (last_vote1.hash, last_vote2.hash);
|
|
ASSERT_EQ (last_vote1.timestamp, last_vote2.timestamp);
|
|
ASSERT_EQ (last_vote1.time, last_vote2.time);
|
|
ASSERT_EQ (0, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached));
|
|
}
|
|
|
|
// Test disabled because it's failing intermittently.
|
|
// PR in which it got disabled: https://github.com/nanocurrency/nano-node/pull/3629
|
|
// Issue for investigating it: https://github.com/nanocurrency/nano-node/issues/3632
|
|
TEST (active_transactions, DISABLED_inactive_votes_cache_multiple_votes)
|
|
{
|
|
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);
|
|
nano::block_hash latest (node.latest (nano::dev::genesis_key.pub));
|
|
nano::keypair key1;
|
|
nano::block_builder builder;
|
|
auto send1 = builder.send ()
|
|
.previous (latest)
|
|
.destination (key1.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 100 * nano::Gxrb_ratio)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest))
|
|
.build_shared ();
|
|
auto send2 = builder.send ()
|
|
.previous (send1->hash ())
|
|
.destination (key1.pub)
|
|
.balance (100 * nano::Gxrb_ratio)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (send1->hash ()))
|
|
.build_shared ();
|
|
auto open = builder.state ()
|
|
.account (key1.pub)
|
|
.previous (0)
|
|
.representative (key1.pub)
|
|
.balance (100 * nano::Gxrb_ratio)
|
|
.link (send1->hash ())
|
|
.sign (key1.prv, key1.pub)
|
|
.work (*system.work.generate (key1.pub))
|
|
.build_shared ();
|
|
node.block_processor.add (send1);
|
|
node.block_processor.add (send2);
|
|
node.block_processor.add (open);
|
|
node.block_processor.flush ();
|
|
// Process votes
|
|
auto vote1 (std::make_shared<nano::vote> (key1.pub, key1.prv, 0, 0, std::vector<nano::block_hash> (1, send1->hash ())));
|
|
node.vote_processor.vote (vote1, std::make_shared<nano::transport::inproc::channel> (node, node));
|
|
auto vote2 (std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, 0, 0, std::vector<nano::block_hash> (1, send1->hash ())));
|
|
node.vote_processor.vote (vote2, std::make_shared<nano::transport::inproc::channel> (node, node));
|
|
ASSERT_TIMELY (5s, node.active.find_inactive_votes_cache (send1->hash ()).voters.size () == 2);
|
|
ASSERT_EQ (1, node.active.inactive_votes_cache_size ());
|
|
node.scheduler.activate (nano::dev::genesis_key.pub, node.store.tx_begin_read ());
|
|
node.scheduler.flush ();
|
|
auto election = node.active.election (send1->qualified_root ());
|
|
ASSERT_NE (nullptr, election);
|
|
ASSERT_EQ (3, election->votes ().size ()); // 2 votes and 1 default not_an_acount
|
|
ASSERT_EQ (2, node.stats.count (nano::stat::type::election, nano::stat::detail::vote_cached));
|
|
}
|
|
|
|
TEST (active_transactions, inactive_votes_cache_election_start)
|
|
{
|
|
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);
|
|
nano::block_hash latest (node.latest (nano::dev::genesis_key.pub));
|
|
nano::keypair key1, key2;
|
|
nano::send_block_builder send_block_builder;
|
|
nano::state_block_builder state_block_builder;
|
|
// Enough weight to trigger election hinting but not enough to confirm block on its own
|
|
auto amount = ((node.online_reps.trended () / 100) * node.config.election_hint_weight_percent) / 2 + 1000 * nano::Gxrb_ratio;
|
|
auto send1 = send_block_builder.make_block ()
|
|
.previous (latest)
|
|
.destination (key1.pub)
|
|
.balance (nano::dev::constants.genesis_amount - amount)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest))
|
|
.build_shared ();
|
|
auto send2 = send_block_builder.make_block ()
|
|
.previous (send1->hash ())
|
|
.destination (key2.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 2 * amount)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (send1->hash ()))
|
|
.build_shared ();
|
|
auto open1 = state_block_builder.make_block ()
|
|
.account (key1.pub)
|
|
.previous (0)
|
|
.representative (key1.pub)
|
|
.balance (amount)
|
|
.link (send1->hash ())
|
|
.sign (key1.prv, key1.pub)
|
|
.work (*system.work.generate (key1.pub))
|
|
.build_shared ();
|
|
auto open2 = state_block_builder.make_block ()
|
|
.account (key2.pub)
|
|
.previous (0)
|
|
.representative (key2.pub)
|
|
.balance (amount)
|
|
.link (send2->hash ())
|
|
.sign (key2.prv, key2.pub)
|
|
.work (*system.work.generate (key2.pub))
|
|
.build_shared ();
|
|
node.block_processor.add (send1);
|
|
node.block_processor.add (send2);
|
|
node.block_processor.add (open1);
|
|
node.block_processor.add (open2);
|
|
node.block_processor.flush ();
|
|
ASSERT_TIMELY (5s, 5 == node.ledger.cache.block_count);
|
|
ASSERT_TRUE (node.active.empty ());
|
|
ASSERT_EQ (1, node.ledger.cache.cemented_count);
|
|
// These blocks will be processed later
|
|
auto send3 = send_block_builder.make_block ()
|
|
.previous (send2->hash ())
|
|
.destination (nano::keypair ().pub)
|
|
.balance (send2->balance ().number () - 1)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (send2->hash ()))
|
|
.build_shared ();
|
|
auto send4 = send_block_builder.make_block ()
|
|
.previous (send3->hash ())
|
|
.destination (nano::keypair ().pub)
|
|
.balance (send3->balance ().number () - 1)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (send3->hash ()))
|
|
.build_shared ();
|
|
// Inactive votes
|
|
std::vector<nano::block_hash> hashes{ open1->hash (), open2->hash (), send4->hash () };
|
|
auto vote1 (std::make_shared<nano::vote> (key1.pub, key1.prv, 0, 0, hashes));
|
|
node.vote_processor.vote (vote1, std::make_shared<nano::transport::inproc::channel> (node, node));
|
|
ASSERT_TIMELY (5s, node.active.inactive_votes_cache_size () == 3);
|
|
ASSERT_TRUE (node.active.empty ());
|
|
ASSERT_EQ (1, node.ledger.cache.cemented_count);
|
|
// 2 votes are required to start election (dev network)
|
|
auto vote2 (std::make_shared<nano::vote> (key2.pub, key2.prv, 0, 0, hashes));
|
|
node.vote_processor.vote (vote2, std::make_shared<nano::transport::inproc::channel> (node, node));
|
|
// Only open1 & open2 blocks elections should start (send4 is missing previous block in ledger)
|
|
ASSERT_TIMELY (5s, 2 == node.active.size ());
|
|
// Confirm elections with weight quorum
|
|
auto vote0 (std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::vote::timestamp_max, nano::vote::duration_max, hashes)); // Final vote for confirmation
|
|
node.vote_processor.vote (vote0, std::make_shared<nano::transport::inproc::channel> (node, node));
|
|
ASSERT_TIMELY (5s, node.active.empty ());
|
|
ASSERT_TIMELY (5s, 5 == node.ledger.cache.cemented_count);
|
|
// A late block arrival also checks the inactive votes cache
|
|
ASSERT_TRUE (node.active.empty ());
|
|
auto send4_cache (node.active.find_inactive_votes_cache (send4->hash ()));
|
|
ASSERT_EQ (3, send4_cache.voters.size ());
|
|
ASSERT_TRUE (send4_cache.status.bootstrap_started);
|
|
ASSERT_TRUE (send4_cache.status.confirmed);
|
|
ASSERT_TRUE (send4_cache.status.election_started); // already marked even though the block does not exist
|
|
node.process_active (send3);
|
|
node.block_processor.flush ();
|
|
// An election is started for send6 but does not confirm
|
|
ASSERT_TIMELY (5s, 1 == node.active.size ());
|
|
node.vote_processor.flush ();
|
|
ASSERT_FALSE (node.block_confirmed_or_being_confirmed (node.store.tx_begin_read (), send3->hash ()));
|
|
// send7 cannot be voted on but an election should be started from inactive votes
|
|
ASSERT_FALSE (node.ledger.dependents_confirmed (node.store.tx_begin_read (), *send4));
|
|
node.process_active (send4);
|
|
node.block_processor.flush ();
|
|
ASSERT_TIMELY (5s, 7 == node.ledger.cache.cemented_count);
|
|
}
|
|
|
|
namespace nano
|
|
{
|
|
TEST (active_transactions, vote_replays)
|
|
{
|
|
nano::system system;
|
|
nano::node_config node_config (nano::get_available_port (), system.logging);
|
|
node_config.enable_voting = false;
|
|
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
|
auto & node = *system.add_node (node_config);
|
|
nano::keypair key;
|
|
nano::state_block_builder builder;
|
|
auto send1 = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (nano::dev::genesis->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
|
|
.link (key.pub)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
|
.build_shared ();
|
|
ASSERT_NE (nullptr, send1);
|
|
auto open1 = builder.make_block ()
|
|
.account (key.pub)
|
|
.previous (0)
|
|
.representative (key.pub)
|
|
.balance (nano::Gxrb_ratio)
|
|
.link (send1->hash ())
|
|
.sign (key.prv, key.pub)
|
|
.work (*system.work.generate (key.pub))
|
|
.build_shared ();
|
|
ASSERT_NE (nullptr, open1);
|
|
node.process_active (send1);
|
|
node.process_active (open1);
|
|
nano::blocks_confirm (node, { send1, open1 });
|
|
ASSERT_EQ (2, node.active.size ());
|
|
// 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::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::vote::timestamp_max, nano::vote::duration_max, std::vector<nano::block_hash>{ send1->hash () }));
|
|
ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote_send1));
|
|
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 is still a replay since it's been recently confirmed
|
|
ASSERT_TIMELY (3s, node.active.size () == 1);
|
|
ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_send1));
|
|
// Open new account
|
|
auto vote_open1 (std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::vote::timestamp_max, nano::vote::duration_max, std::vector<nano::block_hash>{ open1->hash () }));
|
|
ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote_open1));
|
|
ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_open1));
|
|
ASSERT_TIMELY (3s, node.active.empty ());
|
|
ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote_open1));
|
|
ASSERT_EQ (nano::Gxrb_ratio, node.ledger.weight (key.pub));
|
|
|
|
auto send2 = builder.make_block ()
|
|
.account (key.pub)
|
|
.previous (open1->hash ())
|
|
.representative (key.pub)
|
|
.balance (nano::Gxrb_ratio - 1)
|
|
.link (key.pub)
|
|
.sign (key.prv, key.pub)
|
|
.work (*system.work.generate (open1->hash ()))
|
|
.build_shared ();
|
|
ASSERT_NE (nullptr, send2);
|
|
node.process_active (send2);
|
|
nano::blocks_confirm (node, { send2 });
|
|
ASSERT_EQ (1, node.active.size ());
|
|
auto vote1_send2 (std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::vote::timestamp_max, nano::vote::duration_max, std::vector<nano::block_hash>{ send2->hash () }));
|
|
auto vote2_send2 (std::make_shared<nano::vote> (key.pub, key.prv, 0, 0, std::vector<nano::block_hash>{ send2->hash () }));
|
|
ASSERT_EQ (nano::vote_code::vote, node.active.vote (vote2_send2));
|
|
ASSERT_EQ (1, node.active.size ());
|
|
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));
|
|
ASSERT_TIMELY (3s, node.active.empty ());
|
|
ASSERT_EQ (0, node.active.size ());
|
|
ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote1_send2));
|
|
ASSERT_EQ (nano::vote_code::replay, node.active.vote (vote2_send2));
|
|
|
|
// Removing blocks as recently confirmed makes every vote indeterminate
|
|
{
|
|
nano::lock_guard<nano::mutex> guard (node.active.mutex);
|
|
node.active.recently_confirmed.clear ();
|
|
}
|
|
ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_send1));
|
|
ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote_open1));
|
|
ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote1_send2));
|
|
ASSERT_EQ (nano::vote_code::indeterminate, node.active.vote (vote2_send2));
|
|
}
|
|
}
|
|
|
|
// Tests that blocks are correctly cleared from the duplicate filter for unconfirmed elections
|
|
TEST (active_transactions, dropped_cleanup)
|
|
{
|
|
nano::system system;
|
|
nano::node_flags flags;
|
|
flags.disable_request_loop = true;
|
|
auto & node (*system.add_node (flags));
|
|
|
|
// Add to network filter to ensure proper cleanup after the election is dropped
|
|
std::vector<uint8_t> block_bytes;
|
|
{
|
|
nano::vectorstream stream (block_bytes);
|
|
nano::dev::genesis->serialize (stream);
|
|
}
|
|
ASSERT_FALSE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ()));
|
|
ASSERT_TRUE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ()));
|
|
|
|
node.block_confirm (nano::dev::genesis);
|
|
node.scheduler.flush ();
|
|
auto election = node.active.election (nano::dev::genesis->qualified_root ());
|
|
ASSERT_NE (nullptr, election);
|
|
|
|
// 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 ()));
|
|
|
|
// Now simulate dropping the election
|
|
ASSERT_FALSE (election->confirmed ());
|
|
node.active.erase (*nano::dev::genesis);
|
|
|
|
// The filter must have been cleared
|
|
ASSERT_FALSE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ()));
|
|
|
|
// An election was recently dropped
|
|
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 ()));
|
|
|
|
// Repeat test for a confirmed election
|
|
ASSERT_TRUE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ()));
|
|
node.block_confirm (nano::dev::genesis);
|
|
node.scheduler.flush ();
|
|
election = node.active.election (nano::dev::genesis->qualified_root ());
|
|
ASSERT_NE (nullptr, election);
|
|
election->force_confirm ();
|
|
ASSERT_TRUE (election->confirmed ());
|
|
node.active.erase (*nano::dev::genesis);
|
|
|
|
// The filter should not have been cleared
|
|
ASSERT_TRUE (node.network.publish_filter.apply (block_bytes.data (), block_bytes.size ()));
|
|
|
|
// Not dropped
|
|
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 ()));
|
|
}
|
|
|
|
TEST (active_transactions, republish_winner)
|
|
{
|
|
nano::system system;
|
|
nano::node_config node_config{ nano::get_available_port (), system.logging };
|
|
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
|
auto & node1 = *system.add_node (node_config);
|
|
node_config.peering_port = nano::get_available_port ();
|
|
auto & node2 = *system.add_node (node_config);
|
|
|
|
nano::keypair key;
|
|
nano::state_block_builder builder;
|
|
auto send1 = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (nano::dev::genesis->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
|
|
.link (key.pub)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
|
.build_shared ();
|
|
|
|
node1.process_active (send1);
|
|
node1.block_processor.flush ();
|
|
ASSERT_TIMELY (3s, node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in) == 1);
|
|
|
|
// Several forks
|
|
for (auto i (0); i < 5; i++)
|
|
{
|
|
auto fork = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (nano::dev::genesis->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 1 - i)
|
|
.link (key.pub)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
|
.build_shared ();
|
|
node1.process_active (fork);
|
|
}
|
|
node1.block_processor.flush ();
|
|
ASSERT_TIMELY (3s, !node1.active.empty ());
|
|
ASSERT_EQ (1, node2.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in));
|
|
|
|
// Process new fork with vote to change winner
|
|
auto fork = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (nano::dev::genesis->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 2 * nano::Gxrb_ratio)
|
|
.link (key.pub)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
|
.build_shared ();
|
|
|
|
node1.process_active (fork);
|
|
node1.block_processor.flush ();
|
|
auto election = node1.active.election (fork->qualified_root ());
|
|
ASSERT_NE (nullptr, election);
|
|
auto vote = std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::vote::timestamp_max, nano::vote::duration_max, std::vector<nano::block_hash>{ fork->hash () });
|
|
node1.vote_processor.vote (vote, std::make_shared<nano::transport::inproc::channel> (node1, node1));
|
|
node1.vote_processor.flush ();
|
|
node1.block_processor.flush ();
|
|
ASSERT_TIMELY (3s, election->confirmed ());
|
|
ASSERT_EQ (fork->hash (), election->status.winner->hash ());
|
|
ASSERT_TIMELY (3s, node2.block_confirmed (fork->hash ()));
|
|
}
|
|
|
|
TEST (active_transactions, fork_filter_cleanup)
|
|
{
|
|
nano::system system{};
|
|
|
|
nano::node_config node_config{ nano::get_available_port (), system.logging };
|
|
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
|
|
|
auto & node1 = *system.add_node (node_config);
|
|
nano::keypair key{};
|
|
nano::state_block_builder builder{};
|
|
auto const latest_hash = nano::dev::genesis->hash ();
|
|
|
|
auto send1 = builder.make_block ()
|
|
.previous (latest_hash)
|
|
.account (nano::dev::genesis_key.pub)
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
|
|
.link (key.pub)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest_hash))
|
|
.build_shared ();
|
|
|
|
std::vector<uint8_t> send_block_bytes{};
|
|
{
|
|
nano::vectorstream stream{ send_block_bytes };
|
|
send1->serialize (stream);
|
|
}
|
|
|
|
// Generate 10 forks to prevent new block insertion to election
|
|
for (auto i = 0; i < 10; ++i)
|
|
{
|
|
auto fork = builder.make_block ()
|
|
.previous (latest_hash)
|
|
.account (nano::dev::genesis_key.pub)
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 1 - i)
|
|
.link (key.pub)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest_hash))
|
|
.build_shared ();
|
|
|
|
node1.process_active (fork);
|
|
ASSERT_TIMELY (5s, node1.active.election (fork->qualified_root ()) != nullptr);
|
|
}
|
|
|
|
// All forks were merged into the same election
|
|
std::shared_ptr<nano::election> election{};
|
|
ASSERT_TIMELY (5s, (election = node1.active.election (send1->qualified_root ())) != nullptr);
|
|
ASSERT_TIMELY (5s, election->blocks ().size () == 10);
|
|
ASSERT_EQ (1, node1.active.size ());
|
|
|
|
// Instantiate a new node
|
|
node_config.peering_port = nano::get_available_port ();
|
|
auto & node2 = *system.add_node (node_config);
|
|
|
|
// Process the first initial block on node2
|
|
node2.process_active (send1);
|
|
ASSERT_TIMELY (5s, node2.active.election (send1->qualified_root ()) != nullptr);
|
|
|
|
// TODO: questions: why doesn't node2 pick up "fork" from node1? because it connected to node1 after node1
|
|
// already process_active()d the fork? shouldn't it broadcast it anyway, even later?
|
|
//
|
|
// how about node1 picking up "send1" from node2? we know it does because we assert at
|
|
// the end that it is within node1's AEC, but why node1.block_count doesn't increase?
|
|
//
|
|
ASSERT_TIMELY (5s, node2.ledger.cache.block_count == 2);
|
|
ASSERT_TIMELY (5s, node1.ledger.cache.block_count == 2);
|
|
|
|
// Block is erased from the duplicate filter
|
|
ASSERT_TIMELY (5s, node1.network.publish_filter.apply (send_block_bytes.data (), send_block_bytes.size ()));
|
|
}
|
|
|
|
TEST (active_transactions, fork_replacement_tally)
|
|
{
|
|
nano::system system;
|
|
nano::node_config node_config (nano::get_available_port (), system.logging);
|
|
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
|
auto & node1 (*system.add_node (node_config));
|
|
|
|
size_t reps_count = 20;
|
|
size_t const max_blocks = 10;
|
|
std::vector<nano::keypair> keys (reps_count);
|
|
auto latest (nano::dev::genesis->hash ());
|
|
auto balance (nano::dev::constants.genesis_amount);
|
|
auto amount (node1.minimum_principal_weight ());
|
|
nano::state_block_builder builder;
|
|
|
|
// Create 20 representatives & confirm blocks
|
|
for (auto i (0); i < reps_count; i++)
|
|
{
|
|
balance -= amount + i;
|
|
auto send = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (latest)
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.balance (balance)
|
|
.link (keys[i].pub)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest))
|
|
.build_shared ();
|
|
node1.process_active (send);
|
|
latest = send->hash ();
|
|
auto open = builder.make_block ()
|
|
.account (keys[i].pub)
|
|
.previous (0)
|
|
.representative (keys[i].pub)
|
|
.balance (amount + i)
|
|
.link (send->hash ())
|
|
.sign (keys[i].prv, keys[i].pub)
|
|
.work (*system.work.generate (keys[i].pub))
|
|
.build_shared ();
|
|
node1.process_active (open);
|
|
// Confirmation
|
|
auto vote (std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::vote::timestamp_max, nano::vote::duration_max, std::vector<nano::block_hash>{ send->hash (), open->hash () }));
|
|
node1.vote_processor.vote (vote, std::make_shared<nano::transport::inproc::channel> (node1, node1));
|
|
}
|
|
node1.block_processor.flush ();
|
|
ASSERT_TIMELY (5s, node1.ledger.cache.cemented_count == 1 + 2 * reps_count);
|
|
|
|
nano::keypair key;
|
|
auto send_last = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (latest)
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.balance (balance - 2 * nano::Gxrb_ratio)
|
|
.link (key.pub)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest))
|
|
.build_shared ();
|
|
|
|
// Forks without votes
|
|
for (auto i (0); i < reps_count; i++)
|
|
{
|
|
auto fork = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (latest)
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.balance (balance - nano::Gxrb_ratio - i)
|
|
.link (key.pub)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest))
|
|
.build_shared ();
|
|
node1.process_active (fork);
|
|
}
|
|
node1.block_processor.flush ();
|
|
ASSERT_TIMELY (3s, !node1.active.empty ());
|
|
// Check overflow of blocks
|
|
auto election (node1.active.election (send_last->qualified_root ()));
|
|
ASSERT_NE (nullptr, election);
|
|
ASSERT_EQ (max_blocks, election->blocks ().size ());
|
|
|
|
// Generate forks with votes to prevent new block insertion to election
|
|
for (auto i (0); i < reps_count; i++)
|
|
{
|
|
auto fork = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (latest)
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.balance (balance - 1 - i)
|
|
.link (key.pub)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest))
|
|
.build_shared ();
|
|
auto vote (std::make_shared<nano::vote> (keys[i].pub, keys[i].prv, 0, 0, std::vector<nano::block_hash>{ fork->hash () }));
|
|
node1.vote_processor.vote (vote, std::make_shared<nano::transport::inproc::channel> (node1, node1));
|
|
node1.vote_processor.flush ();
|
|
node1.process_active (fork);
|
|
}
|
|
node1.block_processor.flush ();
|
|
// Check overflow of blocks
|
|
ASSERT_EQ (max_blocks, election->blocks ().size ());
|
|
// Check that only max weight blocks remains (and start winner)
|
|
auto votes1 (election->votes ());
|
|
ASSERT_EQ (max_blocks, votes1.size ());
|
|
for (auto i (max_blocks + 1); i < reps_count; i++)
|
|
{
|
|
ASSERT_TRUE (votes1.find (keys[i].pub) != votes1.end ());
|
|
}
|
|
|
|
// Process correct block
|
|
node_config.peering_port = nano::get_available_port ();
|
|
auto & node2 (*system.add_node (node_config));
|
|
node2.network.flood_block (send_last);
|
|
ASSERT_TIMELY (3s, node1.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in) > 0);
|
|
node1.block_processor.flush ();
|
|
std::this_thread::sleep_for (50ms);
|
|
|
|
// Correct block without votes is ignored
|
|
auto blocks1 (election->blocks ());
|
|
ASSERT_EQ (max_blocks, blocks1.size ());
|
|
ASSERT_FALSE (blocks1.find (send_last->hash ()) != blocks1.end ());
|
|
|
|
// Process vote for correct block & replace existing lowest tally block
|
|
auto vote (std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, 0, 0, std::vector<nano::block_hash>{ send_last->hash () }));
|
|
node1.vote_processor.vote (vote, std::make_shared<nano::transport::inproc::channel> (node1, node1));
|
|
node1.vote_processor.flush ();
|
|
node2.network.flood_block (send_last);
|
|
ASSERT_TIMELY (3s, node1.stats.count (nano::stat::type::message, nano::stat::detail::publish, nano::stat::dir::in) > 1);
|
|
node1.block_processor.flush ();
|
|
std::this_thread::sleep_for (50ms);
|
|
|
|
auto blocks2 (election->blocks ());
|
|
ASSERT_EQ (max_blocks, blocks2.size ());
|
|
ASSERT_TRUE (blocks2.find (send_last->hash ()) != blocks2.end ());
|
|
auto votes2 (election->votes ());
|
|
ASSERT_EQ (max_blocks, votes2.size ());
|
|
for (auto i (max_blocks + 2); i < reps_count; i++)
|
|
{
|
|
ASSERT_TRUE (votes2.find (keys[i].pub) != votes2.end ());
|
|
}
|
|
ASSERT_FALSE (votes2.find (keys[max_blocks].pub) != votes2.end ());
|
|
ASSERT_FALSE (votes2.find (keys[max_blocks + 1].pub) != votes2.end ());
|
|
ASSERT_TRUE (votes2.find (nano::dev::genesis_key.pub) != votes2.end ());
|
|
}
|
|
|
|
namespace nano
|
|
{
|
|
// Blocks that won an election must always be seen as confirming or cemented
|
|
TEST (active_transactions, confirmation_consistency)
|
|
{
|
|
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);
|
|
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
|
|
for (unsigned i = 0; i < 10; ++i)
|
|
{
|
|
auto block (system.wallet (0)->send_action (nano::dev::genesis_key.pub, nano::public_key (), node.config.receive_minimum.number ()));
|
|
ASSERT_NE (nullptr, block);
|
|
system.deadline_set (5s);
|
|
while (!node.ledger.block_confirmed (node.store.tx_begin_read (), block->hash ()))
|
|
{
|
|
node.scheduler.activate (nano::dev::genesis_key.pub, node.store.tx_begin_read ());
|
|
ASSERT_NO_ERROR (system.poll (5ms));
|
|
}
|
|
ASSERT_NO_ERROR (system.poll_until_true (1s, [&node, &block, i] {
|
|
nano::lock_guard<nano::mutex> guard (node.active.mutex);
|
|
EXPECT_EQ (i + 1, node.active.recently_confirmed.size ());
|
|
EXPECT_EQ (block->qualified_root (), node.active.recently_confirmed.back ().first);
|
|
return i + 1 == node.active.recently_cemented.size (); // done after a callback
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Test disabled because it's failing intermittently.
|
|
// PR in which it got disabled: https://github.com/nanocurrency/nano-node/pull/3629
|
|
// Issue for investigating it: https://github.com/nanocurrency/nano-node/issues/3634
|
|
TEST (active_transactions, DISABLED_confirm_new)
|
|
{
|
|
nano::system system (1);
|
|
auto & node1 = *system.nodes[0];
|
|
auto send = nano::send_block_builder ()
|
|
.previous (nano::dev::genesis->hash ())
|
|
.destination (nano::public_key ())
|
|
.balance (nano::dev::constants.genesis_amount - 100)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
|
.build_shared ();
|
|
node1.process_active (send);
|
|
node1.block_processor.flush ();
|
|
node1.scheduler.flush ();
|
|
ASSERT_EQ (1, node1.active.size ());
|
|
auto & node2 = *system.add_node ();
|
|
// Add key to node2
|
|
system.wallet (1)->insert_adhoc (nano::dev::genesis_key.prv);
|
|
// Let node2 know about the block
|
|
ASSERT_TIMELY (5s, node2.block (send->hash ()));
|
|
// Wait confirmation
|
|
ASSERT_TIMELY (5s, node1.ledger.cache.cemented_count == 2 && node2.ledger.cache.cemented_count == 2);
|
|
}
|
|
|
|
// Ensures votes are tallied on election::publish even if no vote is inserted through inactive_votes_cache
|
|
TEST (active_transactions, conflicting_block_vote_existing_election)
|
|
{
|
|
nano::system system;
|
|
nano::node_flags node_flags;
|
|
node_flags.disable_request_loop = true;
|
|
auto & node = *system.add_node (node_flags);
|
|
nano::keypair key;
|
|
nano::state_block_builder builder;
|
|
auto send = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (nano::dev::genesis->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 100)
|
|
.link (key.pub)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
|
.build_shared ();
|
|
auto fork = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (nano::dev::genesis->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 200)
|
|
.link (key.pub)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
|
.build_shared ();
|
|
auto vote_fork (std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::vote::timestamp_max, nano::vote::duration_max, std::vector<nano::block_hash>{ fork->hash () }));
|
|
|
|
ASSERT_EQ (nano::process_result::progress, node.process_local (send).code);
|
|
node.scheduler.flush ();
|
|
ASSERT_EQ (1, node.active.size ());
|
|
|
|
// Vote for conflicting block, but the block does not yet exist in the ledger
|
|
node.active.vote (vote_fork);
|
|
|
|
// Block now gets processed
|
|
ASSERT_EQ (nano::process_result::fork, node.process_local (fork).code);
|
|
|
|
// Election must be confirmed
|
|
auto election (node.active.election (fork->qualified_root ()));
|
|
ASSERT_NE (nullptr, election);
|
|
ASSERT_TIMELY (3s, election->confirmed ());
|
|
}
|
|
|
|
TEST (active_transactions, activate_account_chain)
|
|
{
|
|
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;
|
|
auto send = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (nano::dev::genesis->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (nano::dev::genesis_key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 1)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
|
.build ();
|
|
auto send2 = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (send->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 2)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (send->hash ()))
|
|
.build ();
|
|
auto send3 = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (send2->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 3)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (send2->hash ()))
|
|
.build ();
|
|
auto 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 ();
|
|
auto receive = builder.make_block ()
|
|
.account (key.pub)
|
|
.previous (open->hash ())
|
|
.representative (key.pub)
|
|
.link (send3->hash ())
|
|
.balance (2)
|
|
.sign (key.prv, key.pub)
|
|
.work (*system.work.generate (open->hash ()))
|
|
.build ();
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*send).code);
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*send2).code);
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*send3).code);
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*open).code);
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*receive).code);
|
|
|
|
node.scheduler.activate (nano::dev::genesis_key.pub, node.store.tx_begin_read ());
|
|
node.scheduler.flush ();
|
|
auto election1 = node.active.election (send->qualified_root ());
|
|
ASSERT_EQ (1, node.active.size ());
|
|
ASSERT_EQ (1, election1->blocks ().count (send->hash ()));
|
|
node.scheduler.activate (nano::dev::genesis_key.pub, node.store.tx_begin_read ());
|
|
auto election2 = node.active.election (send->qualified_root ());
|
|
ASSERT_EQ (election2, election1);
|
|
election1->force_confirm ();
|
|
ASSERT_TIMELY (3s, node.block_confirmed (send->hash ()));
|
|
// On cementing, the next election is started
|
|
ASSERT_TIMELY (3s, node.active.active (send2->qualified_root ()));
|
|
node.scheduler.activate (nano::dev::genesis_key.pub, node.store.tx_begin_read ());
|
|
auto election3 = node.active.election (send2->qualified_root ());
|
|
ASSERT_NE (nullptr, election3);
|
|
ASSERT_EQ (1, election3->blocks ().count (send2->hash ()));
|
|
election3->force_confirm ();
|
|
ASSERT_TIMELY (3s, node.block_confirmed (send2->hash ()));
|
|
// On cementing, the next election is started
|
|
ASSERT_TIMELY (3s, node.active.active (open->qualified_root ()));
|
|
ASSERT_TIMELY (3s, node.active.active (send3->qualified_root ()));
|
|
node.scheduler.activate (nano::dev::genesis_key.pub, node.store.tx_begin_read ());
|
|
auto election4 = node.active.election (send3->qualified_root ());
|
|
ASSERT_NE (nullptr, election4);
|
|
ASSERT_EQ (1, election4->blocks ().count (send3->hash ()));
|
|
node.scheduler.activate (key.pub, node.store.tx_begin_read ());
|
|
auto election5 = node.active.election (open->qualified_root ());
|
|
ASSERT_NE (nullptr, election5);
|
|
ASSERT_EQ (1, election5->blocks ().count (open->hash ()));
|
|
election5->force_confirm ();
|
|
ASSERT_TIMELY (3s, node.block_confirmed (open->hash ()));
|
|
// Until send3 is also confirmed, the receive block should not activate
|
|
std::this_thread::sleep_for (200ms);
|
|
node.scheduler.activate (key.pub, node.store.tx_begin_read ());
|
|
election4->force_confirm ();
|
|
ASSERT_TIMELY (3s, node.block_confirmed (send3->hash ()));
|
|
ASSERT_TIMELY (3s, node.active.active (receive->qualified_root ()));
|
|
}
|
|
|
|
TEST (active_transactions, activate_inactive)
|
|
{
|
|
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;
|
|
auto send = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (nano::dev::genesis->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 1)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
|
.build_shared ();
|
|
auto send2 = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (send->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (nano::keypair ().pub)
|
|
.balance (nano::dev::constants.genesis_amount - 2)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (send->hash ()))
|
|
.build_shared ();
|
|
auto open = builder.make_block ()
|
|
.account (key.pub)
|
|
.previous (0)
|
|
.representative (key.pub)
|
|
.link (send->hash ())
|
|
.balance (1)
|
|
.sign (key.prv, key.pub)
|
|
.work (*system.work.generate (key.pub))
|
|
.build_shared ();
|
|
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*send).code);
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*send2).code);
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*open).code);
|
|
|
|
node.block_confirm (send2);
|
|
auto election = node.active.election (send2->qualified_root ());
|
|
ASSERT_NE (nullptr, election);
|
|
election->force_confirm ();
|
|
|
|
ASSERT_TIMELY (3s, !node.confirmation_height_processor.is_processing_added_block (send2->hash ()));
|
|
ASSERT_TRUE (node.block_confirmed (send2->hash ()));
|
|
ASSERT_TRUE (node.block_confirmed (send->hash ()));
|
|
|
|
ASSERT_EQ (1, node.stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out));
|
|
ASSERT_EQ (1, node.stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out));
|
|
ASSERT_EQ (0, node.stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_conf_height, nano::stat::dir::out));
|
|
|
|
// The first block was not active so no activation takes place
|
|
ASSERT_FALSE (node.active.active (open->qualified_root ()) || node.block_confirmed_or_being_confirmed (node.store.tx_begin_read (), open->hash ()));
|
|
}
|
|
|
|
TEST (active_transactions, list_active)
|
|
{
|
|
nano::system system (1);
|
|
auto & node = *system.nodes[0];
|
|
|
|
nano::keypair key;
|
|
nano::state_block_builder builder;
|
|
auto send = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (nano::dev::genesis->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (nano::dev::genesis_key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 1)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
|
.build_shared ();
|
|
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*send).code);
|
|
|
|
auto send2 = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (send->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 2)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (send->hash ()))
|
|
.build_shared ();
|
|
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*send2).code);
|
|
|
|
auto 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_shared ();
|
|
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*open).code);
|
|
|
|
nano::blocks_confirm (node, { send, send2, open });
|
|
ASSERT_EQ (3, node.active.size ());
|
|
ASSERT_EQ (1, node.active.list_active (1).size ());
|
|
ASSERT_EQ (2, node.active.list_active (2).size ());
|
|
ASSERT_EQ (3, node.active.list_active (3).size ());
|
|
ASSERT_EQ (3, node.active.list_active (4).size ());
|
|
ASSERT_EQ (3, node.active.list_active (99999).size ());
|
|
ASSERT_EQ (3, node.active.list_active ().size ());
|
|
|
|
auto active = node.active.list_active ();
|
|
}
|
|
|
|
TEST (active_transactions, vacancy)
|
|
{
|
|
nano::system system;
|
|
nano::node_config config{ nano::get_available_port (), system.logging };
|
|
config.active_elections_size = 1;
|
|
auto & node = *system.add_node (config);
|
|
nano::state_block_builder builder;
|
|
auto send = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (nano::dev::genesis->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (nano::dev::genesis_key.pub)
|
|
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (nano::dev::genesis->hash ()))
|
|
.build_shared ();
|
|
std::atomic<bool> updated = false;
|
|
node.active.vacancy_update = [&updated] () { updated = true; };
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*send).code);
|
|
ASSERT_EQ (1, node.active.vacancy ());
|
|
ASSERT_EQ (0, node.active.size ());
|
|
node.scheduler.activate (nano::dev::genesis_key.pub, node.store.tx_begin_read ());
|
|
ASSERT_TIMELY (1s, updated);
|
|
updated = false;
|
|
ASSERT_EQ (0, node.active.vacancy ());
|
|
ASSERT_EQ (1, node.active.size ());
|
|
auto election1 = node.active.election (send->qualified_root ());
|
|
ASSERT_NE (nullptr, election1);
|
|
election1->force_confirm ();
|
|
ASSERT_TIMELY (1s, updated);
|
|
ASSERT_EQ (1, node.active.vacancy ());
|
|
ASSERT_EQ (0, node.active.size ());
|
|
}
|
|
|
|
// Ensure transactions in excess of capacity are removed in fifo order
|
|
TEST (active_transactions, fifo)
|
|
{
|
|
nano::system system{};
|
|
|
|
nano::node_config config{ nano::get_available_port (), system.logging };
|
|
config.active_elections_size = 1;
|
|
|
|
auto & node = *system.add_node (config);
|
|
auto latest_hash = nano::dev::genesis->hash ();
|
|
nano::keypair key0{};
|
|
nano::state_block_builder builder{};
|
|
|
|
// Construct two pending entries that can be received simultaneously
|
|
auto send1 = builder.make_block ()
|
|
.previous (latest_hash)
|
|
.account (nano::dev::genesis_key.pub)
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (key0.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 1)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest_hash))
|
|
.build_shared ();
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*send1).code);
|
|
node.process_confirmed (nano::election_status{ send1 });
|
|
ASSERT_TIMELY (5s, node.block_confirmed (send1->hash ()));
|
|
|
|
nano::keypair key1{};
|
|
latest_hash = send1->hash ();
|
|
auto send2 = builder.make_block ()
|
|
.previous (latest_hash)
|
|
.account (nano::dev::genesis_key.pub)
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (key1.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 2)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest_hash))
|
|
.build_shared ();
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*send2).code);
|
|
node.process_confirmed (nano::election_status{ send2 });
|
|
ASSERT_TIMELY (5s, node.block_confirmed (send2->hash ()));
|
|
|
|
auto receive1 = builder.make_block ()
|
|
.previous (0)
|
|
.account (key0.pub)
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (send1->hash ())
|
|
.balance (1)
|
|
.sign (key0.prv, key0.pub)
|
|
.work (*system.work.generate (key0.pub))
|
|
.build_shared ();
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*receive1).code);
|
|
|
|
auto receive2 = builder.make_block ()
|
|
.previous (0)
|
|
.account (key1.pub)
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (send2->hash ())
|
|
.balance (1)
|
|
.sign (key1.prv, key1.pub)
|
|
.work (*system.work.generate (key1.pub))
|
|
.build_shared ();
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*receive2).code);
|
|
|
|
// Ensure first transaction becomes active
|
|
node.scheduler.manual (receive1);
|
|
ASSERT_TIMELY (5s, node.active.election (receive1->qualified_root ()) != nullptr);
|
|
|
|
// Ensure second transaction becomes active
|
|
node.scheduler.manual (receive2);
|
|
ASSERT_TIMELY (5s, node.active.election (receive2->qualified_root ()) != nullptr);
|
|
|
|
// Ensure excess transactions get trimmed
|
|
ASSERT_TIMELY (5s, node.active.size () == 1);
|
|
|
|
// Ensure overflow stats have been incremented
|
|
ASSERT_EQ (1, node.stats.count (nano::stat::type::election, nano::stat::detail::election_drop_overflow));
|
|
|
|
// Ensure the surviving transaction is the least recently inserted
|
|
ASSERT_TIMELY (1s, node.active.election (receive2->qualified_root ()) != nullptr);
|
|
}
|
|
|
|
// Ensures we limit the number of vote hinted elections in AEC
|
|
TEST (active_transactions, limit_vote_hinted_elections)
|
|
{
|
|
nano::system system;
|
|
nano::node_config config{ nano::get_available_port (), system.logging };
|
|
config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
|
|
config.active_elections_size = 10;
|
|
config.active_elections_hinted_limit_percentage = 10; // Should give us a limit of 1 hinted election
|
|
auto & node = *system.add_node (config);
|
|
|
|
// Setup representatives
|
|
nano::keypair rep1, rep2;
|
|
{
|
|
nano::block_hash latest = node.latest (nano::dev::genesis_key.pub);
|
|
nano::keypair key1, key2;
|
|
nano::send_block_builder send_block_builder;
|
|
nano::state_block_builder state_block_builder;
|
|
// Enough weight to trigger election hinting but not enough to confirm block on its own
|
|
auto amount = ((node.online_reps.trended () / 100) * node.config.election_hint_weight_percent) / 2 + 1000 * nano::Gxrb_ratio;
|
|
auto send1 = send_block_builder.make_block ()
|
|
.previous (latest)
|
|
.destination (key1.pub)
|
|
.balance (nano::dev::constants.genesis_amount - amount)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest))
|
|
.build_shared ();
|
|
auto send2 = send_block_builder.make_block ()
|
|
.previous (send1->hash ())
|
|
.destination (key2.pub)
|
|
.balance (nano::dev::constants.genesis_amount - 2 * amount)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (send1->hash ()))
|
|
.build_shared ();
|
|
auto open1 = state_block_builder.make_block ()
|
|
.account (key1.pub)
|
|
.previous (0)
|
|
.representative (key1.pub)
|
|
.balance (amount)
|
|
.link (send1->hash ())
|
|
.sign (key1.prv, key1.pub)
|
|
.work (*system.work.generate (key1.pub))
|
|
.build_shared ();
|
|
auto open2 = state_block_builder.make_block ()
|
|
.account (key2.pub)
|
|
.previous (0)
|
|
.representative (key2.pub)
|
|
.balance (amount)
|
|
.link (send2->hash ())
|
|
.sign (key2.prv, key2.pub)
|
|
.work (*system.work.generate (key2.pub))
|
|
.build_shared ();
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*send1).code);
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*send2).code);
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*open1).code);
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*open2).code);
|
|
nano::blocks_confirm (node, { send1, send2, open1, open2 }, true);
|
|
ASSERT_TIMELY (1s, node.block_confirmed (send1->hash ()));
|
|
ASSERT_TIMELY (1s, node.block_confirmed (send2->hash ()));
|
|
ASSERT_TIMELY (1s, node.block_confirmed (open1->hash ()));
|
|
ASSERT_TIMELY (1s, node.block_confirmed (open2->hash ()));
|
|
ASSERT_TIMELY (1s, node.active.empty ());
|
|
rep1 = key1;
|
|
rep2 = key2;
|
|
}
|
|
// Test vote hinting behavior
|
|
{
|
|
auto latest_balance = node.balance (nano::dev::genesis_key.pub);
|
|
auto latest = node.latest (nano::dev::genesis_key.pub);
|
|
nano::keypair key0, key1;
|
|
nano::state_block_builder builder;
|
|
// Construct two pending entries that can be received simultaneously
|
|
auto send0 = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (latest)
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (key0.pub)
|
|
.balance (latest_balance - 1)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (latest))
|
|
.build_shared ();
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*send0).code);
|
|
nano::blocks_confirm (node, { send0 }, true);
|
|
ASSERT_TIMELY (1s, node.block_confirmed (send0->hash ()));
|
|
ASSERT_TIMELY (1s, node.active.empty ());
|
|
auto send1 = builder.make_block ()
|
|
.account (nano::dev::genesis_key.pub)
|
|
.previous (send0->hash ())
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (key1.pub)
|
|
.balance (latest_balance - 2)
|
|
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
|
|
.work (*system.work.generate (send0->hash ()))
|
|
.build_shared ();
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*send1).code);
|
|
nano::blocks_confirm (node, { send1 }, true);
|
|
ASSERT_TIMELY (1s, node.block_confirmed (send1->hash ()));
|
|
ASSERT_TIMELY (1s, node.active.empty ());
|
|
|
|
auto receive0 = builder.make_block ()
|
|
.account (key0.pub)
|
|
.previous (0)
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (send0->hash ())
|
|
.balance (1)
|
|
.sign (key0.prv, key0.pub)
|
|
.work (*system.work.generate (key0.pub))
|
|
.build_shared ();
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*receive0).code);
|
|
auto receive1 = builder.make_block ()
|
|
.account (key1.pub)
|
|
.previous (0)
|
|
.representative (nano::dev::genesis_key.pub)
|
|
.link (send1->hash ())
|
|
.balance (1)
|
|
.sign (key1.prv, key1.pub)
|
|
.work (*system.work.generate (key1.pub))
|
|
.build_shared ();
|
|
ASSERT_EQ (nano::process_result::progress, node.process (*receive1).code);
|
|
ASSERT_TRUE (node.active.empty ());
|
|
ASSERT_EQ (7, node.ledger.cache.cemented_count);
|
|
|
|
// Inactive vote
|
|
auto vote1 (std::make_shared<nano::vote> (rep1.pub, rep1.prv, 0, 0, std::vector<nano::block_hash>{ receive0->hash (), receive1->hash () }));
|
|
node.vote_processor.vote (vote1, std::make_shared<nano::transport::inproc::channel> (node, node));
|
|
ASSERT_TIMELY (1s, node.active.inactive_votes_cache_size () == 2);
|
|
ASSERT_TRUE (node.active.empty ());
|
|
ASSERT_EQ (7, node.ledger.cache.cemented_count);
|
|
|
|
// This vote should trigger election hinting for first receive block
|
|
auto vote2 (std::make_shared<nano::vote> (rep2.pub, rep2.prv, 0, 0, std::vector<nano::block_hash>{ receive0->hash () }));
|
|
node.vote_processor.vote (vote2, std::make_shared<nano::transport::inproc::channel> (node, node));
|
|
ASSERT_TIMELY (1s, 1 == node.active.size ());
|
|
// Ensure first transaction becomes active
|
|
ASSERT_TIMELY (1s, node.active.election (receive0->qualified_root ()) != nullptr);
|
|
|
|
// This vote should trigger election hinting but not become active due to limit of active hinted elections
|
|
auto vote3 (std::make_shared<nano::vote> (rep2.pub, rep2.prv, 0, 0, std::vector<nano::block_hash>{ receive1->hash () }));
|
|
node.vote_processor.vote (vote3, std::make_shared<nano::transport::inproc::channel> (node, node));
|
|
ASSERT_TIMELY (1s, node.stats.count (nano::stat::type::election, nano::stat::detail::election_hinted_overflow) == 1);
|
|
ASSERT_TIMELY (1s, 1 == node.active.size ());
|
|
// Ensure second transaction does not become active
|
|
ASSERT_TIMELY (1s, node.active.election (receive1->qualified_root ()) == nullptr);
|
|
|
|
// This final vote should confirm the first receive block
|
|
auto vote4 = (std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, nano::vote::timestamp_max, nano::vote::duration_max, std::vector<nano::block_hash>{ receive0->hash () }));
|
|
node.vote_processor.vote (vote4, std::make_shared<nano::transport::inproc::channel> (node, node));
|
|
ASSERT_TIMELY (1s, node.active.empty ());
|
|
ASSERT_EQ (8, node.ledger.cache.cemented_count);
|
|
ASSERT_TIMELY (1s, node.active.inactive_votes_cache_size () == 1);
|
|
|
|
// Now it should be possible to vote hint second block
|
|
auto vote5 = (std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, 0, 0, std::vector<nano::block_hash>{ receive1->hash () }));
|
|
node.vote_processor.vote (vote5, std::make_shared<nano::transport::inproc::channel> (node, node));
|
|
ASSERT_TIMELY (1s, node.stats.count (nano::stat::type::election, nano::stat::detail::election_hinted_overflow) == 1);
|
|
ASSERT_TIMELY (1s, 1 == node.active.size ());
|
|
ASSERT_EQ (8, node.ledger.cache.cemented_count);
|
|
ASSERT_TIMELY (1s, node.active.inactive_votes_cache_size () == 1);
|
|
|
|
// Ensure there was no overflow
|
|
ASSERT_EQ (0, node.stats.count (nano::stat::type::election, nano::stat::detail::election_drop_overflow));
|
|
}
|
|
}
|