From 9dfa4ede1f7b7a9695d19a2606be111acfacb3ce Mon Sep 17 00:00:00 2001 From: Brian Johnson Date: Thu, 16 Nov 2017 11:08:52 -0600 Subject: [PATCH] STAT-81 (GH-607) Added logic for tracking db usage and limiting. --- .../include/eos/chain/wasm_interface.hpp | 24 ++-- libraries/chain/wasm_interface.cpp | 125 +++++++++++++++--- 2 files changed, 124 insertions(+), 25 deletions(-) diff --git a/libraries/chain/include/eos/chain/wasm_interface.hpp b/libraries/chain/include/eos/chain/wasm_interface.hpp index 55bb57a81..3593fa902 100644 --- a/libraries/chain/include/eos/chain/wasm_interface.hpp +++ b/libraries/chain/include/eos/chain/wasm_interface.hpp @@ -33,10 +33,10 @@ class wasm_interface { }; typedef map TableMap; struct ModuleState { - Runtime::ModuleInstance* instance = nullptr; - IR::Module* module = nullptr; - int mem_start = 0; - int mem_end = 1<<16; + Runtime::ModuleInstance* instance = nullptr; + IR::Module* module = nullptr; + int mem_start = 0; + int mem_end = 1<<16; vector init_memory; fc::sha256 code_version; TableMap table_key_types; @@ -55,19 +55,23 @@ class wasm_interface { static key_type to_key_type(const types::type_name& type_name); static std::string to_type_name(key_type key_type); - apply_context* current_apply_context = nullptr; - apply_context* current_validate_context = nullptr; + apply_context* current_apply_context = nullptr; + apply_context* current_validate_context = nullptr; apply_context* current_precondition_context = nullptr; - Runtime::MemoryInstance* current_memory = nullptr; - Runtime::ModuleInstance* current_module = nullptr; - ModuleState* current_state = nullptr; + Runtime::MemoryInstance* current_memory = nullptr; + Runtime::ModuleInstance* current_module = nullptr; + ModuleState* current_state = nullptr; wasm_memory* current_memory_management = nullptr; TableMap* table_key_types = nullptr; - bool tables_fixed = false; + bool tables_fixed = false; + int64_t table_storage_delta = 0; uint32_t checktime_limit = 0; + int32_t per_code_account_max_db_limit_mbytes = config::default_per_code_account_max_db_limit_mbytes; + uint32_t row_overhead_db_limit_bytes = config::default_row_overhead_db_limit_bytes; + private: void load( const account_name& name, const chainbase::database& db ); diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index f998c5075..365c4980c 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "Platform/Platform.h" #include "WAST/WAST.h" #include "Runtime/Runtime.h" @@ -25,6 +26,7 @@ namespace eosio { namespace chain { using namespace IR; using namespace Runtime; typedef boost::multiprecision::cpp_bin_float_50 DOUBLE; + const uint32_t bytes_per_mbyte = 1024 * 1024; class wasm_memory { @@ -129,6 +131,14 @@ DEFINE_INTRINSIC_FUNCTION0(env,checktime,checktime,none) { return func(wasm.current_apply_context, &keys, value, valuelen); } + int64_t round_to_byte_boundary(int64_t bytes) + { + const unsigned int byte_boundary = sizeof(uint64_t); + int64_t remainder = bytes % byte_boundary; + if (remainder > 0) + bytes += byte_boundary - remainder; + return bytes; + } #define VERIFY_TABLE(TYPE) \ const auto table_name = name(table); \ @@ -159,20 +169,63 @@ DEFINE_INTRINSIC_FUNCTION0(env,checktime,checktime,none) { auto lambda = [&](apply_context* ctx, INDEX::value_type::key_type* keys, char *data, uint32_t datalen) -> int32_t { \ return ctx->UPDATEFUNC( name(scope), name(ctx->code.value), table_name, keys, data, datalen); \ }; \ - return validate(valueptr, DATASIZE, lambda); + const int32_t ret = validate(valueptr, DATASIZE, lambda); + +char* key_str(uint64_t key) +{ + const char nums[] = "0123456789"; + char* ret = new char[512]; + char* ret2 = new char[512]; + const uint64_t rem = 10; + uint32_t index = 0; + for (; key > 0; ++index) + { + ret[index] = nums[(uint32_t)(key % rem)]; + key /= rem; + } + for (uint32_t i = 0; i < index; ++i) + { + ret2[i] = ret[index - 1 - i]; + } + ret2[index] = '\0'; + return ret2; +} -#define DEFINE_RECORD_UPDATE_FUNCTIONS(OBJTYPE, INDEX) \ +#define DEFINE_RECORD_UPDATE_FUNCTIONS(OBJTYPE, INDEX, KEY_SIZE) \ DEFINE_INTRINSIC_FUNCTION4(env,store_##OBJTYPE,store_##OBJTYPE,i32,i64,scope,i64,table,i32,valueptr,i32,valuelen) { \ VERIFY_TABLE(OBJTYPE) \ UPDATE_RECORD(store_record, INDEX, valuelen); \ + /* ret is -1 if record created, or else it is the length of the data portion of the originally stored */ \ + /* structure (it does not include the key portion */ \ + const bool created = (ret == -1); \ + int64_t& delta = wasm_interface::get().table_storage_delta; \ + if (created) \ + delta += round_to_byte_boundary(valuelen) + wasm_interface::get().row_overhead_db_limit_bytes; \ + else \ + /* need to calculate the difference between the original rounded byte size and the new rounded byte size */ \ + delta += round_to_byte_boundary(valuelen) - round_to_byte_boundary(KEY_SIZE + ret); \ + return created ? 1 : 0; \ } \ DEFINE_INTRINSIC_FUNCTION4(env,update_##OBJTYPE,update_##OBJTYPE,i32,i64,scope,i64,table,i32,valueptr,i32,valuelen) { \ VERIFY_TABLE(OBJTYPE) \ UPDATE_RECORD(update_record, INDEX, valuelen); \ + /* ret is -1 if record created, or else it is the length of the data portion of the originally stored */ \ + /* structure (it does not include the key portion */ \ + if (ret == -1) return 0; \ + int64_t& delta = wasm_interface::get().table_storage_delta; \ + /* need to calculate the difference between the original rounded byte size and the new rounded byte size */ \ + delta += round_to_byte_boundary(valuelen) - round_to_byte_boundary(KEY_SIZE + ret); \ + return 1; \ } \ DEFINE_INTRINSIC_FUNCTION3(env,remove_##OBJTYPE,remove_##OBJTYPE,i32,i64,scope,i64,table,i32,valueptr) { \ VERIFY_TABLE(OBJTYPE) \ UPDATE_RECORD(remove_record, INDEX, sizeof(typename INDEX::value_type::key_type)*INDEX::value_type::number_of_keys); \ + /* ret is -1 if record created, or else it is the length of the data portion of the originally stored */ \ + /* structure (it does not include the key portion */ \ + if (ret == -1) return 0; \ + int64_t& delta = wasm_interface::get().table_storage_delta; \ + delta -= round_to_byte_boundary(KEY_SIZE + ret) + wasm_interface::get().row_overhead_db_limit_bytes; \ + return 1; \ } #define DEFINE_RECORD_READ_FUNCTION(OBJTYPE, ACTION, FUNCPREFIX, INDEX, SCOPE) \ @@ -190,14 +243,14 @@ DEFINE_INTRINSIC_FUNCTION0(env,checktime,checktime,none) { DEFINE_RECORD_READ_FUNCTION(OBJTYPE, lower_bound, FUNCPREFIX, INDEX, SCOPE) \ DEFINE_RECORD_READ_FUNCTION(OBJTYPE, upper_bound, FUNCPREFIX, INDEX, SCOPE) -DEFINE_RECORD_UPDATE_FUNCTIONS(i64, key_value_index); +DEFINE_RECORD_UPDATE_FUNCTIONS(i64, key_value_index, 8); DEFINE_RECORD_READ_FUNCTIONS(i64,,key_value_index, by_scope_primary); -DEFINE_RECORD_UPDATE_FUNCTIONS(i128i128, key128x128_value_index); +DEFINE_RECORD_UPDATE_FUNCTIONS(i128i128, key128x128_value_index, 32); DEFINE_RECORD_READ_FUNCTIONS(i128i128, primary_, key128x128_value_index, by_scope_primary); DEFINE_RECORD_READ_FUNCTIONS(i128i128, secondary_, key128x128_value_index, by_scope_secondary); -DEFINE_RECORD_UPDATE_FUNCTIONS(i64i64i64, key64x64x64_value_index); +DEFINE_RECORD_UPDATE_FUNCTIONS(i64i64i64, key64x64x64_value_index, 24); DEFINE_RECORD_READ_FUNCTIONS(i64i64i64, primary_, key64x64x64_value_index, by_scope_primary); DEFINE_RECORD_READ_FUNCTIONS(i64i64i64, secondary_, key64x64x64_value_index, by_scope_secondary); DEFINE_RECORD_READ_FUNCTIONS(i64i64i64, tertiary_, key64x64x64_value_index, by_scope_tertiary); @@ -208,7 +261,7 @@ DEFINE_RECORD_READ_FUNCTIONS(i64i64i64, tertiary_, key64x64x64_value_index, by_ auto lambda = [&](apply_context* ctx, std::string* keys, char *data, uint32_t datalen) -> int32_t { \ return ctx->FUNCTION( name(scope), name(ctx->code.value), table_name, keys, data, datalen); \ }; \ - return validate_str(keyptr, keylen, valueptr, valuelen, lambda); + const int32_t ret = validate_str(keyptr, keylen, valueptr, valuelen, lambda); #define READ_RECORD_STR(FUNCTION) \ VERIFY_TABLE(str) \ @@ -219,14 +272,34 @@ DEFINE_RECORD_READ_FUNCTIONS(i64i64i64, tertiary_, key64x64x64_value_index, by_ return validate_str(keyptr, keylen, valueptr, valuelen, lambda); DEFINE_INTRINSIC_FUNCTION6(env,store_str,store_str,i32,i64,scope,i64,table,i32,keyptr,i32,keylen,i32,valueptr,i32,valuelen) { - UPDATE_RECORD_STR(store_record) + UPDATE_RECORD_STR(store_record) + const bool created = (ret == -1); + auto& delta = wasm_interface::get().table_storage_delta; + if (created) + { + delta += round_to_byte_boundary(keylen + valuelen) + wasm_interface::get().row_overhead_db_limit_bytes; + } + else + // need to calculate the difference between the original rounded byte size and the new rounded byte size + delta += round_to_byte_boundary(keylen + valuelen) - round_to_byte_boundary(keylen + ret); + + return created ? 1 : 0; } DEFINE_INTRINSIC_FUNCTION6(env,update_str,update_str,i32,i64,scope,i64,table,i32,keyptr,i32,keylen,i32,valueptr,i32,valuelen) { - UPDATE_RECORD_STR(update_record) + UPDATE_RECORD_STR(update_record) + if (ret == -1) return 0; + auto& delta = wasm_interface::get().table_storage_delta; + // need to calculate the difference between the original rounded byte size and the new rounded byte size + delta += round_to_byte_boundary(keylen + valuelen) - round_to_byte_boundary(keylen + ret); + return 1; } DEFINE_INTRINSIC_FUNCTION4(env,remove_str,remove_str,i32,i64,scope,i64,table,i32,keyptr,i32,keylen) { - int32_t valueptr=0, valuelen=0; - UPDATE_RECORD_STR(remove_record) + int32_t valueptr=0, valuelen=0; + UPDATE_RECORD_STR(remove_record) + if (ret == -1) return 0; + auto& delta = wasm_interface::get().table_storage_delta; + delta -= round_to_byte_boundary(keylen + ret) + wasm_interface::get().row_overhead_db_limit_bytes; + return 1; } DEFINE_INTRINSIC_FUNCTION7(env,load_str,load_str,i32,i64,scope,i64,code,i64,table,i32,keyptr,i32,keylen,i32,valueptr,i32,valuelen) { @@ -765,6 +838,27 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) { vm_apply(); + if (table_storage_delta != 0) + { + auto rate_limiting = c.db.find(c.code); + if (rate_limiting == nullptr) + { + EOS_ASSERT(table_storage_delta <= per_code_account_max_db_limit_mbytes * bytes_per_mbyte, tx_code_db_limit_exceeded, "Database limit exceeded for account=${name}",("name", c.code)); + + c.mutable_db.create([delta=this->table_storage_delta,code=c.code](rate_limiting_object& rlo) { + rlo.name = code; + rlo.per_code_account_db_bytes = delta; + }); + } + else + { + EOS_ASSERT(table_storage_delta <= (per_code_account_max_db_limit_mbytes * bytes_per_mbyte) - rate_limiting->per_code_account_db_bytes, tx_code_db_limit_exceeded, "Database limit exceeded for account=${name}",("name", c.code)); + + c.mutable_db.modify(*rate_limiting, [delta=this->table_storage_delta] (rate_limiting_object& rlo) { + rlo.per_code_account_db_bytes += delta; + }); + } + } } FC_CAPTURE_AND_RETHROW() } void wasm_interface::init( apply_context& c ) { @@ -864,11 +958,12 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) { throw; } } - current_module = state.instance; - current_memory = getDefaultMemory( current_module ); - current_state = &state; - table_key_types = &state.table_key_types; - tables_fixed = state.tables_fixed; + current_module = state.instance; + current_memory = getDefaultMemory( current_module ); + current_state = &state; + table_key_types = &state.table_key_types; + tables_fixed = state.tables_fixed; + table_storage_delta = 0; } wasm_memory::wasm_memory(wasm_interface& interface) -- GitLab