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

Move get_block abi serialization off the main thread #696

Merged
merged 10 commits into from
Feb 15, 2023
41 changes: 37 additions & 4 deletions plugins/chain_api_plugin/chain_api_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,18 @@ void chain_api_plugin::plugin_startup() {
ilog( "starting chain_api_plugin" );
my.reset(new chain_api_plugin_impl(app().get_plugin<chain_plugin>().chain()));
auto& chain = app().get_plugin<chain_plugin>();
auto& http = app().get_plugin<http_plugin>();
fc::microseconds max_response_time = http.get_max_response_time();
auto& _http_plugin = app().get_plugin<http_plugin>();
fc::microseconds max_response_time = _http_plugin.get_max_response_time();

auto ro_api = chain.get_read_only_api(max_response_time);
auto rw_api = chain.get_read_write_api(max_response_time);

auto& _http_plugin = app().get_plugin<http_plugin>();
ro_api.set_shorten_abi_errors( !http_plugin::verbose_errors() );

_http_plugin.add_api( {
CHAIN_RO_CALL(get_info, 200, http_params_types::no_params)}, appbase::priority::medium_high);
_http_plugin.add_api({
CHAIN_RO_CALL(get_activated_protocol_features, 200, http_params_types::possible_no_params),
CHAIN_RO_CALL(get_block, 200, http_params_types::params_required),
CHAIN_RO_CALL(get_block_info, 200, http_params_types::params_required),
CHAIN_RO_CALL(get_block_header_state, 200, http_params_types::params_required),
CHAIN_RO_CALL(get_account, 200, http_params_types::params_required),
Expand Down Expand Up @@ -151,6 +149,41 @@ void chain_api_plugin::plugin_startup() {
CHAIN_RO_CALL_WITH_400(get_transaction_status, 200, http_params_types::params_required),
});
}

_http_plugin.add_api({
{ std::string("/v1/chain/get_block"),
[ro_api, &_http_plugin, max_time=std::min(chain.get_abi_serializer_max_time(),max_response_time)]
( string, string body, url_response_callback cb ) mutable {
auto deadline = ro_api.start();
try {
auto start = fc::time_point::now();
auto params = parse_params<chain_apis::read_only::get_block_params, http_params_types::params_required>(body);
FC_CHECK_DEADLINE( deadline );
chain::signed_block_ptr block = ro_api.get_block( params, deadline );

auto abi_cache = ro_api.get_block_serializers( block, max_time );
FC_CHECK_DEADLINE( deadline );

auto post_time = fc::time_point::now();
auto remaining_time = max_time - (post_time - start);
_http_plugin.post_http_thread_pool(
[ro_api, cb, deadline, post_time, remaining_time, abi_cache{std::move(abi_cache)}, block{std::move( block )}]() {
try {
auto new_deadline = deadline + (fc::time_point::now() - post_time);

fc::variant result = ro_api.convert_block( block, abi_cache, remaining_time );

cb( 200, new_deadline, std::move( result ) );
} catch( ... ) {
http_plugin::handle_exception( "chain", "get_block", "", cb );
}
} );
} catch( ... ) {
http_plugin::handle_exception("chain", "get_block", body, cb);
}
}
}
} );
}

void chain_api_plugin::plugin_shutdown() {}
Expand Down
59 changes: 50 additions & 9 deletions plugins/chain_plugin/chain_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
#include <eosio/chain_plugin/trx_finality_status_processing.hpp>
#include <eosio/chain/permission_link_object.hpp>
#include <eosio/chain/global_property_object.hpp>
#include <eosio/chain/eosio_contract.hpp>

#include <eosio/resource_monitor_plugin/resource_monitor_plugin.hpp>

Expand All @@ -30,7 +29,6 @@

#include <fc/io/json.hpp>
#include <fc/variant.hpp>
#include <signal.h>
#include <cstdlib>

// reflect chainbase::environment for --print-build-info option
Expand Down Expand Up @@ -1881,7 +1879,7 @@ read_only::get_scheduled_transactions( const read_only::get_scheduled_transactio
return result;
}

