diff --git a/plugins/account_history_api_plugin/account_history_api_plugin.cpp b/plugins/account_history_api_plugin/account_history_api_plugin.cpp index aea78844be0..5705c5ee263 100644 --- a/plugins/account_history_api_plugin/account_history_api_plugin.cpp +++ b/plugins/account_history_api_plugin/account_history_api_plugin.cpp @@ -39,7 +39,8 @@ void account_history_api_plugin::plugin_startup() { auto rw_api = app().get_plugin().get_read_write_api(); app().get_plugin().add_api({ - CHAIN_RO_CALL(get_transaction) + CHAIN_RO_CALL(get_transaction), + CHAIN_RO_CALL(get_transactions) }); } diff --git a/plugins/account_history_plugin/account_history_plugin.cpp b/plugins/account_history_plugin/account_history_plugin.cpp index d8e891b7bb4..adcb0a938cf 100644 --- a/plugins/account_history_plugin/account_history_plugin.cpp +++ b/plugins/account_history_plugin/account_history_plugin.cpp @@ -28,12 +28,20 @@ using namespace boost::multi_index; class account_history_plugin_impl { public: ProcessedTransaction get_transaction(const chain::transaction_id_type& transaction_id) const; + vector get_transactions(const AccountName& account_name, const optional& after_transaction_id) const; void applied_block(const signed_block&); + chain_plugin* chain_plug; + static const int DEFAULT_TRANSACTION_LIMIT; + int transactions_limit = DEFAULT_TRANSACTION_LIMIT; + std::set filter_on; private: optional find_block_id(const transaction_id_type& transaction_id) const; + ProcessedTransaction find_transaction(const chain::transaction_id_type& transaction_id, const block_id_type& block_id) const; + bool scope_relevant(const eos::types::Vector& scope); }; +const int account_history_plugin_impl::DEFAULT_TRANSACTION_LIMIT = 100; optional account_history_plugin_impl::find_block_id(const transaction_id_type& transaction_id) const { @@ -48,24 +56,27 @@ optional account_history_plugin_impl::find_block_id(const transac return block_id; } +ProcessedTransaction account_history_plugin_impl::find_transaction(const chain::transaction_id_type& transaction_id, const chain::block_id_type& block_id) const +{ + auto block = chain_plug->chain().fetch_block_by_id(block_id); + 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)); + + 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_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)); +} + 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)); + return find_transaction(transaction_id, *block_id); } #warning TODO: lookup of recent transactions @@ -73,20 +84,73 @@ ProcessedTransaction account_history_plugin_impl::get_transaction(const chain::t "Could not find transaction for: ${id}", ("id", transaction_id.str())); } +vector account_history_plugin_impl::get_transactions(const AccountName& account_name, const optional& after_transaction_id) const +{ + const auto& db = chain_plug->chain().get_database(); + typedef std::map transaction_map; + transaction_map transactions; + + db.with_read_lock( [&]() { + const auto& account_idx = db.get_index(); + auto range = account_idx.equal_range( account_name ); + for (auto transaction_history = range.first; transaction_history != range.second; ++transaction_history) + { + transactions.emplace(std::make_pair(transaction_history->transaction_id, transaction_history->block_id)); + } + } ); + vector results; + transaction_map::const_iterator trans; + if (after_transaction_id.valid()) + { + trans = transactions.find(*after_transaction_id); + if (trans != transactions.cend()) + ++trans; + } + else + trans = transactions.cbegin(); + + for(; trans != transactions.end() && results.size() < transactions_limit; ++trans) + { + const auto trx = find_transaction(trans->first, trans->second); + const auto pretty_trx = chain_plug->chain().transaction_to_variant(trx); + results.emplace_back(account_history_apis::read_only::get_transaction_results{trans->first, pretty_trx}); + } + + return results; +} + 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(); + const bool check_relevance = filter_on.size(); 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(); - }); + for (const auto& trx : thread.user_input) + { + if (check_relevance && !scope_relevant(trx.scope)) + continue; + + for (const auto& account_name : trx.scope) + { + db.create([&block_id,&trx,&account_name](transaction_history_object& transaction_history) { + transaction_history.block_id = block_id; + transaction_history.transaction_id = trx.id(); + transaction_history.account_name = account_name; + }); + } } } +bool account_history_plugin_impl::scope_relevant(const eos::types::Vector& scope) +{ + for (const AccountName& account_name : scope) + if (filter_on.count(account_name)) + return true; + + return false; +} + account_history_plugin::account_history_plugin() :my(new account_history_plugin_impl()) @@ -99,10 +163,24 @@ account_history_plugin::~account_history_plugin() void account_history_plugin::set_program_options(options_description& cli, options_description& cfg) { + cfg.add_options() + ("filter_on_accounts,f", bpo::value>()->composing(), + "Track only transactions whose scopes involve the listed accounts. Default is to track all transactions.") + ("get-transactions-limit", bpo::value()->default_value(account_history_plugin_impl::DEFAULT_TRANSACTION_LIMIT), + "Limits the number of transactions returned for get_transactions") + ; } void account_history_plugin::plugin_initialize(const variables_map& options) { + my->transactions_limit = options.at("get-transactions-limit").as(); + + if(options.count("filter_on_accounts")) + { + auto foa = options.at("filter_on_accounts").as>(); + for(auto filter_account : foa) + my->filter_on.emplace(filter_account); + } } void account_history_plugin::plugin_startup() @@ -125,7 +203,12 @@ 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) }; + return { params.transaction_id, account_history->chain_plug->chain().transaction_to_variant(trx) }; +} + +read_only::get_transactions_results read_only::get_transactions(const read_only::get_transactions_params& params) const +{ + return { account_history->get_transactions(params.account_name, params.after_transaction_id) }; } } // namespace account_history_apis 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 index 2b6ab38a7a1..32c99447014 100644 --- 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 @@ -2,7 +2,20 @@ #include +namespace std { + + template<> + struct hash + { + size_t operator()( const eos::chain::AccountName& name )const + { + return (uint64_t)name; + } + }; +} + namespace eos { +using chain::AccountName; using chain::block_id_type; using chain::transaction_id_type; using namespace boost::multi_index; @@ -13,15 +26,18 @@ class transaction_history_object : public chainbase::object, 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> + hashed_non_unique, BOOST_MULTI_INDEX_MEMBER(transaction_history_object, transaction_id_type, transaction_id), std::hash>, + hashed_non_unique, BOOST_MULTI_INDEX_MEMBER(transaction_history_object, AccountName, account_name), std::hash> > >; @@ -31,5 +47,5 @@ typedef chainbase::generic_index transaction_hi CHAINBASE_SET_INDEX_TYPE( eos::transaction_history_object, eos::transaction_history_multi_index ) -FC_REFLECT( eos::transaction_history_object, (block_id)(transaction_id) ) +FC_REFLECT( eos::transaction_history_object, (block_id)(transaction_id)(account_name) ) 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 index a1acdfcc770..75ffcad2a40 100644 --- 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 @@ -30,11 +30,21 @@ class read_only { chain::transaction_id_type transaction_id; }; struct get_transaction_results { - fc::variant transaction; + chain::transaction_id_type transaction_id; + fc::variant transaction; }; get_transaction_results get_transaction(const get_transaction_params& params) const; + struct get_transactions_params { + chain::AccountName account_name; + optional after_transaction_id; + }; + struct get_transactions_results { + vector transactions; + }; + + get_transactions_results get_transactions(const get_transactions_params& params) const; }; class read_write { @@ -69,4 +79,6 @@ class account_history_plugin : public plugin { 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) ) +FC_REFLECT(eos::account_history_apis::read_only::get_transaction_results, (transaction_id)(transaction) ) +FC_REFLECT(eos::account_history_apis::read_only::get_transactions_params, (account_name)(after_transaction_id) ) +FC_REFLECT(eos::account_history_apis::read_only::get_transactions_results, (transactions) ) diff --git a/programs/eosc/main.cpp b/programs/eosc/main.cpp index 114be077765..23c4860e78b 100644 --- a/programs/eosc/main.cpp +++ b/programs/eosc/main.cpp @@ -39,7 +39,7 @@ 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"; - +const string get_transactions_func = account_history_func_base + "/get_transactions"; inline std::vector sort_names( std::vector&& names ) { std::sort( names.begin(), names.end() ); @@ -366,9 +366,24 @@ int send_command (const vector &cmd_line) } else if( command == "do" ) { } else if( command == "transaction" ) { - FC_ASSERT( cmd_line.size() == 2 ); + if( cmd_line.size() != 2 ) + { + std::cerr << "usage: " << program << " transaction TRANSACTION_ID\n"; + return -1; + } 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; + } else if( command == "transactions" ) { + if( cmd_line.size() < 2 || cmd_line.size() > 3 ) + { + std::cerr << "usage: " << program << " transactions ACCOUNT_TO_LOOKUP [AFTER_TRANSACTION_ID]\n"; + return -1; + } + chain::AccountName account_name(cmd_line[1]); + auto arg = (cmd_line.size() == 3) + ? fc::mutable_variant_object( "account_name", account_name)("after_transaction_id", cmd_line[2]) + : fc::mutable_variant_object( "account_name", account_name); + std::cout << fc::json::to_pretty_string( call( get_transactions_func, arg) ) << std::endl; } return 0; }