diff --git a/nano/secure/CMakeLists.txt b/nano/secure/CMakeLists.txt index 10dc452e23..764780be74 100644 --- a/nano/secure/CMakeLists.txt +++ b/nano/secure/CMakeLists.txt @@ -44,6 +44,9 @@ add_library( account_iterator.cpp account_iterator.hpp account_iterator_impl.hpp + block_check_context.cpp + block_check_context.hpp + block_delta.hpp common.hpp common.cpp generate_cache_flags.hpp diff --git a/nano/secure/block_check_context.cpp b/nano/secure/block_check_context.cpp new file mode 100644 index 0000000000..6dd1df7204 --- /dev/null +++ b/nano/secure/block_check_context.cpp @@ -0,0 +1,555 @@ +#include +#include +#include +#include +#include +#include +#include + +nano::block_check_context::block_check_context (nano::ledger & ledger, std::shared_ptr block) : + block_m{ block }, + ledger{ ledger } +{ +} + +auto nano::block_check_context::op () const -> block_op +{ + debug_assert (state.has_value ()); + switch (block_m->type ()) + { + case nano::block_type::state: + if (block_m->balance_field ().value () < state->balance) + { + return block_op::send; + } + if (previous != nullptr && block_m->link_field ().value ().is_zero ()) + { + return block_op::noop; + } + if (ledger.constants.epochs.is_epoch_link (block_m->link_field ().value ())) + { + return block_op::epoch; + } + return block_op::receive; + case nano::block_type::send: + return block_op::send; + case nano::block_type::open: + case nano::block_type::receive: + return block_op::receive; + case nano::block_type::change: + return block_op::noop; + case nano::block_type::not_a_block: + case nano::block_type::invalid: + release_assert (false); + break; + } + release_assert (false); +} + +bool nano::block_check_context::is_send () const +{ + return op () == block_op::send; +} + +bool nano::block_check_context::is_receive () const +{ + return op () == block_op::receive; +} + +bool nano::block_check_context::is_epoch () const +{ + return op () == block_op::epoch; +} + +nano::amount nano::block_check_context::balance () const +{ + switch (block_m->type ()) + { + case nano::block_type::state: + case nano::block_type::send: + return block_m->balance_field ().value (); + case nano::block_type::open: + return receivable->amount; + case nano::block_type::change: + return previous->balance (); + case nano::block_type::receive: + return previous->balance ().number () + receivable->amount.number (); + default: + release_assert (false); + } +} + +uint64_t nano::block_check_context::height () const +{ + return previous ? previous->sideband ().height + 1 : 1; +} + +nano::epoch nano::block_check_context::epoch () const +{ + if (is_epoch ()) + { + return ledger.constants.epochs.epoch (block_m->link_field ().value ()); + } + nano::epoch account_epoch{ nano::epoch::epoch_0 }; + nano::epoch source_epoch{ nano::epoch::epoch_0 }; + if (previous != nullptr) + { + account_epoch = previous->sideband ().details.epoch; + } + if (receivable.has_value ()) + { + source_epoch = receivable->epoch; + } + return std::max (account_epoch, source_epoch); +} + +nano::amount nano::block_check_context::amount () const +{ + auto balance_l = balance (); + auto previous_balance = previous ? previous->balance () : 0; + switch (op ()) + { + case block_op::receive: + return balance_l.number () - previous_balance.number (); + case block_op::send: + return previous_balance.number () - balance_l.number (); + case block_op::epoch: + case block_op::noop: + release_assert (balance_l.number () == previous_balance.number ()); + return 0; + } +} + +nano::account nano::block_check_context::representative () const +{ + switch (block_m->type ()) + { + case nano::block_type::state: + case nano::block_type::open: + case nano::block_type::change: + return block_m->representative_field ().value (); + case nano::block_type::send: + case nano::block_type::receive: + return state->representative; + default: + release_assert (false); + } +} + +nano::block_hash nano::block_check_context::open () const +{ + if (previous == nullptr) + { + return block_m->hash (); + } + return state->open_block; +} + +bool nano::block_check_context::old () const +{ + return block_m == nullptr; +} + +nano::account nano::block_check_context::account () const +{ + switch (block_m->type ()) + { + case nano::block_type::change: + case nano::block_type::receive: + case nano::block_type::send: + debug_assert (previous != nullptr); + switch (previous->type ()) + { + case nano::block_type::state: + case nano::block_type::open: + return previous->account (); + case nano::block_type::change: + case nano::block_type::receive: + case nano::block_type::send: + return previous->sideband ().account; + case nano::block_type::not_a_block: + case nano::block_type::invalid: + debug_assert (false); + break; + } + break; + case nano::block_type::state: + case nano::block_type::open: + return block_m->account_field ().value (); + case nano::block_type::not_a_block: + case nano::block_type::invalid: + debug_assert (false); + break; + } + // std::unreachable (); c++23 + return 1; // Return an account that cannot be signed for. +} + +nano::block_hash nano::block_check_context::source () const +{ + switch (block_m->type ()) + { + case nano::block_type::send: + case nano::block_type::change: + // 0 is returned for source on send/change blocks + return 0; + case nano::block_type::receive: + case nano::block_type::open: + return block_m->source_field ().value (); + case nano::block_type::state: + return block_m->link_field ().value ().as_block_hash (); + case nano::block_type::not_a_block: + case nano::block_type::invalid: + return 0; + } + debug_assert (false); + return 0; +} + +nano::account nano::block_check_context::signer (nano::epochs const & epochs) const +{ + debug_assert (block_m != nullptr); + switch (block_m->type ()) + { + case nano::block_type::send: + case nano::block_type::receive: + case nano::block_type::change: + debug_assert (previous != nullptr); // Previous block must be passed in for non-open blocks + switch (previous->type ()) + { + case nano::block_type::state: + debug_assert (false && "Legacy blocks can't follow state blocks"); + break; + case nano::block_type::open: + // Open blocks have the account written in the block. + return previous->account (); + default: + // Other legacy block types have the account stored in sideband. + return previous->sideband ().account; + } + break; + case nano::block_type::state: + { + debug_assert (dynamic_cast (block_m.get ())); + // If the block is a send, while the link field may contain an epoch link value, it is actually a malformed destination address. + return (!epochs.is_epoch_link (block_m->link_field ().value ()) || is_send ()) ? block_m->account_field ().value () : epochs.signer (epochs.epoch (block_m->link_field ().value ())); + } + case nano::block_type::open: // Open block signer is determined statelessly as it's written in the block + return block_m->account_field ().value (); + case nano::block_type::invalid: + case nano::block_type::not_a_block: + debug_assert (false); + break; + } + // std::unreachable (); c++23 + return 1; // Return an account that cannot be signed for. +} + +bool nano::block_check_context::gap_previous () const +{ + return !block_m->previous ().is_zero () && previous == nullptr; +} + +bool nano::block_check_context::failed (nano::block_status const & code) const +{ + return code != nano::block_status::progress; +} + +nano::block_status nano::block_check_context::rule_sufficient_work () const +{ + if (ledger.constants.work.difficulty (*block_m) < ledger.constants.work.threshold (block_m->work_version (), details)) + { + return nano::block_status::insufficient_work; + } + return nano::block_status::progress; +} + +nano::block_status nano::block_check_context::rule_reserved_account () const +{ + switch (block_m->type ()) + { + case nano::block_type::open: + case nano::block_type::state: + if (!block_m->account_field ().value ().is_zero ()) + { + return nano::block_status::progress; + } + else + { + return nano::block_status::opened_burn_account; + } + break; + case nano::block_type::change: + case nano::block_type::receive: + case nano::block_type::send: + return nano::block_status::progress; + case nano::block_type::invalid: + case nano::block_type::not_a_block: + release_assert (false); + break; + } + release_assert (false); +} + +nano::block_status nano::block_check_context::rule_previous_frontier () const +{ + debug_assert (block_m != nullptr); // + if (gap_previous ()) + { + return nano::block_status::gap_previous; + } + else + { + return nano::block_status::progress; + } +} + +nano::block_status nano::block_check_context::rule_state_block_account_position () const +{ + if (previous == nullptr) + { + return nano::block_status::progress; + } + switch (block_m->type ()) + { + case nano::block_type::send: + case nano::block_type::receive: + case nano::block_type::change: + { + switch (previous->type ()) + { + case nano::block_type::state: + return nano::block_status::block_position; + default: + return nano::block_status::progress; + } + } + default: + return nano::block_status::progress; + } +} + +nano::block_status nano::block_check_context::rule_state_block_source_position () const +{ + if (!receivable.has_value ()) + { + return nano::block_status::progress; + } + switch (block_m->type ()) + { + case nano::block_type::receive: + case nano::block_type::open: + { + if (receivable->epoch > nano::epoch::epoch_0) + { + return nano::block_status::unreceivable; + } + return nano::block_status::progress; + } + case nano::block_type::state: + return nano::block_status::progress; + default: + release_assert (false); + } +} + +nano::block_status nano::block_check_context::rule_block_signed () const +{ + if (!nano::validate_message (signer (ledger.constants.epochs), block_m->hash (), block_m->block_signature ())) + { + return nano::block_status::progress; + } + return nano::block_status::bad_signature; +} + +nano::block_status nano::block_check_context::rule_metastable () const +{ + debug_assert (state.has_value ()); + if (block_m->previous () == state->head) + { + return nano::block_status::progress; + } + else + { + return nano::block_status::fork; + } +} + +nano::block_status nano::block_check_context::check_receive_rules () const +{ + if (!source_exists) + { + // Probably redundant to check as receivable would also have no value + return nano::block_status::gap_source; + } + if (!receivable.has_value ()) + { + return nano::block_status::unreceivable; + } + if (block_m->type () == nano::block_type::state) + { + auto next_balance = state->balance.number () + receivable->amount.number (); + if (next_balance != balance ().number ()) + { + return nano::block_status::balance_mismatch; + } + } + return nano::block_status::progress; +} + +nano::block_status nano::block_check_context::check_epoch_rules () const +{ + debug_assert (state.has_value ()); + // Epoch blocks may not change an account's balance + if (state->balance != balance ()) + { + return nano::block_status::balance_mismatch; + } + // Epoch blocks may not change an account's representative + if (state->representative != representative ()) + { + return nano::block_status::representative_mismatch; + } + // Epoch blocks may not be created for accounts that have no receivable entries + if (block_m->previous ().is_zero () && !any_receivable) + { + return nano::block_status::gap_epoch_open_pending; + } + auto previous_epoch = nano::epoch::epoch_0; + if (previous != nullptr) + { + previous_epoch = previous->sideband ().details.epoch; + } + // Epoch blocks may only increase epoch number by one + if (!state->head.is_zero () && !nano::epochs::is_sequential (previous_epoch, epoch ())) + { + return nano::block_status::block_position; + } + return nano::block_status::progress; +} + +nano::block_status nano::block_check_context::check_send_rules () const +{ + debug_assert (block_m->type () == nano::block_type::send || block_m->type () == nano::block_type::state); + if (state->balance < balance ()) + { + return nano::block_status::negative_spend; + } + return nano::block_status::progress; +} + +nano::block_status nano::block_check_context::check_noop_rules () const +{ + if (balance () != previous->balance ()) + { + return nano::block_status::balance_mismatch; + } + return nano::block_status::progress; +} + +void nano::block_check_context::load (store::transaction const & transaction) +{ + auto hash = block_m->hash (); + if (ledger.any.exists_or_pruned (transaction, hash)) + { + block_m = nullptr; // Signal this block already exists by nulling out block + return; + } + auto & block = *block_m; + if (!block.previous ().is_zero ()) + { + previous = ledger.any.get (transaction, block.previous ()); + } + if (!gap_previous ()) + { + auto account_l = account (); + auto source_l = source (); + state = ledger.any.get (transaction, account_l); + if (!state) + { + state = nano::account_info{}; + } + source_exists = ledger.any.exists_or_pruned (transaction, source_l); + nano::pending_key key{ account_l, source_l }; + receivable = ledger.any.get (transaction, key); + any_receivable = ledger.any.receivable_any (transaction, account_l); + details = block_details{ epoch (), is_send (), is_receive (), is_epoch () }; + } +} + +nano::block_status nano::block_check_context::check (store::transaction const & transaction) +{ + load (transaction); + if (old ()) + { + return nano::block_status::old; + } + nano::block_status result; + if (failed (result = rule_sufficient_work ())) + { + return result; + } + if (failed (result = rule_reserved_account ())) + { + return result; + } + if (failed (result = rule_previous_frontier ())) + { + return result; + } + if (failed (result = rule_state_block_account_position ())) + { + return result; + } + if (failed (result = rule_state_block_source_position ())) + { + return result; + } + if (failed (result = rule_block_signed ())) + { + return result; + } + if (failed (result = rule_metastable ())) + { + return result; + } + switch (op ()) + { + case block_op::receive: + result = check_receive_rules (); + break; + case block_op::send: + result = check_send_rules (); + break; + case block_op::noop: + result = check_noop_rules (); + break; + case block_op::epoch: + result = check_epoch_rules (); + break; + } + if (result == nano::block_status::progress) + { + nano::block_sideband sideband{ account (), 0, balance (), height (), nano::seconds_since_epoch (), details, receivable ? receivable->epoch : nano::epoch::epoch_0 }; + block_m->sideband_set (sideband); + std::pair, std::optional> receivable; + if (is_send ()) + { + receivable.first = { block_m->destination (), block_m->hash () }; + receivable.second = { account (), amount (), epoch () }; + } + else if (is_receive ()) + { + receivable.first = { block_m->account (), block_m->source () }; + } + std::pair, std::optional> weight; + if (previous != nullptr) + { + weight.first = state->representative; + weight.second = state->balance; + } + nano::account_info info{ block_m->hash (), representative (), open (), balance (), nano::seconds_since_epoch (), height (), epoch () }; + delta = { block_m, info, receivable, weight }; + } + return result; +} diff --git a/nano/secure/block_check_context.hpp b/nano/secure/block_check_context.hpp new file mode 100644 index 0000000000..58153b576f --- /dev/null +++ b/nano/secure/block_check_context.hpp @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace nano +{ +class block; +class ledger; +class ledger_constants; +} + +namespace nano::store +{ +class transaction; +} + +namespace nano +{ +class block_check_context +{ + enum class block_op + { + receive, + send, + noop, + epoch + }; + std::shared_ptr block_m; + std::shared_ptr previous; + std::optional state; + std::optional receivable; + bool any_receivable{ false }; + bool source_exists{ false }; + bool failed (nano::block_status const & code) const; + nano::ledger & ledger; + nano::block_details details; + +private: + bool is_send () const; + bool is_receive () const; + bool is_epoch () const; + nano::account account () const; + nano::block_hash source () const; + nano::account signer (nano::epochs const & epochs) const; + bool gap_previous () const; + nano::amount balance () const; + uint64_t height () const; + nano::epoch epoch () const; + nano::amount amount () const; + nano::account representative () const; + nano::block_hash open () const; + +private: // Block checking rules + nano::block_status rule_sufficient_work () const; + /** + Check for account numbers that cannot be used in blocks e.g. account number 0. + */ + nano::block_status rule_reserved_account () const; + /** + This rule checks if the previous block for this block is the head block of the specified account + */ + nano::block_status rule_previous_frontier () const; + + /** + This rule checks that legacy blocks cannot come after state blocks in an account + */ + nano::block_status rule_state_block_account_position () const; + + /** + This rule checks that legacy blocks cannot have a state block as a source + */ + nano::block_status rule_state_block_source_position () const; + nano::block_status rule_block_signed () const; + + /** + This rule identifies metastable blocks (forked blocks) with respect to the ledger and rejects them. + Rejected blocks need to be resolved via consensus + It is assumed that the previous block has already been loaded in to `context' if it exists + Metastable scenarios are: + 1) An initial block arriving for an account that's already been initialized + 2) The previous block exists but it is not the head block + Both of these scenarios can be ifentified by checking: if block->previous () == head + */ + nano::block_status rule_metastable () const; + nano::block_status check_receive_rules () const; + nano::block_status check_epoch_rules () const; + nano::block_status check_send_rules () const; + nano::block_status check_noop_rules () const; + + block_op op () const; + bool old () const; + void load (store::transaction const & transaction); + +public: + block_check_context (nano::ledger & ledger, std::shared_ptr block); + /** + This filters blocks in four directions based on how the link field should be interpreted + For state blocks the link field is interpreted as: + If the balance has decreased, a destination account + If the balance has not decreased + If the link field is 0, a noop + If the link field is an epoch link, an epoch sentinel + Otherwise, a block hash of an block ready to be received + For legacy blocks, the link field interpretation is applied to source field for receive and open blocks or the destination field for send blocks */ + nano::block_status check (store::transaction const & transaction); + std::optional delta; +}; +} // namespace nano diff --git a/nano/secure/block_delta.hpp b/nano/secure/block_delta.hpp new file mode 100644 index 0000000000..73f31c523b --- /dev/null +++ b/nano/secure/block_delta.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace nano +{ +class block_delta +{ +public: + std::shared_ptr block; + nano::account_info head; + std::pair, std::optional> receivable; + std::pair, std::optional> weight; +}; +} diff --git a/nano/secure/ledger.cpp b/nano/secure/ledger.cpp index a8cda02c33..d787702dab 100644 --- a/nano/secure/ledger.cpp +++ b/nano/secure/ledger.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -181,487 +182,6 @@ class rollback_visitor : public nano::block_visitor bool error{ false }; }; -class ledger_processor : public nano::mutable_block_visitor -{ -public: - ledger_processor (nano::ledger &, nano::store::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::store::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.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.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.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.cache.rep_weights.representation_add_dual (transaction, info.representative, 0 - info.balance.number (), block_a.hashables.representative, block_a.hashables.balance.number ()); - } - else - { - // Add in amount delta only - ledger.cache.rep_weights.representation_add (transaction, block_a.hashables.representative, block_a.hashables.balance.number ()); - } - - 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.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_any (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.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.get (transaction, account); - debug_assert (info); - result = info->head != block_a.hashables.previous ? nano::block_status::fork : nano::block_status::progress; - if (result == nano::block_status::progress) - { - debug_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.cache.rep_weights.representation_add_dual (transaction, block_a.hashables.representative, balance.number (), info->representative, 0 - balance.number ()); - 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.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.get (transaction, account); - debug_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)); - debug_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.cache.rep_weights.representation_add (transaction, info->representative, 0 - 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.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.get (transaction, account); - debug_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.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 ()); -#ifdef NDEBUG - if (ledger.store.block.exists (transaction, block_a.hashables.source)) - { - auto info = ledger.account_info (transaction, pending.value ().source); - debug_assert (info); - } -#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); - 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.cache.rep_weights.representation_add (transaction, info->representative, pending.value ().amount.number ()); - 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.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.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) - { -#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.value ().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.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.cache.rep_weights.representation_add (transaction, block_a.representative_field ().value (), pending.value ().amount.number ()); - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::open); - } - } - } - } - } - } - } - } -} - -ledger_processor::ledger_processor (nano::ledger & ledger_a, nano::store::write_transaction const & transaction_a) : - ledger (ledger_a), - transaction (transaction_a) -{ -} - /** * Determine the representative for this block */ @@ -854,13 +374,14 @@ void nano::ledger::confirm (nano::store::write_transaction const & transaction, nano::block_status nano::ledger::process (store::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); - block_a->visit (processor); - if (processor.result == nano::block_status::progress) + nano::block_check_context ctx{ *this, block_a }; + auto code = ctx.check (transaction_a); + if (code == nano::block_status::progress) { - ++cache.block_count; + debug_assert (block_a->has_sideband ()); + track (transaction_a, ctx.delta.value ()); } - return processor.result; + return code; } nano::block_hash nano::ledger::representative (store::transaction const & transaction_a, nano::block_hash const & hash_a) @@ -1164,6 +685,35 @@ void nano::ledger::update_account (store::write_transaction const & transaction_ } } +void nano::ledger::track (store::write_transaction const & transaction, nano::block_delta const & delta) +{ + auto & block = *delta.block; + store.block.put (transaction, delta.block->hash (), block); + ++cache.block_count; + store.account.put (transaction, block.account (), delta.head); + if (block.previous ().is_zero ()) + { + ++cache.account_count; + } + auto const & [receivable_key, receivable_info] = delta.receivable; + if (receivable_key) + { + if (receivable_info) + { + store.pending.put (transaction, receivable_key.value (), receivable_info.value ()); + } + else + { + store.pending.del (transaction, receivable_key.value ()); + } + } + if (delta.weight.first) + { + cache.rep_weights.representation_add (transaction, delta.weight.first.value (), 0 - delta.weight.second.value ().number ()); + } + cache.rep_weights.representation_add (transaction, delta.head.representative, delta.head.balance.number ()); +} + std::shared_ptr nano::ledger::forked_block (store::transaction const & transaction_a, nano::block const & block_a) { debug_assert (!any.exists (transaction_a, block_a.hash ())); diff --git a/nano/secure/ledger.hpp b/nano/secure/ledger.hpp index 9e21e15601..159a6d0a46 100644 --- a/nano/secure/ledger.hpp +++ b/nano/secure/ledger.hpp @@ -21,6 +21,7 @@ class write_transaction; namespace nano { class block; +class block_delta; enum class block_status; enum class epoch : uint8_t; class ledger_constants; @@ -89,6 +90,7 @@ class ledger final private: void initialize (nano::generate_cache_flags const &); + void track (store::write_transaction const & transaction, nano::block_delta const & delta); void confirm (nano::store::write_transaction const & transaction, nano::block const & block); std::unique_ptr any_impl;