提交 685b1a54 编写于 作者: A arhag

cleanup of transaction_context, throwing soft resource limit exceptions if...

cleanup of transaction_context, throwing soft resource limit exceptions if limit is due to block, and preferential network billing of prunable data (#2000)
上级 84648949
......@@ -372,9 +372,11 @@ struct controller_impl {
etrx.expiration = self.pending_block_time() + fc::microseconds(999'999); // Round up to avoid appearing expired
etrx.set_reference_block( self.head_block_id() );
transaction_trace_ptr trace;
transaction_context trx_context( self, etrx, etrx.id() );
transaction_trace_ptr trace = trx_context.trace;
try {
transaction_context trx_context( trace, self, etrx, etrx.id(), deadline, true, 0, cpu_usage );
trx_context.init_for_implicit_trx( deadline, 0, cpu_usage );
trx_context.exec(); // Automatically rounds up network and CPU usage in trace and bills payers if successful
trace->elapsed = fc::time_point::now() - start;
......@@ -430,15 +432,17 @@ struct controller_impl {
}
auto start = fc::time_point::now();
transaction_trace_ptr trace;
signed_transaction dtrx;
fc::raw::unpack(ds,static_cast<transaction&>(dtrx) );
transaction_context trx_context( self, dtrx, gto.trx_id );
transaction_trace_ptr trace = trx_context.trace;
flat_set<account_name> bill_to_accounts;
uint64_t max_cpu;
bool abort_on_error = false;
try {
signed_transaction dtrx;
fc::raw::unpack(ds,static_cast<transaction&>(dtrx) );
trx_context.init_for_deferred_trx( deadline, gto.published );
transaction_context trx_context( trace, self, dtrx, gto.trx_id, deadline, gto.published );
bill_to_accounts = trx_context.bill_to_accounts;
max_cpu = trx_context.max_cpu;
trx_context.exec(); // Automatically rounds up network and CPU usage in trace and bills payers if successful
......@@ -469,10 +473,9 @@ struct controller_impl {
trace->soft_except_ptr = std::current_exception();
trace->elapsed = fc::time_point::now() - start;
}
// Only soft or hard failure logic below:
trx_context.undo_session.undo();
// Make sure failure was not due to problems with deserializing the deferred transaction.
FC_ASSERT( bool(trace), "failed to deserialize transaction" );
// Only soft or hard failure logic below:
if( gto.sender != account_name() ) {
// Attempt error handling for the generated transaction.
......@@ -505,6 +508,7 @@ struct controller_impl {
emit( self.applied_transaction, trace );
undo_session.squash();
} FC_CAPTURE_AND_RETHROW() } /// push_scheduled_transaction
......@@ -559,12 +563,18 @@ struct controller_impl {
}
auto start = fc::time_point::now();
transaction_trace_ptr trace;
transaction_context trx_context( self, trx->trx, trx->id);
transaction_trace_ptr trace = trx_context.trace;
try {
unapplied_transactions.erase( trx->signed_id );
transaction_context trx_context( trace, self, trx->trx, trx->id, deadline,
implicit, ( implicit ? 0 : trx->packed_trx.get_billable_size() ) );
if( implicit ) {
trx_context.init_for_implicit_trx( deadline );
} else {
trx_context.init_for_input_trx( deadline,
trx->packed_trx.get_unprunable_size(),
trx->packed_trx.get_prunable_size() );
}
fc::microseconds required_delay(0);
if( !implicit ) {
......@@ -575,8 +585,6 @@ struct controller_impl {
"authorization imposes a delay (${required_delay} sec) greater than the delay specified in transaction header (${specified_delay} sec)",
("required_delay", required_delay.to_seconds())("specified_delay", trx_context.delay.to_seconds()) );
emit( self.accepted_transaction, trx);
trx_context.exec(); // Automatically rounds up network and CPU usage in trace and bills payers if successful
trace->elapsed = fc::time_point::now() - start;
......
......@@ -86,6 +86,8 @@ const static uint32_t resource_processing_cpu_overhead_per_billed_account = 25
const static uint32_t determine_payers_cpu_overhead_per_authorization = 64; // TODO: is this reasonable?
const static uint32_t ram_usage_validation_overhead_per_account = 64; // TODO: is this reasonable?
const static uint32_t fixed_net_overhead_of_packed_trx = 16; // TODO: is this reasonable?
const static uint32_t overhead_per_row_per_index_ram_bytes = 32; ///< overhead accounts for basic tracking structures in a row per index
const static uint32_t overhead_per_account_ram_bytes = 2*1024; ///< overhead accounts for basic account storage and pre-pays features like account recovery
const static uint32_t setcode_ram_bytes_multiplier = 10; ///< multiplier on contract size to account for multiple copies and cached compilation
......
......@@ -128,7 +128,8 @@ namespace eosio { namespace chain {
3060004, "Contract Query Exception" )
FC_DECLARE_DERIVED_EXCEPTION( wasm_exception, chain_exception, 3070000, "WASM Exception" )
FC_DECLARE_DERIVED_EXCEPTION( wasm_exception, chain_exception,
3070000, "WASM Exception" )
FC_DECLARE_DERIVED_EXCEPTION( page_memory_error, wasm_exception,
3070001, "error in WASM page memory" )
......@@ -142,14 +143,19 @@ namespace eosio { namespace chain {
FC_DECLARE_DERIVED_EXCEPTION( resource_exhausted_exception, chain_exception,
3080000, "resource exhausted exception" )
FC_DECLARE_DERIVED_EXCEPTION( ram_usage_exceeded, resource_exhausted_exception,
3080001, "account using more than allotted RAM usage" )
FC_DECLARE_DERIVED_EXCEPTION( tx_cpu_resource_exhausted, resource_exhausted_exception,
3080002, "transaction exceeded CPU usage limit" )
FC_DECLARE_DERIVED_EXCEPTION( tx_net_resource_exhausted, resource_exhausted_exception,
3080003, "transaction exceeded network usage limit" )
FC_DECLARE_DERIVED_EXCEPTION( checktime_exceeded, transaction_exception,
3080004, "transaction exceeded allotted processing time" )
FC_DECLARE_DERIVED_EXCEPTION( tx_net_usage_exceeded, resource_exhausted_exception,
3080002, "transaction exceeded the current network usage limit imposed on the transaction" )
FC_DECLARE_DERIVED_EXCEPTION( tx_soft_net_usage_exceeded, resource_exhausted_exception,
3080003, "transaction network usage is too much for the remaining allowable usage of the current block" )
FC_DECLARE_DERIVED_EXCEPTION( tx_cpu_usage_exceeded, resource_exhausted_exception,
3080004, "transaction exceeded the current CPU usage limit imposed on the transaction" )
FC_DECLARE_DERIVED_EXCEPTION( tx_soft_cpu_usage_exceeded, resource_exhausted_exception,
3080005, "transaction CPU usage is too much for the remaining allowable usage of the current block" )
FC_DECLARE_DERIVED_EXCEPTION( tx_deadline_exceeded, resource_exhausted_exception,
3080006, "transaction took too long" )
FC_DECLARE_DERIVED_EXCEPTION( misc_exception, chain_exception,
......@@ -193,9 +199,4 @@ namespace eosio { namespace chain {
3110006, "No available wallet" )
#define EOS_RECODE_EXC( cause_type, effect_type ) \
catch( const cause_type& e ) \
{ throw( effect_type( e.what(), e.get_log() ) ); }
} } // eosio::chain
......@@ -77,8 +77,6 @@ namespace eosio { namespace chain {
>
>;
typedef chainbase::generic_index<generated_transaction_multi_index> generated_transaction_index;
namespace config {
template<>
struct billable_size<generated_transaction_object> {
......
......@@ -118,7 +118,8 @@ namespace eosio { namespace chain {
set_transaction(t, std::move(t.context_free_data), _compression);
}
uint32_t get_billable_size()const;
uint32_t get_unprunable_size()const;
uint32_t get_prunable_size()const;
digest_type packed_digest()const;
......
......@@ -7,38 +7,24 @@ namespace eosio { namespace chain {
class transaction_context {
private:
// Common private constructor
void init( uint64_t initial_net_usage, uint64_t initial_cpu_usage );
public:
transaction_context( controller& c,
transaction_trace_ptr& trace_ptr,
const signed_transaction& t,
const transaction_id_type& trx_id,
fc::time_point deadline,
fc::time_point published,
uint64_t initial_net_usage,
uint64_t initial_cpu_usage );
const transaction_id_type& trx_id );
static uint64_t calculate_initial_net_usage( const controller& c,
const signed_transaction& t,
uint64_t packed_trx_billable_size );
void init_for_implicit_trx( fc::time_point deadline,
uint64_t initial_net_usage = 0,
uint64_t initial_cpu_usage = 0 );
public:
// For deferred transactions
transaction_context( transaction_trace_ptr& trace_ptr,
controller& c,
const signed_transaction& t,
const transaction_id_type& trx_id,
fc::time_point deadline,
fc::time_point published );
void init_for_input_trx( fc::time_point deadline,
uint64_t packed_trx_unprunable_size,
uint64_t packed_trx_prunable_size );
// For implicit or input transactions
transaction_context( transaction_trace_ptr& trace_ptr,
controller& c,
const signed_transaction& t,
const transaction_id_type& trx_id,
fc::time_point deadline,
bool is_implicit,
uint64_t packed_trx_billable_size,
uint64_t initial_cpu_usage = 0 );
void init_for_deferred_trx( fc::time_point deadline,
fc::time_point published );
void exec();
void squash();
......@@ -70,8 +56,8 @@ namespace eosio { namespace chain {
transaction_id_type id;
chainbase::database::session undo_session;
transaction_trace_ptr trace;
fc::time_point deadline;
fc::time_point published;
fc::time_point deadline = fc::time_point::maximum();
vector<action_receipt> executed;
flat_set<account_name> bill_to_accounts;
......@@ -88,6 +74,7 @@ namespace eosio { namespace chain {
uint64_t& cpu_usage; /// reference to trace->cpu_usage
bool net_limit_due_to_block = false;
bool cpu_limit_due_to_block = false;
bool is_initialized = false;
};
......
......@@ -97,7 +97,7 @@ void resource_limits_manager::add_transaction_usage(const flat_set<account_name>
uint128_t capacity_cpu_ex = state.virtual_cpu_limit * config::rate_limiting_precision;
EOS_ASSERT( state.total_cpu_weight > 0 && (consumed_cpu_ex * state.total_cpu_weight) <= (limits.cpu_weight * capacity_cpu_ex),
tx_cpu_resource_exhausted,
tx_cpu_usage_exceeded,
"authorizing account '${n}' has insufficient cpu resources for this transaction",
("n", name(a))
("consumed", (double)consumed_cpu_ex/(double)config::rate_limiting_precision)
......@@ -112,7 +112,7 @@ void resource_limits_manager::add_transaction_usage(const flat_set<account_name>
uint128_t capacity_net_ex = state.virtual_net_limit * config::rate_limiting_precision;
EOS_ASSERT( state.total_net_weight > 0 && (consumed_net_ex * state.total_net_weight) <= (limits.net_weight * capacity_net_ex),
tx_net_resource_exhausted,
tx_net_usage_exceeded,
"authorizing account '${n}' has insufficient net resources for this transaction",
("n", name(a))
("consumed", (double)consumed_net_ex/(double)config::rate_limiting_precision)
......
......@@ -130,8 +130,16 @@ flat_set<public_key_type> signed_transaction::get_signature_keys( const chain_id
return transaction::get_signature_keys(signatures, chain_id, context_free_data, allow_duplicate_keys);
}
uint32_t packed_transaction::get_billable_size()const {
auto size = fc::raw::pack_size(*this);
uint32_t packed_transaction::get_unprunable_size()const {
uint64_t size = config::fixed_net_overhead_of_packed_trx;
size += packed_trx.size();
FC_ASSERT( size <= std::numeric_limits<uint32_t>::max(), "packed_transaction is too big" );
return static_cast<uint32_t>(size);
}
uint32_t packed_transaction::get_prunable_size()const {
uint64_t size = fc::raw::pack_size(signatures);
size += packed_context_free_data.size();
FC_ASSERT( size <= std::numeric_limits<uint32_t>::max(), "packed_transaction is too big" );
return static_cast<uint32_t>(size);
}
......
......@@ -9,26 +9,25 @@
namespace eosio { namespace chain {
transaction_context::transaction_context( controller& c,
transaction_trace_ptr& trace_ptr,
const signed_transaction& t,
const transaction_id_type& trx_id,
fc::time_point d,
fc::time_point p,
uint64_t initial_net_usage,
uint64_t initial_cpu_usage )
const transaction_id_type& trx_id )
:control(c)
,trx(t)
,id(trx_id)
,undo_session(c.db().start_undo_session(true))
,trace(std::make_shared<transaction_trace>())
,deadline(d)
,published(p)
,net_usage(trace->net_usage)
,cpu_usage(trace->cpu_usage)
{
trace->id = id;
trace_ptr = trace;
executed.reserve( trx.total_actions() );
is_input = false;
apply_context_free = false;
}
void transaction_context::init(uint64_t initial_net_usage, uint64_t initial_cpu_usage )
{
FC_ASSERT( !is_initialized, "cannot initialize twice" );
// Record accounts to be billed for network and CPU usage
uint64_t determine_payers_cpu_cost = 0;
......@@ -95,51 +94,60 @@ namespace eosio { namespace chain {
if( initial_net_usage > 0 )
check_net_usage(); // Fail early if current net usage is already greater than the calculated limit
check_cpu_usage(); // Fail early if current CPU usage is already greater than the calculated limit
is_initialized = true;
}
void transaction_context::init_for_implicit_trx( fc::time_point d, uint64_t initial_net_usage, uint64_t initial_cpu_usage )
{
published = control.pending_block_time();
deadline = d;
init( initial_net_usage, initial_cpu_usage );
}
uint64_t transaction_context::calculate_initial_net_usage( const controller& c,
const signed_transaction& t,
uint64_t packed_trx_billable_size ) {
const auto& cfg = c.get_global_properties().configuration;
uint64_t initial_net_usage = static_cast<uint64_t>(cfg.base_per_transaction_net_usage) + packed_trx_billable_size;
if( t.delay_sec.value > 0 ) {
void transaction_context::init_for_input_trx( fc::time_point d,
uint64_t packed_trx_unprunable_size,
uint64_t packed_trx_prunable_size )
{
const auto& cfg = control.get_global_properties().configuration;
uint64_t discounted_size_for_pruned_data = packed_trx_prunable_size;
if( cfg.context_free_discount_net_usage_den > 0
&& cfg.context_free_discount_net_usage_num < cfg.context_free_discount_net_usage_den )
{
discounted_size_for_pruned_data *= cfg.context_free_discount_net_usage_num;
discounted_size_for_pruned_data /= cfg.context_free_discount_net_usage_den;
}
uint64_t initial_net_usage = static_cast<uint64_t>(cfg.base_per_transaction_net_usage)
+ packed_trx_unprunable_size + discounted_size_for_pruned_data;
if( trx.delay_sec.value > 0 ) {
// If delayed, also charge ahead of time for the additional net usage needed to retire the delayed transaction
// whether that be by successfully executing, soft failure, hard failure, or expiration.
initial_net_usage += static_cast<uint64_t>(cfg.base_per_transaction_net_usage)
+ static_cast<uint64_t>(config::transaction_id_net_usage);
}
return initial_net_usage;
published = control.pending_block_time();
deadline = d;
is_input = true;
init( initial_net_usage, 0 );
}
transaction_context::transaction_context( transaction_trace_ptr& trace_ptr,
controller& c,
const signed_transaction& t,
const transaction_id_type& trx_id,
fc::time_point d,
fc::time_point p )
:transaction_context( c, trace_ptr, t, trx_id, d, p, 0, 0 )
void transaction_context::init_for_deferred_trx( fc::time_point d,
fc::time_point p )
{
published = p;
deadline = d;
trace->scheduled = true;
is_input = false;
apply_context_free = false;
}
transaction_context::transaction_context( transaction_trace_ptr& trace_ptr,
controller& c,
const signed_transaction& t,
const transaction_id_type& trx_id,
fc::time_point d,
bool is_implicit,
uint64_t packed_trx_billable_size,
uint64_t initial_cpu_usage )
:transaction_context( c, trace_ptr, t, trx_id, d, c.pending_block_time(),
calculate_initial_net_usage(c, t, packed_trx_billable_size), initial_cpu_usage )
{
trace->scheduled = false;
is_input = !is_implicit;
init( 0, 0 );
}
void transaction_context::exec() {
FC_ASSERT( is_initialized, "must first initialize" );
//trx.validate(); // Not needed anymore since overflow is prevented by using uint64_t instead of uint32_t
control.validate_tapos( trx );
control.validate_referenced_accounts( trx );
......@@ -182,22 +190,35 @@ namespace eosio { namespace chain {
}
void transaction_context::check_net_usage()const {
EOS_ASSERT( BOOST_LIKELY(net_usage <= max_net), tx_net_resource_exhausted,
"net usage of transaction is too high: ${actual_net_usage} > ${net_usage_limit}",
("actual_net_usage", net_usage)("net_usage_limit", max_net) );
if( BOOST_UNLIKELY(net_usage > max_net) ) {
if( BOOST_UNLIKELY( net_limit_due_to_block ) ) {
EOS_THROW( tx_soft_net_usage_exceeded,
"not enough space left in block: ${actual_net_usage} > ${net_usage_limit}",
("actual_net_usage", net_usage)("net_usage_limit", max_net) );
} else {
EOS_THROW( tx_net_usage_exceeded,
"net usage of transaction is too high: ${actual_net_usage} > ${net_usage_limit}",
("actual_net_usage", net_usage)("net_usage_limit", max_net) );
}
}
}
void transaction_context::check_cpu_usage()const {
EOS_ASSERT( BOOST_LIKELY(cpu_usage <= max_cpu), tx_cpu_resource_exhausted,
"cpu usage of transaction is too high: ${actual_net_usage} > ${cpu_usage_limit}",
("actual_net_usage", cpu_usage)("cpu_usage_limit", max_cpu) );
if( BOOST_UNLIKELY(cpu_usage > max_cpu) ) {
if( BOOST_UNLIKELY( cpu_limit_due_to_block ) ) {
EOS_THROW( tx_soft_cpu_usage_exceeded,
"not enough CPU usage allotment left in block: ${actual_cpu_usage} > ${cpu_usage_limit}",
("actual_cpu_usage", cpu_usage)("cpu_usage_limit", max_cpu) );
} else {
EOS_THROW( tx_cpu_usage_exceeded,
"cpu usage of transaction is too high: ${actual_cpu_usage} > ${cpu_usage_limit}",
("actual_cpu_usage", cpu_usage)("cpu_usage_limit", max_cpu) );
}
}
}
void transaction_context::check_time()const {
if( BOOST_UNLIKELY(fc::time_point::now() > deadline) ) {
wlog( "deadline passed" );
throw checktime_exceeded();
}
EOS_ASSERT( BOOST_LIKELY(fc::time_point::now() <= deadline), tx_deadline_exceeded, "deadline exceeded" );
}
void transaction_context::add_ram_usage( account_name account, int64_t ram_delta ) {
......
......@@ -196,16 +196,15 @@ bool is_access_violation(fc::unhandled_exception const & e) {
}
return false;
}
bool is_access_violation(const Runtime::Exception& e) {
return true;
}
bool is_access_violation(const Runtime::Exception& e) { return true; }
bool is_assert_exception(fc::assert_exception const & e) { return true; }
bool is_page_memory_error(page_memory_error const &e) { return true; }
bool is_tx_missing_sigs(tx_missing_sigs const & e) { return true;}
bool is_wasm_execution_error(eosio::chain::wasm_execution_error const& e) {return true;}
bool is_tx_net_resource_exhausted(const tx_net_resource_exhausted& e) { return true; }
bool is_tx_cpu_resource_exhausted(const tx_cpu_resource_exhausted& e) { return true; }
bool is_checktime_exceeded(const checktime_exceeded& e) { return true; }
bool is_tx_net_usage_exceeded(const tx_net_usage_exceeded& e) { return true; }
bool is_tx_cpu_usage_exceeded(const tx_cpu_usage_exceeded& e) { return true; }
bool is_tx_deadline_exceeded(const tx_deadline_exceeded& e) { return true; }
/*
* register test suite `api_tests`
......@@ -476,7 +475,7 @@ BOOST_FIXTURE_TEST_CASE(cf_action_tests, TESTER) { try {
sigs = trx.sign(get_private_key(N(testapi), "active"), chain_id_type());
BOOST_CHECK_EXCEPTION(push_transaction(trx), assert_exception,
[](const fc::exception& e) {
return expect_assert_message(e, "context_free: only context free api's can be used in this context");
return expect_assert_message(e, "this API may only be called from context_free apply" );
}
);
}
......@@ -567,7 +566,7 @@ BOOST_AUTO_TEST_CASE(checktime_fail_tests) { try {
test.produce_block();
};
BOOST_CHECK_EXCEPTION(call_test( t, test_api_action<TEST_METHOD("test_checktime", "checktime_failure")>{}), tx_cpu_resource_exhausted, is_tx_cpu_resource_exhausted /*checktime_exceeded, is_checktime_exceeded*/);
BOOST_CHECK_EXCEPTION(call_test( t, test_api_action<TEST_METHOD("test_checktime", "checktime_failure")>{}), tx_cpu_usage_exceeded, is_tx_cpu_usage_exceeded /*tx_deadline_exceeded, is_tx_deadline_exceeded*/);
BOOST_REQUIRE_EQUAL( t.validate(), true );
} FC_LOG_AND_RETHROW() }
......
......@@ -163,7 +163,7 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test)
}
// use too much, and expect failure;
BOOST_REQUIRE_THROW(add_transaction_usage({account}, expected_limits.at(idx) + 1, 0, 0), tx_cpu_resource_exhausted);
BOOST_REQUIRE_THROW(add_transaction_usage({account}, expected_limits.at(idx) + 1, 0, 0), tx_cpu_usage_exceeded);
}
} FC_LOG_AND_RETHROW();
......@@ -195,7 +195,7 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test)
}
// use too much, and expect failure;
BOOST_REQUIRE_THROW(add_transaction_usage({account}, 0, expected_limits.at(idx) + 1, 0), tx_net_resource_exhausted);
BOOST_REQUIRE_THROW(add_transaction_usage({account}, 0, expected_limits.at(idx) + 1, 0), tx_net_usage_exceeded);
}
} FC_LOG_AND_RETHROW();
......
......@@ -560,7 +560,7 @@ BOOST_FIXTURE_TEST_CASE(cpu_usage_tests, tester ) try {
produce_blocks(1);
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
pass = true;
} catch (eosio::chain::tx_cpu_resource_exhausted &) {
} catch (eosio::chain::tx_cpu_usage_exceeded &) {
produce_blocks(1);
}
......@@ -621,7 +621,7 @@ BOOST_FIXTURE_TEST_CASE(weighted_cpu_limit_tests, tester ) try {
BOOST_REQUIRE_EQUAL(true, chain_has_transaction(trx.id()));
pass = true;
count++;
} catch (eosio::chain::tx_cpu_resource_exhausted &) {
} catch (eosio::chain::tx_cpu_usage_exceeded &) {
BOOST_REQUIRE_EQUAL(count, 3);
break;
}
......@@ -1487,7 +1487,7 @@ BOOST_FIXTURE_TEST_CASE(net_usage_tests, tester ) try {
push_transaction(trx);
produce_blocks(1);
return true;
} catch (tx_net_resource_exhausted &) {
} catch (tx_net_usage_exceeded &) {
return false;
} catch (transaction_exception &) {
return false;
......@@ -1538,7 +1538,7 @@ BOOST_FIXTURE_TEST_CASE(weighted_net_usage_tests, tester ) try {
push_transaction(trx);
produce_blocks(1);
return true;
} catch (tx_net_resource_exhausted &) {
} catch (tx_net_usage_exceeded &) {
return false;
}
};
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册