dncurrency/nano/core_test/node.cpp
Piotr Wójcik ef63c23b6e Fix tests
2025-03-03 17:31:49 +01:00

3670 lines
154 KiB
C++

#include <nano/lib/blocks.hpp>
#include <nano/lib/config.hpp>
#include <nano/lib/logging.hpp>
#include <nano/lib/work_version.hpp>
#include <nano/node/active_elections.hpp>
#include <nano/node/confirming_set.hpp>
#include <nano/node/election.hpp>
#include <nano/node/inactive_node.hpp>
#include <nano/node/local_vote_history.hpp>
#include <nano/node/make_store.hpp>
#include <nano/node/online_reps.hpp>
#include <nano/node/portmapping.hpp>
#include <nano/node/pruning.hpp>
#include <nano/node/scheduler/component.hpp>
#include <nano/node/scheduler/manual.hpp>
#include <nano/node/scheduler/priority.hpp>
#include <nano/node/transport/fake.hpp>
#include <nano/node/transport/inproc.hpp>
#include <nano/node/transport/tcp_listener.hpp>
#include <nano/node/vote_generator.hpp>
#include <nano/node/vote_router.hpp>
#include <nano/secure/ledger.hpp>
#include <nano/secure/ledger_set_any.hpp>
#include <nano/secure/ledger_set_confirmed.hpp>
#include <nano/secure/vote.hpp>
#include <nano/test_common/chains.hpp>
#include <nano/test_common/network.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/testutil.hpp>
#include <gtest/gtest.h>
#include <fstream>
#include <numeric>
using namespace std::chrono_literals;
TEST (node, null_account)
{
auto const & null_account = nano::account::null ();
ASSERT_EQ (null_account, nullptr);
ASSERT_FALSE (null_account != nullptr);
nano::account default_account{};
ASSERT_FALSE (default_account == nullptr);
ASSERT_NE (default_account, nullptr);
}
TEST (node, stop)
{
nano::test::system system (1);
ASSERT_NE (system.nodes[0]->wallets.items.end (), system.nodes[0]->wallets.items.begin ());
system.stop_node (*system.nodes[0]);
ASSERT_TRUE (true);
}
TEST (node, work_generate)
{
nano::test::system system (1);
auto & node (*system.nodes[0]);
nano::block_hash root{ 1 };
nano::work_version version{ nano::work_version::work_1 };
{
auto difficulty = nano::difficulty::from_multiplier (1.5, node.network_params.work.base);
auto work = node.work_generate_blocking (version, root, difficulty);
ASSERT_TRUE (work.has_value ());
ASSERT_GE (nano::dev::network_params.work.difficulty (version, root, work.value ()), difficulty);
}
{
auto difficulty = nano::difficulty::from_multiplier (0.5, node.network_params.work.base);
std::optional<uint64_t> work;
do
{
work = node.work_generate_blocking (version, root, difficulty);
} while (nano::dev::network_params.work.difficulty (version, root, work.value ()) >= node.network_params.work.base);
ASSERT_TRUE (work.has_value ());
ASSERT_GE (nano::dev::network_params.work.difficulty (version, root, work.value ()), difficulty);
ASSERT_FALSE (nano::dev::network_params.work.difficulty (version, root, work.value ()) >= node.network_params.work.base);
}
}
TEST (node, block_store_path_failure)
{
nano::test::system system;
auto path (nano::unique_path ());
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
auto node (std::make_shared<nano::node> (system.io_ctx, system.get_available_port (), path, pool));
system.register_node (node);
ASSERT_TRUE (node->wallets.items.empty ());
}
#if defined(__clang__) && defined(__linux__) && CI
// Disable test due to instability with clang and actions
TEST (node_DeathTest, DISABLED_readonly_block_store_not_exist)
#else
TEST (node_DeathTest, readonly_block_store_not_exist)
#endif
{
// This is a read-only node with no ledger file
if (nano::rocksdb_config::using_rocksdb_in_tests ())
{
nano::inactive_node node (nano::unique_path (), nano::inactive_node_flag_defaults ());
ASSERT_TRUE (node.node->init_error ());
}
else
{
ASSERT_EXIT (nano::inactive_node node (nano::unique_path (), nano::inactive_node_flag_defaults ()), ::testing::ExitedWithCode (1), "");
}
}
TEST (node, password_fanout)
{
nano::test::system system;
nano::node_config config;
config.peering_port = system.get_available_port ();
nano::work_pool pool{ nano::dev::network_params.network, std::numeric_limits<unsigned>::max () };
config.password_fanout = 10;
auto & node = *system.add_node (config);
auto wallet (node.wallets.create (100));
ASSERT_EQ (10, wallet->store.password.values.size ());
}
TEST (node, balance)
{
nano::test::system system (1);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
auto transaction = system.nodes[0]->ledger.tx_begin_write ();
ASSERT_EQ (std::numeric_limits<nano::uint128_t>::max (), system.nodes[0]->ledger.any.account_balance (transaction, nano::dev::genesis_key.pub));
}
TEST (node, send_unkeyed)
{
nano::test::system system (1);
nano::keypair key2;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
system.wallet (0)->store.password.value_set (nano::keypair ().prv);
ASSERT_EQ (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ()));
}
TEST (node, send_self)
{
nano::test::system system (1);
nano::keypair key2;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
system.wallet (0)->insert_adhoc (key2.prv);
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ()));
ASSERT_TIMELY (10s, !system.nodes[0]->balance (key2.pub).is_zero ());
ASSERT_EQ (std::numeric_limits<nano::uint128_t>::max () - system.nodes[0]->config.receive_minimum.number (), system.nodes[0]->balance (nano::dev::genesis_key.pub));
}
TEST (node, send_single)
{
nano::test::system system (2);
nano::keypair key2;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
system.wallet (1)->insert_adhoc (key2.prv);
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ()));
ASSERT_EQ (std::numeric_limits<nano::uint128_t>::max () - system.nodes[0]->config.receive_minimum.number (), system.nodes[0]->balance (nano::dev::genesis_key.pub));
ASSERT_TRUE (system.nodes[0]->balance (key2.pub).is_zero ());
ASSERT_TIMELY (10s, !system.nodes[0]->balance (key2.pub).is_zero ());
}
TEST (node, send_single_observing_peer)
{
nano::test::system system (3);
nano::keypair key2;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
system.wallet (1)->insert_adhoc (key2.prv);
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, system.nodes[0]->config.receive_minimum.number ()));
ASSERT_EQ (std::numeric_limits<nano::uint128_t>::max () - system.nodes[0]->config.receive_minimum.number (), system.nodes[0]->balance (nano::dev::genesis_key.pub));
ASSERT_TRUE (system.nodes[0]->balance (key2.pub).is_zero ());
ASSERT_TIMELY (10s, std::all_of (system.nodes.begin (), system.nodes.end (), [&] (std::shared_ptr<nano::node> const & node_a) { return !node_a->balance (key2.pub).is_zero (); }));
}
TEST (node, send_out_of_order)
{
nano::test::system system (2);
auto & node1 (*system.nodes[0]);
nano::keypair key2;
nano::send_block_builder builder;
auto send1 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key2.pub)
.balance (std::numeric_limits<nano::uint128_t>::max () - node1.config.receive_minimum.number ())
.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 ()
.previous (send1->hash ())
.destination (key2.pub)
.balance (std::numeric_limits<nano::uint128_t>::max () - 2 * node1.config.receive_minimum.number ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build ();
auto send3 = builder.make_block ()
.previous (send2->hash ())
.destination (key2.pub)
.balance (std::numeric_limits<nano::uint128_t>::max () - 3 * node1.config.receive_minimum.number ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send2->hash ()))
.build ();
node1.process_active (send3);
node1.process_active (send2);
node1.process_active (send1);
ASSERT_TIMELY (10s, std::all_of (system.nodes.begin (), system.nodes.end (), [&] (std::shared_ptr<nano::node> const & node_a) { return node_a->balance (nano::dev::genesis_key.pub) == nano::dev::constants.genesis_amount - node1.config.receive_minimum.number () * 3; }));
}
TEST (node, quick_confirm)
{
nano::test::system system (1);
auto & node1 (*system.nodes[0]);
nano::keypair key;
nano::block_hash previous (node1.latest (nano::dev::genesis_key.pub));
auto genesis_start_balance (node1.balance (nano::dev::genesis_key.pub));
system.wallet (0)->insert_adhoc (key.prv);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
auto send = nano::send_block_builder ()
.previous (previous)
.destination (key.pub)
.balance (node1.online_reps.delta () + 1)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (previous))
.build ();
node1.process_active (send);
ASSERT_TIMELY (10s, !node1.balance (key.pub).is_zero ());
ASSERT_EQ (node1.balance (nano::dev::genesis_key.pub), node1.online_reps.delta () + 1);
ASSERT_EQ (node1.balance (key.pub), genesis_start_balance - (node1.online_reps.delta () + 1));
}
TEST (node, node_receive_quorum)
{
nano::test::system system (1);
auto & node1 = *system.nodes[0];
nano::keypair key;
nano::block_hash previous (node1.latest (nano::dev::genesis_key.pub));
system.wallet (0)->insert_adhoc (key.prv);
auto send = nano::send_block_builder ()
.previous (previous)
.destination (key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Knano_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (previous))
.build ();
node1.process_active (send);
ASSERT_TIMELY (10s, node1.block_or_pruned_exists (send->hash ()));
ASSERT_TIMELY (10s, node1.active.election (nano::qualified_root (previous, previous)) != nullptr);
auto election (node1.active.election (nano::qualified_root (previous, previous)));
ASSERT_NE (nullptr, election);
ASSERT_FALSE (election->confirmed ());
ASSERT_EQ (1, election->votes ().size ());
nano::test::system system2;
system2.add_node ();
system2.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
ASSERT_TRUE (node1.balance (key.pub).is_zero ());
node1.network.tcp_channels.start_tcp (system2.nodes[0]->network.endpoint ());
while (node1.balance (key.pub).is_zero ())
{
ASSERT_NO_ERROR (system.poll ());
ASSERT_NO_ERROR (system2.poll ());
}
}
TEST (node, auto_bootstrap)
{
nano::test::system system;
nano::node_config config (system.get_available_port ());
config.backlog_scan.enable = false;
nano::node_flags node_flags;
node_flags.disable_bootstrap_bulk_push_client = true;
node_flags.disable_lazy_bootstrap = true;
auto node0 = system.add_node (config, node_flags);
nano::keypair key2;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
system.wallet (0)->insert_adhoc (key2.prv);
auto send1 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, node0->config.receive_minimum.number ()));
ASSERT_NE (nullptr, send1);
ASSERT_TIMELY_EQ (10s, node0->balance (key2.pub), node0->config.receive_minimum.number ());
auto node1 (std::make_shared<nano::node> (system.io_ctx, system.get_available_port (), nano::unique_path (), system.work, node_flags));
ASSERT_FALSE (node1->init_error ());
node1->start ();
system.nodes.push_back (node1);
ASSERT_NE (nullptr, nano::test::establish_tcp (system, *node1, node0->network.endpoint ()));
ASSERT_TIMELY_EQ (10s, node1->balance (key2.pub), node0->config.receive_minimum.number ());
ASSERT_TRUE (node1->block_or_pruned_exists (send1->hash ()));
// Wait block receive
ASSERT_TIMELY_EQ (5s, node1->ledger.block_count (), 3);
// Confirmation for all blocks
ASSERT_TIMELY_EQ (5s, node1->ledger.cemented_count (), 3);
}
TEST (node, auto_bootstrap_reverse)
{
nano::test::system system;
nano::node_config config (system.get_available_port ());
config.backlog_scan.enable = false;
nano::node_flags node_flags;
node_flags.disable_bootstrap_bulk_push_client = true;
node_flags.disable_lazy_bootstrap = true;
auto node0 = system.add_node (config, node_flags);
nano::keypair key2;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
system.wallet (0)->insert_adhoc (key2.prv);
auto node1 (std::make_shared<nano::node> (system.io_ctx, system.get_available_port (), nano::unique_path (), system.work, node_flags));
ASSERT_FALSE (node1->init_error ());
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, node0->config.receive_minimum.number ()));
node1->start ();
system.nodes.push_back (node1);
ASSERT_NE (nullptr, nano::test::establish_tcp (system, *node0, node1->network.endpoint ()));
ASSERT_TIMELY_EQ (10s, node1->balance (key2.pub), node0->config.receive_minimum.number ());
}
TEST (node, merge_peers)
{
nano::test::system system (1);
std::array<nano::endpoint, 8> endpoints;
endpoints.fill (nano::endpoint (boost::asio::ip::address_v6::loopback (), system.get_available_port ()));
endpoints[0] = nano::endpoint (boost::asio::ip::address_v6::loopback (), system.get_available_port ());
system.nodes[0]->network.merge_peers (endpoints);
ASSERT_EQ (0, system.nodes[0]->network.size ());
}
TEST (node, search_receivable)
{
nano::test::system system (1);
auto node (system.nodes[0]);
nano::keypair key2;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, node->config.receive_minimum.number ()));
system.wallet (0)->insert_adhoc (key2.prv);
ASSERT_FALSE (system.wallet (0)->search_receivable (system.wallet (0)->wallets.tx_begin_read ()));
ASSERT_TIMELY (10s, !node->balance (key2.pub).is_zero ());
}
TEST (node, search_receivable_same)
{
nano::test::system system (1);
auto node (system.nodes[0]);
nano::keypair key2;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, node->config.receive_minimum.number ()));
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, node->config.receive_minimum.number ()));
system.wallet (0)->insert_adhoc (key2.prv);
ASSERT_FALSE (system.wallet (0)->search_receivable (system.wallet (0)->wallets.tx_begin_read ()));
ASSERT_TIMELY_EQ (10s, node->balance (key2.pub), 2 * node->config.receive_minimum.number ());
}
TEST (node, search_receivable_multiple)
{
nano::test::system system (1);
auto node (system.nodes[0]);
nano::keypair key2;
nano::keypair key3;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
system.wallet (0)->insert_adhoc (key3.prv);
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key3.pub, node->config.receive_minimum.number ()));
ASSERT_TIMELY (10s, !node->balance (key3.pub).is_zero ());
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, node->config.receive_minimum.number ()));
ASSERT_NE (nullptr, system.wallet (0)->send_action (key3.pub, key2.pub, node->config.receive_minimum.number ()));
system.wallet (0)->insert_adhoc (key2.prv);
ASSERT_FALSE (system.wallet (0)->search_receivable (system.wallet (0)->wallets.tx_begin_read ()));
ASSERT_TIMELY_EQ (10s, node->balance (key2.pub), 2 * node->config.receive_minimum.number ());
}
TEST (node, search_receivable_confirmed)
{
nano::test::system system;
nano::node_config node_config (system.get_available_port ());
node_config.backlog_scan.enable = false;
auto node = system.add_node (node_config);
nano::keypair key2;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
auto send1 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, node->config.receive_minimum.number ()));
ASSERT_NE (nullptr, send1);
ASSERT_TIMELY (5s, nano::test::confirmed (*node, { send1 }));
auto send2 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, node->config.receive_minimum.number ()));
ASSERT_NE (nullptr, send2);
ASSERT_TIMELY (5s, nano::test::confirmed (*node, { send2 }));
{
auto transaction (node->wallets.tx_begin_write ());
system.wallet (0)->store.erase (transaction, nano::dev::genesis_key.pub);
}
system.wallet (0)->insert_adhoc (key2.prv);
ASSERT_FALSE (system.wallet (0)->search_receivable (system.wallet (0)->wallets.tx_begin_read ()));
ASSERT_TIMELY (5s, !node->vote_router.active (send1->hash ()));
ASSERT_TIMELY (5s, !node->vote_router.active (send2->hash ()));
ASSERT_TIMELY_EQ (5s, node->balance (key2.pub), 2 * node->config.receive_minimum.number ());
}
TEST (node, search_receivable_pruned)
{
nano::test::system system;
nano::node_config node_config (system.get_available_port ());
node_config.backlog_scan.enable = false;
auto node1 = system.add_node (node_config);
nano::node_flags node_flags;
node_flags.enable_pruning = true;
nano::node_config config (system.get_available_port ());
config.enable_voting = false; // Remove after allowing pruned voting
auto node2 = system.add_node (config, node_flags);
nano::keypair key2;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
auto send1 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, node2->config.receive_minimum.number ()));
ASSERT_NE (nullptr, send1);
auto send2 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, node2->config.receive_minimum.number ()));
ASSERT_NE (nullptr, send2);
// Confirmation
ASSERT_TIMELY (10s, node1->active.empty () && node2->active.empty ());
ASSERT_TIMELY (5s, node1->ledger.confirmed.block_exists_or_pruned (node1->ledger.tx_begin_read (), send2->hash ()));
ASSERT_TIMELY_EQ (5s, node2->ledger.cemented_count (), 3);
system.wallet (0)->store.erase (node1->wallets.tx_begin_write (), nano::dev::genesis_key.pub);
// Pruning
{
auto transaction = node2->ledger.tx_begin_write ();
ASSERT_EQ (1, node2->ledger.pruning_action (transaction, send1->hash (), 1));
}
ASSERT_EQ (1, node2->ledger.pruned_count ());
ASSERT_TRUE (node2->block_or_pruned_exists (send1->hash ())); // true for pruned
// Receive pruned block
system.wallet (1)->insert_adhoc (key2.prv);
ASSERT_FALSE (system.wallet (1)->search_receivable (system.wallet (1)->wallets.tx_begin_read ()));
ASSERT_TIMELY_EQ (10s, node2->balance (key2.pub), 2 * node2->config.receive_minimum.number ());
}
TEST (node, unlock_search)
{
nano::test::system system (1);
auto node (system.nodes[0]);
nano::keypair key2;
nano::uint128_t balance (node->balance (nano::dev::genesis_key.pub));
{
auto transaction (system.wallet (0)->wallets.tx_begin_write ());
system.wallet (0)->store.rekey (transaction, "");
}
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, node->config.receive_minimum.number ()));
ASSERT_TIMELY (10s, node->balance (nano::dev::genesis_key.pub) != balance);
ASSERT_TIMELY (10s, node->active.empty ());
system.wallet (0)->insert_adhoc (key2.prv);
{
nano::lock_guard<std::recursive_mutex> lock{ system.wallet (0)->store.mutex };
system.wallet (0)->store.password.value_set (nano::keypair ().prv);
}
{
auto transaction (system.wallet (0)->wallets.tx_begin_write ());
ASSERT_FALSE (system.wallet (0)->enter_password (transaction, ""));
}
ASSERT_TIMELY (10s, !node->balance (key2.pub).is_zero ());
}
TEST (node, working)
{
auto path (nano::working_path ());
ASSERT_FALSE (path.empty ());
}
TEST (node, confirm_locked)
{
nano::test::system system (1);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
auto transaction (system.wallet (0)->wallets.tx_begin_read ());
system.wallet (0)->enter_password (transaction, "1");
auto block = nano::send_block_builder ()
.previous (0)
.destination (0)
.balance (0)
.sign (nano::keypair ().prv, 0)
.work (0)
.build ();
system.nodes[0]->network.flood_block (block, nano::transport::traffic_type::test);
}
TEST (node_config, random_rep)
{
auto path (nano::unique_path ());
nano::node_config config1 (100);
auto rep (config1.random_representative ());
ASSERT_NE (config1.preconfigured_representatives.end (), std::find (config1.preconfigured_representatives.begin (), config1.preconfigured_representatives.end (), rep));
}
TEST (node, expire)
{
std::weak_ptr<nano::node> node0;
{
nano::test::system system (1);
node0 = system.nodes[0];
auto & node1 (*system.nodes[0]);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
}
ASSERT_TRUE (node0.expired ());
}
// This test is racy, there is no guarantee that the election won't be confirmed until all forks are fully processed
TEST (node, fork_publish)
{
nano::test::system system (1);
auto & node1 (*system.nodes[0]);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key1;
nano::send_block_builder builder;
auto send1 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - 100)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build ();
node1.work_generate_blocking (*send1);
nano::keypair key2;
auto send2 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key2.pub)
.balance (nano::dev::constants.genesis_amount - 100)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build ();
node1.work_generate_blocking (*send2);
node1.process_active (send1);
node1.process_active (send2);
ASSERT_TIMELY_EQ (5s, 1, node1.active.size ());
ASSERT_TIMELY (5s, node1.active.active (*send2));
auto election (node1.active.election (send1->qualified_root ()));
ASSERT_NE (nullptr, election);
// Wait until the genesis rep activated & makes vote
ASSERT_TIMELY_EQ (1s, election->votes ().size (), 2);
auto votes1 (election->votes ());
auto existing1 (votes1.find (nano::dev::genesis_key.pub));
ASSERT_NE (votes1.end (), existing1);
ASSERT_EQ (send1->hash (), existing1->second.hash);
auto winner (*election->tally ().begin ());
ASSERT_EQ (*send1, *winner.second);
ASSERT_EQ (nano::dev::constants.genesis_amount - 100, winner.first);
}
// In test case there used to be a race condition, it was worked around in:.
// https://github.com/nanocurrency/nano-node/pull/4091
// The election and the processing of block send2 happen in parallel.
// Usually the election happens first and the send2 block is added to the election.
// However, if the send2 block is processed before the election is started then
// there is a race somewhere and the election might not notice the send2 block.
// The test case can be made to pass by ensuring the election is started before the send2 is processed.
// However, is this a problem with the test case or this is a problem with the node handling of forks?
TEST (node, fork_publish_inactive)
{
nano::test::system system (1);
auto & node = *system.nodes[0];
nano::keypair key1;
nano::keypair key2;
nano::send_block_builder builder;
auto send1 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key1.pub)
.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 ();
auto send2 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key2.pub)
.balance (nano::dev::constants.genesis_amount - 100)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (send1->block_work ())
.build ();
node.process_active (send1);
ASSERT_TIMELY (5s, node.block (send1->hash ()));
std::shared_ptr<nano::election> election;
ASSERT_TIMELY (5s, election = node.active.election (send1->qualified_root ()));
ASSERT_EQ (nano::block_status::fork, node.process_local (send2).value ());
ASSERT_TIMELY_EQ (5s, election->blocks ().size (), 2);
auto find_block = [&election] (nano::block_hash hash_a) -> bool {
auto blocks = election->blocks ();
return blocks.end () != blocks.find (hash_a);
};
ASSERT_TRUE (find_block (send1->hash ()));
ASSERT_TRUE (find_block (send2->hash ()));
ASSERT_EQ (election->winner ()->hash (), send1->hash ());
ASSERT_NE (election->winner ()->hash (), send2->hash ());
}
TEST (node, fork_keep)
{
nano::test::system system (2);
auto & node1 (*system.nodes[0]);
auto & node2 (*system.nodes[1]);
ASSERT_EQ (1, node1.network.size ());
nano::keypair key1;
nano::keypair key2;
nano::send_block_builder builder;
// send1 and send2 fork to different accounts
auto send1 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key1.pub)
.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 ();
auto send2 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key2.pub)
.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 ();
node1.process_active (send1);
node2.process_active (builder.make_block ().from (*send1).build ());
ASSERT_TIMELY_EQ (5s, 1, node1.active.size ());
ASSERT_TIMELY_EQ (5s, 1, node2.active.size ());
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
// Fill node with forked blocks
node1.process_active (send2);
ASSERT_TIMELY (5s, node1.active.active (*send2));
node2.process_active (builder.make_block ().from (*send2).build ());
ASSERT_TIMELY (5s, node2.active.active (*send2));
auto election1 (node2.active.election (nano::qualified_root (nano::dev::genesis->hash (), nano::dev::genesis->hash ())));
ASSERT_NE (nullptr, election1);
ASSERT_EQ (1, election1->votes ().size ());
ASSERT_TRUE (node1.block_or_pruned_exists (send1->hash ()));
ASSERT_TRUE (node2.block_or_pruned_exists (send1->hash ()));
// Wait until the genesis rep makes a vote
ASSERT_TIMELY (1.5min, election1->votes ().size () != 1);
auto transaction0 (node1.ledger.tx_begin_read ());
auto transaction1 (node2.ledger.tx_begin_read ());
// The vote should be in agreement with what we already have.
auto winner (*election1->tally ().begin ());
ASSERT_EQ (*send1, *winner.second);
ASSERT_EQ (nano::dev::constants.genesis_amount - 100, winner.first);
ASSERT_TRUE (node1.ledger.any.block_exists (transaction0, send1->hash ()));
ASSERT_TRUE (node2.ledger.any.block_exists (transaction1, send1->hash ()));
}
// This test is racy, there is no guarantee that the election won't be confirmed until all forks are fully processed
TEST (node, fork_flip)
{
nano::test::system system (2);
auto & node1 (*system.nodes[0]);
auto & node2 (*system.nodes[1]);
ASSERT_EQ (1, node1.network.size ());
nano::keypair key1;
nano::send_block_builder builder;
auto send1 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key1.pub)
.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 ();
nano::publish publish1{ nano::dev::network_params.network, send1 };
nano::keypair key2;
auto send2 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key2.pub)
.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 ();
nano::publish publish2{ nano::dev::network_params.network, send2 };
node1.inbound (publish1, nano::test::fake_channel (node1));
node2.inbound (publish2, nano::test::fake_channel (node2));
ASSERT_TIMELY_EQ (5s, 1, node1.active.size ());
ASSERT_TIMELY_EQ (5s, 1, node2.active.size ());
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
// Fill nodes with forked blocks
node1.inbound (publish2, nano::test::fake_channel (node1));
ASSERT_TIMELY (5s, node1.active.active (*send2));
node2.inbound (publish1, nano::test::fake_channel (node2));
ASSERT_TIMELY (5s, node2.active.active (*send1));
auto election1 (node2.active.election (nano::qualified_root (nano::dev::genesis->hash (), nano::dev::genesis->hash ())));
ASSERT_NE (nullptr, election1);
ASSERT_EQ (1, election1->votes ().size ());
ASSERT_NE (nullptr, node1.block (publish1.block->hash ()));
ASSERT_NE (nullptr, node2.block (publish2.block->hash ()));
ASSERT_TIMELY (10s, node2.block_or_pruned_exists (publish1.block->hash ()));
auto winner (*election1->tally ().begin ());
ASSERT_EQ (*publish1.block, *winner.second);
ASSERT_EQ (nano::dev::constants.genesis_amount - 100, winner.first);
ASSERT_TRUE (node1.block_or_pruned_exists (publish1.block->hash ()));
ASSERT_TRUE (node2.block_or_pruned_exists (publish1.block->hash ()));
ASSERT_FALSE (node2.block_or_pruned_exists (publish2.block->hash ()));
}
// Test that more than one block can be rolled back
TEST (node, fork_multi_flip)
{
auto type = nano::transport::transport_type::tcp;
nano::test::system system;
nano::node_flags node_flags;
nano::node_config node_config (system.get_available_port ());
node_config.backlog_scan.enable = false;
auto & node1 (*system.add_node (node_config, node_flags, type));
node_config.peering_port = system.get_available_port ();
node_config.bootstrap.account_sets.cooldown = 100ms; // Reduce cooldown to speed up fork resolution
auto & node2 (*system.add_node (node_config, node_flags, type));
ASSERT_EQ (1, node1.network.size ());
nano::keypair key1;
nano::send_block_builder builder;
auto send1 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key1.pub)
.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 ();
nano::keypair key2;
auto send2 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key2.pub)
.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 ();
auto send3 = builder.make_block ()
.previous (send2->hash ())
.destination (key2.pub)
.balance (nano::dev::constants.genesis_amount - 100)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send2->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, node1.ledger.process (node1.ledger.tx_begin_write (), send1));
// Node2 has two blocks that will be rolled back by node1's vote
ASSERT_EQ (nano::block_status::progress, node2.ledger.process (node2.ledger.tx_begin_write (), send2));
ASSERT_EQ (nano::block_status::progress, node2.ledger.process (node2.ledger.tx_begin_write (), send3));
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); // Insert voting key in to node1
auto election = nano::test::start_election (system, node2, send2->hash ());
ASSERT_NE (nullptr, election);
ASSERT_TIMELY (10s, election->contains (send1->hash ()));
nano::test::confirm (node1.ledger, send1);
ASSERT_TIMELY (10s, node2.block_or_pruned_exists (send1->hash ()));
ASSERT_TRUE (nano::test::block_or_pruned_none_exists (node2, { send2, send3 }));
auto winner = *election->tally ().begin ();
ASSERT_EQ (*send1, *winner.second);
ASSERT_EQ (nano::dev::constants.genesis_amount - 100, winner.first);
}
// Blocks that are no longer actively being voted on should be able to be evicted through bootstrapping.
// This could happen if a fork wasn't resolved before the process previously shut down
TEST (node, fork_bootstrap_flip)
{
nano::test::system system;
nano::node_config config1{ system.get_available_port () };
config1.backlog_scan.enable = false;
nano::node_flags node_flags;
node_flags.disable_bootstrap_bulk_push_client = true;
node_flags.disable_lazy_bootstrap = true;
auto & node1 = *system.add_node (config1, node_flags);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::node_config config2 (system.get_available_port ());
config2.bootstrap.account_sets.cooldown = 100ms; // Reduce cooldown to speed up fork resolution
auto & node2 = *system.make_disconnected_node (config2, node_flags);
nano::block_hash latest = node1.latest (nano::dev::genesis_key.pub);
nano::keypair key1;
nano::send_block_builder builder;
auto send1 = builder.make_block ()
.previous (latest)
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Knano_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest))
.build ();
nano::keypair key2;
auto send2 = builder.make_block ()
.previous (latest)
.destination (key2.pub)
.balance (nano::dev::constants.genesis_amount - nano::Knano_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest))
.build ();
// Insert but don't rebroadcast, simulating settled blocks
ASSERT_EQ (nano::block_status::progress, node1.ledger.process (node1.ledger.tx_begin_write (), send1));
ASSERT_EQ (nano::block_status::progress, node2.ledger.process (node2.ledger.tx_begin_write (), send2));
nano::test::confirm (node1.ledger, send1);
ASSERT_TIMELY (5s, node1.ledger.any.block_exists (node1.ledger.tx_begin_read (), send1->hash ()));
ASSERT_TIMELY (5s, node2.ledger.any.block_exists (node2.ledger.tx_begin_read (), send2->hash ()));
// Additionally add new peer to confirm & replace bootstrap block
node2.network.merge_peer (node1.network.endpoint ());
ASSERT_TIMELY (10s, node2.ledger.any.block_exists (node2.ledger.tx_begin_read (), send1->hash ()));
}
TEST (node, fork_open)
{
nano::test::system system (1);
auto & node = *system.nodes[0];
std::shared_ptr<nano::election> election;
// create block send1, to send all the balance from genesis to key1
// this is done to ensure that the open block(s) cannot be voted on and confirmed
nano::keypair key1;
auto send1 = nano::send_block_builder ()
.previous (nano::dev::genesis->hash ())
.destination (key1.pub)
.balance (0)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
nano::publish publish1{ nano::dev::network_params.network, send1 };
auto channel1 = std::make_shared<nano::transport::fake::channel> (node);
node.inbound (publish1, channel1);
ASSERT_TIMELY (5s, (election = node.active.election (publish1.block->qualified_root ())) != nullptr);
election->force_confirm ();
ASSERT_TIMELY (5s, node.active.empty () && node.block_confirmed (publish1.block->hash ()));
// register key for genesis account, not sure why we do this, it seems needless,
// since the genesis account at this stage has zero voting weight
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
// create the 1st open block to receive send1, which should be regarded as the winner just because it is first
nano::open_block_builder builder;
auto open1 = builder.make_block ()
.source (publish1.block->hash ())
.representative (1)
.account (key1.pub)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1.pub))
.build ();
nano::publish publish2{ nano::dev::network_params.network, open1 };
node.inbound (publish2, channel1);
ASSERT_TIMELY_EQ (5s, 1, node.active.size ());
// create 2nd open block, which is a fork of open1 block
auto open2 = builder.make_block ()
.source (publish1.block->hash ())
.representative (2)
.account (key1.pub)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1.pub))
.build ();
nano::publish publish3{ nano::dev::network_params.network, open2 };
node.inbound (publish3, channel1);
ASSERT_TIMELY (5s, (election = node.active.election (publish3.block->qualified_root ())) != nullptr);
// we expect to find 2 blocks in the election and we expect the first block to be the winner just because it was first
ASSERT_TIMELY_EQ (5s, 2, election->blocks ().size ());
ASSERT_EQ (publish2.block->hash (), election->winner ()->hash ());
// wait for a second and check that the election did not get confirmed
system.delay_ms (1000ms);
ASSERT_FALSE (election->confirmed ());
// check that only the first block is saved to the ledger
ASSERT_TIMELY (5s, node.block (publish2.block->hash ()));
ASSERT_FALSE (node.block (publish3.block->hash ()));
}
TEST (node, fork_open_flip)
{
nano::test::system system (1);
auto & node1 = *system.nodes[0];
std::shared_ptr<nano::election> election;
nano::keypair key1;
nano::keypair rep1;
nano::keypair rep2;
// send 1 raw from genesis to key1 on both node1 and node2
auto send1 = nano::send_block_builder ()
.previous (nano::dev::genesis->hash ())
.destination (key1.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 ();
node1.process_active (send1);
// We should be keeping this block
nano::open_block_builder builder;
auto open1 = builder.make_block ()
.source (send1->hash ())
.representative (rep1.pub)
.account (key1.pub)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1.pub))
.build ();
// create a fork of block open1, this block will lose the election
auto open2 = builder.make_block ()
.source (send1->hash ())
.representative (rep2.pub)
.account (key1.pub)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1.pub))
.build ();
ASSERT_FALSE (*open1 == *open2);
// give block open1 to node1, manually trigger an election for open1 and ensure it is in the ledger
node1.process_active (open1);
ASSERT_TIMELY (5s, node1.block (open1->hash ()) != nullptr);
node1.scheduler.manual.push (open1);
ASSERT_TIMELY (5s, (election = node1.active.election (open1->qualified_root ())) != nullptr);
election->transition_active ();
// create node2, with blocks send1 and open2 pre-initialised in the ledger,
// so that block open1 cannot possibly get in the ledger before open2 via background sync
system.initialization_blocks.push_back (send1);
system.initialization_blocks.push_back (open2);
auto & node2 = *system.add_node ();
system.initialization_blocks.clear ();
// ensure open2 is in node2 ledger (and therefore has sideband) and manually trigger an election for open2
ASSERT_TIMELY (5s, node2.block (open2->hash ()) != nullptr);
node2.scheduler.manual.push (open2);
ASSERT_TIMELY (5s, (election = node2.active.election (open2->qualified_root ())) != nullptr);
election->transition_active ();
ASSERT_TIMELY_EQ (5s, 2, node1.active.size ());
ASSERT_TIMELY_EQ (5s, 2, node2.active.size ());
// allow node1 to vote and wait for open1 to be confirmed on node1
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
ASSERT_TIMELY (5s, node1.block_confirmed (open1->hash ()));
// Notify both nodes of both blocks, both nodes will become aware that a fork exists
node1.process_active (open2);
node2.process_active (open1);
ASSERT_TIMELY_EQ (5s, 2, election->votes ().size ()); // one more than expected due to elections having dummy votes
// Node2 should eventually settle on open1
ASSERT_TIMELY (10s, node2.block (open1->hash ()));
ASSERT_TIMELY (5s, node1.block_confirmed (open1->hash ()));
auto winner = *election->tally ().begin ();
ASSERT_EQ (*open1, *winner.second);
ASSERT_EQ (nano::dev::constants.genesis_amount - 1, winner.first);
// check the correct blocks are in the ledgers
auto transaction1 = node1.ledger.tx_begin_read ();
auto transaction2 = node2.ledger.tx_begin_read ();
ASSERT_TRUE (node1.ledger.any.block_exists (transaction1, open1->hash ()));
ASSERT_TRUE (node2.ledger.any.block_exists (transaction2, open1->hash ()));
ASSERT_FALSE (node2.ledger.any.block_exists (transaction2, open2->hash ()));
}
TEST (node, coherent_observer)
{
nano::test::system system (1);
auto & node1 (*system.nodes[0]);
node1.observers.blocks.add ([&node1] (nano::election_status const & status_a, std::vector<nano::vote_with_weight_info> const &, nano::account const &, nano::uint128_t const &, bool, bool) {
ASSERT_TRUE (node1.ledger.any.block_exists (node1.ledger.tx_begin_read (), status_a.winner->hash ()));
});
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key;
system.wallet (0)->send_action (nano::dev::genesis_key.pub, key.pub, 1);
}
TEST (node, fork_no_vote_quorum)
{
nano::test::system system (3);
auto & node1 (*system.nodes[0]);
auto & node2 (*system.nodes[1]);
auto & node3 (*system.nodes[2]);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
auto key4 (system.wallet (0)->deterministic_insert ());
system.wallet (0)->send_action (nano::dev::genesis_key.pub, key4, nano::dev::constants.genesis_amount / 4);
auto key1 (system.wallet (1)->deterministic_insert ());
{
auto transaction (system.wallet (1)->wallets.tx_begin_write ());
system.wallet (1)->store.representative_set (transaction, key1);
}
auto block (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1, node1.config.receive_minimum.number ()));
ASSERT_NE (nullptr, block);
ASSERT_TIMELY (30s, node3.balance (key1) == node1.config.receive_minimum.number () && node2.balance (key1) == node1.config.receive_minimum.number () && node1.balance (key1) == node1.config.receive_minimum.number ());
ASSERT_EQ (node1.config.receive_minimum.number (), node1.weight (key1));
ASSERT_EQ (node1.config.receive_minimum.number (), node2.weight (key1));
ASSERT_EQ (node1.config.receive_minimum.number (), node3.weight (key1));
nano::block_builder builder;
auto send1 = builder
.state ()
.account (nano::dev::genesis_key.pub)
.previous (block->hash ())
.representative (nano::dev::genesis_key.pub)
.balance ((nano::dev::constants.genesis_amount / 4) - (node1.config.receive_minimum.number () * 2))
.link (key1)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (block->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, node1.process (send1));
ASSERT_EQ (nano::block_status::progress, node2.process (send1));
ASSERT_EQ (nano::block_status::progress, node3.process (send1));
auto key2 (system.wallet (2)->deterministic_insert ());
auto send2 = nano::send_block_builder ()
.previous (block->hash ())
.destination (key2)
.balance ((nano::dev::constants.genesis_amount / 4) - (node1.config.receive_minimum.number () * 2))
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (block->hash ()))
.build ();
nano::raw_key key3;
auto transaction (system.wallet (1)->wallets.tx_begin_read ());
ASSERT_FALSE (system.wallet (1)->store.fetch (transaction, key1, key3));
auto vote = std::make_shared<nano::vote> (key1, key3, 0, 0, std::vector<nano::block_hash>{ send2->hash () });
nano::confirm_ack confirm{ nano::dev::network_params.network, vote };
auto channel = node2.network.find_node_id (node3.node_id.pub);
ASSERT_NE (nullptr, channel);
channel->send (confirm, nano::transport::traffic_type::test);
ASSERT_TIMELY (10s, node3.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::in) >= 3);
ASSERT_EQ (node1.latest (nano::dev::genesis_key.pub), send1->hash ());
ASSERT_EQ (node2.latest (nano::dev::genesis_key.pub), send1->hash ());
ASSERT_EQ (node3.latest (nano::dev::genesis_key.pub), send1->hash ());
}
// Disabled because it sometimes takes way too long (but still eventually finishes)
TEST (node, DISABLED_fork_pre_confirm)
{
nano::test::system system (3);
auto & node0 (*system.nodes[0]);
auto & node1 (*system.nodes[1]);
auto & node2 (*system.nodes[2]);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key1;
system.wallet (1)->insert_adhoc (key1.prv);
{
auto transaction (system.wallet (1)->wallets.tx_begin_write ());
system.wallet (1)->store.representative_set (transaction, key1.pub);
}
nano::keypair key2;
system.wallet (2)->insert_adhoc (key2.prv);
{
auto transaction (system.wallet (2)->wallets.tx_begin_write ());
system.wallet (2)->store.representative_set (transaction, key2.pub);
}
auto block0 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, nano::dev::constants.genesis_amount / 3));
ASSERT_NE (nullptr, block0);
ASSERT_TIMELY (30s, node0.balance (key1.pub) != 0);
auto block1 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, nano::dev::constants.genesis_amount / 3));
ASSERT_NE (nullptr, block1);
ASSERT_TIMELY (30s, node0.balance (key2.pub) != 0);
nano::keypair key3;
nano::keypair key4;
nano::state_block_builder builder;
auto block2 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (node0.latest (nano::dev::genesis_key.pub))
.representative (key3.pub)
.balance (node0.balance (nano::dev::genesis_key.pub))
.link (0)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build ();
auto block3 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (node0.latest (nano::dev::genesis_key.pub))
.representative (key4.pub)
.balance (node0.balance (nano::dev::genesis_key.pub))
.link (0)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build ();
node0.work_generate_blocking (*block2);
node0.work_generate_blocking (*block3);
node0.process_active (block2);
node1.process_active (block2);
node2.process_active (block3);
auto done (false);
// Extend deadline; we must finish within a total of 100 seconds
system.deadline_set (70s);
while (!done)
{
done |= node0.latest (nano::dev::genesis_key.pub) == block2->hash () && node1.latest (nano::dev::genesis_key.pub) == block2->hash () && node2.latest (nano::dev::genesis_key.pub) == block2->hash ();
done |= node0.latest (nano::dev::genesis_key.pub) == block3->hash () && node1.latest (nano::dev::genesis_key.pub) == block3->hash () && node2.latest (nano::dev::genesis_key.pub) == block3->hash ();
ASSERT_NO_ERROR (system.poll ());
}
}
// Sometimes hangs on the bootstrap_initiator.bootstrap call
TEST (node, DISABLED_fork_stale)
{
nano::test::system system1 (1);
system1.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::test::system system2 (1);
auto & node1 (*system1.nodes[0]);
auto & node2 (*system2.nodes[0]);
auto channel = nano::test::establish_tcp (system1, node2, node1.network.endpoint ());
auto vote = std::make_shared<nano::vote> (nano::dev::genesis_key.pub, nano::dev::genesis_key.prv, 0, 0, std::vector<nano::block_hash> ());
ASSERT_TRUE (node2.rep_crawler.process (vote, channel));
nano::keypair key1;
nano::keypair key2;
nano::state_block_builder builder;
auto send3 = 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::nano_ratio)
.link (key1.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build ();
node1.work_generate_blocking (*send3);
node1.process_active (send3);
system2.deadline_set (10s);
while (node2.block (send3->hash ()) == nullptr)
{
system1.poll ();
ASSERT_NO_ERROR (system2.poll ());
}
auto send1 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (send3->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 2 * nano::nano_ratio)
.link (key1.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build ();
node1.work_generate_blocking (*send1);
auto send2 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (send3->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 2 * nano::nano_ratio)
.link (key2.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build ();
node1.work_generate_blocking (*send2);
{
auto transaction1 = node1.ledger.tx_begin_write ();
ASSERT_EQ (nano::block_status::progress, node1.ledger.process (transaction1, send1));
auto transaction2 = node2.ledger.tx_begin_write ();
ASSERT_EQ (nano::block_status::progress, node2.ledger.process (transaction2, send2));
}
node1.process_active (send1);
node1.process_active (send2);
node2.process_active (send1);
node2.process_active (send2);
while (node2.block (send1->hash ()) == nullptr)
{
system1.poll ();
ASSERT_NO_ERROR (system2.poll ());
}
}
// Test disabled because it's failing intermittently.
// PR in which it got disabled: https://github.com/nanocurrency/nano-node/pull/3512
// Issue for investigating it: https://github.com/nanocurrency/nano-node/issues/3516
TEST (node, DISABLED_broadcast_elected)
{
auto type = nano::transport::transport_type::tcp;
nano::node_flags node_flags;
nano::test::system system;
nano::node_config node_config (system.get_available_port ());
node_config.backlog_scan.enable = false;
auto node0 = system.add_node (node_config, node_flags, type);
node_config.peering_port = system.get_available_port ();
auto node1 = system.add_node (node_config, node_flags, type);
node_config.peering_port = system.get_available_port ();
auto node2 = system.add_node (node_config, node_flags, type);
nano::keypair rep_big;
nano::keypair rep_small;
nano::keypair rep_other;
nano::block_builder builder;
{
auto transaction0 = node0->ledger.tx_begin_write ();
auto transaction1 = node1->ledger.tx_begin_write ();
auto transaction2 = node2->ledger.tx_begin_write ();
auto fund_big = builder.send ()
.previous (nano::dev::genesis->hash ())
.destination (rep_big.pub)
.balance (nano::Knano_ratio * 5)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
auto open_big = builder.open ()
.source (fund_big->hash ())
.representative (rep_big.pub)
.account (rep_big.pub)
.sign (rep_big.prv, rep_big.pub)
.work (*system.work.generate (rep_big.pub))
.build ();
auto fund_small = builder.send ()
.previous (fund_big->hash ())
.destination (rep_small.pub)
.balance (nano::Knano_ratio * 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (fund_big->hash ()))
.build ();
auto open_small = builder.open ()
.source (fund_small->hash ())
.representative (rep_small.pub)
.account (rep_small.pub)
.sign (rep_small.prv, rep_small.pub)
.work (*system.work.generate (rep_small.pub))
.build ();
auto fund_other = builder.send ()
.previous (fund_small->hash ())
.destination (rep_other.pub)
.balance (nano::Knano_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (fund_small->hash ()))
.build ();
auto open_other = builder.open ()
.source (fund_other->hash ())
.representative (rep_other.pub)
.account (rep_other.pub)
.sign (rep_other.prv, rep_other.pub)
.work (*system.work.generate (rep_other.pub))
.build ();
ASSERT_EQ (nano::block_status::progress, node0->ledger.process (transaction0, fund_big));
ASSERT_EQ (nano::block_status::progress, node1->ledger.process (transaction1, fund_big));
ASSERT_EQ (nano::block_status::progress, node2->ledger.process (transaction2, fund_big));
ASSERT_EQ (nano::block_status::progress, node0->ledger.process (transaction0, open_big));
ASSERT_EQ (nano::block_status::progress, node1->ledger.process (transaction1, open_big));
ASSERT_EQ (nano::block_status::progress, node2->ledger.process (transaction2, open_big));
ASSERT_EQ (nano::block_status::progress, node0->ledger.process (transaction0, fund_small));
ASSERT_EQ (nano::block_status::progress, node1->ledger.process (transaction1, fund_small));
ASSERT_EQ (nano::block_status::progress, node2->ledger.process (transaction2, fund_small));
ASSERT_EQ (nano::block_status::progress, node0->ledger.process (transaction0, open_small));
ASSERT_EQ (nano::block_status::progress, node1->ledger.process (transaction1, open_small));
ASSERT_EQ (nano::block_status::progress, node2->ledger.process (transaction2, open_small));
ASSERT_EQ (nano::block_status::progress, node0->ledger.process (transaction0, fund_other));
ASSERT_EQ (nano::block_status::progress, node1->ledger.process (transaction1, fund_other));
ASSERT_EQ (nano::block_status::progress, node2->ledger.process (transaction2, fund_other));
ASSERT_EQ (nano::block_status::progress, node0->ledger.process (transaction0, open_other));
ASSERT_EQ (nano::block_status::progress, node1->ledger.process (transaction1, open_other));
ASSERT_EQ (nano::block_status::progress, node2->ledger.process (transaction2, open_other));
}
// Confirm blocks to allow voting
for (auto & node : system.nodes)
{
auto block (node->block (node->latest (nano::dev::genesis_key.pub)));
ASSERT_NE (nullptr, block);
node->start_election (block);
auto election (node->active.election (block->qualified_root ()));
ASSERT_NE (nullptr, election);
election->force_confirm ();
ASSERT_TIMELY_EQ (5s, 4, node->ledger.cemented_count ())
}
system.wallet (0)->insert_adhoc (rep_big.prv);
system.wallet (1)->insert_adhoc (rep_small.prv);
system.wallet (2)->insert_adhoc (rep_other.prv);
auto fork0 = builder.send ()
.previous (node2->latest (nano::dev::genesis_key.pub))
.destination (rep_small.pub)
.balance (0)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node0->work_generate_blocking (node2->latest (nano::dev::genesis_key.pub)))
.build ();
// A copy is necessary to avoid data races during ledger processing, which sets the sideband
auto fork0_copy (std::make_shared<nano::send_block> (*fork0));
node0->process_active (fork0);
node1->process_active (fork0_copy);
auto fork1 = builder.send ()
.previous (node2->latest (nano::dev::genesis_key.pub))
.destination (rep_big.pub)
.balance (0)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node0->work_generate_blocking (node2->latest (nano::dev::genesis_key.pub)))
.build ();
system.wallet (2)->insert_adhoc (rep_small.prv);
node2->process_active (fork1);
ASSERT_TIMELY (10s, node0->block_or_pruned_exists (fork0->hash ()) && node1->block_or_pruned_exists (fork0->hash ()));
system.deadline_set (50s);
while (!node2->block_or_pruned_exists (fork0->hash ()))
{
auto ec = system.poll ();
ASSERT_TRUE (node0->block_or_pruned_exists (fork0->hash ()));
ASSERT_TRUE (node1->block_or_pruned_exists (fork0->hash ()));
ASSERT_NO_ERROR (ec);
}
ASSERT_TIMELY (5s, node1->stats.count (nano::stat::type::confirmation_observer, nano::stat::detail::inactive_conf_height, nano::stat::dir::out) != 0);
}
TEST (node, rep_self_vote)
{
nano::test::system system;
nano::node_flags node_flags;
node_flags.disable_request_loop = true; // Prevent automatic election cleanup
nano::node_config node_config = system.default_config ();
node_config.online_weight_minimum = std::numeric_limits<nano::uint128_t>::max ();
// Disable automatic election activation
node_config.backlog_scan.enable = false;
node_config.priority_scheduler.enable = false;
node_config.hinted_scheduler.enable = false;
node_config.optimistic_scheduler.enable = false;
auto node0 = system.add_node (node_config, node_flags);
nano::keypair rep_big;
nano::block_builder builder;
auto fund_big = builder.send ()
.previous (nano::dev::genesis->hash ())
.destination (rep_big.pub)
.balance (nano::uint128_t{ "0xb0000000000000000000000000000000" })
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
auto open_big = builder.open ()
.source (fund_big->hash ())
.representative (rep_big.pub)
.account (rep_big.pub)
.sign (rep_big.prv, rep_big.pub)
.work (*system.work.generate (rep_big.pub))
.build ();
ASSERT_EQ (nano::block_status::progress, node0->process (fund_big));
ASSERT_EQ (nano::block_status::progress, node0->process (open_big));
// Confirm both blocks, allowing voting on the upcoming block
node0->start_election (node0->block (open_big->hash ()));
std::shared_ptr<nano::election> election;
ASSERT_TIMELY (5s, election = node0->active.election (open_big->qualified_root ()));
election->force_confirm ();
// Insert representatives into the node to allow voting
system.wallet (0)->insert_adhoc (rep_big.prv);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
ASSERT_EQ (system.wallet (0)->wallets.reps ().voting, 2);
auto block0 = builder.send ()
.previous (fund_big->hash ())
.destination (rep_big.pub)
.balance (nano::uint128_t ("0x60000000000000000000000000000000"))
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (fund_big->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, node0->process (block0));
auto election1 = nano::test::start_election (system, *node0, block0->hash ());
ASSERT_NE (nullptr, election1);
// Wait until representatives are activated & make vote
ASSERT_TIMELY_EQ (1s, election1->votes ().size (), 3);
// Election should receive votes from representatives hosted on the same node
auto rep_votes (election1->votes ());
ASSERT_NE (rep_votes.end (), rep_votes.find (nano::dev::genesis_key.pub));
ASSERT_NE (rep_votes.end (), rep_votes.find (rep_big.pub));
}
// Bootstrapping shouldn't republish the blocks to the network.
TEST (node, DISABLED_bootstrap_no_publish)
{
nano::test::system system0 (1);
nano::test::system system1 (1);
auto node0 (system0.nodes[0]);
auto node1 (system1.nodes[0]);
nano::keypair key0;
// node0 knows about send0 but node1 doesn't.
nano::block_builder builder;
auto send0 = builder
.send ()
.previous (node0->latest (nano::dev::genesis_key.pub))
.destination (key0.pub)
.balance (500)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build ();
{
auto transaction = node0->ledger.tx_begin_write ();
ASSERT_EQ (nano::block_status::progress, node0->ledger.process (transaction, send0));
}
ASSERT_TRUE (node1->active.empty ());
system1.deadline_set (10s);
while (node1->block (send0->hash ()) == nullptr)
{
// Poll until the TCP connection is torn down and in_progress goes false
system0.poll ();
auto ec = system1.poll ();
// There should never be an active transaction because the only activity is bootstrapping 1 block which shouldn't be publishing.
ASSERT_TRUE (node1->active.empty ());
ASSERT_NO_ERROR (ec);
}
}
// Bootstrapping a forked open block should succeed.
TEST (node, bootstrap_fork_open)
{
nano::test::system system;
nano::node_config node_config (system.get_available_port ());
node_config.bootstrap.account_sets.cooldown = 100ms; // Reduce cooldown to speed up fork resolution
node_config.bootstrap.frontier_scan.head_parallelism = 3; // Make sure we can process the full account number range
node_config.bootstrap.frontier_rate_limit = 0; // Disable rate limiting to speed up the scan
// Disable automatic election activation
node_config.backlog_scan.enable = false;
node_config.priority_scheduler.enable = false;
node_config.hinted_scheduler.enable = false;
node_config.optimistic_scheduler.enable = false;
auto node0 = system.add_node (node_config);
node_config.peering_port = system.get_available_port ();
auto node1 = system.add_node (node_config);
nano::keypair key0;
nano::block_builder builder;
auto send0 = builder.send ()
.previous (nano::dev::genesis->hash ())
.destination (key0.pub)
.balance (nano::dev::constants.genesis_amount - 500)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
auto open0 = builder.open ()
.source (send0->hash ())
.representative (1)
.account (key0.pub)
.sign (key0.prv, key0.pub)
.work (*system.work.generate (key0.pub))
.build ();
auto open1 = builder.open ()
.source (send0->hash ())
.representative (2)
.account (key0.pub)
.sign (key0.prv, key0.pub)
.work (*system.work.generate (key0.pub))
.build ();
// Both know about send0
ASSERT_EQ (nano::block_status::progress, node0->process (send0));
ASSERT_EQ (nano::block_status::progress, node1->process (send0));
// Confirm send0 to allow starting and voting on the following blocks
nano::test::confirm (*node0, { send0 });
nano::test::confirm (*node1, { send0 });
ASSERT_TIMELY (5s, node0->block_confirmed (send0->hash ()));
ASSERT_TIMELY (5s, node1->block_confirmed (send0->hash ()));
// They disagree about open0/open1
ASSERT_EQ (nano::block_status::progress, node0->process (open0));
node0->confirming_set.add (open0->hash ());
ASSERT_TIMELY (5s, node0->block_confirmed (open0->hash ()));
ASSERT_EQ (nano::block_status::progress, node1->process (open1));
ASSERT_TRUE (node1->block_or_pruned_exists (open1->hash ()));
node1->start_election (open1); // Start election for open block which is necessary to resolve the fork
ASSERT_TIMELY (5s, node1->active.active (*open1));
// Allow node0 to vote on its fork
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
ASSERT_TIMELY (10s, !node1->block_or_pruned_exists (open1->hash ()) && node1->block_or_pruned_exists (open0->hash ()));
}
// Unconfirmed blocks from bootstrap should be confirmed
TEST (node, bootstrap_confirm_frontiers)
{
nano::test::system system;
auto node0 = system.add_node ();
auto node1 = system.add_node ();
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key0;
// create block to send 500 raw from genesis to key0 and save into node0 ledger without immediately triggering an election
auto send0 = nano::send_block_builder ()
.previous (nano::dev::genesis->hash ())
.destination (key0.pub)
.balance (nano::dev::constants.genesis_amount - 500)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node0->work_generate_blocking (nano::dev::genesis->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, node0->process (send0));
ASSERT_TIMELY (10s, node1->block_confirmed (send0->hash ()));
}
// Test that if we create a block that isn't confirmed, the bootstrapping processes sync the missing block.
TEST (node, unconfirmed_send)
{
nano::test::system system{};
auto & node1 = *system.add_node ();
auto wallet1 = system.wallet (0);
wallet1->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key2{};
auto & node2 = *system.add_node ();
auto wallet2 = system.wallet (1);
wallet2->insert_adhoc (key2.prv);
// firstly, send two units from node1 to node2 and expect that both nodes see the block as confirmed
// (node1 will start an election for it, vote on it and node2 gets synced up)
auto send1 = wallet1->send_action (nano::dev::genesis_key.pub, key2.pub, 2 * nano::nano_ratio);
ASSERT_TIMELY (5s, node1.block_confirmed (send1->hash ()));
ASSERT_TIMELY (5s, node2.block_confirmed (send1->hash ()));
// wait until receive1 (auto-receive created by wallet) is cemented
ASSERT_TIMELY_EQ (5s, node2.ledger.confirmed.account_height (node2.ledger.tx_begin_read (), key2.pub), 1);
ASSERT_EQ (node2.balance (key2.pub), 2 * nano::nano_ratio);
auto recv1 = node2.ledger.find_receive_block_by_send_hash (node2.ledger.tx_begin_read (), key2.pub, send1->hash ());
// create send2 to send from node2 to node1 and save it to node2's ledger without triggering an election (node1 does not hear about it)
auto send2 = nano::state_block_builder{}
.make_block ()
.account (key2.pub)
.previous (recv1->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::nano_ratio)
.link (nano::dev::genesis_key.pub)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (recv1->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, node2.process (send2));
auto send3 = wallet2->send_action (key2.pub, nano::dev::genesis_key.pub, nano::nano_ratio);
ASSERT_TIMELY (5s, node2.block_confirmed (send2->hash ()));
ASSERT_TIMELY (5s, node1.block_confirmed (send2->hash ()));
ASSERT_TIMELY (5s, node2.block_confirmed (send3->hash ()));
ASSERT_TIMELY (5s, node1.block_confirmed (send3->hash ()));
ASSERT_TIMELY_EQ (5s, node2.ledger.cemented_count (), 7);
ASSERT_TIMELY_EQ (5s, node1.balance (nano::dev::genesis_key.pub), nano::dev::constants.genesis_amount);
}
// Test that nodes can disable representative voting
TEST (node, no_voting)
{
nano::test::system system (1);
auto & node0 (*system.nodes[0]);
nano::node_config node_config (system.get_available_port ());
node_config.enable_voting = false;
system.add_node (node_config);
auto wallet0 (system.wallet (0));
auto wallet1 (system.wallet (1));
// Node1 has a rep
wallet1->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key1;
wallet1->insert_adhoc (key1.prv);
// Broadcast a confirm so others should know this is a rep node
wallet1->send_action (nano::dev::genesis_key.pub, key1.pub, nano::nano_ratio);
ASSERT_TIMELY (10s, node0.active.empty ());
ASSERT_EQ (0, node0.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::in));
}
TEST (node, send_callback)
{
nano::test::system system (1);
auto & node0 (*system.nodes[0]);
nano::keypair key2;
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
system.wallet (0)->insert_adhoc (key2.prv);
node0.config.callback_address = "localhost";
node0.config.callback_port = 8010;
node0.config.callback_target = "/";
ASSERT_NE (nullptr, system.wallet (0)->send_action (nano::dev::genesis_key.pub, key2.pub, node0.config.receive_minimum.number ()));
ASSERT_TIMELY (10s, node0.balance (key2.pub).is_zero ());
ASSERT_EQ (std::numeric_limits<nano::uint128_t>::max () - node0.config.receive_minimum.number (), node0.balance (nano::dev::genesis_key.pub));
}
TEST (node, balance_observer)
{
nano::test::system system (1);
auto & node1 (*system.nodes[0]);
std::atomic<int> balances (0);
nano::keypair key;
node1.observers.account_balance.add ([&key, &balances] (nano::account const & account_a, bool is_pending) {
if (key.pub == account_a && is_pending)
{
balances++;
}
else if (nano::dev::genesis_key.pub == account_a && !is_pending)
{
balances++;
}
});
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
system.wallet (0)->send_action (nano::dev::genesis_key.pub, key.pub, 1);
system.deadline_set (10s);
auto done (false);
while (!done)
{
auto ec = system.poll ();
done = balances.load () == 2;
ASSERT_NO_ERROR (ec);
}
}
TEST (node, block_confirm)
{
auto type = nano::transport::transport_type::tcp;
nano::node_flags node_flags;
nano::test::system system (2, type, node_flags);
auto & node1 (*system.nodes[0]);
auto & node2 (*system.nodes[1]);
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::Knano_ratio)
.link (key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node1.work_generate_blocking (nano::dev::genesis->hash ()))
.build ();
// A copy is necessary to avoid data races during ledger processing, which sets the sideband
auto send1_copy = builder.make_block ()
.from (*send1)
.build ();
auto hash1 = send1->hash ();
auto hash2 = send1_copy->hash ();
node1.block_processor.add (send1);
node2.block_processor.add (send1_copy);
ASSERT_TIMELY (5s, node1.block_or_pruned_exists (send1->hash ()) && node2.block_or_pruned_exists (send1_copy->hash ()));
ASSERT_TRUE (node1.block_or_pruned_exists (send1->hash ()));
ASSERT_TRUE (node2.block_or_pruned_exists (send1_copy->hash ()));
// Confirm send1 on node2 so it can vote for send2
node2.start_election (send1_copy);
std::shared_ptr<nano::election> election;
ASSERT_TIMELY (5s, election = node2.active.election (send1_copy->qualified_root ()));
// Make node2 genesis representative so it can vote
system.wallet (1)->insert_adhoc (nano::dev::genesis_key.prv);
ASSERT_TIMELY_EQ (10s, node1.active.recently_cemented.list ().size (), 1);
}
TEST (node, confirm_quorum)
{
nano::test::system system (1);
auto & node1 = *system.nodes[0];
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
// Put greater than node.delta () in pending so quorum can't be reached
nano::amount new_balance = node1.online_reps.delta () - nano::Knano_ratio;
auto send1 = nano::state_block_builder ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (new_balance)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node1.work_generate_blocking (nano::dev::genesis->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, node1.process (send1));
system.wallet (0)->send_action (nano::dev::genesis_key.pub, nano::dev::genesis_key.pub, new_balance.number ());
ASSERT_TIMELY (2s, node1.active.election (send1->qualified_root ()));
auto election = node1.active.election (send1->qualified_root ());
ASSERT_NE (nullptr, election);
ASSERT_FALSE (election->confirmed ());
ASSERT_EQ (1, election->votes ().size ());
ASSERT_EQ (0, node1.balance (nano::dev::genesis_key.pub));
}
// TODO: Local vote cache is no longer used when generating votes
TEST (node, DISABLED_local_votes_cache)
{
nano::test::system system;
nano::node_config node_config (system.get_available_port ());
node_config.backlog_scan.enable = false;
node_config.receive_minimum = nano::dev::constants.genesis_amount;
auto & node (*system.add_node (node_config));
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::Knano_ratio)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node.work_generate_blocking (nano::dev::genesis->hash ()))
.build ();
auto send2 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (send1->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 2 * nano::Knano_ratio)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node.work_generate_blocking (send1->hash ()))
.build ();
auto send3 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (send2->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 3 * nano::Knano_ratio)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node.work_generate_blocking (send2->hash ()))
.build ();
{
auto transaction = node.ledger.tx_begin_write ();
ASSERT_EQ (nano::block_status::progress, node.ledger.process (transaction, send1));
ASSERT_EQ (nano::block_status::progress, node.ledger.process (transaction, send2));
}
// Confirm blocks to allow voting
node.start_election (send2);
std::shared_ptr<nano::election> election;
ASSERT_TIMELY (5s, election = node.active.election (send2->qualified_root ()));
election->force_confirm ();
ASSERT_TIMELY_EQ (3s, node.ledger.cemented_count (), 3);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::confirm_req message1{ nano::dev::network_params.network, send1->hash (), send1->root () };
nano::confirm_req message2{ nano::dev::network_params.network, send2->hash (), send2->root () };
auto channel = std::make_shared<nano::transport::fake::channel> (node);
node.inbound (message1, channel);
ASSERT_TIMELY_EQ (3s, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes), 1);
node.inbound (message2, channel);
ASSERT_TIMELY_EQ (3s, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes), 2);
for (auto i (0); i < 100; ++i)
{
node.inbound (message1, channel);
node.inbound (message2, channel);
}
// Make sure a new vote was not generated
ASSERT_TIMELY_EQ (3s, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes), 2);
// Max cache
{
auto transaction = node.ledger.tx_begin_write ();
ASSERT_EQ (nano::block_status::progress, node.ledger.process (transaction, send3));
}
nano::confirm_req message3{ nano::dev::network_params.network, send3->hash (), send3->root () };
node.inbound (message3, channel);
ASSERT_TIMELY_EQ (3s, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes), 3);
ASSERT_TIMELY (3s, !node.history.votes (send1->root (), send1->hash ()).empty ());
ASSERT_TIMELY (3s, !node.history.votes (send2->root (), send2->hash ()).empty ());
ASSERT_TIMELY (3s, !node.history.votes (send3->root (), send3->hash ()).empty ());
// All requests should be served from the cache
for (auto i (0); i < 100; ++i)
{
node.inbound (message3, channel);
}
ASSERT_TIMELY_EQ (3s, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes), 3);
}
// Test disabled because it's failing intermittently.
// PR in which it got disabled: https://github.com/nanocurrency/nano-node/pull/3532
// Issue for investigating it: https://github.com/nanocurrency/nano-node/issues/3481
// TODO: Local vote cache is no longer used when generating votes
TEST (node, DISABLED_local_votes_cache_batch)
{
nano::test::system system;
nano::node_config node_config (system.get_available_port ());
node_config.backlog_scan.enable = false;
auto & node (*system.add_node (node_config));
ASSERT_GE (node.network_params.voting.max_cache, 2);
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key1;
auto send1 = nano::state_block_builder ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Knano_ratio)
.link (key1.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node.work_generate_blocking (nano::dev::genesis->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, node.ledger.process (node.ledger.tx_begin_write (), send1));
node.confirming_set.add (send1->hash ());
ASSERT_TIMELY (5s, node.ledger.confirmed.block_exists_or_pruned (node.ledger.tx_begin_read (), send1->hash ()));
auto send2 = nano::state_block_builder ()
.account (nano::dev::genesis_key.pub)
.previous (send1->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 2 * nano::Knano_ratio)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node.work_generate_blocking (send1->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, node.ledger.process (node.ledger.tx_begin_write (), send2));
auto receive1 = nano::state_block_builder ()
.account (key1.pub)
.previous (0)
.representative (nano::dev::genesis_key.pub)
.balance (nano::Knano_ratio)
.link (send1->hash ())
.sign (key1.prv, key1.pub)
.work (*node.work_generate_blocking (key1.pub))
.build ();
ASSERT_EQ (nano::block_status::progress, node.ledger.process (node.ledger.tx_begin_write (), receive1));
std::vector<std::pair<nano::block_hash, nano::root>> batch{ { send2->hash (), send2->root () }, { receive1->hash (), receive1->root () } };
nano::confirm_req message{ nano::dev::network_params.network, batch };
auto channel = std::make_shared<nano::transport::fake::channel> (node);
// Generates and sends one vote for both hashes which is then cached
node.inbound (message, channel);
ASSERT_TIMELY_EQ (3s, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out), 1);
ASSERT_EQ (1, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out));
ASSERT_FALSE (node.history.votes (send2->root (), send2->hash ()).empty ());
ASSERT_FALSE (node.history.votes (receive1->root (), receive1->hash ()).empty ());
// Only one confirm_ack should be sent if all hashes are part of the same vote
node.inbound (message, channel);
ASSERT_TIMELY_EQ (3s, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out), 2);
ASSERT_EQ (2, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out));
// Test when votes are different
node.history.erase (send2->root ());
node.history.erase (receive1->root ());
node.inbound (nano::confirm_req{ nano::dev::network_params.network, send2->hash (), send2->root () }, channel);
ASSERT_TIMELY_EQ (3s, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out), 3);
ASSERT_EQ (3, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out));
node.inbound (nano::confirm_req{ nano::dev::network_params.network, receive1->hash (), receive1->root () }, channel);
ASSERT_TIMELY_EQ (3s, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out), 4);
ASSERT_EQ (4, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out));
// There are two different votes, so both should be sent in response
node.inbound (message, channel);
ASSERT_TIMELY_EQ (3s, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out), 6);
ASSERT_EQ (6, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out));
}
/**
* There is a cache for locally generated votes. This test checks that the node
* properly caches and uses those votes when replying to confirm_req requests.
*/
// TODO: Local vote cache is no longer used when generating votes
TEST (node, DISABLED_local_votes_cache_generate_new_vote)
{
nano::test::system system;
nano::node_config node_config (system.get_available_port ());
node_config.backlog_scan.enable = false;
auto & node (*system.add_node (node_config));
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
// Send a confirm req for genesis block to node
nano::confirm_req message1{ nano::dev::network_params.network, nano::dev::genesis->hash (), nano::dev::genesis->root () };
auto channel = std::make_shared<nano::transport::fake::channel> (node);
node.inbound (message1, channel);
// check that the node generated a vote for the genesis block and that it is stored in the local vote cache and it is the only vote
ASSERT_TIMELY (5s, !node.history.votes (nano::dev::genesis->root (), nano::dev::genesis->hash ()).empty ());
auto votes1 = node.history.votes (nano::dev::genesis->root (), nano::dev::genesis->hash ());
ASSERT_EQ (1, votes1.size ());
ASSERT_EQ (1, votes1[0]->hashes.size ());
ASSERT_EQ (nano::dev::genesis->hash (), votes1[0]->hashes[0]);
ASSERT_TIMELY_EQ (3s, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes), 1);
auto send1 = nano::state_block_builder ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Knano_ratio)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node.work_generate_blocking (nano::dev::genesis->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, node.process (send1));
// One of the hashes is cached
std::vector<std::pair<nano::block_hash, nano::root>> roots_hashes{ std::make_pair (nano::dev::genesis->hash (), nano::dev::genesis->root ()), std::make_pair (send1->hash (), send1->root ()) };
nano::confirm_req message2{ nano::dev::network_params.network, roots_hashes };
node.inbound (message2, channel);
ASSERT_TIMELY (3s, !node.history.votes (send1->root (), send1->hash ()).empty ());
auto votes2 (node.history.votes (send1->root (), send1->hash ()));
ASSERT_EQ (1, votes2.size ());
ASSERT_EQ (1, votes2[0]->hashes.size ());
ASSERT_TIMELY_EQ (3s, node.stats.count (nano::stat::type::requests, nano::stat::detail::requests_generated_votes), 2);
ASSERT_FALSE (node.history.votes (nano::dev::genesis->root (), nano::dev::genesis->hash ()).empty ());
ASSERT_FALSE (node.history.votes (send1->root (), send1->hash ()).empty ());
// First generated + again cached + new generated
ASSERT_TIMELY_EQ (3s, 3, node.stats.count (nano::stat::type::message, nano::stat::detail::confirm_ack, nano::stat::dir::out));
}
// TODO: Local vote cache is no longer used when generating votes
TEST (node, DISABLED_local_votes_cache_fork)
{
nano::test::system system;
nano::node_flags node_flags;
node_flags.disable_bootstrap_bulk_push_client = true;
node_flags.disable_bootstrap_bulk_pull_server = true;
node_flags.disable_bootstrap_listener = true;
node_flags.disable_lazy_bootstrap = true;
node_flags.disable_legacy_bootstrap = true;
node_flags.disable_wallet_bootstrap = true;
nano::node_config node_config (system.get_available_port ());
node_config.backlog_scan.enable = false;
auto & node1 (*system.add_node (node_config, node_flags));
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
auto send1 = nano::state_block_builder ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Knano_ratio)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node1.work_generate_blocking (nano::dev::genesis->hash ()))
.build ();
auto send1_fork = nano::state_block_builder ()
.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::Knano_ratio)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node1.work_generate_blocking (nano::dev::genesis->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, node1.process (send1));
// Cache vote
auto vote = nano::test::make_vote (nano::dev::genesis_key, { send1 }, 0, 0);
node1.vote_processor.vote (vote, std::make_shared<nano::transport::fake::channel> (node1));
node1.history.add (send1->root (), send1->hash (), vote);
auto votes2 (node1.history.votes (send1->root (), send1->hash ()));
ASSERT_EQ (1, votes2.size ());
ASSERT_EQ (1, votes2[0]->hashes.size ());
// Start election for forked block
node_config.peering_port = system.get_available_port ();
auto & node2 (*system.add_node (node_config, node_flags));
node2.process_active (send1_fork);
ASSERT_TIMELY (5s, node2.block_or_pruned_exists (send1->hash ()));
}
TEST (node, vote_republish)
{
nano::test::system system (2);
auto & node1 = *system.nodes[0];
auto & node2 = *system.nodes[1];
nano::keypair key2;
// by not setting a private key on node1's wallet for genesis account, it is stopped from voting
system.wallet (1)->insert_adhoc (key2.prv);
// send1 and send2 are forks of each other
nano::send_block_builder builder;
auto send1 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key2.pub)
.balance (std::numeric_limits<nano::uint128_t>::max () - node1.config.receive_minimum.number ())
.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 ()
.previous (nano::dev::genesis->hash ())
.destination (key2.pub)
.balance (std::numeric_limits<nano::uint128_t>::max () - node1.config.receive_minimum.number () * 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
// process send1 first, this will make sure send1 goes into the ledger and an election is started
node1.process_active (send1);
ASSERT_TIMELY (5s, node2.block (send1->hash ()));
ASSERT_TIMELY (5s, node1.active.active (*send1));
ASSERT_TIMELY (5s, node2.active.active (*send1));
// now process send2, send2 will not go in the ledger because only the first block of a fork goes in the ledger
node1.process_active (send2);
ASSERT_TIMELY (5s, node1.active.active (*send2));
// send2 cannot be synced because it is not in the ledger of node1, it is only in the election object in RAM on node1
ASSERT_FALSE (node1.block (send2->hash ()));
// the vote causes the election to reach quorum and for the vote (and block?) to be published from node1 to node2
auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { send2 });
node1.vote_processor.vote (vote, std::make_shared<nano::transport::fake::channel> (node1));
// FIXME: there is a race condition here, if the vote arrives before the block then the vote is wasted and the test fails
// we could resend the vote but then there is a race condition between the vote resending and the election reaching quorum on node1
// the proper fix would be to observe on node2 that both the block and the vote arrived in whatever order
// the real node will do a confirm request if it needs to find a lost vote
// check that send2 won on both nodes
ASSERT_TIMELY (5s, node1.block_confirmed (send2->hash ()));
ASSERT_TIMELY (5s, node2.block_confirmed (send2->hash ()));
// check that send1 is deleted from the ledger on nodes
ASSERT_FALSE (node1.block (send1->hash ()));
ASSERT_FALSE (node2.block (send1->hash ()));
ASSERT_TIMELY_EQ (5s, node2.balance (key2.pub), node1.config.receive_minimum.number () * 2);
ASSERT_TIMELY_EQ (5s, node1.balance (key2.pub), node1.config.receive_minimum.number () * 2);
}
TEST (node, vote_by_hash_bundle)
{
// Keep max_hashes above system to ensure it is kept in scope as votes can be added during system destruction
std::atomic<size_t> max_hashes{ 0 };
nano::test::system system (1);
auto & node = *system.nodes[0];
nano::state_block_builder builder;
std::vector<std::shared_ptr<nano::state_block>> blocks;
auto block = 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)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
blocks.push_back (block);
ASSERT_EQ (nano::block_status::progress, node.ledger.process (node.ledger.tx_begin_write (), blocks.back ()));
for (auto i = 2; i < 200; ++i)
{
auto block = builder.make_block ()
.from (*blocks.back ())
.previous (blocks.back ()->hash ())
.balance (nano::dev::constants.genesis_amount - i)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (blocks.back ()->hash ()))
.build ();
blocks.push_back (block);
ASSERT_EQ (nano::block_status::progress, node.ledger.process (node.ledger.tx_begin_write (), blocks.back ()));
}
// Confirming last block will confirm whole chain and allow us to generate votes for those blocks later
nano::test::confirm (node.ledger, blocks.back ());
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::keypair key1;
system.wallet (0)->insert_adhoc (key1.prv);
system.nodes[0]->observers.vote.add ([&max_hashes] (std::shared_ptr<nano::vote> const & vote_a, std::shared_ptr<nano::transport::channel> const &, nano::vote_source, nano::vote_code) {
if (vote_a->hashes.size () > max_hashes)
{
max_hashes = vote_a->hashes.size ();
}
});
for (auto const & block : blocks)
{
system.nodes[0]->generator.add (block->root (), block->hash ());
}
// Verify that bundling occurs. While reaching 12 should be common on most hardware in release mode,
// we set this low enough to allow the test to pass on CI/with sanitizers.
ASSERT_TIMELY (20s, max_hashes.load () >= 3);
}
// This test places block send1 onto every node. Then it creates block send2 (which is a fork of send1) and sends it to node1.
// Then it sends a vote for send2 to node1 and expects node2 to also get the block plus vote and confirm send2.
// TODO: This test enforces the order block followed by vote on node1, should vote followed by block also work? It doesn't currently.
TEST (node, vote_by_hash_republish)
{
nano::test::system system{ 2 };
auto & node1 = *system.nodes[0];
auto & node2 = *system.nodes[1];
nano::keypair key2;
system.wallet (1)->insert_adhoc (key2.prv);
// send1 and send2 are forks of each other
nano::send_block_builder builder;
auto send1 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key2.pub)
.balance (std::numeric_limits<nano::uint128_t>::max () - node1.config.receive_minimum.number ())
.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 ()
.previous (nano::dev::genesis->hash ())
.destination (key2.pub)
.balance (std::numeric_limits<nano::uint128_t>::max () - node1.config.receive_minimum.number () * 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
// give block send1 to node1 and check that an election for send1 starts on both nodes
node1.process_active (send1);
ASSERT_TIMELY (5s, node1.active.active (*send1));
ASSERT_TIMELY (5s, node2.active.active (*send1));
// give block send2 to node1 and wait until the block is received and processed by node1
node1.network.filter.clear ();
node1.process_active (send2);
ASSERT_TIMELY (5s, node1.active.active (*send2));
// construct a vote for send2 in order to overturn send1
std::vector<nano::block_hash> vote_blocks;
vote_blocks.push_back (send2->hash ());
auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { vote_blocks });
node1.vote_processor.vote (vote, std::make_shared<nano::transport::fake::channel> (node1));
// send2 should win on both nodes
ASSERT_TIMELY (5s, node1.block_confirmed (send2->hash ()));
ASSERT_TIMELY (5s, node2.block_confirmed (send2->hash ()));
ASSERT_FALSE (node1.block (send1->hash ()));
ASSERT_FALSE (node2.block (send1->hash ()));
ASSERT_TIMELY_EQ (5s, node2.balance (key2.pub), node1.config.receive_minimum.number () * 2);
ASSERT_TIMELY_EQ (5s, node1.balance (key2.pub), node1.config.receive_minimum.number () * 2);
}
// 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/3638
TEST (node, DISABLED_vote_by_hash_epoch_block_republish)
{
nano::test::system system (2);
auto & node1 (*system.nodes[0]);
auto & node2 (*system.nodes[1]);
nano::keypair key2;
system.wallet (1)->insert_adhoc (key2.prv);
auto send1 = nano::send_block_builder ()
.previous (nano::dev::genesis->hash ())
.destination (key2.pub)
.balance (std::numeric_limits<nano::uint128_t>::max () - node1.config.receive_minimum.number ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
auto epoch1 = nano::state_block_builder ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount)
.link (node1.ledger.epoch_link (nano::epoch::epoch_1))
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
node1.process_active (send1);
ASSERT_TIMELY (5s, node2.active.active (*send1));
node1.active.publish (epoch1);
std::vector<nano::block_hash> vote_blocks;
vote_blocks.push_back (epoch1->hash ());
auto vote = nano::test::make_vote (nano::dev::genesis_key, { vote_blocks }, 0, 0);
ASSERT_TRUE (node1.active.active (*send1));
ASSERT_TRUE (node2.active.active (*send1));
node1.vote_processor.vote (vote, std::make_shared<nano::transport::fake::channel> (node1));
ASSERT_TIMELY (10s, node1.block (epoch1->hash ()));
ASSERT_TIMELY (10s, node2.block (epoch1->hash ()));
ASSERT_FALSE (node1.block (send1->hash ()));
ASSERT_FALSE (node2.block (send1->hash ()));
}
TEST (node, epoch_conflict_confirm)
{
nano::test::system system;
nano::node_config node_config (system.get_available_port ());
node_config.backlog_scan.enable = false;
auto & node0 = *system.add_node (node_config);
node_config.peering_port = system.get_available_port ();
auto & node1 = *system.add_node (node_config);
nano::keypair key;
nano::keypair epoch_signer (nano::dev::genesis_key);
nano::state_block_builder builder;
// Node 1 is the voting node
// Send sends to an account we control: send -> open -> change
// Send2 sends to an account with public key of the open block
// Epoch open qualified root: (open, 0) on account with the same public key as the hash of the open block
// Epoch open and change have the same root!
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 - 1)
.link (key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
auto open = builder.make_block ()
.account (key.pub)
.previous (0)
.representative (key.pub)
.balance (1)
.link (send->hash ())
.sign (key.prv, key.pub)
.work (*system.work.generate (key.pub))
.build ();
auto change = builder.make_block ()
.account (key.pub)
.previous (open->hash ())
.representative (key.pub)
.balance (1)
.link (0)
.sign (key.prv, key.pub)
.work (*system.work.generate (open->hash ()))
.build ();
auto send2 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (send->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 2)
.link (open->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send->hash ()))
.build ();
auto epoch_open = builder.make_block ()
.account (change->root ().as_account ())
.previous (0)
.representative (0)
.balance (0)
.link (node0.ledger.epoch_link (nano::epoch::epoch_1))
.sign (epoch_signer.prv, epoch_signer.pub)
.work (*system.work.generate (open->hash ()))
.build ();
// Process initial blocks
ASSERT_TRUE (nano::test::process (node0, nano::test::clone ({ send, send2, open })));
ASSERT_TRUE (nano::test::process (node1, nano::test::clone ({ send, send2, open })));
// Process conflicting blocks on nodes as blocks coming from live network
ASSERT_TRUE (nano::test::process_live (node0, nano::test::clone ({ change, epoch_open })));
ASSERT_TRUE (nano::test::process_live (node1, nano::test::clone ({ change, epoch_open })));
// Ensure blocks were propagated to both nodes
ASSERT_TIMELY (5s, nano::test::exists (node0, { change, epoch_open }));
ASSERT_TIMELY (5s, nano::test::exists (node1, { change, epoch_open }));
// Confirm initial blocks in node1 to allow generating votes later
nano::test::confirm (node1, { change, epoch_open, send2 });
ASSERT_TIMELY (5s, nano::test::confirmed (node1, { change, epoch_open, send2 }));
// Start elections on node0 for conflicting change and epoch_open blocks (these two blocks have the same root)
ASSERT_TRUE (nano::test::activate (node0, { change, epoch_open }));
ASSERT_TIMELY (5s, nano::test::active (node0, { change, epoch_open }));
// Make node1 a representative so it can vote for both blocks
system.wallet (1)->insert_adhoc (nano::dev::genesis_key.prv);
// Ensure the elections for conflicting blocks have started
ASSERT_TIMELY (5s, nano::test::active (node0, { change, epoch_open }));
// Ensure both conflicting blocks were successfully processed and confirmed
ASSERT_TIMELY (5s, nano::test::confirmed (node0, { change, epoch_open }));
}
// Test disabled because it's failing intermittently.
// PR in which it got disabled: https://github.com/nanocurrency/nano-node/pull/3526
// Issue for investigating it: https://github.com/nanocurrency/nano-node/issues/3527
TEST (node, DISABLED_fork_invalid_block_signature)
{
nano::test::system system;
nano::node_flags node_flags;
// Disabling republishing + waiting for a rollback before sending the correct vote below fixes an intermittent failure in this test
// If these are taken out, one of two things may cause the test two fail often:
// - Block *send2* might get processed before the rollback happens, simply due to timings, with code "fork", and not be processed again. Waiting for the rollback fixes this issue.
// - Block *send1* might get processed again after the rollback happens, which causes *send2* to be processed with code "fork". Disabling block republishing ensures "send1" is not processed again.
// An alternative would be to repeatedly flood the correct vote
node_flags.disable_block_processor_republishing = true;
auto & node1 (*system.add_node (node_flags));
auto & node2 (*system.add_node (node_flags));
nano::keypair key2;
nano::send_block_builder builder;
auto send1 = builder.make_block ()
.previous (nano::dev::genesis->hash ())
.destination (key2.pub)
.balance (std::numeric_limits<nano::uint128_t>::max () - node1.config.receive_minimum.number ())
.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 ()
.previous (nano::dev::genesis->hash ())
.destination (key2.pub)
.balance (std::numeric_limits<nano::uint128_t>::max () - node1.config.receive_minimum.number () * 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
auto send2_corrupt (std::make_shared<nano::send_block> (*send2));
send2_corrupt->signature = nano::signature (123);
auto vote = nano::test::make_vote (nano::dev::genesis_key, { send2 }, 0, 0);
auto vote_corrupt = nano::test::make_vote (nano::dev::genesis_key, { send2_corrupt }, 0, 0);
node1.process_active (send1);
ASSERT_TIMELY (5s, node1.block (send1->hash ()));
// Send the vote with the corrupt block signature
ASSERT_TRUE (node2.network.flood_vote_rebroadcasted (vote_corrupt, 1.0f));
// Wait for the rollback
ASSERT_TIMELY (5s, node1.stats.count (nano::stat::type::rollback));
// Send the vote with the correct block
ASSERT_TRUE (node2.network.flood_vote_rebroadcasted (vote, 1.0f));
ASSERT_TIMELY (10s, !node1.block (send1->hash ()));
ASSERT_TIMELY (10s, node1.block (send2->hash ()));
ASSERT_EQ (node1.block (send2->hash ())->block_signature (), send2->block_signature ());
}
TEST (node, fork_election_invalid_block_signature)
{
nano::test::system system (1);
auto & node1 (*system.nodes[0]);
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 - nano::Knano_ratio)
.link (nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.build ();
auto send2 = 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 - 2 * nano::Knano_ratio)
.link (nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.build ();
auto send3 = 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 - 2 * nano::Knano_ratio)
.link (nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.sign (nano::dev::genesis_key.prv, 0) // Invalid signature
.build ();
auto channel1 = std::make_shared<nano::transport::fake::channel> (node1);
node1.inbound (nano::publish{ nano::dev::network_params.network, send1 }, channel1);
ASSERT_TIMELY (5s, node1.active.active (send1->qualified_root ()));
auto election (node1.active.election (send1->qualified_root ()));
ASSERT_NE (nullptr, election);
ASSERT_EQ (1, election->blocks ().size ());
node1.inbound (nano::publish{ nano::dev::network_params.network, send3 }, channel1);
node1.inbound (nano::publish{ nano::dev::network_params.network, send2 }, channel1);
ASSERT_TIMELY (3s, election->blocks ().size () > 1);
ASSERT_EQ (election->blocks ()[send2->hash ()]->block_signature (), send2->block_signature ());
}
TEST (node, block_processor_signatures)
{
nano::test::system system{ 1 };
auto & node1 = *system.nodes[0];
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
nano::block_hash latest = system.nodes[0]->latest (nano::dev::genesis_key.pub);
nano::state_block_builder builder;
nano::keypair key1;
nano::keypair key2;
nano::keypair key3;
auto send1 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (latest)
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - nano::Knano_ratio)
.link (key1.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node1.work_generate_blocking (latest))
.build ();
auto send2 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (send1->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 2 * nano::Knano_ratio)
.link (key2.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node1.work_generate_blocking (send1->hash ()))
.build ();
auto send3 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (send2->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 3 * nano::Knano_ratio)
.link (key3.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node1.work_generate_blocking (send2->hash ()))
.build ();
// Invalid signature bit
auto send4 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (send3->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 4 * nano::Knano_ratio)
.link (key3.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node1.work_generate_blocking (send3->hash ()))
.build ();
send4->signature.bytes[32] ^= 0x1;
// Invalid signature bit (force)
auto send5 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (send3->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 5 * nano::Knano_ratio)
.link (key3.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node1.work_generate_blocking (send3->hash ()))
.build ();
send5->signature.bytes[32] ^= 0x1;
// Invalid signature to unchecked
node1.unchecked.put (send5->previous (), nano::unchecked_info{ send5 });
auto receive1 = builder.make_block ()
.account (key1.pub)
.previous (0)
.representative (nano::dev::genesis_key.pub)
.balance (nano::Knano_ratio)
.link (send1->hash ())
.sign (key1.prv, key1.pub)
.work (*node1.work_generate_blocking (key1.pub))
.build ();
auto receive2 = builder.make_block ()
.account (key2.pub)
.previous (0)
.representative (nano::dev::genesis_key.pub)
.balance (nano::Knano_ratio)
.link (send2->hash ())
.sign (key2.prv, key2.pub)
.work (*node1.work_generate_blocking (key2.pub))
.build ();
// Invalid private key
auto receive3 = builder.make_block ()
.account (key3.pub)
.previous (0)
.representative (nano::dev::genesis_key.pub)
.balance (nano::Knano_ratio)
.link (send3->hash ())
.sign (key2.prv, key3.pub)
.work (*node1.work_generate_blocking (key3.pub))
.build ();
node1.process_active (send1);
node1.process_active (send2);
node1.process_active (send3);
node1.process_active (send4);
node1.process_active (receive1);
node1.process_active (receive2);
node1.process_active (receive3);
ASSERT_TIMELY (5s, node1.block (receive2->hash ()) != nullptr); // Implies send1, send2, send3, receive1.
ASSERT_TIMELY_EQ (5s, node1.unchecked.count (), 0);
ASSERT_EQ (nullptr, node1.block (receive3->hash ())); // Invalid signer
ASSERT_EQ (nullptr, node1.block (send4->hash ())); // Invalid signature via process_active
ASSERT_EQ (nullptr, node1.block (send5->hash ())); // Invalid signature via unchecked
}
/*
* State blocks go through a different signature path, ensure invalidly signed state blocks are rejected
* This test can freeze if the wake conditions in block_processor::flush are off, for that reason this is done async here
*/
TEST (node, block_processor_reject_state)
{
nano::test::system system (1);
auto & node (*system.nodes[0]);
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::Knano_ratio)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node.work_generate_blocking (nano::dev::genesis->hash ()))
.build ();
send1->signature.bytes[0] ^= 1;
ASSERT_FALSE (node.block_or_pruned_exists (send1->hash ()));
node.process_active (send1);
ASSERT_TIMELY_EQ (5s, 1, node.stats.count (nano::stat::type::block_processor_result, nano::stat::detail::bad_signature));
ASSERT_FALSE (node.block_or_pruned_exists (send1->hash ()));
auto send2 = 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::Knano_ratio)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node.work_generate_blocking (nano::dev::genesis->hash ()))
.build ();
node.process_active (send2);
ASSERT_TIMELY (5s, node.block_or_pruned_exists (send2->hash ()));
}
TEST (node, confirm_back)
{
nano::test::system system (1);
nano::keypair key;
auto & node (*system.nodes[0]);
auto genesis_start_balance (node.balance (nano::dev::genesis_key.pub));
auto send1 = nano::send_block_builder ()
.previous (nano::dev::genesis->hash ())
.destination (key.pub)
.balance (genesis_start_balance - 1)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
nano::state_block_builder builder;
auto open = builder.make_block ()
.account (key.pub)
.previous (0)
.representative (key.pub)
.balance (1)
.link (send1->hash ())
.sign (key.prv, key.pub)
.work (*system.work.generate (key.pub))
.build ();
auto send2 = builder.make_block ()
.account (key.pub)
.previous (open->hash ())
.representative (key.pub)
.balance (0)
.link (nano::dev::genesis_key.pub)
.sign (key.prv, key.pub)
.work (*system.work.generate (open->hash ()))
.build ();
node.process_active (send1);
node.process_active (open);
node.process_active (send2);
ASSERT_TIMELY (5s, node.block (send2->hash ()) != nullptr);
ASSERT_TRUE (nano::test::start_elections (system, node, { send1, open, send2 }));
ASSERT_EQ (3, node.active.size ());
std::vector<nano::block_hash> vote_blocks;
vote_blocks.push_back (send2->hash ());
auto vote = nano::test::make_final_vote (nano::dev::genesis_key, { vote_blocks });
node.vote_processor.vote_blocking (vote, std::make_shared<nano::transport::fake::channel> (node));
ASSERT_TIMELY (10s, node.active.empty ());
}
TEST (node, peers)
{
nano::test::system system (1);
auto node1 (system.nodes[0]);
ASSERT_TRUE (node1->network.empty ());
auto node2 (std::make_shared<nano::node> (system.io_ctx, system.get_available_port (), nano::unique_path (), system.work));
system.nodes.push_back (node2);
auto endpoint = node1->network.endpoint ();
nano::endpoint_key endpoint_key{ endpoint.address ().to_v6 ().to_bytes (), endpoint.port () };
auto & store = node2->store;
{
// Add a peer to the database
auto transaction (store.tx_begin_write ());
store.peer.put (transaction, endpoint_key, 37);
// Add a peer which is not contactable
store.peer.put (transaction, nano::endpoint_key{ boost::asio::ip::address_v6::any ().to_bytes (), 55555 }, 42);
}
node2->start ();
ASSERT_TIMELY (10s, !node2->network.empty () && !node1->network.empty ())
// Wait to finish TCP node ID handshakes
ASSERT_TIMELY (10s, node1->tcp_listener.realtime_count () != 0 && node2->tcp_listener.realtime_count () != 0);
// Confirm that the peers match with the endpoints we are expecting
ASSERT_EQ (1, node1->network.size ());
auto list1 (node1->network.list (2));
ASSERT_EQ (node2->get_node_id (), list1[0]->get_node_id ());
ASSERT_EQ (nano::transport::transport_type::tcp, list1[0]->get_type ());
ASSERT_EQ (1, node2->network.size ());
auto list2 (node2->network.list (2));
ASSERT_EQ (node1->get_node_id (), list2[0]->get_node_id ());
ASSERT_EQ (nano::transport::transport_type::tcp, list2[0]->get_type ());
// Uncontactable peer should not be stored
ASSERT_TIMELY_EQ (5s, store.peer.count (store.tx_begin_read ()), 1);
ASSERT_TRUE (store.peer.exists (store.tx_begin_read (), endpoint_key));
// Stop the peer node and check that it is removed from the store
system.stop_node (*node1);
// TODO: In `tcp_channels::store_all` we skip store operation when there are no peers present,
// so the best we can do here is check if network is empty
ASSERT_TIMELY (10s, node2->network.empty ());
}
TEST (node, peer_history_restart)
{
nano::test::system system (1);
auto node1 (system.nodes[0]);
ASSERT_TRUE (node1->network.empty ());
auto endpoint = node1->network.endpoint ();
nano::endpoint_key endpoint_key{ endpoint.address ().to_v6 ().to_bytes (), endpoint.port () };
auto path (nano::unique_path ());
{
auto node2 (std::make_shared<nano::node> (system.io_ctx, system.get_available_port (), path, system.work));
system.nodes.push_back (node2);
auto & store = node2->store;
{
// Add a peer to the database
auto transaction (store.tx_begin_write ());
store.peer.put (transaction, endpoint_key, 37);
}
node2->start ();
ASSERT_TIMELY (10s, !node2->network.empty ());
// Confirm that the peers match with the endpoints we are expecting
auto list (node2->network.list (2));
ASSERT_EQ (node1->network.endpoint (), list[0]->get_remote_endpoint ());
ASSERT_EQ (1, node2->network.size ());
system.stop_node (*node2);
}
// Restart node
{
nano::node_flags node_flags;
node_flags.read_only = true;
auto node3 (std::make_shared<nano::node> (system.io_ctx, system.get_available_port (), path, system.work, node_flags));
system.nodes.push_back (node3);
// Check cached peers after restart
node3->network.start ();
node3->add_initial_peers ();
auto & store = node3->store;
{
auto transaction (store.tx_begin_read ());
ASSERT_EQ (store.peer.count (transaction), 1);
ASSERT_TRUE (store.peer.exists (transaction, endpoint_key));
}
ASSERT_TIMELY (10s, !node3->network.empty ());
// Confirm that the peers match with the endpoints we are expecting
auto list (node3->network.list (2));
ASSERT_EQ (node1->network.endpoint (), list[0]->get_remote_endpoint ());
ASSERT_EQ (1, node3->network.size ());
system.stop_node (*node3);
}
}
/** This checks that a node can be opened (without being blocked) when a write lock is held elsewhere */
TEST (node, dont_write_lock_node)
{
auto path = nano::unique_path ();
std::promise<void> write_lock_held_promise;
std::promise<void> finished_promise;
std::thread ([&path, &write_lock_held_promise, &finished_promise] () {
nano::logger logger;
auto store = nano::make_store (logger, path, nano::dev::constants, false, true);
{
nano::ledger_cache ledger_cache{ store->rep_weight };
auto transaction (store->tx_begin_write ());
store->initialize (transaction, ledger_cache, nano::dev::constants);
}
// Hold write lock open until main thread is done needing it
auto transaction (store->tx_begin_write ());
write_lock_held_promise.set_value ();
finished_promise.get_future ().wait ();
})
.detach ();
write_lock_held_promise.get_future ().wait ();
// Check inactive node can finish executing while a write lock is open
nano::inactive_node node (path, nano::inactive_node_flag_defaults ());
finished_promise.set_value ();
}
TEST (node, bidirectional_tcp)
{
nano::test::system system;
nano::node_flags node_flags;
// Disable bootstrap to start elections for new blocks
node_flags.disable_legacy_bootstrap = true;
node_flags.disable_lazy_bootstrap = true;
node_flags.disable_wallet_bootstrap = true;
nano::node_config node_config (system.get_available_port ());
node_config.backlog_scan.enable = false;
auto node1 = system.add_node (node_config, node_flags);
node_config.peering_port = system.get_available_port ();
node_config.tcp.max_inbound_connections = 0; // Disable incoming TCP connections for node 2
auto node2 = system.add_node (node_config, node_flags);
// Check network connections
ASSERT_EQ (1, node1->network.size ());
ASSERT_EQ (1, node2->network.size ());
auto list1 (node1->network.list (1));
ASSERT_EQ (nano::transport::transport_type::tcp, list1[0]->get_type ());
ASSERT_NE (node2->network.endpoint (), list1[0]->get_remote_endpoint ()); // Ephemeral port
ASSERT_EQ (node2->node_id.pub, list1[0]->get_node_id ());
auto list2 (node2->network.list (1));
ASSERT_EQ (nano::transport::transport_type::tcp, list2[0]->get_type ());
ASSERT_EQ (node1->network.endpoint (), list2[0]->get_remote_endpoint ());
ASSERT_EQ (node1->node_id.pub, list2[0]->get_node_id ());
// Test block propagation from node 1
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::Knano_ratio)
.link (key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node1->work_generate_blocking (nano::dev::genesis->hash ()))
.build ();
node1->process_active (send1);
ASSERT_TIMELY (10s, node1->block_or_pruned_exists (send1->hash ()) && node2->block_or_pruned_exists (send1->hash ()));
// Test block confirmation from node 1 (add representative to node 1)
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
// Wait to find new reresentative
ASSERT_TIMELY (10s, node2->rep_crawler.representative_count () != 0);
/* Wait for confirmation
To check connection we need only node 2 confirmation status
Node 1 election can be unconfirmed because representative private key was inserted after election start (and node 2 isn't flooding new votes to principal representatives) */
bool confirmed (false);
system.deadline_set (10s);
while (!confirmed)
{
auto transaction2 = node2->ledger.tx_begin_read ();
confirmed = node2->ledger.confirmed.block_exists_or_pruned (transaction2, send1->hash ());
ASSERT_NO_ERROR (system.poll ());
}
// Test block propagation & confirmation from node 2 (remove representative from node 1)
{
auto transaction (system.wallet (0)->wallets.tx_begin_write ());
system.wallet (0)->store.erase (transaction, nano::dev::genesis_key.pub);
}
/* Test block propagation from node 2
Node 2 has only ephemeral TCP port open. Node 1 cannot establish connection to node 2 listening port */
auto send2 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (send1->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 2 * nano::Knano_ratio)
.link (key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node1->work_generate_blocking (send1->hash ()))
.build ();
node2->process_active (send2);
ASSERT_TIMELY (10s, node1->block_or_pruned_exists (send2->hash ()) && node2->block_or_pruned_exists (send2->hash ()));
// Test block confirmation from node 2 (add representative to node 2)
system.wallet (1)->insert_adhoc (nano::dev::genesis_key.prv);
// Wait to find changed reresentative
ASSERT_TIMELY (10s, node1->rep_crawler.representative_count () != 0);
/* Wait for confirmation
To check connection we need only node 1 confirmation status
Node 2 election can be unconfirmed because representative private key was inserted after election start (and node 1 isn't flooding new votes to principal representatives) */
confirmed = false;
system.deadline_set (20s);
while (!confirmed)
{
auto transaction1 = node1->ledger.tx_begin_read ();
confirmed = node1->ledger.confirmed.block_exists_or_pruned (transaction1, send2->hash ());
ASSERT_NO_ERROR (system.poll ());
}
}
TEST (node, node_sequence)
{
nano::test::system system (3);
ASSERT_EQ (0, system.nodes[0]->node_seq);
ASSERT_EQ (0, system.nodes[0]->node_seq);
ASSERT_EQ (1, system.nodes[1]->node_seq);
ASSERT_EQ (2, system.nodes[2]->node_seq);
}
/**
* This test checks that a node can generate a self generated vote to rollback an election.
* It also checks that the vote aggregrator replies with the election winner at the time.
*/
TEST (node, rollback_vote_self)
{
nano::test::system system;
nano::node_flags flags;
flags.disable_request_loop = true;
auto & node = *system.add_node (flags);
nano::state_block_builder builder;
nano::keypair key;
// send half the voting weight to a non voting rep to ensure quorum cannot be reached
auto send1 = 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 - (nano::dev::constants.genesis_amount / 2))
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (nano::dev::genesis->hash ()))
.build ();
auto open = builder.make_block ()
.account (key.pub)
.previous (0)
.representative (key.pub)
.link (send1->hash ())
.balance (nano::dev::constants.genesis_amount / 2)
.sign (key.prv, key.pub)
.work (*system.work.generate (key.pub))
.build ();
// send 1 raw
auto send2 = builder.make_block ()
.from (*send1)
.previous (send1->hash ())
.balance (send1->balance_field ().value ().number () - 1)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build ();
// fork of send2 block
auto fork = builder.make_block ()
.from (*send2)
.balance (send1->balance_field ().value ().number () - 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.build ();
// Process and mark the first 2 blocks as confirmed to allow voting
ASSERT_TRUE (nano::test::process (node, { send1, open }));
nano::test::confirm (node.ledger, open);
// wait until the rep weights have caught up with the weight transfer
ASSERT_TIMELY_EQ (5s, nano::dev::constants.genesis_amount / 2, node.weight (key.pub));
// process forked blocks, send2 will be the winner because it was first and there are no votes yet
node.process_active (send2);
std::shared_ptr<nano::election> election;
ASSERT_TIMELY (5s, election = node.active.election (send2->qualified_root ()));
node.process_active (fork);
ASSERT_TIMELY_EQ (5s, 2, election->blocks ().size ());
ASSERT_EQ (election->winner ()->hash (), send2->hash ());
{
// The write guard prevents the block processor from performing the rollback
auto write_guard = node.store.write_queue.wait (nano::store::writer::testing);
ASSERT_EQ (0, election->votes_with_weight ().size ());
// Vote with key to switch the winner
election->vote (key.pub, 0, fork->hash (), nano::vote_source::live);
ASSERT_EQ (1, election->votes_with_weight ().size ());
// The winner changed
ASSERT_EQ (election->winner ()->hash (), fork->hash ());
// Insert genesis key in the wallet
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
// Without the rollback being finished, the aggregator should not reply with any vote
node.aggregator.request ({ { send2->hash (), send2->root () } }, node.loopback_channel);
ASSERT_ALWAYS (1s, !election->votes ().contains (nano::dev::genesis_key.pub));
// Going out of the scope allows the rollback to complete
}
// A vote is eventually generated from the local representative
auto is_genesis_vote = [] (nano::vote_with_weight_info info) {
return info.representative == nano::dev::genesis_key.pub;
};
ASSERT_TIMELY_EQ (5s, 2, election->votes_with_weight ().size ());
auto votes_with_weight = election->votes_with_weight ();
ASSERT_EQ (1, std::count_if (votes_with_weight.begin (), votes_with_weight.end (), is_genesis_vote));
auto vote = std::find_if (votes_with_weight.begin (), votes_with_weight.end (), is_genesis_vote);
ASSERT_NE (votes_with_weight.end (), vote);
ASSERT_EQ (fork->hash (), vote->hash);
}
TEST (node, rollback_gap_source)
{
nano::test::system system;
nano::node_config node_config (system.get_available_port ());
node_config.backlog_scan.enable = false;
auto & node = *system.add_node (node_config);
nano::state_block_builder builder;
nano::keypair key;
auto send1 = 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 ();
// Side a of a forked open block receiving from send1
// This is a losing block
auto fork1a = builder.make_block ()
.account (key.pub)
.previous (0)
.representative (key.pub)
.link (send1->hash ())
.balance (1)
.sign (key.prv, key.pub)
.work (*system.work.generate (key.pub))
.build ();
auto send2 = builder.make_block ()
.from (*send1)
.previous (send1->hash ())
.balance (send1->balance_field ().value ().number () - 1)
.link (key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build ();
// Side b of a forked open block receiving from send2.
// This is the winning block
auto fork1b = builder.make_block ()
.from (*fork1a)
.link (send2->hash ())
.sign (key.prv, key.pub)
.build ();
// Set 'node' up with losing block 'fork1a'
ASSERT_EQ (nano::block_status::progress, node.process (send1));
ASSERT_EQ (nano::block_status::progress, node.process (fork1a));
// Node has 'fork1a' & doesn't have source 'send2' for winning 'fork1b' block
ASSERT_EQ (nullptr, node.block (send2->hash ()));
node.block_processor.force (fork1b);
ASSERT_TIMELY_EQ (5s, node.block (fork1a->hash ()), nullptr);
// Wait for the rollback (attempt to replace fork with open)
ASSERT_TIMELY_EQ (5s, node.stats.count (nano::stat::type::rollback, nano::stat::detail::open), 1);
// But replacing is not possible (missing source block - send2)
ASSERT_EQ (nullptr, node.block (fork1b->hash ()));
// Fork can be returned by some other forked node
node.process_active (fork1a);
ASSERT_TIMELY (5s, node.block (fork1a->hash ()) != nullptr);
// With send2 block in ledger election can start again to remove fork block
ASSERT_EQ (nano::block_status::progress, node.process (send2));
node.block_processor.force (fork1b);
// Wait for new rollback
ASSERT_TIMELY_EQ (5s, node.stats.count (nano::stat::type::rollback, nano::stat::detail::open), 2);
// Now fork block should be replaced with open
ASSERT_TIMELY (5s, node.block (fork1b->hash ()) != nullptr);
ASSERT_EQ (nullptr, node.block (fork1a->hash ()));
}
// Confirm a complex dependency graph starting from the first block
TEST (node, dependency_graph)
{
nano::test::system system;
nano::node_config config (system.get_available_port ());
config.backlog_scan.enable = false;
auto & node = *system.add_node (config);
nano::state_block_builder builder;
nano::keypair key1, key2, key3;
// Send to key1
auto gen_send1 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.link (key1.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 ();
// Receive from genesis
auto key1_open = builder.make_block ()
.account (key1.pub)
.previous (0)
.representative (key1.pub)
.link (gen_send1->hash ())
.balance (1)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1.pub))
.build ();
// Send to genesis
auto key1_send1 = builder.make_block ()
.account (key1.pub)
.previous (key1_open->hash ())
.representative (key1.pub)
.link (nano::dev::genesis_key.pub)
.balance (0)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1_open->hash ()))
.build ();
// Receive from key1
auto gen_receive = builder.make_block ()
.from (*gen_send1)
.previous (gen_send1->hash ())
.link (key1_send1->hash ())
.balance (nano::dev::constants.genesis_amount)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (gen_send1->hash ()))
.build ();
// Send to key2
auto gen_send2 = builder.make_block ()
.from (*gen_receive)
.previous (gen_receive->hash ())
.link (key2.pub)
.balance (gen_receive->balance_field ().value ().number () - 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (gen_receive->hash ()))
.build ();
// Receive from genesis
auto key2_open = builder.make_block ()
.account (key2.pub)
.previous (0)
.representative (key2.pub)
.link (gen_send2->hash ())
.balance (2)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (key2.pub))
.build ();
// Send to key3
auto key2_send1 = builder.make_block ()
.account (key2.pub)
.previous (key2_open->hash ())
.representative (key2.pub)
.link (key3.pub)
.balance (1)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (key2_open->hash ()))
.build ();
// Receive from key2
auto key3_open = builder.make_block ()
.account (key3.pub)
.previous (0)
.representative (key3.pub)
.link (key2_send1->hash ())
.balance (1)
.sign (key3.prv, key3.pub)
.work (*system.work.generate (key3.pub))
.build ();
// Send to key1
auto key2_send2 = builder.make_block ()
.from (*key2_send1)
.previous (key2_send1->hash ())
.link (key1.pub)
.balance (key2_send1->balance_field ().value ().number () - 1)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (key2_send1->hash ()))
.build ();
// Receive from key2
auto key1_receive = builder.make_block ()
.from (*key1_send1)
.previous (key1_send1->hash ())
.link (key2_send2->hash ())
.balance (key1_send1->balance_field ().value ().number () + 1)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1_send1->hash ()))
.build ();
// Send to key3
auto key1_send2 = builder.make_block ()
.from (*key1_receive)
.previous (key1_receive->hash ())
.link (key3.pub)
.balance (key1_receive->balance_field ().value ().number () - 1)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1_receive->hash ()))
.build ();
// Receive from key1
auto key3_receive = builder.make_block ()
.from (*key3_open)
.previous (key3_open->hash ())
.link (key1_send2->hash ())
.balance (key3_open->balance_field ().value ().number () + 1)
.sign (key3.prv, key3.pub)
.work (*system.work.generate (key3_open->hash ()))
.build ();
// Upgrade key3
auto key3_epoch = builder.make_block ()
.from (*key3_receive)
.previous (key3_receive->hash ())
.link (node.ledger.epoch_link (nano::epoch::epoch_1))
.balance (key3_receive->balance_field ().value ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (key3_receive->hash ()))
.build ();
ASSERT_EQ (nano::block_status::progress, node.process (gen_send1));
ASSERT_EQ (nano::block_status::progress, node.process (key1_open));
ASSERT_EQ (nano::block_status::progress, node.process (key1_send1));
ASSERT_EQ (nano::block_status::progress, node.process (gen_receive));
ASSERT_EQ (nano::block_status::progress, node.process (gen_send2));
ASSERT_EQ (nano::block_status::progress, node.process (key2_open));
ASSERT_EQ (nano::block_status::progress, node.process (key2_send1));
ASSERT_EQ (nano::block_status::progress, node.process (key3_open));
ASSERT_EQ (nano::block_status::progress, node.process (key2_send2));
ASSERT_EQ (nano::block_status::progress, node.process (key1_receive));
ASSERT_EQ (nano::block_status::progress, node.process (key1_send2));
ASSERT_EQ (nano::block_status::progress, node.process (key3_receive));
ASSERT_EQ (nano::block_status::progress, node.process (key3_epoch));
ASSERT_TRUE (node.active.empty ());
// Hash -> Ancestors
std::unordered_map<nano::block_hash, std::vector<nano::block_hash>> dependency_graph{
{ key1_open->hash (), { gen_send1->hash () } },
{ key1_send1->hash (), { key1_open->hash () } },
{ gen_receive->hash (), { gen_send1->hash (), key1_open->hash () } },
{ gen_send2->hash (), { gen_receive->hash () } },
{ key2_open->hash (), { gen_send2->hash () } },
{ key2_send1->hash (), { key2_open->hash () } },
{ key3_open->hash (), { key2_send1->hash () } },
{ key2_send2->hash (), { key2_send1->hash () } },
{ key1_receive->hash (), { key1_send1->hash (), key2_send2->hash () } },
{ key1_send2->hash (), { key1_send1->hash () } },
{ key3_receive->hash (), { key3_open->hash (), key1_send2->hash () } },
{ key3_epoch->hash (), { key3_receive->hash () } },
};
ASSERT_EQ (node.ledger.block_count () - 2, dependency_graph.size ());
// Start an election for the first block of the dependency graph, and ensure all blocks are eventually confirmed
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
node.start_election (gen_send1);
ASSERT_NO_ERROR (system.poll_until_true (15s, [&] {
// Not many blocks should be active simultaneously
EXPECT_LT (node.active.size (), 6);
// Ensure that active blocks have their ancestors confirmed
auto error = std::any_of (dependency_graph.cbegin (), dependency_graph.cend (), [&] (auto entry) {
if (node.vote_router.active (entry.first))
{
for (auto ancestor : entry.second)
{
if (!node.block_confirmed (ancestor))
{
return true;
}
}
}
return false;
});
EXPECT_FALSE (error);
return error || node.ledger.cemented_count () == node.ledger.block_count ();
}));
ASSERT_EQ (node.ledger.cemented_count (), node.ledger.block_count ());
ASSERT_TIMELY (5s, node.active.empty ());
}
// Confirm a complex dependency graph. Uses frontiers confirmation which will fail to
// confirm a frontier optimistically then fallback to pessimistic confirmation.
TEST (node, dependency_graph_frontier)
{
nano::test::system system;
nano::node_config config (system.get_available_port ());
config.backlog_scan.enable = false;
auto & node1 = *system.add_node (config);
config.peering_port = system.get_available_port ();
config.backlog_scan.enable = true;
auto & node2 = *system.add_node (config);
nano::state_block_builder builder;
nano::keypair key1, key2, key3;
// Send to key1
auto gen_send1 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.link (key1.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 ();
// Receive from genesis
auto key1_open = builder.make_block ()
.account (key1.pub)
.previous (0)
.representative (key1.pub)
.link (gen_send1->hash ())
.balance (1)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1.pub))
.build ();
// Send to genesis
auto key1_send1 = builder.make_block ()
.account (key1.pub)
.previous (key1_open->hash ())
.representative (key1.pub)
.link (nano::dev::genesis_key.pub)
.balance (0)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1_open->hash ()))
.build ();
// Receive from key1
auto gen_receive = builder.make_block ()
.from (*gen_send1)
.previous (gen_send1->hash ())
.link (key1_send1->hash ())
.balance (nano::dev::constants.genesis_amount)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (gen_send1->hash ()))
.build ();
// Send to key2
auto gen_send2 = builder.make_block ()
.from (*gen_receive)
.previous (gen_receive->hash ())
.link (key2.pub)
.balance (gen_receive->balance_field ().value ().number () - 2)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (gen_receive->hash ()))
.build ();
// Receive from genesis
auto key2_open = builder.make_block ()
.account (key2.pub)
.previous (0)
.representative (key2.pub)
.link (gen_send2->hash ())
.balance (2)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (key2.pub))
.build ();
// Send to key3
auto key2_send1 = builder.make_block ()
.account (key2.pub)
.previous (key2_open->hash ())
.representative (key2.pub)
.link (key3.pub)
.balance (1)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (key2_open->hash ()))
.build ();
// Receive from key2
auto key3_open = builder.make_block ()
.account (key3.pub)
.previous (0)
.representative (key3.pub)
.link (key2_send1->hash ())
.balance (1)
.sign (key3.prv, key3.pub)
.work (*system.work.generate (key3.pub))
.build ();
// Send to key1
auto key2_send2 = builder.make_block ()
.from (*key2_send1)
.previous (key2_send1->hash ())
.link (key1.pub)
.balance (key2_send1->balance_field ().value ().number () - 1)
.sign (key2.prv, key2.pub)
.work (*system.work.generate (key2_send1->hash ()))
.build ();
// Receive from key2
auto key1_receive = builder.make_block ()
.from (*key1_send1)
.previous (key1_send1->hash ())
.link (key2_send2->hash ())
.balance (key1_send1->balance_field ().value ().number () + 1)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1_send1->hash ()))
.build ();
// Send to key3
auto key1_send2 = builder.make_block ()
.from (*key1_receive)
.previous (key1_receive->hash ())
.link (key3.pub)
.balance (key1_receive->balance_field ().value ().number () - 1)
.sign (key1.prv, key1.pub)
.work (*system.work.generate (key1_receive->hash ()))
.build ();
// Receive from key1
auto key3_receive = builder.make_block ()
.from (*key3_open)
.previous (key3_open->hash ())
.link (key1_send2->hash ())
.balance (key3_open->balance_field ().value ().number () + 1)
.sign (key3.prv, key3.pub)
.work (*system.work.generate (key3_open->hash ()))
.build ();
// Upgrade key3
auto key3_epoch = builder.make_block ()
.from (*key3_receive)
.previous (key3_receive->hash ())
.link (node1.ledger.epoch_link (nano::epoch::epoch_1))
.balance (key3_receive->balance_field ().value ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (key3_receive->hash ()))
.build ();
for (auto const & node : system.nodes)
{
auto transaction = node->ledger.tx_begin_write ();
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, gen_send1));
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, key1_open));
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, key1_send1));
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, gen_receive));
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, gen_send2));
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, key2_open));
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, key2_send1));
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, key3_open));
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, key2_send2));
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, key1_receive));
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, key1_send2));
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, key3_receive));
ASSERT_EQ (nano::block_status::progress, node->ledger.process (transaction, key3_epoch));
}
// node1 can vote, but only on the first block
system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv);
ASSERT_TIMELY (10s, node2.active.active (gen_send1->qualified_root ()));
node1.start_election (gen_send1);
ASSERT_TIMELY_EQ (15s, node1.ledger.cemented_count (), node1.ledger.block_count ());
ASSERT_TIMELY_EQ (15s, node2.ledger.cemented_count (), node2.ledger.block_count ());
}
namespace nano
{
TEST (node, deferred_dependent_elections)
{
nano::test::system system;
nano::node_config node_config_1{ system.get_available_port () };
node_config_1.backlog_scan.enable = false;
nano::node_config node_config_2{ system.get_available_port () };
node_config_2.backlog_scan.enable = false;
nano::node_flags flags;
flags.disable_request_loop = true;
auto & node = *system.add_node (node_config_1, flags);
auto & node2 = *system.add_node (node_config_2, flags); // node2 will be used to ensure all blocks are being propagated
nano::state_block_builder builder;
nano::keypair key;
auto send1 = 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 ();
auto open = builder.make_block ()
.account (key.pub)
.previous (0)
.representative (key.pub)
.link (send1->hash ())
.balance (1)
.sign (key.prv, key.pub)
.work (*system.work.generate (key.pub))
.build ();
auto send2 = builder.make_block ()
.from (*send1)
.previous (send1->hash ())
.balance (send1->balance_field ().value ().number () - 1)
.link (key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (send1->hash ()))
.build ();
auto receive = builder.make_block ()
.from (*open)
.previous (open->hash ())
.link (send2->hash ())
.balance (2)
.sign (key.prv, key.pub)
.work (*system.work.generate (open->hash ()))
.build ();
auto fork = builder.make_block ()
.from (*receive)
.representative (nano::dev::genesis_key.pub) // was key.pub
.sign (key.prv, key.pub)
.build ();
nano::test::process (node, { send1 });
auto election_send1 = nano::test::start_election (system, node, send1->hash ());
ASSERT_NE (nullptr, election_send1);
// Should process and republish but not start an election for any dependent blocks
nano::test::process (node, { open, send2 });
ASSERT_TIMELY (5s, node.block (open->hash ()));
ASSERT_TIMELY (5s, node.block (send2->hash ()));
ASSERT_NEVER (0.5s, node.active.active (open->qualified_root ()) || node.active.active (send2->qualified_root ()));
ASSERT_TIMELY (5s, node2.block (open->hash ()));
ASSERT_TIMELY (5s, node2.block (send2->hash ()));
// Re-processing older blocks with updated work also does not start an election
node.work_generate_blocking (*open, nano::dev::network_params.work.difficulty (*open) + 1);
node.process_local (open);
ASSERT_NEVER (0.5s, node.active.active (open->qualified_root ()));
// It is however possible to manually start an election from elsewhere
ASSERT_TRUE (nano::test::start_election (system, node, open->hash ()));
node.active.erase (*open);
ASSERT_FALSE (node.active.active (open->qualified_root ()));
/// The election was dropped but it's still not possible to restart it
node.work_generate_blocking (*open, nano::dev::network_params.work.difficulty (*open) + 1);
ASSERT_FALSE (node.active.active (open->qualified_root ()));
node.process_local (open);
ASSERT_NEVER (0.5s, node.active.active (open->qualified_root ()));
// Drop both elections
node.active.erase (*open);
ASSERT_FALSE (node.active.active (open->qualified_root ()));
node.active.erase (*send2);
ASSERT_FALSE (node.active.active (send2->qualified_root ()));
// Confirming send1 will automatically start elections for the dependents
election_send1->force_confirm ();
ASSERT_TIMELY (5s, node.block_confirmed (send1->hash ()));
ASSERT_TIMELY (5s, node.active.active (open->qualified_root ()));
ASSERT_TIMELY (5s, node.active.active (send2->qualified_root ()));
auto election_open = node.active.election (open->qualified_root ());
ASSERT_NE (nullptr, election_open);
auto election_send2 = node.active.election (send2->qualified_root ());
ASSERT_NE (nullptr, election_open);
// Confirm one of the dependents of the receive but not the other, to ensure both have to be confirmed to start an election on processing
ASSERT_EQ (nano::block_status::progress, node.process (receive));
ASSERT_FALSE (node.active.active (receive->qualified_root ()));
election_open->force_confirm ();
ASSERT_TIMELY (5s, node.block_confirmed (open->hash ()));
ASSERT_FALSE (node.ledger.dependents_confirmed (node.ledger.tx_begin_read (), *receive));
ASSERT_NEVER (0.5s, node.active.active (receive->qualified_root ()));
ASSERT_FALSE (node.ledger.rollback (node.ledger.tx_begin_write (), receive->hash ()));
ASSERT_FALSE (node.block (receive->hash ()));
node.process_local (receive);
ASSERT_TIMELY (5s, node.block (receive->hash ()));
ASSERT_NEVER (0.5s, node.active.active (receive->qualified_root ()));
// Processing a fork will also not start an election
ASSERT_EQ (nano::block_status::fork, node.process (fork));
node.process_local (fork);
ASSERT_NEVER (0.5s, node.active.active (receive->qualified_root ()));
// Confirming the other dependency allows starting an election from a fork
election_send2->force_confirm ();
ASSERT_TIMELY (5s, node.block_confirmed (send2->hash ()));
ASSERT_TIMELY (5s, node.active.active (receive->qualified_root ()));
}
}
// Test that a node configured with `enable_pruning` and `max_pruning_age = 1s` will automatically
// prune old confirmed blocks without explicitly saying `node.ledger_pruning` in the unit test
TEST (node, pruning_automatic)
{
nano::test::system system{};
nano::node_config node_config{ system.get_available_port () };
// TODO: remove after allowing pruned voting
node_config.enable_voting = false;
node_config.max_pruning_age = std::chrono::seconds (1);
nano::node_flags node_flags{};
node_flags.enable_pruning = true;
auto & node1 = *system.add_node (node_config, node_flags);
nano::keypair key1{};
nano::send_block_builder builder{};
auto latest_hash = nano::dev::genesis->hash ();
auto send1 = builder.make_block ()
.previous (latest_hash)
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Knano_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest_hash))
.build ();
node1.process_active (send1);
latest_hash = send1->hash ();
auto send2 = builder.make_block ()
.previous (latest_hash)
.destination (key1.pub)
.balance (0)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest_hash))
.build ();
node1.process_active (send2);
ASSERT_TIMELY (5s, node1.block (send2->hash ()) != nullptr);
// Force-confirm both blocks
node1.confirming_set.add (send1->hash ());
ASSERT_TIMELY (5s, node1.block_confirmed (send1->hash ()));
node1.confirming_set.add (send2->hash ());
ASSERT_TIMELY (5s, node1.block_confirmed (send2->hash ()));
// Check pruning result
ASSERT_EQ (3, node1.ledger.block_count ());
ASSERT_TIMELY_EQ (5s, node1.ledger.pruned_count (), 1);
ASSERT_TIMELY_EQ (5s, node1.store.pruned.count (node1.store.tx_begin_read ()), 1);
ASSERT_EQ (1, node1.ledger.pruned_count ());
ASSERT_EQ (3, node1.ledger.block_count ());
ASSERT_TRUE (nano::test::block_or_pruned_all_exists (node1, { nano::dev::genesis, send1, send2 }));
}
TEST (node, DISABLED_pruning_age)
{
nano::test::system system{};
nano::node_config node_config{ system.get_available_port () };
// TODO: remove after allowing pruned voting
node_config.enable_voting = false;
nano::node_flags node_flags{};
node_flags.enable_pruning = true;
auto & node1 = *system.add_node (node_config, node_flags);
nano::keypair key1{};
nano::send_block_builder builder{};
auto latest_hash = nano::dev::genesis->hash ();
auto send1 = builder.make_block ()
.previous (latest_hash)
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Knano_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest_hash))
.build ();
node1.process_active (send1);
latest_hash = send1->hash ();
auto send2 = builder.make_block ()
.previous (latest_hash)
.destination (key1.pub)
.balance (0)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest_hash))
.build ();
node1.process_active (send2);
// Force-confirm both blocks
node1.confirming_set.add (send1->hash ());
ASSERT_TIMELY (5s, node1.block_confirmed (send1->hash ()));
node1.confirming_set.add (send2->hash ());
ASSERT_TIMELY (5s, node1.block_confirmed (send2->hash ()));
// Three blocks in total, nothing pruned yet
ASSERT_EQ (0, node1.ledger.pruned_count ());
ASSERT_EQ (3, node1.ledger.block_count ());
// Pruning with default age 1 day
node1.pruning.ledger_pruning (1, true);
ASSERT_EQ (0, node1.ledger.pruned_count ());
ASSERT_EQ (3, node1.ledger.block_count ());
// Pruning with max age 0
node1.config.max_pruning_age = std::chrono::seconds{ 0 };
node1.pruning.ledger_pruning (1, true);
ASSERT_EQ (1, node1.ledger.pruned_count ());
ASSERT_EQ (3, node1.ledger.block_count ());
ASSERT_TRUE (nano::test::block_or_pruned_all_exists (node1, { nano::dev::genesis, send1, send2 }));
}
// Test that a node configured with `enable_pruning` will
// prune DEEP-enough confirmed blocks by explicitly saying `node.ledger_pruning` in the unit test
TEST (node, DISABLED_pruning_depth)
{
nano::test::system system{};
nano::node_config node_config{ system.get_available_port () };
// TODO: remove after allowing pruned voting
node_config.enable_voting = false;
nano::node_flags node_flags{};
node_flags.enable_pruning = true;
auto & node1 = *system.add_node (node_config, node_flags);
nano::keypair key1{};
nano::send_block_builder builder{};
auto latest_hash = nano::dev::genesis->hash ();
auto send1 = builder.make_block ()
.previous (latest_hash)
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Knano_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest_hash))
.build ();
node1.process_active (send1);
latest_hash = send1->hash ();
auto send2 = builder.make_block ()
.previous (latest_hash)
.destination (key1.pub)
.balance (0)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest_hash))
.build ();
node1.process_active (send2);
// Force-confirm both blocks
node1.confirming_set.add (send1->hash ());
ASSERT_TIMELY (5s, node1.block_confirmed (send1->hash ()));
node1.confirming_set.add (send2->hash ());
ASSERT_TIMELY (5s, node1.block_confirmed (send2->hash ()));
// Three blocks in total, nothing pruned yet
ASSERT_EQ (0, node1.ledger.pruned_count ());
ASSERT_EQ (3, node1.ledger.block_count ());
// Pruning with default depth (unlimited)
node1.pruning.ledger_pruning (1, true);
ASSERT_EQ (0, node1.ledger.pruned_count ());
ASSERT_EQ (3, node1.ledger.block_count ());
// Pruning with max depth 1
node1.config.max_pruning_depth = 1;
node1.pruning.ledger_pruning (1, true);
ASSERT_EQ (1, node1.ledger.pruned_count ());
ASSERT_EQ (3, node1.ledger.block_count ());
ASSERT_TRUE (nano::test::block_or_pruned_all_exists (node1, { nano::dev::genesis, send1, send2 }));
}
TEST (node_config, node_id_private_key_persistence)
{
nano::test::system system;
// create the directory and the file
auto path = nano::unique_path ();
ASSERT_TRUE (std::filesystem::exists (path));
auto priv_key_filename = path / "node_id_private.key";
// check that the key generated is random when the key does not exist
nano::keypair kp1 = nano::load_or_create_node_id (path);
std::filesystem::remove (priv_key_filename);
nano::keypair kp2 = nano::load_or_create_node_id (path);
ASSERT_NE (kp1.prv, kp2.prv);
// check that the key persists
nano::keypair kp3 = nano::load_or_create_node_id (path);
ASSERT_EQ (kp2.prv, kp3.prv);
// write the key file manually and check that right key is loaded
std::ofstream ofs (priv_key_filename.string (), std::ofstream::out | std::ofstream::trunc);
ofs << "3F28D035B8AA75EA53DF753BFD065CF6138E742971B2C99B84FD8FE328FED2D9" << std::flush;
ofs.close ();
nano::keypair kp4 = nano::load_or_create_node_id (path);
ASSERT_EQ (kp4.prv, nano::keypair ("3F28D035B8AA75EA53DF753BFD065CF6138E742971B2C99B84FD8FE328FED2D9").prv);
}
TEST (node, port_mapping)
{
nano::test::system system;
auto node = system.add_node ();
node->port_mapping.refresh_devices ();
}
TEST (node, process_local_overflow)
{
nano::test::system system;
auto config = system.default_config ();
config.block_processor.max_system_queue = 0;
auto & node = *system.add_node (config);
nano::keypair key1;
nano::send_block_builder builder;
auto latest_hash = nano::dev::genesis->hash ();
auto send1 = builder.make_block ()
.previous (latest_hash)
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Knano_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest_hash))
.build ();
auto result = node.process_local (send1);
ASSERT_FALSE (result);
}
TEST (node, local_block_broadcast)
{
nano::test::system system;
// Disable active elections to prevent the block from being broadcasted by the election
auto node_config = system.default_config ();
node_config.priority_scheduler.enable = false;
node_config.hinted_scheduler.enable = false;
node_config.optimistic_scheduler.enable = false;
node_config.local_block_broadcaster.rebroadcast_interval = 1s;
auto & node1 = *system.add_node (node_config);
auto & node2 = *system.make_disconnected_node ();
nano::keypair key1;
nano::send_block_builder builder;
auto latest_hash = nano::dev::genesis->hash ();
auto send1 = builder.make_block ()
.previous (latest_hash)
.destination (key1.pub)
.balance (nano::dev::constants.genesis_amount - nano::Knano_ratio)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*system.work.generate (latest_hash))
.build ();
auto result = node1.process_local (send1);
ASSERT_TRUE (result);
ASSERT_NEVER (500ms, node1.active.active (send1->qualified_root ()));
// Wait until a broadcast is attempted
ASSERT_TIMELY_EQ (5s, node1.local_block_broadcaster.size (), 1);
ASSERT_TIMELY (5s, node1.stats.count (nano::stat::type::local_block_broadcaster, nano::stat::detail::broadcast, nano::stat::dir::out) >= 1);
// The other node should not have received the block
ASSERT_NEVER (500ms, node2.block (send1->hash ()));
// Connect the nodes and check that the block is propagated
node1.network.merge_peer (node2.network.endpoint ());
ASSERT_TIMELY (5s, node1.network.find_node_id (node2.get_node_id ()));
ASSERT_TIMELY (10s, node2.block (send1->hash ()));
}
TEST (node, container_info)
{
nano::test::system system;
auto & node1 = *system.add_node ();
auto & node2 = *system.add_node ();
// Generate some random activity
std::vector<nano::account> accounts;
auto dev_genesis_key = nano::dev::genesis_key;
system.wallet (0)->insert_adhoc (dev_genesis_key.prv);
accounts.push_back (dev_genesis_key.pub);
for (int n = 0; n < 10; ++n)
{
system.generate_activity (node1, accounts);
}
// This should just execute, sanitizers will catch any problems
ASSERT_NO_THROW (node1.container_info ());
ASSERT_NO_THROW (node2.container_info ());
}
TEST (node, bounded_backlog)
{
nano::test::system system;
nano::node_config node_config;
node_config.max_backlog = 10;
node_config.backlog_scan.enable = false;
auto & node = *system.add_node (node_config);
const int howmany_blocks = 64;
const int howmany_chains = 16;
auto chains = nano::test::setup_chains (system, node, howmany_chains, howmany_blocks, nano::dev::genesis_key, /* do not confirm */ false);
node.backlog_scan.trigger ();
ASSERT_TIMELY_EQ (20s, node.ledger.block_count (), 11); // 10 + genesis
}