dncurrency/nano/core_test/confirmation_height.cpp
Dimitrios Siganos 78631a6b1c
Move test code into namespace nano::test (#3890)
* Move nano::establish_tcp into nano::test namespace
* Move system.hpp declarations into namespace nano::test
* Move telemetry test code to namespace nano::test
* Move testutil declarations into namespace nano::test
2022-08-10 20:48:31 +01:00

2244 lines
94 KiB
C++

#include <nano/node/election.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/testutil.hpp>
#include <gtest/gtest.h>
#include <boost/format.hpp>
using namespace std::chrono_literals;
namespace
{
void add_callback_stats (nano::node & node, std::vector<nano::block_hash> * observer_order = nullptr, nano::mutex * mutex = nullptr)
{
node.observers.blocks.add ([&stats = node.stats, observer_order, mutex] (nano::election_status const & status_a, std::vector<nano::vote_with_weight_info> const &, nano::account const &, nano::amount const &, bool, bool) {
stats.inc (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out);
if (mutex)
{
nano::lock_guard<nano::mutex> guard (*mutex);
debug_assert (observer_order);
observer_order->push_back (status_a.winner->hash ());
}
});
}
nano::stat::detail get_stats_detail (nano::confirmation_height_mode mode_a)
{
debug_assert (mode_a == nano::confirmation_height_mode::bounded || mode_a == nano::confirmation_height_mode::unbounded);
return (mode_a == nano::confirmation_height_mode::bounded) ? nano::stat::detail::blocks_confirmed_bounded : nano::stat::detail::blocks_confirmed_unbounded;
}
}
TEST (confirmation_height, single)
{
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
auto amount (std::numeric_limits<nano::uint128_t>::max ());
nano::test::system system;
nano::node_flags node_flags;
node_flags.confirmation_height_processor_mode = mode_a;
auto node = system.add_node (node_flags);
nano::keypair key1;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::block_hash latest1 (node->latest (nano::dev::genesis_key.pub));
nano::block_builder builder;
auto send1 = builder
.state ()
.account (nano::dev::genesis_key.pub)
.previous (latest1)
.representative (nano::dev::genesis_key.pub)
.balance (amount - 100)
.link (key1.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest1))
.build_shared ();
// Check confirmation heights before, should be uninitialized (1 for genesis).
nano::confirmation_height_info confirmation_height_info;
add_callback_stats (*node);
auto transaction = node->store.tx_begin_read ();
ASSERT_FALSE (node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub, confirmation_height_info));
ASSERT_EQ (1, confirmation_height_info.height);
ASSERT_EQ (nano::dev::genesis->hash (), confirmation_height_info.frontier);
node->process_active (send1);
node->block_processor.flush ();
ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) == 1);
{
auto transaction = node->store.tx_begin_write ();
ASSERT_TRUE (node->ledger.block_confirmed (transaction, send1->hash ()));
ASSERT_FALSE (node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub, confirmation_height_info));
ASSERT_EQ (2, confirmation_height_info.height);
ASSERT_EQ (send1->hash (), confirmation_height_info.frontier);
// Rollbacks should fail as these blocks have been cemented
ASSERT_TRUE (node->ledger.rollback (transaction, latest1));
ASSERT_TRUE (node->ledger.rollback (transaction, send1->hash ()));
ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in));
ASSERT_EQ (1, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out));
ASSERT_EQ (2, node->ledger.cache.cemented_count);
ASSERT_EQ (0, node->active.election_winner_details_size ());
}
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
TEST (confirmation_height, multiple_accounts)
{
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
nano::test::system system;
nano::node_flags node_flags;
node_flags.confirmation_height_processor_mode = mode_a;
nano::node_config node_config (nano::test::get_available_port (), system.logging);
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto node = system.add_node (node_config, node_flags);
nano::keypair key1;
nano::keypair key2;
nano::keypair key3;
nano::block_hash latest1 (system.nodes[0]->latest (nano::dev::genesis_key.pub));
nano::block_builder builder;
// Send to all accounts
auto send1 = builder
.send ()
.previous (latest1)
.destination (key1.pub)
.balance (node->online_reps.delta () + 300)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest1))
.build ();
auto send2 = builder
.send ()
.previous (send1->hash ())
.destination (key2.pub)
.balance (node->online_reps.delta () + 200)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build ();
auto send3 = builder
.send ()
.previous (send2->hash ())
.destination (key3.pub)
.balance (node->online_reps.delta () + 100)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send2->hash ()))
.build ();
// Open all accounts
auto open1 = builder
.open ()
.source (send1->hash ())
.representative (nano::dev::genesis->account ())
.account (key1.pub)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1.pub))
.build ();
auto open2 = builder
.open ()
.source (send2->hash ())
.representative (nano::dev::genesis->account ())
.account (key2.pub)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (key2.pub))
.build ();
auto open3 = builder
.open ()
.source (send3->hash ())
.representative (nano::dev::genesis->account ())
.account (key3.pub)
.sign (key3.prv, key3.pub)
.work (*system.work.generate (key3.pub))
.build ();
// Send and receive various blocks to these accounts
auto send4 = builder
.send ()
.previous (open1->hash ())
.destination (key2.pub)
.balance (50)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (open1->hash ()))
.build ();
auto send5 = builder
.send ()
.previous (send4->hash ())
.destination (key2.pub)
.balance (10)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (send4->hash ()))
.build ();
auto receive1 = builder
.receive ()
.previous (open2->hash ())
.source (send4->hash ())
.sign (key2.prv, key2.pub)
.work (*system.work.generate (open2->hash ()))
.build ();
auto send6 = builder
.send ()
.previous (receive1->hash ())
.destination (key3.pub)
.balance (10)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (receive1->hash ()))
.build ();
auto receive2 = builder
.receive ()
.previous (send6->hash ())
.source (send5->hash ())
.sign (key2.prv, key2.pub)
.work (*system.work.generate (send6->hash ()))
.build ();
add_callback_stats (*node);
{
auto transaction = node->store.tx_begin_write ();
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send3).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *open1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *open2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *open3).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send4).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send5).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send6).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive2).code);
// Check confirmation heights of all the accounts (except genesis) are uninitialized (0),
// as we have any just added them to the ledger and not processed any live transactions yet.
nano::confirmation_height_info confirmation_height_info;
ASSERT_FALSE (node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub, confirmation_height_info));
ASSERT_EQ (1, confirmation_height_info.height);
ASSERT_EQ (nano::dev::genesis->hash (), confirmation_height_info.frontier);
ASSERT_TRUE (node->store.confirmation_height.get (transaction, key1.pub, confirmation_height_info));
ASSERT_EQ (0, confirmation_height_info.height);
ASSERT_EQ (nano::block_hash (0), confirmation_height_info.frontier);
ASSERT_TRUE (node->store.confirmation_height.get (transaction, key2.pub, confirmation_height_info));
ASSERT_EQ (0, confirmation_height_info.height);
ASSERT_EQ (nano::block_hash (0), confirmation_height_info.frontier);
ASSERT_TRUE (node->store.confirmation_height.get (transaction, key3.pub, confirmation_height_info));
ASSERT_EQ (0, confirmation_height_info.height);
ASSERT_EQ (nano::block_hash (0), confirmation_height_info.frontier);
}
// The nodes process a live receive which propagates across to all accounts
auto receive3 = builder
.receive ()
.previous (open3->hash ())
.source (send6->hash ())
.sign (key3.prv, key3.pub)
.work (*system.work.generate (open3->hash ()))
.build_shared ();
node->process_active (receive3);
node->block_processor.flush ();
node->block_confirm (receive3);
auto election = node->active.election (receive3->qualified_root ());
ASSERT_NE (nullptr, election);
election->force_confirm ();
ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) == 10);
nano::account_info account_info;
nano::confirmation_height_info confirmation_height_info;
auto & store = node->store;
auto transaction = node->store.tx_begin_read ();
ASSERT_TRUE (node->ledger.block_confirmed (transaction, receive3->hash ()));
ASSERT_FALSE (store.account.get (transaction, nano::dev::genesis_key.pub, account_info));
ASSERT_FALSE (node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub, confirmation_height_info));
ASSERT_EQ (4, confirmation_height_info.height);
ASSERT_EQ (send3->hash (), confirmation_height_info.frontier);
ASSERT_EQ (4, account_info.block_count);
ASSERT_FALSE (store.account.get (transaction, key1.pub, account_info));
ASSERT_FALSE (node->store.confirmation_height.get (transaction, key1.pub, confirmation_height_info));
ASSERT_EQ (2, confirmation_height_info.height);
ASSERT_EQ (send4->hash (), confirmation_height_info.frontier);
ASSERT_EQ (3, account_info.block_count);
ASSERT_FALSE (store.account.get (transaction, key2.pub, account_info));
ASSERT_FALSE (node->store.confirmation_height.get (transaction, key2.pub, confirmation_height_info));
ASSERT_EQ (3, confirmation_height_info.height);
ASSERT_EQ (send6->hash (), confirmation_height_info.frontier);
ASSERT_EQ (4, account_info.block_count);
ASSERT_FALSE (store.account.get (transaction, key3.pub, account_info));
ASSERT_FALSE (node->store.confirmation_height.get (transaction, key3.pub, confirmation_height_info));
ASSERT_EQ (2, confirmation_height_info.height);
ASSERT_EQ (receive3->hash (), confirmation_height_info.frontier);
ASSERT_EQ (2, account_info.block_count);
// The accounts for key1 and key2 have 1 more block in the chain than is confirmed.
// So this can be rolled back, but the one before that cannot. Check that this is the case
{
auto transaction = node->store.tx_begin_write ();
ASSERT_FALSE (node->ledger.rollback (transaction, node->latest (key2.pub)));
ASSERT_FALSE (node->ledger.rollback (transaction, node->latest (key1.pub)));
}
{
// These rollbacks should fail
auto transaction = node->store.tx_begin_write ();
ASSERT_TRUE (node->ledger.rollback (transaction, node->latest (key1.pub)));
ASSERT_TRUE (node->ledger.rollback (transaction, node->latest (key2.pub)));
// Confirm the other latest can't be rolled back either
ASSERT_TRUE (node->ledger.rollback (transaction, node->latest (key3.pub)));
ASSERT_TRUE (node->ledger.rollback (transaction, node->latest (nano::dev::genesis_key.pub)));
// Attempt some others which have been cemented
ASSERT_TRUE (node->ledger.rollback (transaction, open1->hash ()));
ASSERT_TRUE (node->ledger.rollback (transaction, send2->hash ()));
}
ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in));
ASSERT_EQ (10, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out));
ASSERT_EQ (11, node->ledger.cache.cemented_count);
ASSERT_EQ (0, node->active.election_winner_details_size ());
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
TEST (confirmation_height, gap_bootstrap)
{
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
nano::test::system system{};
nano::node_flags node_flags{};
node_flags.confirmation_height_processor_mode = mode_a;
auto & node1 = *system.add_node (node_flags);
nano::keypair destination{};
nano::block_builder builder;
auto send1 = builder
.state ()
.account (nano::dev::genesis->account ())
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis->account ())
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
.link (destination.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
node1.work_generate_blocking (*send1);
auto send2 = builder
.state ()
.account (nano::dev::genesis->account ())
.previous (send1->hash ())
.representative (nano::dev::genesis->account ())
.balance (nano::dev::constants.genesis_amount - 2 * nano::Gxrb_ratio)
.link (destination.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
node1.work_generate_blocking (*send2);
auto send3 = builder
.state ()
.account (nano::dev::genesis->account ())
.previous (send2->hash ())
.representative (nano::dev::genesis->account ())
.balance (nano::dev::constants.genesis_amount - 3 * nano::Gxrb_ratio)
.link (destination.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
node1.work_generate_blocking (*send3);
auto open1 = builder
.open ()
.source (send1->hash ())
.representative (destination.pub)
.account (destination.pub)
.sign (destination.prv, destination.pub)
.work (0)
.build_shared ();
node1.work_generate_blocking (*open1);
// Receive
auto receive1 = builder
.receive ()
.previous (open1->hash ())
.source (send2->hash ())
.sign (destination.prv, destination.pub)
.work (0)
.build_shared ();
node1.work_generate_blocking (*receive1);
auto receive2 = builder
.receive ()
.previous (receive1->hash ())
.source (send3->hash ())
.sign (destination.prv, destination.pub)
.work (0)
.build_shared ();
node1.work_generate_blocking (*receive2);
node1.block_processor.add (send1);
node1.block_processor.add (send2);
node1.block_processor.add (send3);
node1.block_processor.add (receive1);
node1.block_processor.flush ();
add_callback_stats (node1);
// Receive 2 comes in on the live network, however the chain has not been finished so it gets added to unchecked
node1.process_active (receive2);
// Waits for the unchecked_map to process the 4 blocks added to the block_processor, saving them in the unchecked table
auto check_block_is_listed = [&] (nano::transaction const & transaction_a, nano::block_hash const & block_hash_a) {
return !node1.unchecked.get (transaction_a, block_hash_a).empty ();
};
ASSERT_TIMELY (15s, check_block_is_listed (node1.store.tx_begin_read (), receive2->previous ()));
// Confirmation heights should not be updated
{
auto transaction (node1.store.tx_begin_read ());
auto unchecked_count (node1.unchecked.count (transaction));
ASSERT_EQ (unchecked_count, 2);
nano::confirmation_height_info confirmation_height_info;
ASSERT_FALSE (node1.store.confirmation_height.get (transaction, nano::dev::genesis_key.pub, confirmation_height_info));
ASSERT_EQ (1, confirmation_height_info.height);
ASSERT_EQ (nano::dev::genesis->hash (), confirmation_height_info.frontier);
}
// Now complete the chain where the block comes in on the bootstrap network.
node1.block_processor.add (open1);
ASSERT_TIMELY (10s, node1.unchecked.count (node1.store.tx_begin_read ()) == 0);
// Confirmation height should be unchanged and unchecked should now be 0
{
auto transaction = node1.store.tx_begin_read ();
nano::confirmation_height_info confirmation_height_info;
ASSERT_FALSE (node1.store.confirmation_height.get (transaction, nano::dev::genesis_key.pub, confirmation_height_info));
ASSERT_EQ (1, confirmation_height_info.height);
ASSERT_EQ (nano::dev::genesis->hash (), confirmation_height_info.frontier);
ASSERT_TRUE (node1.store.confirmation_height.get (transaction, destination.pub, confirmation_height_info));
ASSERT_EQ (0, confirmation_height_info.height);
ASSERT_EQ (nano::block_hash (0), confirmation_height_info.frontier);
}
ASSERT_EQ (0, node1.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
ASSERT_EQ (0, node1.stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in));
ASSERT_EQ (0, node1.stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out));
ASSERT_EQ (1, node1.ledger.cache.cemented_count);
ASSERT_EQ (0, node1.active.election_winner_details_size ());
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
TEST (confirmation_height, gap_live)
{
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
nano::test::system system{};
nano::node_flags node_flags{};
node_flags.confirmation_height_processor_mode = mode_a;
nano::node_config node_config{ nano::test::get_available_port (), system.logging };
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto node = system.add_node (node_config, node_flags);
node_config.peering_port = nano::test::get_available_port ();
node_config.receive_minimum = nano::dev::constants.genesis_amount; // Prevent auto-receive & open1/receive1/receive2 blocks conflicts
system.add_node (node_config, node_flags);
nano::keypair destination;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
system.wallet (1)->insert_adhoc (destination.prv);
nano::block_builder builder;
auto send1 = builder
.state ()
.account (nano::dev::genesis->account ())
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis->account ())
.balance (nano::dev::constants.genesis_amount - 1)
.link (destination.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
node->work_generate_blocking (*send1);
auto send2 = builder
.state ()
.account (nano::dev::genesis->account ())
.previous (send1->hash ())
.representative (nano::dev::genesis->account ())
.balance (nano::dev::constants.genesis_amount - 2)
.link (destination.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
node->work_generate_blocking (*send2);
auto send3 = builder
.state ()
.account (nano::dev::genesis->account ())
.previous (send2->hash ())
.representative (nano::dev::genesis->account ())
.balance (nano::dev::constants.genesis_amount - 3)
.link (destination.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
node->work_generate_blocking (*send3);
auto open1 = builder
.open ()
.source (send1->hash ())
.representative (destination.pub)
.account (destination.pub)
.sign (destination.prv, destination.pub)
.work (0)
.build_shared ();
node->work_generate_blocking (*open1);
auto receive1 = builder
.receive ()
.previous (open1->hash ())
.source (send2->hash ())
.sign (destination.prv, destination.pub)
.work (0)
.build_shared ();
node->work_generate_blocking (*receive1);
auto receive2 = builder
.receive ()
.previous (receive1->hash ())
.source (send3->hash ())
.sign (destination.prv, destination.pub)
.work (0)
.build_shared ();
node->work_generate_blocking (*receive2);
node->block_processor.add (send1);
node->block_processor.add (send2);
node->block_processor.add (send3);
node->block_processor.add (receive1);
node->block_processor.flush ();
add_callback_stats (*node);
// Receive 2 comes in on the live network, however the chain has not been finished so it gets added to unchecked
node->process_active (receive2);
node->block_processor.flush ();
// Confirmation heights should not be updated
{
auto transaction = node->store.tx_begin_read ();
nano::confirmation_height_info confirmation_height_info;
ASSERT_FALSE (node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub, confirmation_height_info));
ASSERT_EQ (1, confirmation_height_info.height);
ASSERT_EQ (nano::dev::genesis->hash (), confirmation_height_info.frontier);
}
// Vote and confirm all existing blocks
node->block_confirm (send1);
ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) == 3);
// Now complete the chain where the block comes in on the live network
node->process_active (open1);
node->block_processor.flush ();
ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) == 6);
// This should confirm the open block and the source of the receive blocks
auto transaction = node->store.tx_begin_read ();
auto unchecked_count = node->unchecked.count (transaction);
ASSERT_EQ (unchecked_count, 0);
nano::confirmation_height_info confirmation_height_info{};
ASSERT_TRUE (node->ledger.block_confirmed (transaction, receive2->hash ()));
ASSERT_FALSE (node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub, confirmation_height_info));
ASSERT_EQ (4, confirmation_height_info.height);
ASSERT_EQ (send3->hash (), confirmation_height_info.frontier);
ASSERT_FALSE (node->store.confirmation_height.get (transaction, destination.pub, confirmation_height_info));
ASSERT_EQ (3, confirmation_height_info.height);
ASSERT_EQ (receive2->hash (), confirmation_height_info.frontier);
ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in));
ASSERT_EQ (6, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out));
ASSERT_EQ (7, node->ledger.cache.cemented_count);
ASSERT_EQ (0, node->active.election_winner_details_size ());
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
TEST (confirmation_height, send_receive_between_2_accounts)
{
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
nano::test::system system;
nano::node_flags node_flags;
node_flags.confirmation_height_processor_mode = mode_a;
nano::node_config node_config (nano::test::get_available_port (), system.logging);
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto node = system.add_node (node_config, node_flags);
nano::keypair key1;
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
nano::block_builder builder;
auto send1 = builder
.send ()
.previous (latest)
.destination (key1.pub)
.balance (node->online_reps.delta () + 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest))
.build ();
auto open1 = builder
.open ()
.source (send1->hash ())
.representative (nano::dev::genesis->account ())
.account (key1.pub)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1.pub))
.build ();
auto send2 = builder
.send ()
.previous (open1->hash ())
.destination (nano::dev::genesis->account ())
.balance (1000)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (open1->hash ()))
.build ();
auto send3 = builder
.send ()
.previous (send2->hash ())
.destination (nano::dev::genesis->account ())
.balance (900)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (send2->hash ()))
.build ();
auto send4 = builder
.send ()
.previous (send3->hash ())
.destination (nano::dev::genesis->account ())
.balance (500)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (send3->hash ()))
.build ();
auto receive1 = builder
.receive ()
.previous (send1->hash ())
.source (send2->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build ();
auto receive2 = builder
.receive ()
.previous (receive1->hash ())
.source (send3->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (receive1->hash ()))
.build ();
auto receive3 = builder
.receive ()
.previous (receive2->hash ())
.source (send4->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (receive2->hash ()))
.build ();
auto send5 = builder
.send ()
.previous (receive3->hash ())
.destination (key1.pub)
.balance (node->online_reps.delta () + 1)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (receive3->hash ()))
.build ();
auto receive4 = builder
.receive ()
.previous (send4->hash ())
.source (send5->hash ())
.sign (key1.prv, key1.pub)
.work (*system.work.generate (send4->hash ()))
.build_shared ();
nano::keypair key2;
auto send6 = builder
.send ()
.previous (send5->hash ())
.destination (key2.pub)
.balance (node->online_reps.delta () + 1)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send5->hash ()))
.build ();
// Unpocketed send
{
auto transaction = node->store.tx_begin_write ();
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *open1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send3).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send4).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive3).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send5).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send6).code);
}
add_callback_stats (*node);
node->process_active (receive4);
node->block_processor.flush ();
node->block_confirm (receive4);
auto election = node->active.election (receive4->qualified_root ());
ASSERT_NE (nullptr, election);
election->force_confirm ();
ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) == 10);
auto transaction (node->store.tx_begin_read ());
ASSERT_TRUE (node->ledger.block_confirmed (transaction, receive4->hash ()));
nano::account_info account_info;
nano::confirmation_height_info confirmation_height_info;
ASSERT_FALSE (node->store.account.get (transaction, nano::dev::genesis_key.pub, account_info));
ASSERT_FALSE (node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub, confirmation_height_info));
ASSERT_EQ (6, confirmation_height_info.height);
ASSERT_EQ (send5->hash (), confirmation_height_info.frontier);
ASSERT_EQ (7, account_info.block_count);
ASSERT_FALSE (node->store.account.get (transaction, key1.pub, account_info));
ASSERT_FALSE (node->store.confirmation_height.get (transaction, key1.pub, confirmation_height_info));
ASSERT_EQ (5, confirmation_height_info.height);
ASSERT_EQ (receive4->hash (), confirmation_height_info.frontier);
ASSERT_EQ (5, account_info.block_count);
ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in));
ASSERT_EQ (10, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out));
ASSERT_EQ (11, node->ledger.cache.cemented_count);
ASSERT_EQ (0, node->active.election_winner_details_size ());
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
TEST (confirmation_height, send_receive_self)
{
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
nano::test::system system;
nano::node_flags node_flags;
node_flags.confirmation_height_processor_mode = mode_a;
nano::node_config node_config (nano::test::get_available_port (), system.logging);
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto node = system.add_node (node_config, node_flags);
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
nano::block_builder builder;
auto send1 = builder
.send ()
.previous (latest)
.destination (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest))
.build ();
auto receive1 = builder
.receive ()
.previous (send1->hash ())
.source (send1->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build_shared ();
auto send2 = builder
.send ()
.previous (receive1->hash ())
.destination (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (receive1->hash ()))
.build ();
auto send3 = builder
.send ()
.previous (send2->hash ())
.destination (nano::dev::genesis_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 receive2 = builder
.receive ()
.previous (send3->hash ())
.source (send2->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send3->hash ()))
.build ();
auto receive3 = builder
.receive ()
.previous (receive2->hash ())
.source (send3->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (receive2->hash ()))
.build_shared ();
// Send to another account to prevent automatic receiving on the genesis account
nano::keypair key1;
auto send4 = builder
.send ()
.previous (receive3->hash ())
.destination (key1.pub)
.balance (node->online_reps.delta ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (receive3->hash ()))
.build ();
{
auto transaction = node->store.tx_begin_write ();
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send3).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive3).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send4).code);
}
add_callback_stats (*node);
node->block_confirm (receive3);
auto election = node->active.election (receive3->qualified_root ());
ASSERT_NE (nullptr, election);
election->force_confirm ();
ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) == 6);
auto transaction (node->store.tx_begin_read ());
ASSERT_TRUE (node->ledger.block_confirmed (transaction, receive3->hash ()));
nano::account_info account_info;
ASSERT_FALSE (node->store.account.get (transaction, nano::dev::genesis_key.pub, account_info));
nano::confirmation_height_info confirmation_height_info;
ASSERT_FALSE (node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub, confirmation_height_info));
ASSERT_EQ (7, confirmation_height_info.height);
ASSERT_EQ (receive3->hash (), confirmation_height_info.frontier);
ASSERT_EQ (8, account_info.block_count);
ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in));
ASSERT_EQ (6, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out));
ASSERT_EQ (confirmation_height_info.height, node->ledger.cache.cemented_count);
ASSERT_EQ (0, node->active.election_winner_details_size ());
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
TEST (confirmation_height, all_block_types)
{
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
nano::test::system system;
nano::node_flags node_flags;
node_flags.confirmation_height_processor_mode = mode_a;
nano::node_config node_config (nano::test::get_available_port (), system.logging);
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto node = system.add_node (node_config, node_flags);
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
nano::keypair key1;
nano::keypair key2;
auto & store = node->store;
nano::block_builder builder;
auto send = builder
.send ()
.previous (latest)
.destination (key1.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 (latest))
.build ();
auto send1 = builder
.send ()
.previous (send->hash ())
.destination (key2.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send->hash ()))
.build ();
auto open = builder
.open ()
.source (send->hash ())
.representative (nano::dev::genesis_key.pub)
.account (key1.pub)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1.pub))
.build ();
auto state_open = builder
.state ()
.account (key2.pub)
.previous (0)
.representative (0)
.balance (nano::Gxrb_ratio)
.link (send1->hash ())
.sign (key2.prv, key2.pub)
.work (*system.work.generate (key2.pub))
.build ();
auto send2 = builder
.send ()
.previous (open->hash ())
.destination (key2.pub)
.balance (0)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (open->hash ()))
.build ();
auto state_receive = builder
.state ()
.account (key2.pub)
.previous (state_open->hash ())
.representative (0)
.balance (nano::Gxrb_ratio * 2)
.link (send2->hash ())
.sign (key2.prv, key2.pub)
.work (*system.work.generate (state_open->hash ()))
.build ();
auto state_send = builder
.state ()
.account (key2.pub)
.previous (state_receive->hash ())
.representative (0)
.balance (nano::Gxrb_ratio)
.link (key1.pub)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (state_receive->hash ()))
.build ();
auto receive = builder
.receive ()
.previous (send2->hash ())
.source (state_send->hash ())
.sign (key1.prv, key1.pub)
.work (*system.work.generate (send2->hash ()))
.build ();
auto change = builder
.change ()
.previous (receive->hash ())
.representative (key2.pub)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (receive->hash ()))
.build ();
auto state_change = builder
.state ()
.account (key2.pub)
.previous (state_send->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::Gxrb_ratio)
.link (0)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (state_send->hash ()))
.build ();
auto epoch = builder
.state ()
.account (key2.pub)
.previous (state_change->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::Gxrb_ratio)
.link (node->ledger.epoch_link (nano::epoch::epoch_1))
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (state_change->hash ()))
.build ();
auto epoch1 = builder
.state ()
.account (key1.pub)
.previous (change->hash ())
.representative (key2.pub)
.balance (nano::Gxrb_ratio)
.link (node->ledger.epoch_link (nano::epoch::epoch_1))
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (change->hash ()))
.build ();
auto state_send1 = builder
.state ()
.account (key1.pub)
.previous (epoch1->hash ())
.representative (0)
.balance (nano::Gxrb_ratio - 1)
.link (key2.pub)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (epoch1->hash ()))
.build ();
auto state_receive2 = builder
.state ()
.account (key2.pub)
.previous (epoch->hash ())
.representative (0)
.balance (nano::Gxrb_ratio + 1)
.link (state_send1->hash ())
.sign (key2.prv, key2.pub)
.work (*system.work.generate (epoch->hash ()))
.build ();
auto state_send2 = builder
.state ()
.account (key2.pub)
.previous (state_receive2->hash ())
.representative (0)
.balance (nano::Gxrb_ratio)
.link (key1.pub)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (state_receive2->hash ()))
.build_shared ();
auto state_send3 = builder
.state ()
.account (key2.pub)
.previous (state_send2->hash ())
.representative (0)
.balance (nano::Gxrb_ratio - 1)
.link (key1.pub)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (state_send2->hash ()))
.build ();
auto state_send4 = builder
.state ()
.account (key1.pub)
.previous (state_send1->hash ())
.representative (0)
.balance (nano::Gxrb_ratio - 2)
.link (nano::dev::genesis_key.pub)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (state_send1->hash ()))
.build ();
auto state_receive3 = builder
.state ()
.account (nano::dev::genesis->account ())
.previous (send1->hash ())
.representative (nano::dev::genesis->account ())
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 2 + 1)
.link (state_send4->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build ();
{
auto transaction (store.tx_begin_write ());
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *open).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *state_open).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *state_receive).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *state_send).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *change).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *state_change).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *epoch).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *epoch1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *state_send1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *state_receive2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *state_send2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *state_send3).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *state_send4).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *state_receive3).code);
}
add_callback_stats (*node);
node->block_confirm (state_send2);
auto election = node->active.election (state_send2->qualified_root ());
ASSERT_NE (nullptr, election);
election->force_confirm ();
ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) == 15);
auto transaction (node->store.tx_begin_read ());
ASSERT_TRUE (node->ledger.block_confirmed (transaction, state_send2->hash ()));
nano::account_info account_info;
nano::confirmation_height_info confirmation_height_info;
ASSERT_FALSE (node->store.account.get (transaction, nano::dev::genesis_key.pub, account_info));
ASSERT_FALSE (node->store.confirmation_height.get (transaction, nano::dev::genesis_key.pub, confirmation_height_info));
ASSERT_EQ (3, confirmation_height_info.height);
ASSERT_EQ (send1->hash (), confirmation_height_info.frontier);
ASSERT_LE (4, account_info.block_count);
ASSERT_FALSE (node->store.account.get (transaction, key1.pub, account_info));
ASSERT_FALSE (node->store.confirmation_height.get (transaction, key1.pub, confirmation_height_info));
ASSERT_EQ (state_send1->hash (), confirmation_height_info.frontier);
ASSERT_EQ (6, confirmation_height_info.height);
ASSERT_LE (7, account_info.block_count);
ASSERT_FALSE (node->store.account.get (transaction, key2.pub, account_info));
ASSERT_FALSE (node->store.confirmation_height.get (transaction, key2.pub, confirmation_height_info));
ASSERT_EQ (7, confirmation_height_info.height);
ASSERT_EQ (state_send2->hash (), confirmation_height_info.frontier);
ASSERT_LE (8, account_info.block_count);
ASSERT_EQ (15, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
ASSERT_EQ (15, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in));
ASSERT_EQ (15, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out));
ASSERT_EQ (16, node->ledger.cache.cemented_count);
ASSERT_EQ (0, node->active.election_winner_details_size ());
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
// this test cements a block on one node and another block on another node
// it therefore tests that once a block is confirmed it cannot be rolled back
// and if both nodes have different branches of the fork cemented then it is a permanent fork
TEST (confirmation_height, conflict_rollback_cemented)
{
// functor to perform the conflict_rollback_cemented test using a certain mode
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
nano::state_block_builder builder{};
auto const genesis_hash = nano::dev::genesis->hash ();
nano::test::system system{};
nano::node_flags node_flags{};
node_flags.confirmation_height_processor_mode = mode_a;
// create node 1 and account key1 (no voting key yet)
auto node1 = system.add_node (node_flags);
nano::keypair key1{};
// create one side of a forked transaction on node1
auto send1 = builder.make_block ()
.previous (genesis_hash)
.account (nano::dev::genesis_key.pub)
.representative (nano::dev::genesis_key.pub)
.link (key1.pub)
.balance (nano::dev::constants.genesis_amount - 100)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (genesis_hash))
.build_shared ();
node1->process_active (send1);
ASSERT_TIMELY (5s, node1->active.election (send1->qualified_root ()) != nullptr);
// create the other side of the fork on node2
nano::keypair key2;
auto send2 = builder.make_block ()
.previous (genesis_hash)
.account (nano::dev::genesis_key.pub)
.representative (nano::dev::genesis_key.pub)
.link (key2.pub)
.balance (nano::dev::constants.genesis_amount - 100)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (genesis_hash))
.build_shared ();
// create node2, with send2 pre-initialised in the ledger so that block send1 cannot possibly get in the ledger first
system.initialization_blocks.push_back (send2);
auto node2 = system.add_node (node_flags);
system.initialization_blocks.clear ();
auto wallet1 = system.wallet (0);
node2->process_active (send2);
ASSERT_TIMELY (5s, node2->active.election (send2->qualified_root ()) != nullptr);
// force confirm send2 on node2
ASSERT_TIMELY (5s, node2->ledger.store.block.get (node2->ledger.store.tx_begin_read (), send2->hash ()));
node2->process_confirmed (nano::election_status{ send2 });
ASSERT_TIMELY (5s, node2->block_confirmed (send2->hash ()));
// make node1 a voting node (it has all the voting weight)
// from now on, node1 can vote for send1 at any time
wallet1->insert_adhoc (nano::dev::genesis_key.prv);
// we expect node1 to vote for one side of the fork only, whichever side
std::shared_ptr<nano::election> election_send1_node1{};
ASSERT_EQ (send1->qualified_root (), send2->qualified_root ());
ASSERT_TIMELY (5s, (election_send1_node1 = node1->active.election (send1->qualified_root ())) != nullptr);
ASSERT_TIMELY (5s, 2 == election_send1_node1->votes ().size ());
// check that the send1 on node1 won the election and got confirmed
// this happens because send1 is seen first by node1, and therefore it already winning and it cannot replaced by send2
ASSERT_TIMELY (5s, election_send1_node1->confirmed ());
auto const winner = election_send1_node1->winner ();
ASSERT_NE (nullptr, winner);
ASSERT_EQ (*winner, *send1);
// node2 already has send2 forced confirmed whilst node1 should have confirmed send1 and therefore we have a cemented fork on node2
// and node2 should print an error message on the log that it cannot rollback send2 because it is already cemented
ASSERT_TIMELY (5s, 1 == node2->stats.count (nano::stat::type::ledger, nano::stat::detail::rollback_failed));
// get the tally for election the election on node1
// we expect the winner to be send1 and we expect send1 to have "genesis balance" vote weight
auto const tally = election_send1_node1->tally ();
ASSERT_FALSE (tally.empty ());
auto const & [amount, winner_alias] = *tally.begin ();
ASSERT_EQ (*winner_alias, *send1);
ASSERT_EQ (amount, nano::dev::constants.genesis_amount - 100);
// we expect send1 to exist on node1, is that because send2 is rolled back?
ASSERT_TRUE (node1->ledger.block_or_pruned_exists (send1->hash ()));
ASSERT_FALSE (node1->ledger.block_or_pruned_exists (send2->hash ()));
// we expect only send2 to be existing on node2
ASSERT_TRUE (node2->ledger.block_or_pruned_exists (send2->hash ()));
ASSERT_FALSE (node2->ledger.block_or_pruned_exists (send1->hash ()));
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
TEST (confirmation_heightDeathTest, rollback_added_block)
{
if (nano::rocksdb_config::using_rocksdb_in_tests ())
{
// Don't test this in rocksdb mode
return;
}
// For ASSERT_DEATH_IF_SUPPORTED
testing::FLAGS_gtest_death_test_style = "threadsafe";
// valgrind can be noisy with death tests
if (!nano::running_within_valgrind ())
{
nano::logger_mt logger;
nano::logging logging;
auto path (nano::unique_path ());
auto store = nano::make_store (logger, path, nano::dev::constants);
ASSERT_TRUE (!store->init_error ());
nano::stat stats;
nano::ledger ledger (*store, stats, nano::dev::constants);
nano::write_database_queue write_database_queue (false);
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
nano::keypair key1;
nano::block_builder builder;
auto send = builder
.send ()
.previous (nano::dev::genesis->hash ())
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*pool.generate (nano::dev::genesis->hash ()))
.build_shared ();
{
auto transaction (store->tx_begin_write ());
store->initialize (transaction, ledger.cache, ledger.constants);
}
uint64_t batch_write_size = 2048;
std::atomic<bool> stopped{ false };
nano::confirmation_height_unbounded unbounded_processor (
ledger, write_database_queue, 10ms, logging, logger, stopped, batch_write_size, [] (auto const &) {}, [] (auto const &) {}, [] () { return 0; });
// Processing a block which doesn't exist should bail
ASSERT_DEATH_IF_SUPPORTED (unbounded_processor.process (send), "");
nano::confirmation_height_bounded bounded_processor (
ledger, write_database_queue, 10ms, logging, logger, stopped, batch_write_size, [] (auto const &) {}, [] (auto const &) {}, [] () { return 0; });
// Processing a block which doesn't exist should bail
ASSERT_DEATH_IF_SUPPORTED (bounded_processor.process (send), "");
}
}
TEST (confirmation_height, observers)
{
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
auto amount (std::numeric_limits<nano::uint128_t>::max ());
nano::test::system system;
nano::node_flags node_flags;
node_flags.confirmation_height_processor_mode = mode_a;
auto node1 = system.add_node (node_flags);
nano::keypair key1;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::block_hash latest1 (node1->latest (nano::dev::genesis_key.pub));
nano::block_builder builder;
auto send1 = builder
.send ()
.previous (latest1)
.destination (key1.pub)
.balance (amount - node1->config.receive_minimum.number ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest1))
.build_shared ();
add_callback_stats (*node1);
node1->process_active (send1);
node1->block_processor.flush ();
ASSERT_TIMELY (10s, node1->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) == 1);
auto transaction = node1->store.tx_begin_read ();
ASSERT_TRUE (node1->ledger.block_confirmed (transaction, send1->hash ()));
ASSERT_EQ (1, node1->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
ASSERT_EQ (1, node1->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in));
ASSERT_EQ (1, node1->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out));
ASSERT_EQ (2, node1->ledger.cache.cemented_count);
ASSERT_EQ (0, node1->active.election_winner_details_size ());
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
// This tests when a read has been done, but the block no longer exists by the time a write is done
TEST (confirmation_heightDeathTest, modified_chain)
{
if (nano::rocksdb_config::using_rocksdb_in_tests ())
{
// Don't test this in rocksdb mode
return;
}
// For ASSERT_DEATH_IF_SUPPORTED
testing::FLAGS_gtest_death_test_style = "threadsafe";
// valgrind can be noisy with death tests
if (!nano::running_within_valgrind ())
{
nano::logging logging;
nano::logger_mt logger;
auto path (nano::unique_path ());
auto store = nano::make_store (logger, path, nano::dev::constants);
ASSERT_TRUE (!store->init_error ());
nano::stat stats;
nano::ledger ledger (*store, stats, nano::dev::constants);
nano::write_database_queue write_database_queue (false);
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
nano::keypair key1;
nano::block_builder builder;
auto send = builder
.send ()
.previous (nano::dev::genesis->hash ())
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*pool.generate (nano::dev::genesis->hash ()))
.build_shared ();
{
auto transaction (store->tx_begin_write ());
store->initialize (transaction, ledger.cache, ledger.constants);
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *send).code);
}
uint64_t batch_write_size = 2048;
std::atomic<bool> stopped{ false };
nano::confirmation_height_bounded bounded_processor (
ledger, write_database_queue, 10ms, logging, logger, stopped, batch_write_size, [] (auto const &) {}, [] (auto const &) {}, [] () { return 0; });
{
// This reads the blocks in the account, but prevents any writes from occuring yet
auto scoped_write_guard = write_database_queue.wait (nano::writer::testing);
bounded_processor.process (send);
}
// Rollback the block and now try to write, the block no longer exists so should bail
ledger.rollback (store->tx_begin_write (), send->hash ());
{
auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height);
ASSERT_DEATH_IF_SUPPORTED (bounded_processor.cement_blocks (scoped_write_guard), "");
}
ASSERT_EQ (nano::process_result::progress, ledger.process (store->tx_begin_write (), *send).code);
store->confirmation_height.put (store->tx_begin_write (), nano::dev::genesis->account (), { 1, nano::dev::genesis->hash () });
nano::confirmation_height_unbounded unbounded_processor (
ledger, write_database_queue, 10ms, logging, logger, stopped, batch_write_size, [] (auto const &) {}, [] (auto const &) {}, [] () { return 0; });
{
// This reads the blocks in the account, but prevents any writes from occuring yet
auto scoped_write_guard = write_database_queue.wait (nano::writer::testing);
unbounded_processor.process (send);
}
// Rollback the block and now try to write, the block no longer exists so should bail
ledger.rollback (store->tx_begin_write (), send->hash ());
{
auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height);
ASSERT_DEATH_IF_SUPPORTED (unbounded_processor.cement_blocks (scoped_write_guard), "");
}
}
}
// This tests when a read has been done, but the account no longer exists by the time a write is done
TEST (confirmation_heightDeathTest, modified_chain_account_removed)
{
if (nano::rocksdb_config::using_rocksdb_in_tests ())
{
// Don't test this in rocksdb mode
return;
}
// For ASSERT_DEATH_IF_SUPPORTED
testing::FLAGS_gtest_death_test_style = "threadsafe";
// valgrind can be noisy with death tests
if (!nano::running_within_valgrind ())
{
nano::logging logging;
nano::logger_mt logger;
auto path (nano::unique_path ());
auto store = nano::make_store (logger, path, nano::dev::constants);
ASSERT_TRUE (!store->init_error ());
nano::stat stats;
nano::ledger ledger (*store, stats, nano::dev::constants);
nano::write_database_queue write_database_queue (false);
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
nano::keypair key1;
nano::block_builder builder;
auto send = builder
.send ()
.previous (nano::dev::genesis->hash ())
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*pool.generate (nano::dev::genesis->hash ()))
.build_shared ();
auto open = builder
.state ()
.account (key1.pub)
.previous (0)
.representative (0)
.balance (nano::Gxrb_ratio)
.link (send->hash ())
.sign (key1.prv, key1.pub)
.work (*pool.generate (key1.pub))
.build_shared ();
{
auto transaction (store->tx_begin_write ());
store->initialize (transaction, ledger.cache, ledger.constants);
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *send).code);
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *open).code);
}
uint64_t batch_write_size = 2048;
std::atomic<bool> stopped{ false };
nano::confirmation_height_unbounded unbounded_processor (
ledger, write_database_queue, 10ms, logging, logger, stopped, batch_write_size, [] (auto const &) {}, [] (auto const &) {}, [] () { return 0; });
{
// This reads the blocks in the account, but prevents any writes from occuring yet
auto scoped_write_guard = write_database_queue.wait (nano::writer::testing);
unbounded_processor.process (open);
}
// Rollback the block and now try to write, the send should be cemented but the account which the open block belongs no longer exists so should bail
ledger.rollback (store->tx_begin_write (), open->hash ());
{
auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height);
ASSERT_DEATH_IF_SUPPORTED (unbounded_processor.cement_blocks (scoped_write_guard), "");
}
// Reset conditions and test with the bounded processor
ASSERT_EQ (nano::process_result::progress, ledger.process (store->tx_begin_write (), *open).code);
store->confirmation_height.put (store->tx_begin_write (), nano::dev::genesis->account (), { 1, nano::dev::genesis->hash () });
nano::confirmation_height_bounded bounded_processor (
ledger, write_database_queue, 10ms, logging, logger, stopped, batch_write_size, [] (auto const &) {}, [] (auto const &) {}, [] () { return 0; });
{
// This reads the blocks in the account, but prevents any writes from occuring yet
auto scoped_write_guard = write_database_queue.wait (nano::writer::testing);
bounded_processor.process (open);
}
// Rollback the block and now try to write, the send should be cemented but the account which the open block belongs no longer exists so should bail
ledger.rollback (store->tx_begin_write (), open->hash ());
auto scoped_write_guard = write_database_queue.wait (nano::writer::confirmation_height);
ASSERT_DEATH_IF_SUPPORTED (bounded_processor.cement_blocks (scoped_write_guard), "");
}
}
namespace nano
{
TEST (confirmation_height, pending_observer_callbacks)
{
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
nano::test::system system;
nano::node_flags node_flags;
node_flags.confirmation_height_processor_mode = mode_a;
nano::node_config node_config (nano::test::get_available_port (), system.logging);
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto node = system.add_node (node_config, node_flags);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
nano::keypair key1;
nano::block_builder builder;
auto send = builder
.send ()
.previous (latest)
.destination (key1.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 (latest))
.build ();
auto send1 = builder
.send ()
.previous (send->hash ())
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send->hash ()))
.build_shared ();
{
auto transaction = node->store.tx_begin_write ();
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code);
}
add_callback_stats (*node);
node->confirmation_height_processor.add (send1);
// Confirm the callback is not called under this circumstance because there is no election information
ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) == 1 && node->ledger.stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::all, nano::stat::dir::out) == 1);
ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in));
ASSERT_EQ (3, node->ledger.cache.cemented_count);
ASSERT_EQ (0, node->active.election_winner_details_size ());
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
}
// The callback and confirmation history should only be updated after confirmation height is set (and not just after voting)
TEST (confirmation_height, callback_confirmed_history)
{
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
nano::test::system system;
nano::node_flags node_flags;
node_flags.force_use_write_database_queue = true;
node_flags.confirmation_height_processor_mode = mode_a;
nano::node_config node_config (nano::test::get_available_port (), system.logging);
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto node = system.add_node (node_config, node_flags);
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
nano::keypair key1;
nano::block_builder builder;
auto send = builder
.send ()
.previous (latest)
.destination (key1.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 (latest))
.build_shared ();
{
auto transaction = node->store.tx_begin_write ();
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code);
}
auto send1 = builder
.send ()
.previous (send->hash ())
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send->hash ()))
.build_shared ();
add_callback_stats (*node);
node->process_active (send1);
node->block_processor.flush ();
node->block_confirm (send1);
{
node->process_active (send);
node->block_processor.flush ();
// The write guard prevents the confirmation height processor doing any writes
auto write_guard = node->write_database_queue.wait (nano::writer::testing);
// Confirm send1
auto election = node->active.election (send1->qualified_root ());
ASSERT_NE (nullptr, election);
election->force_confirm ();
ASSERT_TIMELY (10s, node->active.size () == 0);
ASSERT_EQ (0, node->active.list_recently_cemented ().size ());
{
nano::lock_guard<nano::mutex> guard (node->active.mutex);
ASSERT_EQ (0, node->active.blocks.size ());
}
auto transaction = node->store.tx_begin_read ();
ASSERT_FALSE (node->ledger.block_confirmed (transaction, send->hash ()));
ASSERT_TIMELY (10s, node->write_database_queue.contains (nano::writer::confirmation_height));
// Confirm that no inactive callbacks have been called when the confirmation height processor has already iterated over it, waiting to write
ASSERT_EQ (0, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out));
}
ASSERT_TIMELY (10s, !node->write_database_queue.contains (nano::writer::confirmation_height));
auto transaction = node->store.tx_begin_read ();
ASSERT_TRUE (node->ledger.block_confirmed (transaction, send->hash ()));
ASSERT_TIMELY (10s, node->active.size () == 0);
ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out) == 1);
ASSERT_EQ (1, node->active.list_recently_cemented ().size ());
ASSERT_EQ (0, node->active.blocks.size ());
// Confirm the callback is not called under this circumstance
ASSERT_EQ (2, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out));
ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out));
ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out));
ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in));
ASSERT_EQ (3, node->ledger.cache.cemented_count);
ASSERT_EQ (0, node->active.election_winner_details_size ());
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
namespace nano
{
TEST (confirmation_height, dependent_election)
{
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
nano::test::system system;
nano::node_flags node_flags;
node_flags.confirmation_height_processor_mode = mode_a;
node_flags.force_use_write_database_queue = true;
nano::node_config node_config (nano::test::get_available_port (), system.logging);
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto node = system.add_node (node_config, node_flags);
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
nano::keypair key1;
nano::block_builder builder;
auto send = builder
.send ()
.previous (latest)
.destination (key1.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 (latest))
.build_shared ();
auto send1 = builder
.send ()
.previous (send->hash ())
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send->hash ()))
.build_shared ();
auto send2 = builder
.send ()
.previous (send1->hash ())
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 3)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build_shared ();
{
auto transaction = node->store.tx_begin_write ();
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send2).code);
}
add_callback_stats (*node);
// This election should be confirmed as active_conf_height
node->block_confirm (send1);
// Start an election and confirm it
node->block_confirm (send2);
auto election = node->active.election (send2->qualified_root ());
ASSERT_NE (nullptr, election);
election->force_confirm ();
ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) == 3);
ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_quorum, nano::stat::dir::out));
ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::active_conf_height, nano::stat::dir::out));
ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out));
ASSERT_EQ (3, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
ASSERT_EQ (3, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in));
ASSERT_EQ (4, node->ledger.cache.cemented_count);
ASSERT_EQ (0, node->active.election_winner_details_size ());
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
// This test checks that a receive block with uncemented blocks below cements them too.
TEST (confirmation_height, cemented_gap_below_receive)
{
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
nano::test::system system;
nano::node_flags node_flags;
node_flags.confirmation_height_processor_mode = mode_a;
nano::node_config node_config (nano::test::get_available_port (), system.logging);
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto node = system.add_node (node_config, node_flags);
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
nano::keypair key1;
nano::block_builder builder;
system.wallet (0)->insert_adhoc (key1.prv);
auto send = builder
.send ()
.previous (latest)
.destination (key1.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 (latest))
.build ();
auto send1 = builder
.send ()
.previous (send->hash ())
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send->hash ()))
.build ();
nano::keypair dummy_key;
auto dummy_send = builder
.send ()
.previous (send1->hash ())
.destination (dummy_key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 3)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build ();
auto open = builder
.open ()
.source (send->hash ())
.representative (nano::dev::genesis->account ())
.account (key1.pub)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1.pub))
.build ();
auto receive1 = builder
.receive ()
.previous (open->hash ())
.source (send1->hash ())
.sign (key1.prv, key1.pub)
.work (*system.work.generate (open->hash ()))
.build ();
auto send2 = builder
.send ()
.previous (receive1->hash ())
.destination (nano::dev::genesis_key.pub)
.balance (nano::Gxrb_ratio)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (receive1->hash ()))
.build ();
auto receive2 = builder
.receive ()
.previous (dummy_send->hash ())
.source (send2->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (dummy_send->hash ()))
.build ();
auto dummy_send1 = builder
.send ()
.previous (receive2->hash ())
.destination (dummy_key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 3)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (receive2->hash ()))
.build ();
nano::keypair key2;
system.wallet (0)->insert_adhoc (key2.prv);
auto send3 = builder
.send ()
.previous (dummy_send1->hash ())
.destination (key2.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 4)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (dummy_send1->hash ()))
.build ();
auto dummy_send2 = builder
.send ()
.previous (send3->hash ())
.destination (dummy_key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 5)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send3->hash ()))
.build ();
auto open1 = builder
.open ()
.source (send3->hash ())
.representative (nano::dev::genesis->account ())
.account (key2.pub)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (key2.pub))
.build_shared ();
{
auto transaction = node->store.tx_begin_write ();
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *dummy_send).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *open).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *dummy_send1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send3).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *dummy_send2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *open1).code);
}
std::vector<nano::block_hash> observer_order;
nano::mutex mutex;
add_callback_stats (*node, &observer_order, &mutex);
node->block_confirm (open1);
auto election = node->active.election (open1->qualified_root ());
ASSERT_NE (nullptr, election);
election->force_confirm ();
ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) == 10);
auto transaction = node->store.tx_begin_read ();
ASSERT_TRUE (node->ledger.block_confirmed (transaction, open1->hash ()));
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));
ASSERT_EQ (9, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out));
ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in));
ASSERT_EQ (11, node->ledger.cache.cemented_count);
ASSERT_EQ (0, node->active.election_winner_details_size ());
// Check that the order of callbacks is correct
std::vector<nano::block_hash> expected_order = { send->hash (), open->hash (), send1->hash (), receive1->hash (), send2->hash (), dummy_send->hash (), receive2->hash (), dummy_send1->hash (), send3->hash (), open1->hash () };
nano::lock_guard<nano::mutex> guard (mutex);
ASSERT_EQ (observer_order, expected_order);
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
// This test checks that a receive block with uncemented blocks below cements them too, compared with the test above, this
// is the first write in this chain.
TEST (confirmation_height, cemented_gap_below_no_cache)
{
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
nano::test::system system;
nano::node_flags node_flags;
node_flags.confirmation_height_processor_mode = mode_a;
nano::node_config node_config (nano::test::get_available_port (), system.logging);
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto node = system.add_node (node_config, node_flags);
nano::block_hash latest (node->latest (nano::dev::genesis_key.pub));
nano::keypair key1;
system.wallet (0)->insert_adhoc (key1.prv);
nano::block_builder builder;
auto send = builder
.send ()
.previous (latest)
.destination (key1.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 (latest))
.build ();
auto send1 = builder
.send ()
.previous (send->hash ())
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send->hash ()))
.build ();
nano::keypair dummy_key;
auto dummy_send = builder
.send ()
.previous (send1->hash ())
.destination (dummy_key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 3)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build ();
auto open = builder
.open ()
.source (send->hash ())
.representative (nano::dev::genesis->account ())
.account (key1.pub)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1.pub))
.build ();
auto receive1 = builder
.receive ()
.previous (open->hash ())
.source (send1->hash ())
.sign (key1.prv, key1.pub)
.work (*system.work.generate (open->hash ()))
.build ();
auto send2 = builder
.send ()
.previous (receive1->hash ())
.destination (nano::dev::genesis_key.pub)
.balance (nano::Gxrb_ratio)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (receive1->hash ()))
.build ();
auto receive2 = builder
.receive ()
.previous (dummy_send->hash ())
.source (send2->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (dummy_send->hash ()))
.build ();
auto dummy_send1 = builder
.send ()
.previous (receive2->hash ())
.destination (dummy_key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 3)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (receive2->hash ()))
.build ();
nano::keypair key2;
system.wallet (0)->insert_adhoc (key2.prv);
auto send3 = builder
.send ()
.previous (dummy_send1->hash ())
.destination (key2.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 4)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (dummy_send1->hash ()))
.build ();
auto dummy_send2 = builder
.send ()
.previous (send3->hash ())
.destination (dummy_key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 5)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send3->hash ()))
.build ();
auto open1 = builder
.open ()
.source (send3->hash ())
.representative (nano::dev::genesis->account ())
.account (key2.pub)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (key2.pub))
.build_shared ();
{
auto transaction = node->store.tx_begin_write ();
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *dummy_send).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *open).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *dummy_send1).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send3).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *dummy_send2).code);
ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *open1).code);
}
// Force some blocks to be cemented so that the cached confirmed info variable is empty
{
auto transaction (node->store.tx_begin_write ());
node->store.confirmation_height.put (transaction, nano::dev::genesis->account (), nano::confirmation_height_info{ 3, send1->hash () });
node->store.confirmation_height.put (transaction, key1.pub, nano::confirmation_height_info{ 2, receive1->hash () });
}
add_callback_stats (*node);
node->block_confirm (open1);
auto election = node->active.election (open1->qualified_root ());
ASSERT_NE (nullptr, election);
election->force_confirm ();
ASSERT_TIMELY (10s, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out) == 6);
auto transaction = node->store.tx_begin_read ();
ASSERT_TRUE (node->ledger.block_confirmed (transaction, open1->hash ()));
ASSERT_EQ (node->active.election_winner_details_size (), 0);
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));
ASSERT_EQ (5, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out));
ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in));
ASSERT_EQ (7, node->ledger.cache.cemented_count);
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
TEST (confirmation_height, election_winner_details_clearing)
{
auto test_mode = [] (nano::confirmation_height_mode mode_a) {
nano::test::system system{};
nano::node_flags node_flags{};
node_flags.confirmation_height_processor_mode = mode_a;
nano::node_config node_config{ nano::test::get_available_port (), system.logging };
node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled;
auto node = system.add_node (node_config, node_flags);
auto const latest = node->latest (nano::dev::genesis_key.pub);
nano::keypair key1{};
nano::send_block_builder builder{};
auto const send1 = builder.make_block ()
.previous (latest)
.destination (key1.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 (latest))
.build_shared ();
ASSERT_EQ (nano::process_result::progress, node->process (*send1).code);
auto const send2 = builder.make_block ()
.previous (send1->hash ())
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build_shared ();
ASSERT_EQ (nano::process_result::progress, node->process (*send2).code);
auto const send3 = builder.make_block ()
.previous (send2->hash ())
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 3)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send2->hash ()))
.build_shared ();
ASSERT_EQ (nano::process_result::progress, node->process (*send3).code);
node->process_confirmed (nano::election_status{ send2 });
ASSERT_TIMELY (5s, node->block_confirmed (send2->hash ()));
ASSERT_TIMELY (5s, 1 == node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out));
node->process_confirmed (nano::election_status{ send3 });
ASSERT_TIMELY (5s, node->block_confirmed (send3->hash ()));
// Add an already cemented block with fake election details. It should get removed
node->active.add_election_winner_details (send3->hash (), nullptr);
node->confirmation_height_processor.add (send3);
ASSERT_TIMELY (10s, node->active.election_winner_details_size () == 0);
ASSERT_EQ (4, node->ledger.cache.cemented_count);
EXPECT_EQ (1, node->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out));
EXPECT_EQ (3, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
EXPECT_EQ (3, node->stats.count (nano::stat::type::confirmation_height, get_stats_detail (mode_a), nano::stat::dir::in));
};
test_mode (nano::confirmation_height_mode::bounded);
test_mode (nano::confirmation_height_mode::unbounded);
}
}
TEST (confirmation_height, election_winner_details_clearing_node_process_confirmed)
{
// Make sure election_winner_details is also cleared if the block never enters the confirmation height processor from node::process_confirmed
nano::test::system system (1);
auto node = system.nodes.front ();
nano::block_builder builder;
auto send = builder
.send ()
.previous (nano::dev::genesis->hash ())
.destination (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 ();
// Add to election_winner_details. Use an unrealistic iteration so that it should fall into the else case and do a cleanup
node->active.add_election_winner_details (send->hash (), nullptr);
nano::election_status election;
election.winner = send;
node->process_confirmed (election, 1000000);
ASSERT_EQ (0, node->active.election_winner_details_size ());
}
TEST (confirmation_height, unbounded_block_cache_iteration)
{
if (nano::rocksdb_config::using_rocksdb_in_tests ())
{
// Don't test this in rocksdb mode
return;
}
nano::logger_mt logger;
auto path (nano::unique_path ());
auto store = nano::make_store (logger, path, nano::dev::constants);
ASSERT_TRUE (!store->init_error ());
nano::stat stats;
nano::ledger ledger (*store, stats, nano::dev::constants);
nano::write_database_queue write_database_queue (false);
boost::latch initialized_latch{ 0 };
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
nano::logging logging;
nano::keypair key1;
nano::block_builder builder;
auto send = builder
.send ()
.previous (nano::dev::genesis->hash ())
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*pool.generate (nano::dev::genesis->hash ()))
.build_shared ();
auto send1 = builder
.send ()
.previous (send->hash ())
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Gxrb_ratio * 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*pool.generate (send->hash ()))
.build_shared ();
{
auto transaction (store->tx_begin_write ());
store->initialize (transaction, ledger.cache, nano::dev::constants);
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *send).code);
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *send1).code);
}
nano::confirmation_height_processor confirmation_height_processor (ledger, write_database_queue, 10ms, logging, logger, initialized_latch, nano::confirmation_height_mode::unbounded);
nano::timer<> timer;
timer.start ();
{
// Prevent conf height processor doing any writes, so that we can query is_processing_block correctly
auto write_guard = write_database_queue.wait (nano::writer::testing);
// Add the frontier block
confirmation_height_processor.add (send1);
// The most uncemented block (previous block) should be seen as processing by the unbounded processor
while (!confirmation_height_processor.is_processing_block (send->hash ()))
{
ASSERT_LT (timer.since_start (), 10s);
}
}
// Wait until the current block is finished processing
while (!confirmation_height_processor.current ().is_zero ())
{
ASSERT_LT (timer.since_start (), 10s);
}
ASSERT_EQ (2, stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in));
ASSERT_EQ (2, stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed_unbounded, nano::stat::dir::in));
ASSERT_EQ (3, ledger.cache.cemented_count);
}
TEST (confirmation_height, pruned_source)
{
nano::logger_mt logger;
nano::logging logging;
auto path (nano::unique_path ());
auto store = nano::make_store (logger, path, nano::dev::constants);
ASSERT_TRUE (!store->init_error ());
nano::stat stats;
nano::ledger ledger (*store, stats, nano::dev::constants);
ledger.pruning = true;
nano::write_database_queue write_database_queue (false);
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
nano::keypair key1, key2;
nano::block_builder builder;
auto send1 = builder
.state ()
.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 (key1.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*pool.generate (nano::dev::genesis->hash ()))
.build_shared ();
auto open1 = builder
.state ()
.account (key1.pub)
.previous (0)
.representative (key1.pub)
.balance (100)
.link (send1->hash ())
.sign (key1.prv, key1.pub)
.work (*pool.generate (key1.pub))
.build_shared ();
auto send2 = builder
.state ()
.account (key1.pub)
.previous (open1->hash ())
.representative (key1.pub)
.balance (50)
.link (key2.pub)
.sign (key1.prv, key1.pub)
.work (*pool.generate (open1->hash ()))
.build_shared ();
auto send3 = builder
.state ()
.account (key1.pub)
.previous (send2->hash ())
.representative (key1.pub)
.balance (25)
.link (key2.pub)
.sign (key1.prv, key1.pub)
.work (*pool.generate (send2->hash ()))
.build_shared ();
auto open2 = builder
.state ()
.account (key2.pub)
.previous (0)
.representative (key1.pub)
.balance (50)
.link (send2->hash ())
.sign (key2.prv, key2.pub)
.work (*pool.generate (key2.pub))
.build_shared ();
{
auto transaction (store->tx_begin_write ());
store->initialize (transaction, ledger.cache, nano::dev::constants);
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *send1).code);
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *open1).code);
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *send2).code);
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *send3).code);
ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, *open2).code);
}
uint64_t batch_write_size = 2;
std::atomic<bool> stopped{ false };
bool first_time{ true };
nano::confirmation_height_bounded bounded_processor (
ledger, write_database_queue, 10ms, logging, logger, stopped, batch_write_size, [&] (auto const & cemented_blocks_a) {
if (first_time)
{
// Prune the send
auto transaction (store->tx_begin_write ());
ASSERT_EQ (2, ledger.pruning_action (transaction, send2->hash (), 2));
}
first_time = false; },
[] (auto const &) {}, [] () { return 0; });
bounded_processor.process (open2);
}