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

Commit

Permalink
Merge pull request #8679 from EOSIO/opt-cpu-check-dev
Browse files Browse the repository at this point in the history
Exit transaction early when insufficient account cpu - develop
  • Loading branch information
heifner authored Feb 25, 2020
2 parents b682368 + 138294a commit 474c26a
Show file tree
Hide file tree
Showing 11 changed files with 241 additions and 61 deletions.
32 changes: 21 additions & 11 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1215,6 +1215,10 @@ struct controller_impl {

transaction_trace_ptr push_scheduled_transaction( const generated_transaction_object& gto, fc::time_point deadline, uint32_t billed_cpu_time_us, bool explicit_billed_cpu_time = false )
{ try {

const bool validating = !self.is_producing_block();
EOS_ASSERT( !validating || explicit_billed_cpu_time, transaction_exception, "validating requires explicit billing" );

maybe_session undo_session;
if ( !self.skip_db_sessions() )
undo_session = maybe_session(db);
Expand Down Expand Up @@ -1322,7 +1326,7 @@ struct controller_impl {

// Only subjective OR soft OR hard failure logic below:

if( gtrx.sender != account_name() && !(explicit_billed_cpu_time ? failure_is_subjective(*trace->except) : scheduled_failure_is_subjective(*trace->except))) {
if( gtrx.sender != account_name() && !(validating ? failure_is_subjective(*trace->except) : scheduled_failure_is_subjective(*trace->except))) {
// Attempt error handling for the generated transaction.

auto error_trace = apply_onerror( gtrx, deadline, trx_context.pseudo_start,
Expand All @@ -1344,7 +1348,7 @@ struct controller_impl {

// subjectivity changes based on producing vs validating
bool subjective = false;
if (explicit_billed_cpu_time) {
if (validating) {
subjective = failure_is_subjective(*trace->except);
} else {
subjective = scheduled_failure_is_subjective(*trace->except);
Expand All @@ -1353,15 +1357,18 @@ struct controller_impl {
if ( !subjective ) {
// hard failure logic

if( !explicit_billed_cpu_time ) {
if( !validating ) {
auto& rl = self.get_mutable_resource_limits_manager();
rl.update_account_usage( trx_context.bill_to_accounts, block_timestamp_type(self.pending_block_time()).slot );
int64_t account_cpu_limit = 0;
std::tie( std::ignore, account_cpu_limit, std::ignore, std::ignore ) = trx_context.max_bandwidth_billed_accounts_can_pay( true );

cpu_time_to_bill_us = static_cast<uint32_t>( std::min( std::min( static_cast<int64_t>(cpu_time_to_bill_us),
account_cpu_limit ),
trx_context.initial_objective_duration_limit.count() ) );
uint32_t limited_cpu_time_to_bill_us = static_cast<uint32_t>( std::min(
std::min( static_cast<int64_t>(cpu_time_to_bill_us), account_cpu_limit ),
trx_context.initial_objective_duration_limit.count() ) );
EOS_ASSERT( !explicit_billed_cpu_time || (cpu_time_to_bill_us == limited_cpu_time_to_bill_us),
transaction_exception, "cpu to bill ${cpu} != limited ${limit}", ("cpu", cpu_time_to_bill_us)("limit", limited_cpu_time_to_bill_us) );
cpu_time_to_bill_us = limited_cpu_time_to_bill_us;
}

resource_limits.add_transaction_usage( trx_context.bill_to_accounts, cpu_time_to_bill_us, 0,
Expand Down Expand Up @@ -1411,7 +1418,7 @@ 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 = false )
bool explicit_billed_cpu_time )
{
EOS_ASSERT(deadline != fc::time_point(), transaction_exception, "deadline cannot be uninitialized");

Expand Down Expand Up @@ -1474,6 +1481,7 @@ struct controller_impl {
? transaction_receipt::executed
: transaction_receipt::delayed;
trace->receipt = push_receipt(*trx->packed_trx(), s, trx_context.billed_cpu_time_us, trace->net_usage);
trx->billed_cpu_time_us = trx_context.billed_cpu_time_us;
pending->_block_stage.get<building_block>()._pending_trx_metas.emplace_back(trx);
} else {
transaction_receipt_header r;
Expand Down Expand Up @@ -2670,18 +2678,20 @@ bool controller::in_immutable_mode()const{
return (db_mode_is_immutable(get_read_mode()));
}

transaction_trace_ptr controller::push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline, uint32_t billed_cpu_time_us ) {
transaction_trace_ptr controller::push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline,
uint32_t billed_cpu_time_us, bool explicit_billed_cpu_time ) {
validate_db_available_size();
EOS_ASSERT( !in_immutable_mode(), transaction_type_exception, "push transaction not allowed in read-only 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, billed_cpu_time_us > 0 );
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, uint32_t billed_cpu_time_us )
transaction_trace_ptr controller::push_scheduled_transaction( const transaction_id_type& trxid, fc::time_point deadline,
uint32_t billed_cpu_time_us, bool explicit_billed_cpu_time )
{
EOS_ASSERT( !in_immutable_mode(), transaction_type_exception, "push scheduled transaction not allowed in read-only mode" );
validate_db_available_size();
return my->push_scheduled_transaction( trxid, deadline, billed_cpu_time_us, billed_cpu_time_us > 0 );
return my->push_scheduled_transaction( trxid, deadline, billed_cpu_time_us, explicit_billed_cpu_time );
}

const flat_set<account_name>& controller::get_actor_whitelist() const {
Expand Down
6 changes: 4 additions & 2 deletions libraries/chain/include/eosio/chain/controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,15 @@ namespace eosio { namespace chain {
/**
*
*/
transaction_trace_ptr push_transaction( const transaction_metadata_ptr& trx, fc::time_point deadline, uint32_t billed_cpu_time_us = 0 );
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 );

/**
* Attempt to execute a specific transaction in our deferred trx database
*
*/
transaction_trace_ptr push_scheduled_transaction( const transaction_id_type& scheduled, fc::time_point deadline, uint32_t billed_cpu_time_us = 0 );
transaction_trace_ptr push_scheduled_transaction( const transaction_id_type& scheduled, fc::time_point deadline,
uint32_t billed_cpu_time_us, bool explicit_billed_cpu_time );

block_state_ptr finalize_block( const signer_callback_type& signer_callback );
void sign_block( const signer_callback_type& signer_callback );
Expand Down
3 changes: 2 additions & 1 deletion libraries/chain/include/eosio/chain/transaction_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ namespace eosio { namespace chain {
void schedule_transaction();
void record_transaction( const transaction_id_type& id, fc::time_point_sec expire );

void validate_cpu_usage_to_bill( int64_t u, bool check_minimum = true )const;
void validate_cpu_usage_to_bill( int64_t billed_us, int64_t account_cpu_limit, bool check_minimum )const;
void validate_account_cpu_usage( int64_t billed_us, int64_t account_cpu_limit, bool estimate )const;

void disallow_transaction_extensions( const char* error_msg )const;

Expand Down
3 changes: 2 additions & 1 deletion libraries/chain/include/eosio/chain/transaction_metadata.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ class transaction_metadata {
public:
const bool implicit;
const bool scheduled;
bool accepted = false; // not thread safe
bool accepted = false; // not thread safe
uint32_t billed_cpu_time_us = 0; // not thread safe

private:
struct private_type{};
Expand Down
24 changes: 16 additions & 8 deletions libraries/chain/resource_limits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -386,13 +386,17 @@ std::pair<account_resource_limit, bool> resource_limits_manager::get_account_cpu
account_resource_limit arl;

uint128_t window_size = config.account_cpu_usage_average_window;
uint64_t greylisted_virtual_cpu_limit = config.cpu_limit_parameters.max * greylist_limit;

bool greylisted = false;
uint128_t virtual_cpu_capacity_in_window = window_size;
if( greylisted_virtual_cpu_limit < state.virtual_cpu_limit ) {
virtual_cpu_capacity_in_window *= greylisted_virtual_cpu_limit;
greylisted = true;
if( greylist_limit < config::maximum_elastic_resource_multiplier ) {
uint64_t greylisted_virtual_cpu_limit = config.cpu_limit_parameters.max * greylist_limit;
if( greylisted_virtual_cpu_limit < state.virtual_cpu_limit ) {
virtual_cpu_capacity_in_window *= greylisted_virtual_cpu_limit;
greylisted = true;
} else {
virtual_cpu_capacity_in_window *= state.virtual_cpu_limit;
}
} else {
virtual_cpu_capacity_in_window *= state.virtual_cpu_limit;
}
Expand Down Expand Up @@ -433,13 +437,17 @@ std::pair<account_resource_limit, bool> resource_limits_manager::get_account_net
account_resource_limit arl;

uint128_t window_size = config.account_net_usage_average_window;
uint64_t greylisted_virtual_net_limit = config.net_limit_parameters.max * greylist_limit;

bool greylisted = false;
uint128_t virtual_network_capacity_in_window = window_size;
if( greylisted_virtual_net_limit < state.virtual_net_limit ) {
virtual_network_capacity_in_window *= greylisted_virtual_net_limit;
greylisted = true;
if( greylist_limit < config::maximum_elastic_resource_multiplier ) {
uint64_t greylisted_virtual_net_limit = config.net_limit_parameters.max * greylist_limit;
if( greylisted_virtual_net_limit < state.virtual_net_limit ) {
virtual_network_capacity_in_window *= greylisted_virtual_net_limit;
greylisted = true;
} else {
virtual_network_capacity_in_window *= state.virtual_net_limit;
}
} else {
virtual_network_capacity_in_window *= state.virtual_net_limit;
}
Expand Down
50 changes: 34 additions & 16 deletions libraries/chain/transaction_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ namespace eosio { namespace chain {

initial_objective_duration_limit = objective_duration_limit;

if( billed_cpu_time_us > 0 ) // could also call on explicit_billed_cpu_time but it would be redundant
validate_cpu_usage_to_bill( billed_cpu_time_us, false ); // Fail early if the amount to be billed is too high
if( explicit_billed_cpu_time )
validate_cpu_usage_to_bill( billed_cpu_time_us, std::numeric_limits<int64_t>::max(), false ); // Fail early if the amount to be billed is too high

// Record accounts to be billed for network and CPU usage
if( control.is_builtin_activated(builtin_protocol_feature_t::only_bill_first_authorizer) ) {
Expand Down Expand Up @@ -170,6 +170,11 @@ namespace eosio { namespace chain {
deadline_exception_code = billing_timer_exception_code;
}

if( !explicit_billed_cpu_time ) {
// if account no longer has enough cpu to exec trx, don't try
validate_account_cpu_usage( billed_cpu_time_us, account_cpu_limit, true );
}

eager_net_limit = (eager_net_limit/8)*8; // Round down to nearest multiple of word size (8 bytes) so check_net_usage can be efficient

if( initial_net_usage > 0 )
Expand Down Expand Up @@ -328,7 +333,7 @@ namespace eosio { namespace chain {

update_billed_cpu_time( now );

validate_cpu_usage_to_bill( billed_cpu_time_us );
validate_cpu_usage_to_bill( billed_cpu_time_us, account_cpu_limit, true );

rl.add_transaction_usage( bill_to_accounts, static_cast<uint64_t>(billed_cpu_time_us), net_usage,
block_timestamp_type(control.pending_block_time()).slot ); // Should never fail
Expand Down Expand Up @@ -418,7 +423,7 @@ namespace eosio { namespace chain {
transaction_timer.start(_deadline);
}

void transaction_context::validate_cpu_usage_to_bill( int64_t billed_us, bool check_minimum )const {
void transaction_context::validate_cpu_usage_to_bill( int64_t billed_us, int64_t account_cpu_limit, bool check_minimum )const {
if (!control.skip_trx_checks()) {
if( check_minimum ) {
const auto& cfg = control.get_global_properties().configuration;
Expand All @@ -428,25 +433,35 @@ namespace eosio { namespace chain {
);
}

if( billing_timer_exception_code == block_cpu_usage_exceeded::code_value ) {
validate_account_cpu_usage( billed_us, account_cpu_limit, false );
}
}

void transaction_context::validate_account_cpu_usage( int64_t billed_us, int64_t account_cpu_limit, bool estimate )const {
if( (billed_us > 0) && !control.skip_trx_checks() ) {
const bool cpu_limited_by_account = (account_cpu_limit <= objective_duration_limit.count());

if( !cpu_limited_by_account && (billing_timer_exception_code == block_cpu_usage_exceeded::code_value) ) {
EOS_ASSERT( billed_us <= objective_duration_limit.count(),
block_cpu_usage_exceeded,
"billed CPU time (${billed} us) is greater than the billable CPU time left in the block (${billable} us)",
("billed", billed_us)("billable", objective_duration_limit.count())
);
"${desc} CPU time (${billed} us) is greater than the billable CPU time left in the block (${billable} us)",
("desc", (estimate ? "estimated" : "billed"))("billed", billed_us)( "billable", objective_duration_limit.count() )
);
} else {
if (cpu_limit_due_to_greylist) {
EOS_ASSERT( billed_us <= objective_duration_limit.count(),
if( cpu_limit_due_to_greylist && cpu_limited_by_account ) {
EOS_ASSERT( billed_us <= account_cpu_limit,
greylist_cpu_usage_exceeded,
"billed CPU time (${billed} us) is greater than the maximum greylisted billable CPU time for the transaction (${billable} us)",
("billed", billed_us)("billable", objective_duration_limit.count())
"${desc} CPU time (${billed} us) is greater than the maximum greylisted billable CPU time for the transaction (${billable} us)",
("desc", (estimate ? "estimated" : "billed"))("billed", billed_us)( "billable", account_cpu_limit )
);
} else {
EOS_ASSERT( billed_us <= objective_duration_limit.count(),
// exceeds trx.max_cpu_usage_ms or cfg.max_transaction_cpu_usage if objective_duration_limit is greater
const int64_t cpu_limit = (cpu_limited_by_account ? account_cpu_limit : objective_duration_limit.count());
EOS_ASSERT( billed_us <= cpu_limit,
tx_cpu_usage_exceeded,
"billed CPU time (${billed} us) is greater than the maximum billable CPU time for the transaction (${billable} us)",
("billed", billed_us)("billable", objective_duration_limit.count())
);
"${desc} CPU time (${billed} us) is greater than the maximum billable CPU time for the transaction (${billable} us)",
("desc", (estimate ? "estimated" : "billed"))("billed", billed_us)( "billable", cpu_limit )
);
}
}
}
Expand Down Expand Up @@ -502,6 +517,9 @@ namespace eosio { namespace chain {
}
}

EOS_ASSERT( (!force_elastic_limits && control.is_producing_block()) || (!greylisted_cpu && !greylisted_net),
transaction_exception, "greylisted when not producing block" );

return std::make_tuple(account_net_limit, account_cpu_limit, greylisted_net, greylisted_cpu);
}

Expand Down
8 changes: 4 additions & 4 deletions libraries/testing/tester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ namespace eosio { namespace testing {

if( !skip_pending_trxs ) {
for( auto itr = unapplied_transactions.begin(); itr != unapplied_transactions.end(); ) {
auto trace = control->push_transaction( itr->trx_meta, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US );
auto trace = control->push_transaction( itr->trx_meta, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US, true );
if(trace->except) {
trace->except->dynamic_rethrow_exception();
}
Expand All @@ -333,7 +333,7 @@ namespace eosio { namespace testing {
vector<transaction_id_type> scheduled_trxs;
while ((scheduled_trxs = get_scheduled_transactions()).size() > 0 ) {
for( const auto& trx : scheduled_trxs ) {
auto trace = control->push_scheduled_transaction( trx, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US );
auto trace = control->push_scheduled_transaction( trx, fc::time_point::maximum(), DEFAULT_BILLED_CPU_TIME_US, true );
if( trace->except ) {
trace->except->dynamic_rethrow_exception();
}
Expand Down Expand Up @@ -535,7 +535,7 @@ namespace eosio { namespace testing {
fc::microseconds::maximum() :
fc::microseconds( deadline - fc::time_point::now() );
auto fut = transaction_metadata::start_recover_keys( ptrx, control->get_thread_pool(), control->get_chain_id(), time_limit );
auto r = control->push_transaction( fut.get(), deadline, billed_cpu_time_us );
auto r = control->push_transaction( fut.get(), deadline, billed_cpu_time_us, billed_cpu_time_us > 0 );
if( r->except_ptr ) std::rethrow_exception( r->except_ptr );
if( r->except ) throw *r->except;
return r;
Expand All @@ -560,7 +560,7 @@ namespace eosio { namespace testing {
fc::microseconds( deadline - fc::time_point::now() );
auto ptrx = std::make_shared<packed_transaction>( trx, c );
auto fut = transaction_metadata::start_recover_keys( ptrx, control->get_thread_pool(), control->get_chain_id(), time_limit );
auto r = control->push_transaction( fut.get(), deadline, billed_cpu_time_us );
auto r = control->push_transaction( fut.get(), deadline, billed_cpu_time_us, billed_cpu_time_us > 0 );
if (no_throw) return r;
if( r->except_ptr ) std::rethrow_exception( r->except_ptr );
if( r->except) throw *r->except;
Expand Down
Loading

0 comments on commit 474c26a

Please sign in to comment.