diff --git a/libraries/chain/include/eos/chain/wasm_interface.hpp b/libraries/chain/include/eos/chain/wasm_interface.hpp index 77c99279065173469562e02031541583d41624c8..ef90ae25615aeb2faec50ccc20ee771dc12d2ba3 100644 --- a/libraries/chain/include/eos/chain/wasm_interface.hpp +++ b/libraries/chain/include/eos/chain/wasm_interface.hpp @@ -24,6 +24,14 @@ class wasm_memory; */ class wasm_interface { public: + enum key_type { + str, + i64, + i128i128, + i64i64i64, + invalid_key_type + }; + typedef map TableMap; struct ModuleState { Runtime::ModuleInstance* instance = nullptr; IR::Module* module = nullptr; @@ -31,17 +39,22 @@ class wasm_interface { int mem_end = 1<<16; vector init_memory; fc::sha256 code_version; + TableMap table_key_types; + bool tables_fixed = false; }; static wasm_interface& get(); void init( apply_context& c ); - void apply( apply_context& c, uint32_t execution_time ); + void apply( apply_context& c, uint32_t execution_time, bool received_block ); void validate( apply_context& c ); void precondition( apply_context& c ); int64_t current_execution_time(); + static key_type to_key_type(const types::TypeName& 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_precondition_context = nullptr; @@ -50,6 +63,8 @@ class wasm_interface { Runtime::ModuleInstance* current_module = nullptr; ModuleState* current_state = nullptr; wasm_memory* current_memory_management = nullptr; + TableMap* table_key_types = nullptr; + bool tables_fixed = false; uint32_t checktime_limit = 0; diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index eca9b65d20f52247c800bd8193f42980e4a9ba15..3fb53e6889d2fc250010850483f237e1201b71c1 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -16,7 +16,9 @@ #include "IR/Validate.h" #include #include +#include #include +#include namespace eos { namespace chain { using namespace IR; @@ -44,6 +46,37 @@ namespace eos { namespace chain { wasm_interface::wasm_interface() { } + wasm_interface::key_type wasm_interface::to_key_type(const types::TypeName& type_name) + { + if ("str" == type_name) + return str; + if ("i64" == type_name) + return i64; + if ("i128i128" == type_name) + return i128i128; + if ("i64i64i64" == type_name) + return i64i64i64; + + return invalid_key_type; + } + + std::string wasm_interface::to_type_name(key_type key_type) + { + switch (key_type) + { + case str: + return "str"; + case i64: + return "i64"; + case i128i128: + return "i128i128"; + case i64i64i64: + return "i64i64i64"; + default: + return std::string("(int(key_type)) + ">"; + } + } + #ifdef NDEBUG const int CHECKTIME_LIMIT = 3000; #else @@ -96,9 +129,26 @@ DEFINE_INTRINSIC_FUNCTION0(env,checktime,checktime,none) { } +#define VERIFY_TABLE(TYPE) \ + const auto table_name = Name(table); \ + auto& wasm = wasm_interface::get(); \ + if (wasm.table_key_types) \ + { \ + auto table_key = wasm.table_key_types->find(table_name); \ + if (table_key == wasm.table_key_types->end()) \ + { \ + FC_ASSERT(!wasm.tables_fixed, "abi did not define table ${t}", ("t", table_name)); \ + wasm.table_key_types->emplace(std::make_pair(table_name,wasm_interface::TYPE)); \ + } \ + else \ + { \ + FC_ASSERT(wasm_interface::TYPE == table_key->second, "abi definition for ${table} expects \"${type}\", but code is requesting \"" #TYPE "\"", ("table",table_name)("type",wasm_interface::to_type_name(table_key->second))); \ + } \ + } + #define READ_RECORD(READFUNC, INDEX, SCOPE) \ auto lambda = [&](apply_context* ctx, INDEX::value_type::key_type* keys, char *data, uint32_t datalen) -> int32_t { \ - auto res = ctx->READFUNC( Name(scope), Name(code), Name(table), keys, data, datalen); \ + auto res = ctx->READFUNC( Name(scope), Name(code), table_name, keys, data, datalen); \ if (res >= 0) res += INDEX::value_type::number_of_keys*sizeof(INDEX::value_type::key_type); \ return res; \ }; \ @@ -106,44 +156,39 @@ DEFINE_INTRINSIC_FUNCTION0(env,checktime,checktime,none) { #define UPDATE_RECORD(UPDATEFUNC, INDEX, DATASIZE) \ 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), Name(table), keys, data, datalen); \ + return ctx->UPDATEFUNC( Name(scope), Name(ctx->code.value), table_name, keys, data, datalen); \ }; \ return validate(valueptr, DATASIZE, lambda); #define DEFINE_RECORD_UPDATE_FUNCTIONS(OBJTYPE, INDEX) \ 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); \ } \ 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); \ } \ 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); \ } -#define DEFINE_RECORD_READ_FUNCTIONS(OBJTYPE, FUNCPREFIX, INDEX, SCOPE) \ - DEFINE_INTRINSIC_FUNCTION5(env,load_##FUNCPREFIX##OBJTYPE,load_##FUNCPREFIX##OBJTYPE,i32,i64,scope,i64,code,i64,table,i32,valueptr,i32,valuelen) { \ - READ_RECORD(load_record, INDEX, SCOPE); \ - } \ - DEFINE_INTRINSIC_FUNCTION5(env,front_##FUNCPREFIX##OBJTYPE,front_##FUNCPREFIX##OBJTYPE,i32,i64,scope,i64,code,i64,table,i32,valueptr,i32,valuelen) { \ - READ_RECORD(front_record, INDEX, SCOPE); \ - } \ - DEFINE_INTRINSIC_FUNCTION5(env,back_##FUNCPREFIX##OBJTYPE,back_##FUNCPREFIX##OBJTYPE,i32,i64,scope,i64,code,i64,table,i32,valueptr,i32,valuelen) { \ - READ_RECORD(back_record, INDEX, SCOPE); \ - } \ - DEFINE_INTRINSIC_FUNCTION5(env,next_##FUNCPREFIX##OBJTYPE,next_##FUNCPREFIX##OBJTYPE,i32,i64,scope,i64,code,i64,table,i32,valueptr,i32,valuelen) { \ - READ_RECORD(next_record, INDEX, SCOPE); \ - } \ - DEFINE_INTRINSIC_FUNCTION5(env,previous_##FUNCPREFIX##OBJTYPE,previous_##FUNCPREFIX##OBJTYPE,i32,i64,scope,i64,code,i64,table,i32,valueptr,i32,valuelen) { \ - READ_RECORD(previous_record, INDEX, SCOPE); \ - } \ - DEFINE_INTRINSIC_FUNCTION5(env,lower_bound_##FUNCPREFIX##OBJTYPE,lower_bound_##FUNCPREFIX##OBJTYPE,i32,i64,scope,i64,code,i64,table,i32,valueptr,i32,valuelen) { \ - READ_RECORD(lower_bound_record, INDEX, SCOPE); \ - } \ - DEFINE_INTRINSIC_FUNCTION5(env,upper_bound_##FUNCPREFIX##OBJTYPE,upper_bound_##FUNCPREFIX##OBJTYPE,i32,i64,scope,i64,code,i64,table,i32,valueptr,i32,valuelen) { \ - READ_RECORD(upper_bound_record, INDEX, SCOPE); \ +#define DEFINE_RECORD_READ_FUNCTION(OBJTYPE, ACTION, FUNCPREFIX, INDEX, SCOPE) \ + DEFINE_INTRINSIC_FUNCTION5(env,ACTION##_##FUNCPREFIX##OBJTYPE,ACTION##_##FUNCPREFIX##OBJTYPE,i32,i64,scope,i64,code,i64,table,i32,valueptr,i32,valuelen) { \ + VERIFY_TABLE(OBJTYPE) \ + READ_RECORD(ACTION##_record, INDEX, SCOPE); \ } +#define DEFINE_RECORD_READ_FUNCTIONS(OBJTYPE, FUNCPREFIX, INDEX, SCOPE) \ + DEFINE_RECORD_READ_FUNCTION(OBJTYPE, load, FUNCPREFIX, INDEX, SCOPE) \ + DEFINE_RECORD_READ_FUNCTION(OBJTYPE, front, FUNCPREFIX, INDEX, SCOPE) \ + DEFINE_RECORD_READ_FUNCTION(OBJTYPE, back, FUNCPREFIX, INDEX, SCOPE) \ + DEFINE_RECORD_READ_FUNCTION(OBJTYPE, next, FUNCPREFIX, INDEX, SCOPE) \ + DEFINE_RECORD_READ_FUNCTION(OBJTYPE, previous, FUNCPREFIX, INDEX, SCOPE) \ + 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_READ_FUNCTIONS(i64,,key_value_index, by_scope_primary); @@ -158,14 +203,16 @@ DEFINE_RECORD_READ_FUNCTIONS(i64i64i64, tertiary_, key64x64x64_value_index, by_ #define UPDATE_RECORD_STR(FUNCTION) \ + VERIFY_TABLE(str) \ auto lambda = [&](apply_context* ctx, std::string* keys, char *data, uint32_t datalen) -> int32_t { \ - return ctx->FUNCTION( Name(scope), Name(ctx->code.value), Name(table), keys, data, datalen); \ + return ctx->FUNCTION( Name(scope), Name(ctx->code.value), table_name, keys, data, datalen); \ }; \ return validate_str(keyptr, keylen, valueptr, valuelen, lambda); #define READ_RECORD_STR(FUNCTION) \ + VERIFY_TABLE(str) \ auto lambda = [&](apply_context* ctx, std::string* keys, char *data, uint32_t datalen) -> int32_t { \ - auto res = ctx->FUNCTION( Name(scope), Name(code), Name(table), keys, data, datalen); \ + auto res = ctx->FUNCTION( Name(scope), Name(code), table_name, keys, data, datalen); \ return res; \ }; \ return validate_str(keyptr, keylen, valueptr, valuelen, lambda); @@ -697,7 +744,7 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) { } FC_CAPTURE_AND_RETHROW() } - void wasm_interface::apply( apply_context& c, uint32_t execution_time ) { + void wasm_interface::apply( apply_context& c, uint32_t execution_time, bool received_block ) { try { current_validate_context = &c; current_precondition_context = &c; @@ -705,6 +752,10 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) { checktime_limit = execution_time; load( c.code, c.db ); + // if this is a received_block, then ignore the table_key_types + if (received_block) + table_key_types = nullptr; + vm_apply(); } FC_CAPTURE_AND_RETHROW() } @@ -729,14 +780,15 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) { auto& state = instances[name]; if( state.code_version != recipient.code_version ) { - if( state.instance ) { - /// TODO: free existing instance and module + if( state.instance ) { + /// TODO: free existing instance and module #warning TODO: free existing module if the code has been updated, currently leak memory - state.instance = nullptr; - state.module = nullptr; - state.code_version = fc::sha256(); - } - state.module = new IR::Module(); + state.instance = nullptr; + state.module = nullptr; + state.code_version = fc::sha256(); + } + state.module = new IR::Module(); + state.table_key_types.clear(); try { @@ -771,6 +823,21 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) { //std::cerr <<"\n"; state.code_version = recipient.code_version; // idump((state.code_version)); + + types::Abi abi; + if( types::AbiSerializer::to_abi(recipient.abi, abi) ) + { + state.tables_fixed = true; + for(auto& table : abi.tables) + { + const auto key_type = to_key_type(table.indextype); + if (key_type == invalid_key_type) + throw Serialization::FatalSerializationException("For code \"" + (std::string)name + "\" indextype of \"" + + table.indextype + "\" referenced but not supported"); + + state.table_key_types.emplace(std::make_pair(table.table, key_type)); + } + } } catch(Serialization::FatalSerializationException exception) { @@ -790,9 +857,11 @@ DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) { throw; } } - current_module = state.instance; - current_memory = getDefaultMemory( current_module ); - current_state = &state; + current_module = state.instance; + current_memory = getDefaultMemory( current_module ); + current_state = &state; + table_key_types = &state.table_key_types; + tables_fixed = state.tables_fixed; } wasm_memory::wasm_memory(wasm_interface& interface)