diff --git a/nano/core_test/ledger.cpp b/nano/core_test/ledger.cpp index 3b8eb22c..6e234696 100644 --- a/nano/core_test/ledger.cpp +++ b/nano/core_test/ledger.cpp @@ -2969,3 +2969,90 @@ TEST (ledger, zero_rep) ASSERT_EQ (nano::genesis_amount, node1.ledger.cache.rep_weights.representation_get (nano::test_genesis_key.pub)); ASSERT_EQ (0, node1.ledger.cache.rep_weights.representation_get (0)); } + +TEST (ledger, work_validation) +{ + nano::logger_mt logger; + auto store = nano::make_store (logger, nano::unique_path ()); + ASSERT_TRUE (!store->init_error ()); + nano::stat stats; + nano::ledger ledger (*store, stats); + nano::genesis genesis; + store->initialize (store->tx_begin_write (), genesis, ledger.cache); + nano::work_pool pool (std::numeric_limits::max ()); + nano::block_builder builder; + auto gen = nano::test_genesis_key; + nano::keypair key; + + // With random work the block doesn't pass, then modifies the block with sufficient work and ensures a correct result + auto process_block = [&store, &ledger, &pool](nano::block & block_a, nano::block_details const details_a) { + EXPECT_EQ (nano::process_result::insufficient_work, ledger.process (store->tx_begin_write (), block_a).code); + block_a.block_work_set (*pool.generate (block_a.root (), nano::work_threshold (block_a.work_version (), details_a))); + EXPECT_EQ (nano::process_result::progress, ledger.process (store->tx_begin_write (), block_a).code); + }; + + std::error_code ec; + + auto send = *builder.send () + .previous (nano::genesis_hash) + .destination (gen.pub) + .balance (nano::genesis_amount - 1) + .sign (gen.prv, gen.pub) + .work (0) + .build (ec); + ASSERT_FALSE (ec); + + auto receive = *builder.receive () + .previous (send.hash ()) + .source (send.hash ()) + .sign (gen.prv, gen.pub) + .work (0) + .build (ec); + ASSERT_FALSE (ec); + + auto change = *builder.change () + .previous (receive.hash ()) + .representative (key.pub) + .sign (gen.prv, gen.pub) + .work (0) + .build (ec); + ASSERT_FALSE (ec); + + auto state = *builder.state () + .account (gen.pub) + .previous (change.hash ()) + .representative (gen.pub) + .balance (nano::genesis_amount - 1) + .link (key.pub) + .sign (gen.prv, gen.pub) + .work (0) + .build (ec); + ASSERT_FALSE (ec); + + auto open = *builder.open () + .account (key.pub) + .source (state.hash ()) + .representative (key.pub) + .sign (key.prv, key.pub) + .work (0) + .build (ec); + ASSERT_FALSE (ec); + + auto epoch = *builder.state () + .account (key.pub) + .previous (open.hash ()) + .balance (1) + .representative (key.pub) + .link (ledger.epoch_link (nano::epoch::epoch_1)) + .sign (gen.prv, gen.pub) + .work (0) + .build (ec); + ASSERT_FALSE (ec); + + process_block (send, {}); + process_block (receive, {}); + process_block (change, {}); + process_block (state, nano::block_details (nano::epoch::epoch_0, true, false, false)); + process_block (open, {}); + process_block (epoch, nano::block_details (nano::epoch::epoch_1, false, false, true)); +} diff --git a/nano/lib/blocks.cpp b/nano/lib/blocks.cpp index 7c0f2361..396bbb63 100644 --- a/nano/lib/blocks.cpp +++ b/nano/lib/blocks.cpp @@ -1707,6 +1707,16 @@ bool nano::block_details::deserialize (nano::stream & stream_a) return result; } +nano::block_sideband::block_sideband (nano::account const & account_a, nano::block_hash const & successor_a, nano::amount const & balance_a, uint64_t height_a, uint64_t timestamp_a, nano::block_details const & details_a) : +successor (successor_a), +account (account_a), +balance (balance_a), +height (height_a), +timestamp (timestamp_a), +details (details_a) +{ +} + nano::block_sideband::block_sideband (nano::account const & account_a, nano::block_hash const & successor_a, nano::amount const & balance_a, uint64_t height_a, uint64_t timestamp_a, nano::epoch epoch_a, bool is_send, bool is_receive, bool is_epoch) : successor (successor_a), account (account_a), diff --git a/nano/lib/blocks.hpp b/nano/lib/blocks.hpp index d0ee95c9..dab4c032 100644 --- a/nano/lib/blocks.hpp +++ b/nano/lib/blocks.hpp @@ -53,6 +53,7 @@ class block_sideband final { public: block_sideband () = default; + block_sideband (nano::account const &, nano::block_hash const &, nano::amount const &, uint64_t, uint64_t, nano::block_details const &); block_sideband (nano::account const &, nano::block_hash const &, nano::amount const &, uint64_t, uint64_t, nano::epoch, bool is_send, bool is_receive, bool is_epoch); void serialize (nano::stream &, nano::block_type) const; bool deserialize (nano::stream &, nano::block_type); diff --git a/nano/node/blockprocessor.cpp b/nano/node/blockprocessor.cpp index ccc7d0da..18124dc0 100644 --- a/nano/node/blockprocessor.cpp +++ b/nano/node/blockprocessor.cpp @@ -86,20 +86,17 @@ void nano::block_processor::add (std::shared_ptr block_a, uint64_t void nano::block_processor::add (nano::unchecked_info const & info_a) { debug_assert (!nano::work_validate (*info_a.block)); - if (info_a.block->difficulty () >= nano::work_threshold (info_a.block->work_version ())) + if (info_a.verified == nano::signature_verification::unknown && (info_a.block->type () == nano::block_type::state || info_a.block->type () == nano::block_type::open || !info_a.account.is_zero ())) + { + state_block_signature_verification.add (info_a); + } + else { - if (info_a.verified == nano::signature_verification::unknown && (info_a.block->type () == nano::block_type::state || info_a.block->type () == nano::block_type::open || !info_a.account.is_zero ())) { - state_block_signature_verification.add (info_a); - } - else - { - { - nano::lock_guard guard (mutex); - blocks.push_back (info_a); - } - condition.notify_all (); + nano::lock_guard guard (mutex); + blocks.push_back (info_a); } + condition.notify_all (); } } @@ -448,6 +445,14 @@ nano::process_return nano::block_processor::process_one (nano::write_transaction } break; } + case nano::process_result::insufficient_work: + { + if (node.config.logging.ledger_logging ()) + { + node.logger.try_log (boost::str (boost::format ("Insufficient work for %1% : %2 (difficulty %3)") % hash.to_string () % info_a.block->block_work () % info_a.block->difficulty ())); + } + break; + } } return result; } diff --git a/nano/secure/common.hpp b/nano/secure/common.hpp index 1781ba20..2f2403b7 100644 --- a/nano/secure/common.hpp +++ b/nano/secure/common.hpp @@ -318,7 +318,8 @@ enum class process_result opened_burn_account, // The impossible happened, someone found the private key associated with the public key '0'. balance_mismatch, // Balance and amount delta don't match representative_mismatch, // Representative is changed when it is not allowed - block_position // This block cannot follow the previous block + block_position, // This block cannot follow the previous block + insufficient_work // Insufficient work for this block, even though it passed the minimal validation }; class process_return final { diff --git a/nano/secure/ledger.cpp b/nano/secure/ledger.cpp index 1e552b5a..2539a7a8 100644 --- a/nano/secure/ledger.cpp +++ b/nano/secure/ledger.cpp @@ -333,37 +333,42 @@ void ledger_processor::state_block_impl (nano::state_block & block_a) } if (result.code == nano::process_result::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 (), epoch, is_send, is_receive, false)); - ledger.store.block_put (transaction, hash, block_a); + nano::block_details block_details (epoch, is_send, is_receive, false); + result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result.code == nano::process_result::progress) + { + 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)); + ledger.store.block_put (transaction, hash, block_a); - if (!info.head.is_zero ()) - { - // Move existing representation - ledger.cache.rep_weights.representation_add (info.representative, 0 - info.balance.number ()); - } - // Add in amount delta - ledger.cache.rep_weights.representation_add (block_a.representative (), block_a.hashables.balance.number ()); + if (!info.head.is_zero ()) + { + // Move existing representation + ledger.cache.rep_weights.representation_add (info.representative, 0 - info.balance.number ()); + } + // Add in amount delta + ledger.cache.rep_weights.representation_add (block_a.representative (), block_a.hashables.balance.number ()); - if (is_send) - { - nano::pending_key key (block_a.hashables.link, hash); - nano::pending_info info (block_a.hashables.account, result.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)); - } + if (is_send) + { + nano::pending_key key (block_a.hashables.link, hash); + nano::pending_info info (block_a.hashables.account, result.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)); + } - nano::account_info new_info (hash, block_a.representative (), info.open_block.is_zero () ? hash : info.open_block, block_a.hashables.balance, nano::seconds_since_epoch (), info.block_count + 1, epoch); - ledger.change_latest (transaction, block_a.hashables.account, info, new_info); - if (!ledger.store.frontier_get (transaction, info.head).is_zero ()) - { - ledger.store.frontier_del (transaction, info.head); + nano::account_info new_info (hash, block_a.representative (), info.open_block.is_zero () ? hash : info.open_block, block_a.hashables.balance, nano::seconds_since_epoch (), info.block_count + 1, epoch); + ledger.change_latest (transaction, block_a.hashables.account, info, new_info); + if (!ledger.store.frontier_get (transaction, info.head).is_zero ()) + { + ledger.store.frontier_del (transaction, info.head); + } + // Frontier table is unnecessary for state blocks and this also prevents old blocks from being inserted on top of state blocks + result.account = block_a.hashables.account; } - // Frontier table is unnecessary for state blocks and this also prevents old blocks from being inserted on top of state blocks - result.account = block_a.hashables.account; } } } @@ -419,16 +424,21 @@ void ledger_processor::epoch_block_impl (nano::state_block & block_a) result.code = block_a.hashables.balance == info.balance ? nano::process_result::progress : nano::process_result::balance_mismatch; if (result.code == nano::process_result::progress) { - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::epoch_block); - result.account = block_a.hashables.account; - result.amount = 0; - block_a.sideband_set (nano::block_sideband (block_a.hashables.account /* unused */, 0, 0 /* unused */, info.block_count + 1, nano::seconds_since_epoch (), epoch, false, false, true)); - ledger.store.block_put (transaction, hash, block_a); - nano::account_info new_info (hash, block_a.representative (), info.open_block.is_zero () ? hash : info.open_block, info.balance, nano::seconds_since_epoch (), info.block_count + 1, epoch); - ledger.change_latest (transaction, block_a.hashables.account, info, new_info); - if (!ledger.store.frontier_get (transaction, info.head).is_zero ()) + nano::block_details block_details (epoch, false, false, true); + result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result.code == nano::process_result::progress) { - ledger.store.frontier_del (transaction, info.head); + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::epoch_block); + result.account = block_a.hashables.account; + result.amount = 0; + 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)); + ledger.store.block_put (transaction, hash, block_a); + nano::account_info new_info (hash, block_a.representative (), info.open_block.is_zero () ? hash : info.open_block, info.balance, nano::seconds_since_epoch (), info.block_count + 1, epoch); + ledger.change_latest (transaction, block_a.hashables.account, info, new_info); + if (!ledger.store.frontier_get (transaction, info.head).is_zero ()) + { + ledger.store.frontier_del (transaction, info.head); + } } } } @@ -468,20 +478,25 @@ void ledger_processor::change_block (nano::change_block & block_a) } if (result.code == nano::process_result::progress) { - debug_assert (!validate_message (account, hash, block_a.signature)); - result.verified = nano::signature_verification::valid; - block_a.sideband_set (nano::block_sideband (account, 0, info.balance, info.block_count + 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */)); - ledger.store.block_put (transaction, hash, block_a); - auto balance (ledger.balance (transaction, block_a.hashables.previous)); - ledger.cache.rep_weights.representation_add (block_a.representative (), balance); - ledger.cache.rep_weights.representation_add (info.representative, 0 - balance); - nano::account_info new_info (hash, block_a.representative (), info.open_block, info.balance, nano::seconds_since_epoch (), info.block_count + 1, nano::epoch::epoch_0); - ledger.change_latest (transaction, account, info, new_info); - ledger.store.frontier_del (transaction, block_a.hashables.previous); - ledger.store.frontier_put (transaction, hash, account); - result.account = account; - result.amount = 0; - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::change); + nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); + result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result.code == nano::process_result::progress) + { + debug_assert (!validate_message (account, hash, block_a.signature)); + result.verified = nano::signature_verification::valid; + block_a.sideband_set (nano::block_sideband (account, 0, info.balance, info.block_count + 1, nano::seconds_since_epoch (), block_details)); + ledger.store.block_put (transaction, hash, block_a); + auto balance (ledger.balance (transaction, block_a.hashables.previous)); + ledger.cache.rep_weights.representation_add (block_a.representative (), balance); + ledger.cache.rep_weights.representation_add (info.representative, 0 - balance); + nano::account_info new_info (hash, block_a.representative (), info.open_block, info.balance, nano::seconds_since_epoch (), info.block_count + 1, nano::epoch::epoch_0); + ledger.change_latest (transaction, account, info, new_info); + ledger.store.frontier_del (transaction, block_a.hashables.previous); + ledger.store.frontier_put (transaction, hash, account); + result.account = account; + result.amount = 0; + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::change); + } } } } @@ -514,29 +529,34 @@ void ledger_processor::send_block (nano::send_block & block_a) } if (result.code == nano::process_result::progress) { - debug_assert (!validate_message (account, hash, block_a.signature)); - result.verified = nano::signature_verification::valid; - nano::account_info info; - auto latest_error (ledger.store.account_get (transaction, account, info)); - (void)latest_error; - debug_assert (!latest_error); - debug_assert (info.head == block_a.hashables.previous); - result.code = info.balance.number () >= block_a.hashables.balance.number () ? nano::process_result::progress : nano::process_result::negative_spend; // Is this trying to spend a negative amount (Malicious) + nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); + result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed) if (result.code == nano::process_result::progress) { - auto amount (info.balance.number () - block_a.hashables.balance.number ()); - ledger.cache.rep_weights.representation_add (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 (), nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* 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.change_latest (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.store.frontier_del (transaction, block_a.hashables.previous); - ledger.store.frontier_put (transaction, hash, account); - result.account = account; - result.amount = amount; - result.pending_account = block_a.hashables.destination; - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::send); + debug_assert (!validate_message (account, hash, block_a.signature)); + result.verified = nano::signature_verification::valid; + nano::account_info info; + auto latest_error (ledger.store.account_get (transaction, account, info)); + (void)latest_error; + debug_assert (!latest_error); + debug_assert (info.head == block_a.hashables.previous); + result.code = info.balance.number () >= block_a.hashables.balance.number () ? nano::process_result::progress : nano::process_result::negative_spend; // Is this trying to spend a negative amount (Malicious) + if (result.code == nano::process_result::progress) + { + auto amount (info.balance.number () - block_a.hashables.balance.number ()); + ledger.cache.rep_weights.representation_add (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)); + 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.change_latest (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.store.frontier_del (transaction, block_a.hashables.previous); + ledger.store.frontier_put (transaction, hash, account); + result.account = account; + result.amount = amount; + result.pending_account = block_a.hashables.destination; + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::send); + } } } } @@ -588,22 +608,27 @@ void ledger_processor::receive_block (nano::receive_block & block_a) result.code = pending.epoch == nano::epoch::epoch_0 ? nano::process_result::progress : nano::process_result::unreceivable; // Are we receiving a state-only send? (Malformed) if (result.code == nano::process_result::progress) { - auto new_balance (info.balance.number () + pending.amount.number ()); - nano::account_info source_info; - auto error (ledger.store.account_get (transaction, pending.source, source_info)); - (void)error; - debug_assert (!error); - 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 (), nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* 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.change_latest (transaction, account, info, new_info); - ledger.cache.rep_weights.representation_add (info.representative, pending.amount.number ()); - ledger.store.frontier_del (transaction, block_a.hashables.previous); - ledger.store.frontier_put (transaction, hash, account); - result.account = account; - result.amount = pending.amount; - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::receive); + nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); + result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result.code == nano::process_result::progress) + { + auto new_balance (info.balance.number () + pending.amount.number ()); + nano::account_info source_info; + auto error (ledger.store.account_get (transaction, pending.source, source_info)); + (void)error; + debug_assert (!error); + 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)); + 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.change_latest (transaction, account, info, new_info); + ledger.cache.rep_weights.representation_add (info.representative, pending.amount.number ()); + ledger.store.frontier_del (transaction, block_a.hashables.previous); + ledger.store.frontier_put (transaction, hash, account); + result.account = account; + result.amount = pending.amount; + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::receive); + } } } } @@ -653,20 +678,25 @@ void ledger_processor::open_block (nano::open_block & block_a) result.code = pending.epoch == nano::epoch::epoch_0 ? nano::process_result::progress : nano::process_result::unreceivable; // Are we receiving a state-only send? (Malformed) if (result.code == nano::process_result::progress) { - nano::account_info source_info; - auto error (ledger.store.account_get (transaction, pending.source, source_info)); - (void)error; - debug_assert (!error); - ledger.store.pending_del (transaction, key); - block_a.sideband_set (nano::block_sideband (block_a.hashables.account, 0, pending.amount, 1, nano::seconds_since_epoch (), nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */)); - ledger.store.block_put (transaction, hash, block_a); - nano::account_info new_info (hash, block_a.representative (), hash, pending.amount.number (), nano::seconds_since_epoch (), 1, nano::epoch::epoch_0); - ledger.change_latest (transaction, block_a.hashables.account, info, new_info); - ledger.cache.rep_weights.representation_add (block_a.representative (), pending.amount.number ()); - ledger.store.frontier_put (transaction, hash, block_a.hashables.account); - result.account = block_a.hashables.account; - result.amount = pending.amount; - ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::open); + nano::block_details block_details (nano::epoch::epoch_0, false /* unused */, false /* unused */, false /* unused */); + result.code = block_a.difficulty () >= nano::work_threshold (block_a.work_version (), block_details) ? nano::process_result::progress : nano::process_result::insufficient_work; // Does this block have sufficient work? (Malformed) + if (result.code == nano::process_result::progress) + { + nano::account_info source_info; + auto error (ledger.store.account_get (transaction, pending.source, source_info)); + (void)error; + debug_assert (!error); + ledger.store.pending_del (transaction, key); + block_a.sideband_set (nano::block_sideband (block_a.hashables.account, 0, pending.amount, 1, nano::seconds_since_epoch (), block_details)); + ledger.store.block_put (transaction, hash, block_a); + nano::account_info new_info (hash, block_a.representative (), hash, pending.amount.number (), nano::seconds_since_epoch (), 1, nano::epoch::epoch_0); + ledger.change_latest (transaction, block_a.hashables.account, info, new_info); + ledger.cache.rep_weights.representation_add (block_a.representative (), pending.amount.number ()); + ledger.store.frontier_put (transaction, hash, block_a.hashables.account); + result.account = block_a.hashables.account; + result.amount = pending.amount; + ledger.stats.inc (nano::stat::type::ledger, nano::stat::detail::open); + } } } } @@ -753,7 +783,7 @@ nano::uint128_t nano::ledger::account_pending (nano::transaction const & transac nano::process_return nano::ledger::process (nano::write_transaction const & transaction_a, nano::block & block_a, nano::signature_verification verification) { - debug_assert (!nano::work_validate (block_a)); + debug_assert (network_params.network.is_test_network () || !nano::work_validate (block_a)); ledger_processor processor (*this, transaction_a, verification); block_a.visit (processor); if (processor.result.code == nano::process_result::progress)