diff --git a/libraries/chain/include/eos/chain/exceptions.hpp b/libraries/chain/include/eos/chain/exceptions.hpp index d9acb6f49f1..ab200fed3ab 100644 --- a/libraries/chain/include/eos/chain/exceptions.hpp +++ b/libraries/chain/include/eos/chain/exceptions.hpp @@ -52,6 +52,7 @@ namespace eos { namespace chain { FC_DECLARE_DERIVED_EXCEPTION( tx_missing_scope, eos::chain::transaction_exception, 3030008, "missing required scope" ) FC_DECLARE_DERIVED_EXCEPTION( tx_missing_recipient, eos::chain::transaction_exception, 3030009, "missing required recipient" ) FC_DECLARE_DERIVED_EXCEPTION( checktime_exceeded, eos::chain::transaction_exception, 3030010, "allotted processing time was exceeded" ) + FC_DECLARE_DERIVED_EXCEPTION( unknown_transaction_exception, eos::chain::transaction_exception, 3030011, "unknown transaction" ) FC_DECLARE_DERIVED_EXCEPTION( invalid_pts_address, eos::chain::utility_exception, 3060001, "invalid pts address" ) FC_DECLARE_DERIVED_EXCEPTION( insufficient_feeds, eos::chain::chain_exception, 37006, "insufficient feeds" ) diff --git a/libraries/chain/include/eos/chain/types.hpp b/libraries/chain/include/eos/chain/types.hpp index 62949e704a7..0cfb844caa5 100644 --- a/libraries/chain/include/eos/chain/types.hpp +++ b/libraries/chain/include/eos/chain/types.hpp @@ -171,6 +171,7 @@ namespace eos { namespace chain { transaction_object_type, producer_object_type, chain_property_object_type, + transaction_history_object_type, ///< Defined by account_history_plugin balance_object_type, ///< Defined by native_contract library staked_balance_object_type, ///< Defined by native_contract library producer_votes_object_type, ///< Defined by native_contract library @@ -216,6 +217,7 @@ FC_REFLECT_ENUM(eos::chain::object_type, (transaction_object_type) (producer_object_type) (chain_property_object_type) + (transaction_history_object_type) (balance_object_type) (staked_balance_object_type) (producer_votes_object_type) diff --git a/libraries/chainbase b/libraries/chainbase index f1a150f97bf..d48ebabf56b 160000 --- a/libraries/chainbase +++ b/libraries/chainbase @@ -1 +1 @@ -Subproject commit f1a150f97bfde1b10113b77c22206e5c00158205 +Subproject commit d48ebabf56b4115753fcabb7648a0ffcf3b0f5e9 diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 9b0480effa2..85eaccbaafb 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -4,3 +4,5 @@ add_subdirectory(database_plugin) add_subdirectory(chain_plugin) add_subdirectory(chain_api_plugin) add_subdirectory(producer_plugin) +add_subdirectory(account_history_plugin) +add_subdirectory(account_history_api_plugin) diff --git a/plugins/account_history_api_plugin/CMakeLists.txt b/plugins/account_history_api_plugin/CMakeLists.txt new file mode 100644 index 00000000000..f6938efd6a5 --- /dev/null +++ b/plugins/account_history_api_plugin/CMakeLists.txt @@ -0,0 +1,16 @@ +file(GLOB HEADERS "include/eos/account_history_api_plugin/*.hpp") +add_library( account_history_api_plugin + account_history_api_plugin.cpp + ${HEADERS} ) + +target_link_libraries( account_history_api_plugin account_history_plugin chain_plugin http_plugin appbase ) +target_include_directories( account_history_api_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) + +install( TARGETS + account_history_api_plugin + + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) +install( FILES ${HEADERS} DESTINATION "include/eos/account_history_api_plugin" ) diff --git a/plugins/account_history_api_plugin/account_history_api_plugin.cpp b/plugins/account_history_api_plugin/account_history_api_plugin.cpp new file mode 100644 index 00000000000..aea78844be0 --- /dev/null +++ b/plugins/account_history_api_plugin/account_history_api_plugin.cpp @@ -0,0 +1,48 @@ +#include +#include +#include + +#include + +namespace eos { + +using namespace eos; + +account_history_api_plugin::account_history_api_plugin(){} +account_history_api_plugin::~account_history_api_plugin(){} + +void account_history_api_plugin::set_program_options(options_description&, options_description&) {} +void account_history_api_plugin::plugin_initialize(const variables_map&) {} + +#define CALL(api_name, api_handle, api_namespace, call_name) \ +{std::string("/v1/" #api_name "/" #call_name), \ + [this, api_handle](string, string body, url_response_callback cb) mutable { \ + try { \ + if (body.empty()) body = "{}"; \ + auto result = api_handle.call_name(fc::json::from_string(body).as()); \ + cb(200, fc::json::to_string(result)); \ + } catch (fc::eof_exception) { \ + cb(400, "Invalid arguments"); \ + elog("Unable to parse arguments: ${args}", ("args", body)); \ + } catch (fc::exception& e) { \ + cb(500, e.to_detail_string()); \ + elog("Exception encountered while processing ${call}: ${e}", ("call", #api_name "." #call_name)("e", e)); \ + } \ + }} + +#define CHAIN_RO_CALL(call_name) CALL(account_history, ro_api, account_history_apis::read_only, call_name) +#define CHAIN_RW_CALL(call_name) CALL(account_history, rw_api, account_history_apis::read_write, call_name) + +void account_history_api_plugin::plugin_startup() { + ilog( "starting account_history_api_plugin" ); + auto ro_api = app().get_plugin().get_read_only_api(); + auto rw_api = app().get_plugin().get_read_write_api(); + + app().get_plugin().add_api({ + CHAIN_RO_CALL(get_transaction) + }); +} + +void account_history_api_plugin::plugin_shutdown() {} + +} diff --git a/plugins/account_history_api_plugin/include/eos/account_history_api_plugin/account_history_api_plugin.hpp b/plugins/account_history_api_plugin/include/eos/account_history_api_plugin/account_history_api_plugin.hpp new file mode 100644 index 00000000000..dc2d7684bea --- /dev/null +++ b/plugins/account_history_api_plugin/include/eos/account_history_api_plugin/account_history_api_plugin.hpp @@ -0,0 +1,28 @@ +#pragma once +#include +#include +#include + +#include + +namespace eos { + + using namespace appbase; + + class account_history_api_plugin : public plugin { + public: + APPBASE_PLUGIN_REQUIRES((account_history_plugin)(chain_plugin)(http_plugin)) + + account_history_api_plugin(); + virtual ~account_history_api_plugin(); + + virtual void set_program_options(options_description&, options_description&) override; + + void plugin_initialize(const variables_map&); + void plugin_startup(); + void plugin_shutdown(); + + private: + }; + +} diff --git a/plugins/account_history_plugin/CMakeLists.txt b/plugins/account_history_plugin/CMakeLists.txt new file mode 100644 index 00000000000..86e820ca14c --- /dev/null +++ b/plugins/account_history_plugin/CMakeLists.txt @@ -0,0 +1,16 @@ +file(GLOB HEADERS "include/eos/account_history_plugin/*.hpp") +add_library( account_history_plugin + account_history_plugin.cpp + ${HEADERS} ) + +target_link_libraries( account_history_plugin chain_plugin eos_chain appbase ) +target_include_directories( account_history_plugin PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) + +install( TARGETS + account_history_plugin + + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) +install( FILES ${HEADERS} DESTINATION "include/eos/account_history_plugin" ) diff --git a/plugins/account_history_plugin/account_history_plugin.cpp b/plugins/account_history_plugin/account_history_plugin.cpp new file mode 100644 index 00000000000..d8e891b7bb4 --- /dev/null +++ b/plugins/account_history_plugin/account_history_plugin.cpp @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace fc { class variant; } + +namespace eos { + +using chain::block_id_type; +using chain::ProcessedTransaction; +using chain::signed_block; +using boost::multi_index_container; +using chain::transaction_id_type; +using namespace boost::multi_index; + +class account_history_plugin_impl { +public: + ProcessedTransaction get_transaction(const chain::transaction_id_type& transaction_id) const; + void applied_block(const signed_block&); + chain_plugin* chain_plug; +private: + + optional find_block_id(const transaction_id_type& transaction_id) const; +}; + +optional account_history_plugin_impl::find_block_id(const transaction_id_type& transaction_id) const +{ + const auto& db = chain_plug->chain().get_database(); + optional block_id; + db.with_read_lock( [&]() { + const auto& trx_idx = db.get_index(); + auto transaction_history = trx_idx.find( transaction_id ); + if (transaction_history != trx_idx.end()) + block_id = transaction_history->block_id; + } ); + return block_id; +} + +ProcessedTransaction account_history_plugin_impl::get_transaction(const chain::transaction_id_type& transaction_id) const +{ + auto block_id = find_block_id(transaction_id); + if( block_id.valid() ) + { + auto block = chain_plug->chain().fetch_block_by_id(*block_id); + if (block.valid()) + { + for (const auto& cycle : block->cycles) + for (const auto& thread : cycle) + for (const auto& trx : thread.user_input) + if (trx.id() == transaction_id) + return trx; + } + + // ERROR in indexing logic + FC_ASSERT(block, "Transaction with ID ${tid} was indexed as being in block ID ${bid}, but no such block was found", ("tid", transaction_id)("bid", block_id)); + FC_THROW("Transaction with ID ${tid} was indexed as being in block ID ${bid}, but was not found in that block", ("tid", transaction_id)("bid", block_id)); + } + +#warning TODO: lookup of recent transactions + FC_THROW_EXCEPTION(chain::unknown_transaction_exception, + "Could not find transaction for: ${id}", ("id", transaction_id.str())); +} + +void account_history_plugin_impl::applied_block(const signed_block& block) +{ + const auto block_id = block.id(); + auto& db = chain_plug->chain().get_mutable_database(); + for (const auto& cycle : block.cycles) + for (const auto& thread : cycle) + for (const auto& trx : thread.user_input) { + db.create([&block_id,&trx](transaction_history_object& transaction_history) { + transaction_history.block_id = block_id; + transaction_history.transaction_id = trx.id(); + }); + } +} + + +account_history_plugin::account_history_plugin() +:my(new account_history_plugin_impl()) +{ +} + +account_history_plugin::~account_history_plugin() +{ +} + +void account_history_plugin::set_program_options(options_description& cli, options_description& cfg) +{ +} + +void account_history_plugin::plugin_initialize(const variables_map& options) +{ +} + +void account_history_plugin::plugin_startup() +{ + my->chain_plug = app().find_plugin(); + auto& db = my->chain_plug->chain().get_mutable_database(); + db.add_index(); + + my->chain_plug->chain().applied_block.connect ([&impl = my](const signed_block& block) { + impl->applied_block(block); + }); +} + +void account_history_plugin::plugin_shutdown() +{ +} + +namespace account_history_apis { + +read_only::get_transaction_results read_only::get_transaction(const read_only::get_transaction_params& params) const +{ + auto trx = account_history->get_transaction(params.transaction_id); + return { account_history->chain_plug->chain().transaction_to_variant(trx) }; +} + +} // namespace account_history_apis +} // namespace eos diff --git a/plugins/account_history_plugin/include/eos/account_history_plugin/account_history_object.hpp b/plugins/account_history_plugin/include/eos/account_history_plugin/account_history_object.hpp new file mode 100644 index 00000000000..2b6ab38a7a1 --- /dev/null +++ b/plugins/account_history_plugin/include/eos/account_history_plugin/account_history_object.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace eos { +using chain::block_id_type; +using chain::transaction_id_type; +using namespace boost::multi_index; + +class transaction_history_object : public chainbase::object { + OBJECT_CTOR(transaction_history_object) + + id_type id; + block_id_type block_id; + transaction_id_type transaction_id; +}; + +struct by_id; +struct by_trx_id; +using transaction_history_multi_index = chainbase::shared_multi_index_container< + transaction_history_object, + indexed_by< + ordered_unique, BOOST_MULTI_INDEX_MEMBER(transaction_history_object, transaction_history_object::id_type, id)>, + hashed_unique, BOOST_MULTI_INDEX_MEMBER(transaction_history_object, transaction_id_type, transaction_id), std::hash> + > +>; + +typedef chainbase::generic_index transaction_history_index; + +} + +CHAINBASE_SET_INDEX_TYPE( eos::transaction_history_object, eos::transaction_history_multi_index ) + +FC_REFLECT( eos::transaction_history_object, (block_id)(transaction_id) ) + diff --git a/plugins/account_history_plugin/include/eos/account_history_plugin/account_history_plugin.hpp b/plugins/account_history_plugin/include/eos/account_history_plugin/account_history_plugin.hpp new file mode 100644 index 00000000000..a1acdfcc770 --- /dev/null +++ b/plugins/account_history_plugin/include/eos/account_history_plugin/account_history_plugin.hpp @@ -0,0 +1,72 @@ +#pragma once +#include + +#include + +namespace fc { class variant; } + +namespace eos { + using chain::transaction_id_type; + using std::shared_ptr; + using namespace appbase; + using chain::Name; + using fc::optional; + using chain::uint128_t; + + typedef shared_ptr account_history_ptr; + typedef shared_ptr account_history_const_ptr; + +namespace account_history_apis { +struct empty{}; + +class read_only { + account_history_const_ptr account_history; + +public: + read_only(account_history_const_ptr&& account_history) + : account_history(account_history) {} + + struct get_transaction_params { + chain::transaction_id_type transaction_id; + }; + struct get_transaction_results { + fc::variant transaction; + }; + + get_transaction_results get_transaction(const get_transaction_params& params) const; + +}; + +class read_write { + account_history_ptr account_history; + +public: + read_write(account_history_ptr account_history) : account_history(account_history) {} +}; +} // namespace account_history_apis + +class account_history_plugin : public plugin { +public: + APPBASE_PLUGIN_REQUIRES((chain_plugin)) + + account_history_plugin(); + virtual ~account_history_plugin(); + + virtual void set_program_options(options_description& cli, options_description& cfg) override; + + void plugin_initialize(const variables_map& options); + void plugin_startup(); + void plugin_shutdown(); + + account_history_apis::read_only get_read_only_api() const { return account_history_apis::read_only(account_history_const_ptr(my)); } + account_history_apis::read_write get_read_write_api() { return account_history_apis::read_write(my); } + +private: + account_history_ptr my; +}; + +} + +FC_REFLECT(eos::account_history_apis::empty, ) +FC_REFLECT(eos::account_history_apis::read_only::get_transaction_params, (transaction_id) ) +FC_REFLECT(eos::account_history_apis::read_only::get_transaction_results, (transaction) ) diff --git a/programs/eosc/main.cpp b/programs/eosc/main.cpp index 9d0bfdafbeb..cb394083862 100644 --- a/programs/eosc/main.cpp +++ b/programs/eosc/main.cpp @@ -30,12 +30,15 @@ string program = "eosc"; string host = "localhost"; uint32_t port = 8888; -string func_base = "/v1/chain"; -string get_info_func = func_base + "/get_info"; -string push_txn_func = func_base + "/push_transaction"; -string json_to_bin_func = func_base + "/abi_json_to_bin"; -string get_block_func = func_base + "/get_block"; -string get_account_func = func_base + "/get_account"; +const string chain_func_base = "/v1/chain"; +const string get_info_func = chain_func_base + "/get_info"; +const string push_txn_func = chain_func_base + "/push_transaction"; +const string json_to_bin_func = chain_func_base + "/abi_json_to_bin"; +const string get_block_func = chain_func_base + "/get_block"; +const string get_account_func = chain_func_base + "/get_account"; + +const string account_history_func_base = "/v1/account_history"; +const string get_transaction_func = account_history_func_base + "/get_transaction"; inline std::vector sort_names( std::vector&& names ) { @@ -364,6 +367,10 @@ int send_command (const vector &cmd_line) } else if( command == "do" ) { + } else if( command == "transaction" ) { + FC_ASSERT( cmd_line.size() == 2 ); + auto arg= fc::mutable_variant_object( "transaction_id", cmd_line[1]); + std::cout << fc::json::to_pretty_string( call( get_transaction_func, arg) ) << std::endl; } return 0; } @@ -440,7 +447,6 @@ int main( int argc, char** argv ) { } } - tic = read_line_and_split(cmd_line, tic); if (tic != '\0') { continue; diff --git a/programs/eosd/CMakeLists.txt b/programs/eosd/CMakeLists.txt index 3377b284e5d..e29f68199ca 100644 --- a/programs/eosd/CMakeLists.txt +++ b/programs/eosd/CMakeLists.txt @@ -10,7 +10,7 @@ if( GPERFTOOLS_FOUND ) endif() target_link_libraries( eosd - PRIVATE appbase chain_api_plugin producer_plugin chain_plugin net_plugin http_plugin eos_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) + PRIVATE appbase account_history_api_plugin account_history_plugin chain_api_plugin producer_plugin chain_plugin net_plugin http_plugin eos_chain fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) install( TARGETS eosd diff --git a/programs/eosd/main.cpp b/programs/eosd/main.cpp index a317fb317b7..9203f9a92b7 100644 --- a/programs/eosd/main.cpp +++ b/programs/eosd/main.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include @@ -22,6 +24,8 @@ int main(int argc, char** argv) app().register_plugin(); app().register_plugin(); app().register_plugin(); + app().register_plugin(); + app().register_plugin(); if(!app().initialize(argc, argv)) return -1; app().startup();