From 76de7eea48e07e819ef804507313cf040e0aa1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Wo=CC=81jcik?= <3044353+pwojcikdev@users.noreply.github.com> Date: Mon, 23 Jun 2025 00:49:29 +0200 Subject: [PATCH] Move block_processor code out of ledger file --- nano/secure/CMakeLists.txt | 2 + nano/secure/ledger.cpp | 470 +------------------------------ nano/secure/ledger_processor.cpp | 460 ++++++++++++++++++++++++++++++ nano/secure/ledger_processor.hpp | 31 ++ 4 files changed, 495 insertions(+), 468 deletions(-) create mode 100644 nano/secure/ledger_processor.cpp create mode 100644 nano/secure/ledger_processor.hpp diff --git a/nano/secure/CMakeLists.txt b/nano/secure/CMakeLists.txt index bbb864c17..8fb356d31 100644 --- a/nano/secure/CMakeLists.txt +++ b/nano/secure/CMakeLists.txt @@ -24,6 +24,8 @@ add_library( generate_cache_flags.hpp ledger.hpp ledger.cpp + ledger_processor.hpp + ledger_processor.cpp ledger_set_any.hpp ledger_set_any.cpp ledger_set_confirmed.hpp diff --git a/nano/secure/ledger.cpp b/nano/secure/ledger.cpp index 73069d4e3..0fa40f75e 100644 --- a/nano/secure/ledger.cpp +++ b/nano/secure/ledger.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -195,473 +196,6 @@ public: bool error{ false }; }; -class ledger_processor : public nano::mutable_block_visitor -{ -public: - ledger_processor (nano::ledger &, nano::secure::write_transaction const &); - virtual ~ledger_processor () = default; - void send_block (nano::send_block &) override; - void receive_block (nano::receive_block &) override; - void open_block (nano::open_block &) override; - void change_block (nano::change_block &) override; - void state_block (nano::state_block &) override; - void state_block_impl (nano::state_block &); - void epoch_block_impl (nano::state_block &); - nano::ledger & ledger; - nano::secure::write_transaction const & transaction; - nano::block_status result; - -private: - bool validate_epoch_block (nano::state_block const & block_a); -}; - -// Returns true if this block which has an epoch link is correctly formed. -bool ledger_processor::validate_epoch_block (nano::state_block const & block_a) -{ - debug_assert (ledger.is_epoch_link (block_a.hashables.link)); - nano::amount prev_balance (0); - if (!block_a.hashables.previous.is_zero ()) - { - result = ledger.store.block.exists (transaction, block_a.hashables.previous) ? nano::block_status::progress : nano::block_status::gap_previous; - if (result == nano::block_status::progress) - { - prev_balance = ledger.any.block_balance (transaction, block_a.hashables.previous).value (); - } - else - { - // Check for possible regular state blocks with epoch link (send subtype) - if (validate_message (block_a.hashables.account, block_a.hash (), block_a.signature)) - { - // Is epoch block signed correctly - if (validate_message (ledger.epoch_signer (block_a.link_field ().value ()), block_a.hash (), block_a.signature)) - { - result = nano::block_status::bad_signature; - } - } - } - } - return (block_a.hashables.balance == prev_balance); -} - -void ledger_processor::state_block (nano::state_block & block_a) -{ - result = nano::block_status::progress; - auto is_epoch_block = false; - if (ledger.is_epoch_link (block_a.hashables.link)) - { - // This function also modifies the result variable if epoch is mal-formed - is_epoch_block = validate_epoch_block (block_a); - } - - if (result == nano::block_status::progress) - { - if (is_epoch_block) - { - epoch_block_impl (block_a); - } - else - { - state_block_impl (block_a); - } - } -} - -void ledger_processor::state_block_impl (nano::state_block & block_a) -{ - auto hash (block_a.hash ()); - auto existing = ledger.any.block_exists_or_pruned (transaction, hash); - result = existing ? nano::block_status::old : nano::block_status::progress; // Have we seen this block before? (Unambiguous) - if (result == nano::block_status::progress) - { - result = validate_message (block_a.hashables.account, hash, block_a.signature) ? nano::block_status::bad_signature : nano::block_status::progress; // Is this block signed correctly (Unambiguous) - if (result == nano::block_status::progress) - { - debug_assert (!validate_message (block_a.hashables.account, hash, block_a.signature)); - result = block_a.hashables.account.is_zero () ? nano::block_status::opened_burn_account : nano::block_status::progress; // Is this for the burn account? (Unambiguous) - if (result == nano::block_status::progress) - { - nano::epoch epoch (nano::epoch::epoch_0); - nano::epoch source_epoch (nano::epoch::epoch_0); - nano::account_info info; - nano::amount amount (block_a.hashables.balance); - auto is_send (false); - auto is_receive (false); - auto account_error (ledger.store.account.get (transaction, block_a.hashables.account, info)); - if (!account_error) - { - // Account already exists - epoch = info.epoch (); - result = block_a.hashables.previous.is_zero () ? nano::block_status::fork : nano::block_status::progress; // Has this account already been opened? (Ambigious) - if (result == nano::block_status::progress) - { - result = ledger.store.block.exists (transaction, block_a.hashables.previous) ? nano::block_status::progress : nano::block_status::gap_previous; // Does the previous block exist in the ledger? (Unambigious) - if (result == nano::block_status::progress) - { - is_send = block_a.hashables.balance < info.balance; - is_receive = !is_send && !block_a.hashables.link.is_zero (); - amount = is_send ? (info.balance.number () - amount.number ()) : (amount.number () - info.balance.number ()); - result = block_a.hashables.previous == info.head ? nano::block_status::progress : nano::block_status::fork; // Is the previous block the account's head block? (Ambigious) - } - } - } - else - { - // Account does not yet exists - result = block_a.previous ().is_zero () ? nano::block_status::progress : nano::block_status::gap_previous; // Does the first block in an account yield 0 for previous() ? (Unambigious) - if (result == nano::block_status::progress) - { - is_receive = true; - result = !block_a.hashables.link.is_zero () ? nano::block_status::progress : nano::block_status::gap_source; // Is the first block receiving from a send ? (Unambigious) - } - } - if (result == nano::block_status::progress) - { - if (!is_send) - { - if (!block_a.hashables.link.is_zero ()) - { - result = ledger.any.block_exists_or_pruned (transaction, block_a.hashables.link.as_block_hash ()) ? nano::block_status::progress : nano::block_status::gap_source; // Have we seen the source block already? (Harmless) - if (result == nano::block_status::progress) - { - nano::pending_key key (block_a.hashables.account, block_a.hashables.link.as_block_hash ()); - auto pending = ledger.store.pending.get (transaction, key); - result = !pending ? nano::block_status::unreceivable : nano::block_status::progress; // Has this source already been received (Malformed) - if (result == nano::block_status::progress) - { - result = amount == pending.value ().amount ? nano::block_status::progress : nano::block_status::balance_mismatch; - source_epoch = pending.value ().epoch; - epoch = std::max (epoch, source_epoch); - } - } - } - else - { - // If there's no link, the balance must remain the same, only the representative can change - result = amount.is_zero () ? nano::block_status::progress : nano::block_status::balance_mismatch; - } - } - } - if (result == nano::block_status::progress) - { - nano::block_details block_details (epoch, is_send, is_receive, false); - result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed) - if (result == nano::block_status::progress) - { - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::state_block); - block_a.sideband_set (nano::block_sideband (block_a.hashables.account /* unused */, 0, 0 /* unused */, info.block_count + 1, nano::seconds_since_epoch (), block_details, source_epoch)); - ledger.store.block.put (transaction, hash, block_a); - - if (!info.head.is_zero ()) - { - // Move existing representation & add in amount delta - // ledger.rep_weights.representation_add_dual (transaction, info.representative, 0 - info.balance.number (), block_a.hashables.representative, block_a.hashables.balance.number ()); - ledger.rep_weights.move_add_sub (transaction, info.representative, info.balance, block_a.hashables.representative, block_a.hashables.balance); - } - else - { - // Add in amount delta only - ledger.rep_weights.add (transaction, block_a.hashables.representative, block_a.hashables.balance); - } - - if (is_send) - { - nano::pending_key key (block_a.hashables.link.as_account (), hash); - nano::pending_info info (block_a.hashables.account, amount.number (), epoch); - ledger.store.pending.put (transaction, key, info); - } - else if (!block_a.hashables.link.is_zero ()) - { - ledger.store.pending.del (transaction, nano::pending_key (block_a.hashables.account, block_a.hashables.link.as_block_hash ())); - } - - nano::account_info new_info (hash, block_a.hashables.representative, info.open_block.is_zero () ? hash : info.open_block, block_a.hashables.balance, nano::seconds_since_epoch (), info.block_count + 1, epoch); - ledger.update_account (transaction, block_a.hashables.account, info, new_info); - } - } - } - } - } -} - -void ledger_processor::epoch_block_impl (nano::state_block & block_a) -{ - auto hash (block_a.hash ()); - auto existing = ledger.any.block_exists_or_pruned (transaction, hash); - result = existing ? nano::block_status::old : nano::block_status::progress; // Have we seen this block before? (Unambiguous) - if (result == nano::block_status::progress) - { - result = validate_message (ledger.epoch_signer (block_a.hashables.link), hash, block_a.signature) ? nano::block_status::bad_signature : nano::block_status::progress; // Is this block signed correctly (Unambiguous) - if (result == nano::block_status::progress) - { - debug_assert (!validate_message (ledger.epoch_signer (block_a.hashables.link), hash, block_a.signature)); - result = block_a.hashables.account.is_zero () ? nano::block_status::opened_burn_account : nano::block_status::progress; // Is this for the burn account? (Unambiguous) - if (result == nano::block_status::progress) - { - nano::account_info info; - auto account_error (ledger.store.account.get (transaction, block_a.hashables.account, info)); - if (!account_error) - { - // Account already exists - result = block_a.hashables.previous.is_zero () ? nano::block_status::fork : nano::block_status::progress; // Has this account already been opened? (Ambigious) - if (result == nano::block_status::progress) - { - result = block_a.hashables.previous == info.head ? nano::block_status::progress : nano::block_status::fork; // Is the previous block the account's head block? (Ambigious) - if (result == nano::block_status::progress) - { - result = block_a.hashables.representative == info.representative ? nano::block_status::progress : nano::block_status::representative_mismatch; - } - } - } - else - { - result = block_a.hashables.representative.is_zero () ? nano::block_status::progress : nano::block_status::representative_mismatch; - // Non-exisitng account should have pending entries - if (result == nano::block_status::progress) - { - bool pending_exists = ledger.any.receivable_exists (transaction, block_a.hashables.account); - result = pending_exists ? nano::block_status::progress : nano::block_status::gap_epoch_open_pending; - } - } - if (result == nano::block_status::progress) - { - auto epoch = ledger.constants.epochs.epoch (block_a.hashables.link); - // Must be an epoch for an unopened account or the epoch upgrade must be sequential - auto is_valid_epoch_upgrade = account_error ? static_cast> (epoch) > 0 : nano::epochs::is_sequential (info.epoch (), epoch); - result = is_valid_epoch_upgrade ? nano::block_status::progress : nano::block_status::block_position; - if (result == nano::block_status::progress) - { - result = block_a.hashables.balance == info.balance ? nano::block_status::progress : nano::block_status::balance_mismatch; - if (result == nano::block_status::progress) - { - nano::block_details block_details (epoch, false, false, true); - result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed) - if (result == nano::block_status::progress) - { - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::epoch_block); - block_a.sideband_set (nano::block_sideband (block_a.hashables.account /* unused */, 0, 0 /* unused */, info.block_count + 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */)); - ledger.store.block.put (transaction, hash, block_a); - nano::account_info new_info (hash, block_a.hashables.representative, info.open_block.is_zero () ? hash : info.open_block, info.balance, nano::seconds_since_epoch (), info.block_count + 1, epoch); - ledger.update_account (transaction, block_a.hashables.account, info, new_info); - } - } - } - } - } - } - } -} - -void ledger_processor::change_block (nano::change_block & block_a) -{ - auto hash (block_a.hash ()); - auto existing = ledger.any.block_exists_or_pruned (transaction, hash); - result = existing ? nano::block_status::old : nano::block_status::progress; // Have we seen this block before? (Harmless) - if (result == nano::block_status::progress) - { - auto previous (ledger.store.block.get (transaction, block_a.hashables.previous)); - result = previous != nullptr ? nano::block_status::progress : nano::block_status::gap_previous; // Have we seen the previous block already? (Harmless) - if (result == nano::block_status::progress) - { - result = block_a.valid_predecessor (*previous) ? nano::block_status::progress : nano::block_status::block_position; - if (result == nano::block_status::progress) - { - auto account = previous->account (); - auto info = ledger.any.account_get (transaction, account); - release_assert (info); - result = info->head != block_a.hashables.previous ? nano::block_status::fork : nano::block_status::progress; - if (result == nano::block_status::progress) - { - release_assert (info->head == block_a.hashables.previous); - result = validate_message (account, hash, block_a.signature) ? nano::block_status::bad_signature : nano::block_status::progress; // Is this block signed correctly (Malformed) - if (result == nano::block_status::progress) - { - nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); - result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed) - if (result == nano::block_status::progress) - { - debug_assert (!validate_message (account, hash, block_a.signature)); - block_a.sideband_set (nano::block_sideband (account, 0, info->balance, info->block_count + 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */)); - ledger.store.block.put (transaction, hash, block_a); - auto balance = previous->balance (); - ledger.rep_weights.move (transaction, info->representative, block_a.hashables.representative, balance); - nano::account_info new_info (hash, block_a.hashables.representative, info->open_block, info->balance, nano::seconds_since_epoch (), info->block_count + 1, nano::epoch::epoch_0); - ledger.update_account (transaction, account, *info, new_info); - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::change); - } - } - } - } - } - } -} - -void ledger_processor::send_block (nano::send_block & block_a) -{ - auto hash (block_a.hash ()); - auto existing = ledger.any.block_exists_or_pruned (transaction, hash); - result = existing ? nano::block_status::old : nano::block_status::progress; // Have we seen this block before? (Harmless) - if (result == nano::block_status::progress) - { - auto previous (ledger.store.block.get (transaction, block_a.hashables.previous)); - result = previous != nullptr ? nano::block_status::progress : nano::block_status::gap_previous; // Have we seen the previous block already? (Harmless) - if (result == nano::block_status::progress) - { - result = block_a.valid_predecessor (*previous) ? nano::block_status::progress : nano::block_status::block_position; - if (result == nano::block_status::progress) - { - auto account = previous->account (); - auto info = ledger.any.account_get (transaction, account); - release_assert (info); - result = info->head != block_a.hashables.previous ? nano::block_status::fork : nano::block_status::progress; - if (result == nano::block_status::progress) - { - result = validate_message (account, hash, block_a.signature) ? nano::block_status::bad_signature : nano::block_status::progress; // Is this block signed correctly (Malformed) - if (result == nano::block_status::progress) - { - nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); - result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed) - if (result == nano::block_status::progress) - { - debug_assert (!validate_message (account, hash, block_a.signature)); - release_assert (info->head == block_a.hashables.previous); - result = info->balance.number () >= block_a.hashables.balance.number () ? nano::block_status::progress : nano::block_status::negative_spend; // Is this trying to spend a negative amount (Malicious) - if (result == nano::block_status::progress) - { - auto amount (info->balance.number () - block_a.hashables.balance.number ()); - ledger.rep_weights.sub (transaction, info->representative, amount); - block_a.sideband_set (nano::block_sideband (account, 0, block_a.hashables.balance /* unused */, info->block_count + 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */)); - ledger.store.block.put (transaction, hash, block_a); - nano::account_info new_info (hash, info->representative, info->open_block, block_a.hashables.balance, nano::seconds_since_epoch (), info->block_count + 1, nano::epoch::epoch_0); - ledger.update_account (transaction, account, *info, new_info); - ledger.store.pending.put (transaction, nano::pending_key (block_a.hashables.destination, hash), { account, amount, nano::epoch::epoch_0 }); - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::send); - } - } - } - } - } - } - } -} - -void ledger_processor::receive_block (nano::receive_block & block_a) -{ - auto hash (block_a.hash ()); - auto existing = ledger.any.block_exists_or_pruned (transaction, hash); - result = existing ? nano::block_status::old : nano::block_status::progress; // Have we seen this block already? (Harmless) - if (result == nano::block_status::progress) - { - auto previous (ledger.store.block.get (transaction, block_a.hashables.previous)); - result = previous != nullptr ? nano::block_status::progress : nano::block_status::gap_previous; - if (result == nano::block_status::progress) - { - result = block_a.valid_predecessor (*previous) ? nano::block_status::progress : nano::block_status::block_position; - if (result == nano::block_status::progress) - { - auto account = previous->account (); - auto info = ledger.any.account_get (transaction, account); - release_assert (info); - result = info->head != block_a.hashables.previous ? nano::block_status::fork : nano::block_status::progress; // If we have the block but it's not the latest we have a signed fork (Malicious) - if (result == nano::block_status::progress) - { - result = validate_message (account, hash, block_a.signature) ? nano::block_status::bad_signature : nano::block_status::progress; // Is the signature valid (Malformed) - if (result == nano::block_status::progress) - { - debug_assert (!validate_message (account, hash, block_a.signature)); - result = ledger.any.block_exists_or_pruned (transaction, block_a.hashables.source) ? nano::block_status::progress : nano::block_status::gap_source; // Have we seen the source block already? (Harmless) - if (result == nano::block_status::progress) - { - result = info->head == block_a.hashables.previous ? nano::block_status::progress : nano::block_status::gap_previous; // Block doesn't immediately follow latest block (Harmless) - if (result == nano::block_status::progress) - { - nano::pending_key key (account, block_a.hashables.source); - auto pending = ledger.store.pending.get (transaction, key); - result = !pending ? nano::block_status::unreceivable : nano::block_status::progress; // Has this source already been received (Malformed) - if (result == nano::block_status::progress) - { - result = pending.value ().epoch == nano::epoch::epoch_0 ? nano::block_status::progress : nano::block_status::unreceivable; // Are we receiving a state-only send? (Malformed) - if (result == nano::block_status::progress) - { - nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); - result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed) - if (result == nano::block_status::progress) - { - auto new_balance (info->balance.number () + pending.value ().amount.number ()); - 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); - nano::account_info new_info (hash, info->representative, info->open_block, new_balance, nano::seconds_since_epoch (), info->block_count + 1, nano::epoch::epoch_0); - ledger.update_account (transaction, account, *info, new_info); - ledger.rep_weights.add (transaction, info->representative, pending.value ().amount); - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::receive); - } - } - } - } - } - } - } - } - } - } -} - -void ledger_processor::open_block (nano::open_block & block_a) -{ - auto hash (block_a.hash ()); - auto existing = ledger.any.block_exists_or_pruned (transaction, hash); - result = existing ? nano::block_status::old : nano::block_status::progress; // Have we seen this block already? (Harmless) - if (result == nano::block_status::progress) - { - result = validate_message (block_a.hashables.account, hash, block_a.signature) ? nano::block_status::bad_signature : nano::block_status::progress; // Is the signature valid (Malformed) - if (result == nano::block_status::progress) - { - debug_assert (!validate_message (block_a.hashables.account, hash, block_a.signature)); - result = ledger.any.block_exists_or_pruned (transaction, block_a.hashables.source) ? nano::block_status::progress : nano::block_status::gap_source; // Have we seen the source block? (Harmless) - if (result == nano::block_status::progress) - { - nano::account_info info; - result = ledger.store.account.get (transaction, block_a.hashables.account, info) ? nano::block_status::progress : nano::block_status::fork; // Has this account already been opened? (Malicious) - if (result == nano::block_status::progress) - { - nano::pending_key key (block_a.hashables.account, block_a.hashables.source); - auto pending = ledger.store.pending.get (transaction, key); - result = !pending ? nano::block_status::unreceivable : nano::block_status::progress; // Has this source already been received (Malformed) - if (result == nano::block_status::progress) - { - result = block_a.hashables.account == ledger.constants.burn_account ? nano::block_status::opened_burn_account : nano::block_status::progress; // Is it burning 0 account? (Malicious) - if (result == nano::block_status::progress) - { - result = pending.value ().epoch == nano::epoch::epoch_0 ? nano::block_status::progress : nano::block_status::unreceivable; // Are we receiving a state-only send? (Malformed) - if (result == nano::block_status::progress) - { - nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); - result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed) - if (result == nano::block_status::progress) - { - ledger.store.pending.del (transaction, key); - block_a.sideband_set (nano::block_sideband (block_a.hashables.account, 0, pending.value ().amount, 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */)); - ledger.store.block.put (transaction, hash, block_a); - nano::account_info new_info (hash, block_a.representative_field ().value (), hash, pending.value ().amount.number (), nano::seconds_since_epoch (), 1, nano::epoch::epoch_0); - ledger.update_account (transaction, block_a.hashables.account, info, new_info); - ledger.rep_weights.add (transaction, block_a.representative_field ().value (), pending.value ().amount); - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::open); - } - } - } - } - } - } - } - } -} - -ledger_processor::ledger_processor (nano::ledger & ledger_a, nano::secure::write_transaction const & transaction_a) : - ledger (ledger_a), - transaction (transaction_a) -{ -} - /** * Determine the representative for this block */ @@ -1063,7 +597,7 @@ void nano::ledger::confirm_one (secure::write_transaction & transaction, nano::b nano::block_status nano::ledger::process (secure::write_transaction const & transaction_a, std::shared_ptr block_a) { debug_assert (!constants.work.validate_entry (*block_a) || constants.genesis == nano::dev::genesis); - ledger_processor processor (*this, transaction_a); + ledger_processor processor (transaction_a, *this); block_a->visit (processor); if (processor.result == nano::block_status::progress) { diff --git a/nano/secure/ledger_processor.cpp b/nano/secure/ledger_processor.cpp new file mode 100644 index 000000000..2947bb6a5 --- /dev/null +++ b/nano/secure/ledger_processor.cpp @@ -0,0 +1,460 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +nano::ledger_processor::ledger_processor (nano::secure::write_transaction const & transaction_a, nano::ledger & ledger_a) : + transaction (transaction_a), + ledger (ledger_a) +{ +} + +void nano::ledger_processor::send_block (nano::send_block & block_a) +{ + auto hash (block_a.hash ()); + auto existing = ledger.any.block_exists_or_pruned (transaction, hash); + result = existing ? nano::block_status::old : nano::block_status::progress; // Have we seen this block before? (Harmless) + if (result == nano::block_status::progress) + { + auto previous (ledger.store.block.get (transaction, block_a.hashables.previous)); + result = previous != nullptr ? nano::block_status::progress : nano::block_status::gap_previous; // Have we seen the previous block already? (Harmless) + if (result == nano::block_status::progress) + { + result = block_a.valid_predecessor (*previous) ? nano::block_status::progress : nano::block_status::block_position; + if (result == nano::block_status::progress) + { + auto account = previous->account (); + auto info = ledger.any.account_get (transaction, account); + release_assert (info); + result = info->head != block_a.hashables.previous ? nano::block_status::fork : nano::block_status::progress; + if (result == nano::block_status::progress) + { + result = validate_message (account, hash, block_a.signature) ? nano::block_status::bad_signature : nano::block_status::progress; // Is this block signed correctly (Malformed) + if (result == nano::block_status::progress) + { + nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); + result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result == nano::block_status::progress) + { + debug_assert (!validate_message (account, hash, block_a.signature)); + release_assert (info->head == block_a.hashables.previous); + result = info->balance.number () >= block_a.hashables.balance.number () ? nano::block_status::progress : nano::block_status::negative_spend; // Is this trying to spend a negative amount (Malicious) + if (result == nano::block_status::progress) + { + auto amount (info->balance.number () - block_a.hashables.balance.number ()); + ledger.rep_weights.sub (transaction, info->representative, amount); + block_a.sideband_set (nano::block_sideband (account, 0, block_a.hashables.balance /* unused */, info->block_count + 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */)); + ledger.store.block.put (transaction, hash, block_a); + nano::account_info new_info (hash, info->representative, info->open_block, block_a.hashables.balance, nano::seconds_since_epoch (), info->block_count + 1, nano::epoch::epoch_0); + ledger.update_account (transaction, account, *info, new_info); + ledger.store.pending.put (transaction, nano::pending_key (block_a.hashables.destination, hash), { account, amount, nano::epoch::epoch_0 }); + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::send); + } + } + } + } + } + } + } +} + +void nano::ledger_processor::receive_block (nano::receive_block & block_a) +{ + auto hash (block_a.hash ()); + auto existing = ledger.any.block_exists_or_pruned (transaction, hash); + result = existing ? nano::block_status::old : nano::block_status::progress; // Have we seen this block already? (Harmless) + if (result == nano::block_status::progress) + { + auto previous (ledger.store.block.get (transaction, block_a.hashables.previous)); + result = previous != nullptr ? nano::block_status::progress : nano::block_status::gap_previous; + if (result == nano::block_status::progress) + { + result = block_a.valid_predecessor (*previous) ? nano::block_status::progress : nano::block_status::block_position; + if (result == nano::block_status::progress) + { + auto account = previous->account (); + auto info = ledger.any.account_get (transaction, account); + release_assert (info); + result = info->head != block_a.hashables.previous ? nano::block_status::fork : nano::block_status::progress; // If we have the block but it's not the latest we have a signed fork (Malicious) + if (result == nano::block_status::progress) + { + result = validate_message (account, hash, block_a.signature) ? nano::block_status::bad_signature : nano::block_status::progress; // Is the signature valid (Malformed) + if (result == nano::block_status::progress) + { + debug_assert (!validate_message (account, hash, block_a.signature)); + result = ledger.any.block_exists_or_pruned (transaction, block_a.hashables.source) ? nano::block_status::progress : nano::block_status::gap_source; // Have we seen the source block already? (Harmless) + if (result == nano::block_status::progress) + { + result = info->head == block_a.hashables.previous ? nano::block_status::progress : nano::block_status::gap_previous; // Block doesn't immediately follow latest block (Harmless) + if (result == nano::block_status::progress) + { + nano::pending_key key (account, block_a.hashables.source); + auto pending = ledger.store.pending.get (transaction, key); + result = !pending ? nano::block_status::unreceivable : nano::block_status::progress; // Has this source already been received (Malformed) + if (result == nano::block_status::progress) + { + result = pending.value ().epoch == nano::epoch::epoch_0 ? nano::block_status::progress : nano::block_status::unreceivable; // Are we receiving a state-only send? (Malformed) + if (result == nano::block_status::progress) + { + nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); + result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result == nano::block_status::progress) + { + auto new_balance (info->balance.number () + pending.value ().amount.number ()); + 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); + nano::account_info new_info (hash, info->representative, info->open_block, new_balance, nano::seconds_since_epoch (), info->block_count + 1, nano::epoch::epoch_0); + ledger.update_account (transaction, account, *info, new_info); + ledger.rep_weights.add (transaction, info->representative, pending.value ().amount); + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::receive); + } + } + } + } + } + } + } + } + } + } +} + +void nano::ledger_processor::open_block (nano::open_block & block_a) +{ + auto hash (block_a.hash ()); + auto existing = ledger.any.block_exists_or_pruned (transaction, hash); + result = existing ? nano::block_status::old : nano::block_status::progress; // Have we seen this block already? (Harmless) + if (result == nano::block_status::progress) + { + result = validate_message (block_a.hashables.account, hash, block_a.signature) ? nano::block_status::bad_signature : nano::block_status::progress; // Is the signature valid (Malformed) + if (result == nano::block_status::progress) + { + debug_assert (!validate_message (block_a.hashables.account, hash, block_a.signature)); + result = ledger.any.block_exists_or_pruned (transaction, block_a.hashables.source) ? nano::block_status::progress : nano::block_status::gap_source; // Have we seen the source block? (Harmless) + if (result == nano::block_status::progress) + { + nano::account_info info; + result = ledger.store.account.get (transaction, block_a.hashables.account, info) ? nano::block_status::progress : nano::block_status::fork; // Has this account already been opened? (Malicious) + if (result == nano::block_status::progress) + { + nano::pending_key key (block_a.hashables.account, block_a.hashables.source); + auto pending = ledger.store.pending.get (transaction, key); + result = !pending ? nano::block_status::unreceivable : nano::block_status::progress; // Has this source already been received (Malformed) + if (result == nano::block_status::progress) + { + result = block_a.hashables.account == ledger.constants.burn_account ? nano::block_status::opened_burn_account : nano::block_status::progress; // Is it burning 0 account? (Malicious) + if (result == nano::block_status::progress) + { + result = pending.value ().epoch == nano::epoch::epoch_0 ? nano::block_status::progress : nano::block_status::unreceivable; // Are we receiving a state-only send? (Malformed) + if (result == nano::block_status::progress) + { + nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); + result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result == nano::block_status::progress) + { + ledger.store.pending.del (transaction, key); + block_a.sideband_set (nano::block_sideband (block_a.hashables.account, 0, pending.value ().amount, 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */)); + ledger.store.block.put (transaction, hash, block_a); + nano::account_info new_info (hash, block_a.representative_field ().value (), hash, pending.value ().amount.number (), nano::seconds_since_epoch (), 1, nano::epoch::epoch_0); + ledger.update_account (transaction, block_a.hashables.account, info, new_info); + ledger.rep_weights.add (transaction, block_a.representative_field ().value (), pending.value ().amount); + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::open); + } + } + } + } + } + } + } + } +} + +void nano::ledger_processor::change_block (nano::change_block & block_a) +{ + auto hash (block_a.hash ()); + auto existing = ledger.any.block_exists_or_pruned (transaction, hash); + result = existing ? nano::block_status::old : nano::block_status::progress; // Have we seen this block before? (Harmless) + if (result == nano::block_status::progress) + { + auto previous (ledger.store.block.get (transaction, block_a.hashables.previous)); + result = previous != nullptr ? nano::block_status::progress : nano::block_status::gap_previous; // Have we seen the previous block already? (Harmless) + if (result == nano::block_status::progress) + { + result = block_a.valid_predecessor (*previous) ? nano::block_status::progress : nano::block_status::block_position; + if (result == nano::block_status::progress) + { + auto account = previous->account (); + auto info = ledger.any.account_get (transaction, account); + release_assert (info); + result = info->head != block_a.hashables.previous ? nano::block_status::fork : nano::block_status::progress; + if (result == nano::block_status::progress) + { + release_assert (info->head == block_a.hashables.previous); + result = validate_message (account, hash, block_a.signature) ? nano::block_status::bad_signature : nano::block_status::progress; // Is this block signed correctly (Malformed) + if (result == nano::block_status::progress) + { + nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); + result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result == nano::block_status::progress) + { + debug_assert (!validate_message (account, hash, block_a.signature)); + block_a.sideband_set (nano::block_sideband (account, 0, info->balance, info->block_count + 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */)); + ledger.store.block.put (transaction, hash, block_a); + auto balance = previous->balance (); + ledger.rep_weights.move (transaction, info->representative, block_a.hashables.representative, balance); + nano::account_info new_info (hash, block_a.hashables.representative, info->open_block, info->balance, nano::seconds_since_epoch (), info->block_count + 1, nano::epoch::epoch_0); + ledger.update_account (transaction, account, *info, new_info); + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::change); + } + } + } + } + } + } +} + +void nano::ledger_processor::state_block (nano::state_block & block_a) +{ + result = nano::block_status::progress; + auto is_epoch_block = false; + if (ledger.is_epoch_link (block_a.hashables.link)) + { + // This function also modifies the result variable if epoch is mal-formed + is_epoch_block = validate_epoch_block (block_a); + } + + if (result == nano::block_status::progress) + { + if (is_epoch_block) + { + epoch_block_impl (block_a); + } + else + { + state_block_impl (block_a); + } + } +} + +void nano::ledger_processor::state_block_impl (nano::state_block & block_a) +{ + auto hash (block_a.hash ()); + auto existing = ledger.any.block_exists_or_pruned (transaction, hash); + result = existing ? nano::block_status::old : nano::block_status::progress; // Have we seen this block before? (Unambiguous) + if (result == nano::block_status::progress) + { + result = validate_message (block_a.hashables.account, hash, block_a.signature) ? nano::block_status::bad_signature : nano::block_status::progress; // Is this block signed correctly (Unambiguous) + if (result == nano::block_status::progress) + { + debug_assert (!validate_message (block_a.hashables.account, hash, block_a.signature)); + result = block_a.hashables.account.is_zero () ? nano::block_status::opened_burn_account : nano::block_status::progress; // Is this for the burn account? (Unambiguous) + if (result == nano::block_status::progress) + { + nano::epoch epoch (nano::epoch::epoch_0); + nano::epoch source_epoch (nano::epoch::epoch_0); + nano::account_info info; + nano::amount amount (block_a.hashables.balance); + auto is_send (false); + auto is_receive (false); + auto account_error (ledger.store.account.get (transaction, block_a.hashables.account, info)); + if (!account_error) + { + // Account already exists + epoch = info.epoch (); + result = block_a.hashables.previous.is_zero () ? nano::block_status::fork : nano::block_status::progress; // Has this account already been opened? (Ambigious) + if (result == nano::block_status::progress) + { + result = ledger.store.block.exists (transaction, block_a.hashables.previous) ? nano::block_status::progress : nano::block_status::gap_previous; // Does the previous block exist in the ledger? (Unambigious) + if (result == nano::block_status::progress) + { + is_send = block_a.hashables.balance < info.balance; + is_receive = !is_send && !block_a.hashables.link.is_zero (); + amount = is_send ? (info.balance.number () - amount.number ()) : (amount.number () - info.balance.number ()); + result = block_a.hashables.previous == info.head ? nano::block_status::progress : nano::block_status::fork; // Is the previous block the account's head block? (Ambigious) + } + } + } + else + { + // Account does not yet exists + result = block_a.previous ().is_zero () ? nano::block_status::progress : nano::block_status::gap_previous; // Does the first block in an account yield 0 for previous() ? (Unambigious) + if (result == nano::block_status::progress) + { + is_receive = true; + result = !block_a.hashables.link.is_zero () ? nano::block_status::progress : nano::block_status::gap_source; // Is the first block receiving from a send ? (Unambigious) + } + } + if (result == nano::block_status::progress) + { + if (!is_send) + { + if (!block_a.hashables.link.is_zero ()) + { + result = ledger.any.block_exists_or_pruned (transaction, block_a.hashables.link.as_block_hash ()) ? nano::block_status::progress : nano::block_status::gap_source; // Have we seen the source block already? (Harmless) + if (result == nano::block_status::progress) + { + nano::pending_key key (block_a.hashables.account, block_a.hashables.link.as_block_hash ()); + auto pending = ledger.store.pending.get (transaction, key); + result = !pending ? nano::block_status::unreceivable : nano::block_status::progress; // Has this source already been received (Malformed) + if (result == nano::block_status::progress) + { + result = amount == pending.value ().amount ? nano::block_status::progress : nano::block_status::balance_mismatch; + source_epoch = pending.value ().epoch; + epoch = std::max (epoch, source_epoch); + } + } + } + else + { + // If there's no link, the balance must remain the same, only the representative can change + result = amount.is_zero () ? nano::block_status::progress : nano::block_status::balance_mismatch; + } + } + } + if (result == nano::block_status::progress) + { + nano::block_details block_details (epoch, is_send, is_receive, false); + result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result == nano::block_status::progress) + { + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::state_block); + block_a.sideband_set (nano::block_sideband (block_a.hashables.account /* unused */, 0, 0 /* unused */, info.block_count + 1, nano::seconds_since_epoch (), block_details, source_epoch)); + ledger.store.block.put (transaction, hash, block_a); + + if (!info.head.is_zero ()) + { + // Move existing representation & add in amount delta + // ledger.rep_weights.representation_add_dual (transaction, info.representative, 0 - info.balance.number (), block_a.hashables.representative, block_a.hashables.balance.number ()); + ledger.rep_weights.move_add_sub (transaction, info.representative, info.balance, block_a.hashables.representative, block_a.hashables.balance); + } + else + { + // Add in amount delta only + ledger.rep_weights.add (transaction, block_a.hashables.representative, block_a.hashables.balance); + } + + if (is_send) + { + nano::pending_key key (block_a.hashables.link.as_account (), hash); + nano::pending_info info (block_a.hashables.account, amount.number (), epoch); + ledger.store.pending.put (transaction, key, info); + } + else if (!block_a.hashables.link.is_zero ()) + { + ledger.store.pending.del (transaction, nano::pending_key (block_a.hashables.account, block_a.hashables.link.as_block_hash ())); + } + + nano::account_info new_info (hash, block_a.hashables.representative, info.open_block.is_zero () ? hash : info.open_block, block_a.hashables.balance, nano::seconds_since_epoch (), info.block_count + 1, epoch); + ledger.update_account (transaction, block_a.hashables.account, info, new_info); + } + } + } + } + } +} + +void nano::ledger_processor::epoch_block_impl (nano::state_block & block_a) +{ + auto hash (block_a.hash ()); + auto existing = ledger.any.block_exists_or_pruned (transaction, hash); + result = existing ? nano::block_status::old : nano::block_status::progress; // Have we seen this block before? (Unambiguous) + if (result == nano::block_status::progress) + { + result = validate_message (ledger.epoch_signer (block_a.hashables.link), hash, block_a.signature) ? nano::block_status::bad_signature : nano::block_status::progress; // Is this block signed correctly (Unambiguous) + if (result == nano::block_status::progress) + { + debug_assert (!validate_message (ledger.epoch_signer (block_a.hashables.link), hash, block_a.signature)); + result = block_a.hashables.account.is_zero () ? nano::block_status::opened_burn_account : nano::block_status::progress; // Is this for the burn account? (Unambiguous) + if (result == nano::block_status::progress) + { + nano::account_info info; + auto account_error (ledger.store.account.get (transaction, block_a.hashables.account, info)); + if (!account_error) + { + // Account already exists + result = block_a.hashables.previous.is_zero () ? nano::block_status::fork : nano::block_status::progress; // Has this account already been opened? (Ambigious) + if (result == nano::block_status::progress) + { + result = block_a.hashables.previous == info.head ? nano::block_status::progress : nano::block_status::fork; // Is the previous block the account's head block? (Ambigious) + if (result == nano::block_status::progress) + { + result = block_a.hashables.representative == info.representative ? nano::block_status::progress : nano::block_status::representative_mismatch; + } + } + } + else + { + result = block_a.hashables.representative.is_zero () ? nano::block_status::progress : nano::block_status::representative_mismatch; + // Non-exisitng account should have pending entries + if (result == nano::block_status::progress) + { + bool pending_exists = ledger.any.receivable_exists (transaction, block_a.hashables.account); + result = pending_exists ? nano::block_status::progress : nano::block_status::gap_epoch_open_pending; + } + } + if (result == nano::block_status::progress) + { + auto epoch = ledger.constants.epochs.epoch (block_a.hashables.link); + // Must be an epoch for an unopened account or the epoch upgrade must be sequential + auto is_valid_epoch_upgrade = account_error ? static_cast> (epoch) > 0 : nano::epochs::is_sequential (info.epoch (), epoch); + result = is_valid_epoch_upgrade ? nano::block_status::progress : nano::block_status::block_position; + if (result == nano::block_status::progress) + { + result = block_a.hashables.balance == info.balance ? nano::block_status::progress : nano::block_status::balance_mismatch; + if (result == nano::block_status::progress) + { + nano::block_details block_details (epoch, false, false, true); + result = ledger.constants.work.difficulty (block_a) >= ledger.constants.work.threshold (block_a.work_version (), block_details) ? nano::block_status::progress : nano::block_status::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result == nano::block_status::progress) + { + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::epoch_block); + block_a.sideband_set (nano::block_sideband (block_a.hashables.account /* unused */, 0, 0 /* unused */, info.block_count + 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */)); + ledger.store.block.put (transaction, hash, block_a); + nano::account_info new_info (hash, block_a.hashables.representative, info.open_block.is_zero () ? hash : info.open_block, info.balance, nano::seconds_since_epoch (), info.block_count + 1, epoch); + ledger.update_account (transaction, block_a.hashables.account, info, new_info); + } + } + } + } + } + } + } +} + +bool nano::ledger_processor::validate_epoch_block (nano::state_block const & block_a) +{ + debug_assert (ledger.is_epoch_link (block_a.hashables.link)); + nano::amount prev_balance (0); + if (!block_a.hashables.previous.is_zero ()) + { + result = ledger.store.block.exists (transaction, block_a.hashables.previous) ? nano::block_status::progress : nano::block_status::gap_previous; + if (result == nano::block_status::progress) + { + prev_balance = ledger.any.block_balance (transaction, block_a.hashables.previous).value (); + } + else + { + // Check for possible regular state blocks with epoch link (send subtype) + if (validate_message (block_a.hashables.account, block_a.hash (), block_a.signature)) + { + // Is epoch block signed correctly + if (validate_message (ledger.epoch_signer (block_a.link_field ().value ()), block_a.hash (), block_a.signature)) + { + result = nano::block_status::bad_signature; + } + } + } + } + return (block_a.hashables.balance == prev_balance); +} \ No newline at end of file diff --git a/nano/secure/ledger_processor.hpp b/nano/secure/ledger_processor.hpp new file mode 100644 index 000000000..2c66bb4c0 --- /dev/null +++ b/nano/secure/ledger_processor.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include + +namespace nano +{ +class ledger_processor final : public nano::mutable_block_visitor +{ +public: + ledger_processor (nano::secure::write_transaction const &, nano::ledger &); + + void send_block (nano::send_block &) override; + void receive_block (nano::receive_block &) override; + void open_block (nano::open_block &) override; + void change_block (nano::change_block &) override; + void state_block (nano::state_block &) override; + + void state_block_impl (nano::state_block &); + void epoch_block_impl (nano::state_block &); + + nano::secure::write_transaction const & transaction; + nano::ledger & ledger; + nano::block_status result; + +private: + bool validate_epoch_block (nano::state_block const & block); +}; +} \ No newline at end of file