diff --git a/libraries/chain/apply_context.cpp b/libraries/chain/apply_context.cpp index f43b190b59b389626413cad24f2dd791674cd2e7..86c76557a7ac2626d4000876feb315d1adacb5f3 100644 --- a/libraries/chain/apply_context.cpp +++ b/libraries/chain/apply_context.cpp @@ -321,7 +321,7 @@ const contracts::table_id_object& apply_context::find_or_create_table( name code require_write_lock(scope); - update_db_usage(payer, config::billable_size_v, "New Table ${c},${s},${t}", _V("c",code)("s",scope)("t",table)); + update_db_usage(payer, config::billable_size_v); return mutable_db.create([&](contracts::table_id_object &t_id){ t_id.code = code; @@ -368,14 +368,14 @@ const bytes& apply_context::get_packed_transaction() { return trx_meta.packed_trx; } -void apply_context::update_db_usage( const account_name& payer, int64_t delta, const char* use_format, const fc::variant_object& args ) { +void apply_context::update_db_usage( const account_name& payer, int64_t delta ) { require_write_lock( payer ); if( (delta > 0) ) { if (!(privileged || payer == account_name(receiver))) { require_authorization( payer ); } - mutable_controller.get_mutable_resource_limits_manager().add_account_ram_usage(payer, delta, use_format, args); + mutable_controller.get_mutable_resource_limits_manager().add_pending_account_ram_usage(payer, delta); } } @@ -452,7 +452,7 @@ int apply_context::db_store_i64( uint64_t code, uint64_t scope, uint64_t table, }); int64_t billable_size = (int64_t)(buffer_size + config::billable_size_v); - update_db_usage( payer, billable_size, "New Row ${id} in (${c},${s},${t})", _V("id", obj.primary_key)("c",receiver)("s",scope)("t",table)); + update_db_usage( payer, billable_size); keyval_cache.cache_table( tab ); return keyval_cache.add( obj ); @@ -474,10 +474,10 @@ void apply_context::db_update_i64( int iterator, account_name payer, const char* // refund the existing payer update_db_usage( obj.payer, -(old_size) ); // charge the new payer - update_db_usage( payer, (new_size), "Transfer Row ${id} in (${c},${s},${t})", _V("id", obj.primary_key)("c",tab.code)("s",tab.scope)("t",tab.table)); + update_db_usage( payer, (new_size)); } else if(old_size != new_size) { // charge/refund the existing payer the difference - update_db_usage( obj.payer, new_size - old_size, "Update Row ${id} in (${c},${s},${t})", _V("id", obj.primary_key)("c",tab.code)("s",tab.scope)("t",tab.table)); + update_db_usage( obj.payer, new_size - old_size); } mutable_db.modify( obj, [&]( auto& o ) { diff --git a/libraries/chain/chain_controller.cpp b/libraries/chain/chain_controller.cpp index 2897e94a62c91ea142cf73b0418150c7617667b2..ce77795f3ffb3b84a84debf4ed8ec56f5e167f5c 100644 --- a/libraries/chain/chain_controller.cpp +++ b/libraries/chain/chain_controller.cpp @@ -920,6 +920,7 @@ void chain_controller::__apply_block(const signed_block& next_block) c_trace.shard_traces.emplace_back(move(s_trace)); } /// for each shard + _resource_limits.synchronize_account_ram_usage(); _apply_cycle_trace(c_trace); r_trace.cycle_traces.emplace_back(move(c_trace)); } /// for each cycle @@ -2028,17 +2029,16 @@ transaction_trace chain_controller::_apply_error( transaction_metadata& meta ) { void chain_controller::_destroy_generated_transaction( const generated_transaction_object& gto ) { auto& generated_transaction_idx = _db.get_mutable_index(); - _resource_limits.add_account_ram_usage(gto.payer, -( config::billable_size_v + gto.packed_trx.size())); + _resource_limits.add_pending_account_ram_usage(gto.payer, -( config::billable_size_v + gto.packed_trx.size())); generated_transaction_idx.remove(gto); } void chain_controller::_create_generated_transaction( const deferred_transaction& dto ) { size_t trx_size = fc::raw::pack_size(dto); - _resource_limits.add_account_ram_usage( + _resource_limits.add_pending_account_ram_usage( dto.payer, - (config::billable_size_v + (int64_t)trx_size), - "Generated Transaction ${id} from ${s}", _V("id", dto.sender_id)("s",dto.sender) + (config::billable_size_v + (int64_t)trx_size) ); _db.create([&](generated_transaction_object &obj) { @@ -2157,6 +2157,7 @@ transaction_trace chain_controller::wrap_transaction_processing( transaction_met // for now apply the transaction serially but schedule it according to those invariants auto result = trx_processing(data); + _resource_limits.synchronize_account_ram_usage(); auto& bcycle = _pending_block->regions.back().cycles_summary.back(); auto& bshard = bcycle.front(); diff --git a/libraries/chain/contracts/eosio_contract.cpp b/libraries/chain/contracts/eosio_contract.cpp index b4d85b6da88ba8583dcb36db3ad9dc3ae4417215..a47a85100da43eba128c4211a04639da744fffba 100644 --- a/libraries/chain/contracts/eosio_contract.cpp +++ b/libraries/chain/contracts/eosio_contract.cpp @@ -75,10 +75,9 @@ void apply_eosio_newaccount(apply_context& context) { a.creation_date = context.controller.head_block_time(); }); resources.initialize_account(create.name); - resources.add_account_ram_usage( + resources.add_pending_account_ram_usage( create.creator, - (int64_t)config::overhead_per_account_ram_bytes, - "New Account ${n}", _V("n", create.name) + (int64_t)config::overhead_per_account_ram_bytes ); auto create_permission = [owner=create.name, &db, &context, &resources](const permission_name& name, permission_object::id_type parent, authority &&auth) { @@ -89,10 +88,9 @@ void apply_eosio_newaccount(apply_context& context) { p.auth = std::move(auth); }); - resources.add_account_ram_usage( + resources.add_pending_account_ram_usage( owner, - (int64_t)(config::billable_size_v + result.auth.get_billable_size()), - "New Permission ${a}@${p}", _V("a", owner)("p",name) + (int64_t)(config::billable_size_v + result.auth.get_billable_size()) ); return result; @@ -141,10 +139,9 @@ void apply_eosio_setcode(apply_context& context) { }); if (new_size != old_size) { - resources.add_account_ram_usage( + resources.add_pending_account_ram_usage( act.account, - new_size - old_size, - "Update Contract Code to ${v} [new size=${new}, old size=${old}]", _V("v",account.code_version)("new", new_size)("old",old_size) + new_size - old_size ); } } @@ -175,10 +172,9 @@ void apply_eosio_setabi(apply_context& context) { }); if (new_size != old_size) { - resources.add_account_ram_usage( + resources.add_pending_account_ram_usage( act.account, - new_size - old_size, - "Update Contract ABI [new size=${new}, old size=${old}]", _V("new", new_size)("old",old_size) + new_size - old_size ); } } @@ -261,10 +257,9 @@ void apply_eosio_updateauth(apply_context& context) { int64_t new_size = (int64_t)(config::billable_size_v + permission->auth.get_billable_size()); - resources.add_account_ram_usage( + resources.add_pending_account_ram_usage( permission->owner, - new_size - old_size, - "Update Permission ${a}@${p} [new size=${new}, old size=${old}] ", _V("a", permission->owner)("p",permission->name) + new_size - old_size ); } else { // TODO/QUESTION: If we are creating a new permission, should we check if the message declared @@ -278,10 +273,9 @@ void apply_eosio_updateauth(apply_context& context) { po.delay = fc::seconds(update.delay); }); - resources.add_account_ram_usage( + resources.add_pending_account_ram_usage( p.owner, - (int64_t)(config::billable_size_v + p.auth.get_billable_size()), - "New Permission ${a}@${p}", _V("a", p.owner)("p",p.name) + (int64_t)(config::billable_size_v + p.auth.get_billable_size()) ); } @@ -315,7 +309,7 @@ void apply_eosio_deleteauth(apply_context& context) { "Cannot delete a linked authority. Unlink the authority first"); } - resources.add_account_ram_usage( + resources.add_pending_account_ram_usage( permission.owner, -(int64_t)(config::billable_size_v + permission.auth.get_billable_size()) ); @@ -360,10 +354,9 @@ void apply_eosio_linkauth(apply_context& context) { link.required_permission = requirement.requirement; }); - resources.add_account_ram_usage( + resources.add_pending_account_ram_usage( l.account, - (int64_t)(config::billable_size_v), - "New Permission Link ${code}::${act} -> ${a}@${p}", _V("code", l.code)("act",l.message_type)("a", l.account)("p",l.required_permission) + (int64_t)(config::billable_size_v) ); } } FC_CAPTURE_AND_RETHROW((requirement)) @@ -379,7 +372,7 @@ void apply_eosio_unlinkauth(apply_context& context) { auto link_key = boost::make_tuple(unlink.account, unlink.code, unlink.type); auto link = db.find(link_key); EOS_ASSERT(link != nullptr, action_validate_exception, "Attempting to unlink authority, but no link found"); - resources.add_account_ram_usage( + resources.add_pending_account_ram_usage( link->account, -(int64_t)(config::billable_size_v) ); diff --git a/libraries/chain/include/eosio/chain/apply_context.hpp b/libraries/chain/include/eosio/chain/apply_context.hpp index 00775aa0134a50674a8ecfc9cceba8296c33eb22..81ef06308942ef0071e5e98706b73e057360b9bb 100644 --- a/libraries/chain/include/eosio/chain/apply_context.hpp +++ b/libraries/chain/include/eosio/chain/apply_context.hpp @@ -556,7 +556,7 @@ class apply_context { int get_action( uint32_t type, uint32_t index, char* buffer, size_t buffer_size )const; int get_context_free_data( uint32_t index, char* buffer, size_t buffer_size )const; - void update_db_usage( const account_name& payer, int64_t delta, const char* use_format = "Unspecified", const fc::variant_object& args = fc::variant_object() ); + void update_db_usage( const account_name& payer, int64_t delta ); int db_store_i64( uint64_t scope, uint64_t table, const account_name& payer, uint64_t id, const char* buffer, size_t buffer_size ); void db_update_i64( int iterator, account_name payer, const char* buffer, size_t buffer_size ); void db_remove_i64( int iterator ); diff --git a/libraries/chain/include/eosio/chain/resource_limits.hpp b/libraries/chain/include/eosio/chain/resource_limits.hpp index 88bcea14fcbdf35e9e7b298ebd259e0b74b47ac9..c7df65f58d8c254ad77df7574ac3bdb33c12ee3f 100644 --- a/libraries/chain/include/eosio/chain/resource_limits.hpp +++ b/libraries/chain/include/eosio/chain/resource_limits.hpp @@ -38,7 +38,9 @@ namespace eosio { namespace chain { namespace resource_limits { void add_transaction_usage( const vector& accounts, uint64_t cpu_usage, uint64_t net_usage, uint32_t ordinal ); - void add_account_ram_usage( const account_name account, int64_t ram_delta, const char* use_format = "Unspecified", const fc::variant_object& args = fc::variant_object() ); + + void add_pending_account_ram_usage( const account_name account, int64_t ram_delta ); + void synchronize_account_ram_usage( ); void set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight); void get_account_limits( const account_name& account, int64_t& ram_bytes, int64_t& net_weight, int64_t& cpu_weight) const; diff --git a/libraries/chain/include/eosio/chain/resource_limits_private.hpp b/libraries/chain/include/eosio/chain/resource_limits_private.hpp index 5a56ed2de72e476cae3718312d946ea09288d5ad..4b6de5a17c3f92bbc8d7a625a2e35e2ffad3b15f 100644 --- a/libraries/chain/include/eosio/chain/resource_limits_private.hpp +++ b/libraries/chain/include/eosio/chain/resource_limits_private.hpp @@ -92,6 +92,7 @@ namespace eosio { namespace chain { namespace resource_limits { }; struct by_owner; + struct by_dirty; using resource_limits_index = chainbase::shared_multi_index_container< resource_limits_object, @@ -116,13 +117,25 @@ namespace eosio { namespace chain { namespace resource_limits { usage_accumulator cpu_usage; uint64_t ram_usage = 0; + uint64_t pending_ram_usage = 0; + + bool is_dirty() const { + // checks for ram_usage overflowing a signed int are maintained in the update step + return ram_usage != pending_ram_usage; + } }; using resource_usage_index = chainbase::shared_multi_index_container< resource_usage_object, indexed_by< ordered_unique, member>, - ordered_unique, member > + ordered_unique, member >, + ordered_unique, + composite_key + > > >; diff --git a/libraries/chain/resource_limits.cpp b/libraries/chain/resource_limits.cpp index 177a8e8bc97db84b62411c1d8e78333352727b3b..35876239460e1d7d4b14f06330b0cf463ea7e7db 100644 --- a/libraries/chain/resource_limits.cpp +++ b/libraries/chain/resource_limits.cpp @@ -120,22 +120,45 @@ void resource_limits_manager::add_transaction_usage(const vector& EOS_ASSERT( state.pending_net_usage <= config.net_limit_parameters.max, block_resource_exhausted, "Block has insufficient net resources" ); } -void resource_limits_manager::add_account_ram_usage( const account_name account, int64_t ram_delta, const char* use_format, const fc::variant_object& args ) { +void resource_limits_manager::add_pending_account_ram_usage( const account_name account, int64_t ram_delta ) { + if (ram_delta == 0) { + return; + } + const auto& usage = _db.get( account ); - const auto& limits = _db.get( boost::make_tuple(false, account)); - if (limits.ram_bytes >= 0 && usage.ram_usage + ram_delta > limits.ram_bytes) { - tx_resource_exhausted e(FC_LOG_MESSAGE(error, "account ${a} has insufficient ram bytes", ("a", account))); - e.append_log(fc::log_message( FC_LOG_CONTEXT(error), use_format, args )); - e.append_log(FC_LOG_MESSAGE(error, "needs ${d} has ${m}", ("d",ram_delta)("m",limits.ram_bytes))); - throw e; - } + EOS_ASSERT(ram_delta < 0 || UINT64_MAX - usage.pending_ram_usage >= (uint64_t)ram_delta, transaction_exception, "Ram usage delta would overflow UINT64_MAX"); + EOS_ASSERT(ram_delta > 0 || usage.pending_ram_usage >= (uint64_t)(-ram_delta), transaction_exception, "Ram usage delta would underflow UINT64_MAX"); _db.modify(usage, [&](resource_usage_object& o){ - o.ram_usage += ram_delta; + o.pending_ram_usage += ram_delta; }); } +void resource_limits_manager::synchronize_account_ram_usage( ) { + auto& multi_index = _db.get_mutable_index(); + auto& by_dirty_index = multi_index.indices().get(); + + while(!by_dirty_index.empty()) { + const auto& itr = by_dirty_index.lower_bound(boost::make_tuple(true)); + if (itr == by_dirty_index.end() || itr->is_dirty() != true) { + break; + } + + const auto& limits = _db.get( boost::make_tuple(false, itr->owner)); + + if (limits.ram_bytes >= 0 && itr->pending_ram_usage > limits.ram_bytes) { + tx_resource_exhausted e(FC_LOG_MESSAGE(error, "account ${a} has insufficient ram bytes", ("a", itr->owner))); + e.append_log(FC_LOG_MESSAGE(error, "needs ${d} has ${m}", ("d",itr->pending_ram_usage)("m",limits.ram_bytes))); + throw e; + } + + _db.modify(*itr, [&](resource_usage_object& o){ + o.ram_usage = o.pending_ram_usage; + }); + } +} + void resource_limits_manager::set_account_limits( const account_name& account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight) { const auto& usage = _db.get( account ); /* diff --git a/tests/library_tests/chain/resource_limits_test.cpp b/tests/library_tests/chain/resource_limits_test.cpp index c66549c99f97afad7245f8f3a43227d6cddf430b..ac7bb8bdc11e657ec7555c37287856d4222c1fd0 100644 --- a/tests/library_tests/chain/resource_limits_test.cpp +++ b/tests/library_tests/chain/resource_limits_test.cpp @@ -245,10 +245,12 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) process_account_limit_updates(); for (int idx = 0; idx < expected_iterations - 1; idx++) { - add_account_ram_usage(account, increment); + add_pending_account_ram_usage(account, increment); + synchronize_account_ram_usage( ); } - BOOST_REQUIRE_THROW(add_account_ram_usage(account, increment), tx_resource_exhausted); + add_pending_account_ram_usage(account, increment); + BOOST_REQUIRE_THROW(synchronize_account_ram_usage( ), tx_resource_exhausted); } FC_LOG_AND_RETHROW(); BOOST_FIXTURE_TEST_CASE(enforce_account_ram_commitment, resource_limits_fixture) try { @@ -262,7 +264,8 @@ BOOST_AUTO_TEST_SUITE(resource_limits_test) initialize_account(account); set_account_limits(account, limit, -1, -1 ); process_account_limit_updates(); - add_account_ram_usage(account, commit); + add_pending_account_ram_usage(account, commit); + synchronize_account_ram_usage( ); for (int idx = 0; idx < expected_iterations - 1; idx++) { set_account_limits(account, limit - increment * idx, -1, -1);