Skip to content
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

Improve packed_transaction parsing performance #1199

Merged
merged 8 commits into from
May 19, 2023
57 changes: 49 additions & 8 deletions libraries/chain/include/eosio/chain/abi_serializer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -736,7 +736,7 @@ namespace impl {
* and can be degraded to the normal ::from_variant(...) processing
*/
template<typename M, typename Resolver, not_require_abi_t<M> = 1>
static void extract( const fc::variant& v, M& o, Resolver, abi_traverse_context& ctx )
static void extract( const fc::variant& v, M& o, const Resolver&, abi_traverse_context& ctx )
{
auto h = ctx.enter_scope();
from_variant(v, o);
Expand Down Expand Up @@ -825,13 +825,14 @@ namespace impl {
from_variant(data, act.data);
heifner marked this conversation as resolved.
Show resolved Hide resolved
valid_empty_data = act.data.empty();
} else if ( data.is_object() ) {
auto abi = resolver(act.account);
if (abi) {
auto type = abi->get_action_type(act.name);
auto abi_optional = resolver(act.account);
if (abi_optional) {
const abi_serializer& abi = *abi_optional;
auto type = abi.get_action_type(act.name);
if (!type.empty()) {
variant_to_binary_context _ctx(*abi, ctx, type);
variant_to_binary_context _ctx(abi, ctx, type);
_ctx.short_path = true; // Just to be safe while avoiding the complexity of threading an override boolean all over the place
act.data = std::move( abi->_variant_to_binary( type, data, _ctx ));
act.data = abi._variant_to_binary( type, data, _ctx );
valid_empty_data = act.data.empty();
}
}
Expand Down Expand Up @@ -1002,6 +1003,7 @@ void abi_serializer::from_variant( const fc::variant& v, T& o, const Resolver& r
} FC_RETHROW_EXCEPTIONS(error, "Failed to deserialize variant", ("variant",v))

using abi_serializer_cache_t = std::unordered_map<account_name, std::optional<abi_serializer>>;
using resolver_fn_t = std::function<std::optional<abi_serializer>(const account_name& name)>;

class abi_resolver {
public:
Expand All @@ -1022,7 +1024,7 @@ class abi_resolver {

class abi_serializer_cache_builder {
public:
explicit abi_serializer_cache_builder(std::function<std::optional<abi_serializer>(const account_name& name)> resolver) :
explicit abi_serializer_cache_builder(resolver_fn_t resolver) :
resolver_(std::move(resolver))
{
}
Expand Down Expand Up @@ -1066,8 +1068,47 @@ class abi_serializer_cache_builder {
}
}

std::function<std::optional<abi_serializer>(const account_name& name)> resolver_;
resolver_fn_t resolver_;
abi_serializer_cache_t abi_serializers;
};

/*
* This is equivalent to a resolver, except that everytime the abi_serializer for an account
* is retrieved, it is stored in an unordered_map, so we won't waste time retrieving it again.
* This is handy when parsing packed_transactions received in a fc::variant.
*/
class caching_resolver {
heifner marked this conversation as resolved.
Show resolved Hide resolved
public:
explicit caching_resolver(resolver_fn_t resolver) :
resolver_(std::move(resolver))
{
}

// make it non-copiable (we should only move it for performance reasons)
caching_resolver(const caching_resolver&) = delete;
caching_resolver& operator=(const caching_resolver&) = delete;

std::optional<std::reference_wrapper<const abi_serializer>> operator()(const account_name& account) const {
auto it = abi_serializers.find(account);
if (it != abi_serializers.end()) {
if (it->second)
return *it->second;
return {};
}
auto serializer = resolver_(account);
auto& dest = abi_serializers[account]; // add entry regardless
if (serializer) {
// we got a serializer, so move it into the cache
dest = abi_serializer_cache_t::mapped_type{std::move(*serializer)};
return *dest; // and return a reference to it
}
return {};
};

private:
const resolver_fn_t resolver_;
mutable abi_serializer_cache_t abi_serializers;
};


} // eosio::chain
36 changes: 17 additions & 19 deletions plugins/chain_plugin/chain_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1897,12 +1897,10 @@ chain::signed_block_ptr read_only::get_raw_block(const read_only::get_raw_block_
std::function<chain::t_or_exception<fc::variant>()> read_only::get_block(const get_raw_block_params& params, const fc::time_point& deadline) const {
chain::signed_block_ptr block = get_raw_block(params, deadline);

auto abi_cache = abi_serializer_cache_builder(make_resolver(db, abi_serializer_max_time, throw_on_yield::no)).add_serializers(block).get();

using return_type = t_or_exception<fc::variant>;
return [this,
resolver = abi_resolver(std::move(abi_cache)),
block = std::move(block)]() mutable -> return_type {
resolver = get_serializers_cache(db, block, abi_serializer_max_time),
block = std::move(block)]() mutable -> return_type {
try {
return convert_block(block, resolver);
} CATCH_AND_RETURN(return_type);
Expand Down Expand Up @@ -1949,7 +1947,7 @@ read_only::get_block_header_result read_only::get_block_header(const read_only::

abi_resolver
read_only::get_block_serializers( const chain::signed_block_ptr& block, const fc::microseconds& max_time ) const {
return abi_resolver(abi_serializer_cache_builder(make_resolver(db, max_time, throw_on_yield::no)).add_serializers(block).get());
return get_serializers_cache(db, block, max_time);
}

fc::variant read_only::convert_block( const chain::signed_block_ptr& block, abi_resolver& resolver ) const {
Expand Down Expand Up @@ -2031,9 +2029,9 @@ void read_write::push_block(read_write::push_block_params&& params, next_functio
void read_write::push_transaction(const read_write::push_transaction_params& params, next_function<read_write::push_transaction_results> next) {
try {
auto pretty_input = std::make_shared<packed_transaction>();
auto resolver = make_resolver(db, abi_serializer_max_time, throw_on_yield::yes);
auto resolver = caching_resolver(make_resolver(db, abi_serializer_max_time, throw_on_yield::yes));
try {
abi_serializer::from_variant(params, *pretty_input, std::move( resolver ), abi_serializer::create_yield_function( abi_serializer_max_time ));
abi_serializer::from_variant(params, *pretty_input, resolver, abi_serializer_max_time);
} EOS_RETHROW_EXCEPTIONS(chain::packed_transaction_type_exception, "Invalid packed transaction")

app().get_method<incoming::methods::transaction_async>()(pretty_input, true, transaction_metadata::trx_type::input, false,
Expand All @@ -2046,9 +2044,8 @@ void read_write::push_transaction(const read_write::push_transaction_params& par
try {
fc::variant output;
try {
auto abi_cache = abi_serializer_cache_builder(make_resolver(db, abi_serializer_max_time, throw_on_yield::no)).add_serializers(trx_trace_ptr).get();
auto resolver = abi_resolver(std::move(abi_cache));
abi_serializer::to_variant(*trx_trace_ptr, output, std::move(resolver), abi_serializer_max_time);
auto resolver = get_serializers_cache(db, trx_trace_ptr, abi_serializer_max_time);
abi_serializer::to_variant(*trx_trace_ptr, output, resolver, abi_serializer_max_time);

// Create map of (closest_unnotified_ancestor_action_ordinal, global_sequence) with action trace
std::map< std::pair<uint32_t, uint64_t>, fc::mutable_variant_object > act_traces_map;
Expand Down Expand Up @@ -2154,9 +2151,9 @@ template<class API, class Result>
void api_base::send_transaction_gen(API &api, send_transaction_params_t params, next_function<Result> next) {
try {
auto ptrx = std::make_shared<packed_transaction>();
auto resolver = make_resolver(api.db, api.abi_serializer_max_time, throw_on_yield::yes);
auto resolver = caching_resolver(make_resolver(api.db, api.abi_serializer_max_time, throw_on_yield::yes));
try {
abi_serializer::from_variant(params.transaction, *ptrx, resolver, abi_serializer::create_yield_function( api.abi_serializer_max_time ));
abi_serializer::from_variant(params.transaction, *ptrx, resolver, api.abi_serializer_max_time);
} EOS_RETHROW_EXCEPTIONS(packed_transaction_type_exception, "Invalid packed transaction")

bool retry = false;
Expand Down Expand Up @@ -2201,13 +2198,14 @@ void api_base::send_transaction_gen(API &api, send_transaction_params_t params,
}
if (!retried) {
// we are still on main thread here. The lambda passed to `next()` below will be executed on the http thread pool
auto abi_cache = abi_serializer_cache_builder(make_resolver(api.db, api.abi_serializer_max_time, throw_on_yield::no)).add_serializers(trx_trace_ptr).get();
using return_type = t_or_exception<Result>;
next([&api, trx_trace_ptr, resolver = abi_resolver(std::move(abi_cache))]() mutable {
next([&api,
trx_trace_ptr,
resolver = get_serializers_cache(api.db, trx_trace_ptr, api.abi_serializer_max_time)]() mutable {
try {
fc::variant output;
try {
abi_serializer::to_variant(*trx_trace_ptr, output, std::move(resolver), api.abi_serializer_max_time);
abi_serializer::to_variant(*trx_trace_ptr, output, resolver, api.abi_serializer_max_time);
} catch( abi_exception& ) {
output = *trx_trace_ptr;
}
Expand Down Expand Up @@ -2498,9 +2496,9 @@ read_only::get_account_return_t read_only::get_account( const get_account_params

read_only::get_required_keys_result read_only::get_required_keys( const get_required_keys_params& params, const fc::time_point& )const {
transaction pretty_input;
auto resolver = make_resolver(db, abi_serializer_max_time, throw_on_yield::yes);
auto resolver = caching_resolver(make_resolver(db, abi_serializer_max_time, throw_on_yield::yes));
try {
abi_serializer::from_variant(params.transaction, pretty_input, resolver, abi_serializer::create_yield_function( abi_serializer_max_time ));
abi_serializer::from_variant(params.transaction, pretty_input, resolver, abi_serializer_max_time);
} EOS_RETHROW_EXCEPTIONS(chain::transaction_type_exception, "Invalid transaction")

auto required_keys_set = db.get_authorization_manager().get_required_keys( pretty_input, params.available_keys, fc::seconds( pretty_input.delay_sec ));
Expand Down Expand Up @@ -2594,7 +2592,7 @@ fc::variant chain_plugin::get_log_trx_trace(const transaction_trace_ptr& trx_tra
fc::variant pretty_output;
try {
abi_serializer::to_log_variant(trx_trace, pretty_output,
make_resolver(chain(), get_abi_serializer_max_time(), throw_on_yield::no),
caching_resolver(make_resolver(chain(), get_abi_serializer_max_time(), throw_on_yield::no)),
get_abi_serializer_max_time());
} catch (...) {
pretty_output = trx_trace;
Expand All @@ -2606,7 +2604,7 @@ fc::variant chain_plugin::get_log_trx(const transaction& trx) const {
fc::variant pretty_output;
try {
abi_serializer::to_log_variant(trx, pretty_output,
make_resolver(chain(), get_abi_serializer_max_time(), throw_on_yield::no),
caching_resolver(make_resolver(chain(), get_abi_serializer_max_time(), throw_on_yield::no)),
get_abi_serializer_max_time());
} catch (...) {
pretty_output = trx;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ namespace eosio {
using chain::action_name;
using chain::abi_def;
using chain::abi_serializer;
using chain::abi_serializer_cache_builder;
using chain::abi_resolver;
using chain::packed_transaction;

Expand All @@ -68,6 +69,11 @@ namespace eosio {
};
}

template<class T>
inline abi_resolver get_serializers_cache(const controller& db, const T& obj, const fc::microseconds& max_time) {
return abi_resolver(abi_serializer_cache_builder(make_resolver(db, max_time, throw_on_yield::no)).add_serializers(obj).get());
}

namespace chain_apis {
struct empty{};

Expand Down
3 changes: 1 addition & 2 deletions plugins/chain_plugin/trx_retry_db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,7 @@ struct trx_retry_db_impl {
// Convert to variant with abi here and now because abi could change in very next transaction.
// Alternatively, we could store off all the abis needed and do the conversion later, but as this is designed
// to run on an API node, probably the best trade off to perform the abi serialization during block processing.
auto abi_cache = abi_serializer_cache_builder(make_resolver(control, abi_max_time, throw_on_yield::no)).add_serializers(trace).get();
auto resolver = abi_resolver(std::move(abi_cache));
auto resolver = get_serializers_cache(control, trace, abi_max_time);
tt.trx_trace_v.clear();
abi_serializer::to_variant(*trace, tt.trx_trace_v, resolver, abi_max_time);
} catch( chain::abi_exception& ) {
Expand Down