Skip to content

Commit

Permalink
initial implementation of wasm interface for transaction contract api…
Browse files Browse the repository at this point in the history
… ref EOSIO#175
  • Loading branch information
wanderingbort committed Sep 7, 2017
1 parent f715852 commit 45ad293
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 28 deletions.
7 changes: 5 additions & 2 deletions contracts/eoslib/transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ extern "C" {

typedef uint32_t TransactionHandle;
#define InvalidTransactionHandle (0xFFFFFFFFUL)
#define SendInline (1)
#define SendDeferred (0)

/**
* @brief create a pending transaction
Expand Down Expand Up @@ -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);


/**
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions libraries/chain/include/eos/chain/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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" )
Expand Down
45 changes: 42 additions & 3 deletions libraries/chain/include/eos/chain/message_handling_contexts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ class apply_context {
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 <typename ObjectType>
int32_t store_record( Name scope, Name code, Name table, typename ObjectType::key_type* keys, char* value, uint32_t valuelen ) {
Expand Down Expand Up @@ -286,11 +287,49 @@ class apply_context {
chainbase::database& mutable_db;

std::deque<AccountName> notified;
std::deque<ProcessedTransaction> sync_transactions; ///< sync calls made
std::deque<GeneratedTransaction> async_transactions; ///< async calls requested
std::deque<Transaction> inline_transactions; ///< queued inline txs
std::deque<Transaction> deferred_transactions; ///< deferred txs

///< Parallel to msg.authorization; tracks which permissions have been used while processing the message
vector<bool> 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<message_destination> current_destination;
vector<types::AccountPermission> current_permissions;

// state to apply when the transaction is pushed
vector<AccountName> scopes;
vector<AccountName> read_scopes;
vector<types::Message> 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_transaction> 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<void(apply_context&)>;
Expand Down
45 changes: 45 additions & 0 deletions libraries/chain/message_handling_contexts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,49 @@ vector<types::AccountPermission> 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
105 changes: 82 additions & 23 deletions libraries/chain/wasm_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const char>( 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<const char*> 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<const char>( 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
*/



Expand Down

0 comments on commit 45ad293

Please sign in to comment.