diff --git a/contracts/eoslib/transaction.h b/contracts/eoslib/transaction.h index 91666f3b071ae7c8c5ee0b77c6e888b932e661a7..fb531e75f927204b979ff4625a29ca85828af034 100644 --- a/contracts/eoslib/transaction.h +++ b/contracts/eoslib/transaction.h @@ -58,6 +58,8 @@ extern "C" { typedef uint32_t TransactionHandle; #define InvalidTransactionHandle (0xFFFFFFFFUL) + #define SendInline (1) + #define SendDeferred (0) /** * @brief create a pending transaction @@ -103,9 +105,10 @@ extern "C" { * This function adds a @ref PermissionName to the pending message * * @param trx - the `TransactionHandle` of the pending transaction to modify + * @param account - the `AccountName` to add * @param permission - the `PermissionName` to add */ - void transactionAddMessagePermission(TransactionHandle trx, PermissionName permission); + void transactionAddMessagePermission(TransactionHandle trx, AccountName account, PermissionName permission); /** @@ -147,7 +150,7 @@ extern "C" { * @param trx - the `TransactionHandle` of the pending transaction to send * @param inlineMode - whether to send as an inline transaction (!=0) or deferred(=0) */ - void transactionSend(TransactionHandle trx, int inlineMode = 0); + void transactionSend(TransactionHandle trx, int mode = SendDeferred); /** * @brief drop a pending transaction diff --git a/libraries/chain/include/eos/chain/exceptions.hpp b/libraries/chain/include/eos/chain/exceptions.hpp index 0417830830f464aa046f148e42b68ebe73024730..3dac4c4f013b1ecfe7d0c0b564533757727004ee 100644 --- a/libraries/chain/include/eos/chain/exceptions.hpp +++ b/libraries/chain/include/eos/chain/exceptions.hpp @@ -57,6 +57,8 @@ namespace eos { namespace chain { FC_DECLARE_DERIVED_EXCEPTION( tx_duplicate, eos::chain::transaction_exception, 3030011, "duplicate transaction" ) FC_DECLARE_DERIVED_EXCEPTION( unknown_transaction_exception, eos::chain::transaction_exception, 3030012, "unknown transaction" ) FC_DECLARE_DERIVED_EXCEPTION( tx_scheduling_exception, eos::chain::transaction_exception, 3030013, "transaction failed during sheduling" ) + FC_DECLARE_DERIVED_EXCEPTION( tx_unknown_argument, eos::chain::transaction_exception, 3030014, "transaction provided an unknown value to a system call" ) + FC_DECLARE_DERIVED_EXCEPTION( tx_resource_exhausted, eos::chain::transaction_exception, 3030015, "transaction exhausted allowed resources" ) FC_DECLARE_DERIVED_EXCEPTION( invalid_pts_address, eos::chain::utility_exception, 3060001, "invalid pts address" ) FC_DECLARE_DERIVED_EXCEPTION( insufficient_feeds, eos::chain::chain_exception, 37006, "insufficient feeds" ) diff --git a/libraries/chain/include/eos/chain/message_handling_contexts.hpp b/libraries/chain/include/eos/chain/message_handling_contexts.hpp index bdb54958c7d239caac2c5bf9bcc46545d1184158..10b1c40597b4ad4b26e9aa5d2f1e4686ba5c7d72 100644 --- a/libraries/chain/include/eos/chain/message_handling_contexts.hpp +++ b/libraries/chain/include/eos/chain/message_handling_contexts.hpp @@ -19,7 +19,8 @@ public: const chain::Message& m, const types::AccountName& code) : controller(con), db(db), trx(t), msg(m), code(code), mutable_controller(con), - mutable_db(db), used_authorizations(msg.authorization.size(), false){} + mutable_db(db), used_authorizations(msg.authorization.size(), false), + next_pending_transaction_serial(0){} template int32_t store_record( Name scope, Name code, Name table, typename ObjectType::key_type* keys, char* value, uint32_t valuelen ) { @@ -286,11 +287,49 @@ public: chainbase::database& mutable_db; std::deque notified; - std::deque sync_transactions; ///< sync calls made - std::deque async_transactions; ///< async calls requested + std::deque inline_transactions; ///< queued inline txs + std::deque deferred_transactions; ///< deferred txs ///< Parallel to msg.authorization; tracks which permissions have been used while processing the message vector used_authorizations; + + ///< pending transaction construction + typedef uint32_t pending_transaction_handle; + struct pending_transaction { + typedef uint32_t handle_type; + static const handle_type Invalid_handle = 0xFFFFFFFFUL; + + handle_type handle; + struct message_dest { + AccountName code; + FuncName type; + }; + + // state set that applies to pushed message data + optional current_destination; + vector current_permissions; + + // state to apply when the transaction is pushed + vector scopes; + vector read_scopes; + vector messages; + + types::Transaction as_transaction() const; + void check_size() const; + + void reset_message() { + current_destination = decltype(current_destination)(); + current_permissions.clear(); + } + }; + + pending_transaction::handle_type next_pending_transaction_serial; + vector pending_transactions; + + pending_transaction& get_pending_transaction(pending_transaction::handle_type handle); + pending_transaction& create_pending_transaction(); + void release_pending_transaction(pending_transaction::handle_type handle); + }; using apply_handler = std::function; diff --git a/libraries/chain/message_handling_contexts.cpp b/libraries/chain/message_handling_contexts.cpp index 353044f65cf3cf1aea3c3a88082502c02a120267..211f2661480e0b0300654ff280cd4f3e62310721 100644 --- a/libraries/chain/message_handling_contexts.cpp +++ b/libraries/chain/message_handling_contexts.cpp @@ -51,4 +51,49 @@ vector apply_context::unused_authorizations() const { return {range.begin(), range.end()}; } +pending_transaction& apply_context::get_pending_transaction(pending_transaction::handle_type handle) { + auto itr = boost::find_if(pending_transactions, [&](const auto& trx) { return trx.handle == handle; }); + EOS_ASSERT(itr != pending_transactions.end(), tx_unknown_argument, + "Transaction refers to non-existant/destroyed pending transaction"); + return *itr; +} + +const int Max_pending_transactions = 4; +const uint32_t Max_pending_transaction_size = 16 * 1024; +const auto Pending_transaction_expiration = fc::seconds(21 * 3); + +pending_transaction& apply_context::create_pending_transaction() { + EOS_ASSERT(pending_transactions.size() < Max_pending_transactions, tx_resource_exhausted, + "Transaction is attempting to create too many pending transactions. The max is ${max}", ("max", Max_pending_transactions));) + + pending_transaction::handle handle = next_pending_transaction_serial++; + pending_transactions.push_back({handle}); + return pending_transaction.back(); +} + +void apply_context::release_pending_transaction(pending_transaction::handle_type handle) { + auto itr = boost::find_if(pending_transactions, [&](const auto& trx) { return trx.handle == handle; }); + EOS_ASSERT(itr != pending_transactions.end(), tx_unknown_argument, + "Transaction refers to non-existant/destroyed pending transaction"); + + auto last = pending_transactions.end() - 1; + if (itr != last) { + std::swap(itr, last); + } + pending_transactions.pop_back(); +} + +types::Transaction apply_context::pending_transaction::as_transaction() const { + decltype(types::Transaction::refBlockNum) head_block_num = chain_controller.head_block_num(); + decltype(types::Transaction::refBlockRef) head_block_ref = fc::endian_reverse_u32(chain_controller.head_block_ref()._hash[0]); + decltype(types::Transaction::expiration) expiration = chain_controller.head_block_time() + Pending_transaction_expiration; + return types::Transaction(head_block_num, head_block_ref, expiration, scopes, read_scopes, messages); +} + +void apply_context::pending_transaction::check_size() const { + auto trx = as_transaction(); + EOS_ASSERT(fc::raw::pack_size(trx) <= Max_pending_transaction_size, tx_resource_exhausted, + "Transaction is attempting to create a transaction which is too large. The max size is ${max} bytes", ("max", Max_pending_transaction_size)); +} + } } // namespace eos::chain diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 7886d70b3c5ea5b46359ffff8bb3612cae1090ec..41a4030c0216d001e09f73a01381c087aa867541 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -241,39 +241,98 @@ DEFINE_INTRINSIC_FUNCTION3(env,memcpy,memcpy,i32,i32,dstp,i32,srcp,i32,len) { } -DEFINE_INTRINSIC_FUNCTION2(env,send,send,i32,i32,trx_buffer, i32,trx_buffer_size ) { +/** + * Transaction C API implementation + * @{ + */ + +static const uint32_t InvalidTransactionHandle = 0xFFFFFFFFUL; + +DEFINE_INTRINSIC_FUNCTION0(env,transactionCreate,transactionCreate,i32) { + auto& ptrx = wasm_interface::get().current_apply_context->create_pending_transaction(); + return ptrx.handle; +} + +DEFINE_INTRINSIC_FUNCTION3(env,transactionAddScope,transactionAddScope,none,i32,handle,i64,scope,i32,readOnly) { + auto& ptrx = wasm_interface::get().current_apply_context->get_pending_transaction(handle); + if(readOnly == 0) { + ptrx.scopes.emplace_back(scope); + } else { + ptrx.read_scopes.emplace_back(scope); + } + + ptrx.check_size(); +} + +DEFINE_INTRINSIC_FUNCTION3(env,transactionSetMessageDestination,transactionSetMessageDestination,none,i32,handle,i64,code,i64,type) { + auto& ptrx = wasm_interface::get().current_apply_context->get_pending_transaction(handle); + ptrx.current_destination = decltype(ptrx.current_destination)({Name(code), Name(type)}); +} + +DEFINE_INTRINSIC_FUNCTION3(env,transactionAddMessagePermission,transactionAddMessagePermission,none,i32,handle,i64,account,i64,permission) { + wasm_interface::get().current_apply_context->require_authorization(Name(account), Name(permission)); + auto& ptrx = wasm_interface::get().current_apply_context->get_pending_transaction(handle); + ptrx.current_permissions.emplace_back(Name(account), Name(permission)); +} + +DEFINE_INTRINSIC_FUNCTION3(env,transactionPushMessage,transactionPushMessage,none,i32,handle,i32,msg_buffer,i32,msg_size) { auto& wasm = wasm_interface::get(); auto mem = wasm.current_memory; - const char* buffer = &memoryRef( mem, trx_buffer ); - FC_ASSERT( trx_buffer_size > 0 ); - FC_ASSERT( wasm.current_apply_context, "not in apply context" ); + EOS_ASSERT( msg_size > 0, tx_unknown_argument + "Attempting to push an empty message" ); - fc::datastream ds(buffer, trx_buffer_size ); - eos::chain::GeneratedTransaction gtrx; - eos::chain::Transaction& trx = gtrx; - fc::raw::unpack( ds, trx ); + const char* buffer = nullptr; + try { + // memoryArrayPtr checks that the entire array of bytes is valid and + // within the bounds of the memory segment so that transactions cannot pass + // bad values in attempts to read improper memory + buffer = memoryArrayPtr( mem, msg_buffer, msg_size ); + } catch( const Runtime::Exception& e ) { + FC_THROW_EXCEPTION(tx_unknown_argument, "Message data is not valid"); + } -/** - * The code below this section provides sanity checks that the generated message is well formed - * before being accepted. These checks do not need to be applied during reindex. - */ -#warning TODO: reserve per-thread static memory for MAX TRX SIZE -/** make sure that packing what we just unpacked produces expected output */ - auto test = fc::raw::pack( trx ); - FC_ASSERT( 0 == memcmp( buffer, test.data(), test.size() ) ); + auto& ptrx = wasm.current_apply_context->get_pending_transaction(handle); + EOS_ASSERT(ptrx.destination.valid(), tx_unknown_argument + "Attempting to push a message without setting a destination"); + + auto& dest = *ptrx.destination; + ptrx.messages.emplace_back(dest.code, dest.type, ptrx.current_permissions, Bytes(buffer, buffer + msg_size)); + ptrx.reset_message(); + ptrx.check_size(); +} + +DEFINE_INTRINSIC_FUNCTION1(env,transactionResetMessage,transactionResetMessage,none,i32,handle) { + auto& ptrx = wasm_interface::get().current_apply_context->get_pending_transaction(handle); + ptrx.reset_message(); +} -/** TODO: make sure that we can call validate() on the message and it passes, this is thread safe and - * ensures the type is properly registered and can be deserialized... one issue is that this could - * construct a RECURSIVE virtual machine state which means the wasm_interface state needs to be a STACK vs - * a per-thread global. - **/ +DEFINE_INTRINSIC_FUNCTION2(env,transactionSend,transactionSend,none,i32,handle,i32,mode) { + EOS_ASSERT(mode == 0 || mode == 1, tx_unknown_argument + "Unknown delivery mode when sending transaction: ${mode}", ("mode":mode)); -// wasm.current_apply_context->generated.emplace_back( std::move(gtrx) ); + auto apply_context = wasm_interface::get().current_apply_context; + auto& ptrx = apply_context->get_pending_transaction(handle); - return 0; + EOS_ASSERT(ptrx.messages.size() > 0, , tx_unknown_argument + "Attempting to send a transaction with no messages"); + + if (mode == 0) { + apply_context->deferred_transactions.emplace_back(ptrx.as_transaction()); + } else { + apply_context->inline_transactions.emplace_back(ptrx.as_transaction()); + } + + apply_context->release_pending_transaction(handle); } +DEFINE_INTRINSIC_FUNCTION1(env,transactionDrop,transactionDrop,none,i32,handle) { + wasm_interface::get().current_apply_context->release_pending_transaction(handle); +} + +/** + * @} Transaction C API implementation + */