Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Use NET bill in transaction receipt during light validation mode #8856

Merged
merged 3 commits into from
Mar 24, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1418,7 +1418,8 @@ struct controller_impl {
transaction_trace_ptr push_transaction( const transaction_metadata_ptr& trx,
fc::time_point deadline,
uint32_t billed_cpu_time_us,
bool explicit_billed_cpu_time )
bool explicit_billed_cpu_time,
fc::optional<uint32_t> explicit_net_usage_words )
{
EOS_ASSERT(deadline != fc::time_point(), transaction_exception, "deadline cannot be uninitialized");

Expand Down Expand Up @@ -1450,13 +1451,18 @@ struct controller_impl {
trace = trx_context.trace;
try {
if( trx->implicit ) {
EOS_ASSERT( !explicit_net_usage_words.valid(), transaction_exception, "NET usage cannot be explicitly set for implicit transactions" );
trx_context.init_for_implicit_trx();
trx_context.enforce_whiteblacklist = false;
} else {
bool skip_recording = replay_head_time && (time_point(trn.expiration) <= *replay_head_time);
trx_context.init_for_input_trx( trx->packed_trx()->get_unprunable_size(),
trx->packed_trx()->get_prunable_size(),
skip_recording);
if( explicit_net_usage_words ) {
trx_context.init_for_input_trx_with_explicit_net( *explicit_net_usage_words, skip_recording );
} else {
trx_context.init_for_input_trx( trx->packed_trx()->get_unprunable_size(),
trx->packed_trx()->get_prunable_size(),
skip_recording );
}
}

trx_context.delay = fc::seconds(trn.delay_sec);
Expand Down Expand Up @@ -1658,7 +1664,7 @@ struct controller_impl {
in_trx_requiring_checks = old_value;
});
in_trx_requiring_checks = true;
push_transaction( onbtrx, fc::time_point::maximum(), gpo.configuration.min_transaction_cpu_usage, true );
push_transaction( onbtrx, fc::time_point::maximum(), gpo.configuration.min_transaction_cpu_usage, true, {} );
} catch( const std::bad_alloc& e ) {
elog( "on block transaction failed due to a std::bad_alloc" );
throw;
Expand Down Expand Up @@ -1890,6 +1896,8 @@ struct controller_impl {

transaction_trace_ptr trace;

bool explicit_net = self.skip_trx_checks();

size_t packed_idx = 0;
const auto& trx_receipts = pending->_block_stage.get<building_block>()._pending_trx_receipts;
for( const auto& receipt : b->transactions ) {
Expand All @@ -1899,7 +1907,11 @@ struct controller_impl {
: ( !!std::get<0>( trx_metas.at( packed_idx ) ) ?
std::get<0>( trx_metas.at( packed_idx ) )
: std::get<1>( trx_metas.at( packed_idx ) ).get() ) );
trace = push_transaction( trx_meta, fc::time_point::maximum(), receipt.cpu_usage_us, true );
fc::optional<uint32_t> explicit_net_usage_words;
if( explicit_net ) {
explicit_net_usage_words = receipt.net_usage_words.value;
}
trace = push_transaction( trx_meta, fc::time_point::maximum(), receipt.cpu_usage_us, true, explicit_net_usage_words );
++packed_idx;
} else if( receipt.trx.contains<transaction_id_type>() ) {
trace = push_scheduled_transaction( receipt.trx.get<transaction_id_type>(), fc::time_point::maximum(), receipt.cpu_usage_us, true );
Expand Down Expand Up @@ -2679,7 +2691,7 @@ transaction_trace_ptr controller::push_transaction( const transaction_metadata_p
validate_db_available_size();
EOS_ASSERT( get_read_mode() != db_read_mode::IRREVERSIBLE, transaction_type_exception, "push transaction not allowed in irreversible mode" );
EOS_ASSERT( trx && !trx->implicit && !trx->scheduled, transaction_type_exception, "Implicit/Scheduled transaction not allowed" );
return my->push_transaction(trx, deadline, billed_cpu_time_us, explicit_billed_cpu_time );
return my->push_transaction(trx, deadline, billed_cpu_time_us, explicit_billed_cpu_time, {} );
}

transaction_trace_ptr controller::push_scheduled_transaction( const transaction_id_type& trxid, fc::time_point deadline,
Expand Down
16 changes: 13 additions & 3 deletions libraries/chain/include/eosio/chain/transaction_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ namespace eosio { namespace chain {

class transaction_context {
private:
void init( uint64_t initial_net_usage);
void init( uint64_t initial_net_usage );

void init_for_input_trx_common( uint64_t initial_net_usage, bool skip_recording );

public:

Expand All @@ -45,7 +47,10 @@ namespace eosio { namespace chain {

void init_for_input_trx( uint64_t packed_trx_unprunable_size,
uint64_t packed_trx_prunable_size,
bool skip_recording);
bool skip_recording );

void init_for_input_trx_with_explicit_net( uint32_t explicit_net_usage_words,
bool skip_recording );

void init_for_deferred_trx( fc::time_point published );

Expand All @@ -54,7 +59,11 @@ namespace eosio { namespace chain {
void squash();
void undo();

inline void add_net_usage( uint64_t u ) { net_usage += u; check_net_usage(); }
inline void add_net_usage( uint64_t u ) {
if( explicit_net_usage ) return;
net_usage += u;
check_net_usage();
}
heifner marked this conversation as resolved.
Show resolved Hide resolved

void check_net_usage()const;

Expand Down Expand Up @@ -156,6 +165,7 @@ namespace eosio { namespace chain {
bool net_limit_due_to_greylist = false;
uint64_t eager_net_limit = 0;
uint64_t& net_usage; /// reference to trace->net_usage
bool explicit_net_usage = false;

bool cpu_limit_due_to_greylist = false;

Expand Down
28 changes: 24 additions & 4 deletions libraries/chain/transaction_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ namespace eosio { namespace chain {
}
}

void transaction_context::init(uint64_t initial_net_usage)
void transaction_context::init( uint64_t initial_net_usage )
{
EOS_ASSERT( !is_initialized, transaction_exception, "cannot initialize twice" );

Expand Down Expand Up @@ -197,7 +197,7 @@ namespace eosio { namespace chain {
}

published = control.pending_block_time();
init( initial_net_usage);
init( initial_net_usage );
}

void transaction_context::init_for_input_trx( uint64_t packed_trx_unprunable_size,
Expand Down Expand Up @@ -230,14 +230,32 @@ namespace eosio { namespace chain {
+ static_cast<uint64_t>(config::transaction_id_net_usage);
}

init_for_input_trx_common( initial_net_usage, skip_recording );
}

void transaction_context::init_for_input_trx_with_explicit_net( uint32_t explicit_net_usage_words,
bool skip_recording )
{
if( trx.transaction_extensions.size() > 0 ) {
disallow_transaction_extensions( "no transaction extensions supported yet for input transactions" );
}
heifner marked this conversation as resolved.
Show resolved Hide resolved

explicit_net_usage = true;
net_usage = (static_cast<uint64_t>(explicit_net_usage_words) * 8);

init_for_input_trx_common( 0, skip_recording );
}

void transaction_context::init_for_input_trx_common( uint64_t initial_net_usage, bool skip_recording )
{
published = control.pending_block_time();
is_input = true;
if (!control.skip_trx_checks()) {
control.validate_expiration(trx);
control.validate_tapos(trx);
validate_referenced_accounts( trx, enforce_whiteblacklist && control.is_producing_block() );
}
init( initial_net_usage);
init( initial_net_usage );
if (!skip_recording)
record_transaction( id, trx.expiration ); /// checks for dupes
}
Expand Down Expand Up @@ -323,7 +341,9 @@ namespace eosio { namespace chain {
billing_timer_exception_code = tx_cpu_usage_exceeded::code_value;
}

net_usage = ((net_usage + 7)/8)*8; // Round up to nearest multiple of word size (8 bytes)
if( !explicit_net_usage ) {
net_usage = ((net_usage + 7)/8)*8; // Round up to nearest multiple of word size (8 bytes)
}
heifner marked this conversation as resolved.
Show resolved Hide resolved

arhag marked this conversation as resolved.
Show resolved Hide resolved
eager_net_limit = net_limit;
check_net_usage();
Expand Down
12 changes: 12 additions & 0 deletions libraries/testing/include/eosio/testing/tester.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,18 @@ namespace eosio { namespace testing {
bool skip_validate = false;
};

/**
* Utility predicate to check whether an fc::exception code is equivalent to a given value
*/
struct fc_exception_code_is {
fc_exception_code_is( int64_t code )
: expected( code ) {}

bool operator()( const fc::exception& ex );

int64_t expected;
};

/**
* Utility predicate to check whether an fc::exception message is equivalent to a given string
*/
Expand Down
9 changes: 9 additions & 0 deletions libraries/testing/tester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,15 @@ namespace eosio { namespace testing {
preactivate_protocol_features( preactivations );
}

bool fc_exception_code_is::operator()( const fc::exception& ex ) {
bool match = (ex.code() == expected);
if( !match ) {
auto message = ex.get_log().at( 0 ).get_message();
BOOST_TEST_MESSAGE( "LOG: expected code: " << expected << ", actual code: " << ex.code() << ", message: " << message );
}
return match;
}

bool fc_exception_message_is::operator()( const fc::exception& ex ) {
auto message = ex.get_log().at( 0 ).get_message();
bool match = (message == expected);
Expand Down
141 changes: 140 additions & 1 deletion unittests/resource_limits_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <eosio/chain/resource_limits.hpp>
#include <eosio/chain/config.hpp>
#include <eosio/testing/chainbase_fixture.hpp>
#include <eosio/testing/tester.hpp>
#include "fork_test_utilities.hpp"

#include <boost/test/unit_test.hpp>

Expand Down Expand Up @@ -417,5 +419,142 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test)

} FC_LOG_AND_RETHROW()

BOOST_AUTO_TEST_CASE(light_net_validation) try {
tester main( setup_policy::preactivate_feature_and_new_bios );
tester validator( setup_policy::none );
tester validator2( setup_policy::none );

name test_account("alice");
name test_account2("bob");

constexpr int64_t net_weight = 1;
constexpr int64_t other_net_weight = 1'000'000'000;

signed_block_ptr trigger_block;

// Create test account with limited NET quota
main.create_accounts( {test_account, test_account2} );
main.push_action( config::system_account_name, N(setalimits), config::system_account_name, fc::mutable_variant_object()
("account", test_account)
("ram_bytes", -1)
("net_weight", net_weight)
("cpu_weight", -1)
);
main.push_action( config::system_account_name, N(setalimits), config::system_account_name, fc::mutable_variant_object()
("account", test_account2)
("ram_bytes", -1)
("net_weight", other_net_weight)
("cpu_weight", -1)
);
main.produce_block();

// Sync validator and validator2 nodes with the main node as of this state.
push_blocks( main, validator );
push_blocks( main, validator2 );

// Restart validator2 node in light validation mode.
{
validator2.close();
auto cfg = validator2.get_config();
cfg.block_validation_mode = validation_mode::LIGHT;
validator2.init( cfg );
}

const auto& rlm = main.control->get_resource_limits_manager();

// NET quota of test account should be enough to support one but not two reqauth transactions.
int64_t before_net_usage = rlm.get_account_net_limit_ex( test_account ).first.used;
main.push_action( config::system_account_name, N(reqauth), test_account, fc::mutable_variant_object()("from", "alice"), 6 );
int64_t after_net_usage = rlm.get_account_net_limit_ex( test_account ).first.used;
int64_t reqauth_net_usage_delta = after_net_usage - before_net_usage;
BOOST_REQUIRE_EXCEPTION(
main.push_action( config::system_account_name, N(reqauth), test_account, fc::mutable_variant_object()("from", "alice"), 7 ),
fc::exception, fc_exception_code_is( tx_net_usage_exceeded::code_value )
);
trigger_block = main.produce_block();

auto main_schedule_hash = main.control->head_block_state()->pending_schedule.schedule_hash;
auto main_block_mroot = main.control->head_block_state()->blockroot_merkle.get_root();

// Modify NET bill in transaction receipt within trigger block to cause the NET usage of test account to exceed its quota.
{
// Increase the NET usage in the reqauth transaction receipt.
trigger_block->transactions.back().net_usage_words.value = 2*((reqauth_net_usage_delta + 7)/8); // double the NET bill

// Re-calculate the transaction merkle
deque<digest_type> trx_digests;
const auto& trxs = trigger_block->transactions;
for( const auto& a : trxs )
trx_digests.emplace_back( a.digest() );
trigger_block->transaction_mroot = merkle( move(trx_digests) );

// Re-sign the block
auto header_bmroot = digest_type::hash( std::make_pair( trigger_block->digest(), main_block_mroot ) );
auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, main_schedule_hash ) );
trigger_block->producer_signature = main.get_private_key(config::system_account_name, "active").sign(sig_digest);
}

// Push trigger block to validator node.
// This should fail because the NET bill calculated by the fully-validating node will differ from the one in the block.
{
auto bs = validator.control->create_block_state_future( trigger_block );
validator.control->abort_block();
BOOST_REQUIRE_EXCEPTION(
validator.control->push_block( bs, forked_branch_callback{}, trx_meta_cache_lookup{} ),
fc::exception, fc_exception_message_is( "receipt does not match" )
);
}

// Push trigger block to validator2 node.
// This should still cause a NET failure, but will no longer be due to a receipt mismatch.
// Because validator2 is in light validation mode, it does not compute the NET bill itself and instead only relies on the value in the block.
// The failure will be due to failing to satisfy the invariant in resource_limits_manager::add_transaction_usage
// because the NET bill in the block is too high.
{
auto bs = validator2.control->create_block_state_future( trigger_block );
validator2.control->abort_block();
BOOST_REQUIRE_EXCEPTION(
validator2.control->push_block( bs, forked_branch_callback{}, trx_meta_cache_lookup{} ),
fc::exception, fc_exception_code_is( tx_net_usage_exceeded::code_value )
);
}

// Modify NET bill in transaction receipt within trigger block to be lower than what it originally was.
{
// Increase the NET usage in the reqauth transaction receipt.
trigger_block->transactions.back().net_usage_words.value = ((reqauth_net_usage_delta + 7)/8)/2; // half the original NET bill

// Re-calculate the transaction merkle
deque<digest_type> trx_digests;
const auto& trxs = trigger_block->transactions;
for( const auto& a : trxs )
trx_digests.emplace_back( a.digest() );
trigger_block->transaction_mroot = merkle( move(trx_digests) );

// Re-sign the block
auto header_bmroot = digest_type::hash( std::make_pair( trigger_block->digest(), main_block_mroot ) );
auto sig_digest = digest_type::hash( std::make_pair(header_bmroot, main_schedule_hash ) );
trigger_block->producer_signature = main.get_private_key(config::system_account_name, "active").sign(sig_digest);
}

// Push new trigger block to validator node.
// This should still fail because the NET bill is incorrect.
{
auto bs = validator.control->create_block_state_future( trigger_block );
validator.control->abort_block();
BOOST_REQUIRE_EXCEPTION(
validator.control->push_block( bs, forked_branch_callback{}, trx_meta_cache_lookup{} ),
fc::exception, fc_exception_message_is( "receipt does not match" )
);
}

// Push new trigger block to validator2 node.
// Because validator2 is in light validation mode, this will not fail despite the fact that the NET bill is incorrect.
{
auto bs = validator2.control->create_block_state_future( trigger_block );
validator2.control->abort_block();
validator2.control->push_block( bs, forked_branch_callback{}, trx_meta_cache_lookup{} );
}
} FC_LOG_AND_RETHROW()

BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()