Skip to content
This repository was archived by the owner on Aug 2, 2022. It is now read-only.

5380 support iteration over scopes & tables in cleos #5486

Merged
merged 4 commits into from
Sep 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions plugins/chain_api_plugin/chain_api_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
42 changes: 42 additions & 0 deletions plugins/chain_plugin/chain_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<chain::table_id_multi_index, chain::by_code_scope_table>();
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<uint64_t>(p.lower_bound, "lower_bound scope");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is a bug!
If I pass '313313313313' to lower_bound, then it's 313313313313 (0x48F2EDC221), SHOULD be 1749138884762583600 (0x1846308C6118C230)
1111111.bank is 595056260428703488 (0x084210842039A700)

313313313313 < N(1111111.bank) < N(313313313313)

You can reproduce BUG by:

`
cleos -u https://mainnet.meet.one --print-request get scope eosio.token -t accounts -l 1 -L 313313313313
REQUEST:

POST /v1/chain/get_table_by_scope HTTP/1.0
Host: mainnet.meet.one
content-length: 118
Accept: /
Connection: close

{
"code": "eosio.token",
"table": "accounts",
"lower_bound": "313313313313",
"upper_bound": "",
"limit": 1
}

{
"rows": [{
"code": "eosio.token",
"scope": "1111111.bank",
"table": "accounts",
"payer": "1111111.bank",
"count": 1
}
],
"more": "111111111111"
}
`

Copy link
Contributor Author

@taokayan taokayan Sep 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a bug. The value of scope will be read as uint64_t first, then be read as account_name if failed to be read as uint64_t, as for different contracts the meaning of scopes may defer. If you want to read it as account_name always, you need to use cleos -u https://mainnet.meet.one --print-request get scope eosio.token -t accounts -l 1 -L " 313313313313"

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<uint64_t>(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<asset> 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 );
Expand Down
25 changes: 25 additions & 0 deletions plugins/chain_plugin/include/eosio/chain_plugin/chain_plugin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<get_table_by_scope_result_row> 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;
Expand Down Expand Up @@ -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));
Expand Down
1 change: 1 addition & 0 deletions programs/cleos/httpc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
17 changes: 17 additions & 0 deletions programs/cleos/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have some tests for this and the other get table functionality. Not required for this PR, but something to do when you have some time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree. let me make some tests for "get table" related functionality.

std::cout << fc::json::to_pretty_string(result)
<< std::endl;
});

// currency accessors
// get currency balance
string symbol;
Expand Down
2 changes: 1 addition & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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} )
Expand Down
124 changes: 124 additions & 0 deletions tests/get_table_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#include <boost/test/unit_test.hpp>
#include <boost/algorithm/string/predicate.hpp>

#include <eosio/testing/tester.hpp>
#include <eosio/chain/abi_serializer.hpp>
#include <eosio/chain/wasm_eosio_constraints.hpp>
#include <eosio/chain/resource_limits.hpp>
#include <eosio/chain/exceptions.hpp>
#include <eosio/chain/wast_to_wasm.hpp>
#include <eosio/chain_plugin/chain_plugin.hpp>

#include <asserter/asserter.wast.hpp>
#include <asserter/asserter.abi.hpp>

#include <stltest/stltest.wast.hpp>
#include <stltest/stltest.abi.hpp>

#include <eosio.system/eosio.system.wast.hpp>
#include <eosio.system/eosio.system.abi.hpp>

#include <eosio.token/eosio.token.wast.hpp>
#include <eosio.token/eosio.token.abi.hpp>

#include <fc/io/fstream.hpp>

#include <Runtime/Runtime.h>

#include <fc/variant_object.hpp>
#include <fc/io/json.hpp>

#include <array>
#include <utility>

#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<account_name> 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()