diff --git a/nano/core_test/CMakeLists.txt b/nano/core_test/CMakeLists.txt index 802b96ac..16491b8b 100644 --- a/nano/core_test/CMakeLists.txt +++ b/nano/core_test/CMakeLists.txt @@ -4,6 +4,7 @@ add_executable (core_test active_transactions.cpp block.cpp block_store.cpp + confirmation_height.cpp conflicts.cpp difficulty.cpp distributed_work.cpp diff --git a/nano/core_test/confirmation_height.cpp b/nano/core_test/confirmation_height.cpp new file mode 100644 index 00000000..a6f901a0 --- /dev/null +++ b/nano/core_test/confirmation_height.cpp @@ -0,0 +1,1184 @@ +#include +#include + +#include + +using namespace std::chrono_literals; + +namespace +{ +void add_callback_stats (nano::node & node) +{ + node.observers.blocks.add ([& stats = node.stats](nano::election_status const & status_a, nano::account const &, nano::amount const &, bool) { + stats.inc (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out); + }); +} +} + +TEST (confirmation_height, single) +{ + auto amount (std::numeric_limits::max ()); + nano::system system (24000, 2); + nano::keypair key1; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + system.wallet (1)->insert_adhoc (key1.prv); + auto send1 (std::make_shared (latest1, key1.pub, amount - system.nodes[0]->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest1))); + + // Check confirmation heights before, should be uninitialized (1 for genesis). + uint64_t confirmation_height; + for (auto & node : system.nodes) + { + add_callback_stats (*node); + auto transaction = node->store.tx_begin_read (); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); + ASSERT_EQ (1, confirmation_height); + } + + for (auto & node : system.nodes) + { + node->process_active (send1); + node->block_processor.flush (); + + system.deadline_set (10s); + while (true) + { + auto transaction = node->store.tx_begin_read (); + if (node->ledger.block_confirmed (transaction, send1->hash ())) + { + break; + } + + ASSERT_NO_ERROR (system.poll ()); + } + + auto transaction = node->store.tx_begin_write (); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); + ASSERT_EQ (2, confirmation_height); + + // Rollbacks should fail as these blocks have been cemented + ASSERT_TRUE (node->ledger.rollback (transaction, latest1)); + ASSERT_TRUE (node->ledger.rollback (transaction, send1->hash ())); + ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (1, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + } +} + +TEST (confirmation_height, multiple_accounts) +{ + nano::system system; + nano::node_config node_config (24001, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + system.add_node (node_config); + node_config.peering_port = 24002; + system.add_node (node_config); + nano::keypair key1; + nano::keypair key2; + nano::keypair key3; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + system.wallet (1)->insert_adhoc (key1.prv); + system.wallet (0)->insert_adhoc (key2.prv); + system.wallet (1)->insert_adhoc (key3.prv); + + // Send to all accounts + nano::send_block send1 (latest1, key1.pub, system.nodes.front ()->config.online_weight_minimum.number () + 300, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest1)); + nano::send_block send2 (send1.hash (), key2.pub, system.nodes.front ()->config.online_weight_minimum.number () + 200, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send1.hash ())); + nano::send_block send3 (send2.hash (), key3.pub, system.nodes.front ()->config.online_weight_minimum.number () + 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send2.hash ())); + + // Open all accounts + nano::open_block open1 (send1.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, system.work.generate (key1.pub)); + nano::open_block open2 (send2.hash (), nano::genesis_account, key2.pub, key2.prv, key2.pub, system.work.generate (key2.pub)); + nano::open_block open3 (send3.hash (), nano::genesis_account, key3.pub, key3.prv, key3.pub, system.work.generate (key3.pub)); + + // Send and recieve various blocks to these accounts + nano::send_block send4 (open1.hash (), key2.pub, 50, key1.prv, key1.pub, system.work.generate (open1.hash ())); + nano::send_block send5 (send4.hash (), key2.pub, 10, key1.prv, key1.pub, system.work.generate (send4.hash ())); + + nano::receive_block receive1 (open2.hash (), send4.hash (), key2.prv, key2.pub, system.work.generate (open2.hash ())); + nano::send_block send6 (receive1.hash (), key3.pub, 10, key2.prv, key2.pub, system.work.generate (receive1.hash ())); + nano::receive_block receive2 (send6.hash (), send5.hash (), key2.prv, key2.pub, system.work.generate (send6.hash ())); + + for (auto & node : system.nodes) + { + add_callback_stats (*node); + + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open3).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send5).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send6).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive2).code); + + // Check confirmation heights of all the accounts are uninitialized (0), + // as we have any just added them to the ledger and not processed any live transactions yet. + uint64_t confirmation_height; + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); + ASSERT_EQ (1, confirmation_height); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height)); + ASSERT_EQ (0, confirmation_height); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key2.pub, confirmation_height)); + ASSERT_EQ (0, confirmation_height); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key3.pub, confirmation_height)); + ASSERT_EQ (0, confirmation_height); + } + + // The nodes process a live receive which propagates across to all accounts + auto receive3 = std::make_shared (open3.hash (), send6.hash (), key3.prv, key3.pub, system.work.generate (open3.hash ())); + + for (auto & node : system.nodes) + { + node->process_active (receive3); + node->block_processor.flush (); + + system.deadline_set (10s); + while (true) + { + auto transaction = node->store.tx_begin_read (); + if (node->ledger.block_confirmed (transaction, receive3->hash ())) + { + break; + } + + ASSERT_NO_ERROR (system.poll ()); + } + + nano::account_info account_info; + uint64_t confirmation_height; + auto & store = node->store; + auto transaction = node->store.tx_begin_read (); + ASSERT_FALSE (store.account_get (transaction, nano::test_genesis_key.pub, account_info)); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); + ASSERT_EQ (4, confirmation_height); + ASSERT_EQ (4, account_info.block_count); + ASSERT_FALSE (store.account_get (transaction, key1.pub, account_info)); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height)); + ASSERT_EQ (2, confirmation_height); + ASSERT_EQ (3, account_info.block_count); + ASSERT_FALSE (store.account_get (transaction, key2.pub, account_info)); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key2.pub, confirmation_height)); + ASSERT_EQ (3, confirmation_height); + ASSERT_EQ (4, account_info.block_count); + ASSERT_FALSE (store.account_get (transaction, key3.pub, account_info)); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key3.pub, confirmation_height)); + ASSERT_EQ (2, confirmation_height); + ASSERT_EQ (2, account_info.block_count); + + // The accounts for key1 and key2 have 1 more block in the chain than is confirmed. + // So this can be rolled back, but the one before that cannot. Check that this is the case + { + auto transaction = node->store.tx_begin_write (); + ASSERT_FALSE (node->ledger.rollback (transaction, node->latest (key2.pub))); + ASSERT_FALSE (node->ledger.rollback (transaction, node->latest (key1.pub))); + } + { + // These rollbacks should fail + auto transaction = node->store.tx_begin_write (); + ASSERT_TRUE (node->ledger.rollback (transaction, node->latest (key1.pub))); + ASSERT_TRUE (node->ledger.rollback (transaction, node->latest (key2.pub))); + + // Confirm the other latest can't be rolled back either + ASSERT_TRUE (node->ledger.rollback (transaction, node->latest (key3.pub))); + ASSERT_TRUE (node->ledger.rollback (transaction, node->latest (nano::test_genesis_key.pub))); + + // Attempt some others which have been cemented + ASSERT_TRUE (node->ledger.rollback (transaction, open1.hash ())); + ASSERT_TRUE (node->ledger.rollback (transaction, send2.hash ())); + } + ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (10, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + } +} + +TEST (confirmation_height, gap_bootstrap) +{ + nano::system system (24000, 1); + auto & node1 (*system.nodes[0]); + nano::genesis genesis; + nano::keypair destination; + auto send1 (std::make_shared (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); + node1.work_generate_blocking (*send1); + auto send2 (std::make_shared (nano::genesis_account, send1->hash (), nano::genesis_account, nano::genesis_amount - 2 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); + node1.work_generate_blocking (*send2); + auto send3 (std::make_shared (nano::genesis_account, send2->hash (), nano::genesis_account, nano::genesis_amount - 3 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); + node1.work_generate_blocking (*send3); + auto open1 (std::make_shared (send1->hash (), destination.pub, destination.pub, destination.prv, destination.pub, 0)); + node1.work_generate_blocking (*open1); + + // Receive + auto receive1 (std::make_shared (open1->hash (), send2->hash (), destination.prv, destination.pub, 0)); + node1.work_generate_blocking (*receive1); + auto receive2 (std::make_shared (receive1->hash (), send3->hash (), destination.prv, destination.pub, 0)); + node1.work_generate_blocking (*receive2); + + node1.block_processor.add (send1); + node1.block_processor.add (send2); + node1.block_processor.add (send3); + node1.block_processor.add (receive1); + node1.block_processor.flush (); + + add_callback_stats (node1); + + // Receive 2 comes in on the live network, however the chain has not been finished so it gets added to unchecked + node1.process_active (receive2); + node1.block_processor.flush (); + + // Confirmation heights should not be updated + { + auto transaction (node1.store.tx_begin_read ()); + auto unchecked_count (node1.store.unchecked_count (transaction)); + ASSERT_EQ (unchecked_count, 2); + + uint64_t confirmation_height; + ASSERT_FALSE (node1.store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); + ASSERT_EQ (1, confirmation_height); + } + + // Now complete the chain where the block comes in on the bootstrap network. + node1.block_processor.add (open1); + node1.block_processor.flush (); + + // Confirmation height should be unchanged and unchecked should now be 0 + { + auto transaction (node1.store.tx_begin_read ()); + auto unchecked_count (node1.store.unchecked_count (transaction)); + ASSERT_EQ (unchecked_count, 0); + + uint64_t confirmation_height; + ASSERT_FALSE (node1.store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); + ASSERT_EQ (1, confirmation_height); + ASSERT_FALSE (node1.store.confirmation_height_get (transaction, destination.pub, confirmation_height)); + ASSERT_EQ (0, confirmation_height); + } + ASSERT_EQ (0, node1.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (0, node1.stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); +} + +TEST (confirmation_height, gap_live) +{ + nano::system system; + nano::node_config node_config (24001, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + system.add_node (node_config); + node_config.peering_port = 24002; + system.add_node (node_config); + nano::keypair destination; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (1)->insert_adhoc (destination.prv); + + nano::genesis genesis; + auto send1 (std::make_shared (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); + system.nodes[0]->work_generate_blocking (*send1); + auto send2 (std::make_shared (nano::genesis_account, send1->hash (), nano::genesis_account, nano::genesis_amount - 2 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); + system.nodes[0]->work_generate_blocking (*send2); + auto send3 (std::make_shared (nano::genesis_account, send2->hash (), nano::genesis_account, nano::genesis_amount - 3 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); + system.nodes[0]->work_generate_blocking (*send3); + + auto open1 (std::make_shared (send1->hash (), destination.pub, destination.pub, destination.prv, destination.pub, 0)); + system.nodes[0]->work_generate_blocking (*open1); + auto receive1 (std::make_shared (open1->hash (), send2->hash (), destination.prv, destination.pub, 0)); + system.nodes[0]->work_generate_blocking (*receive1); + auto receive2 (std::make_shared (receive1->hash (), send3->hash (), destination.prv, destination.pub, 0)); + system.nodes[0]->work_generate_blocking (*receive2); + + for (auto & node : system.nodes) + { + node->block_processor.add (send1); + node->block_processor.add (send2); + node->block_processor.add (send3); + node->block_processor.add (receive1); + node->block_processor.flush (); + + add_callback_stats (*node); + + // Receive 2 comes in on the live network, however the chain has not been finished so it gets added to unchecked + node->process_active (receive2); + node->block_processor.flush (); + + // Confirmation heights should not be updated + { + auto transaction = node->store.tx_begin_read (); + uint64_t confirmation_height; + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); + ASSERT_EQ (1, confirmation_height); + } + + // Now complete the chain where the block comes in on the live network + node->process_active (open1); + node->block_processor.flush (); + + system.deadline_set (10s); + while (true) + { + auto transaction = node->store.tx_begin_read (); + if (node->ledger.block_confirmed (transaction, receive2->hash ())) + { + break; + } + + ASSERT_NO_ERROR (system.poll ()); + } + + // This should confirm the open block and the source of the receive blocks + auto transaction (node->store.tx_begin_read ()); + auto unchecked_count (node->store.unchecked_count (transaction)); + ASSERT_EQ (unchecked_count, 0); + + uint64_t confirmation_height; + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); + ASSERT_EQ (4, confirmation_height); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, destination.pub, confirmation_height)); + ASSERT_EQ (3, confirmation_height); + + ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (6, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + } +} + +TEST (confirmation_height, send_receive_between_2_accounts) +{ + nano::system system; + nano::node_config node_config (24000, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); + nano::keypair key1; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + system.wallet (0)->insert_adhoc (key1.prv); + + nano::send_block send1 (latest, key1.pub, node->config.online_weight_minimum.number () + 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); + nano::open_block open1 (send1.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, system.work.generate (key1.pub)); + + nano::send_block send2 (open1.hash (), nano::genesis_account, 1000, key1.prv, key1.pub, system.work.generate (open1.hash ())); + nano::send_block send3 (send2.hash (), nano::genesis_account, 900, key1.prv, key1.pub, system.work.generate (send2.hash ())); + nano::send_block send4 (send3.hash (), nano::genesis_account, 500, key1.prv, key1.pub, system.work.generate (send3.hash ())); + + nano::receive_block receive1 (send1.hash (), send2.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send1.hash ())); + nano::receive_block receive2 (receive1.hash (), send3.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (receive1.hash ())); + nano::receive_block receive3 (receive2.hash (), send4.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (receive2.hash ())); + + nano::send_block send5 (receive3.hash (), key1.pub, node->config.online_weight_minimum.number () + 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (receive3.hash ())); + auto receive4 = std::make_shared (send4.hash (), send5.hash (), key1.prv, key1.pub, system.work.generate (send4.hash ())); + // Unpocketed send + nano::keypair key2; + nano::send_block send6 (send5.hash (), key2.pub, node->config.online_weight_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send5.hash ())); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open1).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive1).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive3).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send5).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send6).code); + } + + add_callback_stats (*node); + + node->process_active (receive4); + node->block_processor.flush (); + + system.deadline_set (10s); + while (true) + { + auto transaction = node->store.tx_begin_read (); + if (node->ledger.block_confirmed (transaction, receive4->hash ())) + { + break; + } + + ASSERT_NO_ERROR (system.poll ()); + } + + auto transaction (node->store.tx_begin_read ()); + + nano::account_info account_info; + uint64_t confirmation_height; + ASSERT_FALSE (node->store.account_get (transaction, nano::test_genesis_key.pub, account_info)); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); + ASSERT_EQ (6, confirmation_height); + ASSERT_EQ (7, account_info.block_count); + + ASSERT_FALSE (node->store.account_get (transaction, key1.pub, account_info)); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height)); + ASSERT_EQ (5, confirmation_height); + ASSERT_EQ (5, account_info.block_count); + + ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (10, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (11, node->ledger.cemented_count); +} + +TEST (confirmation_height, send_receive_self) +{ + nano::system system; + nano::node_config node_config (24000, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + + nano::send_block send1 (latest, nano::test_genesis_key.pub, nano::genesis_amount - 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); + nano::receive_block receive1 (send1.hash (), send1.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send1.hash ())); + nano::send_block send2 (receive1.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (receive1.hash ())); + nano::send_block send3 (send2.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 3, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send2.hash ())); + + nano::receive_block receive2 (send3.hash (), send2.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send3.hash ())); + auto receive3 = std::make_shared (receive2.hash (), send3.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (receive2.hash ())); + + // Send to another account to prevent automatic receiving on the genesis account + nano::keypair key1; + nano::send_block send4 (receive3->hash (), key1.pub, node->config.online_weight_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (receive3->hash ())); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive3).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); + } + + add_callback_stats (*node); + + node->block_confirm (receive3); + + system.deadline_set (10s); + while (true) + { + auto transaction = node->store.tx_begin_read (); + if (node->ledger.block_confirmed (transaction, receive3->hash ())) + { + break; + } + + ASSERT_NO_ERROR (system.poll ()); + } + + auto transaction (node->store.tx_begin_read ()); + nano::account_info account_info; + ASSERT_FALSE (node->store.account_get (transaction, nano::test_genesis_key.pub, account_info)); + uint64_t confirmation_height; + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); + ASSERT_EQ (7, confirmation_height); + ASSERT_EQ (8, account_info.block_count); + ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (6, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (confirmation_height, node->ledger.cemented_count); +} + +TEST (confirmation_height, all_block_types) +{ + nano::system system; + nano::node_config node_config (24000, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + nano::keypair key1; + nano::keypair key2; + auto & store = node->store; + nano::send_block send (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); + nano::send_block send1 (send.hash (), key2.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send.hash ())); + + nano::open_block open (send.hash (), nano::test_genesis_key.pub, key1.pub, key1.prv, key1.pub, system.work.generate (key1.pub)); + nano::state_block state_open (key2.pub, 0, 0, nano::Gxrb_ratio, send1.hash (), key2.prv, key2.pub, system.work.generate (key2.pub)); + + nano::send_block send2 (open.hash (), key2.pub, 0, key1.prv, key1.pub, system.work.generate (open.hash ())); + nano::state_block state_receive (key2.pub, state_open.hash (), 0, nano::Gxrb_ratio * 2, send2.hash (), key2.prv, key2.pub, system.work.generate (state_open.hash ())); + + nano::state_block state_send (key2.pub, state_receive.hash (), 0, nano::Gxrb_ratio, key1.pub, key2.prv, key2.pub, system.work.generate (state_receive.hash ())); + nano::receive_block receive (send2.hash (), state_send.hash (), key1.prv, key1.pub, system.work.generate (send2.hash ())); + + nano::change_block change (receive.hash (), key2.pub, key1.prv, key1.pub, system.work.generate (receive.hash ())); + + nano::state_block state_change (key2.pub, state_send.hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, system.work.generate (state_send.hash ())); + + nano::state_block epoch (key2.pub, state_change.hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, node->ledger.link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (state_change.hash ())); + + nano::state_block epoch1 (key1.pub, change.hash (), key2.pub, nano::Gxrb_ratio, node->ledger.link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (change.hash ())); + nano::state_block state_send1 (key1.pub, epoch1.hash (), 0, nano::Gxrb_ratio - 1, key2.pub, key1.prv, key1.pub, system.work.generate (epoch1.hash ())); + nano::state_block state_receive2 (key2.pub, epoch.hash (), 0, nano::Gxrb_ratio + 1, state_send1.hash (), key2.prv, key2.pub, system.work.generate (epoch.hash ())); + + auto state_send2 = std::make_shared (key2.pub, state_receive2.hash (), 0, nano::Gxrb_ratio, key1.pub, key2.prv, key2.pub, system.work.generate (state_receive2.hash ())); + nano::state_block state_send3 (key2.pub, state_send2->hash (), 0, nano::Gxrb_ratio - 1, key1.pub, key2.prv, key2.pub, system.work.generate (state_send2->hash ())); + + nano::state_block state_send4 (key1.pub, state_send1.hash (), 0, nano::Gxrb_ratio - 2, nano::test_genesis_key.pub, key1.prv, key1.pub, system.work.generate (state_send1.hash ())); + nano::state_block state_receive3 (nano::genesis_account, send1.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio * 2 + 1, state_send4.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send1.hash ())); + + { + auto transaction (store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_open).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_receive).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, change).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_change).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, epoch).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, epoch1).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_receive2).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *state_send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send3).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send4).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_receive3).code); + } + + add_callback_stats (*node); + node->block_confirm (state_send2); + + system.deadline_set (10s); + while (true) + { + auto transaction = node->store.tx_begin_read (); + if (node->ledger.block_confirmed (transaction, state_send2->hash ())) + { + break; + } + + ASSERT_NO_ERROR (system.poll ()); + } + + auto transaction (node->store.tx_begin_read ()); + nano::account_info account_info; + uint64_t confirmation_height; + ASSERT_FALSE (node->store.account_get (transaction, nano::test_genesis_key.pub, account_info)); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); + ASSERT_EQ (3, confirmation_height); + ASSERT_LE (4, account_info.block_count); + + ASSERT_FALSE (node->store.account_get (transaction, key1.pub, account_info)); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height)); + ASSERT_EQ (6, confirmation_height); + ASSERT_LE (7, account_info.block_count); + + ASSERT_FALSE (node->store.account_get (transaction, key2.pub, account_info)); + ASSERT_FALSE (node->store.confirmation_height_get (transaction, key2.pub, confirmation_height)); + ASSERT_EQ (7, confirmation_height); + ASSERT_LE (8, account_info.block_count); + + ASSERT_EQ (15, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (15, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (16, node->ledger.cemented_count); +} + +/* Bulk of the this test was taken from the node.fork_flip test */ +TEST (confirmation_height, conflict_rollback_cemented) +{ + boost::iostreams::stream_buffer sb; + sb.open (nano::stringstream_mt_sink{}); + nano::boost_log_cerr_redirect redirect_cerr (&sb); + nano::system system (24000, 2); + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); + ASSERT_EQ (1, node1.network.size ()); + nano::keypair key1; + nano::genesis genesis; + auto send1 (std::make_shared (genesis.hash (), key1.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (genesis.hash ()))); + nano::publish publish1 (send1); + nano::keypair key2; + auto send2 (std::make_shared (genesis.hash (), key2.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (genesis.hash ()))); + nano::publish publish2 (send2); + auto channel1 (node1.network.udp_channels.create (node1.network.endpoint ())); + node1.network.process_message (publish1, channel1); + node1.block_processor.flush (); + auto channel2 (node2.network.udp_channels.create (node1.network.endpoint ())); + node2.network.process_message (publish2, channel2); + node2.block_processor.flush (); + ASSERT_EQ (1, node1.active.size ()); + ASSERT_EQ (1, node2.active.size ()); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + node1.network.process_message (publish2, channel1); + node1.block_processor.flush (); + node2.network.process_message (publish1, channel2); + node2.block_processor.flush (); + nano::unique_lock lock (node2.active.mutex); + auto conflict (node2.active.roots.find (nano::qualified_root (genesis.hash (), genesis.hash ()))); + ASSERT_NE (node2.active.roots.end (), conflict); + auto votes1 (conflict->election); + ASSERT_NE (nullptr, votes1); + ASSERT_EQ (1, votes1->last_votes.size ()); + lock.unlock (); + // Force blocks to be cemented on both nodes + { + auto transaction (system.nodes[0]->store.tx_begin_write ()); + ASSERT_TRUE (node1.store.block_exists (transaction, publish1.block->hash ())); + node1.store.confirmation_height_put (transaction, nano::genesis_account, 2); + } + { + auto transaction (system.nodes[1]->store.tx_begin_write ()); + ASSERT_TRUE (node2.store.block_exists (transaction, publish2.block->hash ())); + node2.store.confirmation_height_put (transaction, nano::genesis_account, 2); + } + + auto rollback_log_entry = boost::str (boost::format ("Failed to roll back %1%") % send2->hash ().to_string ()); + system.deadline_set (20s); + auto done (false); + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + done = (sb.component ()->str ().find (rollback_log_entry) != std::string::npos); + } + auto transaction1 (system.nodes[0]->store.tx_begin_read ()); + auto transaction2 (system.nodes[1]->store.tx_begin_read ()); + lock.lock (); + auto winner (*votes1->tally ().begin ()); + ASSERT_EQ (*publish1.block, *winner.second); + ASSERT_EQ (nano::genesis_amount - 100, winner.first); + ASSERT_TRUE (node1.store.block_exists (transaction1, publish1.block->hash ())); + ASSERT_TRUE (node2.store.block_exists (transaction2, publish2.block->hash ())); + ASSERT_FALSE (node2.store.block_exists (transaction2, publish1.block->hash ())); +} + +TEST (confirmation_height, observers) +{ + auto amount (std::numeric_limits::max ()); + nano::system system (24000, 1); + auto node1 (system.nodes[0]); + nano::keypair key1; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::block_hash latest1 (node1->latest (nano::test_genesis_key.pub)); + auto send1 (std::make_shared (latest1, key1.pub, amount - node1->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest1))); + + add_callback_stats (*node1); + + node1->process_active (send1); + node1->block_processor.flush (); + bool confirmed (false); + system.deadline_set (10s); + while (!confirmed) + { + auto transaction = node1->store.tx_begin_read (); + confirmed = node1->ledger.block_confirmed (transaction, send1->hash ()); + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (1, node1->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (1, node1->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); +} + +// This tests when a read has been done and the block no longer exists by the time a write is done +TEST (confirmation_height, modified_chain) +{ + nano::system system; + nano::node_config node_config (24000, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); + + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + + nano::keypair key1; + auto & store = node->store; + auto send = std::make_shared (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); + + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code); + } + + node->confirmation_height_processor.add (send->hash ()); + + { + // The write guard prevents the confirmation height processor doing any writes + auto write_guard = node->write_database_queue.wait (nano::writer::testing); + while (!node->write_database_queue.contains (nano::writer::confirmation_height)) + ; + + store.block_del (store.tx_begin_write (), send->hash ()); + } + + while (node->write_database_queue.contains (nano::writer::confirmation_height)) + ; + + ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::invalid_block, nano::stat::dir::in)); +} + +namespace nano +{ +TEST (confirmation_height, pending_observer_callbacks) +{ + nano::system system; + nano::node_config node_config (24000, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); + + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + + nano::keypair key1; + nano::send_block send (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); + auto send1 = std::make_shared (send.hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send.hash ())); + + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code); + } + + add_callback_stats (*node); + + node->confirmation_height_processor.add (send1->hash ()); + + while (true) + { + if (node->pending_confirmation_height.size () == 0) + { + break; + } + } + // Can have timing issues. + node->confirmation_height_processor.add (send.hash ()); + { + nano::unique_lock lk (node->pending_confirmation_height.mutex); + while (!node->pending_confirmation_height.current_hash.is_zero ()) + { + lk.unlock (); + std::this_thread::yield (); + lk.lock (); + } + } + + // Confirm the callback is not called under this circumstance + ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (0, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (3, node->ledger.cemented_count); +} + +TEST (confirmation_height, prioritize_frontiers) +{ + nano::system system; + // Prevent frontiers being confirmed as it will affect the priorization checking + nano::node_config node_config (24000, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); + + nano::keypair key1; + nano::keypair key2; + nano::keypair key3; + nano::keypair key4; + nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); + + // Send different numbers of blocks all accounts + nano::send_block send1 (latest1, key1.pub, node->config.online_weight_minimum.number () + 10000, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest1)); + nano::send_block send2 (send1.hash (), key1.pub, node->config.online_weight_minimum.number () + 8500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send1.hash ())); + nano::send_block send3 (send2.hash (), key1.pub, node->config.online_weight_minimum.number () + 8000, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send2.hash ())); + nano::send_block send4 (send3.hash (), key2.pub, node->config.online_weight_minimum.number () + 7500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send3.hash ())); + nano::send_block send5 (send4.hash (), key3.pub, node->config.online_weight_minimum.number () + 6500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send4.hash ())); + nano::send_block send6 (send5.hash (), key4.pub, node->config.online_weight_minimum.number () + 6000, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send5.hash ())); + + // Open all accounts and add other sends to get different uncemented counts (as well as some which are the same) + nano::open_block open1 (send1.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, system.work.generate (key1.pub)); + nano::send_block send7 (open1.hash (), nano::test_genesis_key.pub, 500, key1.prv, key1.pub, system.work.generate (open1.hash ())); + + nano::open_block open2 (send4.hash (), nano::genesis_account, key2.pub, key2.prv, key2.pub, system.work.generate (key2.pub)); + + nano::open_block open3 (send5.hash (), nano::genesis_account, key3.pub, key3.prv, key3.pub, system.work.generate (key3.pub)); + nano::send_block send8 (open3.hash (), nano::test_genesis_key.pub, 500, key3.prv, key3.pub, system.work.generate (open3.hash ())); + nano::send_block send9 (send8.hash (), nano::test_genesis_key.pub, 200, key3.prv, key3.pub, system.work.generate (send8.hash ())); + + nano::open_block open4 (send6.hash (), nano::genesis_account, key4.pub, key4.prv, key4.pub, system.work.generate (key4.pub)); + nano::send_block send10 (open4.hash (), nano::test_genesis_key.pub, 500, key4.prv, key4.pub, system.work.generate (open4.hash ())); + nano::send_block send11 (send10.hash (), nano::test_genesis_key.pub, 200, key4.prv, key4.pub, system.work.generate (send10.hash ())); + + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send5).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send6).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send7).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open2).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open3).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send8).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send9).code); + + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open4).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send10).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send11).code); + } + + auto transaction = node->store.tx_begin_read (); + constexpr auto num_accounts = 5; + // clang-format off + auto priority_orders_match = [](auto const & cementable_frontiers, auto const & desired_order) { + return std::equal (desired_order.begin (), desired_order.end (), cementable_frontiers.template get<1> ().begin (), cementable_frontiers.template get<1> ().end (), [](nano::account const & account, nano::cementable_account const & cementable_account) { + return (account == cementable_account.account); + }); + }; + // clang-format on + { + node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); + ASSERT_EQ (node->active.priority_cementable_frontiers_size (), num_accounts); + // Check the order of accounts is as expected (greatest number of uncemented blocks at the front). key3 and key4 have the same value, the order is unspecified so check both + std::array desired_order_1{ nano::genesis_account, key3.pub, key4.pub, key1.pub, key2.pub }; + std::array desired_order_2{ nano::genesis_account, key4.pub, key3.pub, key1.pub, key2.pub }; + ASSERT_TRUE (priority_orders_match (node->active.priority_cementable_frontiers, desired_order_1) || priority_orders_match (node->active.priority_cementable_frontiers, desired_order_2)); + } + + { + // Add some to the local node wallets and check ordering of both containers + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key1.prv); + system.wallet (0)->insert_adhoc (key2.prv); + node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); + ASSERT_EQ (node->active.priority_cementable_frontiers_size (), num_accounts - 3); + ASSERT_EQ (node->active.priority_wallet_cementable_frontiers_size (), num_accounts - 2); + std::array local_desired_order{ nano::genesis_account, key1.pub, key2.pub }; + ASSERT_TRUE (priority_orders_match (node->active.priority_wallet_cementable_frontiers, local_desired_order)); + std::array desired_order_1{ key3.pub, key4.pub }; + std::array desired_order_2{ key4.pub, key3.pub }; + ASSERT_TRUE (priority_orders_match (node->active.priority_cementable_frontiers, desired_order_1) || priority_orders_match (node->active.priority_cementable_frontiers, desired_order_2)); + } + + { + // Add the remainder of accounts to node wallets and check size/ordering is correct + system.wallet (0)->insert_adhoc (key3.prv); + system.wallet (0)->insert_adhoc (key4.prv); + node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); + ASSERT_EQ (node->active.priority_cementable_frontiers_size (), 0); + ASSERT_EQ (node->active.priority_wallet_cementable_frontiers_size (), num_accounts); + std::array desired_order_1{ nano::genesis_account, key3.pub, key4.pub, key1.pub, key2.pub }; + std::array desired_order_2{ nano::genesis_account, key4.pub, key3.pub, key1.pub, key2.pub }; + ASSERT_TRUE (priority_orders_match (node->active.priority_wallet_cementable_frontiers, desired_order_1) || priority_orders_match (node->active.priority_wallet_cementable_frontiers, desired_order_2)); + } + + // Check that accounts which already exist have their order modified when the uncemented count changes. + nano::send_block send12 (send9.hash (), nano::test_genesis_key.pub, 100, key3.prv, key3.pub, system.work.generate (send9.hash ())); + nano::send_block send13 (send12.hash (), nano::test_genesis_key.pub, 90, key3.prv, key3.pub, system.work.generate (send12.hash ())); + nano::send_block send14 (send13.hash (), nano::test_genesis_key.pub, 80, key3.prv, key3.pub, system.work.generate (send13.hash ())); + nano::send_block send15 (send14.hash (), nano::test_genesis_key.pub, 70, key3.prv, key3.pub, system.work.generate (send14.hash ())); + nano::send_block send16 (send15.hash (), nano::test_genesis_key.pub, 60, key3.prv, key3.pub, system.work.generate (send15.hash ())); + nano::send_block send17 (send16.hash (), nano::test_genesis_key.pub, 50, key3.prv, key3.pub, system.work.generate (send16.hash ())); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send12).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send13).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send14).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send15).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send16).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send17).code); + } + transaction.refresh (); + node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); + ASSERT_TRUE (priority_orders_match (node->active.priority_wallet_cementable_frontiers, std::array{ key3.pub, nano::genesis_account, key4.pub, key1.pub, key2.pub })); + node->active.confirm_frontiers (transaction); + + // Check that the active transactions roots contains the frontiers + system.deadline_set (std::chrono::seconds (10)); + while (node->active.size () != num_accounts) + { + ASSERT_NO_ERROR (system.poll ()); + } + + std::array frontiers{ send17.qualified_root (), send6.qualified_root (), send7.qualified_root (), open2.qualified_root (), send11.qualified_root () }; + for (auto & frontier : frontiers) + { + ASSERT_NE (node->active.roots.find (frontier), node->active.roots.end ()); + } +} +} + +TEST (confirmation_height, frontiers_confirmation_mode) +{ + nano::genesis genesis; + nano::keypair key; + // Always mode + { + nano::system system; + nano::node_config node_config (24000, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::always; + auto node = system.add_node (node_config); + nano::state_block send (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (genesis.hash ())); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + } + system.deadline_set (5s); + while (node->active.size () != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + } + // Auto mode + { + nano::system system; + nano::node_config node_config (24000, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::automatic; + auto node = system.add_node (node_config); + nano::state_block send (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (genesis.hash ())); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + } + system.deadline_set (5s); + while (node->active.size () != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + } + // Disabled mode + { + nano::system system; + nano::node_config node_config (24000, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); + nano::state_block send (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (genesis.hash ())); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); + } + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + std::this_thread::sleep_for (std::chrono::seconds (1)); + ASSERT_EQ (0, node->active.size ()); + } +} + +// The callback and confirmation history should only be updated after confirmation height is set (and not just after voting) +TEST (confirmation_height, callback_confirmed_history) +{ + nano::system system; + nano::node_config node_config (24000, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); + + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + + nano::keypair key1; + auto & store = node->store; + auto send = std::make_shared (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code); + } + + auto send1 = std::make_shared (send->hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send->hash ())); + + add_callback_stats (*node); + + node->process_active (send1); + node->block_processor.flush (); + + { + node->process_active (send); + node->block_processor.flush (); + // The write guard prevents the confirmation height processor doing any writes + auto write_guard = node->write_database_queue.wait (nano::writer::testing); + system.deadline_set (10s); + while (node->active.size () > 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + + ASSERT_EQ (0, node->active.list_confirmed ().size ()); + { + nano::lock_guard guard (node->active.mutex); + ASSERT_EQ (0, node->active.blocks.size ()); + } + + auto transaction = node->store.tx_begin_read (); + ASSERT_FALSE (node->ledger.block_confirmed (transaction, send->hash ())); + + system.deadline_set (10s); + while (!node->write_database_queue.contains (nano::writer::confirmation_height)) + { + ASSERT_NO_ERROR (system.poll ()); + } + + // Confirm that no inactive callbacks have been called when the confirmation height processor has already iterated over it, waiting to write + ASSERT_EQ (0, node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_inactive, nano::stat::dir::out)); + } + + system.deadline_set (10s); + while (node->write_database_queue.contains (nano::writer::confirmation_height)) + { + ASSERT_NO_ERROR (system.poll ()); + } + + auto transaction = node->store.tx_begin_read (); + ASSERT_TRUE (node->ledger.block_confirmed (transaction, send->hash ())); + + ASSERT_EQ (1, node->active.list_confirmed ().size ()); + ASSERT_EQ (0, node->active.blocks.size ()); + + // Confirm the callback is not called under this circumstance + ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + ASSERT_EQ (2, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (1, node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_active_quorum, nano::stat::dir::out)); + ASSERT_EQ (1, node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_inactive, nano::stat::dir::out)); + + nano::lock_guard guard (node->active.mutex); + ASSERT_EQ (0, node->active.pending_conf_height.size ()); +} + +namespace nano +{ +TEST (confirmation_height, dependent_election) +{ + nano::system system; + nano::node_config node_config (24001, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); + + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + + nano::keypair key1; + auto & store = node->store; + auto send = std::make_shared (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); + auto send1 = std::make_shared (send->hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send->hash ())); + auto send2 = std::make_shared (send1->hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 3, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send1->hash ())); + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send2).code); + } + + add_callback_stats (*node); + + // Prevent the confirmation height processor from doing any processing + node->confirmation_height_processor.pause (); + + // Wait until it has been processed + node->block_confirm (send2); + system.deadline_set (10s); + while (node->active.size () > 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + + system.deadline_set (10s); + while (node->pending_confirmation_height.size () != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + + { + nano::lock_guard guard (node->pending_confirmation_height.mutex); + ASSERT_EQ (*node->pending_confirmation_height.pending.begin (), send2->hash ()); + } + + // Now put the other block in active so it can be confirmed as a dependent election + node->block_confirm (send1); + node->confirmation_height_processor.unpause (); + + system.deadline_set (10s); + while (node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_active_quorum, nano::stat::dir::out) != 1 && node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_active_conf_height, nano::stat::dir::out) != 1) + { + ASSERT_NO_ERROR (system.poll ()); + } + + ASSERT_EQ (3, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); + ASSERT_EQ (1, node->stats.count (nano::stat::type::observer, nano::stat::detail::observer_confirmation_inactive, nano::stat::dir::out)); + ASSERT_EQ (3, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); + + nano::lock_guard guard (node->active.mutex); + ASSERT_EQ (0, node->active.pending_conf_height.size ()); +} + +TEST (confirmation_height, dependent_election_after_already_cemented) +{ + nano::system system; + nano::node_config node_config (24001, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); + + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + + nano::keypair key1; + auto & store = node->store; + auto send = std::make_shared (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); + auto send1 = std::make_shared (send->hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send->hash ())); + + { + auto transaction = node->store.tx_begin_write (); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code); + } + + add_callback_stats (*node); + + { + node->block_confirm (send1); + auto write_guard = node->write_database_queue.wait (nano::writer::testing); + system.deadline_set (10s); + while (node->active.size () > 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + + ASSERT_EQ (0, node->active.list_confirmed ().size ()); + { + nano::lock_guard guard (node->active.mutex); + ASSERT_EQ (0, node->active.blocks.size ()); + } + + auto transaction = node->store.tx_begin_read (); + ASSERT_FALSE (node->ledger.block_confirmed (transaction, send->hash ())); + + system.deadline_set (10s); + while (!node->write_database_queue.contains (nano::writer::confirmation_height)) + { + ASSERT_NO_ERROR (system.poll ()); + } + + node->block_confirm (send); + system.deadline_set (10s); + while (node->active.size () > 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + } + + system.deadline_set (10s); + while (node->pending_confirmation_height.size () != 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + + system.deadline_set (10s); + nano::unique_lock lk (node->active.mutex); + while (node->active.pending_conf_height.size () > 0) + { + lk.unlock (); + ASSERT_NO_ERROR (system.poll ()); + lk.lock (); + } +} +} diff --git a/nano/core_test/network.cpp b/nano/core_test/network.cpp index 08aa7d73..5eee1d88 100644 --- a/nano/core_test/network.cpp +++ b/nano/core_test/network.cpp @@ -1473,779 +1473,6 @@ TEST (bootstrap, tcp_node_id_handshake) } } -namespace -{ -void add_callback_stats (nano::node & node) -{ - node.observers.blocks.add ([& stats = node.stats](nano::election_status const & status_a, nano::account const &, nano::amount const &, bool) { - stats.inc (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out); - }); -} -} - -TEST (confirmation_height, single) -{ - auto amount (std::numeric_limits::max ()); - nano::system system (24000, 2); - nano::keypair key1; - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); - system.wallet (1)->insert_adhoc (key1.prv); - auto send1 (std::make_shared (latest1, key1.pub, amount - system.nodes[0]->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest1))); - - // Check confirmation heights before, should be uninitialized (1 for genesis). - uint64_t confirmation_height; - for (auto & node : system.nodes) - { - add_callback_stats (*node); - auto transaction = node->store.tx_begin_read (); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (1, confirmation_height); - } - - for (auto & node : system.nodes) - { - node->process_active (send1); - node->block_processor.flush (); - - system.deadline_set (10s); - while (true) - { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, send1->hash ())) - { - break; - } - - ASSERT_NO_ERROR (system.poll ()); - } - - auto transaction = node->store.tx_begin_write (); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (2, confirmation_height); - - // Rollbacks should fail as these blocks have been cemented - ASSERT_TRUE (node->ledger.rollback (transaction, latest1)); - ASSERT_TRUE (node->ledger.rollback (transaction, send1->hash ())); - ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (1, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - } -} - -TEST (confirmation_height, multiple_accounts) -{ - nano::system system; - nano::node_config node_config (24001, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - system.add_node (node_config); - node_config.peering_port = 24002; - system.add_node (node_config); - nano::keypair key1; - nano::keypair key2; - nano::keypair key3; - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); - system.wallet (1)->insert_adhoc (key1.prv); - system.wallet (0)->insert_adhoc (key2.prv); - system.wallet (1)->insert_adhoc (key3.prv); - - // Send to all accounts - nano::send_block send1 (latest1, key1.pub, system.nodes.front ()->config.online_weight_minimum.number () + 300, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest1)); - nano::send_block send2 (send1.hash (), key2.pub, system.nodes.front ()->config.online_weight_minimum.number () + 200, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send1.hash ())); - nano::send_block send3 (send2.hash (), key3.pub, system.nodes.front ()->config.online_weight_minimum.number () + 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send2.hash ())); - - // Open all accounts - nano::open_block open1 (send1.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, system.work.generate (key1.pub)); - nano::open_block open2 (send2.hash (), nano::genesis_account, key2.pub, key2.prv, key2.pub, system.work.generate (key2.pub)); - nano::open_block open3 (send3.hash (), nano::genesis_account, key3.pub, key3.prv, key3.pub, system.work.generate (key3.pub)); - - // Send and recieve various blocks to these accounts - nano::send_block send4 (open1.hash (), key2.pub, 50, key1.prv, key1.pub, system.work.generate (open1.hash ())); - nano::send_block send5 (send4.hash (), key2.pub, 10, key1.prv, key1.pub, system.work.generate (send4.hash ())); - - nano::receive_block receive1 (open2.hash (), send4.hash (), key2.prv, key2.pub, system.work.generate (open2.hash ())); - nano::send_block send6 (receive1.hash (), key3.pub, 10, key2.prv, key2.pub, system.work.generate (receive1.hash ())); - nano::receive_block receive2 (send6.hash (), send5.hash (), key2.prv, key2.pub, system.work.generate (send6.hash ())); - - for (auto & node : system.nodes) - { - add_callback_stats (*node); - - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open3).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send5).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send6).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive2).code); - - // Check confirmation heights of all the accounts are uninitialized (0), - // as we have any just added them to the ledger and not processed any live transactions yet. - uint64_t confirmation_height; - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (1, confirmation_height); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height)); - ASSERT_EQ (0, confirmation_height); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key2.pub, confirmation_height)); - ASSERT_EQ (0, confirmation_height); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key3.pub, confirmation_height)); - ASSERT_EQ (0, confirmation_height); - } - - // The nodes process a live receive which propagates across to all accounts - auto receive3 = std::make_shared (open3.hash (), send6.hash (), key3.prv, key3.pub, system.work.generate (open3.hash ())); - - for (auto & node : system.nodes) - { - node->process_active (receive3); - node->block_processor.flush (); - - system.deadline_set (10s); - while (true) - { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, receive3->hash ())) - { - break; - } - - ASSERT_NO_ERROR (system.poll ()); - } - - nano::account_info account_info; - uint64_t confirmation_height; - auto & store = node->store; - auto transaction = node->store.tx_begin_read (); - ASSERT_FALSE (store.account_get (transaction, nano::test_genesis_key.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (4, confirmation_height); - ASSERT_EQ (4, account_info.block_count); - ASSERT_FALSE (store.account_get (transaction, key1.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height)); - ASSERT_EQ (2, confirmation_height); - ASSERT_EQ (3, account_info.block_count); - ASSERT_FALSE (store.account_get (transaction, key2.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key2.pub, confirmation_height)); - ASSERT_EQ (3, confirmation_height); - ASSERT_EQ (4, account_info.block_count); - ASSERT_FALSE (store.account_get (transaction, key3.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key3.pub, confirmation_height)); - ASSERT_EQ (2, confirmation_height); - ASSERT_EQ (2, account_info.block_count); - - // The accounts for key1 and key2 have 1 more block in the chain than is confirmed. - // So this can be rolled back, but the one before that cannot. Check that this is the case - { - auto transaction = node->store.tx_begin_write (); - ASSERT_FALSE (node->ledger.rollback (transaction, node->latest (key2.pub))); - ASSERT_FALSE (node->ledger.rollback (transaction, node->latest (key1.pub))); - } - { - // These rollbacks should fail - auto transaction = node->store.tx_begin_write (); - ASSERT_TRUE (node->ledger.rollback (transaction, node->latest (key1.pub))); - ASSERT_TRUE (node->ledger.rollback (transaction, node->latest (key2.pub))); - - // Confirm the other latest can't be rolled back either - ASSERT_TRUE (node->ledger.rollback (transaction, node->latest (key3.pub))); - ASSERT_TRUE (node->ledger.rollback (transaction, node->latest (nano::test_genesis_key.pub))); - - // Attempt some others which have been cemented - ASSERT_TRUE (node->ledger.rollback (transaction, open1.hash ())); - ASSERT_TRUE (node->ledger.rollback (transaction, send2.hash ())); - } - ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (10, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - } -} - -TEST (confirmation_height, gap_bootstrap) -{ - nano::system system (24000, 1); - auto & node1 (*system.nodes[0]); - nano::genesis genesis; - nano::keypair destination; - auto send1 (std::make_shared (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - node1.work_generate_blocking (*send1); - auto send2 (std::make_shared (nano::genesis_account, send1->hash (), nano::genesis_account, nano::genesis_amount - 2 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - node1.work_generate_blocking (*send2); - auto send3 (std::make_shared (nano::genesis_account, send2->hash (), nano::genesis_account, nano::genesis_amount - 3 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - node1.work_generate_blocking (*send3); - auto open1 (std::make_shared (send1->hash (), destination.pub, destination.pub, destination.prv, destination.pub, 0)); - node1.work_generate_blocking (*open1); - - // Receive - auto receive1 (std::make_shared (open1->hash (), send2->hash (), destination.prv, destination.pub, 0)); - node1.work_generate_blocking (*receive1); - auto receive2 (std::make_shared (receive1->hash (), send3->hash (), destination.prv, destination.pub, 0)); - node1.work_generate_blocking (*receive2); - - node1.block_processor.add (send1); - node1.block_processor.add (send2); - node1.block_processor.add (send3); - node1.block_processor.add (receive1); - node1.block_processor.flush (); - - add_callback_stats (node1); - - // Receive 2 comes in on the live network, however the chain has not been finished so it gets added to unchecked - node1.process_active (receive2); - node1.block_processor.flush (); - - // Confirmation heights should not be updated - { - auto transaction (node1.store.tx_begin_read ()); - auto unchecked_count (node1.store.unchecked_count (transaction)); - ASSERT_EQ (unchecked_count, 2); - - uint64_t confirmation_height; - ASSERT_FALSE (node1.store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (1, confirmation_height); - } - - // Now complete the chain where the block comes in on the bootstrap network. - node1.block_processor.add (open1); - node1.block_processor.flush (); - - // Confirmation height should be unchanged and unchecked should now be 0 - { - auto transaction (node1.store.tx_begin_read ()); - auto unchecked_count (node1.store.unchecked_count (transaction)); - ASSERT_EQ (unchecked_count, 0); - - uint64_t confirmation_height; - ASSERT_FALSE (node1.store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (1, confirmation_height); - ASSERT_FALSE (node1.store.confirmation_height_get (transaction, destination.pub, confirmation_height)); - ASSERT_EQ (0, confirmation_height); - } - ASSERT_EQ (0, node1.stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (0, node1.stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); -} - -TEST (confirmation_height, gap_live) -{ - nano::system system; - nano::node_config node_config (24001, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - system.add_node (node_config); - node_config.peering_port = 24002; - system.add_node (node_config); - nano::keypair destination; - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - system.wallet (1)->insert_adhoc (destination.prv); - - nano::genesis genesis; - auto send1 (std::make_shared (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - system.nodes[0]->work_generate_blocking (*send1); - auto send2 (std::make_shared (nano::genesis_account, send1->hash (), nano::genesis_account, nano::genesis_amount - 2 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - system.nodes[0]->work_generate_blocking (*send2); - auto send3 (std::make_shared (nano::genesis_account, send2->hash (), nano::genesis_account, nano::genesis_amount - 3 * nano::Gxrb_ratio, destination.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0)); - system.nodes[0]->work_generate_blocking (*send3); - - auto open1 (std::make_shared (send1->hash (), destination.pub, destination.pub, destination.prv, destination.pub, 0)); - system.nodes[0]->work_generate_blocking (*open1); - auto receive1 (std::make_shared (open1->hash (), send2->hash (), destination.prv, destination.pub, 0)); - system.nodes[0]->work_generate_blocking (*receive1); - auto receive2 (std::make_shared (receive1->hash (), send3->hash (), destination.prv, destination.pub, 0)); - system.nodes[0]->work_generate_blocking (*receive2); - - for (auto & node : system.nodes) - { - node->block_processor.add (send1); - node->block_processor.add (send2); - node->block_processor.add (send3); - node->block_processor.add (receive1); - node->block_processor.flush (); - - add_callback_stats (*node); - - // Receive 2 comes in on the live network, however the chain has not been finished so it gets added to unchecked - node->process_active (receive2); - node->block_processor.flush (); - - // Confirmation heights should not be updated - { - auto transaction = node->store.tx_begin_read (); - uint64_t confirmation_height; - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (1, confirmation_height); - } - - // Now complete the chain where the block comes in on the live network - node->process_active (open1); - node->block_processor.flush (); - - system.deadline_set (10s); - while (true) - { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, receive2->hash ())) - { - break; - } - - ASSERT_NO_ERROR (system.poll ()); - } - - // This should confirm the open block and the source of the receive blocks - auto transaction (node->store.tx_begin_read ()); - auto unchecked_count (node->store.unchecked_count (transaction)); - ASSERT_EQ (unchecked_count, 0); - - uint64_t confirmation_height; - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (4, confirmation_height); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, destination.pub, confirmation_height)); - ASSERT_EQ (3, confirmation_height); - - ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (6, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - } -} - -TEST (confirmation_height, send_receive_between_2_accounts) -{ - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - nano::keypair key1; - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); - system.wallet (0)->insert_adhoc (key1.prv); - - nano::send_block send1 (latest, key1.pub, node->config.online_weight_minimum.number () + 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); - nano::open_block open1 (send1.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, system.work.generate (key1.pub)); - - nano::send_block send2 (open1.hash (), nano::genesis_account, 1000, key1.prv, key1.pub, system.work.generate (open1.hash ())); - nano::send_block send3 (send2.hash (), nano::genesis_account, 900, key1.prv, key1.pub, system.work.generate (send2.hash ())); - nano::send_block send4 (send3.hash (), nano::genesis_account, 500, key1.prv, key1.pub, system.work.generate (send3.hash ())); - - nano::receive_block receive1 (send1.hash (), send2.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send1.hash ())); - nano::receive_block receive2 (receive1.hash (), send3.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (receive1.hash ())); - nano::receive_block receive3 (receive2.hash (), send4.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (receive2.hash ())); - - nano::send_block send5 (receive3.hash (), key1.pub, node->config.online_weight_minimum.number () + 1, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (receive3.hash ())); - auto receive4 = std::make_shared (send4.hash (), send5.hash (), key1.prv, key1.pub, system.work.generate (send4.hash ())); - // Unpocketed send - nano::keypair key2; - nano::send_block send6 (send5.hash (), key2.pub, node->config.online_weight_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send5.hash ())); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open1).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive1).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive3).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send5).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send6).code); - } - - add_callback_stats (*node); - - node->process_active (receive4); - node->block_processor.flush (); - - system.deadline_set (10s); - while (true) - { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, receive4->hash ())) - { - break; - } - - ASSERT_NO_ERROR (system.poll ()); - } - - auto transaction (node->store.tx_begin_read ()); - - nano::account_info account_info; - uint64_t confirmation_height; - ASSERT_FALSE (node->store.account_get (transaction, nano::test_genesis_key.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (6, confirmation_height); - ASSERT_EQ (7, account_info.block_count); - - ASSERT_FALSE (node->store.account_get (transaction, key1.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height)); - ASSERT_EQ (5, confirmation_height); - ASSERT_EQ (5, account_info.block_count); - - ASSERT_EQ (10, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (10, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - ASSERT_EQ (11, node->ledger.cemented_count); -} - -TEST (confirmation_height, send_receive_self) -{ - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); - - nano::send_block send1 (latest, nano::test_genesis_key.pub, nano::genesis_amount - 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); - nano::receive_block receive1 (send1.hash (), send1.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send1.hash ())); - nano::send_block send2 (receive1.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (receive1.hash ())); - nano::send_block send3 (send2.hash (), nano::test_genesis_key.pub, nano::genesis_amount - 3, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send2.hash ())); - - nano::receive_block receive2 (send3.hash (), send2.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send3.hash ())); - auto receive3 = std::make_shared (receive2.hash (), send3.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (receive2.hash ())); - - // Send to another account to prevent automatic receiving on the genesis account - nano::keypair key1; - nano::send_block send4 (receive3->hash (), key1.pub, node->config.online_weight_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (receive3->hash ())); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *receive3).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); - } - - add_callback_stats (*node); - - node->block_confirm (receive3); - - system.deadline_set (10s); - while (true) - { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, receive3->hash ())) - { - break; - } - - ASSERT_NO_ERROR (system.poll ()); - } - - auto transaction (node->store.tx_begin_read ()); - nano::account_info account_info; - ASSERT_FALSE (node->store.account_get (transaction, nano::test_genesis_key.pub, account_info)); - uint64_t confirmation_height; - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (7, confirmation_height); - ASSERT_EQ (8, account_info.block_count); - ASSERT_EQ (6, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (6, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - ASSERT_EQ (confirmation_height, node->ledger.cemented_count); -} - -TEST (confirmation_height, all_block_types) -{ - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); - nano::keypair key1; - nano::keypair key2; - auto & store = node->store; - nano::send_block send (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); - nano::send_block send1 (send.hash (), key2.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send.hash ())); - - nano::open_block open (send.hash (), nano::test_genesis_key.pub, key1.pub, key1.prv, key1.pub, system.work.generate (key1.pub)); - nano::state_block state_open (key2.pub, 0, 0, nano::Gxrb_ratio, send1.hash (), key2.prv, key2.pub, system.work.generate (key2.pub)); - - nano::send_block send2 (open.hash (), key2.pub, 0, key1.prv, key1.pub, system.work.generate (open.hash ())); - nano::state_block state_receive (key2.pub, state_open.hash (), 0, nano::Gxrb_ratio * 2, send2.hash (), key2.prv, key2.pub, system.work.generate (state_open.hash ())); - - nano::state_block state_send (key2.pub, state_receive.hash (), 0, nano::Gxrb_ratio, key1.pub, key2.prv, key2.pub, system.work.generate (state_receive.hash ())); - nano::receive_block receive (send2.hash (), state_send.hash (), key1.prv, key1.pub, system.work.generate (send2.hash ())); - - nano::change_block change (receive.hash (), key2.pub, key1.prv, key1.pub, system.work.generate (receive.hash ())); - - nano::state_block state_change (key2.pub, state_send.hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, 0, key2.prv, key2.pub, system.work.generate (state_send.hash ())); - - nano::state_block epoch (key2.pub, state_change.hash (), nano::test_genesis_key.pub, nano::Gxrb_ratio, node->ledger.link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (state_change.hash ())); - - nano::state_block epoch1 (key1.pub, change.hash (), key2.pub, nano::Gxrb_ratio, node->ledger.link (nano::epoch::epoch_1), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (change.hash ())); - nano::state_block state_send1 (key1.pub, epoch1.hash (), 0, nano::Gxrb_ratio - 1, key2.pub, key1.prv, key1.pub, system.work.generate (epoch1.hash ())); - nano::state_block state_receive2 (key2.pub, epoch.hash (), 0, nano::Gxrb_ratio + 1, state_send1.hash (), key2.prv, key2.pub, system.work.generate (epoch.hash ())); - - auto state_send2 = std::make_shared (key2.pub, state_receive2.hash (), 0, nano::Gxrb_ratio, key1.pub, key2.prv, key2.pub, system.work.generate (state_receive2.hash ())); - nano::state_block state_send3 (key2.pub, state_send2->hash (), 0, nano::Gxrb_ratio - 1, key1.pub, key2.prv, key2.pub, system.work.generate (state_send2->hash ())); - - nano::state_block state_send4 (key1.pub, state_send1.hash (), 0, nano::Gxrb_ratio - 2, nano::test_genesis_key.pub, key1.prv, key1.pub, system.work.generate (state_send1.hash ())); - nano::state_block state_receive3 (nano::genesis_account, send1.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio * 2 + 1, state_send4.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send1.hash ())); - - { - auto transaction (store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_open).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_receive).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, receive).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, change).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_change).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, epoch).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, epoch1).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_receive2).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *state_send2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send3).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_send4).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, state_receive3).code); - } - - add_callback_stats (*node); - node->block_confirm (state_send2); - - system.deadline_set (10s); - while (true) - { - auto transaction = node->store.tx_begin_read (); - if (node->ledger.block_confirmed (transaction, state_send2->hash ())) - { - break; - } - - ASSERT_NO_ERROR (system.poll ()); - } - - auto transaction (node->store.tx_begin_read ()); - nano::account_info account_info; - uint64_t confirmation_height; - ASSERT_FALSE (node->store.account_get (transaction, nano::test_genesis_key.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, nano::test_genesis_key.pub, confirmation_height)); - ASSERT_EQ (3, confirmation_height); - ASSERT_LE (4, account_info.block_count); - - ASSERT_FALSE (node->store.account_get (transaction, key1.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key1.pub, confirmation_height)); - ASSERT_EQ (6, confirmation_height); - ASSERT_LE (7, account_info.block_count); - - ASSERT_FALSE (node->store.account_get (transaction, key2.pub, account_info)); - ASSERT_FALSE (node->store.confirmation_height_get (transaction, key2.pub, confirmation_height)); - ASSERT_EQ (7, confirmation_height); - ASSERT_LE (8, account_info.block_count); - - ASSERT_EQ (15, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (15, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - ASSERT_EQ (16, node->ledger.cemented_count); -} - -/* Bulk of the this test was taken from the node.fork_flip test */ -TEST (confirmation_height, conflict_rollback_cemented) -{ - boost::iostreams::stream_buffer sb; - sb.open (nano::stringstream_mt_sink{}); - nano::boost_log_cerr_redirect redirect_cerr (&sb); - nano::system system (24000, 2); - auto & node1 (*system.nodes[0]); - auto & node2 (*system.nodes[1]); - ASSERT_EQ (1, node1.network.size ()); - nano::keypair key1; - nano::genesis genesis; - auto send1 (std::make_shared (genesis.hash (), key1.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (genesis.hash ()))); - nano::publish publish1 (send1); - nano::keypair key2; - auto send2 (std::make_shared (genesis.hash (), key2.pub, nano::genesis_amount - 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (genesis.hash ()))); - nano::publish publish2 (send2); - auto channel1 (node1.network.udp_channels.create (node1.network.endpoint ())); - node1.network.process_message (publish1, channel1); - node1.block_processor.flush (); - auto channel2 (node2.network.udp_channels.create (node1.network.endpoint ())); - node2.network.process_message (publish2, channel2); - node2.block_processor.flush (); - ASSERT_EQ (1, node1.active.size ()); - ASSERT_EQ (1, node2.active.size ()); - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - node1.network.process_message (publish2, channel1); - node1.block_processor.flush (); - node2.network.process_message (publish1, channel2); - node2.block_processor.flush (); - nano::unique_lock lock (node2.active.mutex); - auto conflict (node2.active.roots.find (nano::qualified_root (genesis.hash (), genesis.hash ()))); - ASSERT_NE (node2.active.roots.end (), conflict); - auto votes1 (conflict->election); - ASSERT_NE (nullptr, votes1); - ASSERT_EQ (1, votes1->last_votes.size ()); - lock.unlock (); - // Force blocks to be cemented on both nodes - { - auto transaction (system.nodes[0]->store.tx_begin_write ()); - ASSERT_TRUE (node1.store.block_exists (transaction, publish1.block->hash ())); - node1.store.confirmation_height_put (transaction, nano::genesis_account, 2); - } - { - auto transaction (system.nodes[1]->store.tx_begin_write ()); - ASSERT_TRUE (node2.store.block_exists (transaction, publish2.block->hash ())); - node2.store.confirmation_height_put (transaction, nano::genesis_account, 2); - } - - auto rollback_log_entry = boost::str (boost::format ("Failed to roll back %1%") % send2->hash ().to_string ()); - system.deadline_set (20s); - auto done (false); - while (!done) - { - ASSERT_NO_ERROR (system.poll ()); - done = (sb.component ()->str ().find (rollback_log_entry) != std::string::npos); - } - auto transaction1 (system.nodes[0]->store.tx_begin_read ()); - auto transaction2 (system.nodes[1]->store.tx_begin_read ()); - lock.lock (); - auto winner (*votes1->tally ().begin ()); - ASSERT_EQ (*publish1.block, *winner.second); - ASSERT_EQ (nano::genesis_amount - 100, winner.first); - ASSERT_TRUE (node1.store.block_exists (transaction1, publish1.block->hash ())); - ASSERT_TRUE (node2.store.block_exists (transaction2, publish2.block->hash ())); - ASSERT_FALSE (node2.store.block_exists (transaction2, publish1.block->hash ())); -} - -TEST (confirmation_height, observers) -{ - auto amount (std::numeric_limits::max ()); - nano::system system (24000, 1); - auto node1 (system.nodes[0]); - nano::keypair key1; - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest1 (node1->latest (nano::test_genesis_key.pub)); - auto send1 (std::make_shared (latest1, key1.pub, amount - node1->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest1))); - - add_callback_stats (*node1); - - node1->process_active (send1); - node1->block_processor.flush (); - bool confirmed (false); - system.deadline_set (10s); - while (!confirmed) - { - auto transaction = node1->store.tx_begin_read (); - confirmed = node1->ledger.block_confirmed (transaction, send1->hash ()); - ASSERT_NO_ERROR (system.poll ()); - } - ASSERT_EQ (1, node1->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (1, node1->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); -} - -// This tests when a read has been done and the block no longer exists by the time a write is done -TEST (confirmation_height, modified_chain) -{ - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); - - nano::keypair key1; - auto & store = node->store; - auto send = std::make_shared (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); - - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send).code); - } - - { - // The write guard prevents the confirmation height processor doing any writes - auto write_guard = node->write_database_queue.wait (nano::writer::process_batch); - node->confirmation_height_processor.add (send->hash ()); - system.deadline_set (10s); - while (!node->write_database_queue.contains (nano::writer::confirmation_height)) - { - ASSERT_NO_ERROR (system.poll ()); - } - - store.block_del (store.tx_begin_write (), send->hash ()); - } - - system.deadline_set (10s); - while (node->write_database_queue.contains (nano::writer::confirmation_height)) - { - ASSERT_NO_ERROR (system.poll ()); - } - - ASSERT_EQ (1, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::invalid_block, nano::stat::dir::in)); -} - -namespace nano -{ -TEST (confirmation_height, pending_observer_callbacks) -{ - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); - - nano::keypair key1; - nano::send_block send (latest, key1.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); - auto send1 = std::make_shared (send.hash (), key1.pub, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send.hash ())); - - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *send1).code); - } - - add_callback_stats (*node); - - node->confirmation_height_processor.add (send1->hash ()); - - while (true) - { - if (node->pending_confirmation_height.size () == 0) - { - break; - } - } - // Can have timing issues. - node->confirmation_height_processor.add (send.hash ()); - { - nano::unique_lock lk (node->pending_confirmation_height.mutex); - while (!node->pending_confirmation_height.current_hash.is_zero ()) - { - lk.unlock (); - std::this_thread::yield (); - lk.lock (); - } - } - - // Confirm the callback is not called under this circumstance - ASSERT_EQ (2, node->stats.count (nano::stat::type::confirmation_height, nano::stat::detail::blocks_confirmed, nano::stat::dir::in)); - ASSERT_EQ (0, node->stats.count (nano::stat::type::http_callback, nano::stat::detail::http_callback, nano::stat::dir::out)); - ASSERT_EQ (3, node->ledger.cemented_count); -} -} - TEST (bootstrap, tcp_listener_timeout_empty) { nano::system system (24000, 1); diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index b179edc6..b05804bb 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -367,8 +367,10 @@ TEST (node, search_pending_multiple) TEST (node, search_pending_confirmed) { - nano::system system (24000, 1); - auto node (system.nodes[0]); + nano::system system; + nano::node_config node_config (24000, system.logging); + node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; + auto node = system.add_node (node_config); nano::keypair key2; system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); auto send1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key2.pub, node->config.receive_minimum.number ())); @@ -1679,6 +1681,7 @@ TEST (node, broadcast_elected) node2->process_active (fork1); //std::cerr << "fork0: " << fork_hash.to_string () << std::endl; //std::cerr << "fork1: " << fork1.hash ().to_string () << std::endl; + system.deadline_set (10s); while (!node0->ledger.block_exists (fork0->hash ()) || !node1->ledger.block_exists (fork0->hash ())) { ASSERT_NO_ERROR (system.poll ()); @@ -3145,203 +3148,6 @@ TEST (node, bidirectional_tcp) } } -namespace nano -{ -TEST (confirmation_height, prioritize_frontiers) -{ - nano::system system; - // Prevent frontiers being confirmed as it will affect the priorization checking - nano::node_config node_config (24001, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - - nano::keypair key1; - nano::keypair key2; - nano::keypair key3; - nano::keypair key4; - nano::block_hash latest1 (system.nodes[0]->latest (nano::test_genesis_key.pub)); - - // Send different numbers of blocks all accounts - nano::send_block send1 (latest1, key1.pub, node->config.online_weight_minimum.number () + 10000, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest1)); - nano::send_block send2 (send1.hash (), key1.pub, node->config.online_weight_minimum.number () + 8500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send1.hash ())); - nano::send_block send3 (send2.hash (), key1.pub, node->config.online_weight_minimum.number () + 8000, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send2.hash ())); - nano::send_block send4 (send3.hash (), key2.pub, node->config.online_weight_minimum.number () + 7500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send3.hash ())); - nano::send_block send5 (send4.hash (), key3.pub, node->config.online_weight_minimum.number () + 6500, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send4.hash ())); - nano::send_block send6 (send5.hash (), key4.pub, node->config.online_weight_minimum.number () + 6000, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (send5.hash ())); - - // Open all accounts and add other sends to get different uncemented counts (as well as some which are the same) - nano::open_block open1 (send1.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, system.work.generate (key1.pub)); - nano::send_block send7 (open1.hash (), nano::test_genesis_key.pub, 500, key1.prv, key1.pub, system.work.generate (open1.hash ())); - - nano::open_block open2 (send4.hash (), nano::genesis_account, key2.pub, key2.prv, key2.pub, system.work.generate (key2.pub)); - - nano::open_block open3 (send5.hash (), nano::genesis_account, key3.pub, key3.prv, key3.pub, system.work.generate (key3.pub)); - nano::send_block send8 (open3.hash (), nano::test_genesis_key.pub, 500, key3.prv, key3.pub, system.work.generate (open3.hash ())); - nano::send_block send9 (send8.hash (), nano::test_genesis_key.pub, 200, key3.prv, key3.pub, system.work.generate (send8.hash ())); - - nano::open_block open4 (send6.hash (), nano::genesis_account, key4.pub, key4.prv, key4.pub, system.work.generate (key4.pub)); - nano::send_block send10 (open4.hash (), nano::test_genesis_key.pub, 500, key4.prv, key4.pub, system.work.generate (open4.hash ())); - nano::send_block send11 (send10.hash (), nano::test_genesis_key.pub, 200, key4.prv, key4.pub, system.work.generate (send10.hash ())); - - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send2).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send3).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send4).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send5).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send6).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open1).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send7).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open2).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open3).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send8).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send9).code); - - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open4).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send10).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send11).code); - } - - auto transaction = node->store.tx_begin_read (); - constexpr auto num_accounts = 5; - // clang-format off - auto priority_orders_match = [](auto const & cementable_frontiers, auto const & desired_order) { - return std::equal (desired_order.begin (), desired_order.end (), cementable_frontiers.template get<1> ().begin (), cementable_frontiers.template get<1> ().end (), [](nano::account const & account, nano::cementable_account const & cementable_account) { - return (account == cementable_account.account); - }); - }; - // clang-format on - { - node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); - ASSERT_EQ (node->active.priority_cementable_frontiers_size (), num_accounts); - // Check the order of accounts is as expected (greatest number of uncemented blocks at the front). key3 and key4 have the same value, the order is unspecified so check both - std::array desired_order_1{ nano::genesis_account, key3.pub, key4.pub, key1.pub, key2.pub }; - std::array desired_order_2{ nano::genesis_account, key4.pub, key3.pub, key1.pub, key2.pub }; - ASSERT_TRUE (priority_orders_match (node->active.priority_cementable_frontiers, desired_order_1) || priority_orders_match (node->active.priority_cementable_frontiers, desired_order_2)); - } - - { - // Add some to the local node wallets and check ordering of both containers - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - system.wallet (0)->insert_adhoc (key1.prv); - system.wallet (0)->insert_adhoc (key2.prv); - node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); - ASSERT_EQ (node->active.priority_cementable_frontiers_size (), num_accounts - 3); - ASSERT_EQ (node->active.priority_wallet_cementable_frontiers_size (), num_accounts - 2); - std::array local_desired_order{ nano::genesis_account, key1.pub, key2.pub }; - ASSERT_TRUE (priority_orders_match (node->active.priority_wallet_cementable_frontiers, local_desired_order)); - std::array desired_order_1{ key3.pub, key4.pub }; - std::array desired_order_2{ key4.pub, key3.pub }; - ASSERT_TRUE (priority_orders_match (node->active.priority_cementable_frontiers, desired_order_1) || priority_orders_match (node->active.priority_cementable_frontiers, desired_order_2)); - } - - { - // Add the remainder of accounts to node wallets and check size/ordering is correct - system.wallet (0)->insert_adhoc (key3.prv); - system.wallet (0)->insert_adhoc (key4.prv); - node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); - ASSERT_EQ (node->active.priority_cementable_frontiers_size (), 0); - ASSERT_EQ (node->active.priority_wallet_cementable_frontiers_size (), num_accounts); - std::array desired_order_1{ nano::genesis_account, key3.pub, key4.pub, key1.pub, key2.pub }; - std::array desired_order_2{ nano::genesis_account, key4.pub, key3.pub, key1.pub, key2.pub }; - ASSERT_TRUE (priority_orders_match (node->active.priority_wallet_cementable_frontiers, desired_order_1) || priority_orders_match (node->active.priority_wallet_cementable_frontiers, desired_order_2)); - } - - // Check that accounts which already exist have their order modified when the uncemented count changes. - nano::send_block send12 (send9.hash (), nano::test_genesis_key.pub, 100, key3.prv, key3.pub, system.work.generate (send9.hash ())); - nano::send_block send13 (send12.hash (), nano::test_genesis_key.pub, 90, key3.prv, key3.pub, system.work.generate (send12.hash ())); - nano::send_block send14 (send13.hash (), nano::test_genesis_key.pub, 80, key3.prv, key3.pub, system.work.generate (send13.hash ())); - nano::send_block send15 (send14.hash (), nano::test_genesis_key.pub, 70, key3.prv, key3.pub, system.work.generate (send14.hash ())); - nano::send_block send16 (send15.hash (), nano::test_genesis_key.pub, 60, key3.prv, key3.pub, system.work.generate (send15.hash ())); - nano::send_block send17 (send16.hash (), nano::test_genesis_key.pub, 50, key3.prv, key3.pub, system.work.generate (send16.hash ())); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send12).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send13).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send14).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send15).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send16).code); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send17).code); - } - transaction.refresh (); - node->active.prioritize_frontiers_for_confirmation (transaction, std::chrono::seconds (1), std::chrono::seconds (1)); - ASSERT_TRUE (priority_orders_match (node->active.priority_wallet_cementable_frontiers, std::array{ key3.pub, nano::genesis_account, key4.pub, key1.pub, key2.pub })); - node->active.confirm_frontiers (transaction); - - // Check that the active transactions roots contains the frontiers - system.deadline_set (std::chrono::seconds (10)); - while (node->active.size () != num_accounts) - { - ASSERT_NO_ERROR (system.poll ()); - } - - std::array frontiers{ send17.qualified_root (), send6.qualified_root (), send7.qualified_root (), open2.qualified_root (), send11.qualified_root () }; - for (auto & frontier : frontiers) - { - ASSERT_NE (node->active.roots.find (frontier), node->active.roots.end ()); - } -} -} - -TEST (confirmation_height, frontiers_confirmation_mode) -{ - nano::genesis genesis; - nano::keypair key; - // Always mode - { - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::always; - auto node = system.add_node (node_config); - nano::state_block send (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (genesis.hash ())); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); - } - system.deadline_set (5s); - while (node->active.size () != 1) - { - ASSERT_NO_ERROR (system.poll ()); - } - } - // Auto mode - { - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::automatic; - auto node = system.add_node (node_config); - nano::state_block send (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (genesis.hash ())); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); - } - system.deadline_set (5s); - while (node->active.size () != 1) - { - ASSERT_NO_ERROR (system.poll ()); - } - } - // Disabled mode - { - nano::system system; - nano::node_config node_config (24000, system.logging); - node_config.frontiers_confirmation = nano::frontiers_confirmation_mode::disabled; - auto node = system.add_node (node_config); - nano::state_block send (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, *node->work_generate_blocking (genesis.hash ())); - { - auto transaction = node->store.tx_begin_write (); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send).code); - } - system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); - std::this_thread::sleep_for (std::chrono::seconds (1)); - ASSERT_EQ (0, node->active.size ()); - } -} - TEST (active_difficulty, recalculate_work) { nano::system system; diff --git a/nano/node/active_transactions.cpp b/nano/node/active_transactions.cpp index 44ab09b8..f177dbf4 100644 --- a/nano/node/active_transactions.cpp +++ b/nano/node/active_transactions.cpp @@ -101,6 +101,51 @@ void nano::active_transactions::confirm_frontiers (nano::transaction const & tra next_frontier_check = steady_clock::now () + (agressive_factor / test_network_factor); } } +void nano::active_transactions::post_confirmation_height_set (nano::transaction const & transaction_a, std::shared_ptr block_a, nano::block_sideband const & sideband_a, nano::election_status_type election_status_type_a) +{ + if (election_status_type_a == nano::election_status_type::inactive_confirmation_height) + { + nano::account account (0); + nano::uint128_t amount (0); + bool is_state_send (false); + nano::account pending_account (0); + node.process_confirmed_data (transaction_a, block_a, block_a->hash (), sideband_a, account, amount, is_state_send, pending_account); + node.observers.blocks.notify (nano::election_status{ block_a, 0, std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), nano::election_status_type::inactive_confirmation_height }, account, amount, is_state_send); + } + else + { + auto hash (block_a->hash ()); + nano::lock_guard lock (mutex); + auto existing (pending_conf_height.find (hash)); + if (existing != pending_conf_height.end ()) + { + auto election = existing->second; + if (election->confirmed && !election->stopped && election->status.winner->hash () == hash) + { + add_confirmed (existing->second->status, block_a->qualified_root ()); + + node.receive_confirmed (transaction_a, block_a, hash); + nano::account account (0); + nano::uint128_t amount (0); + bool is_state_send (false); + nano::account pending_account (0); + node.process_confirmed_data (transaction_a, block_a, hash, sideband_a, account, amount, is_state_send, pending_account); + election->status.type = election_status_type_a; + node.observers.blocks.notify (election->status, account, amount, is_state_send); + if (amount > 0) + { + node.observers.account_balance.notify (account, false); + if (!pending_account.is_zero ()) + { + node.observers.account_balance.notify (pending_account, true); + } + } + } + + pending_conf_height.erase (hash); + } + } +} void nano::active_transactions::request_confirm (nano::unique_lock & lock_a) { @@ -128,11 +173,10 @@ void nano::active_transactions::request_confirm (nano::unique_lock & auto election_l (i->election); if ((election_l->confirmed || election_l->stopped) && election_l->confirmation_request_count >= minimum_confirmation_request_count - 1) { - if (election_l->confirmed) + if (election_l->stopped) { - add_confirmed (election_l->status, root); + inactive.insert (root); } - inactive.insert (root); } else { @@ -877,7 +921,14 @@ bool nano::active_transactions::publish (std::shared_ptr block_a) return result; } -void nano::active_transactions::confirm_block (nano::transaction const & transaction_a, std::shared_ptr block_a, nano::block_sideband const & sideband_a) +void nano::active_transactions::clear_block (nano::block_hash const & hash_a) +{ + nano::lock_guard guard (mutex); + pending_conf_height.erase (hash_a); +} + +// Returns the type of election status requiring callbacks calling later +boost::optional nano::active_transactions::confirm_block (nano::transaction const & transaction_a, std::shared_ptr block_a) { auto hash (block_a->hash ()); nano::unique_lock lock (mutex); @@ -887,17 +938,16 @@ void nano::active_transactions::confirm_block (nano::transaction const & transac if (!existing->second->confirmed && !existing->second->stopped && existing->second->status.winner->hash () == hash) { existing->second->confirm_once (nano::election_status_type::active_confirmation_height); + return nano::election_status_type::active_confirmation_height; + } + else + { + return boost::optional{}; } } else { - lock.unlock (); - nano::account account (0); - nano::uint128_t amount (0); - bool is_state_send (false); - nano::account pending_account (0); - node.process_confirmed_data (transaction_a, block_a, hash, sideband_a, account, amount, is_state_send, pending_account); - node.observers.blocks.notify (nano::election_status{ block_a, 0, std::chrono::duration_cast (std::chrono::system_clock::now ().time_since_epoch ()), std::chrono::duration_values::zero (), nano::election_status_type::inactive_confirmation_height }, account, amount, is_state_send); + return nano::election_status_type::inactive_confirmation_height; } } @@ -985,17 +1035,20 @@ std::unique_ptr collect_seq_con_info (active_transaction size_t roots_count = 0; size_t blocks_count = 0; size_t confirmed_count = 0; + size_t pending_conf_height_count = 0; { nano::lock_guard guard (active_transactions.mutex); roots_count = active_transactions.roots.size (); blocks_count = active_transactions.blocks.size (); confirmed_count = active_transactions.confirmed.size (); + pending_conf_height_count = active_transactions.pending_conf_height.size (); } auto composite = std::make_unique (name); composite->add_component (std::make_unique (seq_con_info{ "roots", roots_count, sizeof (decltype (active_transactions.roots)::value_type) })); composite->add_component (std::make_unique (seq_con_info{ "blocks", blocks_count, sizeof (decltype (active_transactions.blocks)::value_type) })); + composite->add_component (std::make_unique (seq_con_info{ "pending_conf_height", pending_conf_height_count, sizeof (decltype (active_transactions.pending_conf_height)::value_type) })); composite->add_component (std::make_unique (seq_con_info{ "confirmed", confirmed_count, sizeof (decltype (active_transactions.confirmed)::value_type) })); composite->add_component (std::make_unique (seq_con_info{ "priority_wallet_cementable_frontiers_count", active_transactions.priority_wallet_cementable_frontiers_size (), sizeof (nano::cementable_account) })); composite->add_component (std::make_unique (seq_con_info{ "priority_cementable_frontiers_count", active_transactions.priority_cementable_frontiers_size (), sizeof (nano::cementable_account) })); diff --git a/nano/node/active_transactions.hpp b/nano/node/active_transactions.hpp index 4281f7af..f3d4a1f5 100644 --- a/nano/node/active_transactions.hpp +++ b/nano/node/active_transactions.hpp @@ -106,7 +106,8 @@ public: size_t size (); void stop (); bool publish (std::shared_ptr block_a); - void confirm_block (nano::transaction const &, std::shared_ptr, nano::block_sideband const &); + boost::optional confirm_block (nano::transaction const &, std::shared_ptr); + void post_confirmation_height_set (nano::transaction const & transaction_a, std::shared_ptr block_a, nano::block_sideband const & sideband_a, nano::election_status_type election_status_type_a); boost::multi_index_container< nano::conflict_info, boost::multi_index::indexed_by< @@ -136,6 +137,8 @@ public: size_t priority_wallet_cementable_frontiers_size (); boost::circular_buffer difficulty_trend (); size_t inactive_votes_cache_size (); + std::unordered_map> pending_conf_height; + void clear_block (nano::block_hash const & hash_a); private: // Call action with confirmed block, may be different than what we started with diff --git a/nano/node/confirmation_height_processor.cpp b/nano/node/confirmation_height_processor.cpp index 65cffb8f..3a19fde2 100644 --- a/nano/node/confirmation_height_processor.cpp +++ b/nano/node/confirmation_height_processor.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -48,7 +49,7 @@ void nano::confirmation_height_processor::run () nano::unique_lock lk (pending_confirmations.mutex); while (!stopped) { - if (!pending_confirmations.pending.empty ()) + if (!paused && !pending_confirmations.pending.empty ()) { pending_confirmations.current_hash = *pending_confirmations.pending.begin (); pending_confirmations.pending.erase (pending_confirmations.current_hash); @@ -83,6 +84,17 @@ void nano::confirmation_height_processor::run () } } +void nano::confirmation_height_processor::pause () +{ + paused = true; +} + +void nano::confirmation_height_processor::unpause () +{ + paused = false; + condition.notify_one (); +} + void nano::confirmation_height_processor::add (nano::block_hash const & hash_a) { { @@ -106,6 +118,7 @@ void nano::confirmation_height_processor::add_confirmation_height (nano::block_h release_assert (receive_source_pairs.empty ()); auto read_transaction (ledger.store.tx_begin_read ()); + auto last_iteration = false; // Traverse account chain and all sources for receive blocks iteratively do { @@ -123,6 +136,7 @@ void nano::confirmation_height_processor::add_confirmation_height (nano::block_h { current = hash_a; receive_details = boost::none; + last_iteration = true; } } @@ -145,7 +159,25 @@ void nano::confirmation_height_processor::add_confirmation_height (nano::block_h } } + if (!last_iteration && current == hash_a && confirmation_height >= block_height) + { + auto it = std::find_if (pending_writes.begin (), pending_writes.end (), [&hash_a](auto & conf_height_details) { + auto it = std::find_if (conf_height_details.block_callbacks_required.begin (), conf_height_details.block_callbacks_required.end (), [&hash_a](auto & callback_data) { + return callback_data.block->hash () == hash_a; + }); + return (it != conf_height_details.block_callbacks_required.end ()); + }); + + if (it == pending_writes.end ()) + { + // This is a block which has been added to the processor but already has its confirmation height set (or about to be set) + // Just need to perform active cleanup, no callbacks are needed. + active.clear_block (hash_a); + } + } + auto count_before_receive = receive_source_pairs.size (); + std::vector block_callbacks_required; if (block_height > iterated_height) { if ((block_height - iterated_height) > 20000) @@ -153,7 +185,7 @@ void nano::confirmation_height_processor::add_confirmation_height (nano::block_h logger.always_log ("Iterating over a large account chain for setting confirmation height. The top block: ", current.to_string ()); } - collect_unconfirmed_receive_and_sources_for_account (block_height, iterated_height, current, account, read_transaction); + collect_unconfirmed_receive_and_sources_for_account (block_height, iterated_height, current, account, read_transaction, block_callbacks_required); } // Exit early when the processor has been stopped, otherwise this function may take a @@ -187,7 +219,7 @@ void nano::confirmation_height_processor::add_confirmation_height (nano::block_h confirmed_iterated_pairs.emplace (account, confirmed_iterated_pair{ block_height, block_height }); } - pending_writes.emplace_back (account, current, block_height, block_height - confirmation_height); + pending_writes.emplace_back (account, current, block_height, block_height - confirmation_height, block_callbacks_required); } if (receive_details) @@ -298,6 +330,11 @@ bool nano::confirmation_height_processor::write_pending (std::deque & block_callbacks_required) { auto hash (hash_a); auto num_to_confirm = block_height_a - confirmation_height_a; @@ -335,8 +372,18 @@ void nano::confirmation_height_processor::collect_unconfirmed_receive_and_source { if (!pending_confirmations.is_processing_block (hash)) { - active.confirm_block (transaction_a, block, sideband); + auto election_status_type = active.confirm_block (transaction_a, block); + if (election_status_type.is_initialized ()) + { + block_callbacks_required.emplace_back (block, sideband, *election_status_type); + } } + else + { + // This block is the original which is having its confirmation height set on + block_callbacks_required.emplace_back (block, sideband, nano::election_status_type::active_confirmed_quorum); + } + auto source (block->source ()); if (source.is_zero ()) { @@ -350,9 +397,15 @@ void nano::confirmation_height_processor::collect_unconfirmed_receive_and_source if (next_height != height_not_set) { receive_source_pairs.back ().receive_details.num_blocks_confirmed = next_height - block_height; + + auto & receive_callbacks_required = receive_source_pairs.back ().receive_details.block_callbacks_required; + + // Don't include the last one as that belongs to the next recieve + std::copy (block_callbacks_required.begin (), block_callbacks_required.end () - 1, std::back_inserter (receive_callbacks_required)); + block_callbacks_required = { block_callbacks_required.back () }; } - receive_source_pairs.emplace_back (conf_height_details{ account_a, hash, block_height, height_not_set }, source); + receive_source_pairs.emplace_back (conf_height_details{ account_a, hash, block_height, height_not_set, {} }, source); ++receive_source_pairs_size; next_height = block_height; } @@ -374,16 +427,18 @@ void nano::confirmation_height_processor::collect_unconfirmed_receive_and_source { auto & last_receive_details = receive_source_pairs.back ().receive_details; last_receive_details.num_blocks_confirmed = last_receive_details.height - confirmation_height_a; + last_receive_details.block_callbacks_required = block_callbacks_required; } } namespace nano { -confirmation_height_processor::conf_height_details::conf_height_details (nano::account const & account_a, nano::block_hash const & hash_a, uint64_t height_a, uint64_t num_blocks_confirmed_a) : +confirmation_height_processor::conf_height_details::conf_height_details (nano::account const & account_a, nano::block_hash const & hash_a, uint64_t height_a, uint64_t num_blocks_confirmed_a, std::vector const & block_callbacks_required_a) : account (account_a), hash (hash_a), height (height_a), -num_blocks_confirmed (num_blocks_confirmed_a) +num_blocks_confirmed (num_blocks_confirmed_a), +block_callbacks_required (block_callbacks_required_a) { } @@ -398,6 +453,13 @@ confirmed_height (confirmed_height_a), iterated_height (iterated_height_a) { } +confirmation_height_processor::callback_data::callback_data (std::shared_ptr const & block_a, nano::block_sideband const & sideband_a, nano::election_status_type election_status_type_a) : +block (block_a), +sideband (sideband_a), +election_status_type (election_status_type_a) +{ +} + std::unique_ptr collect_seq_con_info (confirmation_height_processor & confirmation_height_processor_a, const std::string & name_a) { size_t receive_source_pairs_count = confirmation_height_processor_a.receive_source_pairs_size; diff --git a/nano/node/confirmation_height_processor.hpp b/nano/node/confirmation_height_processor.hpp index bd2ac46e..e1dfd28a 100644 --- a/nano/node/confirmation_height_processor.hpp +++ b/nano/node/confirmation_height_processor.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #include @@ -30,6 +32,8 @@ private: nano::block_hash current_hash{ 0 }; friend class confirmation_height_processor; friend class confirmation_height_pending_observer_callbacks_Test; + friend class confirmation_height_dependent_election_Test; + friend class confirmation_height_dependent_election_after_already_cemented_Test; }; std::unique_ptr collect_seq_con_info (pending_confirmation_height &, const std::string &); @@ -41,6 +45,8 @@ public: ~confirmation_height_processor (); void add (nano::block_hash const &); void stop (); + void pause (); + void unpause (); /** The maximum amount of accounts to iterate over while writing */ static uint64_t constexpr batch_write_size = 2048; @@ -49,15 +55,25 @@ public: static uint64_t constexpr batch_read_size = 4096; private: + class callback_data final + { + public: + callback_data (std::shared_ptr const &, nano::block_sideband const &, nano::election_status_type); + std::shared_ptr block; + nano::block_sideband sideband; + nano::election_status_type election_status_type; + }; + class conf_height_details final { public: - conf_height_details (nano::account const &, nano::block_hash const &, uint64_t, uint64_t); + conf_height_details (nano::account const &, nano::block_hash const &, uint64_t, uint64_t, std::vector const &); nano::account account; nano::block_hash hash; uint64_t height; uint64_t num_blocks_confirmed; + std::vector block_callbacks_required; }; class receive_source_pair final @@ -80,6 +96,7 @@ private: nano::condition_variable condition; nano::pending_confirmation_height & pending_confirmations; std::atomic stopped{ false }; + std::atomic paused{ false }; nano::ledger & ledger; nano::active_transactions & active; nano::logger_mt & logger; @@ -97,7 +114,7 @@ private: void run (); void add_confirmation_height (nano::block_hash const &); - void collect_unconfirmed_receive_and_sources_for_account (uint64_t, uint64_t, nano::block_hash const &, nano::account const &, nano::read_transaction const &); + void collect_unconfirmed_receive_and_sources_for_account (uint64_t, uint64_t, nano::block_hash const &, nano::account const &, nano::read_transaction const &, std::vector &); bool write_pending (std::deque &); friend std::unique_ptr collect_seq_con_info (confirmation_height_processor &, const std::string &); diff --git a/nano/node/election.cpp b/nano/node/election.cpp index 7d262579..ce2b4f8b 100644 --- a/nano/node/election.cpp +++ b/nano/node/election.cpp @@ -51,7 +51,7 @@ void nano::election::confirm_once (nano::election_status_type type_a) --node.active.long_unconfirmed_size; } auto root (status.winner->qualified_root ()); - node.active.add_confirmed (status, root); + node.active.pending_conf_height.emplace (status.winner->hash (), shared_from_this ()); clear_blocks (); clear_dependent (); node.active.roots.erase (root); diff --git a/nano/node/node.cpp b/nano/node/node.cpp index 82749df3..93d28c83 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -687,6 +687,7 @@ void nano::node::stop () if (!stopped.exchange (true)) { logger.always_log ("Node stopping"); + write_database_queue.stop (); block_processor.stop (); if (block_processor_thread.joinable ()) { @@ -706,7 +707,6 @@ void nano::node::stop () checker.stop (); wallets.stop (); stats.stop (); - write_database_queue.stop (); worker.stop (); // work pool is not stopped on purpose due to testing setup } @@ -1181,41 +1181,27 @@ void nano::node::process_confirmed_data (nano::transaction const & transaction_a void nano::node::process_confirmed (nano::election_status const & status_a, uint8_t iteration) { - auto block_a (status_a.winner); - auto hash (block_a->hash ()); - nano::block_sideband sideband; - auto transaction (store.tx_begin_read ()); - if (store.block_get (transaction, hash, &sideband) != nullptr) + if (status_a.type == nano::election_status_type::active_confirmed_quorum) { - confirmation_height_processor.add (hash); - - receive_confirmed (transaction, block_a, hash); - nano::account account (0); - nano::uint128_t amount (0); - bool is_state_send (false); - nano::account pending_account (0); - process_confirmed_data (transaction, block_a, hash, sideband, account, amount, is_state_send, pending_account); - observers.blocks.notify (status_a, account, amount, is_state_send); - if (amount > 0) + auto block_a (status_a.winner); + auto hash (block_a->hash ()); + auto transaction (store.tx_begin_read ()); + if (store.block_get (transaction, hash) != nullptr) { - observers.account_balance.notify (account, false); - if (!pending_account.is_zero ()) - { - observers.account_balance.notify (pending_account, true); - } + confirmation_height_processor.add (hash); + } + // Limit to 0.5 * 20 = 10 seconds (more than max block_processor::process_batch finish time) + else if (iteration < 20) + { + iteration++; + std::weak_ptr node_w (shared ()); + alarm.add (std::chrono::steady_clock::now () + network_params.node.process_confirmed_interval, [node_w, status_a, iteration]() { + if (auto node_l = node_w.lock ()) + { + node_l->process_confirmed (status_a, iteration); + } + }); } - } - // Limit to 0.5 * 20 = 10 seconds (more than max block_processor::process_batch finish time) - else if (iteration < 20) - { - iteration++; - std::weak_ptr node_w (shared ()); - alarm.add (std::chrono::steady_clock::now () + network_params.node.process_confirmed_interval, [node_w, status_a, iteration]() { - if (auto node_l = node_w.lock ()) - { - node_l->process_confirmed (status_a, iteration); - } - }); } } @@ -1379,12 +1365,10 @@ std::unique_ptr nano::make_store (nano::logger_mt & logger, b #if NANO_ROCKSDB /** To use RocksDB in tests make sure the node is built with the cmake variable -DNANO_ROCKSDB=ON and the environment variable TEST_USE_ROCKSDB=1 is set */ static nano::network_constants network_constants; - if (auto use_rocksdb_str = std::getenv ("TEST_USE_ROCKSDB") && network_constants.is_test_network ()) + auto use_rocksdb_str = std::getenv ("TEST_USE_ROCKSDB"); + if (use_rocksdb_str && (boost::lexical_cast (use_rocksdb_str) == 1) && network_constants.is_test_network ()) { - if (boost::lexical_cast (use_rocksdb_str) == 1) - { - return make_rocksdb (); - } + return make_rocksdb (); } #endif } diff --git a/nano/node/write_database_queue.hpp b/nano/node/write_database_queue.hpp index 5fa97c1e..59c9c9cf 100644 --- a/nano/node/write_database_queue.hpp +++ b/nano/node/write_database_queue.hpp @@ -12,7 +12,8 @@ namespace nano enum class writer { confirmation_height, - process_batch + process_batch, + testing // Used in tests to emulate a write lock }; class write_guard final