From 60a30466bc505ea0b2cf3f579c00ff38223d369f Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Sat, 17 Jun 2017 10:44:56 -0400 Subject: [PATCH] first smart contract coin created and tested with web assembly --- libraries/chain/chain_controller.cpp | 28 +- .../include/eos/chain/account_object.hpp | 14 +- .../include/eos/chain/action_objects.hpp | 42 - .../include/eos/chain/chain_controller.hpp | 2 +- libraries/chain/include/eos/chain/types.hpp | 2 +- .../include/eos/chain/wasm_interface.hpp | 36 +- libraries/chain/wasm_interface.cpp | 295 +++++-- .../eos/native_contract/system_contract.hpp | 2 +- libraries/native_contract/system_contract.cpp | 46 +- libraries/types/types.eos | 12 +- tests/tests/block_tests.cpp | 826 +++++++++++++++--- 11 files changed, 1007 insertions(+), 298 deletions(-) diff --git a/libraries/chain/chain_controller.cpp b/libraries/chain/chain_controller.cpp index e98e44eda..94a6ff3ed 100644 --- a/libraries/chain/chain_controller.cpp +++ b/libraries/chain/chain_controller.cpp @@ -635,30 +635,11 @@ void chain_controller::apply_message( apply_context& context ) return; } } - const auto& scope = _db.get( context.scope ); - const auto& recipient = _db.get( context.msg.recipient ); - - auto handler = _db.find( boost::make_tuple(scope.id, recipient.id, context.msg.type) ); - if( handler ) { - wasm_interface::get().load( handler->apply.data(), handler->apply.size() ); - wasm_interface::get().apply( context ); - /* - wdump((handler->apply.c_str())); - wrenpp::VM vm; - vm.executeString( R"( - foreign class ApplyContext { - foreign get(key) - foreign set(key,value) - } - )"); - vm.executeString( handler->apply.c_str() ); - auto apply_method = vm.method( "main", "Handler", "apply(_,_)" ); - //apply_method( context, m.data ); - //apply_method( "context", 1 ); - apply_method( &context, 1 ); - */ + const auto& recipient = _db.get( context.scope ); + if( recipient.code.size() ) { + wasm_interface::get().apply( context ); } - /// TODO: dispatch to script if not handled above + } FC_CAPTURE_AND_RETHROW((context.msg)) } @@ -800,7 +781,6 @@ uint32_t chain_controller::last_irreversible_block_num() const { void chain_controller::initialize_indexes() { _db.add_index(); _db.add_index(); - _db.add_index(); _db.add_index(); _db.add_index(); _db.add_index(); diff --git a/libraries/chain/include/eos/chain/account_object.hpp b/libraries/chain/include/eos/chain/account_object.hpp index 4e787e5d7..58ad03557 100644 --- a/libraries/chain/include/eos/chain/account_object.hpp +++ b/libraries/chain/include/eos/chain/account_object.hpp @@ -57,11 +57,15 @@ namespace eos { namespace chain { }; class account_object : public chainbase::object { - OBJECT_CTOR(account_object) + OBJECT_CTOR(account_object,(code)) - id_type id; - AccountName name; - Time creation_date; + id_type id; + AccountName name; + uint8_t vm_type = 0; + uint8_t vm_version = 0; + uint16_t code_version = 0; + shared_vector code; + Time creation_date; }; using account_id_type = account_object::id_type; @@ -120,6 +124,6 @@ CHAINBASE_SET_INDEX_TYPE(eos::chain::permission_object, eos::chain::permission_i FC_REFLECT(chainbase::oid, (_id)) FC_REFLECT(chainbase::oid, (_id)) -FC_REFLECT(eos::chain::account_object, (id)(name)(creation_date)) +FC_REFLECT(eos::chain::account_object, (id)(name)(vm_type)(vm_version)(code_version)(code)(creation_date)) // TODO: Reflect permission_object::auth FC_REFLECT(eos::chain::permission_object, (id)(owner)(parent)(name)) diff --git a/libraries/chain/include/eos/chain/action_objects.hpp b/libraries/chain/include/eos/chain/action_objects.hpp index 2997debed..9c1abdbbe 100644 --- a/libraries/chain/include/eos/chain/action_objects.hpp +++ b/libraries/chain/include/eos/chain/action_objects.hpp @@ -29,46 +29,6 @@ namespace eos { namespace chain { - /** - * This table defines all of the event handlers for every contract. Every message is - * delivered TO a particular contract and also processed in parallel by several other contracts. - * - * Each account can define a custom handler based upon the tuple { processor, recipient, type } where - * processor is the account that is processing the message, recipient is the account specified by - * message::recipient and type is messagse::type. - * - * - */ - class action_code_object : public chainbase::object - { - OBJECT_CTOR(action_code_object, (validate_action)(validate_precondition)(apply) ) - - id_type id; - account_id_type recipient; - account_id_type processor; - TypeName type; ///< the name of the action (defines serialization) - - shared_string validate_action; ///< read only access to action - shared_string validate_precondition; ///< read only access to state - shared_string apply; ///< the code that executes the state transition - }; - - struct by_parent; - struct by_processor_recipient_type; - using action_code_index = chainbase::shared_multi_index_container< - action_code_object, - indexed_by< - ordered_unique, member>, - ordered_unique, - composite_key< action_code_object, - member, - member, - member - > - > - > - >; - /** * Maps the permission level on the code to the permission level specififed by owner, when specifying a contract the * contract will specify 1 permission_object per action, and by default the parent of that permission object will be @@ -118,8 +78,6 @@ namespace eos { namespace chain { } } // eos::chain -CHAINBASE_SET_INDEX_TYPE(eos::chain::action_code_object, eos::chain::action_code_index) CHAINBASE_SET_INDEX_TYPE(eos::chain::action_permission_object, eos::chain::action_permission_index) -FC_REFLECT(eos::chain::action_code_object, (id)(recipient)(processor)(type)(validate_action)(validate_precondition)(apply) ) FC_REFLECT(eos::chain::action_permission_object, (id)(owner)(owner_permission)(scope_permission) ) diff --git a/libraries/chain/include/eos/chain/chain_controller.hpp b/libraries/chain/include/eos/chain/chain_controller.hpp index 5a02ae1f9..56310bfab 100644 --- a/libraries/chain/include/eos/chain/chain_controller.hpp +++ b/libraries/chain/include/eos/chain/chain_controller.hpp @@ -90,7 +90,7 @@ namespace eos { namespace chain { } /** - * The database can override any script handler with native code. + * The controller can override any script endpoint with native code. */ ///@{ void set_validate_handler( const AccountName& contract, const AccountName& scope, const TypeName& action, message_validate_handler v ); diff --git a/libraries/chain/include/eos/chain/types.hpp b/libraries/chain/include/eos/chain/types.hpp index 82d2571ec..e94a3254c 100644 --- a/libraries/chain/include/eos/chain/types.hpp +++ b/libraries/chain/include/eos/chain/types.hpp @@ -64,7 +64,7 @@ { c(*this); } #define OBJECT_CTOR(...) BOOST_PP_OVERLOAD(OBJECT_CTOR, __VA_ARGS__)(__VA_ARGS__) -#define EOS_SYSTEM_CONTRACT_FUNCTIONS (CreateAccount)(DefineStruct)(SetMessageHandler) +#define EOS_SYSTEM_CONTRACT_FUNCTIONS (CreateAccount)(DefineStruct)(SetCode) #define EOS_CONTRACT_FUNCTIONS (Transfer)(TransferToLocked) #define EOS_STAKED_BALANCE_CONTRACT_FUNCTIONS (CreateProducer)(UpdateProducer)(ApproveProducer) diff --git a/libraries/chain/include/eos/chain/wasm_interface.hpp b/libraries/chain/include/eos/chain/wasm_interface.hpp index 18116ad14..080bd110d 100644 --- a/libraries/chain/include/eos/chain/wasm_interface.hpp +++ b/libraries/chain/include/eos/chain/wasm_interface.hpp @@ -20,35 +20,39 @@ class wasm_interface { public: static wasm_interface& get(); - enum database_access_type { - none, - read_only, - read_write - }; - - void load(const char* wasmbytes, size_t len ); + + void init( apply_context& c ); void apply( apply_context& c ); void validate( message_validate_context& c ); void precondition( precondition_validate_context& c ); - apply_context* current_apply_context = nullptr; - message_validate_context* current_validate_context = nullptr; + + apply_context* current_apply_context = nullptr; + message_validate_context* current_validate_context = nullptr; precondition_validate_context* current_precondition_context = nullptr; Runtime::MemoryInstance* current_memory = nullptr; Runtime::ModuleInstance* current_module = nullptr; - chain_controller* current_chain = nullptr; - const AccountName* current_account = nullptr; - const Message* current_message = nullptr; - database_access_type current_access = none; private: - char* vm_allocate( int bytes ); - void vm_apply( const vector& message ); + void load( const AccountName& name, const chainbase::database& db ); + + char* vm_allocate( int bytes ); + void vm_apply(); + void vm_onInit(); U32 vm_pointer_to_offset( char* ); - IR::Module* module = nullptr; + struct ModuleState { + Runtime::ModuleInstance* instance = nullptr; + IR::Module* module = nullptr; + vector init_memory; + uint16_t code_version = -1; + }; + + map instances; + + wasm_interface(); }; diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 8347365a3..b6fc2a312 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -8,6 +8,7 @@ #include "IR/Operators.h" #include "IR/Validate.h" #include +#include namespace eos { namespace chain { using namespace IR; @@ -17,6 +18,7 @@ namespace eos { namespace chain { } DEFINE_INTRINSIC_FUNCTION4(env,store,store,none,i32,keyptr,i32,keylen,i32,valueptr,i32,valuelen ) { + ilog( "store" ); FC_ASSERT( keylen > 0 ); FC_ASSERT( valuelen >= 0 ); @@ -31,6 +33,9 @@ DEFINE_INTRINSIC_FUNCTION4(env,store,store,none,i32,keyptr,i32,keylen,i32,valuep char* value = &memoryRef( mem, valueptr ); string keystr( key, key+keylen); + idump((keystr)); + if( valuelen == 8 ) idump(( *((int64_t*)value))); + const auto* obj = db.find( boost::make_tuple(scope, keystr) ); if( obj ) { @@ -47,7 +52,84 @@ DEFINE_INTRINSIC_FUNCTION4(env,store,store,none,i32,keyptr,i32,keylen,i32,valuep } } +DEFINE_INTRINSIC_FUNCTION2(env,remove,remove,i32,i32,keyptr,i32,keylen) { + FC_ASSERT( keylen > 0 ); + + auto& wasm = wasm_interface::get(); + + FC_ASSERT( wasm.current_apply_context, "no apply context found" ); + + auto& db = wasm.current_apply_context->mutable_db; + auto& scope = wasm.current_apply_context->scope; + auto mem = wasm.current_memory; + char* key = &memoryRef( mem, keyptr ); + string keystr( key, key+keylen); + + const auto* obj = db.find( boost::make_tuple(scope, keystr) ); + if( obj ) { + db.remove( *obj ); + return true; + } + return false; +} + +DEFINE_INTRINSIC_FUNCTION3(env,memcpy,memcpy,i32,i32,dstp,i32,srcp,i32,len) { + auto& wasm = wasm_interface::get(); + auto mem = wasm.current_memory; + char* dst = &memoryRef( mem, dstp); + const char* src = &memoryRef( mem, srcp ); + char* dst_end = &memoryRef( mem, dstp+uint32_t(len)); + const char* src_end = &memoryRef( mem, srcp+uint32_t(len) ); + +#warning TODO: wasm memcpy has undefined behavior if memory ranges overlap +/* + if( dst > src ) + FC_ASSERT( dst < src_end && src < dst_end, "overlap of memory range is undefined", ("d",dstp)("s",srcp)("l",len) ); + else + FC_ASSERT( src < dst_end && dst < src_end, "overlap of memory range is undefined", ("d",dstp)("s",srcp)("l",len) ); +*/ + memcpy( dst, src, uint32_t(len) ); + return dstp; +} + + +DEFINE_INTRINSIC_FUNCTION2(env,Varint_unpack,Varint_unpack,none,i32,streamptr,i32,valueptr) { + auto& wasm = wasm_interface::get(); + auto mem = wasm.current_memory; + + uint32_t* stream = &memoryRef( mem, streamptr ); + const char* pos = &memoryRef( mem, stream[1] ); + const char* end = &memoryRef( mem, stream[2] ); + uint32_t& value = memoryRef( mem, valueptr ); + + fc::unsigned_int vi; + fc::datastream ds(pos,end-pos); + fc::raw::unpack( ds, vi ); + value = vi.value; + + stream[1] += ds.pos() - pos; +} + +DEFINE_INTRINSIC_FUNCTION2(env,AccountName_unpack,AccountName_unpack,none,i32,streamptr,i32,accountptr) { + auto& wasm = wasm_interface::get(); + auto mem = wasm.current_memory; + + + uint32_t* stream = &memoryRef( mem, streamptr ); + const char* pos = &memoryRef( mem, stream[1] ); + const char* end = &memoryRef( mem, stream[2] ); + AccountName* name = &memoryRef( mem, accountptr ); + + fc::datastream ds( pos, end - pos ); + fc::raw::unpack( ds, *name ); + + stream[1] += ds.pos() - pos; +} + + + DEFINE_INTRINSIC_FUNCTION4(env,load,load,i32,i32,keyptr,i32,keylen,i32,valueptr,i32,valuelen ) { + ilog( "load" ); FC_ASSERT( keylen > 0 ); FC_ASSERT( valuelen >= 0 ); @@ -61,22 +143,40 @@ DEFINE_INTRINSIC_FUNCTION4(env,load,load,i32,i32,keyptr,i32,keylen,i32,valueptr, char* key = &memoryRef( mem, keyptr ); char* value = &memoryRef( mem, valueptr ); string keystr( key, key+keylen); + idump((keystr)); const auto* obj = db.find( boost::make_tuple(scope, keystr) ); if( obj == nullptr ) return -1; auto copylen = std::min(obj->value.size(),valuelen); if( copylen ) { memcpy( value, obj->value.data(), copylen ); + if( copylen == 8 ) idump(( *((int64_t*)value))); } return copylen; } +DEFINE_INTRINSIC_FUNCTION2(env,readMessage,readMessage,i32,i32,destptr,i32,destsize) { + FC_ASSERT( destsize > 0 ); + wasm_interface& wasm = wasm_interface::get(); + auto mem = wasm.current_memory; + char* begin = &Runtime::memoryRef( mem, destptr ); + Runtime::memoryRef( mem, destptr + destsize ); + + int minlen = std::min(wasm.current_validate_context->msg.data.size(), destsize); + memcpy( begin, wasm.current_validate_context->msg.data.data(), minlen ); + return minlen; +} + DEFINE_INTRINSIC_FUNCTION2(env,assert,assert,none,i32,test,i32,msg) { std::string message = &Runtime::memoryRef( wasm_interface::get().current_memory, msg ); if( !test ) edump((message)); - FC_ASSERT( test, "assertion failed: ${s}", ("s",message) ); + FC_ASSERT( test, "assertion failed: ${s}", ("s",message)("ptr",msg) ); } +DEFINE_INTRINSIC_FUNCTION0(env,messageSize,messageSize,i32) { + return wasm_interface::get().current_validate_context->msg.data.size(); +} + DEFINE_INTRINSIC_FUNCTION1(env,malloc,malloc,i32,i32,size) { FC_ASSERT( size > 0 ); int32_t& end = Runtime::memoryRef( Runtime::getDefaultMemory(wasm_interface::get().current_module), 0); @@ -89,13 +189,16 @@ DEFINE_INTRINSIC_FUNCTION1(env,malloc,malloc,i32,i32,size) { DEFINE_INTRINSIC_FUNCTION1(env,printi,printi,none,i32,val) { idump((val)); } +DEFINE_INTRINSIC_FUNCTION1(env,printi64,printi64,none,i64,val) { + idump((val)); +} DEFINE_INTRINSIC_FUNCTION2(env,print,print,none,i32,charptr,i32,size) { FC_ASSERT( size > 0 ); - char* str = &Runtime::memoryRef( Runtime::getDefaultMemory(wasm_interface::get().current_module), charptr); - char* end = &Runtime::memoryRef( Runtime::getDefaultMemory(wasm_interface::get().current_module), charptr+size); + const char* str = &Runtime::memoryRef( Runtime::getDefaultMemory(wasm_interface::get().current_module), charptr); + const char* end = &Runtime::memoryRef( Runtime::getDefaultMemory(wasm_interface::get().current_module), charptr+size); edump((charptr)(size)); - wlog( std::string( str, end ) ); + wlog( std::string( str, size ) ); } DEFINE_INTRINSIC_FUNCTION1(env,free,free,none,i32,ptr) { @@ -132,62 +235,10 @@ DEFINE_INTRINSIC_FUNCTION1(env,toUpper,toUpper,none,i32,charptr) { bool resolve(const std::string& moduleName,const std::string& exportName,ObjectType type,ObjectInstance*& outObject) override { - // Try to resolve an intrinsic first. - if(IntrinsicResolver::singleton.resolve(moduleName,exportName,type,outObject)) { return true; } - FC_ASSERT( !"unresolvable", "${module}.${export}", ("module",moduleName)("export",exportName) ); - - // Then look for a named module. - auto namedResolverIt = moduleNameToResolverMap.find(moduleName); - if(namedResolverIt != moduleNameToResolverMap.end()) - { - return namedResolverIt->second->resolve(moduleName,exportName,type,outObject); - } - - // Finally, stub in missing function imports. - if(type.kind == ObjectKind::function) - { - // Generate a function body that just uses the unreachable op to fault if called. - Serialization::ArrayOutputStream codeStream; - OperatorEncoderStream encoder(codeStream); - encoder.unreachable(); - encoder.end(); - - // Generate a module for the stub function. - Module stubModule; - DisassemblyNames stubModuleNames; - stubModule.types.push_back(asFunctionType(type)); - stubModule.functions.defs.push_back({{0},{},std::move(codeStream.getBytes()),{}}); - stubModule.exports.push_back({"importStub",ObjectKind::function,0}); - stubModuleNames.functions.push_back({std::string(moduleName) + "." + exportName,{}}); - IR::setDisassemblyNames(stubModule,stubModuleNames); - IR::validateDefinitions(stubModule); - - // Instantiate the module and return the stub function instance. - auto stubModuleInstance = instantiateModule(stubModule,{}); - outObject = getInstanceExport(stubModuleInstance,"importStub"); - //Log::printf(Log::Category::error,"Generated stub for missing function import %s.%s : %s\n",moduleName.c_str(),exportName.c_str(),asString(type).c_str()); - return true; - } - else if(type.kind == ObjectKind::memory) - { - outObject = asObject(Runtime::createMemory(asMemoryType(type))); - //Log::printf(Log::Category::error,"Generated stub for missing memory import %s.%s : %s\n",moduleName.c_str(),exportName.c_str(),asString(type).c_str()); - return true; - } - else if(type.kind == ObjectKind::table) - { - outObject = asObject(Runtime::createTable(asTableType(type))); - //Log::printf(Log::Category::error,"Generated stub for missing table import %s.%s : %s\n",moduleName.c_str(),exportName.c_str(),asString(type).c_str()); - return true; - } - else if(type.kind == ObjectKind::global) - { - outObject = asObject(Runtime::createGlobal(asGlobalType(type),Runtime::Value(asGlobalType(type).valueType,Runtime::UntaggedValue()))); - //Log::printf(Log::Category::error,"Generated stub for missing global import %s.%s : %s\n",moduleName.c_str(),exportName.c_str(),asString(type).c_str()); - return true; - } - - return false; + // Try to resolve an intrinsic first. + if(IntrinsicResolver::singleton.resolve(moduleName,exportName,type,outObject)) { return true; } + FC_ASSERT( !"unresolvable", "${module}.${export}", ("module",moduleName)("export",exportName) ); + return false; } }; @@ -221,19 +272,44 @@ DEFINE_INTRINSIC_FUNCTION1(env,toUpper,toUpper,none,i32,charptr) { return U32(ptr - &memoryRef(current_memory,0)); } - void wasm_interface::vm_apply( const vector& message ) + void wasm_interface::vm_apply() { try { try { - FunctionInstance* apply = asFunctionNullable(getInstanceExport(current_module,"apply")); + std::string mangledapply("onApply_"); + mangledapply += std::string( current_validate_context->msg.type ) + "_"; + mangledapply += std::string( current_validate_context->msg.recipient ); + idump((mangledapply)); + + FunctionInstance* apply = asFunctionNullable(getInstanceExport(current_module,mangledapply.c_str())); + if( !apply ) return; /// if not found then it is a no-op + const FunctionType* functionType = getFunctionType(apply); - FC_ASSERT( functionType->parameters.size() == 2 ); + FC_ASSERT( functionType->parameters.size() == 0 ); - auto buffer = vm_allocate( message.size() ); - memcpy( buffer, message.data(), message.size() ); + std::vector args(0); - std::vector args(2); - args[0] = vm_pointer_to_offset(buffer); - args[1] = U32(message.size()); + Runtime::invokeFunction(apply,args); + } catch( const Runtime::Exception& e ) { + edump((std::string(describeExceptionCause(e.cause)))); + edump((e.callStack)); + throw; + } + } FC_CAPTURE_AND_RETHROW() } + + void wasm_interface::vm_onInit() + { try { + try { + wlog( "onInit" ); + FunctionInstance* apply = asFunctionNullable(getInstanceExport(current_module,"onInit")); + if( !apply ) { + wlog( "no onInit method found" ); + return; /// if not found then it is a no-op + } + + const FunctionType* functionType = getFunctionType(apply); + FC_ASSERT( functionType->parameters.size() == 0 ); + + std::vector args(0); Runtime::invokeFunction(apply,args); } catch( const Runtime::Exception& e ) { @@ -241,25 +317,97 @@ DEFINE_INTRINSIC_FUNCTION1(env,toUpper,toUpper,none,i32,charptr) { edump((e.callStack)); throw; } - } FC_CAPTURE_AND_RETHROW( (message) ) } + } FC_CAPTURE_AND_RETHROW() } void wasm_interface::apply( apply_context& c ) { try { + load( c.scope, c.db ); + + current_validate_context = &c; + current_precondition_context = &c; + current_apply_context = &c; + + vm_apply(); + + } FC_CAPTURE_AND_RETHROW() } + + void wasm_interface::init( apply_context& c ) { + try { + ilog( "WASM INTERFACE INIT" ); + load( c.scope, c.db ); + current_validate_context = &c; current_precondition_context = &c; current_apply_context = &c; - vm_apply( current_validate_context->msg.data ); + vm_onInit(); - current_validate_context = nullptr; - current_precondition_context = nullptr; - current_apply_context = nullptr; } FC_CAPTURE_AND_RETHROW() } + void wasm_interface::load( const AccountName& name, const chainbase::database& db ) { + const auto& recipient = db.get( name ); + + auto& state = instances[name]; + if( state.code_version != recipient.code_version ) { + 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 = -1; + } + state.module = new IR::Module(); + + try + { + Serialization::MemoryInputStream stream((const U8*)recipient.code.data(),recipient.code.size()); + WASM::serialize(stream,*state.module); + + RootResolver rootResolver; + LinkResult linkResult = linkModule(*state.module,rootResolver); + state.instance = instantiateModule( *state.module, std::move(linkResult.resolvedImports) ); + FC_ASSERT( state.instance ); + current_memory = Runtime::getDefaultMemory(state.instance); + + char* memstart = &memoryRef( current_memory, 0 ); + state.init_memory.resize(1<<16); /// TODO: actually get memory size + memcpy( state.init_memory.data(), memstart, state.init_memory.size() ); + std::cerr <<"INIT MEMORY: \n"; + for( uint32_t i = 0; i < 10000; ++i ) + if( memstart[i] ) + std::cerr << (char)memstart[i]; + std::cerr <<"\n"; + state.code_version = recipient.code_version; + } + catch(Serialization::FatalSerializationException exception) + { + std::cerr << "Error deserializing WebAssembly binary file:" << std::endl; + std::cerr << exception.message << std::endl; + throw; + } + catch(IR::ValidationException exception) + { + std::cerr << "Error validating WebAssembly binary file:" << std::endl; + std::cerr << exception.message << std::endl; + throw; + } + catch(std::bad_alloc) + { + std::cerr << "Memory allocation failed: input is likely malformed" << std::endl; + throw; + } + } + + current_module = state.instance; + current_memory = getDefaultMemory( current_module ); + char* memstart = &memoryRef( current_memory, 0 ); + memcpy( memstart, state.init_memory.data(), state.init_memory.size() ); + } +/* void wasm_interface::load(const char* bytes, size_t len) { try { static vector memory_backup; @@ -322,5 +470,6 @@ DEFINE_INTRINSIC_FUNCTION1(env,toUpper,toUpper,none,i32,charptr) { throw; } } FC_CAPTURE_AND_RETHROW() } + */ } } diff --git a/libraries/native_contract/include/eos/native_contract/system_contract.hpp b/libraries/native_contract/include/eos/native_contract/system_contract.hpp index f1084b7db..94cb7b99c 100644 --- a/libraries/native_contract/include/eos/native_contract/system_contract.hpp +++ b/libraries/native_contract/include/eos/native_contract/system_contract.hpp @@ -12,7 +12,7 @@ struct DefineStruct { static void apply(chain::apply_context& context); }; -struct SetMessageHandler { +struct SetCode { static void validate(chain::message_validate_context& context); static void validate_preconditions(chain::precondition_validate_context& context); static void apply(chain::apply_context& context); diff --git a/libraries/native_contract/system_contract.cpp b/libraries/native_contract/system_contract.cpp index 02546b6f8..963374af4 100644 --- a/libraries/native_contract/system_contract.cpp +++ b/libraries/native_contract/system_contract.cpp @@ -1,12 +1,13 @@ #include #include -#include #include #include #include #include +#include + namespace eos { using namespace chain; @@ -40,37 +41,34 @@ void DefineStruct::apply(apply_context& context) { }); } -void SetMessageHandler::validate(message_validate_context& context) { - auto msg = context.msg.as(); +void SetCode::validate(message_validate_context& context) { + auto msg = context.msg.as(); + FC_ASSERT( msg.vmtype == 0 ); + FC_ASSERT( msg.vmversion == 0 ); + // TODO: verify code compiles and is properly sanitized } -void SetMessageHandler::validate_preconditions(precondition_validate_context& context) +void SetCode::validate_preconditions(precondition_validate_context& context) { try { - auto& db = context.db; - auto msg = context.msg.as(); - idump((msg.recipient)(msg.processor)(msg.type)); - // db.get( boost::make_tuple(msg.account, msg.type)) + auto& db = context.db; + auto msg = context.msg.as(); + // db.get( boost::make_tuple(msg.account, msg.type)) - // TODO: verify code compiles } FC_CAPTURE_AND_RETHROW() } -void SetMessageHandler::apply(apply_context& context) { +void SetCode::apply(apply_context& context) { auto& db = context.mutable_db; - auto msg = context.msg.as(); - const auto& processor_acnt = db.get(msg.processor); - const auto& recipient_acnt = db.get(msg.recipient); - db.create( [&](auto& action){ - action.processor = processor_acnt.id; - action.recipient = recipient_acnt.id; - action.type = msg.type; - action.validate_action = msg.validate.c_str(); ///TODO: fix this - action.validate_precondition = msg.precondition.c_str(); ///TODO: fix this - - - action.apply.resize(msg.apply.size()); - memcpy( action.apply.data(), msg.apply.data(), msg.apply.size() ); + auto msg = context.msg.as(); + const auto& account = db.get(msg.account); + wlog( "set code: ${size}", ("size",msg.code.size())); + db.modify( account, [&]( auto& a ) { + a.code_version++; + a.code.resize( msg.code.size() ); + memcpy( a.code.data(), msg.code.data(), msg.code.size() ); }); - idump((msg.apply)); + + apply_context init_context( context.mutable_db, chain::Message(), msg.account ); + wasm_interface::get().init( init_context ); } void CreateAccount::validate(message_validate_context& context) { diff --git a/libraries/types/types.eos b/libraries/types/types.eos index d00cac241..dae3a8d79 100644 --- a/libraries/types/types.eos +++ b/libraries/types/types.eos @@ -76,13 +76,11 @@ struct CreateAccount recovery Authority deposit Asset -struct SetMessageHandler - processor AccountName # the account that is handling the message - recipient AccountName # the account the message was sent to - type TypeName # the type of message being processed (relative to to account) - validate String # the script to validate - precondition String # the pre condition validation - apply Bytes # the apply +struct SetCode + account AccountName # the account that is handling the message + vmtype UInt8 # the virtual machine type + vmversion UInt8 # the virtual machine version + code Bytes # the apply struct CreateProducer name AccountName diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index ba7fa2dbd..53fbce310 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -121,31 +121,28 @@ BOOST_FIXTURE_TEST_CASE(create_script, testing_fixture) { try { Make_Database(db); db.produce_blocks(10); + Make_Account(db, simplecoin); + db.produce_blocks(1); - SignedTransaction trx; - trx.messages.resize(1); - trx.set_reference_block(db.head_block_id()); - trx.expiration = db.head_block_time() + 100; - trx.messages[0].sender = "init1"; - trx.messages[0].recipient = "sys"; - types::SetMessageHandler handler; - handler.processor = "init1"; - handler.recipient = config::EosContractName; - handler.type = "Transfer"; /* auto c_apply = R"( +/// Start the EOS Built In Library HERE +typedef long long uint64_t; +typedef unsigned int uint32_t; + void print( char* string, int length ); void printi( int ); +void printi64( uint64_t ); void assert( int test, char* message ); void store( const char* key, int keylength, const char* value, int valuelen ); int load( const char* key, int keylength, char* value, int maxlen ); +void* memcpy( void* dest, const void* src, uint32_t size ); +int readMessage( char* dest, int destsize ); - - -char* alloc( int size ) { - static char dynamic_memory[1024]; +void* malloc( unsigned int size ) { + static char dynamic_memory[1024*8]; static int start = 0; int old_start = start; start += 8*((size+7)/8); @@ -154,53 +151,171 @@ char* alloc( int size ) { } -char buffer[100]; -void apply(char* message, int length ) { - store( "last", 4, message, length ); - load( "last", 4, buffer, 100 ); - for( int i = 0; i < length; ++i ) { - assert( buffer[i] == message[i], "restore failed"); - } +typedef struct { + uint64_t name[4]; +} AccountName; + +typedef struct { + uint32_t length; + char data[]; +} String; + + +typedef struct { + char* start; + char* pos; + char* end; +} DataStream; + +void DataStream_init( DataStream* ds, char* start, int length ) { + ds->start = start; + ds->end = start + length; + ds->pos = start; +} +void AccountName_initString( AccountName* a, String* s ) { + assert( s->length <= sizeof(AccountName), "String is longer than account name allows" ); + memcpy( a, s->data, s->length ); +} +void AccountName_initCString( AccountName* a, const char* s, uint32_t len ) { + assert( len <= sizeof(AccountName), "String is longer than account name allows" ); + memcpy( a, s, len ); +} + +void AccountName_unpack( DataStream* ds, AccountName* account ); +void uint64_unpack( DataStream* ds, uint64_t* value ) { + assert( ds->pos + sizeof(uint64_t) <= ds->end, "read past end of stream" ); + memcpy( (char*)value, ds->pos, 8 ); + ds->pos += sizeof(uint64_t); +} +void Varint_unpack( DataStream* ds, uint32_t* value ); +void String_unpack( DataStream* ds, String** value ) { + static uint32_t size; + Varint_unpack( ds, &size ); + assert( ds->pos + size <= ds->end, "read past end of stream"); + String* str = (String*)malloc( size + sizeof(String) ); + memcpy( str->data, ds->pos, size ); + *value = str; +} + +/// END BUILT IN LIBRARY.... everything below this is "user contract" + + +typedef struct { + AccountName from; + AccountName to; + uint64_t amount; + String* memo; +} Transfer; + +void Transfer_unpack( DataStream* ds, Transfer* transfer ) +{ + AccountName_unpack( ds, &transfer->from ); + AccountName_unpack( ds, &transfer->to ); + uint64_unpack( ds, &transfer->amount ); + String_unpack( ds, &transfer->memo ); +} + +typedef struct { + uint64_t balance; +} Balance; + +void onInit() { + static Balance initial; + static AccountName simplecoin; + AccountName_initCString( &simplecoin, "simplecoin", 10 ); + initial.balance = 1000*1000; + + store( &simplecoin, sizeof(AccountName), &initial, sizeof(Balance)); +} + + +void onApply_Transfer_simplecoin() { + static char buffer[100]; + + int read = readMessage( buffer, 100 ); + static Transfer message; + static DataStream ds; + DataStream_init( &ds, buffer, read ); + Transfer_unpack( &ds, &message ); + + static Balance from_balance; + static Balance to_balance; + to_balance.balance = 0; + + read = load( (char*)&message.from, sizeof(message.from), (char*)&from_balance.balance, sizeof(from_balance.balance) ); + assert( read == sizeof(Balance), "no existing balance" ); + assert( from_balance.balance >= message.amount, "insufficient funds" ); + read = load( (char*)&message.to, sizeof(message.to), (char*)&from_balance.balance, sizeof(from_balance.balance) ); + + to_balance.balance += message.amount; + from_balance.balance -= message.amount; + + if( from_balance.balance ) + store( (char*)&message.from, sizeof(message.from), (char*)&from_balance.balance, sizeof(from_balance.balance) ); + else + remove( (char*)&message.from, sizeof(message.from) ); + + store( (char*)&message.to, sizeof(message.to), (char*)&to_balance.balance, sizeof(to_balance.balance) ); } )"; */ - std::string wast_apply = +std::string wast_apply = R"( (module (type $FUNCSIG$vii (func (param i32 i32))) (type $FUNCSIG$viiii (func (param i32 i32 i32 i32))) + (type $FUNCSIG$iii (func (param i32 i32) (result i32))) + (type $FUNCSIG$vj (func (param i64))) (type $FUNCSIG$iiiii (func (param i32 i32 i32 i32) (result i32))) + (type $FUNCSIG$i (func (result i32))) + (type $FUNCSIG$iiii (func (param i32 i32 i32) (result i32))) + (import "env" "AccountName_unpack" (func $AccountName_unpack (param i32 i32))) + (import "env" "Varint_unpack" (func $Varint_unpack (param i32 i32))) (import "env" "assert" (func $assert (param i32 i32))) (import "env" "load" (func $load (param i32 i32 i32 i32) (result i32))) + (import "env" "memcpy" (func $memcpy (param i32 i32 i32) (result i32))) + (import "env" "print" (func $print (param i32 i32))) + (import "env" "printi64" (func $printi64 (param i64))) + (import "env" "readMessage" (func $readMessage (param i32 i32) (result i32))) + (import "env" "remove" (func $remove (param i32 i32) (result i32))) (import "env" "store" (func $store (param i32 i32 i32 i32))) (table 0 anyfunc) (memory $0 1) - (data (i32.const 1056) "out of memory\00") - (data (i32.const 1072) "last\00") - (data (i32.const 1200) "restore failed\00") + (data (i32.const 8224) "out of memory\00") + (data (i32.const 8240) "String is longer than account name allows\00") + (data (i32.const 8288) "read past end of stream\00") + (data (i32.const 8368) "simplecoin\00") + (data (i32.const 8384) "onInit simiplecoin\00") + (data (i32.const 8416) "onApply_Transfer_simplecoin\00") + (data (i32.const 8672) "no existing balance\00") + (data (i32.const 8704) "insufficient funds\00") (export "memory" (memory $0)) - (export "alloc" (func $alloc)) - (export "apply" (func $apply)) - (func $alloc (param $0 i32) (result i32) + (export "malloc" (func $malloc)) + (export "DataStream_init" (func $DataStream_init)) + (export "AccountName_initString" (func $AccountName_initString)) + (export "AccountName_initCString" (func $AccountName_initCString)) + (export "uint64_unpack" (func $uint64_unpack)) + (export "String_unpack" (func $String_unpack)) + (export "Transfer_unpack" (func $Transfer_unpack)) + (export "onInit" (func $onInit)) + (export "onApply_Transfer_simplecoin" (func $onApply_Transfer_simplecoin)) + (func $malloc (param $0 i32) (result i32) (local $1 i32) - (i32.store offset=1040 + (i32.store offset=8208 (i32.const 0) (tee_local $0 (i32.add (tee_local $1 - (i32.load offset=1040 + (i32.load offset=8208 (i32.const 0) ) ) - (i32.shl - (i32.div_s - (i32.add - (get_local $0) - (i32.const 7) - ) - (i32.const 8) + (i32.and + (i32.add + (get_local $0) + (i32.const 7) ) - (i32.const 3) + (i32.const -8) ) ) ) @@ -208,117 +323,620 @@ R"( (call $assert (i32.lt_u (get_local $0) - (i32.const 1024) + (i32.const 8192) ) - (i32.const 1056) + (i32.const 8224) ) (i32.add (get_local $1) (i32.const 16) ) ) - (func $apply (param $0 i32) (param $1 i32) - (local $2 i32) - (call $store - (i32.const 1072) - (i32.const 4) + (func $DataStream_init (param $0 i32) (param $1 i32) (param $2 i32) + (i32.store (get_local $0) (get_local $1) ) - (drop - (call $load - (i32.const 1072) - (i32.const 4) - (i32.const 1088) - (i32.const 100) + (i32.store offset=4 + (get_local $0) + (get_local $1) + ) + (i32.store offset=8 + (get_local $0) + (i32.add + (get_local $1) + (get_local $2) ) ) - (block $label$0 - (br_if $label$0 - (i32.lt_s + ) + (func $AccountName_initString (param $0 i32) (param $1 i32) + (call $assert + (i32.lt_u + (i32.load (get_local $1) - (i32.const 1) ) + (i32.const 33) ) - (set_local $2 - (i32.const 0) + (i32.const 8240) + ) + (drop + (call $memcpy + (get_local $0) + (i32.add + (get_local $1) + (i32.const 4) + ) + (i32.load + (get_local $1) + ) ) - (loop $label$1 - (call $assert - (i32.eq - (i32.load8_u - (i32.add - (get_local $2) - (i32.const 1088) + ) + ) + (func $AccountName_initCString (param $0 i32) (param $1 i32) (param $2 i32) + (call $assert + (i32.lt_u + (get_local $2) + (i32.const 33) + ) + (i32.const 8240) + ) + (drop + (call $memcpy + (get_local $0) + (get_local $1) + (get_local $2) + ) + ) + ) + (func $uint64_unpack (param $0 i32) (param $1 i32) + (call $assert + (i32.le_u + (i32.add + (i32.load offset=4 + (get_local $0) + ) + (i32.const 8) + ) + (i32.load offset=8 + (get_local $0) + ) + ) + (i32.const 8288) + ) + (i64.store align=1 + (get_local $1) + (i64.load align=1 + (i32.load offset=4 + (get_local $0) + ) + ) + ) + (i32.store offset=4 + (get_local $0) + (i32.add + (i32.load offset=4 + (get_local $0) + ) + (i32.const 8) + ) + ) + ) + (func $String_unpack (param $0 i32) (param $1 i32) + (local $2 i32) + (local $3 i32) + (call $Varint_unpack + (get_local $0) + (i32.const 8312) + ) + (call $assert + (i32.le_u + (i32.add + (i32.load offset=4 + (get_local $0) + ) + (i32.load offset=8312 + (i32.const 0) + ) + ) + (i32.load offset=8 + (get_local $0) + ) + ) + (i32.const 8288) + ) + (i32.store offset=8208 + (i32.const 0) + (tee_local $3 + (i32.add + (i32.and + (i32.add + (i32.load offset=8312 + (i32.const 0) ) + (i32.const 11) + ) + (i32.const -8) + ) + (tee_local $2 + (i32.load offset=8208 + (i32.const 0) ) - (i32.load8_u - (i32.add - (get_local $0) - (get_local $2) + ) + ) + ) + ) + (call $assert + (i32.lt_u + (get_local $3) + (i32.const 8192) + ) + (i32.const 8224) + ) + (drop + (call $memcpy + (i32.add + (get_local $2) + (i32.const 20) + ) + (i32.load offset=4 + (get_local $0) + ) + (i32.load offset=8312 + (i32.const 0) + ) + ) + ) + (i32.store + (get_local $1) + (i32.add + (get_local $2) + (i32.const 16) + ) + ) + ) + (func $Transfer_unpack (param $0 i32) (param $1 i32) + (local $2 i32) + (local $3 i32) + (call $AccountName_unpack + (get_local $0) + (get_local $1) + ) + (call $AccountName_unpack + (get_local $0) + (i32.add + (get_local $1) + (i32.const 32) + ) + ) + (call $assert + (i32.le_u + (i32.add + (i32.load offset=4 + (get_local $0) + ) + (i32.const 8) + ) + (i32.load offset=8 + (get_local $0) + ) + ) + (i32.const 8288) + ) + (i64.store offset=64 align=1 + (get_local $1) + (i64.load align=1 + (i32.load offset=4 + (get_local $0) + ) + ) + ) + (i32.store offset=4 + (get_local $0) + (i32.add + (i32.load offset=4 + (get_local $0) + ) + (i32.const 8) + ) + ) + (call $Varint_unpack + (get_local $0) + (i32.const 8312) + ) + (call $assert + (i32.le_u + (i32.add + (i32.load offset=4 + (get_local $0) + ) + (i32.load offset=8312 + (i32.const 0) + ) + ) + (i32.load offset=8 + (get_local $0) + ) + ) + (i32.const 8288) + ) + (i32.store offset=8208 + (i32.const 0) + (tee_local $3 + (i32.add + (i32.and + (i32.add + (i32.load offset=8312 + (i32.const 0) ) + (i32.const 11) + ) + (i32.const -8) + ) + (tee_local $2 + (i32.load offset=8208 + (i32.const 0) ) ) - (i32.const 1200) ) - (br_if $label$1 - (i32.ne - (get_local $1) - (tee_local $2 - (i32.add - (get_local $2) - (i32.const 1) + ) + ) + (call $assert + (i32.lt_u + (get_local $3) + (i32.const 8192) + ) + (i32.const 8224) + ) + (drop + (call $memcpy + (i32.add + (get_local $2) + (i32.const 20) + ) + (i32.load offset=4 + (get_local $0) + ) + (i32.load offset=8312 + (i32.const 0) + ) + ) + ) + (i32.store offset=72 + (get_local $1) + (i32.add + (get_local $2) + (i32.const 16) + ) + ) + ) + (func $onInit + (call $assert + (i32.const 1) + (i32.const 8240) + ) + (i64.store offset=8320 + (i32.const 0) + (i64.const 1000000) + ) + (i32.store16 offset=8336 + (i32.const 0) + (i32.load16_u offset=8376 align=1 + (i32.const 0) + ) + ) + (i64.store offset=8328 + (i32.const 0) + (i64.load offset=8368 align=1 + (i32.const 0) + ) + ) + (call $store + (i32.const 8328) + (i32.const 32) + (i32.const 8320) + (i32.const 8) + ) + (call $print + (i32.const 8384) + (i32.const 15) + ) + ) + (func $onApply_Transfer_simplecoin + (local $0 i32) + (local $1 i32) + (local $2 i64) + (call $print + (i32.const 8416) + (i32.const 20) + ) + (set_local $0 + (call $readMessage + (i32.const 8448) + (i32.const 100) + ) + ) + (i32.store offset=8632 + (i32.const 0) + (i32.const 8448) + ) + (i32.store offset=8636 + (i32.const 0) + (i32.const 8448) + ) + (i32.store offset=8640 + (i32.const 0) + (i32.add + (get_local $0) + (i32.const 8448) + ) + ) + (call $AccountName_unpack + (i32.const 8632) + (i32.const 8552) + ) + (call $AccountName_unpack + (i32.const 8632) + (i32.const 8584) + ) + (call $assert + (i32.le_u + (i32.add + (i32.load offset=8636 + (i32.const 0) + ) + (i32.const 8) + ) + (i32.load offset=8640 + (i32.const 0) + ) + ) + (i32.const 8288) + ) + (i64.store offset=8616 + (i32.const 0) + (i64.load align=1 + (tee_local $0 + (i32.load offset=8636 + (i32.const 0) + ) + ) + ) + ) + (i32.store offset=8636 + (i32.const 0) + (i32.add + (get_local $0) + (i32.const 8) + ) + ) + (call $Varint_unpack + (i32.const 8632) + (i32.const 8312) + ) + (call $assert + (i32.le_u + (i32.add + (i32.load offset=8636 + (i32.const 0) + ) + (i32.load offset=8312 + (i32.const 0) + ) + ) + (i32.load offset=8640 + (i32.const 0) + ) + ) + (i32.const 8288) + ) + (i32.store offset=8208 + (i32.const 0) + (tee_local $1 + (i32.add + (i32.and + (i32.add + (i32.load offset=8312 + (i32.const 0) ) + (i32.const 11) + ) + (i32.const -8) + ) + (tee_local $0 + (i32.load offset=8208 + (i32.const 0) ) ) ) ) ) + (call $assert + (i32.lt_u + (get_local $1) + (i32.const 8192) + ) + (i32.const 8224) + ) + (drop + (call $memcpy + (i32.add + (get_local $0) + (i32.const 20) + ) + (i32.load offset=8636 + (i32.const 0) + ) + (i32.load offset=8312 + (i32.const 0) + ) + ) + ) + (i32.store offset=8624 + (i32.const 0) + (i32.add + (get_local $0) + (i32.const 16) + ) + ) + (call $print + (i32.const 8552) + (i32.const 32) + ) + (call $print + (i32.const 8584) + (i32.const 32) + ) + (call $printi64 + (i64.load offset=8616 + (i32.const 0) + ) + ) + (i64.store offset=8656 + (i32.const 0) + (i64.const 0) + ) + (call $assert + (i32.eq + (call $load + (i32.const 8552) + (i32.const 32) + (i32.const 8648) + (i32.const 8) + ) + (i32.const 8) + ) + (i32.const 8672) + ) + (call $assert + (i64.ge_s + (i64.load offset=8648 + (i32.const 0) + ) + (i64.load offset=8616 + (i32.const 0) + ) + ) + (i32.const 8704) + ) + (drop + (call $load + (i32.const 8584) + (i32.const 32) + (i32.const 8648) + (i32.const 8) + ) + ) + (i64.store offset=8656 + (i32.const 0) + (i64.add + (i64.load offset=8656 + (i32.const 0) + ) + (tee_local $2 + (i64.load offset=8616 + (i32.const 0) + ) + ) + ) + ) + (i64.store offset=8648 + (i32.const 0) + (tee_local $2 + (i64.sub + (i64.load offset=8648 + (i32.const 0) + ) + (get_local $2) + ) + ) + ) + (block $label$0 + (block $label$1 + (br_if $label$1 + (i64.eqz + (get_local $2) + ) + ) + (call $store + (i32.const 8552) + (i32.const 32) + (i32.const 8648) + (i32.const 8) + ) + (br $label$0) + ) + (drop + (call $remove + (i32.const 8552) + (i32.const 32) + ) + ) + ) + (call $store + (i32.const 8584) + (i32.const 32) + (i32.const 8656) + (i32.const 8) + ) ) ) - - )"; - + types::SetCode handler; + handler.account = "simplecoin"; + types::DefineStruct interface; + interface.scope = "simplecoin"; + interface.definition = types::GetStruct::type(); auto wasm = assemble_wast( wast_apply ); - handler.apply.resize(wasm.size()); - memcpy( handler.apply.data(), wasm.data(), wasm.size() ); - - edump((handler.apply.size())); - edump((handler.apply)); - - /* - handler.apply = R"( - System.print( "Loading Handler" ) - class Handler { - static apply( context, msg ) { - System.print( "On Apply Transfer to init1" ) - System.print( context ) - context.set( "hello", "world" ) - System.print( "set it, now get it" ) - System.print( context.get("hello") ) - System.print( "got it" ) - } - } - )"; - */ + handler.code.resize(wasm.size()); + memcpy( handler.code.data(), wasm.data(), wasm.size() ); - trx.setMessage(0, "SetMessageHandler", handler); + { + eos::chain::SignedTransaction trx; + trx.messages.resize(2); + trx.messages[0].sender = "simplecoin"; + trx.messages[0].recipient = "sys"; + trx.setMessage(0, "DefineStruct",interface); + trx.messages[1].sender = "simplecoin"; + trx.messages[1].recipient = "sys"; + trx.setMessage(1, "SetCode", handler); + trx.expiration = db.head_block_time() + 100; + trx.set_reference_block(db.head_block_id()); + db.push_transaction(trx); + db.produce_blocks(1); + } - db.push_transaction(trx); - db.produce_blocks(1); - Transfer_Asset(db, init3, init1, Asset(1), "transfer 100"); + { + eos::chain::SignedTransaction trx; + trx.emplaceMessage("simplecoin", "simplecoin", vector{"init1"}, "Transfer", + types::Transfer{"simplecoin", "init1", 101, "hello"} ); + trx.expiration = db.head_block_time() + 100; + trx.set_reference_block(db.head_block_id()); + db.push_transaction(trx); + } + { + wlog( "transfer 102 from init1 to init2" ); + eos::chain::SignedTransaction trx; + trx.emplaceMessage("init1", "simplecoin", vector{"init2"}, "Transfer", + types::Transfer{"init1", "init2", 102, "hello again"} ); + trx.expiration = db.head_block_time() + 100; + trx.set_reference_block(db.head_block_id()); + db.push_transaction(trx); + } + + +/* auto start = fc::time_point::now(); for( uint32_t i = 0; i < 200; ++i ) { Transfer_Asset(db, init3, init1, Asset(2+i), "transfer 100"); } auto end = fc::time_point::now(); idump(( 200*1000000.0 / (end-start).count() ) ); +*/ db.produce_blocks(1); /* -- GitLab