Skip to content

SC: Implement get_call_data, set_call_return_value and get_call_data host functions #1258

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Mar 31, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 71 additions & 8 deletions libraries/chain/apply_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,18 +246,19 @@ void apply_context::exec()

} /// exec()

void apply_context::execute_sync_call(name receiver, uint64_t flags, std::span<const char> data)
{
uint32_t apply_context::execute_sync_call(name receiver, uint64_t flags, std::span<const char> data) {
assert(sync_call_ctx.has_value() ^ (act != nullptr)); // can be only one of action and sync call

dlog("receiver: ${r}, flags: ${f}, data size: ${s}",
("r", receiver)("f", flags)("s", data.size()));

auto* code = control.db().find<account_object, by_name>(receiver);
EOS_ASSERT(code != nullptr, action_validate_exception,
EOS_ASSERT(code != nullptr, sync_call_validate_exception,
"sync call's receiver account ${r} does not exist", ("r", receiver));

EOS_ASSERT((flags & 0xFFFFFFFFFFFFFFFE) == 0, action_validate_exception,
const auto max_sync_call_data_size = control.get_global_properties().configuration.max_sync_call_data_size;
EOS_ASSERT(data.size() <= max_sync_call_data_size, sync_call_call_data_exception,
"sync call call data size must be less or equal to ${s} bytes", ("s", max_sync_call_data_size));
EOS_ASSERT((flags & 0xFFFFFFFFFFFFFFFE) == 0, sync_call_validate_exception,
"sync call's flags ${f} can only set bit 0", ("f", flags));

auto handle_exception = [&](const auto& e)
Expand All @@ -266,20 +267,29 @@ void apply_context::execute_sync_call(name receiver, uint64_t flags, std::span<c
throw;
};

sync_call_return_value = std::nullopt; // reset for current sync call
uint32_t return_value_size = 0;

try {
try {
const account_metadata_object* receiver_account = &db.get<account_metadata_object, by_name>( receiver);
if (receiver_account->code_hash.empty()) {
// TBD store the info in sync call trace
ilog("receiver_account->code_hash empty");
return;
return return_value_size;
}

try {
// use a new apply_context for a new sync call
apply_context a_ctx(control, trx_context, get_sync_call_sender(), receiver, std::move(data));

control.get_wasm_interface().do_sync_call(receiver_account->code_hash, receiver_account->vm_type, receiver_account->vm_version, a_ctx);

// store return value
if (a_ctx.sync_call_ctx.has_value()) {
sync_call_return_value = std::move(a_ctx.sync_call_ctx->return_value);
return_value_size = sync_call_return_value->size();
dlog("sync_call_return_value size: ${s}", ("s", return_value_size));
}
} catch( const wasm_exit&) {}
} FC_RETHROW_EXCEPTIONS(warn, "sync call exception on ${receiver}", ("receiver", receiver))
} catch (const std::bad_alloc&) {
Expand All @@ -292,8 +302,8 @@ void apply_context::execute_sync_call(name receiver, uint64_t flags, std::span<c
auto wrapper = fc::std_exception_wrapper::from_current_exception(e);
handle_exception(wrapper);
}

trx_context.checktime(); // protect against the case where during the removal of the callback, the timer expires.
return return_value_size;
}

// Returns the sender of any sync call initiated by this apply_context or sync_call_ctx
Expand All @@ -303,6 +313,59 @@ action_name apply_context::get_sync_call_sender() const {
return sync_call_ctx ? sync_call_ctx->receiver : get_receiver();
}

uint32_t apply_context::get_call_return_value(std::span<char> memory) const {
assert(sync_call_ctx.has_value() ^ (act != nullptr)); // can be only one of action and sync call

if (!sync_call_return_value.has_value()) {
return 0;
}

const auto data_size = sync_call_return_value->size();
const auto copy_size = std::min(memory.size(), data_size);

if (copy_size == 0) {
return data_size;
}

// Copy up to the length of memory of data to memory
std::memcpy(memory.data(), sync_call_return_value->data(), copy_size);

// Return the number of bytes of the data that can be retrieved
return data_size;
}

uint32_t apply_context::get_call_data(std::span<char> memory) const {
assert(sync_call_ctx.has_value() ^ (act != nullptr)); // can be only one of action and sync call
EOS_ASSERT(sync_call_ctx.has_value(), sync_call_validate_exception,
"get_call_data can be only used in sync call");

const auto& data = sync_call_ctx->data;
auto data_size = data.size();
auto copy_size = std::min(memory.size(), data_size);

if (copy_size == 0) {
return data_size;
}

// Copy up to the length of memory of data to memory
std::memcpy(memory.data(), data.data(), copy_size);

// Return the number of bytes of the data that can be retrieved
return data_size;
}

void apply_context::set_call_return_value(std::span<const char> return_value) {
assert(sync_call_ctx.has_value() ^ (act != nullptr)); // can be only one of action and sync call
EOS_ASSERT(sync_call_ctx.has_value(), sync_call_validate_exception,
"set_call_return_value can be only used in sync call");

const auto max_sync_call_data_size = control.get_global_properties().configuration.max_sync_call_data_size;
EOS_ASSERT(return_value.size() <= max_sync_call_data_size, sync_call_return_value_exception,
"sync call return value size must be less or equal to ${s} bytes", ("s", max_sync_call_data_size));

sync_call_ctx->return_value.assign(return_value.data(), return_value.data() + return_value.size());
}

bool apply_context::is_account( const account_name& account )const {
return nullptr != db.find<account_object,by_name>( account );
}
Expand Down
1 change: 1 addition & 0 deletions libraries/chain/controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6337,6 +6337,7 @@ template<>
void controller_impl::on_activation<builtin_protocol_feature_t::sync_call>() {
db.modify( db.get<protocol_state_object>(), [&]( auto& ps ) {
add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "call" );
add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "get_call_return_value" );
add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "get_call_data" );
add_intrinsic_to_whitelist( ps.whitelisted_intrinsics, "set_call_return_value" );
} );
Expand Down
7 changes: 5 additions & 2 deletions libraries/chain/include/eosio/chain/apply_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -502,8 +502,10 @@ class apply_context {

void exec_one();
void exec();
void execute_sync_call(name receiver, uint64_t flags, std::span<const char> data
);
uint32_t execute_sync_call(name receiver, uint64_t flags, std::span<const char> data);
uint32_t get_call_return_value(std::span<char> memory) const;
uint32_t get_call_data(std::span<char> memory) const;
void set_call_return_value(std::span<const char> return_value);
void execute_inline( action&& a );
void execute_context_free_inline( action&& a );
void schedule_deferred_transaction( const uint128_t& sender_id, account_name payer, transaction&& trx, bool replace_existing );
Expand Down Expand Up @@ -628,6 +630,7 @@ class apply_context {
bool context_free = false;

std::optional<sync_call_context> sync_call_ctx{}; // only one of act and sync_call_ctx can be present
std::optional<std::vector<char>> sync_call_return_value{};

// Returns the sender of any sync call initiated by this apply_context or its sync_call_ctx
action_name get_sync_call_sender() const;
Expand Down
9 changes: 9 additions & 0 deletions libraries/chain/include/eosio/chain/exceptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -679,4 +679,13 @@ namespace eosio { namespace chain {
3260000, "Finalizer exception" )
FC_DECLARE_DERIVED_EXCEPTION( finalizer_safety_exception, finalizer_exception,
3260001, "Finalizer safety file exception" )

FC_DECLARE_DERIVED_EXCEPTION( sync_call_exception, chain_exception,
3270000, "Sync call exception" )
FC_DECLARE_DERIVED_EXCEPTION( sync_call_validate_exception, sync_call_exception,
3270001, "Sync call validation exception" )
FC_DECLARE_DERIVED_EXCEPTION( sync_call_return_value_exception, sync_call_exception,
3270002, "Sync call return value exception" )
FC_DECLARE_DERIVED_EXCEPTION( sync_call_call_data_exception, sync_call_exception,
3270003, "Sync call call data exception" )
} } // eosio::chain
4 changes: 4 additions & 0 deletions libraries/chain/include/eosio/chain/sync_call_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@

namespace eosio { namespace chain {
struct sync_call_context {
// input
account_name sender{};
account_name receiver{};
std::span<const char> data{}; // includes function name, arguments, and other information

// output
std::vector<char> return_value{};
};
} } /// namespace eosio::chain

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ inline constexpr auto get_intrinsic_table() {
"env.set_finalizers",
"eosvmoc_internal.check_memcpy_params",
"env.call",
"env.get_call_return_value",
"env.get_call_data",
"env.set_call_return_value"
);
Expand Down
17 changes: 15 additions & 2 deletions libraries/chain/include/eosio/chain/webassembly/interface.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -694,12 +694,25 @@ namespace webassembly {
* the sync call. LSB bit indicates read-only. All other bits
* are reserved to be 0.
* @param data - the data of the sync call, which may include function name, arguments and other information.
* @return the number of bytes of the return value of the call (to be used by next get_call_return_value) .
*
*/
void call(name receiver, uint64_t flags, span<const char> data);
uint32_t call(name receiver, uint64_t flags, span<const char> data);

/**
* Copies the current sync call data up to the length of memory.
* Copies the last sync call return value to `memory` up to the length of `memory`.
* This should be called by the caller right after `call` host function.
* Otherwise the return value will be overwritten by future sync calls in the same action or function
*
* @ingroup sync call
* @param memory - a pointer where up to the size of memory will be copied
*
* @return the number of bytes of the return value that can be retrieved (the number of total bytes).
*/
uint32_t get_call_return_value(span<char> memory) const;

/**
* Copies the current sync call data to `memory` up to the length of `memory`.
*
* @ingroup sync call
* @param memory - a pointer where up to the size of memory will be copied
Expand Down
3 changes: 2 additions & 1 deletion libraries/chain/webassembly/runtimes/eos-vm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ class eos_vm_instantiated_module : public wasm_instantiated_module_interface {
iface, "env", "sync_call",
sync_call_ctx->sender.to_uint64_t(),
sync_call_ctx->receiver.to_uint64_t(),
static_cast<uint64_t>(sync_call_ctx->data.size())); // size_t can be uint32_t or uint64_t depending on architecture. Safely cast to uint64_t to always match the type expected by sync_call entry point.
static_cast<uint32_t>(sync_call_ctx->data.size()));
};

execute(context, bkend, exec_ctx, wasm_alloc, fn, true);
Expand Down Expand Up @@ -522,6 +522,7 @@ REGISTER_HOST_FUNCTION(set_action_return_value);

// sync call api. sync calls are not allowed in context-free actions
REGISTER_HOST_FUNCTION(call);
REGISTER_HOST_FUNCTION(get_call_return_value);
REGISTER_HOST_FUNCTION(get_call_data);
REGISTER_HOST_FUNCTION(set_call_return_value);

Expand Down
15 changes: 9 additions & 6 deletions libraries/chain/webassembly/sync_call.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
#include <eosio/chain/apply_context.hpp>

namespace eosio { namespace chain { namespace webassembly {
void interface::call(name receiver, uint64_t flags, std::span<const char> data) {
context.execute_sync_call(receiver, flags, data);
uint32_t interface::call(name receiver, uint64_t flags, std::span<const char> data) {
return context.execute_sync_call(receiver, flags, data);
}

uint32_t interface::get_call_data(span<char> /* memory */) const {
return 0;
uint32_t interface::get_call_return_value(span<char> memory) const {
return context.get_call_return_value(memory);;
}

void interface::set_call_return_value(span<const char> /* value */) {
;
uint32_t interface::get_call_data(std::span<char> memory) const {
return context.get_call_data(memory);
}

void interface::set_call_return_value(std::span<const char> return_value) {
context.set_call_return_value(return_value);;
}
}}} // ns eosio::chain::webassembly
8 changes: 3 additions & 5 deletions unittests/protocol_feature_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2319,19 +2319,17 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(set_finalizers_test, T, testers) { try {
// A basic contract to show new host functions can be called.
static const char basic_sync_call_host_funcs_wast[] = R"=====(
(module
(import "env" "call" (func $call (param i64 i64 i32 i32)))
(import "env" "call" (func $call (param i64 i64 i32 i32)(result i32)))
(import "env" "get_call_data" (func $get_call_data (param i32 i32)(result i32)))
(import "env" "set_call_return_value" (func $set_call_return_value (param i32 i32)))
(memory $0 1)

(export "sync_call" (func $sync_call))
(func $sync_call (param $sender i64) (param $receiver i64) (param $data_size i64))
(func $sync_call (param $sender i64) (param $receiver i64) (param $data_size i32))

(export "apply" (func $apply))
(func $apply (param $receiver i64) (param $account i64) (param $action_name i64)
(call $call (get_local $receiver) (i64.const 0) (i32.const 8) (i32.const 16))
(drop (call $get_call_data (i32.const 8) (i32.const 16)))
(call $set_call_return_value (i32.const 8) (i32.const 16))
(drop (call $call (get_local $receiver) (i64.const 0) (i32.const 8) (i32.const 16)))
)
)
)=====";
Expand Down
Loading
Loading