diff --git a/docs/02_cleos/03_command-reference/get/transaction-status.md b/docs/02_cleos/03_command-reference/get/transaction-status.md new file mode 100644 index 0000000000..5ad7424b73 --- /dev/null +++ b/docs/02_cleos/03_command-reference/get/transaction-status.md @@ -0,0 +1,35 @@ +## Description + +Gets current blockchain state and, if available, transaction information given the transaction id + +## Position Parameters + +- `id` _TEXT_ - The string ID of the transaction to retrieve status about (required) + +## Options +- `-h` - --help Print this help message and exit +## Example + + +```sh +cleos get transaction-status 6438df82216dfaf46978f703fb818b49110dbfc5d9b521b5d08c342277438b29 +``` + +This command simply returns the current chain status and transaction status information (if available). + +```json +{ + "state": "IN_BLOCK", + "block_number": 90, + "block_id": "0000005accfd59ba80a05380f60d51687406337b2aedd28b7daa33fdb8c16b5a", + "block_timestamp": "2022-04-27T16:11:26.500", + "expiration": "2022-04-27T16:11:56.000", + "head_number": 186, + "head_id": "000000bab27da51f76f483bb629b532510c22e2eb1acc632f5b37b421adecf63", + "head_timestamp": "2022-04-27T16:12:14.500", + "irreversible_number": 25, + "irreversible_id": "0000001922118216bc16bf2c60d950598d80af3beca820eab751f7beecdb29e4", + "irreversible_timestamp": "2022-04-27T16:10:54.000", + "last_tracked_block_id": "000000129cee97f3e27312f0184d52d006a470f0e620553dfb4c5b4f3c856ab2" +} +``` diff --git a/plugins/chain_api_plugin/chain_api_plugin.cpp b/plugins/chain_api_plugin/chain_api_plugin.cpp index 8945333754..1794b4c23c 100644 --- a/plugins/chain_api_plugin/chain_api_plugin.cpp +++ b/plugins/chain_api_plugin/chain_api_plugin.cpp @@ -161,6 +161,7 @@ void chain_api_plugin::plugin_startup() { CHAIN_RO_CALL(abi_bin_to_json, 200), CHAIN_RO_CALL(get_required_keys, 200), CHAIN_RO_CALL(get_transaction_id, 200), + CHAIN_RO_CALL_WITH_400(get_transaction_status, 200), CHAIN_RO_CALL_ASYNC_WITH_400(compute_transaction, chain_apis::read_only::compute_transaction_results, 200), CHAIN_RW_CALL_ASYNC(push_block, chain_apis::read_write::push_block_results, 202), CHAIN_RW_CALL_ASYNC(push_transaction, chain_apis::read_write::push_transaction_results, 202), diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index fbb74d5c1b..3614f60256 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -794,9 +794,15 @@ void chain_plugin::plugin_initialize(const variables_map& options) { my->_trx_signals_processor.emplace(); if (my->_trx_finality_status_processing) { my->_trx_signals_processor->register_callbacks( - []( const chain::signals_processor::trx_deque& trxs, const chain::block_state_ptr& blk ) {}, - []( const chain::block_state_ptr& blk ) {}, - []( uint32_t block_num ) {} + [this]( const chain::signals_processor::trx_deque& trxs, const chain::block_state_ptr& blk ) { + my->_trx_finality_status_processing->signal_applied_transactions(trxs, blk); + }, + [this]( const chain::block_state_ptr& blk ) { + my->_trx_finality_status_processing->signal_irreversible_block(blk); + }, + [this]( uint32_t block_num ) { + my->_trx_finality_status_processing->signal_block_start(block_num); + } ); } } @@ -1362,7 +1368,7 @@ chain_apis::read_write chain_plugin::get_read_write_api() { } chain_apis::read_only chain_plugin::get_read_only_api() const { - return chain_apis::read_only(chain(), my->_account_query_db, get_abi_serializer_max_time(), my->producer_plug); + return chain_apis::read_only(chain(), my->_account_query_db, get_abi_serializer_max_time(), my->producer_plug, my->_trx_finality_status_processing.get()); } @@ -1683,6 +1689,39 @@ read_only::get_info_results read_only::get_info(const read_only::get_info_params }; } +read_only::get_transaction_status_results read_only::get_transaction_status(const read_only::get_transaction_status_params& param) const { + transaction_id_type input_id; + auto input_id_length = param.id.size(); + try + { + FC_ASSERT(input_id_length <= 64, "hex string is too long to represent an actual transaction id"); + FC_ASSERT(input_id_length >= 8, "hex string representing transaction id should be at least 8 characters long to avoid excessive collisions"); + input_id = transaction_id_type(param.id); + } + EOS_RETHROW_EXCEPTIONS(transaction_id_type_exception, "Invalid transaction ID: ${transaction_id}", ("transaction_id", param.id)) + + EOS_ASSERT(trx_finality_status_proc, unsupported_feature, "Transaction Status Interface not enabled. To enable, configure nodeos with '--transaction-finality-status-max-storage-size-gb '."); + + trx_finality_status_processing::chain_state ch_state = trx_finality_status_proc->get_chain_state(); + + auto trx_st = trx_finality_status_proc->get_trx_state(input_id); + + return { + trx_st ? trx_st->status : "UNKNOWN", + trx_st ? std::optional(chain::block_header::num_from_id(trx_st->block_id)) : std::optional{}, + trx_st ? std::optional(trx_st->block_id) : std::optional{}, + trx_st ? std::optional(trx_st->block_timestamp) : std::optional{}, + trx_st ? std::optional(trx_st->expiration) : std::optional{}, + chain::block_header::num_from_id(ch_state.head_id), + ch_state.head_id, + ch_state.head_block_timestamp, + chain::block_header::num_from_id(ch_state.irr_id), + ch_state.irr_id, + ch_state.irr_block_timestamp, + ch_state.last_tracked_block_id + }; +} + read_only::get_activated_protocol_features_results read_only::get_activated_protocol_features( const read_only::get_activated_protocol_features_params& params )const { read_only::get_activated_protocol_features_results result; 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 250080095e..2b6097b291 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -18,6 +18,7 @@ #include #include +#include #include @@ -86,12 +87,13 @@ class read_only { const fc::microseconds abi_serializer_max_time; bool shorten_abi_errors = true; const producer_plugin* producer_plug; + const trx_finality_status_processing* trx_finality_status_proc; public: static const string KEYi64; - read_only(const controller& db, const std::optional& aqdb, const fc::microseconds& abi_serializer_max_time, const producer_plugin* producer_plug) - : db(db), aqdb(aqdb), abi_serializer_max_time(abi_serializer_max_time), producer_plug(producer_plug) { + read_only(const controller& db, const std::optional& aqdb, const fc::microseconds& abi_serializer_max_time, const producer_plugin* producer_plug, const trx_finality_status_processing* trx_finality_status_proc) + : db(db), aqdb(aqdb), abi_serializer_max_time(abi_serializer_max_time), producer_plug(producer_plug), trx_finality_status_proc(trx_finality_status_proc) { } void validate() const {} @@ -126,6 +128,27 @@ class read_only { }; get_info_results get_info(const get_info_params&) const; + struct get_transaction_status_params { + string id; + }; + + struct get_transaction_status_results { + string state; + std::optional block_number; + std::optional block_id; + std::optional block_timestamp; + std::optional expiration; + uint32_t head_number = 0; + chain::block_id_type head_id; + fc::time_point head_timestamp; + uint32_t irreversible_number = 0; + chain::block_id_type irreversible_id; + fc::time_point irreversible_timestamp; + chain::block_id_type last_tracked_block_id; + }; + get_transaction_status_results get_transaction_status(const get_transaction_status_params& params) const; + + struct get_activated_protocol_features_params { std::optional lower_bound; std::optional upper_bound; @@ -790,6 +813,9 @@ FC_REFLECT(eosio::chain_apis::read_only::get_info_results, (head_block_id)(head_block_time)(head_block_producer) (virtual_block_cpu_limit)(virtual_block_net_limit)(block_cpu_limit)(block_net_limit) (server_version_string)(fork_db_head_block_num)(fork_db_head_block_id)(server_full_version_string)(total_cpu_weight)(total_net_weight) ) +FC_REFLECT(eosio::chain_apis::read_only::get_transaction_status_params, (id) ) +FC_REFLECT(eosio::chain_apis::read_only::get_transaction_status_results, (state)(block_number)(block_id)(block_timestamp)(expiration)(head_number)(head_id) + (head_timestamp)(irreversible_number)(irreversible_id)(irreversible_timestamp)(last_tracked_block_id) ) FC_REFLECT(eosio::chain_apis::read_only::get_activated_protocol_features_params, (lower_bound)(upper_bound)(limit)(search_by_block_num)(reverse) ) FC_REFLECT(eosio::chain_apis::read_only::get_activated_protocol_features_results, (activated_protocol_features)(more) ) FC_REFLECT(eosio::chain_apis::read_only::get_block_params, (block_num_or_id)) diff --git a/plugins/chain_plugin/include/eosio/chain_plugin/trx_finality_status_processing.hpp b/plugins/chain_plugin/include/eosio/chain_plugin/trx_finality_status_processing.hpp index ddaa717d39..95037100c5 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/trx_finality_status_processing.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/trx_finality_status_processing.hpp @@ -18,15 +18,18 @@ namespace eosio::chain_apis { public: struct chain_state { - chain::block_id_type head_id; - chain::block_id_type irr_id; - chain::block_id_type last_tracked_block_id; + chain::block_id_type head_id; + chain::block_timestamp_type head_block_timestamp; + chain::block_id_type irr_id; + chain::block_timestamp_type irr_block_timestamp; + chain::block_id_type last_tracked_block_id; }; struct trx_state { chain::block_id_type block_id; fc::time_point block_timestamp; fc::time_point received; + fc::time_point expiration; std::string status; }; diff --git a/plugins/chain_plugin/test/test_trx_finality_status_processing.cpp b/plugins/chain_plugin/test/test_trx_finality_status_processing.cpp index 99e0097c02..3edfdcdda8 100644 --- a/plugins/chain_plugin/test/test_trx_finality_status_processing.cpp +++ b/plugins/chain_plugin/test/test_trx_finality_status_processing.cpp @@ -255,6 +255,7 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { BOOST_REQUIRE(ts); BOOST_CHECK(ts->block_id == bs_20->id); BOOST_CHECK(ts->block_timestamp == bs_20->block->timestamp); + BOOST_CHECK(fc::time_point_sec(ts->expiration) == (std::get<1>(trx_pairs_20[1])->expiration())); BOOST_CHECK_EQUAL(std::string(ts->received), pre_block_20_time); BOOST_CHECK_EQUAL(ts->status, "IN_BLOCK"); @@ -706,6 +707,7 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_logic) { try { cs = status.get_chain_state(); BOOST_CHECK(cs.head_id == bs_19_alt->id); BOOST_CHECK(cs.irr_id == bs_19_alt->id); + BOOST_CHECK(cs.irr_block_timestamp == bs_19_alt->block->timestamp); BOOST_CHECK(cs.last_tracked_block_id == bs_19_alt->id); @@ -1004,7 +1006,9 @@ BOOST_AUTO_TEST_CASE(trx_finality_status_storage_reduction) { try { cs = status.get_chain_state(); BOOST_CHECK(cs.head_id == b_12.bs->id); + BOOST_CHECK(cs.head_block_timestamp == b_12.bs->block->timestamp); BOOST_CHECK(cs.irr_id == eosio::chain::block_id_type{}); + BOOST_CHECK(cs.irr_block_timestamp == eosio::chain::block_timestamp_type{}); BOOST_CHECK(cs.last_tracked_block_id == b_03.bs->id); diff --git a/plugins/chain_plugin/trx_finality_status_processing.cpp b/plugins/chain_plugin/trx_finality_status_processing.cpp index 5d07d6650d..9967ca30f0 100644 --- a/plugins/chain_plugin/trx_finality_status_processing.cpp +++ b/plugins/chain_plugin/trx_finality_status_processing.cpp @@ -25,7 +25,9 @@ namespace eosio::chain_apis { fc::tracked_storage _storage; uint32_t _last_proc_block_num = finality_status::no_block_num; chain::block_id_type _head_block_id; + chain::block_timestamp_type _head_block_timestamp; chain::block_id_type _irr_block_id; + chain::block_timestamp_type _irr_block_timestamp; chain::block_id_type _last_tracked_block_id; const fc::microseconds _success_duration; const fc::microseconds _failure_duration; @@ -44,6 +46,7 @@ namespace eosio::chain_apis { void trx_finality_status_processing::signal_irreversible_block( const chain::block_state_ptr& bsp ) { _my->_irr_block_id = bsp->id; + _my->_irr_block_timestamp = bsp->block->timestamp; } void trx_finality_status_processing::signal_block_start( uint32_t block_num ) { @@ -57,6 +60,7 @@ namespace eosio::chain_apis { chain::block_timestamp_type block_timestamp; if (bsp) { _head_block_id = block_id; + _head_block_timestamp = bsp->block->timestamp; block_timestamp = bsp->block->timestamp; if (chain::block_header::num_from_id(_head_block_id) <= _last_proc_block_num) { handle_rollback(); @@ -235,7 +239,7 @@ namespace eosio::chain_apis { } trx_finality_status_processing::chain_state trx_finality_status_processing::get_chain_state() const { - return { .head_id = _my->_head_block_id, .irr_id = _my->_irr_block_id, .last_tracked_block_id = _my->_last_tracked_block_id }; + return { .head_id = _my->_head_block_id, .head_block_timestamp = _my->_head_block_timestamp, .irr_id = _my->_irr_block_id, .irr_block_timestamp = _my->_irr_block_timestamp, .last_tracked_block_id = _my->_last_tracked_block_id }; } std::optional trx_finality_status_processing::get_trx_state( const chain::transaction_id_type& id ) const { @@ -258,7 +262,7 @@ namespace eosio::chain_apis { const auto lib = chain::block_header::num_from_id(_my->_irr_block_id); status = (block_num > lib) ? "IN_BLOCK" : "IRREVERSIBLE"; } - return trx_finality_status_processing::trx_state{ .block_id = iter->block_id, .block_timestamp = iter->block_timestamp, .received = iter->received, .status = status }; + return trx_finality_status_processing::trx_state{ .block_id = iter->block_id, .block_timestamp = iter->block_timestamp, .received = iter->received, .expiration = iter->trx_expiry, .status = status }; } size_t trx_finality_status_processing::get_storage_memory_size() const { diff --git a/programs/cleos/httpc.hpp b/programs/cleos/httpc.hpp index d8ccf45add..f0e60f7494 100644 --- a/programs/cleos/httpc.hpp +++ b/programs/cleos/httpc.hpp @@ -80,6 +80,7 @@ namespace eosio { namespace client { namespace http { const string chain_func_base = "/v1/chain"; const string get_info_func = chain_func_base + "/get_info"; + const string get_transaction_status_func = chain_func_base + "/get_transaction_status"; const string send_txn_func = chain_func_base + "/send_transaction"; const string push_txn_func = chain_func_base + "/push_transaction"; const string send2_txn_func = chain_func_base + "/send_transaction2"; diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index cf85a5b6cc..90cb4537de 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -2625,6 +2625,15 @@ int main( int argc, char** argv ) { std::cout << fc::json::to_pretty_string(get_info()) << std::endl; }); + // get transaction status + string status_transaction_id_str; + auto getTransactionStatus = get->add_subcommand("transaction-status", localized("Get transaction status information")); + getTransactionStatus->add_option("id", status_transaction_id_str, localized("ID of the transaction to retrieve"))->required(); + getTransactionStatus->callback([&status_transaction_id_str] { + auto arg= fc::mutable_variant_object( "id", status_transaction_id_str); + std::cout << fc::json::to_pretty_string(call(get_transaction_status_func, arg)) << std::endl; + }); + // get block string blockArg; bool get_bhs = false; diff --git a/tests/chain_plugin_tests.cpp b/tests/chain_plugin_tests.cpp index ec15d9cff9..c130c672d1 100644 --- a/tests/chain_plugin_tests.cpp +++ b/tests/chain_plugin_tests.cpp @@ -89,7 +89,7 @@ BOOST_FIXTURE_TEST_CASE( get_block_with_invalid_abi, TESTER ) try { char headnumstr[20]; sprintf(headnumstr, "%d", headnum); chain_apis::read_only::get_block_params param{headnumstr}; - chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), {}); + chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), {}, {}); // block should be decoded successfully std::string block_str = json::to_pretty_string(plugin.get_block(param)); diff --git a/tests/get_table_tests.cpp b/tests/get_table_tests.cpp index 9525e9f41a..f842c8fd54 100644 --- a/tests/get_table_tests.cpp +++ b/tests/get_table_tests.cpp @@ -89,7 +89,7 @@ BOOST_FIXTURE_TEST_CASE( get_scope_test, TESTER ) try { produce_blocks(1); // iterate over scope - eosio::chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), {}); + eosio::chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), {}, {}); eosio::chain_apis::read_only::get_table_by_scope_params param{"eosio.token"_n, "accounts"_n, "inita", "", 10}; eosio::chain_apis::read_only::get_table_by_scope_result result = plugin.read_only::get_table_by_scope(param); @@ -194,7 +194,7 @@ BOOST_FIXTURE_TEST_CASE( get_table_test, TESTER ) try { produce_blocks(1); // get table: normal case - eosio::chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), {}); + eosio::chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), {}, {}); eosio::chain_apis::read_only::get_table_rows_params p; p.code = "eosio.token"_n; p.scope = "inita"; @@ -363,7 +363,7 @@ BOOST_FIXTURE_TEST_CASE( get_table_by_seckey_test, TESTER ) try { produce_blocks(1); // get table: normal case - eosio::chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), {}); + eosio::chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), {}, {}); eosio::chain_apis::read_only::get_table_rows_params p; p.code = "eosio"_n; p.scope = "eosio"; @@ -515,7 +515,7 @@ BOOST_FIXTURE_TEST_CASE( get_table_next_key_test, TESTER ) try { // } - chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), {}); + chain_apis::read_only plugin(*(this->control), {}, fc::microseconds::maximum(), {}, {}); chain_apis::read_only::get_table_rows_params params{ .json=true, .code="test"_n,