diff --git a/libraries/chain/include/eosio/chain/abi_serializer.hpp b/libraries/chain/include/eosio/chain/abi_serializer.hpp index 9ff9b27fde..3cad62bcf5 100644 --- a/libraries/chain/include/eosio/chain/abi_serializer.hpp +++ b/libraries/chain/include/eosio/chain/abi_serializer.hpp @@ -736,7 +736,7 @@ namespace impl { * and can be degraded to the normal ::from_variant(...) processing */ template = 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); @@ -825,13 +825,14 @@ namespace impl { from_variant(data, act.data); 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(); } } @@ -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>; +using resolver_fn_t = std::function(const account_name& name)>; class abi_resolver { public: @@ -1022,7 +1024,7 @@ class abi_resolver { class abi_serializer_cache_builder { public: - explicit abi_serializer_cache_builder(std::function(const account_name& name)> resolver) : + explicit abi_serializer_cache_builder(resolver_fn_t resolver) : resolver_(std::move(resolver)) { } @@ -1066,8 +1068,47 @@ class abi_serializer_cache_builder { } } - std::function(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 { +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> 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 diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index 3d775d1e32..f073454d30 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1897,12 +1897,10 @@ chain::signed_block_ptr read_only::get_raw_block(const read_only::get_raw_block_ std::function()> 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; 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); @@ -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 { @@ -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 next) { try { auto pretty_input = std::make_shared(); - 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()(pretty_input, true, transaction_metadata::trx_type::input, false, @@ -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, fc::mutable_variant_object > act_traces_map; @@ -2154,9 +2151,9 @@ template void api_base::send_transaction_gen(API &api, send_transaction_params_t params, next_function next) { try { auto ptrx = std::make_shared(); - 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; @@ -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; - 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; } @@ -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 )); @@ -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; @@ -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; diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp index e98bc1520f..5b10c19219 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -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; @@ -68,6 +69,11 @@ namespace eosio { }; } + template + 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{}; diff --git a/plugins/chain_plugin/trx_retry_db.cpp b/plugins/chain_plugin/trx_retry_db.cpp index 1907c55fc3..9ba86794d4 100644 --- a/plugins/chain_plugin/trx_retry_db.cpp +++ b/plugins/chain_plugin/trx_retry_db.cpp @@ -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& ) {