fc::variant read_only::get_block(const read_only::get_block_params& params, const fc::time_point& deadline) const {
chain::signed_block_ptr read_only::get_block(const read_only::get_block_params& params, const fc::time_point& deadline) const {
signed_block_ptr block;
std::optional<uint64_t> block_num;

Expand All @@ -1903,18 +1901,61 @@ fc::variant read_only::get_block(const read_only::get_block_params& params, cons
}

EOS_ASSERT( block, unknown_block_exception, "Could not find block: ${block}", ("block", params.block_num_or_id));
FC_CHECK_DEADLINE(deadline);

return block;
}

std::unordered_map<account_name, std::optional<abi_serializer>>
read_only::get_block_serializers( const chain::signed_block_ptr& block, const fc::microseconds& max_time ) const {
auto yield = abi_serializer::create_yield_function( max_time );
auto resolver = make_resolver(db, yield );
std::unordered_map<account_name, std::optional<abi_serializer> > abi_cache;
auto add_to_cache = [&]( const chain::action& a ) {
auto it = abi_cache.find( a.account );
if( it == abi_cache.end() ) {
try {
abi_cache.emplace_hint( it, a.account, resolver( a.account ) );
} catch( ... ) {
// keep behavior of not throwing on invalid abi, will result in hex data
}
}
};
for( const auto& receipt: block->transactions ) {
if( std::holds_alternative<chain::packed_transaction>( receipt.trx ) ) {
const auto& pt = std::get<chain::packed_transaction>( receipt.trx );
const auto& t = pt.get_transaction();
for( const auto& a: t.actions )
add_to_cache( a );
for( const auto& a: t.context_free_actions )
add_to_cache( a );
}
}
return abi_cache;
}

fc::variant read_only::convert_block( const chain::signed_block_ptr& block,
std::unordered_map<account_name, std::optional<abi_serializer>> abi_cache,
const fc::microseconds& max_time ) const {

auto abi_serializer_resolver = [&abi_cache](const account_name& account) -> std::optional<abi_serializer> {
auto it = abi_cache.find( account );
if( it != abi_cache.end() )
return it->second;
return {};
};

fc::variant pretty_output;
abi_serializer::to_variant(*block, pretty_output, make_resolver(db, abi_serializer::create_yield_function( abi_serializer_max_time )),
abi_serializer::create_yield_function( abi_serializer_max_time ));
abi_serializer::to_variant( *block, pretty_output, abi_serializer_resolver,
abi_serializer::create_yield_function( max_time ) );

const auto block_id = block->calculate_id();
uint32_t ref_block_prefix = block_id._hash[1];

return fc::mutable_variant_object(pretty_output.get_object())
("id", block_id)
("block_num",block->block_num())
("ref_block_prefix", ref_block_prefix);
return fc::mutable_variant_object( pretty_output.get_object() )
( "id", block_id )
( "block_num", block->block_num() )
( "ref_block_prefix", ref_block_prefix );
}

fc::variant read_only::get_block_info(const read_only::get_block_info_params& params, const fc::time_point& deadline) const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,14 @@ class read_only {
string block_num_or_id;
};

fc::variant get_block(const get_block_params& params, const fc::time_point& deadline) const;
chain::signed_block_ptr get_block(const get_block_params& params, const fc::time_point& deadline) const;
// call from app() thread
std::unordered_map<account_name, std::optional<abi_serializer>>
get_block_serializers( const chain::signed_block_ptr& block, const fc::microseconds& max_time ) const;
// call from any thread
fc::variant convert_block( const chain::signed_block_ptr& block,
std::unordered_map<account_name, std::optional<abi_serializer>> abi_cache,
const fc::microseconds& max_time ) const;

struct get_block_info_params {
uint32_t block_num = 0;
Expand Down
5 changes: 5 additions & 0 deletions plugins/http_plugin/http_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,11 @@ class http_plugin_impl : public std::enable_shared_from_this<http_plugin_impl> {
my->plugin_state->url_handlers[url] = my->make_http_thread_url_handler(handler);
}

void http_plugin::post_http_thread_pool(std::function<void()> f) {
if( f )
boost::asio::post( my->plugin_state->thread_pool.get_executor(), f );
}

void http_plugin::handle_exception( const char *api_name, const char *call_name, const string& body, const url_response_callback& cb) {
try {
try {
Expand Down
2 changes: 2 additions & 0 deletions plugins/http_plugin/include/eosio/http_plugin/http_plugin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ namespace eosio {
// standard exception handling for api handlers
static void handle_exception( const char *api_name, const char *call_name, const string& body, const url_response_callback& cb );

void post_http_thread_pool(std::function<void()> f);

bool is_on_loopback() const;
bool is_secure() const;

Expand Down
8 changes: 6 additions & 2 deletions tests/chain_plugin_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ BOOST_FIXTURE_TEST_CASE( get_block_with_invalid_abi, TESTER ) try {
chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), fc::microseconds::maximum(), {}, {});

// block should be decoded successfully
std::string block_str = json::to_pretty_string(plugin.get_block(param, fc::time_point::maximum()));
auto block = plugin.get_block(param, fc::time_point::maximum());
auto abi_cache = plugin.get_block_serializers(block, fc::microseconds::maximum());
std::string block_str = json::to_pretty_string(plugin.convert_block(block, abi_cache, fc::microseconds::maximum()));
BOOST_TEST(block_str.find("procassert") != std::string::npos);
BOOST_TEST(block_str.find("condition") != std::string::npos);
BOOST_TEST(block_str.find("Should Not Assert!") != std::string::npos);
Expand All @@ -111,7 +113,9 @@ BOOST_FIXTURE_TEST_CASE( get_block_with_invalid_abi, TESTER ) try {
BOOST_CHECK_THROW(resolver("asserter"_n), invalid_type_inside_abi);

// get the same block as string, results in decode failed(invalid abi) but not exception
std::string block_str2 = json::to_pretty_string(plugin.get_block(param, fc::time_point::maximum()));
auto block2 = plugin.get_block(param, fc::time_point::maximum());
auto abi_cache2 = plugin.get_block_serializers(block2, fc::microseconds::maximum());
std::string block_str2 = json::to_pretty_string(plugin.convert_block(block2, abi_cache2, fc::microseconds::maximum()));
BOOST_TEST(block_str2.find("procassert") != std::string::npos);
BOOST_TEST(block_str2.find("condition") == std::string::npos); // decode failed
BOOST_TEST(block_str2.find("Should Not Assert!") == std::string::npos); // decode failed
Expand Down