From 36b46547bb1968ce79f2cd263a9a8de00ad431b8 Mon Sep 17 00:00:00 2001 From: Guilherme Lawless Date: Fri, 22 Feb 2019 21:13:47 +0000 Subject: [PATCH] RPC unopened (#1727) * Add RPC unopened to get unopened accounts with pending blocks * Format * Faster approach using pending tables * Avoid using unordered map and exclude the burn account * Add test for burn account * Check for account number overflow * Change RPC response format * Add edge case with no accounts * Only add if pending>0 * Require RPC control * Check for errors * Add starting 'account' and total 'count' options. --- nano/core_test/rpc.cpp | 119 +++++++++++++++++++++++++++++++++++++++++ nano/node/rpc.cpp | 67 +++++++++++++++++++++++ nano/node/rpc.hpp | 1 + 3 files changed, 187 insertions(+) diff --git a/nano/core_test/rpc.cpp b/nano/core_test/rpc.cpp index 415dcf944..a859bdd69 100644 --- a/nano/core_test/rpc.cpp +++ b/nano/core_test/rpc.cpp @@ -4361,6 +4361,125 @@ TEST (rpc, stats_clear) ASSERT_LE (system.nodes[0]->stats.last_reset ().count (), 5); } +TEST (rpc, unopened) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto transaction (system.wallet (0)->wallets.tx_begin_write ()); + nano::account account1 (1), account2 (account1.number () + 1); + auto genesis (system.nodes[0]->latest (nano::test_genesis_key.pub)); + ASSERT_FALSE (genesis.is_zero ()); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, account1, 1)); + ASSERT_NE (nullptr, send); + auto send2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, account2, 2)); + ASSERT_NE (nullptr, send2); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + { + boost::property_tree::ptree request; + request.put ("action", "unopened"); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto & accounts (response.json.get_child ("accounts")); + ASSERT_EQ (2, accounts.size ()); + ASSERT_EQ ("1", accounts.get (account1.to_account ())); + ASSERT_EQ ("2", accounts.get (account2.to_account ())); + } + { + // starting at second account should get a single result + boost::property_tree::ptree request; + request.put ("action", "unopened"); + request.put ("account", account2.to_account ()); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto & accounts (response.json.get_child ("accounts")); + ASSERT_EQ (1, accounts.size ()); + ASSERT_EQ ("2", accounts.get (account2.to_account ())); + } + { + // starting at third account should get no results + boost::property_tree::ptree request; + request.put ("action", "unopened"); + request.put ("account", nano::account (account2.number () + 1).to_account ()); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto & accounts (response.json.get_child ("accounts")); + ASSERT_EQ (0, accounts.size ()); + } + { + // using count=1 should get a single result + boost::property_tree::ptree request; + request.put ("action", "unopened"); + request.put ("count", "1"); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto & accounts (response.json.get_child ("accounts")); + ASSERT_EQ (1, accounts.size ()); + ASSERT_EQ ("1", accounts.get (account1.to_account ())); + } +} + +TEST (rpc, unopened_burn) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto genesis (system.nodes[0]->latest (nano::test_genesis_key.pub)); + ASSERT_FALSE (genesis.is_zero ()); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::burn_account, 1)); + ASSERT_NE (nullptr, send); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "unopened"); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto & accounts (response.json.get_child ("accounts")); + ASSERT_EQ (0, accounts.size ()); +} + +TEST (rpc, unopened_no_accounts) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "unopened"); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto & accounts (response.json.get_child ("accounts")); + ASSERT_EQ (0, accounts.size ()); +} + TEST (rpc, uptime) { nano::system system (24000, 1); diff --git a/nano/node/rpc.cpp b/nano/node/rpc.cpp index 63c104f3f..e3c1ad33c 100644 --- a/nano/node/rpc.cpp +++ b/nano/node/rpc.cpp @@ -3243,6 +3243,69 @@ void nano::rpc_handler::unchecked_keys () response_errors (); } +void nano::rpc_handler::unopened () +{ + rpc_control_impl (); + if (!ec) + { + auto count (count_optional_impl ()); + nano::account start (1); // exclude burn account by default + boost::optional account_text (request.get_optional ("account")); + if (account_text.is_initialized ()) + { + if (start.decode_account (account_text.get ())) + { + ec = nano::error_common::bad_account_number; + } + } + if (!ec) + { + auto transaction (node.store.tx_begin_read ()); + auto iterator (node.store.pending_begin (transaction, nano::pending_key (start, 0))); + auto end (node.store.pending_end ()); + nano::account current_account (start); + nano::uint128_t current_account_sum{ 0 }; + boost::property_tree::ptree accounts; + while (iterator != end && accounts.size () < count) + { + nano::pending_key key (iterator->first); + nano::account account (key.account); + nano::pending_info info (iterator->second); + if (node.store.account_exists (transaction, account)) + { + if (account.number () == std::numeric_limits::max ()) + { + break; + } + // Skip existing accounts + iterator = node.store.pending_begin (transaction, nano::pending_key (account.number () + 1, 0)); + } + else + { + if (account != current_account) + { + if (current_account_sum > 0) + { + accounts.put (current_account.to_account (), current_account_sum.convert_to ()); + current_account_sum = 0; + } + current_account = account; + } + current_account_sum += info.amount.number (); + ++iterator; + } + } + // last one after iterator reaches end + if (current_account_sum > 0 && accounts.size () < count) + { + accounts.put (current_account.to_account (), current_account_sum.convert_to ()); + } + response_l.add_child ("accounts", accounts); + } + } + response_errors (); +} + void nano::rpc_handler::uptime () { response_l.put ("seconds", std::chrono::duration_cast (std::chrono::steady_clock::now () - node.startup_time).count ()); @@ -4536,6 +4599,10 @@ void nano::rpc_handler::process_request () { unchecked_keys (); } + else if (action == "unopened") + { + unopened (); + } else if (action == "uptime") { uptime (); diff --git a/nano/node/rpc.hpp b/nano/node/rpc.hpp index 8c52155c9..6a03fff2d 100644 --- a/nano/node/rpc.hpp +++ b/nano/node/rpc.hpp @@ -166,6 +166,7 @@ public: void unchecked_clear (); void unchecked_get (); void unchecked_keys (); + void unopened (); void uptime (); void validate_account_number (); void version ();