diff --git a/plugins/chain_api_plugin/chain.swagger.yaml b/plugins/chain_api_plugin/chain.swagger.yaml index 5bef6ef0d0..339568989b 100644 --- a/plugins/chain_api_plugin/chain.swagger.yaml +++ b/plugins/chain_api_plugin/chain.swagger.yaml @@ -186,6 +186,30 @@ paths: schema: description: Returns Nothing + /get_block_header_state: + post: + description: Retrieves a block header state object with only block_num, id, header, and additional_signatures filled. Other fields are left empty or defaulted to a static value. + operationId: get_block_header_state + requestBody: + content: + application/json: + schema: + type: object + required: + - block_num_or_id + properties: + block_num_or_id: + type: string + description: Provide a block_number or a block_id + + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "https://docs.eosnetwork.com/openapi/v2.0/BlockHeaderState.yaml" + /get_abi: post: description: Retrieves the ABI for a contract based on its account name diff --git a/plugins/chain_api_plugin/chain_api_plugin.cpp b/plugins/chain_api_plugin/chain_api_plugin.cpp index fc558536f5..bd1a05611a 100644 --- a/plugins/chain_api_plugin/chain_api_plugin.cpp +++ b/plugins/chain_api_plugin/chain_api_plugin.cpp @@ -132,6 +132,7 @@ void chain_api_plugin::plugin_startup() { CHAIN_RO_CALL(get_activated_protocol_features, 200, http_params_types::possible_no_params), CHAIN_RO_CALL_POST(get_block, fc::variant, 200, http_params_types::params_required), // _POST because get_block() returns a lambda to be executed on the http thread pool 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_POST(get_account, chain_apis::read_only::get_account_results, 200, http_params_types::params_required), CHAIN_RO_CALL(get_code, 200, http_params_types::params_required), CHAIN_RO_CALL(get_code_hash, 200, http_params_types::params_required), diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index c98d503664..11608e7327 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include @@ -2015,6 +2016,35 @@ fc::variant read_only::get_block_info(const read_only::get_block_info_params& pa ("ref_block_prefix", ref_block_prefix); } +fc::variant read_only::get_block_header_state(const get_block_header_state_params& params, const fc::time_point&) const { + signed_block_ptr sbp; + std::optional block_num; + + try { + block_num = fc::to_uint64(params.block_num_or_id); + } catch( ... ) {} + + if( block_num ) { + sbp = db.fetch_block_by_number(*block_num); + } else { + try { + sbp = db.fetch_block_by_id(block_id_type(params.block_num_or_id)); + } EOS_RETHROW_EXCEPTIONS(chain::block_id_type_exception, "Invalid block ID: ${block_num_or_id}", ("block_num_or_id", params.block_num_or_id)) + } + + EOS_ASSERT( sbp, unknown_block_exception, "Could not find block: ${block}", ("block", params.block_num_or_id)); + + block_header_state_legacy ret; + ret.block_num = sbp->block_num(); + ret.id = sbp->calculate_id(); + ret.header = *sbp; + ret.additional_signatures = detail::extract_additional_signatures(sbp); + + fc::variant vo; + fc::to_variant( ret, vo ); + return vo; +} + void read_write::push_block(read_write::push_block_params&& params, next_function next) { try { auto b = std::make_shared( std::move(params) ); 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 45576abe0e..6798d7e44c 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -409,6 +409,12 @@ class read_only : public api_base { fc::variant get_block_info(const get_block_info_params& params, const fc::time_point& deadline) const; + struct get_block_header_state_params { + string block_num_or_id; + }; + + fc::variant get_block_header_state(const get_block_header_state_params& params, const fc::time_point& deadline) const; + struct get_table_rows_params { bool json = false; name code; @@ -1022,6 +1028,7 @@ FC_REFLECT(eosio::chain_apis::read_only::get_activated_protocol_features_params, FC_REFLECT(eosio::chain_apis::read_only::get_activated_protocol_features_results, (activated_protocol_features)(more) ) FC_REFLECT(eosio::chain_apis::read_only::get_raw_block_params, (block_num_or_id)) FC_REFLECT(eosio::chain_apis::read_only::get_block_info_params, (block_num)) +FC_REFLECT(eosio::chain_apis::read_only::get_block_header_state_params, (block_num_or_id)) FC_REFLECT(eosio::chain_apis::read_only::get_block_header_params, (block_num_or_id)(include_extensions)) FC_REFLECT(eosio::chain_apis::read_only::get_block_header_result, (id)(signed_block_header)(block_extensions)) diff --git a/tests/plugin_http_api_test.py b/tests/plugin_http_api_test.py index f9628847cc..de4791ba47 100755 --- a/tests/plugin_http_api_test.py +++ b/tests/plugin_http_api_test.py @@ -336,6 +336,51 @@ def test_ChainApi(self) : ret_json = self.nodeos.processUrllibRequest(resource, command, payload, endpoint=endpoint) self.assertEqual(ret_json["payload"]["block_num"], 1) + # get_block_header_state with empty parameter + command = "get_block_header_state" + ret_json = self.nodeos.processUrllibRequest(resource, command, endpoint=endpoint) + self.assertEqual(ret_json["code"], 400) + self.assertEqual(ret_json["error"]["code"], 3200006) + # get_block_header_state with empty content parameter + ret_json = self.nodeos.processUrllibRequest(resource, command, self.empty_content_dict, endpoint=endpoint) + self.assertEqual(ret_json["code"], 400) + self.assertEqual(ret_json["error"]["code"], 3200006) + # get_block_header_state with invalid parameter + ret_json = self.nodeos.processUrllibRequest(resource, command, self.http_post_invalid_param, endpoint=endpoint) + self.assertEqual(ret_json["code"], 400) + self.assertEqual(ret_json["error"]["code"], 3200006) + self.nodeos.waitForNextBlock() + # get_block_header_state with reversible block + head_block_num = self.nodeos.getHeadBlockNum() + payload = {"block_num_or_id":head_block_num} + ret_json = self.nodeos.processUrllibRequest(resource, command, payload, endpoint=endpoint) + self.assertEqual(ret_json["payload"]["block_num"], head_block_num) + self.assertEqual(ret_json["payload"]["header"]["producer"], "eosio") + # and by id + ret_json = self.nodeos.processUrllibRequest(resource, command, {"block_num_or_id":ret_json["payload"]["id"]}, endpoint=endpoint) + self.assertEqual(ret_json["payload"]["block_num"], head_block_num) + self.assertEqual(ret_json["payload"]["header"]["producer"], "eosio") + # get_block_header_state with irreversible block + lib_block_num = self.nodeos.getIrreversibleBlockNum() + payload = {"block_num_or_id":lib_block_num} + ret_json = self.nodeos.processUrllibRequest(resource, command, payload, endpoint=endpoint) + self.assertEqual(ret_json["payload"]["block_num"], lib_block_num) + self.assertEqual(ret_json["payload"]["header"]["producer"], "eosio") + # and by id + ret_json = self.nodeos.processUrllibRequest(resource, command, {"block_num_or_id":ret_json["payload"]["id"]}, endpoint=endpoint) + self.assertEqual(ret_json["payload"]["block_num"], lib_block_num) + self.assertEqual(ret_json["payload"]["header"]["producer"], "eosio") + # get_block_header_state with block far in future + payload = {"block_num_or_id":head_block_num+2000000} + ret_json = self.nodeos.processUrllibRequest(resource, command, payload, endpoint=endpoint) + self.assertEqual(ret_json["code"], 400) + self.assertEqual(ret_json["error"]["code"], 3100002) + #invalid num and invalid sha256 + payload = {"block_num_or_id":"spoon was here"} + ret_json = self.nodeos.processUrllibRequest(resource, command, payload, endpoint=endpoint) + self.assertEqual(ret_json["code"], 500) + self.assertEqual(ret_json["error"]["code"], 3010008) + # get_account with empty parameter command = "get_account" ret_json = self.nodeos.processUrllibRequest(resource, command, endpoint=endpoint)