Skip to content

Commit

Permalink
Block checker
Browse files Browse the repository at this point in the history
  • Loading branch information
clemahieu committed Nov 14, 2023
1 parent 083f243 commit 6d34835
Show file tree
Hide file tree
Showing 8 changed files with 524 additions and 23 deletions.
13 changes: 7 additions & 6 deletions nano/core_test/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3076,6 +3076,8 @@ TEST (node, block_processor_half_full)
node_flags.force_use_write_database_queue = true;
auto & node = *system.add_node (nano::node_config (system.get_available_port (), system.logging), node_flags);
nano::state_block_builder builder;

// All blocks are forks of each other so none of the blocks going through pipeline have a gap
auto send1 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (nano::dev::genesis->hash ())
Expand All @@ -3087,24 +3089,23 @@ TEST (node, block_processor_half_full)
.build_shared ();
auto send2 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (send1->hash ())
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 2 * nano::Gxrb_ratio)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node.work_generate_blocking (send1->hash ()))
.work (*node.work_generate_blocking (nano::dev::genesis->hash ()))
.build_shared ();
auto send3 = builder.make_block ()
.account (nano::dev::genesis_key.pub)
.previous (send2->hash ())
.previous (nano::dev::genesis->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 3 * nano::Gxrb_ratio)
.link (nano::dev::genesis_key.pub)
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (*node.work_generate_blocking (send2->hash ()))
.work (*node.work_generate_blocking (nano::dev::genesis->hash ()))
.build_shared ();
// The write guard prevents block processor doing any writes
auto write_guard = node.write_database_queue.wait (nano::writer::testing);
node.block_processor.stop ();
node.block_processor.add (send1);
ASSERT_FALSE (node.block_processor.half_full ());
node.block_processor.add (send2);
Expand Down
4 changes: 4 additions & 0 deletions nano/node/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ add_library(
block_arrival.cpp
block_broadcast.cpp
block_broadcast.hpp
block_checker.cpp
block_checker.hpp
block_checker_rules.cpp
block_checker_rules.hpp
block_publisher.cpp
block_publisher.hpp
gap_tracker.cpp
Expand Down
225 changes: 225 additions & 0 deletions nano/node/block_checker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
#include <nano/node/block_checker.hpp>
#include <nano/node/block_checker_rules.hpp>
#include <nano/secure/ledger.hpp>
#include <nano/store/block.hpp>
#include <nano/store/component.hpp>
#include <nano/store/pending.hpp>

nano::block_checker::context::context (nano::ledger & ledger, std::shared_ptr<nano::block> block) :
block{ block },
epochs{ ledger.constants.epochs }
{
auto transaction = ledger.store.tx_begin_read ();
previous = ledger.store.block.get (transaction, block->previous ());
if (!gap_previous ())
{
state = ledger.account_info (transaction, account ());
if (!state)
{
state = nano::account_info{};
}
source_exists = ledger.block_or_pruned_exists (transaction, source ());
pending = ledger.pending_info (transaction, { account (), source () });
any_pending = ledger.store.pending.any (transaction, account ());
}
if (ledger.store.block.exists (transaction, block->hash ()))
{
this->block = nullptr; // Signal this block already exists by nulling out block
}
//nano::account const & account_a, nano::block_hash const & successor_a, nano::amount const & balance_a, uint64_t const height_a, nano::seconds_t const timestamp_a, nano::block_details const & details_a, nano::epoch const source_epoch_a
//block->sideband_set (nano::block_sideband (account (), 0, pending.amount, 1, nano::seconds_since_epoch (), block_details, nano::epoch::epoch_0 /* unused */));
}
/**
This class 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_checker::block_op nano::block_checker::context::block_op () const
{
debug_assert (state.has_value ());
switch (block->type ())
{
case nano::block_type::state:
if (block->balance () < state->balance)
{
return nano::block_checker::block_op::send;
}
if (block->link ().is_zero ())
{
return nano::block_checker::block_op::noop;
}
if (epochs.is_epoch_link (block->link ()))
{
return nano::block_checker::block_op::epoch;
}
return nano::block_checker::block_op::receive;
case nano::block_type::send:
return nano::block_checker::block_op::send;
case nano::block_type::open:
case nano::block_type::receive:
return nano::block_checker::block_op::receive;
case nano::block_type::change:
return nano::block_checker::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_checker::context::is_send () const
{
debug_assert (state.has_value ());
auto legacy_send = block->type () == nano::block_type::send;
auto type = block->type () == nano::block_type::state;
auto decreased = block->balance () < state->balance;
return legacy_send || (type && decreased);
}

nano::account nano::block_checker::context::account () const
{
switch (block->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->account ();
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_checker::context::source () const
{
switch (block->type ())
{
case nano::block_type::send:
case nano::block_type::change:
// 0 is returned for source on send/change blocks
case nano::block_type::receive:
case nano::block_type::open:
return block->source ();
case nano::block_type::state:
return block->link ().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_checker::context::signer (nano::epochs const & epochs) const
{
debug_assert (block != nullptr);
switch (block->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<nano::state_block *> (block.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->link ()) || is_send ()) ? block->account () : epochs.signer (epochs.epoch (block->link ()));
}
case nano::block_type::open: // Open block signer is determined statelessly as it's written in the block
return block->account ();
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_checker::context::gap_previous () const
{
return !block->previous ().is_zero () && previous == nullptr;
}

nano::block_checker::block_checker ()
{
}

nano::process_result nano::block_checker::check (context & context)
{
if (!context.block)
{
return nano::process_result::old;
}
nano::process_result result;
if (result = nano::rule_reserved_account (context), result != nano::process_result::progress)
{
return result;
}
if (result = nano::rule_previous_frontier (context), result != nano::process_result::progress)
{
return result;
}
if (result = nano::rule_block_position (context), result != nano::process_result::progress)
{
return result;
}
if (result = nano::rule_block_signed (context), result != nano::process_result::progress)
{
return result;
}
if (result = nano::rule_metastable (context), result != nano::process_result::progress)
{
return result;
}
switch (context.block_op ())
{
case block_op::receive:
return nano::rule_receivable (context);
case block_op::send:
return nano::rule_send_restrictions (context);
case block_op::noop:
return nano::process_result::progress;
case block_op::epoch:
return nano::rule_epoch_restrictions (context);
}
}
52 changes: 52 additions & 0 deletions nano/node/block_checker.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#pragma once

#include <nano/lib/epoch.hpp>
#include <nano/lib/numbers.hpp>
#include <nano/secure/common.hpp>

#include <memory>
#include <optional>

namespace nano
{
class block;
class ledger;
}

namespace nano
{
class block_checker
{
public:
enum class block_op
{
receive,
send,
noop,
epoch
};
// Context that is passed between pipeline stages
class context
{
public:
context (nano::ledger & ledger, std::shared_ptr<nano::block> block);
bool is_send () const;
nano::account account () const;
nano::block_hash source () const;
nano::account signer (nano::epochs const & epochs) const;
bool gap_previous () const;
block_op block_op () const;
std::shared_ptr<nano::block> block;
std::shared_ptr<nano::block> previous;
std::optional<nano::account_info> state;
std::optional<nano::pending_info> pending;
bool any_pending{ false };
bool source_exists{ false };
nano::epochs & epochs;
};

public:
block_checker ();
nano::process_result check (context & context);
};
} // namespace nano
Loading

0 comments on commit 6d34835

Please sign in to comment.