diff --git a/nano/rpc_test/CMakeLists.txt b/nano/rpc_test/CMakeLists.txt index 4753ebb0..df4525a6 100644 --- a/nano/rpc_test/CMakeLists.txt +++ b/nano/rpc_test/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable(rpc_test entry.cpp rpc.cpp) +add_executable(rpc_test common.hpp common.cpp entry.cpp receivable.cpp rpc.cpp) target_link_libraries(rpc_test node secure rpc test_common gtest) diff --git a/nano/rpc_test/common.cpp b/nano/rpc_test/common.cpp new file mode 100644 index 00000000..6f7ed1c7 --- /dev/null +++ b/nano/rpc_test/common.cpp @@ -0,0 +1,150 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +namespace nano +{ +class rpc; + +nano::test::test_response::test_response (boost::property_tree::ptree const & request_a, boost::asio::io_context & io_ctx_a) : + request (request_a), + sock (io_ctx_a) +{ +} + +nano::test::test_response::test_response (boost::property_tree::ptree const & request_a, uint16_t port_a, boost::asio::io_context & io_ctx_a) : + request (request_a), + sock (io_ctx_a) +{ + run (port_a); +} + +void nano::test::test_response::run (uint16_t port_a) +{ + sock.async_connect (nano::tcp_endpoint (boost::asio::ip::address_v6::loopback (), port_a), [this] (boost::system::error_code const & ec) { + if (!ec) + { + std::stringstream ostream; + boost::property_tree::write_json (ostream, request); + req.method (boost::beast::http::verb::post); + req.target ("/"); + req.version (11); + ostream.flush (); + req.body () = ostream.str (); + req.prepare_payload (); + boost::beast::http::async_write (sock, req, [this] (boost::system::error_code const & ec, size_t bytes_transferred) { + if (!ec) + { + boost::beast::http::async_read (sock, sb, resp, [this] (boost::system::error_code const & ec, size_t bytes_transferred) { + if (!ec) + { + std::stringstream body (resp.body ()); + try + { + boost::property_tree::read_json (body, json); + status = 200; + } + catch (std::exception &) + { + status = 500; + } + } + else + { + status = 400; + } + }); + } + else + { + status = 600; + } + }); + } + else + { + status = 400; + } + }); +} + +nano::test::rpc_context::rpc_context (std::shared_ptr & rpc_a, std::unique_ptr & ipc_server_a, std::unique_ptr & ipc_rpc_processor_a, std::unique_ptr & node_rpc_config_a) +{ + rpc = std::move (rpc_a); + ipc_server = std::move (ipc_server_a); + ipc_rpc_processor = std::move (ipc_rpc_processor_a); + node_rpc_config = std::move (node_rpc_config_a); +} + +std::shared_ptr nano::test::add_ipc_enabled_node (nano::test::system & system, nano::node_config & node_config, nano::node_flags const & node_flags) +{ + node_config.ipc_config.transport_tcp.enabled = true; + node_config.ipc_config.transport_tcp.port = nano::test::get_available_port (); + return system.add_node (node_config, node_flags); +} + +std::shared_ptr nano::test::add_ipc_enabled_node (nano::test::system & system, nano::node_config & node_config) +{ + return add_ipc_enabled_node (system, node_config, nano::node_flags ()); +} + +std::shared_ptr nano::test::add_ipc_enabled_node (nano::test::system & system) +{ + nano::node_config node_config (nano::test::get_available_port (), system.logging); + return add_ipc_enabled_node (system, node_config); +} + +void nano::test::reset_confirmation_height (nano::store & store, nano::account const & account) +{ + auto transaction = store.tx_begin_write (); + nano::confirmation_height_info confirmation_height_info; + if (!store.confirmation_height.get (transaction, account, confirmation_height_info)) + { + store.confirmation_height.clear (transaction, account); + } +} + +void nano::test::wait_response_impl (nano::test::system & system, rpc_context const & rpc_ctx, boost::property_tree::ptree & request, std::chrono::duration const & time, boost::property_tree::ptree & response_json) +{ + test_response response (request, rpc_ctx.rpc->listening_port (), system.io_ctx); + ASSERT_TIMELY (time, response.status != 0); + ASSERT_EQ (200, response.status); + response_json = response.json; +} + +boost::property_tree::ptree nano::test::wait_response (nano::test::system & system, rpc_context const & rpc_ctx, boost::property_tree::ptree & request, std::chrono::duration const & time) +{ + boost::property_tree::ptree response_json; + wait_response_impl (system, rpc_ctx, request, time, response_json); + return response_json; +} + +bool nano::test::check_block_response_count (nano::test::system & system, rpc_context const & rpc_ctx, boost::property_tree::ptree & request, uint64_t size_count) +{ + auto response (wait_response (system, rpc_ctx, request)); + auto & blocks = response.get_child ("blocks"); + return size_count == blocks.size (); +} + +nano::test::rpc_context nano::test::add_rpc (nano::test::system & system, std::shared_ptr const & node_a) +{ + auto node_rpc_config (std::make_unique ()); + auto ipc_server (std::make_unique (*node_a, *node_rpc_config)); + nano::rpc_config rpc_config (node_a->network_params.network, nano::test::get_available_port (), true); + const auto ipc_tcp_port = ipc_server->listening_tcp_port (); + debug_assert (ipc_tcp_port.has_value ()); + auto ipc_rpc_processor (std::make_unique (system.io_ctx, rpc_config, ipc_tcp_port.value ())); + auto rpc (std::make_shared (system.io_ctx, rpc_config, *ipc_rpc_processor)); + rpc->start (); + + return rpc_context{ rpc, ipc_server, ipc_rpc_processor, node_rpc_config }; +} +} diff --git a/nano/rpc_test/common.hpp b/nano/rpc_test/common.hpp new file mode 100644 index 00000000..bffc8ac8 --- /dev/null +++ b/nano/rpc_test/common.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +using namespace std::chrono_literals; + +namespace nano +{ +class ipc_rpc_processor; +class node; +class node_config; +class node_flags; +class node_rpc_config; +class public_key; +class rpc; +class store; + +using account = public_key; +namespace ipc +{ + class ipc_server; +} +namespace test +{ + class system; + class test_response + { + public: + test_response (boost::property_tree::ptree const & request_a, boost::asio::io_context & io_ctx_a); + test_response (boost::property_tree::ptree const & request_a, uint16_t port_a, boost::asio::io_context & io_ctx_a); + void run (uint16_t port_a); + boost::property_tree::ptree const & request; + boost::asio::ip::tcp::socket sock; + boost::property_tree::ptree json; + boost::beast::flat_buffer sb; + boost::beast::http::request req; + boost::beast::http::response resp; + std::atomic status{ 0 }; + }; + class rpc_context + { + public: + rpc_context (std::shared_ptr & rpc_a, std::unique_ptr & ipc_server_a, std::unique_ptr & ipc_rpc_processor_a, std::unique_ptr & node_rpc_config_a); + + std::shared_ptr rpc; + std::unique_ptr ipc_server; + std::unique_ptr ipc_rpc_processor; + std::unique_ptr node_rpc_config; + }; + + std::shared_ptr add_ipc_enabled_node (nano::test::system & system, nano::node_config & node_config, nano::node_flags const & node_flags); + std::shared_ptr add_ipc_enabled_node (nano::test::system & system, nano::node_config & node_config); + std::shared_ptr add_ipc_enabled_node (nano::test::system & system); + void reset_confirmation_height (nano::store & store, nano::account const & account); + void wait_response_impl (nano::test::system & system, rpc_context const & rpc_ctx, boost::property_tree::ptree & request, std::chrono::duration const & time, boost::property_tree::ptree & response_json); + boost::property_tree::ptree wait_response (nano::test::system & system, rpc_context const & rpc_ctx, boost::property_tree::ptree & request, std::chrono::duration const & time = 5s); + bool check_block_response_count (nano::test::system & system, rpc_context const & rpc_ctx, boost::property_tree::ptree & request, uint64_t size_count); + rpc_context add_rpc (nano::test::system & system, std::shared_ptr const & node_a); +} +} diff --git a/nano/rpc_test/receivable.cpp b/nano/rpc_test/receivable.cpp new file mode 100644 index 00000000..b94ae990 --- /dev/null +++ b/nano/rpc_test/receivable.cpp @@ -0,0 +1,378 @@ +#include +#include +#include +#include +#include +#include + +#include + +using namespace nano::test; + +TEST (rpc, receivable) +{ + nano::test::system system; + auto node = add_ipc_enabled_node (system); + auto chain = nano::test::setup_chain (system, *node, 1); + auto block1 = chain[0]; + ASSERT_TIMELY (5s, node->block_confirmed (block1->hash ())); + auto const rpc_ctx = add_rpc (system, node); + boost::property_tree::ptree request; + request.put ("action", "receivable"); + request.put ("account", block1->link ().to_account ()); + auto response = wait_response (system, rpc_ctx, request); + auto & blocks_node = response.get_child ("blocks"); + ASSERT_EQ (1, blocks_node.size ()); + nano::block_hash hash{ blocks_node.begin ()->second.get ("") }; + ASSERT_EQ (block1->hash (), hash); +} + +TEST (rpc, receivable_sorting) +{ + nano::test::system system; + auto node = add_ipc_enabled_node (system); + auto chain = nano::test::setup_chain (system, *node, 1); + auto block1 = chain[0]; + ASSERT_TIMELY (5s, node->block_confirmed (block1->hash ())); + auto const rpc_ctx = add_rpc (system, node); + boost::property_tree::ptree request; + request.put ("action", "receivable"); + request.put ("account", block1->link ().to_account ()); + request.put ("sorting", "true"); // Sorting test + auto response = wait_response (system, rpc_ctx, request); + auto & blocks_node = response.get_child ("blocks"); + ASSERT_EQ (1, blocks_node.size ()); + nano::block_hash hash{ blocks_node.begin ()->first }; + ASSERT_EQ (block1->hash (), hash); + std::string amount{ blocks_node.begin ()->second.get ("") }; + ASSERT_EQ ("1", amount); +} + +TEST (rpc, receivable_threshold_sufficient) +{ + nano::test::system system; + auto node = add_ipc_enabled_node (system); + auto chain = nano::test::setup_chain (system, *node, 1); + auto block1 = chain[0]; + ASSERT_TIMELY (5s, node->block_confirmed (block1->hash ())); + auto const rpc_ctx = add_rpc (system, node); + boost::property_tree::ptree request; + request.put ("action", "receivable"); + request.put ("account", block1->link ().to_account ()); + request.put ("threshold", "1"); // Threshold test + auto response = wait_response (system, rpc_ctx, request); + auto & blocks_node = response.get_child ("blocks"); + ASSERT_EQ (1, blocks_node.size ()); + std::unordered_map blocks; + for (auto i (blocks_node.begin ()), j (blocks_node.end ()); i != j; ++i) + { + nano::block_hash hash; + hash.decode_hex (i->first); + nano::uint128_union amount; + amount.decode_dec (i->second.get ("")); + blocks[hash] = amount; + auto source = i->second.get_optional ("source"); + ASSERT_FALSE (source.is_initialized ()); + auto min_version = i->second.get_optional ("min_version"); + ASSERT_FALSE (min_version.is_initialized ()); + } + ASSERT_EQ (blocks[block1->hash ()], 1); +} + +TEST (rpc, receivable_threshold_insufficient) +{ + nano::test::system system; + auto node = add_ipc_enabled_node (system); + auto chain = nano::test::setup_chain (system, *node, 1); + auto block1 = chain[0]; + ASSERT_TIMELY (5s, node->block_confirmed (block1->hash ())); + auto const rpc_ctx = add_rpc (system, node); + boost::property_tree::ptree request; + request.put ("action", "receivable"); + request.put ("account", block1->link ().to_account ()); + request.put ("threshold", "2"); // Chains are set up with 1 raw transfers therefore all blocks are less than 2 raw. + auto response = wait_response (system, rpc_ctx, request, 10s); + auto & blocks_node = response.get_child ("blocks"); + ASSERT_EQ (0, blocks_node.size ()); +} + +TEST (rpc, receivable_source_min_version) +{ + nano::test::system system; + auto node = add_ipc_enabled_node (system); + auto chain = nano::test::setup_chain (system, *node, 1); + auto block1 = chain[0]; + ASSERT_TIMELY (5s, node->block_confirmed (block1->hash ())); + auto const rpc_ctx = add_rpc (system, node); + boost::property_tree::ptree request; + request.put ("action", "receivable"); + request.put ("account", block1->link ().to_account ()); + request.put ("source", "true"); + request.put ("min_version", "true"); + auto response (wait_response (system, rpc_ctx, request)); + auto & blocks_node (response.get_child ("blocks")); + ASSERT_EQ (1, blocks_node.size ()); + std::unordered_map amounts; + std::unordered_map sources; + for (auto i (blocks_node.begin ()), j (blocks_node.end ()); i != j; ++i) + { + nano::block_hash hash; + hash.decode_hex (i->first); + amounts[hash].decode_dec (i->second.get ("amount")); + sources[hash].decode_account (i->second.get ("source")); + ASSERT_EQ (i->second.get ("min_version"), 0); + } + ASSERT_EQ (amounts[block1->hash ()], 1); + ASSERT_EQ (sources[block1->hash ()], nano::dev::genesis_key.pub); +} + +TEST (rpc, receivable_unconfirmed) +{ + nano::test::system system; + auto node = add_ipc_enabled_node (system); + nano::thread_role::set (nano::thread_role::name::unknown); // thread_role::name::io is disallowed for performance reasons by write transactions. Set our thread to ::unknown. + auto chain = nano::test::setup_chain (system, *node, 1, nano::dev::genesis_key, false); + auto block1 = chain[0]; + auto const rpc_ctx = add_rpc (system, node); + boost::property_tree::ptree request; + request.put ("action", "receivable"); + request.put ("account", block1->link ().to_account ()); + ASSERT_TRUE (check_block_response_count (system, rpc_ctx, request, 0)); + request.put ("include_only_confirmed", "true"); + ASSERT_TRUE (check_block_response_count (system, rpc_ctx, request, 0)); + request.put ("include_only_confirmed", "false"); + ASSERT_TRUE (check_block_response_count (system, rpc_ctx, request, 1)); + { + node->store.confirmation_height.put (node->store.tx_begin_write (), nano::dev::genesis_key.pub, { 2, block1->hash () }); + } + ASSERT_TRUE (check_block_response_count (system, rpc_ctx, request, 1)); +} + +/*TEST (rpc, amounts) +{ + auto block2 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 200)); + auto block3 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 300)); + auto block4 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 400)); + rpc_ctx.io_scope->renew (); + + ASSERT_TIMELY (10s, node->ledger.account_receivable (node->store.tx_begin_read (), key1.pub) == 1000); + ASSERT_TIMELY (5s, !node->active.active (*block4)); + ASSERT_TIMELY (5s, node->block_confirmed (block4->hash ())); + + request.put ("count", "2"); + { + auto response (wait_response (system, rpc_ctx, request)); + auto & blocks_node (response.get_child ("blocks")); + ASSERT_EQ (2, blocks_node.size ()); + nano::block_hash hash (blocks_node.begin ()->first); + nano::block_hash hash1 ((++blocks_node.begin ())->first); + ASSERT_EQ (block4->hash (), hash); + ASSERT_EQ (block3->hash (), hash1); + } +}*/ + +/** + * This test case tests the receivable RPC command when used with offsets and sorting. + */ +TEST (rpc, receivable_offset_and_sorting) +{ + nano::test::system system; + auto node = add_ipc_enabled_node (system); + nano::keypair key1; + system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); + + auto block1 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 200); + auto block2 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 100); + auto block3 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 400); + auto block4 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 300); + auto block5 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 300); + auto block6 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 300); + + // check that all blocks got confirmed + ASSERT_TIMELY (5s, node->ledger.account_receivable (node->store.tx_begin_read (), key1.pub, true) == 1600); + + // check confirmation height is as expected, there is no perfect clarity yet when confirmation height updates after a block get confirmed + nano::confirmation_height_info confirmation_height_info; + ASSERT_FALSE (node->store.confirmation_height.get (node->store.tx_begin_read (), nano::dev::genesis->account (), confirmation_height_info)); + ASSERT_EQ (confirmation_height_info.height, 7); + ASSERT_EQ (confirmation_height_info.frontier, block6->hash ()); + + // returns true if hash is found in node + // if match_first is set then the function looks for key (first item) + // if match_first is not set then the function looks for value (second item) + auto hash_exists = [] (boost::property_tree::ptree & node, bool match_first, nano::block_hash hash) { + std::stringstream ss; + boost::property_tree::json_parser::write_json (ss, node); + for (auto itr = node.begin (); itr != node.end (); ++itr) + { + std::string possible_match = match_first ? itr->first : itr->second.get (""); + if (possible_match == hash.to_string ()) + { + return true; + } + } + return false; + }; + + auto const rpc_ctx = add_rpc (system, node); + boost::property_tree::ptree request; + request.put ("action", "receivable"); + request.put ("account", key1.pub.to_account ()); + + request.put ("offset", "0"); + request.put ("sorting", "false"); + { + auto response (wait_response (system, rpc_ctx, request)); + auto & blocks_node (response.get_child ("blocks")); + ASSERT_EQ (6, blocks_node.size ()); + + // check that all 6 blocks are listed, the order does not matter + ASSERT_TRUE (hash_exists (blocks_node, false, block1->hash ())); + ASSERT_TRUE (hash_exists (blocks_node, false, block2->hash ())); + ASSERT_TRUE (hash_exists (blocks_node, false, block3->hash ())); + ASSERT_TRUE (hash_exists (blocks_node, false, block4->hash ())); + ASSERT_TRUE (hash_exists (blocks_node, false, block5->hash ())); + ASSERT_TRUE (hash_exists (blocks_node, false, block6->hash ())); + } + + request.put ("offset", "4"); + { + auto response (wait_response (system, rpc_ctx, request)); + auto & blocks_node (response.get_child ("blocks")); + // since we haven't asked for sorted, we can't be sure which 2 blocks will be returned + ASSERT_EQ (2, blocks_node.size ()); + } + + request.put ("count", "2"); + request.put ("offset", "2"); + { + auto response (wait_response (system, rpc_ctx, request)); + auto & blocks_node (response.get_child ("blocks")); + // since we haven't asked for sorted, we can't be sure which 2 blocks will be returned + ASSERT_EQ (2, blocks_node.size ()); + } + + // Sort by amount from here onwards, this is a sticky setting that applies for the rest of the test case + request.put ("sorting", "true"); + + request.put ("count", "5"); + request.put ("offset", "0"); + { + auto response (wait_response (system, rpc_ctx, request)); + auto & blocks_node (response.get_child ("blocks")); + ASSERT_EQ (5, blocks_node.size ()); + + // the first block should be block3 with amount 400 + auto itr = blocks_node.begin (); + ASSERT_EQ (block3->hash (), nano::block_hash{ itr->first }); + ASSERT_EQ ("400", itr->second.get ("")); + + // the next 3 block will be of amount 300 but in unspecified order + ++itr; + ASSERT_EQ ("300", itr->second.get ("")); + + ++itr; + ASSERT_EQ ("300", itr->second.get ("")); + + ++itr; + ASSERT_EQ ("300", itr->second.get ("")); + + // the last one will be block1 with amount 200 + ++itr; + ASSERT_EQ (block1->hash (), nano::block_hash{ itr->first }); + ASSERT_EQ ("200", itr->second.get ("")); + + // check that the blocks returned with 300 amounts have the right hashes + ASSERT_TRUE (hash_exists (blocks_node, true, block4->hash ())); + ASSERT_TRUE (hash_exists (blocks_node, true, block5->hash ())); + ASSERT_TRUE (hash_exists (blocks_node, true, block6->hash ())); + } + + request.put ("count", "3"); + request.put ("offset", "3"); + { + auto response (wait_response (system, rpc_ctx, request)); + auto & blocks_node (response.get_child ("blocks")); + ASSERT_EQ (3, blocks_node.size ()); + + auto itr = blocks_node.begin (); + ASSERT_EQ ("300", itr->second.get ("")); + + ++itr; + ASSERT_EQ (block1->hash (), nano::block_hash{ itr->first }); + ASSERT_EQ ("200", itr->second.get ("")); + + ++itr; + ASSERT_EQ (block2->hash (), nano::block_hash{ itr->first }); + ASSERT_EQ ("100", itr->second.get ("")); + } + + request.put ("source", "true"); + request.put ("min_version", "true"); + request.put ("count", "3"); + request.put ("offset", "2"); + { + auto response (wait_response (system, rpc_ctx, request)); + auto & blocks_node (response.get_child ("blocks")); + ASSERT_EQ (3, blocks_node.size ()); + + auto itr = blocks_node.begin (); + ASSERT_EQ ("300", itr->second.get ("amount")); + + ++itr; + ASSERT_EQ ("300", itr->second.get ("amount")); + + ++itr; + ASSERT_EQ (block1->hash (), nano::block_hash{ itr->first }); + ASSERT_EQ ("200", itr->second.get ("amount")); + } +} + +TEST (rpc, receivable_burn) +{ + nano::test::system system; + auto node = add_ipc_enabled_node (system); + system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); + auto block1 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, nano::dev::constants.burn_account, 100)); + auto const rpc_ctx = add_rpc (system, node); + ASSERT_TIMELY (5s, node->block_confirmed (block1->hash ())); + boost::property_tree::ptree request; + request.put ("action", "receivable"); + request.put ("account", nano::dev::constants.burn_account.to_account ()); + request.put ("count", "100"); + { + auto response (wait_response (system, rpc_ctx, request)); + auto & blocks_node (response.get_child ("blocks")); + ASSERT_EQ (1, blocks_node.size ()); + nano::block_hash hash (blocks_node.begin ()->second.get ("")); + ASSERT_EQ (block1->hash (), hash); + } +} + +TEST (rpc, search_receivable) +{ + nano::test::system system; + auto node = add_ipc_enabled_node (system); + system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); + auto wallet (node->wallets.items.begin ()->first.to_string ()); + auto latest (node->latest (nano::dev::genesis_key.pub)); + nano::block_builder builder; + auto block = builder + .send () + .previous (latest) + .destination (nano::dev::genesis_key.pub) + .balance (nano::dev::constants.genesis_amount - node->config.receive_minimum.number ()) + .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) + .work (*node->work_generate_blocking (latest)) + .build (); + { + auto transaction (node->store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *block).code); + } + auto const rpc_ctx = add_rpc (system, node); + boost::property_tree::ptree request; + request.put ("action", "search_receivable"); + request.put ("wallet", wallet); + auto response (wait_response (system, rpc_ctx, request)); + ASSERT_TIMELY (10s, node->balance (nano::dev::genesis_key.pub) == nano::dev::constants.genesis_amount); +} diff --git a/nano/rpc_test/rpc.cpp b/nano/rpc_test/rpc.cpp index 9d337cdb..d6007c7a 100644 --- a/nano/rpc_test/rpc.cpp +++ b/nano/rpc_test/rpc.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -22,163 +23,7 @@ #include using namespace std::chrono_literals; - -namespace -{ -class test_response -{ -public: - test_response (boost::property_tree::ptree const & request_a, boost::asio::io_context & io_ctx_a) : - request (request_a), - sock (io_ctx_a) - { - } - - test_response (boost::property_tree::ptree const & request_a, uint16_t port_a, boost::asio::io_context & io_ctx_a) : - request (request_a), - sock (io_ctx_a) - { - run (port_a); - } - - void run (uint16_t port_a) - { - sock.async_connect (nano::tcp_endpoint (boost::asio::ip::address_v6::loopback (), port_a), [this] (boost::system::error_code const & ec) { - if (!ec) - { - std::stringstream ostream; - boost::property_tree::write_json (ostream, request); - req.method (boost::beast::http::verb::post); - req.target ("/"); - req.version (11); - ostream.flush (); - req.body () = ostream.str (); - req.prepare_payload (); - boost::beast::http::async_write (sock, req, [this] (boost::system::error_code const & ec, size_t bytes_transferred) { - if (!ec) - { - boost::beast::http::async_read (sock, sb, resp, [this] (boost::system::error_code const & ec, size_t bytes_transferred) { - if (!ec) - { - std::stringstream body (resp.body ()); - try - { - boost::property_tree::read_json (body, json); - status = 200; - } - catch (std::exception &) - { - status = 500; - } - } - else - { - status = 400; - } - }); - } - else - { - status = 600; - } - }); - } - else - { - status = 400; - } - }); - } - boost::property_tree::ptree const & request; - boost::asio::ip::tcp::socket sock; - boost::property_tree::ptree json; - boost::beast::flat_buffer sb; - boost::beast::http::request req; - boost::beast::http::response resp; - std::atomic status{ 0 }; -}; - -class rpc_context -{ -public: - rpc_context (std::shared_ptr & rpc_a, std::unique_ptr & ipc_server_a, std::unique_ptr & ipc_rpc_processor_a, std::unique_ptr & node_rpc_config_a) - { - rpc = std::move (rpc_a); - ipc_server = std::move (ipc_server_a); - ipc_rpc_processor = std::move (ipc_rpc_processor_a); - node_rpc_config = std::move (node_rpc_config_a); - } - - std::shared_ptr rpc; - std::unique_ptr ipc_server; - std::unique_ptr ipc_rpc_processor; - std::unique_ptr node_rpc_config; -}; - -std::shared_ptr add_ipc_enabled_node (nano::test::system & system, nano::node_config & node_config, nano::node_flags const & node_flags) -{ - node_config.ipc_config.transport_tcp.enabled = true; - node_config.ipc_config.transport_tcp.port = nano::test::get_available_port (); - return system.add_node (node_config, node_flags); -} - -std::shared_ptr add_ipc_enabled_node (nano::test::system & system, nano::node_config & node_config) -{ - return add_ipc_enabled_node (system, node_config, nano::node_flags ()); -} - -std::shared_ptr add_ipc_enabled_node (nano::test::system & system) -{ - nano::node_config node_config (nano::test::get_available_port (), system.logging); - return add_ipc_enabled_node (system, node_config); -} - -void reset_confirmation_height (nano::store & store, nano::account const & account) -{ - auto transaction = store.tx_begin_write (); - nano::confirmation_height_info confirmation_height_info; - if (!store.confirmation_height.get (transaction, account, confirmation_height_info)) - { - store.confirmation_height.clear (transaction, account); - } -} - -void wait_response_impl (nano::test::system & system, rpc_context const & rpc_ctx, boost::property_tree::ptree & request, std::chrono::duration const & time, boost::property_tree::ptree & response_json) -{ - test_response response (request, rpc_ctx.rpc->listening_port (), system.io_ctx); - ASSERT_TIMELY (time, response.status != 0); - ASSERT_EQ (200, response.status); - response_json = response.json; -} - -boost::property_tree::ptree wait_response (nano::test::system & system, rpc_context const & rpc_ctx, boost::property_tree::ptree & request, std::chrono::duration const & time = 5s) -{ - boost::property_tree::ptree response_json; - wait_response_impl (system, rpc_ctx, request, time, response_json); - return response_json; -} - -bool check_block_response_count (nano::test::system & system, rpc_context const & rpc_ctx, boost::property_tree::ptree & request, uint64_t size_count) -{ - auto response (wait_response (system, rpc_ctx, request)); - auto & blocks = response.get_child ("blocks"); - return size_count == blocks.size (); -} - -rpc_context add_rpc (nano::test::system & system, std::shared_ptr const & node_a) -{ - auto node_rpc_config (std::make_unique ()); - auto ipc_server (std::make_unique (*node_a, *node_rpc_config)); - nano::rpc_config rpc_config (node_a->network_params.network, nano::test::get_available_port (), true); - const auto ipc_tcp_port = ipc_server->listening_tcp_port (); - debug_assert (ipc_tcp_port.has_value ()); - auto ipc_rpc_processor (std::make_unique (system.io_ctx, rpc_config, ipc_tcp_port.value ())); - auto rpc (std::make_shared (system.io_ctx, rpc_config, *ipc_rpc_processor)); - rpc->start (); - - return rpc_context{ rpc, ipc_server, ipc_rpc_processor, node_rpc_config }; -} -} +using namespace nano::test; TEST (rpc, wrapped_task) { @@ -1883,322 +1728,6 @@ TEST (rpc, peers_node_id) // The previous version of this test had an UDP connection to an arbitrary IP address, so it could check for two peers. This doesn't work with TCP. } -TEST (rpc, pending) -{ - nano::test::system system; - auto node = add_ipc_enabled_node (system); - nano::keypair key1; - system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); - auto block1 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 100)); - ASSERT_TIMELY (5s, node->block_confirmed (block1->hash ())); - auto const rpc_ctx = add_rpc (system, node); - boost::property_tree::ptree request; - request.put ("action", "pending"); - request.put ("account", key1.pub.to_account ()); - request.put ("count", "100"); - { - auto response (wait_response (system, rpc_ctx, request)); - auto & blocks_node (response.get_child ("blocks")); - ASSERT_EQ (1, blocks_node.size ()); - nano::block_hash hash (blocks_node.begin ()->second.get ("")); - ASSERT_EQ (block1->hash (), hash); - } - request.put ("sorting", "true"); // Sorting test - { - auto response (wait_response (system, rpc_ctx, request)); - auto & blocks_node (response.get_child ("blocks")); - ASSERT_EQ (1, blocks_node.size ()); - nano::block_hash hash (blocks_node.begin ()->first); - ASSERT_EQ (block1->hash (), hash); - std::string amount (blocks_node.begin ()->second.get ("")); - ASSERT_EQ ("100", amount); - } - request.put ("threshold", "100"); // Threshold test - { - auto response (wait_response (system, rpc_ctx, request)); - auto & blocks_node (response.get_child ("blocks")); - ASSERT_EQ (1, blocks_node.size ()); - std::unordered_map blocks; - for (auto i (blocks_node.begin ()), j (blocks_node.end ()); i != j; ++i) - { - nano::block_hash hash; - hash.decode_hex (i->first); - nano::uint128_union amount; - amount.decode_dec (i->second.get ("")); - blocks[hash] = amount; - boost::optional source (i->second.get_optional ("source")); - ASSERT_FALSE (source.is_initialized ()); - boost::optional min_version (i->second.get_optional ("min_version")); - ASSERT_FALSE (min_version.is_initialized ()); - } - ASSERT_EQ (blocks[block1->hash ()], 100); - } - request.put ("threshold", "101"); - { - auto response (wait_response (system, rpc_ctx, request, 10s)); - auto & blocks_node (response.get_child ("blocks")); - ASSERT_EQ (0, blocks_node.size ()); - } - request.put ("threshold", "0"); - request.put ("source", "true"); - request.put ("min_version", "true"); - { - auto response (wait_response (system, rpc_ctx, request)); - auto & blocks_node (response.get_child ("blocks")); - ASSERT_EQ (1, blocks_node.size ()); - std::unordered_map amounts; - std::unordered_map sources; - for (auto i (blocks_node.begin ()), j (blocks_node.end ()); i != j; ++i) - { - nano::block_hash hash; - hash.decode_hex (i->first); - amounts[hash].decode_dec (i->second.get ("amount")); - sources[hash].decode_account (i->second.get ("source")); - ASSERT_EQ (i->second.get ("min_version"), 0); - } - ASSERT_EQ (amounts[block1->hash ()], 100); - ASSERT_EQ (sources[block1->hash ()], nano::dev::genesis_key.pub); - } - - request.put ("account", key1.pub.to_account ()); - request.put ("source", "false"); - request.put ("min_version", "false"); - - ASSERT_TRUE (check_block_response_count (system, rpc_ctx, request, 1)); - reset_confirmation_height (system.nodes.front ()->store, block1->account ()); - ASSERT_TRUE (check_block_response_count (system, rpc_ctx, request, 0)); - request.put ("include_only_confirmed", "false"); - ASSERT_TRUE (check_block_response_count (system, rpc_ctx, request, 1)); - request.put ("include_only_confirmed", "true"); - - // Sorting with a smaller count than total should give absolute sorted amounts - node->store.confirmation_height.put (node->store.tx_begin_write (), nano::dev::genesis_key.pub, { 2, block1->hash () }); - auto block2 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 200)); - auto block3 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 300)); - auto block4 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 400)); - - ASSERT_TIMELY (10s, node->ledger.account_receivable (node->store.tx_begin_read (), key1.pub) == 1000); - ASSERT_TIMELY (5s, !node->active.active (*block4)); - ASSERT_TIMELY (5s, node->block_confirmed (block4->hash ())); - - request.put ("count", "2"); - { - auto response (wait_response (system, rpc_ctx, request)); - auto & blocks_node (response.get_child ("blocks")); - ASSERT_EQ (2, blocks_node.size ()); - nano::block_hash hash (blocks_node.begin ()->first); - nano::block_hash hash1 ((++blocks_node.begin ())->first); - ASSERT_EQ (block4->hash (), hash); - ASSERT_EQ (block3->hash (), hash1); - } -} - -/** - * This test case tests the receivable RPC command when used with offsets and sorting. - */ -TEST (rpc, receivable_offset_and_sorting) -{ - nano::test::system system; - auto node = add_ipc_enabled_node (system); - nano::keypair key1; - system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); - - auto block1 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 200); - auto block2 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 100); - auto block3 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 400); - auto block4 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 300); - auto block5 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 300); - auto block6 = system.wallet (0)->send_action (nano::dev::genesis_key.pub, key1.pub, 300); - - // check that all blocks got confirmed - ASSERT_TIMELY (5s, node->ledger.account_receivable (node->store.tx_begin_read (), key1.pub, true) == 1600); - - // check confirmation height is as expected, there is no perfect clarity yet when confirmation height updates after a block get confirmed - nano::confirmation_height_info confirmation_height_info; - ASSERT_FALSE (node->store.confirmation_height.get (node->store.tx_begin_read (), nano::dev::genesis->account (), confirmation_height_info)); - ASSERT_EQ (confirmation_height_info.height, 7); - ASSERT_EQ (confirmation_height_info.frontier, block6->hash ()); - - // returns true if hash is found in node - // if match_first is set then the function looks for key (first item) - // if match_first is not set then the function looks for value (second item) - auto hash_exists = [] (boost::property_tree::ptree & node, bool match_first, nano::block_hash hash) { - std::stringstream ss; - boost::property_tree::json_parser::write_json (ss, node); - for (auto itr = node.begin (); itr != node.end (); ++itr) - { - std::string possible_match = match_first ? itr->first : itr->second.get (""); - if (possible_match == hash.to_string ()) - { - return true; - } - } - return false; - }; - - auto const rpc_ctx = add_rpc (system, node); - boost::property_tree::ptree request; - request.put ("action", "receivable"); - request.put ("account", key1.pub.to_account ()); - - request.put ("offset", "0"); - request.put ("sorting", "false"); - { - auto response (wait_response (system, rpc_ctx, request)); - auto & blocks_node (response.get_child ("blocks")); - ASSERT_EQ (6, blocks_node.size ()); - - // check that all 6 blocks are listed, the order does not matter - ASSERT_TRUE (hash_exists (blocks_node, false, block1->hash ())); - ASSERT_TRUE (hash_exists (blocks_node, false, block2->hash ())); - ASSERT_TRUE (hash_exists (blocks_node, false, block3->hash ())); - ASSERT_TRUE (hash_exists (blocks_node, false, block4->hash ())); - ASSERT_TRUE (hash_exists (blocks_node, false, block5->hash ())); - ASSERT_TRUE (hash_exists (blocks_node, false, block6->hash ())); - } - - request.put ("offset", "4"); - { - auto response (wait_response (system, rpc_ctx, request)); - auto & blocks_node (response.get_child ("blocks")); - // since we haven't asked for sorted, we can't be sure which 2 blocks will be returned - ASSERT_EQ (2, blocks_node.size ()); - } - - request.put ("count", "2"); - request.put ("offset", "2"); - { - auto response (wait_response (system, rpc_ctx, request)); - auto & blocks_node (response.get_child ("blocks")); - // since we haven't asked for sorted, we can't be sure which 2 blocks will be returned - ASSERT_EQ (2, blocks_node.size ()); - } - - // Sort by amount from here onwards, this is a sticky setting that applies for the rest of the test case - request.put ("sorting", "true"); - - request.put ("count", "5"); - request.put ("offset", "0"); - { - auto response (wait_response (system, rpc_ctx, request)); - auto & blocks_node (response.get_child ("blocks")); - ASSERT_EQ (5, blocks_node.size ()); - - // the first block should be block3 with amount 400 - auto itr = blocks_node.begin (); - ASSERT_EQ (block3->hash (), nano::block_hash{ itr->first }); - ASSERT_EQ ("400", itr->second.get ("")); - - // the next 3 block will be of amount 300 but in unspecified order - ++itr; - ASSERT_EQ ("300", itr->second.get ("")); - - ++itr; - ASSERT_EQ ("300", itr->second.get ("")); - - ++itr; - ASSERT_EQ ("300", itr->second.get ("")); - - // the last one will be block1 with amount 200 - ++itr; - ASSERT_EQ (block1->hash (), nano::block_hash{ itr->first }); - ASSERT_EQ ("200", itr->second.get ("")); - - // check that the blocks returned with 300 amounts have the right hashes - ASSERT_TRUE (hash_exists (blocks_node, true, block4->hash ())); - ASSERT_TRUE (hash_exists (blocks_node, true, block5->hash ())); - ASSERT_TRUE (hash_exists (blocks_node, true, block6->hash ())); - } - - request.put ("count", "3"); - request.put ("offset", "3"); - { - auto response (wait_response (system, rpc_ctx, request)); - auto & blocks_node (response.get_child ("blocks")); - ASSERT_EQ (3, blocks_node.size ()); - - auto itr = blocks_node.begin (); - ASSERT_EQ ("300", itr->second.get ("")); - - ++itr; - ASSERT_EQ (block1->hash (), nano::block_hash{ itr->first }); - ASSERT_EQ ("200", itr->second.get ("")); - - ++itr; - ASSERT_EQ (block2->hash (), nano::block_hash{ itr->first }); - ASSERT_EQ ("100", itr->second.get ("")); - } - - request.put ("source", "true"); - request.put ("min_version", "true"); - request.put ("count", "3"); - request.put ("offset", "2"); - { - auto response (wait_response (system, rpc_ctx, request)); - auto & blocks_node (response.get_child ("blocks")); - ASSERT_EQ (3, blocks_node.size ()); - - auto itr = blocks_node.begin (); - ASSERT_EQ ("300", itr->second.get ("amount")); - - ++itr; - ASSERT_EQ ("300", itr->second.get ("amount")); - - ++itr; - ASSERT_EQ (block1->hash (), nano::block_hash{ itr->first }); - ASSERT_EQ ("200", itr->second.get ("amount")); - } -} - -TEST (rpc, pending_burn) -{ - nano::test::system system; - auto node = add_ipc_enabled_node (system); - system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); - auto block1 (system.wallet (0)->send_action (nano::dev::genesis_key.pub, nano::dev::constants.burn_account, 100)); - auto const rpc_ctx = add_rpc (system, node); - ASSERT_TIMELY (5s, node->block_confirmed (block1->hash ())); - boost::property_tree::ptree request; - request.put ("action", "pending"); - request.put ("account", nano::dev::constants.burn_account.to_account ()); - request.put ("count", "100"); - { - auto response (wait_response (system, rpc_ctx, request)); - auto & blocks_node (response.get_child ("blocks")); - ASSERT_EQ (1, blocks_node.size ()); - nano::block_hash hash (blocks_node.begin ()->second.get ("")); - ASSERT_EQ (block1->hash (), hash); - } -} - -TEST (rpc, search_receivable) -{ - nano::test::system system; - auto node = add_ipc_enabled_node (system); - system.wallet (0)->insert_adhoc (nano::dev::genesis_key.prv); - auto wallet (node->wallets.items.begin ()->first.to_string ()); - auto latest (node->latest (nano::dev::genesis_key.pub)); - nano::block_builder builder; - auto block = builder - .send () - .previous (latest) - .destination (nano::dev::genesis_key.pub) - .balance (nano::dev::constants.genesis_amount - node->config.receive_minimum.number ()) - .sign (nano::dev::genesis_key.prv, nano::dev::genesis_key.pub) - .work (*node->work_generate_blocking (latest)) - .build (); - { - auto transaction (node->store.tx_begin_write ()); - ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, *block).code); - } - auto const rpc_ctx = add_rpc (system, node); - boost::property_tree::ptree request; - request.put ("action", "search_receivable"); - request.put ("wallet", wallet); - auto response (wait_response (system, rpc_ctx, request)); - ASSERT_TIMELY (10s, node->balance (nano::dev::genesis_key.pub) == nano::dev::constants.genesis_amount); -} - TEST (rpc, version) { nano::test::system system;