Merge pull request #4192 from clemahieu/rpc_test_pending_cleanup

Applies a number of cleanups to rpc tests.
This commit is contained in:
clemahieu 2023-03-21 11:14:28 +00:00 committed by GitHub
commit 9369e4e66b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 597 additions and 474 deletions

View file

@ -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)

150
nano/rpc_test/common.cpp Normal file
View file

@ -0,0 +1,150 @@
#include <nano/lib/threading.hpp>
#include <nano/node/ipc/ipc_server.hpp>
#include <nano/node/transport/tcp.hpp>
#include <nano/rpc/rpc_request_processor.hpp>
#include <nano/rpc_test/common.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/testutil.hpp>
#include <gtest/gtest.h>
#include <boost/property_tree/json_parser.hpp>
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<nano::rpc> & rpc_a, std::unique_ptr<nano::ipc::ipc_server> & ipc_server_a, std::unique_ptr<nano::ipc_rpc_processor> & ipc_rpc_processor_a, std::unique_ptr<nano::node_rpc_config> & 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::node> 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::node> 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::node> 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<double, std::nano> 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<double, std::nano> 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<nano::node> const & node_a)
{
auto node_rpc_config (std::make_unique<nano::node_rpc_config> ());
auto ipc_server (std::make_unique<nano::ipc::ipc_server> (*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<nano::ipc_rpc_processor> (system.io_ctx, rpc_config, ipc_tcp_port.value ()));
auto rpc (std::make_shared<nano::rpc> (system.io_ctx, rpc_config, *ipc_rpc_processor));
rpc->start ();
return rpc_context{ rpc, ipc_server, ipc_rpc_processor, node_rpc_config };
}
}

66
nano/rpc_test/common.hpp Normal file
View file

@ -0,0 +1,66 @@
#pragma once
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/beast/http.hpp>
#include <boost/property_tree/ptree.hpp>
#include <memory>
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<boost::beast::http::string_body> req;
boost::beast::http::response<boost::beast::http::string_body> resp;
std::atomic<int> status{ 0 };
};
class rpc_context
{
public:
rpc_context (std::shared_ptr<nano::rpc> & rpc_a, std::unique_ptr<nano::ipc::ipc_server> & ipc_server_a, std::unique_ptr<nano::ipc_rpc_processor> & ipc_rpc_processor_a, std::unique_ptr<nano::node_rpc_config> & node_rpc_config_a);
std::shared_ptr<nano::rpc> rpc;
std::unique_ptr<nano::ipc::ipc_server> ipc_server;
std::unique_ptr<nano::ipc_rpc_processor> ipc_rpc_processor;
std::unique_ptr<nano::node_rpc_config> node_rpc_config;
};
std::shared_ptr<nano::node> add_ipc_enabled_node (nano::test::system & system, nano::node_config & node_config, nano::node_flags const & node_flags);
std::shared_ptr<nano::node> add_ipc_enabled_node (nano::test::system & system, nano::node_config & node_config);
std::shared_ptr<nano::node> 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<double, std::nano> 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<double, std::nano> 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<nano::node> const & node_a);
}
}

View file

@ -0,0 +1,378 @@
#include <nano/node/ipc/ipc_server.hpp>
#include <nano/rpc/rpc_request_processor.hpp>
#include <nano/rpc_test/common.hpp>
#include <nano/test_common/chains.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/testutil.hpp>
#include <gtest/gtest.h>
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<std::string> ("") };
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<std::string> ("") };
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<nano::block_hash, nano::uint128_union> 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<std::string> (""));
blocks[hash] = amount;
auto source = i->second.get_optional<std::string> ("source");
ASSERT_FALSE (source.is_initialized ());
auto min_version = i->second.get_optional<uint8_t> ("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<nano::block_hash, nano::uint128_union> amounts;
std::unordered_map<nano::block_hash, nano::account> 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<std::string> ("amount"));
sources[hash].decode_account (i->second.get<std::string> ("source"));
ASSERT_EQ (i->second.get<uint8_t> ("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<std::string> ("");
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<std::string> (""));
// the next 3 block will be of amount 300 but in unspecified order
++itr;
ASSERT_EQ ("300", itr->second.get<std::string> (""));
++itr;
ASSERT_EQ ("300", itr->second.get<std::string> (""));
++itr;
ASSERT_EQ ("300", itr->second.get<std::string> (""));
// 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<std::string> (""));
// 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<std::string> (""));
++itr;
ASSERT_EQ (block1->hash (), nano::block_hash{ itr->first });
ASSERT_EQ ("200", itr->second.get<std::string> (""));
++itr;
ASSERT_EQ (block2->hash (), nano::block_hash{ itr->first });
ASSERT_EQ ("100", itr->second.get<std::string> (""));
}
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<std::string> ("amount"));
++itr;
ASSERT_EQ ("300", itr->second.get<std::string> ("amount"));
++itr;
ASSERT_EQ (block1->hash (), nano::block_hash{ itr->first });
ASSERT_EQ ("200", itr->second.get<std::string> ("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<std::string> (""));
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);
}

