diff --git a/contracts/eosiolib/transaction.hpp b/contracts/eosiolib/transaction.hpp index d1bb1a9a35fb74db4f7d0cd790ee2f2bc0356773..5dc0e2cf1a6f4e7fb0c43d29498fffa4ec70fa96 100644 --- a/contracts/eosiolib/transaction.hpp +++ b/contracts/eosiolib/transaction.hpp @@ -36,11 +36,14 @@ namespace eosio { time expiration; region_id region; uint16_t ref_block_num; - uint32_t ref_block_id; + uint32_t ref_block_prefix; + uint16_t packed_bandwidth_words = 0; /// number of 8 byte words this transaction can compress into + uint16_t context_free_cpu_bandwidth = 0; /// number of CPU usage units to bill transaction for + vector context_free_actions; vector actions; - EOSLIB_SERIALIZE( transaction, (expiration)(region)(ref_block_num)(ref_block_id)(actions) ); + EOSLIB_SERIALIZE( transaction, (expiration)(region)(ref_block_num)(ref_block_prefix)(packed_bandwidth_words)(context_free_cpu_bandwidth)(context_free_actions)(actions) ); }; class deferred_transaction : public transaction { diff --git a/libraries/chain/apply_context.cpp b/libraries/chain/apply_context.cpp index 149e7939cf8d7bdeeda1a36f567961d3defba0fd..322747dd93feab3177f70002289e76cb6589d9db 100644 --- a/libraries/chain/apply_context.cpp +++ b/libraries/chain/apply_context.cpp @@ -304,6 +304,45 @@ void apply_context::update_db_usage( const account_name& payer, int64_t delta ) } +int apply_context::get_action( uint32_t type, uint32_t index, char* buffer, size_t buffer_size )const +{ + const transaction& trx = trx_meta.trx(); + const action* act = nullptr; + if( type == 0 ) { + if( index >= trx.context_free_actions.size() ) + return -1; + act = &trx.context_free_actions[index]; + } + else if( type == 1 ) { + if( index >= trx.actions.size() ) + return -1; + act = &trx.actions[index]; + } + + auto ps = fc::raw::pack_size( *act ); + if( ps <= buffer_size ) { + fc::datastream ds(buffer, buffer_size); + fc::raw::pack( ds, *act ); + } + return ps; +} + +int apply_context::get_context_free_data( uint32_t index, char* buffer, size_t buffer_size )const { + if( index >= trx_meta.context_free_data.size() ) return -1; + + auto s = trx_meta.context_free_data[index].size(); + + if( buffer_size == 0 ) return s; + + if( buffer_size < s ) + memcpy( buffer, trx_meta.context_free_data.data(), buffer_size ); + else + memcpy( buffer, trx_meta.context_free_data.data(), s ); + + return s; +} + + int apply_context::db_store_i64( uint64_t scope, uint64_t table, const account_name& payer, uint64_t id, const char* buffer, size_t buffer_size ) { require_write_lock( scope ); const auto& tab = find_or_create_table( receiver, scope, table ); diff --git a/libraries/chain/chain_controller.cpp b/libraries/chain/chain_controller.cpp index b70a606e14606a0c09496cb3c5c5eac8a09359ba..b66a203e56c9724c00950fcbd82ca5576e0cc0bd 100644 --- a/libraries/chain/chain_controller.cpp +++ b/libraries/chain/chain_controller.cpp @@ -1406,9 +1406,23 @@ static void log_handled_exceptions(const transaction& trx) { transaction_trace chain_controller::__apply_transaction( transaction_metadata& meta ) { transaction_trace result(meta.id); + + for (const auto &act : meta.trx().context_free_actions) { + FC_ASSERT( act.authorization.size() == 0, "context free actions cannot require authorization" ); + apply_context context(*this, _db, act, meta); + context.context_free = true; + context.exec(); + fc::move_append(result.action_traces, std::move(context.results.applied_actions)); + FC_ASSERT( result.deferred_transactions.size() == 0 ); + FC_ASSERT( result.canceled_deferred.size() == 0 ); + } + for (const auto &act : meta.trx().actions) { apply_context context(*this, _db, act, meta); context.exec(); + context.used_context_free_api |= act.authorization.size(); + + FC_ASSERT( context.used_context_free_api, "action did not reference database state, it should be moved to context_free_actions", ("act",act) ); fc::move_append(result.action_traces, std::move(context.results.applied_actions)); fc::move_append(result.deferred_transactions, std::move(context.results.generated_transactions)); fc::move_append(result.canceled_deferred, std::move(context.results.canceled_deferred)); diff --git a/libraries/chain/include/eosio/chain/apply_context.hpp b/libraries/chain/include/eosio/chain/apply_context.hpp index 51724a4fc800ac998e066e949933a3d7b8809db8..bcf40ca4bba611637e02767e62e4a55b4cd98496 100644 --- a/libraries/chain/include/eosio/chain/apply_context.hpp +++ b/libraries/chain/include/eosio/chain/apply_context.hpp @@ -389,7 +389,9 @@ class apply_context { const chainbase::database& db; ///< database where state is stored const action& act; ///< message being applied account_name receiver; ///< the code that is currently running - bool privileged = false; + bool privileged = false; + bool context_free = false; + bool used_context_free_api = false; chain_controller& mutable_controller; chainbase::database& mutable_db; @@ -425,6 +427,9 @@ class apply_context { void checktime(uint32_t instruction_count) const; + 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 ); 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 ); diff --git a/libraries/chain/include/eosio/chain/transaction.hpp b/libraries/chain/include/eosio/chain/transaction.hpp index ec53089e2804812a05a5faf85a764097f80feb89..5e89338e7fa49eec9ff87a91976c1100583968ba 100644 --- a/libraries/chain/include/eosio/chain/transaction.hpp +++ b/libraries/chain/include/eosio/chain/transaction.hpp @@ -115,6 +115,8 @@ namespace eosio { namespace chain { uint16_t region = 0; ///< the computational memory region this transaction applies to. uint16_t ref_block_num = 0; ///< specifies a block num in the last 2^16 blocks. uint32_t ref_block_prefix = 0; ///< specifies the lower 32 bits of the blockid at get_ref_blocknum + uint16_t packed_bandwidth_words = 0; /// number of 8 byte words this transaction can compress into + uint16_t context_free_cpu_bandwidth = 0; /// number of CPU usage units to bill transaction for /** * @return the absolute block number given the relative ref_block_num @@ -132,6 +134,7 @@ namespace eosio { namespace chain { * read and write scopes. */ struct transaction : public transaction_header { + vector context_free_actions; vector actions; transaction_id_type id()const; @@ -152,6 +155,7 @@ namespace eosio { namespace chain { } vector signatures; + vector> context_free_data; ///< for each context-free action, there is an entry here const signature_type& sign(const private_key_type& key, const chain_id_type& chain_id); signature_type sign(const private_key_type& key, const chain_id_type& chain_id)const; @@ -252,9 +256,9 @@ namespace eosio { namespace chain { FC_REFLECT( eosio::chain::permission_level, (actor)(permission) ) FC_REFLECT( eosio::chain::action, (account)(name)(authorization)(data) ) -FC_REFLECT( eosio::chain::transaction_header, (expiration)(region)(ref_block_num)(ref_block_prefix) ) -FC_REFLECT_DERIVED( eosio::chain::transaction, (eosio::chain::transaction_header), (actions) ) -FC_REFLECT_DERIVED( eosio::chain::signed_transaction, (eosio::chain::transaction), (signatures) ) +FC_REFLECT( eosio::chain::transaction_header, (expiration)(region)(ref_block_num)(ref_block_prefix)(packed_bandwidth_words)(context_free_cpu_bandwidth) ) +FC_REFLECT_DERIVED( eosio::chain::transaction, (eosio::chain::transaction_header), (context_free_actions)(actions) ) +FC_REFLECT_DERIVED( eosio::chain::signed_transaction, (eosio::chain::transaction), (signatures)(context_free_data) ) FC_REFLECT_ENUM( eosio::chain::packed_transaction::compression_type, (none)(zlib)) FC_REFLECT( eosio::chain::packed_transaction, (signatures)(compression)(data) ) FC_REFLECT_DERIVED( eosio::chain::deferred_transaction, (eosio::chain::transaction), (sender_id)(sender)(execute_after) ) diff --git a/libraries/chain/include/eosio/chain/transaction_metadata.hpp b/libraries/chain/include/eosio/chain/transaction_metadata.hpp index cb8bc17033bd25dc8f11cb185aa16bf102af49d7..5a8725e35ec8a91741ddc0a7072764c809bf9bbc 100644 --- a/libraries/chain/include/eosio/chain/transaction_metadata.hpp +++ b/libraries/chain/include/eosio/chain/transaction_metadata.hpp @@ -27,6 +27,7 @@ class transaction_metadata { // things for packed_transaction optional raw_trx; optional decompressed_trx; + vector> context_free_data; // things for signed/packed transactions optional> signing_keys; diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 54c472ae30c4688f5662fc357c69a468dead9c9b..cc887b4bfbcd4d3f9ed68990239174f3bfe3afaf 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -408,10 +408,15 @@ namespace eosio { namespace chain { class context_aware_api { public: - context_aware_api(wasm_interface& wasm) - :context(intrinsics_accessor::get_context(wasm).context), code(intrinsics_accessor::get_context(wasm).code), - sbrk_bytes(intrinsics_accessor::get_context(wasm).sbrk_bytes) - {} + context_aware_api(wasm_interface& wasm, bool context_free = false ) + :sbrk_bytes(intrinsics_accessor::get_context(wasm).sbrk_bytes), + code(intrinsics_accessor::get_context(wasm).code), + context(intrinsics_accessor::get_context(wasm).context) + { + if( context.context_free ) + FC_ASSERT( context_free, "only context free api's can be used in this context" ); + context.used_context_free_api |= !context_free; + } protected: uint32_t& sbrk_bytes; @@ -419,6 +424,18 @@ class context_aware_api { apply_context& context; }; +class context_free_api : public context_aware_api { + public: + context_free_api( wasm_interface& wasm ) + :context_aware_api(wasm, true) { + /* the context_free_data is not available during normal application because it is prunable */ + FC_ASSERT( context.context_free, "this API may only be called from context_free apply" ); + } + + int get_context_free_data( uint32_t index, array_ptr buffer, size_t buffer_size )const { + return context.get_context_free_data( index, buffer, buffer_size ); + } +}; class privileged_api : public context_aware_api { public: privileged_api( wasm_interface& wasm ) @@ -909,7 +926,8 @@ class db_index_api : public context_aware_api { class memory_api : public context_aware_api { public: - using context_aware_api::context_aware_api; + memory_api( wasm_interface& wasm ) + :context_aware_api(wasm,true){} char* memcpy( array_ptr dest, array_ptr src, size_t length) { return (char *)::memcpy(dest, src, length); @@ -966,6 +984,36 @@ class transaction_api : public context_aware_api { public: using context_aware_api::context_aware_api; + void send_inline( array_ptr data, size_t data_len ) { + // TODO: use global properties object for dynamic configuration of this default_max_gen_trx_size + FC_ASSERT( data_len < config::default_max_inline_action_size, "inline action too big" ); + + action act; + fc::raw::unpack(data, data_len, act); + context.execute_inline(std::move(act)); + } + + void send_deferred( uint32_t sender_id, const fc::time_point_sec& execute_after, array_ptr data, size_t data_len ) { + try { + // TODO: use global properties object for dynamic configuration of this default_max_gen_trx_size + FC_ASSERT(data_len < config::default_max_gen_trx_size, "generated transaction too big"); + + deferred_transaction dtrx; + fc::raw::unpack(data, data_len, dtrx); + dtrx.sender = context.receiver; + dtrx.sender_id = sender_id; + dtrx.execute_after = execute_after; + context.execute_deferred(std::move(dtrx)); + } FC_CAPTURE_AND_RETHROW((fc::to_hex(data, data_len))); + } +}; + + +class context_free_transaction_api : public context_aware_api { + public: + context_free_transaction_api( wasm_interface& wasm ) + :context_aware_api(wasm,true){} + int read_transaction( array_ptr data, size_t data_len ) { bytes trx = context.get_packed_transaction(); if (data_len >= trx.size()) { @@ -989,28 +1037,8 @@ class transaction_api : public context_aware_api { return context.trx_meta.trx().ref_block_prefix; } - void send_inline( array_ptr data, size_t data_len ) { - // TODO: use global properties object for dynamic configuration of this default_max_gen_trx_size - FC_ASSERT( data_len < config::default_max_inline_action_size, "inline action too big" ); - - action act; - fc::raw::unpack(data, data_len, act); - context.execute_inline(std::move(act)); - } - - - void send_deferred( uint32_t sender_id, const fc::time_point_sec& execute_after, array_ptr data, size_t data_len ) { - try { - // TODO: use global properties object for dynamic configuration of this default_max_gen_trx_size - FC_ASSERT(data_len < config::default_max_gen_trx_size, "generated transaction too big"); - - deferred_transaction dtrx; - fc::raw::unpack(data, data_len, dtrx); - dtrx.sender = context.receiver; - dtrx.sender_id = sender_id; - dtrx.execute_after = execute_after; - context.execute_deferred(std::move(dtrx)); - } FC_CAPTURE_AND_RETHROW((fc::to_hex(data, data_len))); + int get_action( uint32_t type, uint32_t index, array_ptr buffer, size_t buffer_size )const { + return context.get_action( type, index, buffer, buffer_size ); } }; @@ -1113,16 +1141,24 @@ REGISTER_INTRINSICS(console_api, (printhex, void(int, int) ) ); -REGISTER_INTRINSICS(transaction_api, +REGISTER_INTRINSICS(context_free_transaction_api, (read_transaction, int(int, int) ) (transaction_size, int() ) (expiration, int() ) (tapos_block_prefix, int() ) (tapos_block_num, int() ) + (get_action, int (int, int, int, int) ) +); + +REGISTER_INTRINSICS(transaction_api, (send_inline, void(int, int) ) (send_deferred, void(int, int, int, int) ) ); +REGISTER_INTRINSICS(context_free_api, + (get_context_free_data, int(int, int, int) ) +) + REGISTER_INTRINSICS(memory_api, (memcpy, int(int, int, int) ) (memmove, int(int, int, int) )