diff --git a/plugins/chain_api_plugin/chain_api_plugin.cpp b/plugins/chain_api_plugin/chain_api_plugin.cpp index b6d36d7fa00..39a7b88b5ee 100644 --- a/plugins/chain_api_plugin/chain_api_plugin.cpp +++ b/plugins/chain_api_plugin/chain_api_plugin.cpp @@ -89,6 +89,7 @@ void chain_api_plugin::plugin_startup() { CHAIN_RO_CALL(get_abi, 200), CHAIN_RO_CALL(get_raw_code_and_abi, 200), CHAIN_RO_CALL(get_table_rows, 200), + CHAIN_RO_CALL(get_table_by_scope, 200), CHAIN_RO_CALL(get_currency_balance, 200), CHAIN_RO_CALL(get_currency_stats, 200), CHAIN_RO_CALL(get_producers, 200), diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index e080a2314d8..1b435ecc708 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -1106,6 +1106,48 @@ read_only::get_table_rows_result read_only::get_table_rows( const read_only::get } } +read_only::get_table_by_scope_result read_only::get_table_by_scope( const read_only::get_table_by_scope_params& p )const { + const auto& d = db.db(); + const auto& idx = d.get_index(); + decltype(idx.lower_bound(boost::make_tuple(0, 0, 0))) lower; + decltype(idx.upper_bound(boost::make_tuple(0, 0, 0))) upper; + + if (p.lower_bound.size()) { + uint64_t scope = convert_to_type(p.lower_bound, "lower_bound scope"); + lower = idx.lower_bound( boost::make_tuple(p.code, scope, p.table)); + } else { + lower = idx.lower_bound(boost::make_tuple(p.code, 0, p.table)); + } + if (p.upper_bound.size()) { + uint64_t scope = convert_to_type(p.upper_bound, "upper_bound scope"); + upper = idx.lower_bound( boost::make_tuple(p.code, scope, 0)); + } else { + upper = idx.lower_bound(boost::make_tuple((uint64_t)p.code + 1, 0, 0)); + } + + auto end = fc::time_point::now() + fc::microseconds(1000 * 10); /// 10ms max time + unsigned int count = 0; + auto itr = lower; + read_only::get_table_by_scope_result result; + for (; itr != upper; ++itr) { + if (p.table && itr->table != p.table) { + if (fc::time_point::now() > end) { + break; + } + continue; + } + result.rows.push_back({itr->code, itr->scope, itr->table, itr->payer, itr->count}); + if (++count == p.limit || fc::time_point::now() > end) { + ++itr; + break; + } + } + if (itr != upper) { + result.more = (string)itr->scope; + } + return result; +} + vector read_only::get_currency_balance( const read_only::get_currency_balance_params& p )const { const abi_def abi = eosio::chain_apis::get_abi( db, p.code ); 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 d92d84109aa..fba45312398 100644 --- a/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp +++ b/plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp @@ -262,6 +262,27 @@ class read_only { get_table_rows_result get_table_rows( const get_table_rows_params& params )const; + struct get_table_by_scope_params { + name code; // mandatory + name table = 0; // optional, act as filter + string lower_bound; // lower bound of scope, optional + string upper_bound; // upper bound of scope, optional + uint32_t limit = 10; + }; + struct get_table_by_scope_result_row { + name code; + name scope; + name table; + name payer; + uint32_t count; + }; + struct get_table_by_scope_result { + vector rows; + string more; ///< fill lower_bound with this value to fetch more rows + }; + + get_table_by_scope_result get_table_by_scope( const get_table_by_scope_params& params )const; + struct get_currency_balance_params { name code; name account; @@ -636,6 +657,10 @@ FC_REFLECT( eosio::chain_apis::read_write::push_transaction_results, (transactio FC_REFLECT( eosio::chain_apis::read_only::get_table_rows_params, (json)(code)(scope)(table)(table_key)(lower_bound)(upper_bound)(limit)(key_type)(index_position)(encode_type) ) FC_REFLECT( eosio::chain_apis::read_only::get_table_rows_result, (rows)(more) ); +FC_REFLECT( eosio::chain_apis::read_only::get_table_by_scope_params, (code)(table)(lower_bound)(upper_bound)(limit) ) +FC_REFLECT( eosio::chain_apis::read_only::get_table_by_scope_result_row, (code)(scope)(table)(payer)(count)); +FC_REFLECT( eosio::chain_apis::read_only::get_table_by_scope_result, (rows)(more) ); + FC_REFLECT( eosio::chain_apis::read_only::get_currency_balance_params, (code)(account)(symbol)); FC_REFLECT( eosio::chain_apis::read_only::get_currency_stats_params, (code)(symbol)); FC_REFLECT( eosio::chain_apis::read_only::get_currency_stats_result, (supply)(max_supply)(issuer)); diff --git a/programs/cleos/httpc.hpp b/programs/cleos/httpc.hpp index 7443c9b0137..532f9b7623d 100644 --- a/programs/cleos/httpc.hpp +++ b/programs/cleos/httpc.hpp @@ -89,6 +89,7 @@ namespace eosio { namespace client { namespace http { const string get_block_header_state_func = chain_func_base + "/get_block_header_state"; const string get_account_func = chain_func_base + "/get_account"; const string get_table_func = chain_func_base + "/get_table_rows"; + const string get_table_by_scope_func = chain_func_base + "/get_table_by_scope"; const string get_code_func = chain_func_base + "/get_code"; const string get_abi_func = chain_func_base + "/get_abi"; const string get_raw_code_and_abi_func = chain_func_base + "/get_raw_code_and_abi"; diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 972818e9f20..04cba918f73 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -2033,6 +2033,23 @@ int main( int argc, char** argv ) { << std::endl; }); + auto getScope = get->add_subcommand( "scope", localized("Retrieve a list of scopes and tables owned by a contract"), false); + getScope->add_option( "contract", code, localized("The contract who owns the table") )->required(); + getScope->add_option( "-t,--table", table, localized("The name of the table as filter") ); + getScope->add_option( "-l,--limit", limit, localized("The maximum number of rows to return") ); + getScope->add_option( "-L,--lower", lower, localized("lower bound of scope") ); + getScope->add_option( "-U,--upper", upper, localized("upper bound of scope") ); + getScope->set_callback([&] { + auto result = call(get_table_by_scope_func, fc::mutable_variant_object("code",code) + ("table",table) + ("lower_bound",lower) + ("upper_bound",upper) + ("limit",limit) + ); + std::cout << fc::json::to_pretty_string(result) + << std::endl; + }); + // currency accessors // get currency balance string symbol; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1469d7c7798..059d42a0856 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -13,7 +13,7 @@ set( CMAKE_CXX_STANDARD 14 ) include_directories("${CMAKE_SOURCE_DIR}/plugins/wallet_plugin/include") -file(GLOB UNIT_TESTS "wallet_tests.cpp") +file(GLOB UNIT_TESTS "*.cpp") add_executable( plugin_test ${UNIT_TESTS} ${WASM_UNIT_TESTS} main.cpp) target_link_libraries( plugin_test eosio_testing eosio_chain chainbase eos_utilities chain_plugin wallet_plugin abi_generator fc ${PLATFORM_SPECIFIC_LIBS} ) diff --git a/tests/get_table_tests.cpp b/tests/get_table_tests.cpp new file mode 100644 index 00000000000..818ca5562d2 --- /dev/null +++ b/tests/get_table_tests.cpp @@ -0,0 +1,124 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include + +#include + +#include +#include + +#include +#include + +#ifdef NON_VALIDATING_TEST +#define TESTER tester +#else +#define TESTER validating_tester +#endif + +using namespace eosio; +using namespace eosio::chain; +using namespace eosio::testing; +using namespace fc; + +BOOST_AUTO_TEST_SUITE(get_table_tests) + +BOOST_FIXTURE_TEST_CASE( get_scope_test, TESTER ) try { + produce_blocks(2); + + create_accounts({ N(eosio.token), N(eosio.ram), N(eosio.ramfee), N(eosio.stake), + N(eosio.bpay), N(eosio.vpay), N(eosio.saving), N(eosio.names) }); + + std::vector accs{N(inita), N(initb), N(initc), N(initd)}; + create_accounts(accs); + produce_block(); + + set_code( N(eosio.token), eosio_token_wast ); + set_abi( N(eosio.token), eosio_token_abi ); + produce_blocks(1); + + // create currency + auto act = mutable_variant_object() + ("issuer", "eosio") + ("maximum_supply", eosio::chain::asset::from_string("1000000000.0000 SYS")); + push_action(N(eosio.token), N(create), N(eosio.token), act ); + + // issue + for (account_name a: accs) { + push_action( N(eosio.token), N(issue), "eosio", mutable_variant_object() + ("to", name(a) ) + ("quantity", eosio::chain::asset::from_string("999.0000 SYS") ) + ("memo", "") + ); + } + produce_blocks(1); + + // iterate over scope + eosio::chain_apis::read_only plugin(*(this->control), fc::microseconds(INT_MAX)); + eosio::chain_apis::read_only::get_table_by_scope_params param{N(eosio.token), N(accounts), "inita", "", 10}; + eosio::chain_apis::read_only::get_table_by_scope_result result = plugin.read_only::get_table_by_scope(param); + + BOOST_REQUIRE_EQUAL(4, result.rows.size()); + BOOST_REQUIRE_EQUAL("", result.more); + if (result.rows.size() >= 4) { + BOOST_REQUIRE_EQUAL(name(N(eosio.token)), result.rows[0].code); + BOOST_REQUIRE_EQUAL(name(N(inita)), result.rows[0].scope); + BOOST_REQUIRE_EQUAL(name(N(accounts)), result.rows[0].table); + BOOST_REQUIRE_EQUAL(name(N(eosio)), result.rows[0].payer); + BOOST_REQUIRE_EQUAL(1, result.rows[0].count); + + BOOST_REQUIRE_EQUAL(name(N(initb)), result.rows[1].scope); + BOOST_REQUIRE_EQUAL(name(N(initc)), result.rows[2].scope); + BOOST_REQUIRE_EQUAL(name(N(initd)), result.rows[3].scope); + } + + param.lower_bound = "initb"; + param.upper_bound = "initd"; + result = plugin.read_only::get_table_by_scope(param); + BOOST_REQUIRE_EQUAL(2, result.rows.size()); + BOOST_REQUIRE_EQUAL("", result.more); + if (result.rows.size() >= 2) { + BOOST_REQUIRE_EQUAL(name(N(initb)), result.rows[0].scope); + BOOST_REQUIRE_EQUAL(name(N(initc)), result.rows[1].scope); + } + + param.limit = 1; + result = plugin.read_only::get_table_by_scope(param); + BOOST_REQUIRE_EQUAL(1, result.rows.size()); + BOOST_REQUIRE_EQUAL("initc", result.more); + + param.table = name(0); + result = plugin.read_only::get_table_by_scope(param); + BOOST_REQUIRE_EQUAL(1, result.rows.size()); + BOOST_REQUIRE_EQUAL("initc", result.more); + + param.table = N(invalid); + result = plugin.read_only::get_table_by_scope(param); + BOOST_REQUIRE_EQUAL(0, result.rows.size()); + BOOST_REQUIRE_EQUAL("", result.more); + +} FC_LOG_AND_RETHROW() /// get_scope_test + +BOOST_AUTO_TEST_SUITE_END() +