View file

@ -7,6 +7,7 @@
#include <nano/node/node_rpc_config.hpp>
#include <nano/rpc/rpc.hpp>
#include <nano/rpc/rpc_request_processor.hpp>
#include <nano/rpc_test/common.hpp>
#include <nano/test_common/network.hpp>
#include <nano/test_common/system.hpp>
#include <nano/test_common/telemetry.hpp>
@ -22,163 +23,7 @@
#include <utility>
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<boost::beast::http::string_body> req;
boost::beast::http::response<boost::beast::http::string_body> resp;
std::atomic<int> status{ 0 };
};
class rpc_context
{
public:
rpc_context (std::shared_ptr<nano::rpc> & rpc_a, std::unique_ptr<nano::ipc::ipc_server> & ipc_server_a, std::unique_ptr<nano::ipc_rpc_processor> & ipc_rpc_processor_a, std::unique_ptr<nano::node_rpc_config> & 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::rpc> rpc;
std::unique_ptr<nano::ipc::ipc_server> ipc_server;
std::unique_ptr<nano::ipc_rpc_processor> ipc_rpc_processor;
std::unique_ptr<nano::node_rpc_config> node_rpc_config;
};
std::shared_ptr<nano::node> 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::node> 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::node> 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<double, std::nano> 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<double, std::nano> 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<nano::node> const & node_a)
{
auto node_rpc_config (std::make_unique<nano::node_rpc_config> ());
auto ipc_server (std::make_unique<nano::ipc::ipc_server> (*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<nano::ipc_rpc_processor> (system.io_ctx, rpc_config, ipc_tcp_port.value ()));
auto rpc (std::make_shared<nano::rpc> (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<std::string> (""));
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<std::string> (""));
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<nano::block_hash, nano::uint128_union> 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<std::string> (""));
blocks[hash] = amount;
boost::optional<std::string> source (i->second.get_optional<std::string> ("source"));
ASSERT_FALSE (source.is_initialized ());
boost::optional<uint8_t> min_version (i->second.get_optional<uint8_t> ("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<nano::block_hash, nano::uint128_union> amounts;
std::unordered_map<nano::block_hash, nano::account> 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<std::string> ("amount"));
sources[hash].decode_account (i->second.get<std::string> ("source"));
ASSERT_EQ (i->second.get<uint8_t> ("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<std::string> ("");
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<std::string> (""));
// the next 3 block will be of amount 300 but in unspecified order
++itr;
ASSERT_EQ ("300", itr->second.get<std::string> (""));
++itr;
ASSERT_EQ ("300", itr->second.get<std::string> (""));
++itr;
ASSERT_EQ ("300", itr->second.get<std::string> (""));
// 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<std::string> (""));
// 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<std::string> (""));
++itr;
ASSERT_EQ (block1->hash (), nano::block_hash{ itr->first });
ASSERT_EQ ("200", itr->second.get<std::string> (""));
++itr;
ASSERT_EQ (block2->hash (), nano::block_hash{ itr->first });
ASSERT_EQ ("100", itr->second.get<std::string> (""));
}
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<std::string> ("amount"));
++itr;
ASSERT_EQ ("300", itr->second.get<std::string> ("amount"));
++itr;
ASSERT_EQ (block1->hash (), nano::block_hash{ itr->first });
ASSERT_EQ ("200", itr->second.get<std::string> ("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<std::string> (""));
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;