diff --git a/nano/core_test/ledger.cpp b/nano/core_test/ledger.cpp index 9639d135..0186dcb8 100644 --- a/nano/core_test/ledger.cpp +++ b/nano/core_test/ledger.cpp @@ -3420,6 +3420,18 @@ TEST (ledger, pruning_action) ASSERT_TRUE (store->pruned_exists (transaction, send1.hash ())); ASSERT_TRUE (store->block_exists (transaction, genesis.hash ())); ASSERT_TRUE (store->block_exists (transaction, send2.hash ())); + // Receiving pruned block + nano::state_block receive1 (nano::genesis_account, send2.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, send1.hash (), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (send2.hash ())); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, receive1).code); + ASSERT_TRUE (store->block_exists (transaction, receive1.hash ())); + auto receive1_stored (store->block_get (transaction, receive1.hash ())); + ASSERT_NE (nullptr, receive1_stored); + ASSERT_EQ (receive1, *receive1_stored); + ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ()))); + ASSERT_EQ (4, receive1_stored->sideband ().height); + ASSERT_FALSE (receive1_stored->sideband ().details.is_send); + ASSERT_TRUE (receive1_stored->sideband ().details.is_receive); + ASSERT_FALSE (receive1_stored->sideband ().details.is_epoch); // Middle block pruning ASSERT_TRUE (store->block_exists (transaction, send2.hash ())); ASSERT_EQ (1, ledger.pruning_action (transaction, send2.hash (), 1)); @@ -3467,6 +3479,166 @@ TEST (ledger, pruning_large_chain) ASSERT_EQ (1, store->block_count (transaction)); // Genesis } +TEST (ledger, pruning_source_rollback) +{ + nano::logger_mt logger; + auto store = nano::make_store (logger, nano::unique_path ()); + ASSERT_TRUE (!store->init_error ()); + nano::stat stats; + nano::ledger ledger (*store, stats); + ledger.pruning = true; + nano::genesis genesis; + auto transaction (store->tx_begin_write ()); + store->initialize (transaction, genesis, ledger.cache); + nano::work_pool pool (std::numeric_limits::max ()); + nano::state_block epoch1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount, ledger.epoch_link (nano::epoch::epoch_1), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (genesis.hash ())); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, epoch1).code); + nano::state_block send1 (nano::genesis_account, epoch1.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (epoch1.hash ())); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); + ASSERT_TRUE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ()))); + nano::state_block send2 (nano::genesis_account, send1.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::genesis_account, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (send1.hash ())); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send2).code); + ASSERT_TRUE (store->block_exists (transaction, send2.hash ())); + // Pruning action + ASSERT_EQ (2, ledger.pruning_action (transaction, send1.hash (), 1)); + ASSERT_FALSE (store->block_exists (transaction, send1.hash ())); + ASSERT_TRUE (store->pruned_exists (transaction, send1.hash ())); + ASSERT_FALSE (store->block_exists (transaction, epoch1.hash ())); + ASSERT_TRUE (store->pruned_exists (transaction, epoch1.hash ())); + ASSERT_TRUE (store->block_exists (transaction, genesis.hash ())); + nano::pending_info info; + ASSERT_FALSE (store->pending_get (transaction, nano::pending_key (nano::genesis_account, send1.hash ()), info)); + ASSERT_EQ (nano::genesis_account, info.source); + ASSERT_EQ (nano::Gxrb_ratio, info.amount.number ()); + ASSERT_EQ (nano::epoch::epoch_1, info.epoch); + // Receiving pruned block + nano::state_block receive1 (nano::genesis_account, send2.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, send1.hash (), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (send2.hash ())); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, receive1).code); + ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ()))); + ASSERT_EQ (2, ledger.cache.pruned_count); + ASSERT_EQ (5, ledger.cache.block_count); + // Rollback receive block + ASSERT_FALSE (ledger.rollback (transaction, receive1.hash ())); + nano::pending_info info2; + ASSERT_FALSE (store->pending_get (transaction, nano::pending_key (nano::genesis_account, send1.hash ()), info2)); + ASSERT_NE (nano::genesis_account, info2.source); // Tradeoff to not store pruned blocks accounts + ASSERT_EQ (nano::Gxrb_ratio, info2.amount.number ()); + ASSERT_EQ (nano::epoch::epoch_1, info2.epoch); + // Process receive block again + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, receive1).code); + ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ()))); + ASSERT_EQ (2, ledger.cache.pruned_count); + ASSERT_EQ (5, ledger.cache.block_count); +} + +TEST (ledger, pruning_source_rollback_legacy) +{ + nano::logger_mt logger; + auto store = nano::make_store (logger, nano::unique_path ()); + ASSERT_TRUE (!store->init_error ()); + nano::stat stats; + nano::ledger ledger (*store, stats); + ledger.pruning = true; + nano::genesis genesis; + auto transaction (store->tx_begin_write ()); + store->initialize (transaction, genesis, ledger.cache); + nano::work_pool pool (std::numeric_limits::max ()); + nano::send_block send1 (genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (genesis.hash ())); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); + ASSERT_TRUE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ()))); + nano::keypair key1; + nano::send_block send2 (send1.hash (), key1.pub, nano::genesis_amount - 2 * nano::Gxrb_ratio, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (send1.hash ())); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send2).code); + ASSERT_TRUE (store->block_exists (transaction, send2.hash ())); + ASSERT_TRUE (store->pending_exists (transaction, nano::pending_key (key1.pub, send2.hash ()))); + nano::send_block send3 (send2.hash (), nano::genesis_account, nano::genesis_amount - 3 * nano::Gxrb_ratio, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (send2.hash ())); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send3).code); + ASSERT_TRUE (store->block_exists (transaction, send3.hash ())); + ASSERT_TRUE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send3.hash ()))); + // Pruning action + ASSERT_EQ (2, ledger.pruning_action (transaction, send2.hash (), 1)); + ASSERT_FALSE (store->block_exists (transaction, send2.hash ())); + ASSERT_TRUE (store->pruned_exists (transaction, send2.hash ())); + ASSERT_FALSE (store->block_exists (transaction, send1.hash ())); + ASSERT_TRUE (store->pruned_exists (transaction, send1.hash ())); + ASSERT_TRUE (store->block_exists (transaction, genesis.hash ())); + nano::pending_info info1; + ASSERT_FALSE (store->pending_get (transaction, nano::pending_key (nano::genesis_account, send1.hash ()), info1)); + ASSERT_EQ (nano::genesis_account, info1.source); + ASSERT_EQ (nano::Gxrb_ratio, info1.amount.number ()); + ASSERT_EQ (nano::epoch::epoch_0, info1.epoch); + nano::pending_info info2; + ASSERT_FALSE (store->pending_get (transaction, nano::pending_key (key1.pub, send2.hash ()), info2)); + ASSERT_EQ (nano::genesis_account, info2.source); + ASSERT_EQ (nano::Gxrb_ratio, info2.amount.number ()); + ASSERT_EQ (nano::epoch::epoch_0, info2.epoch); + // Receiving pruned block + nano::receive_block receive1 (send3.hash (), send1.hash (), nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (send3.hash ())); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, receive1).code); + ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ()))); + ASSERT_EQ (2, ledger.cache.pruned_count); + ASSERT_EQ (5, ledger.cache.block_count); + // Rollback receive block + ASSERT_FALSE (ledger.rollback (transaction, receive1.hash ())); + nano::pending_info info3; + ASSERT_FALSE (store->pending_get (transaction, nano::pending_key (nano::genesis_account, send1.hash ()), info3)); + ASSERT_NE (nano::genesis_account, info3.source); // Tradeoff to not store pruned blocks accounts + ASSERT_EQ (nano::Gxrb_ratio, info3.amount.number ()); + ASSERT_EQ (nano::epoch::epoch_0, info3.epoch); + // Process receive block again + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, receive1).code); + ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (nano::genesis_account, send1.hash ()))); + ASSERT_EQ (2, ledger.cache.pruned_count); + ASSERT_EQ (5, ledger.cache.block_count); + // Receiving pruned block (open) + nano::open_block open1 (send2.hash (), nano::genesis_account, key1.pub, key1.prv, key1.pub, *pool.generate (key1.pub)); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, open1).code); + ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (key1.pub, send2.hash ()))); + ASSERT_EQ (2, ledger.cache.pruned_count); + ASSERT_EQ (6, ledger.cache.block_count); + // Rollback open block + ASSERT_FALSE (ledger.rollback (transaction, open1.hash ())); + nano::pending_info info4; + ASSERT_FALSE (store->pending_get (transaction, nano::pending_key (key1.pub, send2.hash ()), info4)); + ASSERT_NE (nano::genesis_account, info4.source); // Tradeoff to not store pruned blocks accounts + ASSERT_EQ (nano::Gxrb_ratio, info4.amount.number ()); + ASSERT_EQ (nano::epoch::epoch_0, info4.epoch); + // Process open block again + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, open1).code); + ASSERT_FALSE (store->pending_exists (transaction, nano::pending_key (key1.pub, send2.hash ()))); + ASSERT_EQ (2, ledger.cache.pruned_count); + ASSERT_EQ (6, ledger.cache.block_count); +} + +TEST (ledger, pruning_process_error) +{ + nano::logger_mt logger; + auto store = nano::make_store (logger, nano::unique_path ()); + ASSERT_TRUE (!store->init_error ()); + nano::stat stats; + nano::ledger ledger (*store, stats); + ledger.pruning = true; + nano::genesis genesis; + auto transaction (store->tx_begin_write ()); + store->initialize (transaction, genesis, ledger.cache); + nano::work_pool pool (std::numeric_limits::max ()); + nano::state_block send1 (nano::genesis_account, genesis.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (genesis.hash ())); + ASSERT_EQ (nano::process_result::progress, ledger.process (transaction, send1).code); + ASSERT_EQ (0, ledger.cache.pruned_count); + ASSERT_EQ (2, ledger.cache.block_count); + // Pruning action for latest block (not valid action) + ASSERT_EQ (1, ledger.pruning_action (transaction, send1.hash (), 1)); + ASSERT_FALSE (store->block_exists (transaction, send1.hash ())); + ASSERT_TRUE (store->pruned_exists (transaction, send1.hash ())); + // Attempt to process pruned block again + ASSERT_EQ (nano::process_result::old, ledger.process (transaction, send1).code); + // Attept to process new block after pruned + nano::state_block send2 (nano::genesis_account, send1.hash (), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio * 2, nano::genesis_account, nano::dev_genesis_key.prv, nano::dev_genesis_key.pub, *pool.generate (send1.hash ())); + ASSERT_EQ (nano::process_result::gap_previous, ledger.process (transaction, send2).code); + ASSERT_EQ (1, ledger.cache.pruned_count); + ASSERT_EQ (2, ledger.cache.block_count); +} + TEST (ledger, pruning_legacy_blocks) { nano::logger_mt logger; @@ -3598,4 +3770,4 @@ TEST (ledger, hash_root_random) done = (root_hash.first == send2.hash ()) && (root_hash.second == send2.root ().as_block_hash ()); ASSERT_LE (iteration, 1000); } -} \ No newline at end of file +} diff --git a/nano/secure/ledger.cpp b/nano/secure/ledger.cpp index f1312d81..2733a454 100644 --- a/nano/secure/ledger.cpp +++ b/nano/secure/ledger.cpp @@ -35,8 +35,7 @@ public: if (!error) { nano::account_info info; - auto error (ledger.store.account_get (transaction, pending.source, info)); - (void)error; + [[maybe_unused]] auto error (ledger.store.account_get (transaction, pending.source, info)); debug_assert (!error); ledger.store.pending_del (transaction, key); ledger.cache.rep_weights.representation_add (info.representative, pending.amount.number ()); @@ -52,12 +51,13 @@ public: void receive_block (nano::receive_block const & block_a) override { auto hash (block_a.hash ()); - auto amount (ledger.amount (transaction, block_a.hashables.source)); + auto amount (ledger.amount (transaction, hash)); auto destination_account (ledger.account (transaction, hash)); - auto source_account (ledger.account (transaction, block_a.hashables.source)); + // Pending account entry can be incorrect if source block was pruned. But it's not affecting correct ledger processing + [[maybe_unused]] bool is_pruned (false); + auto source_account (ledger.account_safe (transaction, block_a.hashables.source, is_pruned)); nano::account_info info; - auto error (ledger.store.account_get (transaction, destination_account, info)); - (void)error; + [[maybe_unused]] auto error (ledger.store.account_get (transaction, destination_account, info)); debug_assert (!error); ledger.cache.rep_weights.representation_add (info.representative, 0 - amount); nano::account_info new_info (block_a.hashables.previous, info.representative, info.open_block, ledger.balance (transaction, block_a.hashables.previous), nano::seconds_since_epoch (), info.block_count - 1, nano::epoch::epoch_0); @@ -72,9 +72,11 @@ public: void open_block (nano::open_block const & block_a) override { auto hash (block_a.hash ()); - auto amount (ledger.amount (transaction, block_a.hashables.source)); + auto amount (ledger.amount (transaction, hash)); auto destination_account (ledger.account (transaction, hash)); - auto source_account (ledger.account (transaction, block_a.hashables.source)); + // Pending account entry can be incorrect if source block was pruned. But it's not affecting correct ledger processing + [[maybe_unused]] bool is_pruned (false); + auto source_account (ledger.account_safe (transaction, block_a.hashables.source, is_pruned)); ledger.cache.rep_weights.representation_add (block_a.representative (), 0 - amount); nano::account_info new_info; ledger.update_account (transaction, destination_account, new_info, new_info); @@ -89,8 +91,7 @@ public: auto rep_block (ledger.representative (transaction, block_a.hashables.previous)); auto account (ledger.account (transaction, block_a.hashables.previous)); nano::account_info info; - auto error (ledger.store.account_get (transaction, account, info)); - (void)error; + [[maybe_unused]] auto error (ledger.store.account_get (transaction, account, info)); debug_assert (!error); auto balance (ledger.balance (transaction, block_a.hashables.previous)); auto block = ledger.store.block_get (transaction, rep_block); @@ -145,7 +146,10 @@ public: } else if (!block_a.hashables.link.is_zero () && !ledger.is_epoch_link (block_a.hashables.link)) { - nano::pending_info pending_info (ledger.account (transaction, block_a.hashables.link.as_block_hash ()), block_a.hashables.balance.number () - balance, block_a.sideband ().source_epoch); + // Pending account entry can be incorrect if source block was pruned. But it's not affecting correct ledger processing + [[maybe_unused]] bool is_pruned (false); + auto source_account (ledger.account_safe (transaction, block_a.hashables.link.as_block_hash (), is_pruned)); + nano::pending_info pending_info (source_account, block_a.hashables.balance.number () - balance, block_a.sideband ().source_epoch); ledger.store.pending_put (transaction, nano::pending_key (block_a.hashables.account, block_a.hashables.link.as_block_hash ()), pending_info); ledger.stats.inc (nano::stat::type::rollback, nano::stat::detail::receive); } @@ -260,7 +264,7 @@ void ledger_processor::state_block (nano::state_block & block_a) void ledger_processor::state_block_impl (nano::state_block & block_a) { auto hash (block_a.hash ()); - auto existing (ledger.store.block_exists (transaction, hash)); + auto existing (ledger.block_or_pruned_exists (transaction, hash)); result.code = existing ? nano::process_result::old : nano::process_result::progress; // Have we seen this block before? (Unambiguous) if (result.code == nano::process_result::progress) { @@ -318,7 +322,7 @@ void ledger_processor::state_block_impl (nano::state_block & block_a) { if (!block_a.hashables.link.is_zero ()) { - result.code = ledger.store.block_exists (transaction, block_a.hashables.link.as_block_hash ()) ? nano::process_result::progress : nano::process_result::gap_source; // Have we seen the source block already? (Harmless) + result.code = ledger.block_or_pruned_exists (transaction, block_a.hashables.link.as_block_hash ()) ? nano::process_result::progress : nano::process_result::gap_source; // Have we seen the source block already? (Harmless) if (result.code == nano::process_result::progress) { nano::pending_key key (block_a.hashables.account, block_a.hashables.link.as_block_hash ()); @@ -387,7 +391,7 @@ void ledger_processor::state_block_impl (nano::state_block & block_a) void ledger_processor::epoch_block_impl (nano::state_block & block_a) { auto hash (block_a.hash ()); - auto existing (ledger.store.block_exists (transaction, hash)); + auto existing (ledger.block_or_pruned_exists (transaction, hash)); result.code = existing ? nano::process_result::old : nano::process_result::progress; // Have we seen this block before? (Unambiguous) if (result.code == nano::process_result::progress) { @@ -477,7 +481,7 @@ void ledger_processor::epoch_block_impl (nano::state_block & block_a) void ledger_processor::change_block (nano::change_block & block_a) { auto hash (block_a.hash ()); - auto existing (ledger.store.block_exists (transaction, hash)); + auto existing (ledger.block_or_pruned_exists (transaction, hash)); result.code = existing ? nano::process_result::old : nano::process_result::progress; // Have we seen this block before? (Harmless) if (result.code == nano::process_result::progress) { @@ -531,7 +535,7 @@ void ledger_processor::change_block (nano::change_block & block_a) void ledger_processor::send_block (nano::send_block & block_a) { auto hash (block_a.hash ()); - auto existing (ledger.store.block_exists (transaction, hash)); + auto existing (ledger.block_or_pruned_exists (transaction, hash)); result.code = existing ? nano::process_result::old : nano::process_result::progress; // Have we seen this block before? (Harmless) if (result.code == nano::process_result::progress) { @@ -590,7 +594,7 @@ void ledger_processor::send_block (nano::send_block & block_a) void ledger_processor::receive_block (nano::receive_block & block_a) { auto hash (block_a.hash ()); - auto existing (ledger.store.block_exists (transaction, hash)); + auto existing (ledger.block_or_pruned_exists (transaction, hash)); result.code = existing ? nano::process_result::old : nano::process_result::progress; // Have we seen this block already? (Harmless) if (result.code == nano::process_result::progress) { @@ -614,7 +618,7 @@ void ledger_processor::receive_block (nano::receive_block & block_a) { debug_assert (!validate_message (account, hash, block_a.signature)); result.verified = nano::signature_verification::valid; - result.code = ledger.store.block_exists (transaction, block_a.hashables.source) ? nano::process_result::progress : nano::process_result::gap_source; // Have we seen the source block already? (Harmless) + result.code = ledger.block_or_pruned_exists (transaction, block_a.hashables.source) ? nano::process_result::progress : nano::process_result::gap_source; // Have we seen the source block already? (Harmless) if (result.code == nano::process_result::progress) { nano::account_info info; @@ -635,10 +639,14 @@ void ledger_processor::receive_block (nano::receive_block & block_a) if (result.code == nano::process_result::progress) { auto new_balance (info.balance.number () + pending.amount.number ()); - nano::account_info source_info; - auto error (ledger.store.account_get (transaction, pending.source, source_info)); - (void)error; - debug_assert (!error); +#ifdef NDEBUG + if (ledger.store.block_exists (transaction, block_a.hashables.source)) + { + nano::account_info source_info; + [[maybe_unused]] auto error (ledger.store.account_get (transaction, pending.source, source_info)); + debug_assert (!error); + } +#endif ledger.store.pending_del (transaction, key); block_a.sideband_set (nano::block_sideband (account, 0, new_balance, info.block_count + 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */)); ledger.store.block_put (transaction, hash, block_a); @@ -668,7 +676,7 @@ void ledger_processor::receive_block (nano::receive_block & block_a) void ledger_processor::open_block (nano::open_block & block_a) { auto hash (block_a.hash ()); - auto existing (ledger.store.block_exists (transaction, hash)); + auto existing (ledger.block_or_pruned_exists (transaction, hash)); result.code = existing ? nano::process_result::old : nano::process_result::progress; // Have we seen this block already? (Harmless) if (result.code == nano::process_result::progress) { @@ -681,7 +689,7 @@ void ledger_processor::open_block (nano::open_block & block_a) { debug_assert (!validate_message (block_a.hashables.account, hash, block_a.signature)); result.verified = nano::signature_verification::valid; - result.code = ledger.store.block_exists (transaction, block_a.hashables.source) ? nano::process_result::progress : nano::process_result::gap_source; // Have we seen the source block? (Harmless) + result.code = ledger.block_or_pruned_exists (transaction, block_a.hashables.source) ? nano::process_result::progress : nano::process_result::gap_source; // Have we seen the source block? (Harmless) if (result.code == nano::process_result::progress) { nano::account_info info; @@ -703,10 +711,14 @@ void ledger_processor::open_block (nano::open_block & block_a) result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed) if (result.code == nano::process_result::progress) { - nano::account_info source_info; - auto error (ledger.store.account_get (transaction, pending.source, source_info)); - (void)error; - debug_assert (!error); +#ifdef NDEBUG + if (ledger.store.block_exists (transaction, block_a.hashables.source)) + { + nano::account_info source_info; + [[maybe_unused]] auto error (ledger.store.account_get (transaction, pending.source, source_info)); + debug_assert (!error); + } +#endif ledger.store.pending_del (transaction, key); block_a.sideband_set (nano::block_sideband (block_a.hashables.account, 0, pending.amount, 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */)); ledger.store.block_put (transaction, hash, block_a);