Skip to content

Commit

Permalink
Adding receive_restrictions_filter which checks validity of blocks re…
Browse files Browse the repository at this point in the history
…ceiving amounts.
  • Loading branch information
clemahieu committed Mar 3, 2022
1 parent c28a82f commit 47bf309
Show file tree
Hide file tree
Showing 14 changed files with 341 additions and 2 deletions.
1 change: 1 addition & 0 deletions nano/core_test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ add_executable(
peer_container.cpp
signer_decorator.cpp
prioritization.cpp
receive_restrictions_filter.cpp
request_aggregator.cpp
reserved_account_filter.cpp
signal_manager.cpp
Expand Down
225 changes: 225 additions & 0 deletions nano/core_test/receive_restrictions_filter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
#include <nano/lib/blockbuilders.hpp>
#include <nano/lib/stats.hpp>
#include <nano/node/block_pipeline/context.hpp>
#include <nano/node/block_pipeline/receive_restrictions_filter.hpp>
#include <nano/secure/common.hpp>
#include <nano/secure/ledger.hpp>
#include <nano/secure/store.hpp>
#include <nano/secure/utility.hpp>

#include <gtest/gtest.h>

namespace
{
class context
{
public:
context ()
{
filter.pass = [this] (nano::block_pipeline::context & context) {
pass.push_back (std::make_pair (context.block, context.previous));
};
filter.reject_balance = [this] (nano::block_pipeline::context & context) {
reject_balance.push_back (context.block);
};
filter.reject_pending = [this] (nano::block_pipeline::context & context) {
reject_pending.push_back (context.block);
};
}
nano::block_pipeline::receive_restrictions_filter filter;
std::vector<std::pair<std::shared_ptr<nano::block>, std::shared_ptr<nano::block>>> pass;
std::vector<std::shared_ptr<nano::block>> reject_balance;
std::vector<std::shared_ptr<nano::block>> reject_pending;
};
nano::block_pipeline::context pass_receive_block ()
{
nano::block_builder builder;
nano::block_pipeline::context result;
auto send = builder.send () // Dummy block
.previous (nano::dev::genesis->hash ())
.destination (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 1) // 1 raw nano is sent
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
result.pending = nano::pending_info{ nano::dev::genesis_key.pub, 1 /* 1 raw nano is receivable */, nano::epoch::epoch_0 };
result.state = nano::account_info{};
result.state->balance = nano::dev::constants.genesis_amount - 1;
result.block = builder.receive ()
.previous (send->hash ())
.source (send->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
return result;
}
nano::block_pipeline::context pass_open_block ()
{
nano::block_builder builder;
nano::block_pipeline::context result;
nano::keypair key;
auto send = builder.send () // Dummy block
.previous (nano::dev::genesis->hash ())
.destination (key.pub)
.balance (nano::dev::constants.genesis_amount - 1) // 1 raw nano is sent
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
result.pending = nano::pending_info{ nano::dev::genesis_key.pub, 1 /* 1 raw nano is receivable */, nano::epoch::epoch_0 };
result.state = nano::account_info{};
result.state->balance = 0;
result.block = builder.open ()
.source (send->hash ())
.representative (nano::dev::genesis_key.pub)
.account (key.pub)
.sign (key.prv, key.pub)
.work (0)
.build_shared ();
return result;
}
nano::block_pipeline::context pass_state_block ()
{
nano::block_builder builder;
nano::block_pipeline::context result;
auto send = builder.send () // Dummy block
.previous (nano::dev::genesis->hash ())
.destination (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 1) // 1 raw nano is sent
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
result.pending = nano::pending_info{ nano::dev::genesis_key.pub, 1 /* 1 raw nano is receivable */, nano::epoch::epoch_0 };
result.state = nano::account_info{};
result.state->balance = 0;
result.block = builder.state ()
.account (nano::dev::genesis_key.pub)
.previous (send->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (1)
.link (send->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
return result;
}
nano::block_pipeline::context reject_pending_state_block ()
{
nano::block_builder builder;
nano::block_pipeline::context result;
auto send = builder.send () // Dummy block
.previous (nano::dev::genesis->hash ())
.destination (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 1) // 1 raw nano is sent
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
// result.pending is not set, there is no pending entry
result.state = nano::account_info{};
result.state->balance = nano::dev::constants.genesis_amount - 1;
result.block = builder.state ()
.account (nano::dev::genesis_key.pub)
.previous (send->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (1)
.link (send->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
return result;
}
nano::block_pipeline::context reject_pending_receive_block ()
{
nano::block_builder builder;
nano::block_pipeline::context result;
auto send = builder.send () // Dummy block
.previous (nano::dev::genesis->hash ())
.destination (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 1) // 1 raw nano is sent
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
// result.pending is not set, there is no pending entry
result.state = nano::account_info{};
result.state->balance = nano::dev::constants.genesis_amount - 1;
result.block = builder.receive ()
.previous (send->hash ())
.source (send->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
return result;
}
nano::block_pipeline::context reject_balance_block ()
{
nano::block_builder builder;
nano::block_pipeline::context result;
auto send = builder.send () // Dummy block
.previous (nano::dev::genesis->hash ())
.destination (nano::dev::genesis_key.pub)
.balance (nano::dev::constants.genesis_amount - 1) // 1 raw nano is sent
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
result.pending = nano::pending_info{ nano::dev::genesis_key.pub, 1 /* 1 raw nano is receivable */, nano::epoch::epoch_0 };
result.state = nano::account_info{};
result.state->balance = nano::dev::constants.genesis_amount - 1;
result.block = builder.state ()
.account (nano::dev::genesis_key.pub)
.previous (send->hash ())
.representative (nano::dev::genesis_key.pub)
.balance (2) // Balance does not match how much was sent
.link (send->hash ())
.sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub)
.work (0)
.build_shared ();
return result;
}
}

TEST (receive_restrictions_filter, pass_receive)
{
context context;
auto pass = pass_receive_block ();
context.filter.sink (pass);
ASSERT_EQ (1, context.pass.size ());
}

TEST (receive_restrictions_filter, pass_open)
{
context context;
auto pass = pass_open_block ();
context.filter.sink (pass);
ASSERT_EQ (1, context.pass.size ());
}

TEST (receive_restrictions_filter, pass_state)
{
context context;
auto pass = pass_state_block ();
context.filter.sink (pass);
ASSERT_EQ (1, context.pass.size ());
}

TEST (receive_restrictions_filter, reject_pending_state)
{
context context;
auto reject = reject_pending_state_block ();
context.filter.sink (reject);
ASSERT_EQ (1, context.reject_pending.size ());
}

TEST (receive_restrictions_filter, reject_pending_receive)
{
context context;
auto reject = reject_pending_receive_block ();
context.filter.sink (reject);
ASSERT_EQ (1, context.reject_pending.size ());
}

TEST (receive_restrictions_filter, reject_balance)
{
context context;
auto reject = reject_balance_block ();
context.filter.sink (reject);
ASSERT_EQ (1, context.reject_balance.size ());
}
9 changes: 9 additions & 0 deletions nano/lib/stats.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -927,6 +927,15 @@ std::string nano::stat::detail_to_string (stat::detail detail)
case nano::stat::detail::metastable_filter_reject:
res = "metastable_filter_reject";
break;
case nano::stat::detail::receive_restrictions_pass:
res = "receive_restrictions_pass";
break;
case nano::stat::detail::receive_restrictions_reject_balance:
res = "receive_restrictions_reject_balance";
break;
case nano::stat::detail::receive_restrictions_reject_pending:
res = "receive_restrictions_reject_pending";
break;
case nano::stat::detail::reserved_account_filter_pass:
res = "reserved_account_filter_pass";
break;
Expand Down
3 changes: 3 additions & 0 deletions nano/lib/stats.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,9 @@ class stat final
link_filter_epoch,
metastable_filter_pass,
metastable_filter_reject,
receive_restrictions_pass,
receive_restrictions_reject_balance,
receive_restrictions_reject_pending,
reserved_account_filter_pass,
reserved_account_filter_reject,
signer_decorator_output,
Expand Down
2 changes: 2 additions & 0 deletions nano/node/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ add_library(
block_pipeline/metastable_filter.hpp
block_pipeline/gap_previous_filter.cpp
block_pipeline/gap_previous_filter.hpp
block_pipeline/receive_restrictions_filter.cpp
block_pipeline/receive_restrictions_filter.hpp
block_pipeline/reserved_account_filter.cpp
block_pipeline/reserved_account_filter.hpp
block_pipeline/signer_decorator.cpp
Expand Down
6 changes: 5 additions & 1 deletion nano/node/block_pipeline/account_state_decorator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ nano::block_pipeline::account_state_decorator::account_state_decorator (nano::le

void nano::block_pipeline::account_state_decorator::sink (context & context) const
{
context.state = ledger.account_info (ledger.store.tx_begin_read (), context.account ());
{
auto transaction = ledger.store.tx_begin_read ();
context.state = ledger.account_info (transaction, context.account ());
context.pending = ledger.pending_info (transaction, { context.account (), context.source () });
}
output (context);
}
18 changes: 18 additions & 0 deletions nano/node/block_pipeline/context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,21 @@ nano::account nano::block_pipeline::context::account () const
break;
}
}

nano::block_hash nano::block_pipeline::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;
}
}
2 changes: 2 additions & 0 deletions nano/node/block_pipeline/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ namespace block_pipeline
context () = default;
bool is_send () const;
nano::account account () const;
nano::block_hash source () const;
std::shared_ptr<nano::block> block;
std::shared_ptr<nano::block> previous;
std::optional<nano::account> signer;
std::optional<nano::account_info> state;
std::optional<nano::pending_info> pending;
nano::signature_verification verification{ nano::signature_verification::unknown };
};
}
Expand Down
24 changes: 24 additions & 0 deletions nano/node/block_pipeline/receive_restrictions_filter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include <nano/node/block_pipeline/context.hpp>
#include <nano/node/block_pipeline/receive_restrictions_filter.hpp>
#include <nano/secure/ledger.hpp>
#include <nano/secure/store.hpp>

void nano::block_pipeline::receive_restrictions_filter::sink (context & context)
{
debug_assert (context.state.has_value ());
if (!context.pending.has_value ())
{
reject_pending (context);
return;
}
if (context.block->type () == nano::block_type::state)
{
auto next_balance = context.state->balance.number () + context.pending->amount.number ();
if (next_balance != context.block->balance ().number ())
{
reject_balance (context);
return;
}
}
pass (context);
}
25 changes: 25 additions & 0 deletions nano/node/block_pipeline/receive_restrictions_filter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <functional>

namespace nano
{
namespace block_pipeline
{
class context;
/**
This class filters blocks that don't follow restrictions on receiving.
Receiving must:
- Receive a block that has not been received already
- Update the balance to the sum of the previous balance plus the amount received
*/
class receive_restrictions_filter
{
public:
void sink (context & context);
std::function<void (context & context)> pass;
std::function<void (context & context)> reject_balance;
std::function<void (context & context)> reject_pending;
};
} // namespace block_pipeline
} // namespacenano
Loading

0 comments on commit 47bf309

Please sign in to comment.