diff --git a/.gitignore b/.gitignore index c9d9bd6a..3b45de31 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,8 @@ *.DS_Store core_test !core_test/ +rpc_test +!rpc_test/ qt_test nano_node nano_wallet diff --git a/CMakeLists.txt b/CMakeLists.txt index db2b102c..d3ca7145 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -292,6 +292,7 @@ add_subdirectory(nano/crypto_lib) add_subdirectory(nano/secure) add_subdirectory(nano/lib) add_subdirectory(nano/node) +add_subdirectory(nano/rpc) add_subdirectory(nano/nano_node) if (NANO_TEST OR RAIBLOCKS_TEST) @@ -315,6 +316,7 @@ if (NANO_TEST OR RAIBLOCKS_TEST) "${CMAKE_SOURCE_DIR}/gtest/googletest/include") add_subdirectory(nano/core_test) + add_subdirectory(nano/rpc_test) add_subdirectory(nano/slow_test) endif () diff --git a/ci/test.sh b/ci/test.sh index ebcc01db..585dc101 100755 --- a/ci/test.sh +++ b/ci/test.sh @@ -65,6 +65,9 @@ run_tests() { fi done + xvfb_run_ ./rpc_test + rpc_test_res=${?} + xvfb_run_ ./qt_test qt_test_res=${?} @@ -72,6 +75,7 @@ run_tests() { load_test_res=${?} echo "Core Test return code: ${core_test_res}" + echo "RPC Test return code: ${rpc_test_res}" echo "QT Test return code: ${qt_test_res}" echo "Load Test return code: ${load_test_res}" return ${core_test_res} diff --git a/nano/core_test/ipc.cpp b/nano/core_test/ipc.cpp index c53cac40..41adc4b8 100644 --- a/nano/core_test/ipc.cpp +++ b/nano/core_test/ipc.cpp @@ -3,9 +3,10 @@ #include #include #include +#include #include -#include #include +#include #include #include @@ -20,7 +21,7 @@ TEST (ipc, asynchronous) nano::ipc::ipc_server ipc (*system.nodes[0], rpc); nano::ipc::ipc_client client (system.nodes[0]->io_ctx); - auto req (client.prepare_request (nano::ipc::payload_encoding::json_legacy, std::string (R"({"action": "block_count"})"))); + auto req (nano::ipc::prepare_request (nano::ipc::payload_encoding::json_legacy, std::string (R"({"action": "block_count"})"))); auto res (std::make_shared> ()); std::atomic call_completed{ false }; client.async_connect ("::1", 24077, [&client, &req, &res, &call_completed](nano::error err) { @@ -61,13 +62,13 @@ TEST (ipc, synchronous) system.nodes[0]->config.ipc_config.transport_tcp.enabled = true; system.nodes[0]->config.ipc_config.transport_tcp.port = 24077; nano::ipc::ipc_server ipc (*system.nodes[0], rpc); - nano::ipc::rpc_ipc_client client (system.nodes[0]->io_ctx); + nano::ipc::ipc_client client (system.nodes[0]->io_ctx); // Start blocking IPC client in a separate thread std::atomic call_completed{ false }; std::thread client_thread ([&client, &call_completed]() { client.connect ("::1", 24077); - std::string response (client.request (std::string (R"({"action": "block_count"})"))); + std::string response (nano::ipc::request (client, std::string (R"({"action": "block_count"})"))); std::stringstream ss; ss << response; // Make sure the response is valid json diff --git a/nano/core_test/rpc.cpp b/nano/core_test/rpc.cpp index 36e1fd36..238dff9c 100644 --- a/nano/core_test/rpc.cpp +++ b/nano/core_test/rpc.cpp @@ -8,8 +8,8 @@ #include #include #include -#include #include +#include using namespace std::chrono_literals; diff --git a/nano/lib/CMakeLists.txt b/nano/lib/CMakeLists.txt index d91f36c6..57b06306 100644 --- a/nano/lib/CMakeLists.txt +++ b/nano/lib/CMakeLists.txt @@ -15,20 +15,27 @@ add_library (nano_lib errors.hpp errors.cpp expected.hpp - blockbuilders.cpp blockbuilders.hpp - blocks.cpp + blockbuilders.cpp blocks.hpp + blocks.cpp config.hpp config.cpp - interface.cpp interface.h + interface.cpp + ipc.hpp + ipc.cpp + ipc_client.hpp + ipc_client.cpp jsonconfig.hpp - numbers.cpp + logger_mt.hpp + rpcconfig.hpp + rpcconfig.cpp numbers.hpp + numbers.cpp timer.hpp - utility.cpp utility.hpp + utility.cpp work.hpp work.cpp) diff --git a/nano/lib/config.hpp b/nano/lib/config.hpp index a870317c..d2fe6bfb 100644 --- a/nano/lib/config.hpp +++ b/nano/lib/config.hpp @@ -7,6 +7,15 @@ #include #include +#define xstr(a) ver_str (a) +#define ver_str(a) #a + +/** +* Returns build version information +*/ +static const char * NANO_MAJOR_MINOR_VERSION = xstr (NANO_VERSION_MAJOR) "." xstr (NANO_VERSION_MINOR); +static const char * NANO_MAJOR_MINOR_RC_VERSION = xstr (NANO_VERSION_MAJOR) "." xstr (NANO_VERSION_MINOR) "RC" xstr (NANO_VERSION_PATCH); + namespace nano { /** diff --git a/nano/lib/ipc.cpp b/nano/lib/ipc.cpp new file mode 100644 index 00000000..dd109b1b --- /dev/null +++ b/nano/lib/ipc.cpp @@ -0,0 +1,43 @@ +#include + +nano::ipc::socket_base::socket_base (boost::asio::io_context & io_ctx_a) : +io_timer (io_ctx_a) +{ +} + +void nano::ipc::socket_base::timer_start (std::chrono::seconds timeout_a) +{ + if (timeout_a < std::chrono::seconds::max ()) + { + io_timer.expires_from_now (boost::posix_time::seconds (static_cast (timeout_a.count ()))); + io_timer.async_wait ([this](const boost::system::error_code & ec) { + if (!ec) + { + this->timer_expired (); + } + }); + } +} + +void nano::ipc::socket_base::timer_expired () +{ + close (); +} + +void nano::ipc::socket_base::timer_cancel () +{ + boost::system::error_code ec; + io_timer.cancel (ec); + assert (!ec); +} + +nano::ipc::dsock_file_remover::dsock_file_remover (std::string const & file_a) : +filename (file_a) +{ + std::remove (filename.c_str ()); +} + +nano::ipc::dsock_file_remover::~dsock_file_remover () +{ + std::remove (filename.c_str ()); +} diff --git a/nano/lib/ipc.hpp b/nano/lib/ipc.hpp new file mode 100644 index 00000000..fdeec354 --- /dev/null +++ b/nano/lib/ipc.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include + +namespace nano +{ +namespace ipc +{ + /** + * The IPC framing format is simple: preamble followed by an encoding specific payload. + * Preamble is uint8_t {'N', encoding_type, reserved, reserved}. Reserved bytes MUST be zero. + * @note This is intentionally not an enum class as the values are only used as vector indices. + */ + enum preamble_offset + { + /** Always 'N' */ + lead = 0, + /** One of the payload_encoding values */ + encoding = 1, + /** Always zero */ + reserved_1 = 2, + /** Always zero */ + reserved_2 = 3, + }; + + /** Abstract base type for sockets, implementing timer logic and a close operation */ + class socket_base + { + public: + socket_base (boost::asio::io_context & io_ctx_a); + virtual ~socket_base () = default; + + /** Close socket */ + virtual void close () = 0; + + /** + * Start IO timer. + * @param timeout_a Seconds to wait. To wait indefinitely, use std::chrono::seconds::max () + */ + void timer_start (std::chrono::seconds timeout_a); + void timer_expired (); + void timer_cancel (); + + private: + /** IO operation timer */ + boost::asio::deadline_timer io_timer; + }; + + /** + * Payload encodings; add protobuf, flatbuffers and so on as needed. + */ + enum class payload_encoding : uint8_t + { + /** + * Request is preamble followed by 32-bit BE payload length and payload bytes. + * Response is 32-bit BE payload length followed by payload bytes. + */ + json_legacy = 1 + }; + + /** IPC transport interface */ + class transport + { + public: + virtual void stop () = 0; + virtual ~transport () = default; + }; + + /** The domain socket file is attempted to be removed at both startup and shutdown. */ + class dsock_file_remover final + { + public: + dsock_file_remover (std::string const & file_a); + ~dsock_file_remover (); + + private: + std::string filename; + }; +} +} diff --git a/nano/lib/ipc_client.cpp b/nano/lib/ipc_client.cpp new file mode 100644 index 00000000..f2b96042 --- /dev/null +++ b/nano/lib/ipc_client.cpp @@ -0,0 +1,227 @@ +#include +#include +#include +#include + +namespace +{ +/** Socket agnostic IO interface */ +class channel +{ +public: + virtual void async_read (std::shared_ptr> buffer_a, size_t size_a, std::function callback_a) = 0; + virtual void async_write (std::shared_ptr> buffer_a, std::function callback_a) = 0; +}; + +/** Domain and TCP client socket */ +template +class socket_client : public nano::ipc::socket_base, public channel +{ +public: + socket_client (boost::asio::io_context & io_ctx_a, ENDPOINT_TYPE endpoint_a) : + socket_base (io_ctx_a), endpoint (endpoint_a), socket (io_ctx_a), resolver (io_ctx_a) + { + } + + void async_resolve (std::string const & host_a, uint16_t port_a, std::function callback_a) + { + this->timer_start (io_timeout); + resolver.async_resolve (boost::asio::ip::tcp::resolver::query (host_a, std::to_string (port_a)), [this, callback_a](boost::system::error_code const & ec, boost::asio::ip::tcp::resolver::iterator endpoint_iterator_a) { + this->timer_cancel (); + boost::asio::ip::tcp::resolver::iterator end; + if (!ec && endpoint_iterator_a != end) + { + endpoint = *endpoint_iterator_a; + callback_a (ec, *endpoint_iterator_a); + } + else + { + callback_a (ec, *end); + } + }); + } + + void async_connect (std::function callback_a) + { + this->timer_start (io_timeout); + socket.async_connect (endpoint, [this, callback_a](boost::system::error_code const & ec) { + this->timer_cancel (); + callback_a (ec); + }); + } + + void async_read (std::shared_ptr> buffer_a, size_t size_a, std::function callback_a) override + { + this->timer_start (io_timeout); + buffer_a->resize (size_a); + boost::asio::async_read (socket, boost::asio::buffer (buffer_a->data (), size_a), [this, callback_a](boost::system::error_code const & ec, size_t size_a) { + this->timer_cancel (); + callback_a (ec, size_a); + }); + } + + void async_write (std::shared_ptr> buffer_a, std::function callback_a) override + { + this->timer_start (io_timeout); + boost::asio::async_write (socket, boost::asio::buffer (buffer_a->data (), buffer_a->size ()), [this, callback_a, buffer_a](boost::system::error_code const & ec, size_t size_a) { + this->timer_cancel (); + callback_a (ec, size_a); + }); + } + + /** Shut down and close socket */ + void close () override + { + socket.shutdown (boost::asio::ip::tcp::socket::shutdown_both); + socket.close (); + } + +private: + ENDPOINT_TYPE endpoint; + SOCKET_TYPE socket; + boost::asio::ip::tcp::resolver resolver; + std::chrono::seconds io_timeout{ 60 }; +}; + +/** + * PIMPL class for ipc_client. This ensures that socket_client and boost details can + * stay out of the header file. + */ +class client_impl : public nano::ipc::ipc_client_impl +{ +public: + client_impl (boost::asio::io_context & io_ctx_a) : + io_ctx (io_ctx_a) + { + } + + void connect (std::string const & host_a, uint16_t port_a, std::function callback_a) + { + tcp_client = std::make_shared> (io_ctx, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v6 (), port_a)); + + tcp_client->async_resolve (host_a, port_a, [this, callback_a](boost::system::error_code const & ec_resolve_a, boost::asio::ip::tcp::endpoint endpoint_a) { + if (!ec_resolve_a) + { + this->tcp_client->async_connect ([callback_a](const boost::system::error_code & ec_connect_a) { + callback_a (nano::error (ec_connect_a)); + }); + } + else + { + callback_a (nano::error (ec_resolve_a)); + } + }); + } + + nano::error connect (std::string const & path_a) + { + nano::error err; +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + domain_client = std::make_shared> (io_ctx, boost::asio::local::stream_protocol::endpoint (path_a)); +#else + err = nano::error ("Domain sockets are not supported by this platform"); +#endif + return err; + } + + channel & get_channel () + { +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + return tcp_client ? static_cast (*tcp_client) : static_cast (*domain_client); +#else + return static_cast (*tcp_client); +#endif + } + +private: + boost::asio::io_context & io_ctx; + std::shared_ptr> tcp_client; +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) + std::shared_ptr> domain_client; +#endif +}; +} + +nano::ipc::ipc_client::ipc_client (boost::asio::io_context & io_ctx_a) : +io_ctx (io_ctx_a) +{ +} + +nano::error nano::ipc::ipc_client::connect (std::string const & path_a) +{ + impl = std::make_unique (io_ctx); + return boost::polymorphic_downcast (impl.get ())->connect (path_a); +} + +void nano::ipc::ipc_client::async_connect (std::string const & host_a, uint16_t port_a, std::function callback_a) +{ + impl = std::make_unique (io_ctx); + auto client (boost::polymorphic_downcast (impl.get ())); + client->connect (host_a, port_a, callback_a); +} + +nano::error nano::ipc::ipc_client::connect (std::string const & host, uint16_t port) +{ + std::promise result_l; + async_connect (host, port, [&result_l](nano::error err_a) { + result_l.set_value (err_a); + }); + return result_l.get_future ().get (); +} + +void nano::ipc::ipc_client::async_write (std::shared_ptr> buffer_a, std::function callback_a) +{ + auto client (boost::polymorphic_downcast (impl.get ())); + client->get_channel ().async_write (buffer_a, [callback_a](const boost::system::error_code & ec_a, size_t bytes_written_a) { + callback_a (nano::error (ec_a), bytes_written_a); + }); +} + +void nano::ipc::ipc_client::async_read (std::shared_ptr> buffer_a, size_t size_a, std::function callback_a) +{ + auto client (boost::polymorphic_downcast (impl.get ())); + client->get_channel ().async_read (buffer_a, size_a, [callback_a](const boost::system::error_code & ec_a, size_t bytes_read_a) { + callback_a (nano::error (ec_a), bytes_read_a); + }); +} + +std::shared_ptr> nano::ipc::prepare_request (nano::ipc::payload_encoding encoding_a, std::string const & payload_a) +{ + auto buffer_l (std::make_shared> ()); + if (encoding_a == nano::ipc::payload_encoding::json_legacy) + { + buffer_l->push_back ('N'); + buffer_l->push_back (static_cast (encoding_a)); + buffer_l->push_back (0); + buffer_l->push_back (0); + + auto payload_length = static_cast (payload_a.size ()); + uint32_t be = boost::endian::native_to_big (payload_length); + char * chars = reinterpret_cast (&be); + buffer_l->insert (buffer_l->end (), chars, chars + sizeof (uint32_t)); + buffer_l->insert (buffer_l->end (), payload_a.begin (), payload_a.end ()); + } + return buffer_l; +} + +std::string nano::ipc::request (nano::ipc::ipc_client & ipc_client, std::string const & rpc_action_a) +{ + auto req (prepare_request (nano::ipc::payload_encoding::json_legacy, rpc_action_a)); + auto res (std::make_shared> ()); + + std::promise result_l; + // clang-format off + ipc_client.async_write (req, [&ipc_client, &res, &result_l](nano::error err_a, size_t size_a) { + // Read length + ipc_client.async_read (res, sizeof (uint32_t), [&ipc_client, &res, &result_l](nano::error err_read_a, size_t size_read_a) { + uint32_t payload_size_l = boost::endian::big_to_native (*reinterpret_cast (res->data ())); + // Read json payload + ipc_client.async_read (res, payload_size_l, [&res, &result_l](nano::error err_read_a, size_t size_read_a) { + result_l.set_value (std::string (res->begin (), res->end ())); + }); + }); + }); + // clang-format on + + return result_l.get_future ().get (); +} diff --git a/nano/lib/ipc_client.hpp b/nano/lib/ipc_client.hpp new file mode 100644 index 00000000..157b960a --- /dev/null +++ b/nano/lib/ipc_client.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace nano +{ +namespace ipc +{ + class ipc_client_impl + { + public: + virtual ~ipc_client_impl () = default; + }; + + /** IPC client */ + class ipc_client + { + public: + ipc_client (boost::asio::io_context & io_ctx_a); + ipc_client (ipc_client && ipc_client) = default; + virtual ~ipc_client () = default; + + /** Connect to a domain socket */ + nano::error connect (std::string const & path); + + /** Connect to a tcp socket synchronously */ + nano::error connect (std::string const & host, uint16_t port); + + /** Connect to a tcp socket asynchronously */ + void async_connect (std::string const & host, uint16_t port, std::function callback); + + /** Write buffer asynchronously */ + void async_write (std::shared_ptr> buffer_a, std::function callback_a); + + /** Read \p size_a bytes asynchronously */ + void async_read (std::shared_ptr> buffer_a, size_t size_a, std::function callback_a); + + private: + boost::asio::io_context & io_ctx; + + // PIMPL pattern to hide implementation details + std::unique_ptr impl; + }; + + /** Convenience function for making synchronous IPC calls. The client must be connected */ + std::string request (nano::ipc::ipc_client & ipc_client, std::string const & rpc_action_a); + + /** + * Returns a buffer with an IPC preamble for the given \p encoding_a followed by the payload. Depending on encoding, + * the buffer may contain a payload length or end sentinel. + */ + std::shared_ptr> prepare_request (nano::ipc::payload_encoding encoding_a, std::string const & payload_a); +} +} diff --git a/nano/lib/jsonconfig.hpp b/nano/lib/jsonconfig.hpp index cd2e36c4..e5ed4fdc 100644 --- a/nano/lib/jsonconfig.hpp +++ b/nano/lib/jsonconfig.hpp @@ -57,11 +57,11 @@ public: } /** - * Reads a json object from the stream and if it was changed, write the object back to the stream. + * Reads a json object from the stream * @return nano::error&, including a descriptive error message if the config file is malformed. */ template - nano::error & read_and_update (T & object, boost::filesystem::path const & path_a) + nano::error & read (boost::filesystem::path const & path_a) { std::fstream stream; open_or_create (stream, path_a.string ()); @@ -80,23 +80,35 @@ public: } } stream.close (); - if (!*error) + } + return *error; + } + + /** + * Reads a json object from the stream and if it was changed, write the object back to the stream. + * @return nano::error&, including a descriptive error message if the config file is malformed. + */ + template + nano::error & read_and_update (T & object, boost::filesystem::path const & path_a) + { + read (path_a); + if (!*error) + { + std::fstream stream; + auto updated (false); + *error = object.deserialize_json (updated, *this); + if (!*error && updated) { - auto updated (false); - *error = object.deserialize_json (updated, *this); - if (!*error && updated) + stream.open (path_a.string (), std::ios_base::out | std::ios_base::trunc); + try { - stream.open (path_a.string (), std::ios_base::out | std::ios_base::trunc); - try - { - boost::property_tree::write_json (stream, tree); - } - catch (std::runtime_error const & ex) - { - *error = ex; - } - stream.close (); + boost::property_tree::write_json (stream, tree); } + catch (std::runtime_error const & ex) + { + *error = ex; + } + stream.close (); } } return *error; diff --git a/nano/lib/logger_mt.hpp b/nano/lib/logger_mt.hpp new file mode 100644 index 00000000..9c67210b --- /dev/null +++ b/nano/lib/logger_mt.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +namespace nano +{ +// A wrapper around a boost logger object to allow minimum +// time spaced output to prevent logging happening too quickly. +class logger_mt +{ +private: + void add_to_stream (boost::log::record_ostream & stream) + { + } + + template + void add_to_stream (boost::log::record_ostream & stream, const LogItem & first_log_item, LogItems &&... remainder_log_items) + { + stream << first_log_item; + add_to_stream (stream, remainder_log_items...); + } + + template + void output (LogItems &&... log_items) + { + boost::log::record rec = boost_logger_mt.open_record (); + if (rec) + { + boost::log::record_ostream strm (rec); + add_to_stream (strm, std::forward (log_items)...); + strm.flush (); + boost_logger_mt.push_record (std::move (rec)); + } + } + +public: + /** + * @param min_log_delta_a The minimum time between successive output + */ + explicit logger_mt (std::chrono::milliseconds const & min_log_delta_a) : + min_log_delta (min_log_delta_a) + { + } + + /* + * @param log_items A collection of objects with overloaded operator<< to be output to the log file + */ + template + void always_log (LogItems &&... log_items) + { + output (std::forward (log_items)...); + } + + /* + * @param log_items Output to the log file if the last write was over min_log_delta time ago. + * @return true if the log was successful + */ + template + bool try_log (LogItems &&... log_items) + { + auto error (true); + auto time_now = std::chrono::steady_clock::now (); + if (((time_now - last_log_time) > min_log_delta) || last_log_time == std::chrono::steady_clock::time_point{}) + { + output (std::forward (log_items)...); + last_log_time = time_now; + error = false; + } + return error; + } + + std::chrono::milliseconds min_log_delta; + +private: + std::chrono::steady_clock::time_point last_log_time; + boost::log::sources::logger_mt boost_logger_mt; +}; +} diff --git a/nano/node/rpcconfig.cpp b/nano/lib/rpcconfig.cpp similarity index 96% rename from nano/node/rpcconfig.cpp rename to nano/lib/rpcconfig.cpp index acca075b..1ec2c121 100644 --- a/nano/node/rpcconfig.cpp +++ b/nano/lib/rpcconfig.cpp @@ -1,12 +1,6 @@ #include #include -#include - -nano::rpc_secure_config::rpc_secure_config () : -enable (false), -verbose_logging (false) -{ -} +#include nano::error nano::rpc_secure_config::serialize_json (nano::jsonconfig & json) const { diff --git a/nano/node/rpcconfig.hpp b/nano/lib/rpcconfig.hpp similarity index 89% rename from nano/node/rpcconfig.hpp rename to nano/lib/rpcconfig.hpp index 8c9eafa0..ea24a7a9 100644 --- a/nano/node/rpcconfig.hpp +++ b/nano/lib/rpcconfig.hpp @@ -10,17 +10,16 @@ namespace nano class jsonconfig; /** Configuration options for RPC TLS */ -class rpc_secure_config +class rpc_secure_config final { public: - rpc_secure_config (); nano::error serialize_json (nano::jsonconfig &) const; nano::error deserialize_json (nano::jsonconfig &); /** If true, enable TLS */ - bool enable; + bool enable{ false }; /** If true, log certificate verification details */ - bool verbose_logging; + bool verbose_logging{ false }; /** Must be set if the private key PEM is password protected */ std::string server_key_passphrase; /** Path to certificate- or chain file. Must be PEM formatted. */ @@ -33,10 +32,10 @@ public: std::string client_certs_path; }; -class rpc_config +class rpc_config final { public: - rpc_config (bool = false); + explicit rpc_config (bool = false); nano::error serialize_json (nano::jsonconfig &) const; nano::error deserialize_json (bool & upgraded_a, nano::jsonconfig &); nano::network_constants network_constants; diff --git a/nano/lib/utility.cpp b/nano/lib/utility.cpp index 64015f7a..44855be9 100644 --- a/nano/lib/utility.cpp +++ b/nano/lib/utility.cpp @@ -130,6 +130,49 @@ void nano::thread_attributes::set (boost::thread::attributes & attrs) attrs_l->set_stack_size (8000000); //8MB } +nano::thread_runner::thread_runner (boost::asio::io_context & io_ctx_a, unsigned service_threads_a) +{ + boost::thread::attributes attrs; + nano::thread_attributes::set (attrs); + for (auto i (0u); i < service_threads_a; ++i) + { + threads.push_back (boost::thread (attrs, [&io_ctx_a]() { + nano::thread_role::set (nano::thread_role::name::io); + try + { + io_ctx_a.run (); + } + catch (...) + { +#ifndef NDEBUG + /* + * In a release build, catch and swallow the + * io_context exception, in debug mode pass it + * on + */ + throw; +#endif + } + })); + } +} + +nano::thread_runner::~thread_runner () +{ + join (); +} + +void nano::thread_runner::join () +{ + for (auto & i : threads) + { + if (i.joinable ()) + { + i.join (); + } + } +} + /* * Backing code for "release_assert", which is itself a macro */ diff --git a/nano/lib/utility.hpp b/nano/lib/utility.hpp index 86b3cf3d..ddadf799 100644 --- a/nano/lib/utility.hpp +++ b/nano/lib/utility.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -108,6 +109,15 @@ namespace thread_attributes void set (boost::thread::attributes &); } +class thread_runner final +{ +public: + thread_runner (boost::asio::io_context &, unsigned); + ~thread_runner (); + void join (); + std::vector threads; +}; + template class observer_set final { diff --git a/nano/nano_node/entry.cpp b/nano/nano_node/entry.cpp index 0d70e520..c7210fbe 100644 --- a/nano/nano_node/entry.cpp +++ b/nano/nano_node/entry.cpp @@ -3,8 +3,9 @@ #include #include #include -#include #include +#include +#include #include #include diff --git a/nano/nano_wallet/entry.cpp b/nano/nano_wallet/entry.cpp index f1c71b34..aadd19af 100644 --- a/nano/nano_wallet/entry.cpp +++ b/nano/nano_wallet/entry.cpp @@ -5,9 +5,9 @@ #include #include #include -#include #include #include +#include #include #include diff --git a/nano/nano_wallet/entry_com.cpp b/nano/nano_wallet/entry_com.cpp index 60777ffe..b7ec7b8e 100644 --- a/nano/nano_wallet/entry_com.cpp +++ b/nano/nano_wallet/entry_com.cpp @@ -1,8 +1,8 @@ #include #include #include -#include #include +#include #include #include diff --git a/nano/node/CMakeLists.txt b/nano/node/CMakeLists.txt index 6542965d..d11ab80a 100644 --- a/nano/node/CMakeLists.txt +++ b/nano/node/CMakeLists.txt @@ -1,7 +1,3 @@ -if (NANO_SECURE_RPC OR RAIBLOCKS_SECURE_RPC) - set (secure_rpc_sources rpc_secure.cpp rpc_secure.hpp) -endif () - if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") # No opencl elseif (${CMAKE_SYSTEM_NAME} MATCHES "Windows") @@ -45,10 +41,6 @@ add_library (node portmapping.cpp repcrawler.hpp repcrawler.cpp - rpc.hpp - rpc.cpp - rpcconfig.hpp - rpcconfig.cpp testing.hpp testing.cpp transport/tcp.cpp @@ -75,6 +67,7 @@ add_library (node target_link_libraries (node secure nano_lib + rpc libminiupnpc-static argon2 lmdb diff --git a/nano/node/daemonconfig.hpp b/nano/node/daemonconfig.hpp index 8184f5e6..536f9868 100644 --- a/nano/node/daemonconfig.hpp +++ b/nano/node/daemonconfig.hpp @@ -2,7 +2,7 @@ #include #include -#include +#include namespace nano { diff --git a/nano/node/ipc.cpp b/nano/node/ipc.cpp index 3c1091ab..5bc09a84 100644 --- a/nano/node/ipc.cpp +++ b/nano/node/ipc.cpp @@ -11,87 +11,25 @@ #include #include #include +#include +#include #include #include #include #include -#include +#include +#include #include using namespace boost::log; + namespace { -/** - * The IPC framing format is simple: preamble followed by an encoding specific payload. - * Preamble is uint8_t {'N', encoding_type, reserved, reserved}. Reserved bytes MUST be zero. - * @note This is intentionally not an enum class as the values are only used as vector indices. - */ -enum preamble_offset -{ - /** Always 'N' */ - lead = 0, - /** One of the payload_encoding values */ - encoding = 1, - /** Always zero */ - reserved_1 = 2, - /** Always zero */ - reserved_2 = 3, -}; -} - -/** Abstract base type for sockets, implementing timer logic and a close operation */ -class socket_base -{ -public: - socket_base (boost::asio::io_context & io_ctx_a) : - io_timer (io_ctx_a) - { - } - virtual ~socket_base () = default; - - /** Close socket */ - virtual void close () = 0; - - /** - * Start IO timer. - * @param timeout_a Seconds to wait. To wait indefinitely, use std::chrono::seconds::max () - */ - void timer_start (std::chrono::seconds timeout_a) - { - if (timeout_a < std::chrono::seconds::max ()) - { - io_timer.expires_from_now (boost::posix_time::seconds (static_cast (timeout_a.count ()))); - io_timer.async_wait ([this](const boost::system::error_code & ec) { - if (!ec) - { - this->timer_expired (); - } - }); - } - } - - void timer_expired () - { - close (); - } - - void timer_cancel () - { - boost::system::error_code ec; - io_timer.cancel (ec); - assert (!ec); - } - -private: - /** IO operation timer */ - boost::asio::deadline_timer io_timer; -}; - /** * A session represents an inbound connection over which multiple requests/reponses are transmitted. */ template -class session : public socket_base, public std::enable_shared_from_this> +class session : public nano::ipc::socket_base, public std::enable_shared_from_this> { public: session (nano::ipc::ipc_server & server_a, boost::asio::io_context & io_ctx_a, nano::ipc::ipc_config_transport & config_transport_a) : @@ -202,14 +140,14 @@ public: // Await next request indefinitely buffer.resize (sizeof (buffer_size)); async_read_exactly (buffer.data (), buffer.size (), std::chrono::seconds::max (), [this_l]() { - if (this_l->buffer[preamble_offset::lead] != 'N' || this_l->buffer[preamble_offset::reserved_1] != 0 || this_l->buffer[preamble_offset::reserved_2] != 0) + if (this_l->buffer[nano::ipc::preamble_offset::lead] != 'N' || this_l->buffer[nano::ipc::preamble_offset::reserved_1] != 0 || this_l->buffer[nano::ipc::preamble_offset::reserved_2] != 0) { if (this_l->node.config.logging.log_ipc ()) { this_l->node.logger.always_log ("IPC: Invalid preamble"); } } - else if (this_l->buffer[preamble_offset::encoding] == static_cast (nano::ipc::payload_encoding::json_legacy)) + else if (this_l->buffer[nano::ipc::preamble_offset::encoding] == static_cast (nano::ipc::payload_encoding::json_legacy)) { // Length of payload this_l->async_read_exactly (&this_l->buffer_size, sizeof (this_l->buffer_size), [this_l]() { @@ -349,22 +287,7 @@ private: std::unique_ptr io_ctx; std::unique_ptr acceptor; }; - -/** The domain socket file is attempted removed at both startup and shutdown. */ -class nano::ipc::dsock_file_remover final -{ -public: - dsock_file_remover (std::string const & file_a) : - filename (file_a) - { - std::remove (filename.c_str ()); - } - ~dsock_file_remover () - { - std::remove (filename.c_str ()); - } - std::string filename; -}; +} nano::ipc::ipc_server::ipc_server (nano::node & node_a, nano::rpc & rpc_a) : node (node_a), rpc (rpc_a) @@ -409,221 +332,3 @@ void nano::ipc::ipc_server::stop () transport->stop (); } } - -/** Socket agnostic IO interface */ -class channel -{ -public: - virtual void async_read (std::shared_ptr> buffer_a, size_t size_a, std::function callback_a) = 0; - virtual void async_write (std::shared_ptr> buffer_a, std::function callback_a) = 0; -}; - -/** Domain and TCP client socket */ -template -class socket_client : public socket_base, public channel -{ -public: - socket_client (boost::asio::io_context & io_ctx_a, ENDPOINT_TYPE endpoint_a) : - socket_base (io_ctx_a), endpoint (endpoint_a), socket (io_ctx_a), resolver (io_ctx_a) - { - } - - void async_resolve (std::string const & host_a, uint16_t port_a, std::function callback_a) - { - this->timer_start (io_timeout); - resolver.async_resolve (boost::asio::ip::tcp::resolver::query (host_a, std::to_string (port_a)), [this, callback_a](boost::system::error_code const & ec, boost::asio::ip::tcp::resolver::iterator endpoint_iterator_a) { - this->timer_cancel (); - boost::asio::ip::tcp::resolver::iterator end; - if (!ec && endpoint_iterator_a != end) - { - endpoint = *endpoint_iterator_a; - callback_a (ec, *endpoint_iterator_a); - } - else - { - callback_a (ec, *end); - } - }); - } - - void async_connect (std::function callback_a) - { - this->timer_start (io_timeout); - socket.async_connect (endpoint, [this, callback_a](boost::system::error_code const & ec) { - this->timer_cancel (); - callback_a (ec); - }); - } - - void async_read (std::shared_ptr> buffer_a, size_t size_a, std::function callback_a) override - { - this->timer_start (io_timeout); - buffer_a->resize (size_a); - boost::asio::async_read (socket, boost::asio::buffer (buffer_a->data (), size_a), [this, callback_a](boost::system::error_code const & ec, size_t size_a) { - this->timer_cancel (); - callback_a (ec, size_a); - }); - } - - void async_write (std::shared_ptr> buffer_a, std::function callback_a) override - { - this->timer_start (io_timeout); - boost::asio::async_write (socket, boost::asio::buffer (buffer_a->data (), buffer_a->size ()), [this, callback_a, buffer_a](boost::system::error_code const & ec, size_t size_a) { - this->timer_cancel (); - callback_a (ec, size_a); - }); - } - - /** Shut down and close socket */ - void close () override - { - socket.shutdown (boost::asio::ip::tcp::socket::shutdown_both); - socket.close (); - } - -private: - ENDPOINT_TYPE endpoint; - SOCKET_TYPE socket; - boost::asio::ip::tcp::resolver resolver; - std::chrono::seconds io_timeout{ 60 }; -}; - -/** - * PIMPL class for ipc_client. This ensures that socket_client and boost details can - * stay out of the header file. - */ -class client_impl : public nano::ipc::ipc_client_impl -{ -public: - client_impl (boost::asio::io_context & io_ctx_a) : - io_ctx (io_ctx_a) - { - } - - void connect (std::string const & host_a, uint16_t port_a, std::function callback_a) - { - tcp_client = std::make_shared> (io_ctx, boost::asio::ip::tcp::endpoint (boost::asio::ip::tcp::v6 (), port_a)); - - tcp_client->async_resolve (host_a, port_a, [this, callback_a](boost::system::error_code const & ec_resolve_a, boost::asio::ip::tcp::endpoint endpoint_a) { - if (!ec_resolve_a) - { - this->tcp_client->async_connect ([callback_a](const boost::system::error_code & ec_connect_a) { - callback_a (nano::error (ec_connect_a)); - }); - } - else - { - callback_a (nano::error (ec_resolve_a)); - } - }); - } - - nano::error connect (std::string const & path_a) - { - nano::error err; -#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) - domain_client = std::make_shared> (io_ctx, boost::asio::local::stream_protocol::endpoint (path_a)); -#else - err = nano::error ("Domain sockets are not supported by this platform"); -#endif - return err; - } - - channel & get_channel () - { -#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) - return tcp_client ? static_cast (*tcp_client) : static_cast (*domain_client); -#else - return static_cast (*tcp_client); -#endif - } - -private: - boost::asio::io_context & io_ctx; - std::shared_ptr> tcp_client; -#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) - std::shared_ptr> domain_client; -#endif -}; - -nano::ipc::ipc_client::ipc_client (boost::asio::io_context & io_ctx_a) : -io_ctx (io_ctx_a) -{ -} - -nano::error nano::ipc::ipc_client::connect (std::string const & path_a) -{ - impl = std::make_unique (io_ctx); - return boost::polymorphic_downcast (impl.get ())->connect (path_a); -} - -void nano::ipc::ipc_client::async_connect (std::string const & host_a, uint16_t port_a, std::function callback_a) -{ - impl = std::make_unique (io_ctx); - auto client (boost::polymorphic_downcast (impl.get ())); - client->connect (host_a, port_a, callback_a); -} - -nano::error nano::ipc::ipc_client::connect (std::string const & host, uint16_t port) -{ - std::promise result_l; - async_connect (host, port, [&result_l](nano::error err_a) { - result_l.set_value (err_a); - }); - return result_l.get_future ().get (); -} - -void nano::ipc::ipc_client::async_write (std::shared_ptr> buffer_a, std::function callback_a) -{ - auto client (boost::polymorphic_downcast (impl.get ())); - client->get_channel ().async_write (buffer_a, [callback_a](const boost::system::error_code & ec_a, size_t bytes_written_a) { - callback_a (nano::error (ec_a), bytes_written_a); - }); -} - -void nano::ipc::ipc_client::async_read (std::shared_ptr> buffer_a, size_t size_a, std::function callback_a) -{ - auto client (boost::polymorphic_downcast (impl.get ())); - client->get_channel ().async_read (buffer_a, size_a, [callback_a](const boost::system::error_code & ec_a, size_t bytes_read_a) { - callback_a (nano::error (ec_a), bytes_read_a); - }); -} - -std::shared_ptr> nano::ipc::ipc_client::prepare_request (nano::ipc::payload_encoding encoding_a, std::string const & payload_a) -{ - auto buffer_l (std::make_shared> ()); - if (encoding_a == nano::ipc::payload_encoding::json_legacy) - { - buffer_l->push_back ('N'); - buffer_l->push_back (static_cast (encoding_a)); - buffer_l->push_back (0); - buffer_l->push_back (0); - - auto payload_length = static_cast (payload_a.size ()); - uint32_t be = boost::endian::native_to_big (payload_length); - char * chars = reinterpret_cast (&be); - buffer_l->insert (buffer_l->end (), chars, chars + sizeof (uint32_t)); - buffer_l->insert (buffer_l->end (), payload_a.begin (), payload_a.end ()); - } - return buffer_l; -} - -std::string nano::ipc::rpc_ipc_client::request (std::string const & rpc_action_a) -{ - auto req (prepare_request (nano::ipc::payload_encoding::json_legacy, rpc_action_a)); - auto res (std::make_shared> ()); - - std::promise result_l; - async_write (req, [this, &res, &result_l](nano::error err_a, size_t size_a) { - // Read length - this->async_read (res, sizeof (uint32_t), [this, &res, &result_l](nano::error err_read_a, size_t size_read_a) { - uint32_t payload_size_l = boost::endian::big_to_native (*reinterpret_cast (res->data ())); - // Read json payload - this->async_read (res, payload_size_l, [&res, &result_l](nano::error err_read_a, size_t size_read_a) { - result_l.set_value (std::string (res->begin (), res->end ())); - }); - }); - }); - - return result_l.get_future ().get (); -} diff --git a/nano/node/ipc.hpp b/nano/node/ipc.hpp index b9ae57a3..fd1711d3 100644 --- a/nano/node/ipc.hpp +++ b/nano/node/ipc.hpp @@ -1,46 +1,17 @@ #pragma once #include -#include -#include -#include +#include #include -#include #include namespace nano { class node; class rpc; -} -namespace nano -{ namespace ipc { - /** - * Payload encodings; add protobuf, flatbuffers and so on as needed. - */ - enum class payload_encoding : uint8_t - { - /** - * Request is preamble followed by 32-bit BE payload length and payload bytes. - * Response is 32-bit BE payload length followed by payload bytes. - */ - json_legacy = 1 - }; - - /** Removes domain socket files on startup and shutdown */ - class dsock_file_remover; - - /** IPC transport interface */ - class transport - { - public: - virtual void stop () = 0; - virtual ~transport () = default; - }; - /** The IPC server accepts connections on one or more configured transports */ class ipc_server { @@ -59,58 +30,5 @@ namespace ipc std::unique_ptr file_remover; std::vector> transports; }; - - class ipc_client_impl - { - public: - virtual ~ipc_client_impl () = default; - }; - - /** IPC client */ - class ipc_client - { - public: - ipc_client (boost::asio::io_context & io_ctx_a); - virtual ~ipc_client () = default; - - /** Connect to a domain socket */ - nano::error connect (std::string const & path); - - /** Connect to a tcp socket synchronously */ - nano::error connect (std::string const & host, uint16_t port); - - /** Connect to a tcp socket asynchronously */ - void async_connect (std::string const & host, uint16_t port, std::function callback); - - /** Write buffer asynchronously */ - void async_write (std::shared_ptr> buffer_a, std::function callback_a); - - /** Read \p size_a bytes asynchronously */ - void async_read (std::shared_ptr> buffer_a, size_t size_a, std::function callback_a); - - /** - * Returns a buffer with an IPC preamble for the given \p encoding_a followed by the payload. Depending on encoding, - * the buffer may contain a payload length or end sentinel. - */ - std::shared_ptr> prepare_request (nano::ipc::payload_encoding encoding_a, std::string const & payload_a); - - private: - boost::asio::io_context & io_ctx; - - // PIMPL pattern to hide implementation details - std::unique_ptr impl; - }; - - /** Convenience wrapper for making synchronous RPC calls via IPC */ - class rpc_ipc_client : public ipc_client - { - public: - rpc_ipc_client (boost::asio::io_context & io_ctx_a) : - ipc_client (io_ctx_a) - { - } - /** Calls the RPC server via IPC and waits for the result. The client must be connected. */ - std::string request (std::string const & rpc_action_a); - }; } } diff --git a/nano/node/logging.hpp b/nano/node/logging.hpp index 0b2f5cf8..5aca027e 100644 --- a/nano/node/logging.hpp +++ b/nano/node/logging.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #define FATAL_LOG_PREFIX "FATAL ERROR: " @@ -13,79 +14,6 @@ using namespace std::chrono; namespace nano { -// A wrapper around a boost logger object to allow -// minimum time spaced output to prevent logging happening -// too quickly. -class logger_mt -{ -private: - void add_to_stream (boost::log::record_ostream & stream) - { - } - - template - void add_to_stream (boost::log::record_ostream & stream, const LogItem & first_log_item, LogItems &&... remainder_log_items) - { - stream << first_log_item; - add_to_stream (stream, remainder_log_items...); - } - - template - void output (LogItems &&... log_items) - { - boost::log::record rec = boost_logger_mt.open_record (); - if (rec) - { - boost::log::record_ostream strm (rec); - add_to_stream (strm, std::forward (log_items)...); - strm.flush (); - boost_logger_mt.push_record (std::move (rec)); - } - } - -public: - /** - * @param min_log_delta_a The minimum time between successive output - */ - explicit logger_mt (std::chrono::milliseconds const & min_log_delta_a) : - min_log_delta (min_log_delta_a) - { - } - - /* - * @param log_items A collection of objects with overloaded operator<< to be output to the log file - */ - template - void always_log (LogItems &&... log_items) - { - output (std::forward (log_items)...); - } - - /* - * @param log_items Output to the log file if the last write was over min_log_delta time ago. - * @return true if the log was successful - */ - template - bool try_log (LogItems &&... log_items) - { - auto error (true); - auto time_now = std::chrono::steady_clock::now (); - if (((time_now - last_log_time) > min_log_delta) || last_log_time == std::chrono::steady_clock::time_point{}) - { - output (std::forward (log_items)...); - last_log_time = time_now; - error = false; - } - return error; - } - - std::chrono::milliseconds min_log_delta; - -private: - std::chrono::steady_clock::time_point last_log_time; - boost::log::sources::logger_mt boost_logger_mt; -}; - class logging { public: diff --git a/nano/node/node.cpp b/nano/node/node.cpp index c0502496..29a5fbb0 100644 --- a/nano/node/node.cpp +++ b/nano/node/node.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include @@ -3465,49 +3465,6 @@ int nano::node::store_version () return store.version_get (transaction); } -nano::thread_runner::thread_runner (boost::asio::io_context & io_ctx_a, unsigned service_threads_a) -{ - boost::thread::attributes attrs; - nano::thread_attributes::set (attrs); - for (auto i (0u); i < service_threads_a; ++i) - { - threads.push_back (boost::thread (attrs, [&io_ctx_a]() { - nano::thread_role::set (nano::thread_role::name::io); - try - { - io_ctx_a.run (); - } - catch (...) - { -#ifndef NDEBUG - /* - * In a release build, catch and swallow the - * io_context exception, in debug mode pass it - * on - */ - throw; -#endif - } - })); - } -} - -nano::thread_runner::~thread_runner () -{ - join (); -} - -void nano::thread_runner::join () -{ - for (auto & i : threads) - { - if (i.joinable ()) - { - i.join (); - } - } -} - nano::inactive_node::inactive_node (boost::filesystem::path const & path_a, uint16_t peering_port_a) : path (path_a), io_context (std::make_shared ()), diff --git a/nano/node/node.hpp b/nano/node/node.hpp index 03d825c8..68606692 100644 --- a/nano/node/node.hpp +++ b/nano/node/node.hpp @@ -28,15 +28,6 @@ #include #include -#define xstr(a) ver_str (a) -#define ver_str(a) #a - -/** -* Returns build version information -*/ -static const char * NANO_MAJOR_MINOR_VERSION = xstr (NANO_VERSION_MAJOR) "." xstr (NANO_VERSION_MINOR); -static const char * NANO_MAJOR_MINOR_RC_VERSION = xstr (NANO_VERSION_MAJOR) "." xstr (NANO_VERSION_MINOR) "RC" xstr (NANO_VERSION_PATCH); - namespace nano { class channel; @@ -521,14 +512,6 @@ private: std::unique_ptr collect_seq_con_info (node & node, const std::string & name); -class thread_runner final -{ -public: - thread_runner (boost::asio::io_context &, unsigned); - ~thread_runner (); - void join (); - std::vector threads; -}; class inactive_node final { public: diff --git a/nano/node/rpc.cpp b/nano/node/rpc.cpp index 4f98673b..3e24c80a 100644 --- a/nano/node/rpc.cpp +++ b/nano/node/rpc.cpp @@ -4420,163 +4420,6 @@ void nano::rpc_handler::work_peers_clear () response_errors (); } -nano::rpc_connection::rpc_connection (nano::node & node_a, nano::rpc & rpc_a) : -node (node_a.shared ()), -rpc (rpc_a), -socket (node_a.io_ctx) -{ - responded.clear (); -} - -void nano::rpc_connection::parse_connection () -{ - read (); -} - -void nano::rpc_connection::prepare_head (unsigned version, boost::beast::http::status status) -{ - res.version (version); - res.result (status); - res.set (boost::beast::http::field::allow, "POST, OPTIONS"); - res.set (boost::beast::http::field::content_type, "application/json"); - res.set (boost::beast::http::field::access_control_allow_origin, "*"); - res.set (boost::beast::http::field::access_control_allow_methods, "POST, OPTIONS"); - res.set (boost::beast::http::field::access_control_allow_headers, "Accept, Accept-Language, Content-Language, Content-Type"); - res.set (boost::beast::http::field::connection, "close"); -} - -void nano::rpc_connection::write_result (std::string body, unsigned version, boost::beast::http::status status) -{ - if (!responded.test_and_set ()) - { - prepare_head (version, status); - res.body () = body; - res.prepare_payload (); - } - else - { - assert (false && "RPC already responded and should only respond once"); - // Guards `res' from being clobbered while async_write is being serviced - } -} - -void nano::rpc_connection::read () -{ - auto this_l (shared_from_this ()); - boost::system::error_code header_error; - auto header_parser (std::make_shared> ()); - std::promise header_available_promise; - std::future header_available = header_available_promise.get_future (); - header_parser->body_limit (rpc.config.max_request_size); - if (!node->network_params.network.is_test_network ()) - { - boost::beast::http::async_read_header (socket, buffer, *header_parser, [this_l, header_parser, &header_available_promise, &header_error](boost::system::error_code const & ec, size_t bytes_transferred) { - size_t header_response_bytes_written = 0; - if (!ec) - { - if (boost::iequals (header_parser->get ()[boost::beast::http::field::expect], "100-continue")) - { - boost::beast::http::response continue_response; - continue_response.version (11); - continue_response.result (boost::beast::http::status::continue_); - continue_response.set (boost::beast::http::field::server, "nano"); - auto response_size (boost::beast::http::async_write (this_l->socket, continue_response, boost::asio::use_future)); - header_response_bytes_written = response_size.get (); - } - } - else - { - header_error = ec; - this_l->node->logger.always_log ("RPC header error: ", ec.message ()); - } - - header_available_promise.set_value (header_response_bytes_written); - }); - - // Avait header - header_available.get (); - } - - if (!header_error) - { - auto body_parser (std::make_shared> (std::move (*header_parser))); - boost::beast::http::async_read (socket, buffer, *body_parser, [this_l, body_parser](boost::system::error_code const & ec, size_t bytes_transferred) { - if (!ec) - { - this_l->node->background ([this_l, body_parser]() { - auto & req (body_parser->get ()); - auto start (std::chrono::steady_clock::now ()); - auto version (req.version ()); - std::string request_id (boost::str (boost::format ("%1%") % boost::io::group (std::hex, std::showbase, reinterpret_cast (this_l.get ())))); - auto response_handler ([this_l, version, start, request_id](boost::property_tree::ptree const & tree_a) { - std::stringstream ostream; - boost::property_tree::write_json (ostream, tree_a); - ostream.flush (); - auto body (ostream.str ()); - this_l->write_result (body, version); - boost::beast::http::async_write (this_l->socket, this_l->res, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) { - this_l->write_completion_handler (this_l); - }); - - if (this_l->node->config.logging.log_rpc ()) - { - this_l->node->logger.always_log (boost::str (boost::format ("RPC request %2% completed in: %1% microseconds") % std::chrono::duration_cast (std::chrono::steady_clock::now () - start).count () % request_id)); - } - }); - auto method = req.method (); - switch (method) - { - case boost::beast::http::verb::post: - { - auto handler (std::make_shared (*this_l->node, this_l->rpc, req.body (), request_id, response_handler)); - handler->process_request (); - break; - } - case boost::beast::http::verb::options: - { - this_l->prepare_head (version); - this_l->res.prepare_payload (); - boost::beast::http::async_write (this_l->socket, this_l->res, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) { - this_l->write_completion_handler (this_l); - }); - break; - } - default: - { - error_response (response_handler, "Can only POST requests"); - break; - } - } - }); - } - else - { - this_l->node->logger.always_log ("RPC read error: ", ec.message ()); - } - }); - } - else - { - // Respond with the reason for the invalid header - auto response_handler ([this_l](boost::property_tree::ptree const & tree_a) { - std::stringstream ostream; - boost::property_tree::write_json (ostream, tree_a); - ostream.flush (); - auto body (ostream.str ()); - this_l->write_result (body, 11); - boost::beast::http::async_write (this_l->socket, this_l->res, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) { - this_l->write_completion_handler (this_l); - }); - }); - error_response (response_handler, std::string ("Invalid header: ") + header_error.message ()); - } -} - -void nano::rpc_connection::write_completion_handler (std::shared_ptr rpc_connection) -{ - // Intentional no-op -} - namespace { std::string filter_request (boost::property_tree::ptree tree_a) diff --git a/nano/rpc/CMakeLists.txt b/nano/rpc/CMakeLists.txt new file mode 100644 index 00000000..404e0b72 --- /dev/null +++ b/nano/rpc/CMakeLists.txt @@ -0,0 +1,24 @@ +if (NANO_SECURE_RPC OR RAIBLOCKS_SECURE_RPC) + set (secure_rpc_sources rpc_secure.hpp rpc_secure.cpp rpc_connection_secure.hpp rpc_connection_secure.cpp) +endif () + +add_library (rpc + ${secure_rpc_sources} + rpc.hpp + rpc.cpp + rpc_connection.hpp + rpc_connection.cpp + rpc_handler.hpp + rpc_handler.cpp) + +target_link_libraries (rpc + node + nano_lib + ${OPENSSL_LIBRARIES} + Boost::boost) + +target_compile_definitions(rpc + PRIVATE + -DNANO_VERSION_MAJOR=${CPACK_PACKAGE_VERSION_MAJOR} + -DNANO_VERSION_MINOR=${CPACK_PACKAGE_VERSION_MINOR} + -DNANO_VERSION_PATCH=${CPACK_PACKAGE_VERSION_PATCH}) \ No newline at end of file diff --git a/nano/rpc/rpc.cpp b/nano/rpc/rpc.cpp new file mode 100644 index 00000000..76d7cad7 --- /dev/null +++ b/nano/rpc/rpc.cpp @@ -0,0 +1,184 @@ +#include +#include +#include +#include + +#include +#include +#include + +#ifdef NANO_SECURE_RPC +#include +#endif + +#include + +nano::rpc::rpc (boost::asio::io_context & io_ctx_a, nano::node & node_a, nano::rpc_config const & config_a) : +acceptor (io_ctx_a), +config (config_a), +node (node_a) +{ +} + +void nano::rpc::add_block_observer () +{ + node.observers.blocks.add ([this](std::shared_ptr block_a, nano::account const & account_a, nano::uint128_t const &, bool) { + observer_action (account_a); + }); +} + +void nano::rpc::start (bool rpc_enabled_a) +{ + if (rpc_enabled_a) + { + auto endpoint (nano::tcp_endpoint (config.address, config.port)); + acceptor.open (endpoint.protocol ()); + acceptor.set_option (boost::asio::ip::tcp::acceptor::reuse_address (true)); + + boost::system::error_code ec; + acceptor.bind (endpoint, ec); + if (ec) + { + node.logger.always_log (boost::str (boost::format ("Error while binding for RPC on port %1%: %2%") % endpoint.port () % ec.message ())); + throw std::runtime_error (ec.message ()); + } + + acceptor.listen (); + } + + add_block_observer (); + + if (rpc_enabled_a) + { + accept (); + } +} + +void nano::rpc::accept () +{ + auto connection (std::make_shared (node, *this)); + acceptor.async_accept (connection->socket, [this, connection](boost::system::error_code const & ec) { + if (ec != boost::asio::error::operation_aborted && acceptor.is_open ()) + { + accept (); + } + if (!ec) + { + connection->parse_connection (); + } + else + { + this->node.logger.always_log (boost::str (boost::format ("Error accepting RPC connections: %1% (%2%)") % ec.message () % ec.value ())); + } + }); +} + +void nano::rpc::stop () +{ + acceptor.close (); +} + +void nano::rpc::observer_action (nano::account const & account_a) +{ + std::shared_ptr observer; + { + std::lock_guard lock (mutex); + auto existing (payment_observers.find (account_a)); + if (existing != payment_observers.end ()) + { + observer = existing->second; + } + } + if (observer != nullptr) + { + observer->observe (); + } +} + +nano::payment_observer::payment_observer (std::function const & response_a, nano::rpc & rpc_a, nano::account const & account_a, nano::amount const & amount_a) : +rpc (rpc_a), +account (account_a), +amount (amount_a), +response (response_a) +{ + completed.clear (); +} + +void nano::payment_observer::start (uint64_t timeout) +{ + auto this_l (shared_from_this ()); + rpc.node.alarm.add (std::chrono::steady_clock::now () + std::chrono::milliseconds (timeout), [this_l]() { + this_l->complete (nano::payment_status::nothing); + }); +} + +nano::payment_observer::~payment_observer () +{ +} + +void nano::payment_observer::observe () +{ + if (rpc.node.balance (account) >= amount.number ()) + { + complete (nano::payment_status::success); + } +} + +void nano::payment_observer::complete (nano::payment_status status) +{ + auto already (completed.test_and_set ()); + if (!already) + { + if (rpc.node.config.logging.log_rpc ()) + { + rpc.node.logger.always_log (boost::str (boost::format ("Exiting payment_observer for account %1% status %2%") % account.to_account () % static_cast (status))); + } + switch (status) + { + case nano::payment_status::nothing: + { + boost::property_tree::ptree response_l; + response_l.put ("deprecated", "1"); + response_l.put ("status", "nothing"); + response (response_l); + break; + } + case nano::payment_status::success: + { + boost::property_tree::ptree response_l; + response_l.put ("deprecated", "1"); + response_l.put ("status", "success"); + response (response_l); + break; + } + default: + { + error_response (response, "Internal payment error"); + break; + } + } + std::lock_guard lock (rpc.mutex); + assert (rpc.payment_observers.find (account) != rpc.payment_observers.end ()); + rpc.payment_observers.erase (account); + } +} + +std::unique_ptr nano::get_rpc (boost::asio::io_context & io_ctx_a, nano::node & node_a, nano::rpc_config const & config_a) +{ + std::unique_ptr impl; + + if (config_a.secure.enable) + { +#ifdef NANO_SECURE_RPC + impl.reset (new rpc_secure (io_ctx_a, node_a, config_a)); +#else + std::cerr << "RPC configured for TLS, but the node is not compiled with TLS support" << std::endl; +#endif + } + else + { + impl.reset (new rpc (io_ctx_a, node_a, config_a)); + } + + return impl; +} diff --git a/nano/rpc/rpc.hpp b/nano/rpc/rpc.hpp new file mode 100644 index 00000000..ab1ec694 --- /dev/null +++ b/nano/rpc/rpc.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nano +{ +class node; +enum class payment_status +{ + not_a_status, + unknown, + nothing, // Timeout and nothing was received + //insufficient, // Timeout and not enough was received + //over, // More than requested received + //success_fork, // Amount received but it involved a fork + success // Amount received +}; +class wallet; +class payment_observer; +class rpc +{ +public: + rpc (boost::asio::io_context &, nano::node &, nano::rpc_config const &); + virtual ~rpc () = default; + + /** + * Start serving RPC requests if \p rpc_enabled_a, otherwise this will only + * add a block observer since requests may still arrive via IPC. + */ + void start (bool rpc_enabled_a = true); + void add_block_observer (); + virtual void accept (); + void stop (); + void observer_action (nano::account const &); + boost::asio::ip::tcp::acceptor acceptor; + std::mutex mutex; + std::unordered_map> payment_observers; + nano::rpc_config config; + nano::node & node; + bool on; +}; + +class payment_observer : public std::enable_shared_from_this +{ +public: + payment_observer (std::function const &, nano::rpc &, nano::account const &, nano::amount const &); + ~payment_observer (); + void start (uint64_t); + void observe (); + void complete (nano::payment_status); + std::mutex mutex; + std::condition_variable condition; + nano::rpc & rpc; + nano::account account; + nano::amount amount; + std::function response; + std::atomic_flag completed; +}; + +/** Returns the correct RPC implementation based on TLS configuration */ +std::unique_ptr get_rpc (boost::asio::io_context & io_ctx_a, nano::node & node_a, nano::rpc_config const & config_a); +} diff --git a/nano/rpc/rpc_connection.cpp b/nano/rpc/rpc_connection.cpp new file mode 100644 index 00000000..f9bae588 --- /dev/null +++ b/nano/rpc/rpc_connection.cpp @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include +#include + +nano::rpc_connection::rpc_connection (nano::node & node_a, nano::rpc & rpc_a) : +node (node_a.shared ()), +rpc (rpc_a), +socket (node_a.io_ctx) +{ + responded.clear (); +} + +void nano::rpc_connection::parse_connection () +{ + read (); +} + +void nano::rpc_connection::prepare_head (unsigned version, boost::beast::http::status status) +{ + res.version (version); + res.result (status); + res.set (boost::beast::http::field::allow, "POST, OPTIONS"); + res.set (boost::beast::http::field::content_type, "application/json"); + res.set (boost::beast::http::field::access_control_allow_origin, "*"); + res.set (boost::beast::http::field::access_control_allow_methods, "POST, OPTIONS"); + res.set (boost::beast::http::field::access_control_allow_headers, "Accept, Accept-Language, Content-Language, Content-Type"); + res.set (boost::beast::http::field::connection, "close"); +} + +void nano::rpc_connection::write_result (std::string body, unsigned version, boost::beast::http::status status) +{ + if (!responded.test_and_set ()) + { + prepare_head (version, status); + res.body () = body; + res.prepare_payload (); + } + else + { + assert (false && "RPC already responded and should only respond once"); + // Guards `res' from being clobbered while async_write is being serviced + } +} + +void nano::rpc_connection::read () +{ + auto this_l (shared_from_this ()); + boost::system::error_code header_error; + auto header_parser (std::make_shared> ()); + std::promise header_available_promise; + std::future header_available = header_available_promise.get_future (); + header_parser->body_limit (rpc.config.max_request_size); + if (!node->network_params.network.is_test_network ()) + { + boost::beast::http::async_read_header (socket, buffer, *header_parser, [this_l, header_parser, &header_available_promise, &header_error](boost::system::error_code const & ec, size_t bytes_transferred) { + size_t header_response_bytes_written = 0; + if (!ec) + { + if (boost::iequals (header_parser->get ()[boost::beast::http::field::expect], "100-continue")) + { + boost::beast::http::response continue_response; + continue_response.version (11); + continue_response.result (boost::beast::http::status::continue_); + continue_response.set (boost::beast::http::field::server, "nano"); + auto response_size (boost::beast::http::async_write (this_l->socket, continue_response, boost::asio::use_future)); + header_response_bytes_written = response_size.get (); + } + } + else + { + header_error = ec; + this_l->node->logger.always_log ("RPC header error: ", ec.message ()); + } + + header_available_promise.set_value (header_response_bytes_written); + }); + + // Avait header + header_available.get (); + } + + if (!header_error) + { + auto body_parser (std::make_shared> (std::move (*header_parser))); + boost::beast::http::async_read (socket, buffer, *body_parser, [this_l, body_parser](boost::system::error_code const & ec, size_t bytes_transferred) { + if (!ec) + { + this_l->node->background ([this_l, body_parser]() { + auto & req (body_parser->get ()); + auto start (std::chrono::steady_clock::now ()); + auto version (req.version ()); + std::string request_id (boost::str (boost::format ("%1%") % boost::io::group (std::hex, std::showbase, reinterpret_cast (this_l.get ())))); + auto response_handler ([this_l, version, start, request_id](boost::property_tree::ptree const & tree_a) { + std::stringstream ostream; + boost::property_tree::write_json (ostream, tree_a); + ostream.flush (); + auto body (ostream.str ()); + this_l->write_result (body, version); + boost::beast::http::async_write (this_l->socket, this_l->res, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) { + this_l->write_completion_handler (this_l); + }); + + if (this_l->node->config.logging.log_rpc ()) + { + this_l->node->logger.always_log (boost::str (boost::format ("RPC request %2% completed in: %1% microseconds") % std::chrono::duration_cast (std::chrono::steady_clock::now () - start).count () % request_id)); + } + }); + auto method = req.method (); + switch (method) + { + case boost::beast::http::verb::post: + { + auto handler (std::make_shared (*this_l->node, this_l->rpc, req.body (), request_id, response_handler)); + handler->process_request (); + break; + } + case boost::beast::http::verb::options: + { + this_l->prepare_head (version); + this_l->res.prepare_payload (); + boost::beast::http::async_write (this_l->socket, this_l->res, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) { + this_l->write_completion_handler (this_l); + }); + break; + } + default: + { + error_response (response_handler, "Can only POST requests"); + break; + } + } + }); + } + else + { + this_l->node->logger.always_log ("RPC read error: ", ec.message ()); + } + }); + } + else + { + // Respond with the reason for the invalid header + auto response_handler ([this_l](boost::property_tree::ptree const & tree_a) { + std::stringstream ostream; + boost::property_tree::write_json (ostream, tree_a); + ostream.flush (); + auto body (ostream.str ()); + this_l->write_result (body, 11); + boost::beast::http::async_write (this_l->socket, this_l->res, [this_l](boost::system::error_code const & ec, size_t bytes_transferred) { + this_l->write_completion_handler (this_l); + }); + }); + error_response (response_handler, std::string ("Invalid header: ") + header_error.message ()); + } +} + +void nano::rpc_connection::write_completion_handler (std::shared_ptr rpc_connection) +{ + // Intentional no-op +} \ No newline at end of file diff --git a/nano/rpc/rpc_connection.hpp b/nano/rpc/rpc_connection.hpp new file mode 100644 index 00000000..557f4651 --- /dev/null +++ b/nano/rpc/rpc_connection.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +namespace nano +{ +class rpc; +class rpc_connection : public std::enable_shared_from_this +{ +public: + rpc_connection (nano::node &, nano::rpc &); + virtual ~rpc_connection () = default; + virtual void parse_connection (); + virtual void write_completion_handler (std::shared_ptr rpc_connection); + virtual void prepare_head (unsigned version, boost::beast::http::status status = boost::beast::http::status::ok); + virtual void write_result (std::string body, unsigned version, boost::beast::http::status status = boost::beast::http::status::ok); + void read (); + std::shared_ptr node; + nano::rpc & rpc; + boost::asio::ip::tcp::socket socket; + boost::beast::flat_buffer buffer; + boost::beast::http::response res; + std::atomic_flag responded; +}; +} diff --git a/nano/rpc/rpc_connection_secure.cpp b/nano/rpc/rpc_connection_secure.cpp new file mode 100644 index 00000000..dc823e95 --- /dev/null +++ b/nano/rpc/rpc_connection_secure.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include + +nano::rpc_connection_secure::rpc_connection_secure (nano::node & node_a, nano::rpc_secure & rpc_a) : +nano::rpc_connection (node_a, rpc_a), +stream (socket, rpc_a.ssl_context) +{ +} + +void nano::rpc_connection_secure::parse_connection () +{ + // Perform the SSL handshake + auto this_l = std::static_pointer_cast (shared_from_this ()); + stream.async_handshake (boost::asio::ssl::stream_base::server, + [this_l](auto & ec) { + this_l->handle_handshake (ec); + }); +} + +void nano::rpc_connection_secure::on_shutdown (const boost::system::error_code & error) +{ + // No-op. We initiate the shutdown (since the RPC server kills the connection after each request) + // and we'll thus get an expected EOF error. If the client disconnects, a short-read error will be expected. +} + +void nano::rpc_connection_secure::handle_handshake (const boost::system::error_code & error) +{ + if (!error) + { + read (); + } + else + { + node->logger.always_log ("TLS: Handshake error: ", error.message ()); + } +} + +void nano::rpc_connection_secure::write_completion_handler (std::shared_ptr rpc) +{ + auto rpc_connection_secure = boost::polymorphic_pointer_downcast (rpc); + rpc_connection_secure->stream.async_shutdown ([rpc_connection_secure](auto const & ec_shutdown) { + rpc_connection_secure->on_shutdown (ec_shutdown); + }); +} diff --git a/nano/rpc/rpc_connection_secure.hpp b/nano/rpc/rpc_connection_secure.hpp new file mode 100644 index 00000000..578f97c1 --- /dev/null +++ b/nano/rpc/rpc_connection_secure.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace nano +{ +class rpc_secure; +/** + * Specialization of nano::rpc_connection for establishing TLS connections. + * Handshakes with client certificates are supported. + */ +class rpc_connection_secure : public rpc_connection +{ +public: + rpc_connection_secure (nano::node &, nano::rpc_secure &); + void parse_connection () override; + void write_completion_handler (std::shared_ptr rpc) override; + /** The TLS handshake callback */ + void handle_handshake (const boost::system::error_code & error); + /** The TLS async shutdown callback */ + void on_shutdown (const boost::system::error_code & error); + +private: + boost::asio::ssl::stream stream; +}; +} diff --git a/nano/rpc/rpc_handler.cpp b/nano/rpc/rpc_handler.cpp new file mode 100644 index 00000000..ca0c8f59 --- /dev/null +++ b/nano/rpc/rpc_handler.cpp @@ -0,0 +1,4620 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +void construct_json (nano::seq_con_info_component * component, boost::property_tree::ptree & parent); +using rpc_handler_no_arg_func_map = std::unordered_map>; +rpc_handler_no_arg_func_map create_rpc_handler_no_arg_func_map (); +auto rpc_handler_no_arg_funcs = create_rpc_handler_no_arg_func_map (); +} + +nano::rpc_handler::rpc_handler (nano::node & node_a, nano::rpc & rpc_a, std::string const & body_a, std::string const & request_id_a, std::function const & response_a) : +body (body_a), +request_id (request_id_a), +node (node_a), +rpc (rpc_a), +response (response_a) +{ +} + +void nano::rpc_handler::response_errors () +{ + if (ec || response_l.empty ()) + { + boost::property_tree::ptree response_error; + response_error.put ("error", ec ? ec.message () : "Empty response"); + response (response_error); + } + else + { + response (response_l); + } +} + +std::shared_ptr nano::rpc_handler::wallet_impl () +{ + if (!ec) + { + std::string wallet_text (request.get ("wallet")); + nano::uint256_union wallet; + if (!wallet.decode_hex (wallet_text)) + { + auto existing (node.wallets.items.find (wallet)); + if (existing != node.wallets.items.end ()) + { + return existing->second; + } + else + { + ec = nano::error_common::wallet_not_found; + } + } + else + { + ec = nano::error_common::bad_wallet_number; + } + } + return nullptr; +} + +bool nano::rpc_handler::wallet_locked_impl (nano::transaction const & transaction_a, std::shared_ptr wallet_a) +{ + bool result (false); + if (!ec) + { + if (!wallet_a->store.valid_password (transaction_a)) + { + ec = nano::error_common::wallet_locked; + result = true; + } + } + return result; +} + +bool nano::rpc_handler::wallet_account_impl (nano::transaction const & transaction_a, std::shared_ptr wallet_a, nano::account const & account_a) +{ + bool result (false); + if (!ec) + { + if (wallet_a->store.find (transaction_a, account_a) != wallet_a->store.end ()) + { + result = true; + } + else + { + ec = nano::error_common::account_not_found_wallet; + } + } + return result; +} + +nano::account nano::rpc_handler::account_impl (std::string account_text) +{ + nano::account result (0); + if (!ec) + { + if (account_text.empty ()) + { + account_text = request.get ("account"); + } + if (result.decode_account (account_text)) + { + ec = nano::error_common::bad_account_number; + } + } + return result; +} + +nano::amount nano::rpc_handler::amount_impl () +{ + nano::amount result (0); + if (!ec) + { + std::string amount_text (request.get ("amount")); + if (result.decode_dec (amount_text)) + { + ec = nano::error_common::invalid_amount; + } + } + return result; +} + +std::shared_ptr nano::rpc_handler::block_impl (bool signature_work_required) +{ + std::shared_ptr result; + if (!ec) + { + std::string block_text (request.get ("block")); + boost::property_tree::ptree block_l; + std::stringstream block_stream (block_text); + boost::property_tree::read_json (block_stream, block_l); + if (!signature_work_required) + { + block_l.put ("signature", "0"); + block_l.put ("work", "0"); + } + result = nano::deserialize_block_json (block_l); + if (result == nullptr) + { + ec = nano::error_blocks::invalid_block; + } + } + return result; +} + +std::shared_ptr nano::rpc_handler::block_json_impl (bool signature_work_required) +{ + std::shared_ptr result; + if (!ec) + { + auto block_l (request.get_child ("block")); + if (!signature_work_required) + { + block_l.put ("signature", "0"); + block_l.put ("work", "0"); + } + result = nano::deserialize_block_json (block_l); + if (result == nullptr) + { + ec = nano::error_blocks::invalid_block; + } + } + return result; +} + +nano::block_hash nano::rpc_handler::hash_impl (std::string search_text) +{ + nano::block_hash result (0); + if (!ec) + { + std::string hash_text (request.get (search_text)); + if (result.decode_hex (hash_text)) + { + ec = nano::error_blocks::invalid_block_hash; + } + } + return result; +} + +nano::amount nano::rpc_handler::threshold_optional_impl () +{ + nano::amount result (0); + boost::optional threshold_text (request.get_optional ("threshold")); + if (!ec && threshold_text.is_initialized ()) + { + if (result.decode_dec (threshold_text.get ())) + { + ec = nano::error_common::bad_threshold; + } + } + return result; +} + +uint64_t nano::rpc_handler::work_optional_impl () +{ + uint64_t result (0); + boost::optional work_text (request.get_optional ("work")); + if (!ec && work_text.is_initialized ()) + { + if (nano::from_string_hex (work_text.get (), result)) + { + ec = nano::error_common::bad_work_format; + } + } + return result; +} + +namespace +{ +bool decode_unsigned (std::string const & text, uint64_t & number) +{ + bool result; + size_t end; + try + { + number = std::stoull (text, &end); + result = false; + } + catch (std::invalid_argument const &) + { + result = true; + } + catch (std::out_of_range const &) + { + result = true; + } + result = result || end != text.size (); + return result; +} +} + +uint64_t nano::rpc_handler::count_impl () +{ + uint64_t result (0); + if (!ec) + { + std::string count_text (request.get ("count")); + if (decode_unsigned (count_text, result) || result == 0) + { + ec = nano::error_common::invalid_count; + } + } + return result; +} + +uint64_t nano::rpc_handler::count_optional_impl (uint64_t result) +{ + boost::optional count_text (request.get_optional ("count")); + if (!ec && count_text.is_initialized ()) + { + if (decode_unsigned (count_text.get (), result)) + { + ec = nano::error_common::invalid_count; + } + } + return result; +} + +uint64_t nano::rpc_handler::offset_optional_impl (uint64_t result) +{ + boost::optional offset_text (request.get_optional ("offset")); + if (!ec && offset_text.is_initialized ()) + { + if (decode_unsigned (offset_text.get (), result)) + { + ec = nano::error_rpc::invalid_offset; + } + } + return result; +} + +bool nano::rpc_handler::rpc_control_impl () +{ + bool result (false); + if (!ec) + { + if (!rpc.config.enable_control) + { + ec = nano::error_rpc::rpc_control_disabled; + } + else + { + result = true; + } + } + return result; +} + +void nano::rpc_handler::account_balance () +{ + auto account (account_impl ()); + if (!ec) + { + auto balance (node.balance_pending (account)); + response_l.put ("balance", balance.first.convert_to ()); + response_l.put ("pending", balance.second.convert_to ()); + } + response_errors (); +} + +void nano::rpc_handler::account_block_count () +{ + auto account (account_impl ()); + if (!ec) + { + auto transaction (node.store.tx_begin_read ()); + nano::account_info info; + if (!node.store.account_get (transaction, account, info)) + { + response_l.put ("block_count", std::to_string (info.block_count)); + } + else + { + ec = nano::error_common::account_not_found; + } + } + response_errors (); +} + +void nano::rpc_handler::account_create () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + if (!ec) + { + const bool generate_work = request.get ("work", true); + nano::account new_key; + auto index_text (request.get_optional ("index")); + if (index_text.is_initialized ()) + { + uint64_t index; + if (decode_unsigned (index_text.get (), index) || index > static_cast (std::numeric_limits::max ())) + { + ec = nano::error_common::invalid_index; + } + else + { + new_key = wallet->deterministic_insert (static_cast (index), generate_work); + } + } + else + { + new_key = wallet->deterministic_insert (generate_work); + } + + if (!ec) + { + if (!new_key.is_zero ()) + { + response_l.put ("account", new_key.to_account ()); + } + else + { + ec = nano::error_common::wallet_locked; + } + } + } + response_errors (); +} + +void nano::rpc_handler::account_get () +{ + std::string key_text (request.get ("key")); + nano::uint256_union pub; + if (!pub.decode_hex (key_text)) + { + response_l.put ("account", pub.to_account ()); + } + else + { + ec = nano::error_common::bad_public_key; + } + response_errors (); +} + +void nano::rpc_handler::account_info () +{ + auto account (account_impl ()); + if (!ec) + { + const bool representative = request.get ("representative", false); + const bool weight = request.get ("weight", false); + const bool pending = request.get ("pending", false); + auto transaction (node.store.tx_begin_read ()); + nano::account_info info; + if (!node.store.account_get (transaction, account, info)) + { + response_l.put ("frontier", info.head.to_string ()); + response_l.put ("open_block", info.open_block.to_string ()); + response_l.put ("representative_block", info.rep_block.to_string ()); + std::string balance; + nano::uint128_union (info.balance).encode_dec (balance); + response_l.put ("balance", balance); + response_l.put ("modified_timestamp", std::to_string (info.modified)); + response_l.put ("block_count", std::to_string (info.block_count)); + response_l.put ("account_version", info.epoch == nano::epoch::epoch_1 ? "1" : "0"); + response_l.put ("confirmation_height", std::to_string (info.confirmation_height)); + if (representative) + { + auto block (node.store.block_get (transaction, info.rep_block)); + assert (block != nullptr); + response_l.put ("representative", block->representative ().to_account ()); + } + if (weight) + { + auto account_weight (node.ledger.weight (transaction, account)); + response_l.put ("weight", account_weight.convert_to ()); + } + if (pending) + { + auto account_pending (node.ledger.account_pending (transaction, account)); + response_l.put ("pending", account_pending.convert_to ()); + } + } + else + { + ec = nano::error_common::account_not_found; + } + } + response_errors (); +} + +void nano::rpc_handler::account_key () +{ + auto account (account_impl ()); + if (!ec) + { + response_l.put ("key", account.to_string ()); + } + response_errors (); +} + +void nano::rpc_handler::account_list () +{ + auto wallet (wallet_impl ()); + if (!ec) + { + boost::property_tree::ptree accounts; + auto transaction (node.wallets.tx_begin_read ()); + for (auto i (wallet->store.begin (transaction)), j (wallet->store.end ()); i != j; ++i) + { + boost::property_tree::ptree entry; + entry.put ("", nano::account (i->first).to_account ()); + accounts.push_back (std::make_pair ("", entry)); + } + response_l.add_child ("accounts", accounts); + } + response_errors (); +} + +void nano::rpc_handler::account_move () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + if (!ec) + { + std::string source_text (request.get ("source")); + auto accounts_text (request.get_child ("accounts")); + nano::uint256_union source; + if (!source.decode_hex (source_text)) + { + auto existing (node.wallets.items.find (source)); + if (existing != node.wallets.items.end ()) + { + auto source (existing->second); + std::vector accounts; + for (auto i (accounts_text.begin ()), n (accounts_text.end ()); i != n; ++i) + { + nano::public_key account; + account.decode_account (i->second.get ("")); + accounts.push_back (account); + } + auto transaction (node.wallets.tx_begin_write ()); + auto error (wallet->store.move (transaction, source->store, accounts)); + response_l.put ("moved", error ? "0" : "1"); + } + else + { + ec = nano::error_rpc::source_not_found; + } + } + else + { + ec = nano::error_rpc::bad_source; + } + } + response_errors (); +} + +void nano::rpc_handler::account_remove () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + auto account (account_impl ()); + if (!ec) + { + auto transaction (node.wallets.tx_begin_write ()); + wallet_locked_impl (transaction, wallet); + wallet_account_impl (transaction, wallet, account); + if (!ec) + { + wallet->store.erase (transaction, account); + response_l.put ("removed", "1"); + } + } + response_errors (); +} + +void nano::rpc_handler::account_representative () +{ + auto account (account_impl ()); + if (!ec) + { + auto transaction (node.store.tx_begin_read ()); + nano::account_info info; + if (!node.store.account_get (transaction, account, info)) + { + auto block (node.store.block_get (transaction, info.rep_block)); + assert (block != nullptr); + response_l.put ("representative", block->representative ().to_account ()); + } + else + { + ec = nano::error_common::account_not_found; + } + } + response_errors (); +} + +void nano::rpc_handler::account_representative_set () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + auto account (account_impl ()); + if (!ec) + { + std::string representative_text (request.get ("representative")); + nano::account representative; + if (!representative.decode_account (representative_text)) + { + auto work (work_optional_impl ()); + if (!ec && work) + { + auto transaction (node.wallets.tx_begin_write ()); + wallet_locked_impl (transaction, wallet); + wallet_account_impl (transaction, wallet, account); + if (!ec) + { + nano::account_info info; + auto block_transaction (node.store.tx_begin_read ()); + if (!node.store.account_get (block_transaction, account, info)) + { + if (nano::work_validate (info.head, work)) + { + ec = nano::error_common::invalid_work; + } + } + else + { + ec = nano::error_common::account_not_found; + } + } + } + if (!ec) + { + bool generate_work (work == 0); // Disable work generation if "work" option is provided + auto response_a (response); + // clang-format off + wallet->change_async (account, representative, [response_a](std::shared_ptr block) { + if (block != nullptr) + { + boost::property_tree::ptree response_l; + response_l.put ("block", block->hash ().to_string ()); + response_a (response_l); + } + else + { + error_response (response_a, "Error generating block"); + } + }, + work, generate_work); + // clang-format on + } + } + else + { + ec = nano::error_rpc::bad_representative_number; + } + } + // Because of change_async + if (ec) + { + response_errors (); + } +} + +void nano::rpc_handler::account_weight () +{ + auto account (account_impl ()); + if (!ec) + { + auto balance (node.weight (account)); + response_l.put ("weight", balance.convert_to ()); + } + response_errors (); +} + +void nano::rpc_handler::accounts_balances () +{ + boost::property_tree::ptree balances; + for (auto & accounts : request.get_child ("accounts")) + { + auto account (account_impl (accounts.second.data ())); + if (!ec) + { + boost::property_tree::ptree entry; + auto balance (node.balance_pending (account)); + entry.put ("balance", balance.first.convert_to ()); + entry.put ("pending", balance.second.convert_to ()); + balances.push_back (std::make_pair (account.to_account (), entry)); + } + } + response_l.add_child ("balances", balances); + response_errors (); +} + +void nano::rpc_handler::accounts_create () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + auto count (count_impl ()); + if (!ec) + { + const bool generate_work = request.get ("work", false); + boost::property_tree::ptree accounts; + for (auto i (0); accounts.size () < count; ++i) + { + nano::account new_key (wallet->deterministic_insert (generate_work)); + if (!new_key.is_zero ()) + { + boost::property_tree::ptree entry; + entry.put ("", new_key.to_account ()); + accounts.push_back (std::make_pair ("", entry)); + } + } + response_l.add_child ("accounts", accounts); + } + response_errors (); +} + +void nano::rpc_handler::accounts_frontiers () +{ + boost::property_tree::ptree frontiers; + auto transaction (node.store.tx_begin_read ()); + for (auto & accounts : request.get_child ("accounts")) + { + auto account (account_impl (accounts.second.data ())); + if (!ec) + { + auto latest (node.ledger.latest (transaction, account)); + if (!latest.is_zero ()) + { + frontiers.put (account.to_account (), latest.to_string ()); + } + } + } + response_l.add_child ("frontiers", frontiers); + response_errors (); +} + +void nano::rpc_handler::accounts_pending () +{ + auto count (count_optional_impl ()); + auto threshold (threshold_optional_impl ()); + const bool source = request.get ("source", false); + const bool include_active = request.get ("include_active", false); + const bool sorting = request.get ("sorting", false); + auto simple (threshold.is_zero () && !source && !sorting); // if simple, response is a list of hashes for each account + boost::property_tree::ptree pending; + auto transaction (node.store.tx_begin_read ()); + for (auto & accounts : request.get_child ("accounts")) + { + auto account (account_impl (accounts.second.data ())); + if (!ec) + { + boost::property_tree::ptree peers_l; + for (auto i (node.store.pending_begin (transaction, nano::pending_key (account, 0))); nano::pending_key (i->first).account == account && peers_l.size () < count; ++i) + { + nano::pending_key key (i->first); + if (include_active || node.ledger.block_confirmed (transaction, key.hash)) + { + if (simple) + { + boost::property_tree::ptree entry; + entry.put ("", key.hash.to_string ()); + peers_l.push_back (std::make_pair ("", entry)); + } + else + { + nano::pending_info info (i->second); + if (info.amount.number () >= threshold.number ()) + { + if (source) + { + boost::property_tree::ptree pending_tree; + pending_tree.put ("amount", info.amount.number ().convert_to ()); + pending_tree.put ("source", info.source.to_account ()); + peers_l.add_child (key.hash.to_string (), pending_tree); + } + else + { + peers_l.put (key.hash.to_string (), info.amount.number ().convert_to ()); + } + } + } + } + } + if (sorting && !simple) + { + if (source) + { + peers_l.sort ([](const auto & child1, const auto & child2) -> bool { + return child1.second.template get ("amount") > child2.second.template get ("amount"); + }); + } + else + { + peers_l.sort ([](const auto & child1, const auto & child2) -> bool { + return child1.second.template get ("") > child2.second.template get (""); + }); + } + } + pending.add_child (account.to_account (), peers_l); + } + } + response_l.add_child ("blocks", pending); + response_errors (); +} + +void nano::rpc_handler::available_supply () +{ + auto genesis_balance (node.balance (node.network_params.ledger.genesis_account)); // Cold storage genesis + auto landing_balance (node.balance (nano::account ("059F68AAB29DE0D3A27443625C7EA9CDDB6517A8B76FE37727EF6A4D76832AD5"))); // Active unavailable account + auto faucet_balance (node.balance (nano::account ("8E319CE6F3025E5B2DF66DA7AB1467FE48F1679C13DD43BFDB29FA2E9FC40D3B"))); // Faucet account + auto burned_balance ((node.balance_pending (nano::account (0))).second); // Burning 0 account + auto available (node.network_params.ledger.genesis_amount - genesis_balance - landing_balance - faucet_balance - burned_balance); + response_l.put ("available", available.convert_to ()); + response_errors (); +} + +void state_subtype (nano::transaction const & transaction_a, nano::node & node_a, std::shared_ptr block_a, nano::uint128_t const & balance_a, boost::property_tree::ptree & tree_a) +{ + // Subtype check + auto previous_balance (node_a.ledger.balance (transaction_a, block_a->previous ())); + if (balance_a < previous_balance) + { + tree_a.put ("subtype", "send"); + } + else + { + if (block_a->link ().is_zero ()) + { + tree_a.put ("subtype", "change"); + } + else if (balance_a == previous_balance && !node_a.ledger.epoch_link.is_zero () && node_a.ledger.is_epoch_link (block_a->link ())) + { + tree_a.put ("subtype", "epoch"); + } + else + { + tree_a.put ("subtype", "receive"); + } + } +} + +void nano::rpc_handler::block_info () +{ + auto hash (hash_impl ()); + if (!ec) + { + nano::block_sideband sideband; + auto transaction (node.store.tx_begin_read ()); + auto block (node.store.block_get (transaction, hash, &sideband)); + if (block != nullptr) + { + nano::account account (block->account ().is_zero () ? sideband.account : block->account ()); + response_l.put ("block_account", account.to_account ()); + auto amount (node.ledger.amount (transaction, hash)); + response_l.put ("amount", amount.convert_to ()); + auto balance (node.ledger.balance (transaction, hash)); + response_l.put ("balance", balance.convert_to ()); + response_l.put ("height", std::to_string (sideband.height)); + response_l.put ("local_timestamp", std::to_string (sideband.timestamp)); + auto confirmed (node.ledger.block_confirmed (transaction, hash)); + response_l.put ("confirmed", confirmed); + + bool json_block_l = request.get ("json_block", false); + if (json_block_l) + { + boost::property_tree::ptree block_node_l; + block->serialize_json (block_node_l); + response_l.add_child ("contents", block_node_l); + } + else + { + std::string contents; + block->serialize_json (contents); + response_l.put ("contents", contents); + } + if (block->type () == nano::block_type::state) + { + state_subtype (transaction, node, block, balance, response_l); + } + } + else + { + ec = nano::error_blocks::not_found; + } + } + response_errors (); +} + +void nano::rpc_handler::block_confirm () +{ + auto hash (hash_impl ()); + if (!ec) + { + auto transaction (node.store.tx_begin_read ()); + auto block_l (node.store.block_get (transaction, hash)); + if (block_l != nullptr) + { + node.block_confirm (std::move (block_l)); + response_l.put ("started", "1"); + } + else + { + ec = nano::error_blocks::not_found; + } + } + response_errors (); +} + +void nano::rpc_handler::blocks () +{ + const bool json_block_l = request.get ("json_block", false); + std::vector hashes; + boost::property_tree::ptree blocks; + auto transaction (node.store.tx_begin_read ()); + for (boost::property_tree::ptree::value_type & hashes : request.get_child ("hashes")) + { + if (!ec) + { + std::string hash_text = hashes.second.data (); + nano::uint256_union hash; + if (!hash.decode_hex (hash_text)) + { + auto block (node.store.block_get (transaction, hash)); + if (block != nullptr) + { + if (json_block_l) + { + boost::property_tree::ptree block_node_l; + block->serialize_json (block_node_l); + blocks.add_child (hash_text, block_node_l); + } + else + { + std::string contents; + block->serialize_json (contents); + blocks.put (hash_text, contents); + } + } + else + { + ec = nano::error_blocks::not_found; + } + } + else + { + ec = nano::error_blocks::bad_hash_number; + } + } + } + response_l.add_child ("blocks", blocks); + response_errors (); +} + +void nano::rpc_handler::blocks_info () +{ + const bool pending = request.get ("pending", false); + const bool source = request.get ("source", false); + const bool json_block_l = request.get ("json_block", false); + + std::vector hashes; + boost::property_tree::ptree blocks; + auto transaction (node.store.tx_begin_read ()); + for (boost::property_tree::ptree::value_type & hashes : request.get_child ("hashes")) + { + if (!ec) + { + std::string hash_text = hashes.second.data (); + nano::uint256_union hash; + if (!hash.decode_hex (hash_text)) + { + nano::block_sideband sideband; + auto block (node.store.block_get (transaction, hash, &sideband)); + if (block != nullptr) + { + boost::property_tree::ptree entry; + nano::account account (block->account ().is_zero () ? sideband.account : block->account ()); + entry.put ("block_account", account.to_account ()); + auto amount (node.ledger.amount (transaction, hash)); + entry.put ("amount", amount.convert_to ()); + auto balance (node.ledger.balance (transaction, hash)); + entry.put ("balance", balance.convert_to ()); + entry.put ("height", std::to_string (sideband.height)); + entry.put ("local_timestamp", std::to_string (sideband.timestamp)); + auto confirmed (node.ledger.block_confirmed (transaction, hash)); + entry.put ("confirmed", confirmed); + + if (json_block_l) + { + boost::property_tree::ptree block_node_l; + block->serialize_json (block_node_l); + entry.add_child ("contents", block_node_l); + } + else + { + std::string contents; + block->serialize_json (contents); + entry.put ("contents", contents); + } + if (block->type () == nano::block_type::state) + { + state_subtype (transaction, node, block, balance, entry); + } + if (pending) + { + bool exists (false); + auto destination (node.ledger.block_destination (transaction, *block)); + if (!destination.is_zero ()) + { + exists = node.store.pending_exists (transaction, nano::pending_key (destination, hash)); + } + entry.put ("pending", exists ? "1" : "0"); + } + if (source) + { + nano::block_hash source_hash (node.ledger.block_source (transaction, *block)); + auto block_a (node.store.block_get (transaction, source_hash)); + if (block_a != nullptr) + { + auto source_account (node.ledger.account (transaction, source_hash)); + entry.put ("source_account", source_account.to_account ()); + } + else + { + entry.put ("source_account", "0"); + } + } + blocks.push_back (std::make_pair (hash_text, entry)); + } + else + { + ec = nano::error_blocks::not_found; + } + } + else + { + ec = nano::error_blocks::bad_hash_number; + } + } + } + response_l.add_child ("blocks", blocks); + response_errors (); +} + +void nano::rpc_handler::block_account () +{ + auto hash (hash_impl ()); + if (!ec) + { + auto transaction (node.store.tx_begin_read ()); + if (node.store.block_exists (transaction, hash)) + { + auto account (node.ledger.account (transaction, hash)); + response_l.put ("account", account.to_account ()); + } + else + { + ec = nano::error_blocks::not_found; + } + } + response_errors (); +} + +void nano::rpc_handler::block_count () +{ + auto transaction (node.store.tx_begin_read ()); + response_l.put ("count", std::to_string (node.store.block_count (transaction).sum ())); + response_l.put ("unchecked", std::to_string (node.store.unchecked_count (transaction))); + response_errors (); +} + +void nano::rpc_handler::block_count_type () +{ + auto transaction (node.store.tx_begin_read ()); + nano::block_counts count (node.store.block_count (transaction)); + response_l.put ("send", std::to_string (count.send)); + response_l.put ("receive", std::to_string (count.receive)); + response_l.put ("open", std::to_string (count.open)); + response_l.put ("change", std::to_string (count.change)); + response_l.put ("state_v0", std::to_string (count.state_v0)); + response_l.put ("state_v1", std::to_string (count.state_v1)); + response_l.put ("state", std::to_string (count.state_v0 + count.state_v1)); + response_errors (); +} + +void nano::rpc_handler::block_create () +{ + rpc_control_impl (); + if (!ec) + { + std::string type (request.get ("type")); + nano::uint256_union wallet (0); + boost::optional wallet_text (request.get_optional ("wallet")); + if (wallet_text.is_initialized ()) + { + if (wallet.decode_hex (wallet_text.get ())) + { + ec = nano::error_common::bad_wallet_number; + } + } + nano::uint256_union account (0); + boost::optional account_text (request.get_optional ("account")); + if (!ec && account_text.is_initialized ()) + { + if (account.decode_account (account_text.get ())) + { + ec = nano::error_common::bad_account_number; + } + } + nano::uint256_union representative (0); + boost::optional representative_text (request.get_optional ("representative")); + if (!ec && representative_text.is_initialized ()) + { + if (representative.decode_account (representative_text.get ())) + { + ec = nano::error_rpc::bad_representative_number; + } + } + nano::uint256_union destination (0); + boost::optional destination_text (request.get_optional ("destination")); + if (!ec && destination_text.is_initialized ()) + { + if (destination.decode_account (destination_text.get ())) + { + ec = nano::error_rpc::bad_destination; + } + } + nano::block_hash source (0); + boost::optional source_text (request.get_optional ("source")); + if (!ec && source_text.is_initialized ()) + { + if (source.decode_hex (source_text.get ())) + { + ec = nano::error_rpc::bad_source; + } + } + nano::uint128_union amount (0); + boost::optional amount_text (request.get_optional ("amount")); + if (!ec && amount_text.is_initialized ()) + { + if (amount.decode_dec (amount_text.get ())) + { + ec = nano::error_common::invalid_amount; + } + } + auto work (work_optional_impl ()); + nano::raw_key prv; + prv.data.clear (); + nano::uint256_union previous (0); + nano::uint128_union balance (0); + if (!ec && wallet != 0 && account != 0) + { + auto existing (node.wallets.items.find (wallet)); + if (existing != node.wallets.items.end ()) + { + auto transaction (node.wallets.tx_begin_read ()); + auto block_transaction (node.store.tx_begin_read ()); + wallet_locked_impl (transaction, existing->second); + wallet_account_impl (transaction, existing->second, account); + if (!ec) + { + existing->second->store.fetch (transaction, account, prv); + previous = node.ledger.latest (block_transaction, account); + balance = node.ledger.account_balance (block_transaction, account); + } + } + else + { + ec = nano::error_common::wallet_not_found; + } + } + boost::optional key_text (request.get_optional ("key")); + if (!ec && key_text.is_initialized ()) + { + if (prv.data.decode_hex (key_text.get ())) + { + ec = nano::error_common::bad_private_key; + } + } + boost::optional previous_text (request.get_optional ("previous")); + if (!ec && previous_text.is_initialized ()) + { + if (previous.decode_hex (previous_text.get ())) + { + ec = nano::error_rpc::bad_previous; + } + } + boost::optional balance_text (request.get_optional ("balance")); + if (!ec && balance_text.is_initialized ()) + { + if (balance.decode_dec (balance_text.get ())) + { + ec = nano::error_rpc::invalid_balance; + } + } + nano::uint256_union link (0); + boost::optional link_text (request.get_optional ("link")); + if (!ec && link_text.is_initialized ()) + { + if (link.decode_account (link_text.get ())) + { + if (link.decode_hex (link_text.get ())) + { + ec = nano::error_rpc::bad_link; + } + } + } + else + { + // Retrieve link from source or destination + link = source.is_zero () ? destination : source; + } + if (prv.data != 0) + { + nano::uint256_union pub (nano::pub_key (prv.data)); + // Fetching account balance & previous for send blocks (if aren't given directly) + if (!previous_text.is_initialized () && !balance_text.is_initialized ()) + { + auto transaction (node.store.tx_begin_read ()); + previous = node.ledger.latest (transaction, pub); + balance = node.ledger.account_balance (transaction, pub); + } + // Double check current balance if previous block is specified + else if (previous_text.is_initialized () && balance_text.is_initialized () && type == "send") + { + auto transaction (node.store.tx_begin_read ()); + if (node.store.block_exists (transaction, previous) && node.store.block_balance (transaction, previous) != balance.number ()) + { + ec = nano::error_rpc::block_create_balance_mismatch; + } + } + // Check for incorrect account key + if (!ec && account_text.is_initialized ()) + { + if (account != pub) + { + ec = nano::error_rpc::block_create_public_key_mismatch; + } + } + if (type == "state") + { + if (previous_text.is_initialized () && !representative.is_zero () && (!link.is_zero () || link_text.is_initialized ())) + { + if (work == 0) + { + work = node.work_generate_blocking (previous.is_zero () ? pub : previous); + } + nano::state_block state (pub, previous, representative, balance, link, prv, pub, work); + response_l.put ("hash", state.hash ().to_string ()); + bool json_block_l = request.get ("json_block", false); + if (json_block_l) + { + boost::property_tree::ptree block_node_l; + state.serialize_json (block_node_l); + response_l.add_child ("block", block_node_l); + } + else + { + std::string contents; + state.serialize_json (contents); + response_l.put ("block", contents); + } + } + else + { + ec = nano::error_rpc::block_create_requirements_state; + } + } + else if (type == "open") + { + if (representative != 0 && source != 0) + { + if (work == 0) + { + work = node.work_generate_blocking (pub); + } + nano::open_block open (source, representative, pub, prv, pub, work); + response_l.put ("hash", open.hash ().to_string ()); + std::string contents; + open.serialize_json (contents); + response_l.put ("block", contents); + } + else + { + ec = nano::error_rpc::block_create_requirements_open; + } + } + else if (type == "receive") + { + if (source != 0 && previous != 0) + { + if (work == 0) + { + work = node.work_generate_blocking (previous); + } + nano::receive_block receive (previous, source, prv, pub, work); + response_l.put ("hash", receive.hash ().to_string ()); + std::string contents; + receive.serialize_json (contents); + response_l.put ("block", contents); + } + else + { + ec = nano::error_rpc::block_create_requirements_receive; + } + } + else if (type == "change") + { + if (representative != 0 && previous != 0) + { + if (work == 0) + { + work = node.work_generate_blocking (previous); + } + nano::change_block change (previous, representative, prv, pub, work); + response_l.put ("hash", change.hash ().to_string ()); + std::string contents; + change.serialize_json (contents); + response_l.put ("block", contents); + } + else + { + ec = nano::error_rpc::block_create_requirements_change; + } + } + else if (type == "send") + { + if (destination != 0 && previous != 0 && balance != 0 && amount != 0) + { + if (balance.number () >= amount.number ()) + { + if (work == 0) + { + work = node.work_generate_blocking (previous); + } + nano::send_block send (previous, destination, balance.number () - amount.number (), prv, pub, work); + response_l.put ("hash", send.hash ().to_string ()); + std::string contents; + send.serialize_json (contents); + response_l.put ("block", contents); + } + else + { + ec = nano::error_common::insufficient_balance; + } + } + else + { + ec = nano::error_rpc::block_create_requirements_send; + } + } + else + { + ec = nano::error_blocks::invalid_type; + } + } + else + { + ec = nano::error_rpc::block_create_key_required; + } + } + response_errors (); +} + +void nano::rpc_handler::block_hash () +{ + const bool json_block_l = request.get ("json_block", false); + std::shared_ptr block; + if (json_block_l) + { + block = block_json_impl (true); + } + else + { + block = block_impl (true); + } + + if (!ec) + { + response_l.put ("hash", block->hash ().to_string ()); + } + response_errors (); +} + +void nano::rpc_handler::bootstrap () +{ + std::string address_text = request.get ("address"); + std::string port_text = request.get ("port"); + boost::system::error_code address_ec; + auto address (boost::asio::ip::address_v6::from_string (address_text, address_ec)); + if (!address_ec) + { + uint16_t port; + if (!nano::parse_port (port_text, port)) + { + node.bootstrap_initiator.bootstrap (nano::endpoint (address, port)); + response_l.put ("success", ""); + } + else + { + ec = nano::error_common::invalid_port; + } + } + else + { + ec = nano::error_common::invalid_ip_address; + } + response_errors (); +} + +void nano::rpc_handler::bootstrap_any () +{ + node.bootstrap_initiator.bootstrap (); + response_l.put ("success", ""); + response_errors (); +} + +void nano::rpc_handler::bootstrap_lazy () +{ + rpc_control_impl (); + auto hash (hash_impl ()); + const bool force = request.get ("force", false); + if (!ec) + { + node.bootstrap_initiator.bootstrap_lazy (hash, force); + response_l.put ("started", "1"); + } + response_errors (); +} + +/* + * @warning This is an internal/diagnostic RPC, do not rely on its interface being stable + */ +void nano::rpc_handler::bootstrap_status () +{ + auto attempt (node.bootstrap_initiator.current_attempt ()); + if (attempt != nullptr) + { + response_l.put ("clients", std::to_string (attempt->clients.size ())); + response_l.put ("pulls", std::to_string (attempt->pulls.size ())); + response_l.put ("pulling", std::to_string (attempt->pulling)); + response_l.put ("connections", std::to_string (attempt->connections)); + response_l.put ("idle", std::to_string (attempt->idle.size ())); + response_l.put ("target_connections", std::to_string (attempt->target_connections (attempt->pulls.size ()))); + response_l.put ("total_blocks", std::to_string (attempt->total_blocks)); + response_l.put ("runs_count", std::to_string (attempt->runs_count)); + std::string mode_text; + if (attempt->mode == nano::bootstrap_mode::legacy) + { + mode_text = "legacy"; + } + else if (attempt->mode == nano::bootstrap_mode::lazy) + { + mode_text = "lazy"; + } + else if (attempt->mode == nano::bootstrap_mode::wallet_lazy) + { + mode_text = "wallet_lazy"; + } + response_l.put ("mode", mode_text); + response_l.put ("lazy_blocks", std::to_string (attempt->lazy_blocks.size ())); + response_l.put ("lazy_state_unknown", std::to_string (attempt->lazy_state_unknown.size ())); + response_l.put ("lazy_balances", std::to_string (attempt->lazy_balances.size ())); + response_l.put ("lazy_pulls", std::to_string (attempt->lazy_pulls.size ())); + response_l.put ("lazy_stopped", std::to_string (attempt->lazy_stopped)); + response_l.put ("lazy_keys", std::to_string (attempt->lazy_keys.size ())); + if (!attempt->lazy_keys.empty ()) + { + response_l.put ("lazy_key_1", (*(attempt->lazy_keys.begin ())).to_string ()); + } + } + else + { + response_l.put ("active", "0"); + } + response_errors (); +} + +void nano::rpc_handler::chain (bool successors) +{ + successors = successors != request.get ("reverse", false); + auto hash (hash_impl ("block")); + auto count (count_impl ()); + auto offset (offset_optional_impl (0)); + if (!ec) + { + boost::property_tree::ptree blocks; + auto transaction (node.store.tx_begin_read ()); + while (!hash.is_zero () && blocks.size () < count) + { + auto block_l (node.store.block_get (transaction, hash)); + if (block_l != nullptr) + { + if (offset > 0) + { + --offset; + } + else + { + boost::property_tree::ptree entry; + entry.put ("", hash.to_string ()); + blocks.push_back (std::make_pair ("", entry)); + } + hash = successors ? node.store.block_successor (transaction, hash) : block_l->previous (); + } + else + { + hash.clear (); + } + } + response_l.add_child ("blocks", blocks); + } + response_errors (); +} + +void nano::rpc_handler::confirmation_active () +{ + uint64_t announcements (0); + boost::optional announcements_text (request.get_optional ("announcements")); + if (announcements_text.is_initialized ()) + { + announcements = strtoul (announcements_text.get ().c_str (), NULL, 10); + } + boost::property_tree::ptree elections; + { + std::lock_guard lock (node.active.mutex); + for (auto i (node.active.roots.begin ()), n (node.active.roots.end ()); i != n; ++i) + { + if (i->election->announcements >= announcements && !i->election->confirmed && !i->election->stopped) + { + boost::property_tree::ptree entry; + entry.put ("", i->root.to_string ()); + elections.push_back (std::make_pair ("", entry)); + } + } + } + response_l.add_child ("confirmations", elections); + response_errors (); +} + +void nano::rpc_handler::confirmation_history () +{ + boost::property_tree::ptree elections; + boost::property_tree::ptree confirmation_stats; + std::chrono::milliseconds running_total (0); + nano::block_hash hash (0); + boost::optional hash_text (request.get_optional ("hash")); + if (hash_text.is_initialized ()) + { + hash = hash_impl (); + } + if (!ec) + { + auto confirmed (node.active.list_confirmed ()); + for (auto i (confirmed.begin ()), n (confirmed.end ()); i != n; ++i) + { + if (hash.is_zero () || i->winner->hash () == hash) + { + boost::property_tree::ptree election; + election.put ("hash", i->winner->hash ().to_string ()); + election.put ("duration", i->election_duration.count ()); + election.put ("time", i->election_end.count ()); + election.put ("tally", i->tally.to_string_dec ()); + elections.push_back (std::make_pair ("", election)); + } + running_total += i->election_duration; + } + } + confirmation_stats.put ("count", elections.size ()); + if (elections.size () >= 1) + { + confirmation_stats.put ("average", (running_total.count ()) / elections.size ()); + } + response_l.add_child ("confirmation_stats", confirmation_stats); + response_l.add_child ("confirmations", elections); + response_errors (); +} + +void nano::rpc_handler::confirmation_info () +{ + const bool representatives = request.get ("representatives", false); + const bool contents = request.get ("contents", true); + const bool json_block_l = request.get ("json_block", false); + std::string root_text (request.get ("root")); + nano::qualified_root root; + if (!root.decode_hex (root_text)) + { + std::lock_guard lock (node.active.mutex); + auto conflict_info (node.active.roots.find (root)); + if (conflict_info != node.active.roots.end ()) + { + response_l.put ("announcements", std::to_string (conflict_info->election->announcements)); + auto election (conflict_info->election); + nano::uint128_t total (0); + response_l.put ("last_winner", election->status.winner->hash ().to_string ()); + auto transaction (node.store.tx_begin_read ()); + auto tally_l (election->tally (transaction)); + boost::property_tree::ptree blocks; + for (auto i (tally_l.begin ()), n (tally_l.end ()); i != n; ++i) + { + boost::property_tree::ptree entry; + auto tally (i->first); + entry.put ("tally", tally.convert_to ()); + total += tally; + if (contents) + { + if (json_block_l) + { + boost::property_tree::ptree block_node_l; + i->second->serialize_json (block_node_l); + entry.add_child ("contents", block_node_l); + } + else + { + std::string contents; + i->second->serialize_json (contents); + entry.put ("contents", contents); + } + } + if (representatives) + { + std::multimap> representatives; + for (auto ii (election->last_votes.begin ()), nn (election->last_votes.end ()); ii != nn; ++ii) + { + if (i->second->hash () == ii->second.hash) + { + nano::account representative (ii->first); + auto amount (node.store.representation_get (transaction, representative)); + representatives.insert (std::make_pair (amount, representative)); + } + } + boost::property_tree::ptree representatives_list; + for (auto ii (representatives.begin ()), nn (representatives.end ()); ii != nn; ++ii) + { + representatives_list.put (ii->second.to_account (), ii->first.convert_to ()); + } + entry.add_child ("representatives", representatives_list); + } + blocks.add_child ((i->second->hash ()).to_string (), entry); + } + response_l.put ("total_tally", total.convert_to ()); + response_l.add_child ("blocks", blocks); + } + else + { + ec = nano::error_rpc::confirmation_not_found; + } + } + else + { + ec = nano::error_rpc::invalid_root; + } + response_errors (); +} + +void nano::rpc_handler::confirmation_quorum () +{ + response_l.put ("quorum_delta", node.delta ().convert_to ()); + response_l.put ("online_weight_quorum_percent", std::to_string (node.config.online_weight_quorum)); + response_l.put ("online_weight_minimum", node.config.online_weight_minimum.to_string_dec ()); + response_l.put ("online_stake_total", node.online_reps.online_stake ().convert_to ()); + response_l.put ("peers_stake_total", node.rep_crawler.total_weight ().convert_to ()); + response_l.put ("peers_stake_required", std::max (node.config.online_weight_minimum.number (), node.delta ()).convert_to ()); + if (request.get ("peer_details", false)) + { + boost::property_tree::ptree peers; + for (auto & peer : node.rep_crawler.representatives_by_weight ()) + { + boost::property_tree::ptree peer_node; + peer_node.put ("account", peer.account.to_account ()); + peer_node.put ("ip", peer.channel->to_string ()); + peer_node.put ("weight", peer.weight.to_string_dec ()); + peers.push_back (std::make_pair ("", peer_node)); + } + response_l.add_child ("peers", peers); + } + response_errors (); +} + +void nano::rpc_handler::delegators () +{ + auto account (account_impl ()); + if (!ec) + { + boost::property_tree::ptree delegators; + auto transaction (node.store.tx_begin_read ()); + for (auto i (node.store.latest_begin (transaction)), n (node.store.latest_end ()); i != n; ++i) + { + nano::account_info info (i->second); + auto block (node.store.block_get (transaction, info.rep_block)); + assert (block != nullptr); + if (block->representative () == account) + { + std::string balance; + nano::uint128_union (info.balance).encode_dec (balance); + delegators.put (nano::account (i->first).to_account (), balance); + } + } + response_l.add_child ("delegators", delegators); + } + response_errors (); +} + +void nano::rpc_handler::delegators_count () +{ + auto account (account_impl ()); + if (!ec) + { + uint64_t count (0); + auto transaction (node.store.tx_begin_read ()); + for (auto i (node.store.latest_begin (transaction)), n (node.store.latest_end ()); i != n; ++i) + { + nano::account_info info (i->second); + auto block (node.store.block_get (transaction, info.rep_block)); + assert (block != nullptr); + if (block->representative () == account) + { + ++count; + } + } + response_l.put ("count", std::to_string (count)); + } + response_errors (); +} + +void nano::rpc_handler::deterministic_key () +{ + std::string seed_text (request.get ("seed")); + std::string index_text (request.get ("index")); + nano::raw_key seed; + if (!seed.data.decode_hex (seed_text)) + { + try + { + uint32_t index (std::stoul (index_text)); + nano::uint256_union prv; + nano::deterministic_key (seed.data, index, prv); + nano::uint256_union pub (nano::pub_key (prv)); + response_l.put ("private", prv.to_string ()); + response_l.put ("public", pub.to_string ()); + response_l.put ("account", pub.to_account ()); + } + catch (std::logic_error const &) + { + ec = nano::error_common::invalid_index; + } + } + else + { + ec = nano::error_common::bad_seed; + } + response_errors (); +} + +void nano::rpc_handler::frontiers () +{ + auto start (account_impl ()); + auto count (count_impl ()); + if (!ec) + { + boost::property_tree::ptree frontiers; + auto transaction (node.store.tx_begin_read ()); + for (auto i (node.store.latest_begin (transaction, start)), n (node.store.latest_end ()); i != n && frontiers.size () < count; ++i) + { + frontiers.put (nano::account (i->first).to_account (), nano::account_info (i->second).head.to_string ()); + } + response_l.add_child ("frontiers", frontiers); + } + response_errors (); +} + +void nano::rpc_handler::account_count () +{ + auto transaction (node.store.tx_begin_read ()); + auto size (node.store.account_count (transaction)); + response_l.put ("count", std::to_string (size)); + response_errors (); +} + +namespace +{ +class history_visitor : public nano::block_visitor +{ +public: + history_visitor (nano::rpc_handler & handler_a, bool raw_a, nano::transaction & transaction_a, boost::property_tree::ptree & tree_a, nano::block_hash const & hash_a, std::vector const & accounts_filter_a = {}) : + handler (handler_a), + raw (raw_a), + transaction (transaction_a), + tree (tree_a), + hash (hash_a), + accounts_filter (accounts_filter_a) + { + } + virtual ~history_visitor () = default; + void send_block (nano::send_block const & block_a) + { + if (should_ignore_account (block_a.hashables.destination)) + { + return; + } + tree.put ("type", "send"); + auto account (block_a.hashables.destination.to_account ()); + tree.put ("account", account); + auto amount (handler.node.ledger.amount (transaction, hash).convert_to ()); + tree.put ("amount", amount); + if (raw) + { + tree.put ("destination", account); + tree.put ("balance", block_a.hashables.balance.to_string_dec ()); + tree.put ("previous", block_a.hashables.previous.to_string ()); + } + } + void receive_block (nano::receive_block const & block_a) + { + if (should_ignore_account (block_a.hashables.source)) + { + return; + } + tree.put ("type", "receive"); + auto account (handler.node.ledger.account (transaction, block_a.hashables.source).to_account ()); + tree.put ("account", account); + auto amount (handler.node.ledger.amount (transaction, hash).convert_to ()); + tree.put ("amount", amount); + if (raw) + { + tree.put ("source", block_a.hashables.source.to_string ()); + tree.put ("previous", block_a.hashables.previous.to_string ()); + } + } + void open_block (nano::open_block const & block_a) + { + if (should_ignore_account (block_a.hashables.source)) + { + return; + } + if (raw) + { + tree.put ("type", "open"); + tree.put ("representative", block_a.hashables.representative.to_account ()); + tree.put ("source", block_a.hashables.source.to_string ()); + tree.put ("opened", block_a.hashables.account.to_account ()); + } + else + { + // Report opens as a receive + tree.put ("type", "receive"); + } + if (block_a.hashables.source != network_params.ledger.genesis_account) + { + tree.put ("account", handler.node.ledger.account (transaction, block_a.hashables.source).to_account ()); + tree.put ("amount", handler.node.ledger.amount (transaction, hash).convert_to ()); + } + else + { + tree.put ("account", network_params.ledger.genesis_account.to_account ()); + tree.put ("amount", network_params.ledger.genesis_amount.convert_to ()); + } + } + void change_block (nano::change_block const & block_a) + { + if (raw && accounts_filter.empty ()) + { + tree.put ("type", "change"); + tree.put ("representative", block_a.hashables.representative.to_account ()); + tree.put ("previous", block_a.hashables.previous.to_string ()); + } + } + void state_block (nano::state_block const & block_a) + { + if (raw) + { + tree.put ("type", "state"); + tree.put ("representative", block_a.hashables.representative.to_account ()); + tree.put ("link", block_a.hashables.link.to_string ()); + tree.put ("balance", block_a.hashables.balance.to_string_dec ()); + tree.put ("previous", block_a.hashables.previous.to_string ()); + } + auto balance (block_a.hashables.balance.number ()); + auto previous_balance (handler.node.ledger.balance (transaction, block_a.hashables.previous)); + if (balance < previous_balance) + { + if (should_ignore_account (block_a.hashables.link)) + { + tree.clear (); + return; + } + if (raw) + { + tree.put ("subtype", "send"); + } + else + { + tree.put ("type", "send"); + } + tree.put ("account", block_a.hashables.link.to_account ()); + tree.put ("amount", (previous_balance - balance).convert_to ()); + } + else + { + if (block_a.hashables.link.is_zero ()) + { + if (raw && accounts_filter.empty ()) + { + tree.put ("subtype", "change"); + } + } + else if (balance == previous_balance && !handler.node.ledger.epoch_link.is_zero () && handler.node.ledger.is_epoch_link (block_a.hashables.link)) + { + if (raw && accounts_filter.empty ()) + { + tree.put ("subtype", "epoch"); + tree.put ("account", handler.node.ledger.epoch_signer.to_account ()); + } + } + else + { + if (should_ignore_account (block_a.hashables.link)) + { + tree.clear (); + return; + } + if (raw) + { + tree.put ("subtype", "receive"); + } + else + { + tree.put ("type", "receive"); + } + tree.put ("account", handler.node.ledger.account (transaction, block_a.hashables.link).to_account ()); + tree.put ("amount", (balance - previous_balance).convert_to ()); + } + } + } + bool should_ignore_account (nano::public_key const & account) + { + bool ignore (false); + if (!accounts_filter.empty ()) + { + if (std::find (accounts_filter.begin (), accounts_filter.end (), account) == accounts_filter.end ()) + { + ignore = true; + } + } + return ignore; + } + nano::rpc_handler & handler; + bool raw; + nano::transaction & transaction; + boost::property_tree::ptree & tree; + nano::block_hash const & hash; + nano::network_params network_params; + std::vector const & accounts_filter; +}; +} + +void nano::rpc_handler::account_history () +{ + std::vector accounts_to_filter; + const auto accounts_filter_node = request.get_child_optional ("account_filter"); + if (accounts_filter_node.is_initialized ()) + { + for (auto & a : (*accounts_filter_node)) + { + nano::public_key account; + auto error (account.decode_account (a.second.get (""))); + if (!error) + { + accounts_to_filter.push_back (account); + } + else + { + ec = nano::error_common::bad_account_number; + break; + } + } + } + nano::account account; + nano::block_hash hash; + bool output_raw (request.get_optional ("raw") == true); + bool reverse (request.get_optional ("reverse") == true); + auto head_str (request.get_optional ("head")); + auto transaction (node.store.tx_begin_read ()); + auto count (count_impl ()); + auto offset (offset_optional_impl (0)); + if (head_str) + { + if (!hash.decode_hex (*head_str)) + { + if (node.store.block_exists (transaction, hash)) + { + account = node.ledger.account (transaction, hash); + } + else + { + ec = nano::error_blocks::not_found; + } + } + else + { + ec = nano::error_blocks::bad_hash_number; + } + } + else + { + account = account_impl (); + if (!ec) + { + if (reverse) + { + nano::account_info info; + if (!node.store.account_get (transaction, account, info)) + { + hash = info.open_block; + } + else + { + ec = nano::error_common::account_not_found; + } + } + else + { + hash = node.ledger.latest (transaction, account); + } + } + } + if (!ec) + { + boost::property_tree::ptree history; + response_l.put ("account", account.to_account ()); + nano::block_sideband sideband; + auto block (node.store.block_get (transaction, hash, &sideband)); + while (block != nullptr && count > 0) + { + if (offset > 0) + { + --offset; + } + else + { + boost::property_tree::ptree entry; + history_visitor visitor (*this, output_raw, transaction, entry, hash, accounts_to_filter); + block->visit (visitor); + if (!entry.empty ()) + { + entry.put ("local_timestamp", std::to_string (sideband.timestamp)); + entry.put ("height", std::to_string (sideband.height)); + entry.put ("hash", hash.to_string ()); + if (output_raw) + { + entry.put ("work", nano::to_string_hex (block->block_work ())); + entry.put ("signature", block->block_signature ().to_string ()); + } + history.push_back (std::make_pair ("", entry)); + --count; + } + } + hash = reverse ? node.store.block_successor (transaction, hash) : block->previous (); + block = node.store.block_get (transaction, hash, &sideband); + } + response_l.add_child ("history", history); + if (!hash.is_zero ()) + { + response_l.put (reverse ? "next" : "previous", hash.to_string ()); + } + } + response_errors (); +} + +void nano::rpc_handler::keepalive () +{ + rpc_control_impl (); + if (!ec) + { + std::string address_text (request.get ("address")); + std::string port_text (request.get ("port")); + uint16_t port; + if (!nano::parse_port (port_text, port)) + { + node.keepalive (address_text, port); + response_l.put ("started", "1"); + } + else + { + ec = nano::error_common::invalid_port; + } + } + response_errors (); +} + +void nano::rpc_handler::key_create () +{ + nano::keypair pair; + response_l.put ("private", pair.prv.data.to_string ()); + response_l.put ("public", pair.pub.to_string ()); + response_l.put ("account", pair.pub.to_account ()); + response_errors (); +} + +void nano::rpc_handler::key_expand () +{ + std::string key_text (request.get ("key")); + nano::uint256_union prv; + if (!prv.decode_hex (key_text)) + { + nano::uint256_union pub (nano::pub_key (prv)); + response_l.put ("private", prv.to_string ()); + response_l.put ("public", pub.to_string ()); + response_l.put ("account", pub.to_account ()); + } + else + { + ec = nano::error_common::bad_private_key; + } + response_errors (); +} + +void nano::rpc_handler::ledger () +{ + rpc_control_impl (); + auto count (count_optional_impl ()); + if (!ec) + { + nano::account start (0); + 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; + } + } + uint64_t modified_since (0); + boost::optional modified_since_text (request.get_optional ("modified_since")); + if (modified_since_text.is_initialized ()) + { + if (decode_unsigned (modified_since_text.get (), modified_since)) + { + ec = nano::error_rpc::invalid_timestamp; + } + } + const bool sorting = request.get ("sorting", false); + const bool representative = request.get ("representative", false); + const bool weight = request.get ("weight", false); + const bool pending = request.get ("pending", false); + boost::property_tree::ptree accounts; + auto transaction (node.store.tx_begin_read ()); + if (!ec && !sorting) // Simple + { + for (auto i (node.store.latest_begin (transaction, start)), n (node.store.latest_end ()); i != n && accounts.size () < count; ++i) + { + nano::account_info info (i->second); + if (info.modified >= modified_since) + { + nano::account account (i->first); + boost::property_tree::ptree response_a; + response_a.put ("frontier", info.head.to_string ()); + response_a.put ("open_block", info.open_block.to_string ()); + response_a.put ("representative_block", info.rep_block.to_string ()); + std::string balance; + nano::uint128_union (info.balance).encode_dec (balance); + response_a.put ("balance", balance); + response_a.put ("modified_timestamp", std::to_string (info.modified)); + response_a.put ("block_count", std::to_string (info.block_count)); + if (representative) + { + auto block (node.store.block_get (transaction, info.rep_block)); + assert (block != nullptr); + response_a.put ("representative", block->representative ().to_account ()); + } + if (weight) + { + auto account_weight (node.ledger.weight (transaction, account)); + response_a.put ("weight", account_weight.convert_to ()); + } + if (pending) + { + auto account_pending (node.ledger.account_pending (transaction, account)); + response_a.put ("pending", account_pending.convert_to ()); + } + accounts.push_back (std::make_pair (account.to_account (), response_a)); + } + } + } + else if (!ec) // Sorting + { + std::vector> ledger_l; + for (auto i (node.store.latest_begin (transaction, start)), n (node.store.latest_end ()); i != n; ++i) + { + nano::account_info info (i->second); + nano::uint128_union balance (info.balance); + if (info.modified >= modified_since) + { + ledger_l.push_back (std::make_pair (balance, nano::account (i->first))); + } + } + std::sort (ledger_l.begin (), ledger_l.end ()); + std::reverse (ledger_l.begin (), ledger_l.end ()); + nano::account_info info; + for (auto i (ledger_l.begin ()), n (ledger_l.end ()); i != n && accounts.size () < count; ++i) + { + node.store.account_get (transaction, i->second, info); + nano::account account (i->second); + boost::property_tree::ptree response_a; + response_a.put ("frontier", info.head.to_string ()); + response_a.put ("open_block", info.open_block.to_string ()); + response_a.put ("representative_block", info.rep_block.to_string ()); + std::string balance; + (i->first).encode_dec (balance); + response_a.put ("balance", balance); + response_a.put ("modified_timestamp", std::to_string (info.modified)); + response_a.put ("block_count", std::to_string (info.block_count)); + if (representative) + { + auto block (node.store.block_get (transaction, info.rep_block)); + assert (block != nullptr); + response_a.put ("representative", block->representative ().to_account ()); + } + if (weight) + { + auto account_weight (node.ledger.weight (transaction, account)); + response_a.put ("weight", account_weight.convert_to ()); + } + if (pending) + { + auto account_pending (node.ledger.account_pending (transaction, account)); + response_a.put ("pending", account_pending.convert_to ()); + } + accounts.push_back (std::make_pair (account.to_account (), response_a)); + } + } + response_l.add_child ("accounts", accounts); + } + response_errors (); +} + +void nano::rpc_handler::mnano_from_raw (nano::uint128_t ratio) +{ + auto amount (amount_impl ()); + if (!ec) + { + auto result (amount.number () / ratio); + response_l.put ("amount", result.convert_to ()); + } + response_errors (); +} + +void nano::rpc_handler::mnano_to_raw (nano::uint128_t ratio) +{ + auto amount (amount_impl ()); + if (!ec) + { + auto result (amount.number () * ratio); + if (result > amount.number ()) + { + response_l.put ("amount", result.convert_to ()); + } + else + { + ec = nano::error_common::invalid_amount_big; + } + } + response_errors (); +} + +/* + * @warning This is an internal/diagnostic RPC, do not rely on its interface being stable + */ +void nano::rpc_handler::node_id () +{ + rpc_control_impl (); + if (!ec) + { + response_l.put ("private", node.node_id.prv.data.to_string ()); + response_l.put ("public", node.node_id.pub.to_string ()); + response_l.put ("as_account", node.node_id.pub.to_account ()); + } + response_errors (); +} + +/* + * @warning This is an internal/diagnostic RPC, do not rely on its interface being stable + */ +void nano::rpc_handler::node_id_delete () +{ + rpc_control_impl (); + if (!ec) + { + auto transaction (node.store.tx_begin_write ()); + node.store.delete_node_id (transaction); + response_l.put ("deleted", "1"); + } + response_errors (); +} + +void nano::rpc_handler::password_change () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + if (!ec) + { + auto transaction (node.wallets.tx_begin_write ()); + wallet_locked_impl (transaction, wallet); + if (!ec) + { + std::string password_text (request.get ("password")); + bool error (wallet->store.rekey (transaction, password_text)); + response_l.put ("changed", error ? "0" : "1"); + if (!error) + { + node.logger.try_log ("Wallet password changed"); + } + } + } + response_errors (); +} + +void nano::rpc_handler::password_enter () +{ + auto wallet (wallet_impl ()); + if (!ec) + { + std::string password_text (request.get ("password")); + auto transaction (wallet->wallets.tx_begin_write ()); + auto error (wallet->enter_password (transaction, password_text)); + response_l.put ("valid", error ? "0" : "1"); + } + response_errors (); +} + +void nano::rpc_handler::password_valid (bool wallet_locked) +{ + auto wallet (wallet_impl ()); + if (!ec) + { + auto transaction (node.wallets.tx_begin_read ()); + auto valid (wallet->store.valid_password (transaction)); + if (!wallet_locked) + { + response_l.put ("valid", valid ? "1" : "0"); + } + else + { + response_l.put ("locked", valid ? "0" : "1"); + } + } + response_errors (); +} + +void nano::rpc_handler::peers () +{ + boost::property_tree::ptree peers_l; + const bool peer_details = request.get ("peer_details", false); + auto peers_list (node.network.udp_channels.list (std::numeric_limits::max ())); + std::sort (peers_list.begin (), peers_list.end ()); + for (auto i (peers_list.begin ()), n (peers_list.end ()); i != n; ++i) + { + std::stringstream text; + auto channel (*i); + text << channel->to_string (); + if (peer_details) + { + boost::property_tree::ptree pending_tree; + pending_tree.put ("protocol_version", std::to_string (channel->network_version)); + if ((*i)->node_id.is_initialized ()) + { + pending_tree.put ("node_id", channel->node_id.get ().to_account ()); + } + else + { + pending_tree.put ("node_id", ""); + } + peers_l.push_back (boost::property_tree::ptree::value_type (text.str (), pending_tree)); + } + else + { + peers_l.push_back (boost::property_tree::ptree::value_type (text.str (), boost::property_tree::ptree (std::to_string (channel->network_version)))); + } + } + response_l.add_child ("peers", peers_l); + response_errors (); +} + +void nano::rpc_handler::pending () +{ + auto account (account_impl ()); + auto count (count_optional_impl ()); + auto threshold (threshold_optional_impl ()); + const bool source = request.get ("source", false); + const bool min_version = request.get ("min_version", false); + const bool include_active = request.get ("include_active", false); + const bool sorting = request.get ("sorting", false); + auto simple (threshold.is_zero () && !source && !min_version && !sorting); // if simple, response is a list of hashes + if (!ec) + { + boost::property_tree::ptree peers_l; + auto transaction (node.store.tx_begin_read ()); + for (auto i (node.store.pending_begin (transaction, nano::pending_key (account, 0))); nano::pending_key (i->first).account == account && peers_l.size () < count; ++i) + { + nano::pending_key key (i->first); + if (include_active || node.ledger.block_confirmed (transaction, key.hash)) + { + if (simple) + { + boost::property_tree::ptree entry; + entry.put ("", key.hash.to_string ()); + peers_l.push_back (std::make_pair ("", entry)); + } + else + { + nano::pending_info info (i->second); + if (info.amount.number () >= threshold.number ()) + { + if (source || min_version) + { + boost::property_tree::ptree pending_tree; + pending_tree.put ("amount", info.amount.number ().convert_to ()); + if (source) + { + pending_tree.put ("source", info.source.to_account ()); + } + if (min_version) + { + pending_tree.put ("min_version", info.epoch == nano::epoch::epoch_1 ? "1" : "0"); + } + peers_l.add_child (key.hash.to_string (), pending_tree); + } + else + { + peers_l.put (key.hash.to_string (), info.amount.number ().convert_to ()); + } + } + } + } + } + if (sorting && !simple) + { + if (source || min_version) + { + peers_l.sort ([](const auto & child1, const auto & child2) -> bool { + return child1.second.template get ("amount") > child2.second.template get ("amount"); + }); + } + else + { + peers_l.sort ([](const auto & child1, const auto & child2) -> bool { + return child1.second.template get ("") > child2.second.template get (""); + }); + } + } + response_l.add_child ("blocks", peers_l); + } + response_errors (); +} + +void nano::rpc_handler::pending_exists () +{ + auto hash (hash_impl ()); + const bool include_active = request.get ("include_active", false); + if (!ec) + { + auto transaction (node.store.tx_begin_read ()); + auto block (node.store.block_get (transaction, hash)); + if (block != nullptr) + { + auto exists (false); + auto destination (node.ledger.block_destination (transaction, *block)); + if (!destination.is_zero ()) + { + exists = node.store.pending_exists (transaction, nano::pending_key (destination, hash)); + } + exists = exists && (include_active || node.ledger.block_confirmed (transaction, hash)); + response_l.put ("exists", exists ? "1" : "0"); + } + else + { + ec = nano::error_blocks::not_found; + } + } + response_errors (); +} + +void nano::rpc_handler::payment_begin () +{ + std::string id_text (request.get ("wallet")); + nano::uint256_union id; + if (!id.decode_hex (id_text)) + { + auto existing (node.wallets.items.find (id)); + if (existing != node.wallets.items.end ()) + { + auto transaction (node.wallets.tx_begin_write ()); + std::shared_ptr wallet (existing->second); + if (wallet->store.valid_password (transaction)) + { + nano::account account (0); + do + { + auto existing (wallet->free_accounts.begin ()); + if (existing != wallet->free_accounts.end ()) + { + account = *existing; + wallet->free_accounts.erase (existing); + if (wallet->store.find (transaction, account) == wallet->store.end ()) + { + node.logger.always_log (boost::str (boost::format ("Transaction wallet %1% externally modified listing account %2% as free but no longer exists") % id.to_string () % account.to_account ())); + account.clear (); + } + else + { + auto block_transaction (node.store.tx_begin_read ()); + if (!node.ledger.account_balance (block_transaction, account).is_zero ()) + { + node.logger.always_log (boost::str (boost::format ("Skipping account %1% for use as a transaction account: non-zero balance") % account.to_account ())); + account.clear (); + } + } + } + else + { + account = wallet->deterministic_insert (transaction); + break; + } + } while (account.is_zero ()); + if (!account.is_zero ()) + { + response_l.put ("deprecated", "1"); + response_l.put ("account", account.to_account ()); + } + else + { + ec = nano::error_rpc::payment_unable_create_account; + } + } + else + { + ec = nano::error_common::wallet_locked; + } + } + else + { + ec = nano::error_common::wallet_not_found; + } + } + else + { + ec = nano::error_common::bad_wallet_number; + } + response_errors (); +} + +void nano::rpc_handler::payment_init () +{ + auto wallet (wallet_impl ()); + if (!ec) + { + auto transaction (node.wallets.tx_begin_write ()); + if (wallet->store.valid_password (transaction)) + { + wallet->init_free_accounts (transaction); + response_l.put ("deprecated", "1"); + response_l.put ("status", "Ready"); + } + else + { + ec = nano::error_common::wallet_locked; + } + } + response_errors (); +} + +void nano::rpc_handler::payment_end () +{ + auto account (account_impl ()); + auto wallet (wallet_impl ()); + if (!ec) + { + auto transaction (node.wallets.tx_begin_read ()); + auto block_transaction (node.store.tx_begin_read ()); + wallet_account_impl (transaction, wallet, account); + if (!ec) + { + if (node.ledger.account_balance (block_transaction, account).is_zero ()) + { + wallet->free_accounts.insert (account); + response_l.put ("deprecated", "1"); + response_l.put ("ended", "1"); + } + else + { + ec = nano::error_rpc::payment_account_balance; + } + } + } + response_errors (); +} + +void nano::rpc_handler::payment_wait () +{ + std::string timeout_text (request.get ("timeout")); + auto account (account_impl ()); + auto amount (amount_impl ()); + if (!ec) + { + uint64_t timeout; + if (!decode_unsigned (timeout_text, timeout)) + { + { + auto observer (std::make_shared (response, rpc, account, amount)); + observer->start (timeout); + std::lock_guard lock (rpc.mutex); + assert (rpc.payment_observers.find (account) == rpc.payment_observers.end ()); + rpc.payment_observers[account] = observer; + } + rpc.observer_action (account); + } + else + { + ec = nano::error_rpc::bad_timeout; + } + } + if (ec) + { + response_errors (); + } +} + +void nano::rpc_handler::process () +{ + const bool json_block_l = request.get ("json_block", false); + std::shared_ptr block; + if (json_block_l) + { + block = block_json_impl (true); + } + else + { + block = block_impl (true); + } + + // State blocks subtype check + if (!ec && block->type () == nano::block_type::state) + { + std::string subtype_text (request.get ("subtype", "")); + if (!subtype_text.empty ()) + { + std::shared_ptr block_state (std::static_pointer_cast (block)); + auto transaction (node.store.tx_begin_read ()); + if (!block_state->hashables.previous.is_zero () && !node.store.block_exists (transaction, block_state->hashables.previous)) + { + ec = nano::error_process::gap_previous; + } + else + { + auto balance (node.ledger.account_balance (transaction, block_state->hashables.account)); + if (subtype_text == "send") + { + if (balance <= block_state->hashables.balance.number ()) + { + ec = nano::error_rpc::invalid_subtype_balance; + } + // Send with previous == 0 fails balance check. No previous != 0 check required + } + else if (subtype_text == "receive") + { + if (balance > block_state->hashables.balance.number ()) + { + ec = nano::error_rpc::invalid_subtype_balance; + } + // Receive can be point to open block. No previous != 0 check required + } + else if (subtype_text == "open") + { + if (!block_state->hashables.previous.is_zero ()) + { + ec = nano::error_rpc::invalid_subtype_previous; + } + } + else if (subtype_text == "change") + { + if (balance != block_state->hashables.balance.number ()) + { + ec = nano::error_rpc::invalid_subtype_balance; + } + else if (block_state->hashables.previous.is_zero ()) + { + ec = nano::error_rpc::invalid_subtype_previous; + } + } + else if (subtype_text == "epoch") + { + if (balance != block_state->hashables.balance.number ()) + { + ec = nano::error_rpc::invalid_subtype_balance; + } + else if (!node.ledger.is_epoch_link (block_state->hashables.link)) + { + ec = ec = nano::error_rpc::invalid_subtype_epoch_link; + } + } + else + { + ec = nano::error_rpc::invalid_subtype; + } + } + } + } + if (!ec) + { + if (!nano::work_validate (*block)) + { + auto hash (block->hash ()); + node.block_arrival.add (hash); + nano::process_return result; + { + auto transaction (node.store.tx_begin_write ()); + // Set current time to trigger automatic rebroadcast and election + nano::unchecked_info info (block, block->account (), nano::seconds_since_epoch (), nano::signature_verification::unknown); + result = node.block_processor.process_one (transaction, info); + } + switch (result.code) + { + case nano::process_result::progress: + { + response_l.put ("hash", hash.to_string ()); + break; + } + case nano::process_result::gap_previous: + { + ec = nano::error_process::gap_previous; + break; + } + case nano::process_result::gap_source: + { + ec = nano::error_process::gap_source; + break; + } + case nano::process_result::old: + { + ec = nano::error_process::old; + break; + } + case nano::process_result::bad_signature: + { + ec = nano::error_process::bad_signature; + break; + } + case nano::process_result::negative_spend: + { + // TODO once we get RPC versioning, this should be changed to "negative spend" + ec = nano::error_process::negative_spend; + break; + } + case nano::process_result::balance_mismatch: + { + ec = nano::error_process::balance_mismatch; + break; + } + case nano::process_result::unreceivable: + { + ec = nano::error_process::unreceivable; + break; + } + case nano::process_result::block_position: + { + ec = nano::error_process::block_position; + break; + } + case nano::process_result::fork: + { + const bool force = request.get ("force", false); + if (force && rpc.config.enable_control) + { + node.active.erase (*block); + node.block_processor.force (block); + response_l.put ("hash", hash.to_string ()); + } + else + { + ec = nano::error_process::fork; + } + break; + } + default: + { + ec = nano::error_process::other; + break; + } + } + } + else + { + ec = nano::error_blocks::work_low; + } + } + response_errors (); +} + +void nano::rpc_handler::receive () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + auto account (account_impl ()); + auto hash (hash_impl ("block")); + if (!ec) + { + auto transaction (node.wallets.tx_begin_read ()); + wallet_locked_impl (transaction, wallet); + wallet_account_impl (transaction, wallet, account); + if (!ec) + { + auto block_transaction (node.store.tx_begin_read ()); + auto block (node.store.block_get (block_transaction, hash)); + if (block != nullptr) + { + if (node.store.pending_exists (block_transaction, nano::pending_key (account, hash))) + { + auto work (work_optional_impl ()); + if (!ec && work) + { + nano::account_info info; + nano::uint256_union head; + if (!node.store.account_get (block_transaction, account, info)) + { + head = info.head; + } + else + { + head = account; + } + if (nano::work_validate (head, work)) + { + ec = nano::error_common::invalid_work; + } + } + if (!ec) + { + bool generate_work (work == 0); // Disable work generation if "work" option is provided + auto response_a (response); + // clang-format off + wallet->receive_async (std::move (block), account, node.network_params.ledger.genesis_amount, [response_a](std::shared_ptr block_a) { + if (block_a != nullptr) + { + boost::property_tree::ptree response_l; + response_l.put ("block", block_a->hash ().to_string ()); + response_a (response_l); + } + else + { + error_response (response_a, "Error generating block"); + } + }, + work, generate_work); + // clang-format on + } + } + else + { + ec = nano::error_process::unreceivable; + } + } + else + { + ec = nano::error_blocks::not_found; + } + } + } + // Because of receive_async + if (ec) + { + response_errors (); + } +} + +void nano::rpc_handler::receive_minimum () +{ + rpc_control_impl (); + if (!ec) + { + response_l.put ("amount", node.config.receive_minimum.to_string_dec ()); + } + response_errors (); +} + +void nano::rpc_handler::receive_minimum_set () +{ + rpc_control_impl (); + auto amount (amount_impl ()); + if (!ec) + { + node.config.receive_minimum = amount; + response_l.put ("success", ""); + } + response_errors (); +} + +void nano::rpc_handler::representatives () +{ + auto count (count_optional_impl ()); + if (!ec) + { + const bool sorting = request.get ("sorting", false); + boost::property_tree::ptree representatives; + auto transaction (node.store.tx_begin_read ()); + if (!sorting) // Simple + { + for (auto i (node.store.representation_begin (transaction)), n (node.store.representation_end ()); i != n && representatives.size () < count; ++i) + { + nano::account account (i->first); + auto amount (node.store.representation_get (transaction, account)); + representatives.put (account.to_account (), amount.convert_to ()); + } + } + else // Sorting + { + std::vector> representation; + for (auto i (node.store.representation_begin (transaction)), n (node.store.representation_end ()); i != n; ++i) + { + nano::account account (i->first); + auto amount (node.store.representation_get (transaction, account)); + representation.push_back (std::make_pair (amount, account.to_account ())); + } + std::sort (representation.begin (), representation.end ()); + std::reverse (representation.begin (), representation.end ()); + for (auto i (representation.begin ()), n (representation.end ()); i != n && representatives.size () < count; ++i) + { + representatives.put (i->second, (i->first).number ().convert_to ()); + } + } + response_l.add_child ("representatives", representatives); + } + response_errors (); +} + +void nano::rpc_handler::representatives_online () +{ + const auto accounts_node = request.get_child_optional ("accounts"); + const bool weight = request.get ("weight", false); + std::vector accounts_to_filter; + if (accounts_node.is_initialized ()) + { + for (auto & a : (*accounts_node)) + { + nano::public_key account; + auto error (account.decode_account (a.second.get (""))); + if (!error) + { + accounts_to_filter.push_back (account); + } + else + { + ec = nano::error_common::bad_account_number; + break; + } + } + } + if (!ec) + { + boost::property_tree::ptree representatives; + auto transaction (node.store.tx_begin_read ()); + auto reps (node.online_reps.list ()); + for (auto & i : reps) + { + if (accounts_node.is_initialized ()) + { + if (accounts_to_filter.empty ()) + { + break; + } + auto found_acc = std::find (accounts_to_filter.begin (), accounts_to_filter.end (), i); + if (found_acc == accounts_to_filter.end ()) + { + continue; + } + else + { + accounts_to_filter.erase (found_acc); + } + } + if (weight) + { + boost::property_tree::ptree weight_node; + auto account_weight (node.ledger.weight (transaction, i)); + weight_node.put ("weight", account_weight.convert_to ()); + representatives.add_child (i.to_account (), weight_node); + } + else + { + boost::property_tree::ptree entry; + entry.put ("", i.to_account ()); + representatives.push_back (std::make_pair ("", entry)); + } + } + response_l.add_child ("representatives", representatives); + } + response_errors (); +} + +void nano::rpc_handler::republish () +{ + auto count (count_optional_impl (1024U)); + uint64_t sources (0); + uint64_t destinations (0); + boost::optional sources_text (request.get_optional ("sources")); + if (!ec && sources_text.is_initialized ()) + { + if (decode_unsigned (sources_text.get (), sources)) + { + ec = nano::error_rpc::invalid_sources; + } + } + boost::optional destinations_text (request.get_optional ("destinations")); + if (!ec && destinations_text.is_initialized ()) + { + if (decode_unsigned (destinations_text.get (), destinations)) + { + ec = nano::error_rpc::invalid_destinations; + } + } + auto hash (hash_impl ()); + if (!ec) + { + boost::property_tree::ptree blocks; + auto transaction (node.store.tx_begin_read ()); + auto block (node.store.block_get (transaction, hash)); + if (block != nullptr) + { + std::deque> republish_bundle; + for (auto i (0); !hash.is_zero () && i < count; ++i) + { + block = node.store.block_get (transaction, hash); + if (sources != 0) // Republish source chain + { + nano::block_hash source (node.ledger.block_source (transaction, *block)); + auto block_a (node.store.block_get (transaction, source)); + std::vector hashes; + while (block_a != nullptr && hashes.size () < sources) + { + hashes.push_back (source); + source = block_a->previous (); + block_a = node.store.block_get (transaction, source); + } + std::reverse (hashes.begin (), hashes.end ()); + for (auto & hash_l : hashes) + { + block_a = node.store.block_get (transaction, hash_l); + republish_bundle.push_back (std::move (block_a)); + boost::property_tree::ptree entry_l; + entry_l.put ("", hash_l.to_string ()); + blocks.push_back (std::make_pair ("", entry_l)); + } + } + republish_bundle.push_back (std::move (block)); // Republish block + boost::property_tree::ptree entry; + entry.put ("", hash.to_string ()); + blocks.push_back (std::make_pair ("", entry)); + if (destinations != 0) // Republish destination chain + { + auto block_b (node.store.block_get (transaction, hash)); + auto destination (node.ledger.block_destination (transaction, *block_b)); + if (!destination.is_zero ()) + { + if (!node.store.pending_exists (transaction, nano::pending_key (destination, hash))) + { + nano::block_hash previous (node.ledger.latest (transaction, destination)); + auto block_d (node.store.block_get (transaction, previous)); + nano::block_hash source; + std::vector hashes; + while (block_d != nullptr && hash != source) + { + hashes.push_back (previous); + source = node.ledger.block_source (transaction, *block_d); + previous = block_d->previous (); + block_d = node.store.block_get (transaction, previous); + } + std::reverse (hashes.begin (), hashes.end ()); + if (hashes.size () > destinations) + { + hashes.resize (destinations); + } + for (auto & hash_l : hashes) + { + block_d = node.store.block_get (transaction, hash_l); + republish_bundle.push_back (std::move (block_d)); + boost::property_tree::ptree entry_l; + entry_l.put ("", hash_l.to_string ()); + blocks.push_back (std::make_pair ("", entry_l)); + } + } + } + } + hash = node.store.block_successor (transaction, hash); + } + node.network.flood_block_batch (republish_bundle, 25); + response_l.put ("success", ""); // obsolete + response_l.add_child ("blocks", blocks); + } + else + { + ec = nano::error_blocks::not_found; + } + } + response_errors (); +} + +void nano::rpc_handler::search_pending () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + if (!ec) + { + auto error (wallet->search_pending ()); + response_l.put ("started", !error); + } + response_errors (); +} + +void nano::rpc_handler::search_pending_all () +{ + rpc_control_impl (); + if (!ec) + { + node.wallets.search_pending_all (); + response_l.put ("success", ""); + } + response_errors (); +} + +void nano::rpc_handler::send () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + auto amount (amount_impl ()); + // Sending 0 amount is invalid with state blocks + if (!ec && amount.is_zero ()) + { + ec = nano::error_common::invalid_amount; + } + if (!ec) + { + std::string source_text (request.get ("source")); + nano::account source; + if (!source.decode_account (source_text)) + { + std::string destination_text (request.get ("destination")); + nano::account destination; + if (!destination.decode_account (destination_text)) + { + auto work (work_optional_impl ()); + nano::uint128_t balance (0); + if (!ec) + { + auto transaction (node.wallets.tx_begin_read ()); + auto block_transaction (node.store.tx_begin_read ()); + if (wallet->store.valid_password (transaction)) + { + if (wallet->store.find (transaction, source) != wallet->store.end ()) + { + nano::account_info info; + if (!node.store.account_get (block_transaction, source, info)) + { + balance = (info.balance).number (); + } + else + { + ec = nano::error_common::account_not_found; + } + if (!ec && work) + { + if (nano::work_validate (info.head, work)) + { + ec = nano::error_common::invalid_work; + } + } + } + else + { + ec = nano::error_common::account_not_found_wallet; + } + } + else + { + ec = nano::error_common::wallet_locked; + } + } + if (!ec) + { + bool generate_work (work == 0); // Disable work generation if "work" option is provided + boost::optional send_id (request.get_optional ("id")); + auto rpc_l (shared_from_this ()); + auto response_a (response); + // clang-format off + wallet->send_async (source, destination, amount.number (), [balance, amount, response_a](std::shared_ptr block_a) { + if (block_a != nullptr) + { + boost::property_tree::ptree response_l; + response_l.put ("block", block_a->hash ().to_string ()); + response_a (response_l); + } + else + { + if (balance >= amount.number ()) + { + error_response (response_a, "Error generating block"); + } + else + { + std::error_code ec (nano::error_common::insufficient_balance); + error_response (response_a, ec.message ()); + } + } + }, + work, generate_work, send_id); + // clang-format on + } + } + else + { + ec = nano::error_rpc::bad_destination; + } + } + else + { + ec = nano::error_rpc::bad_source; + } + } + // Because of send_async + if (ec) + { + response_errors (); + } +} + +void nano::rpc_handler::sign () +{ + const bool json_block_l = request.get ("json_block", false); + // Retrieving hash + nano::block_hash hash (0); + boost::optional hash_text (request.get_optional ("hash")); + if (hash_text.is_initialized ()) + { + hash = hash_impl (); + } + // Retrieving block + std::shared_ptr block; + boost::optional block_text (request.get_optional ("block")); + if (!ec && block_text.is_initialized ()) + { + if (json_block_l) + { + block = block_json_impl (true); + } + else + { + block = block_impl (true); + } + if (block != nullptr) + { + hash = block->hash (); + } + } + + // Hash or block are not initialized + if (!ec && hash.is_zero ()) + { + ec = nano::error_blocks::invalid_block; + } + // Hash is initialized without config permission + else if (!ec && !hash.is_zero () && block == nullptr && !rpc.config.enable_sign_hash) + { + ec = nano::error_rpc::sign_hash_disabled; + } + if (!ec) + { + nano::raw_key prv; + prv.data.clear (); + // Retrieving private key from request + boost::optional key_text (request.get_optional ("key")); + if (key_text.is_initialized ()) + { + if (prv.data.decode_hex (key_text.get ())) + { + ec = nano::error_common::bad_private_key; + } + } + else + { + // Retrieving private key from wallet + boost::optional account_text (request.get_optional ("account")); + boost::optional wallet_text (request.get_optional ("wallet")); + if (wallet_text.is_initialized () && account_text.is_initialized ()) + { + auto account (account_impl ()); + auto wallet (wallet_impl ()); + if (!ec) + { + auto transaction (node.wallets.tx_begin_read ()); + wallet_locked_impl (transaction, wallet); + wallet_account_impl (transaction, wallet, account); + if (!ec) + { + wallet->store.fetch (transaction, account, prv); + } + } + } + } + // Signing + if (prv.data != 0) + { + nano::public_key pub (nano::pub_key (prv.data)); + nano::signature signature (nano::sign_message (prv, pub, hash)); + response_l.put ("signature", signature.to_string ()); + if (block != nullptr) + { + block->signature_set (signature); + + if (json_block_l) + { + boost::property_tree::ptree block_node_l; + block->serialize_json (block_node_l); + response_l.add_child ("block", block_node_l); + } + else + { + std::string contents; + block->serialize_json (contents); + response_l.put ("block", contents); + } + } + } + else + { + ec = nano::error_rpc::block_create_key_required; + } + } + response_errors (); +} + +void nano::rpc_handler::stats () +{ + auto sink = node.stats.log_sink_json (); + std::string type (request.get ("type", "")); + bool use_sink = false; + if (type == "counters") + { + node.stats.log_counters (*sink); + use_sink = true; + } + else if (type == "objects") + { + rpc_control_impl (); + if (!ec) + { + construct_json (collect_seq_con_info (node, "node").get (), response_l); + } + } + else if (type == "samples") + { + node.stats.log_samples (*sink); + use_sink = true; + } + else + { + ec = nano::error_rpc::invalid_missing_type; + } + if (!ec && use_sink) + { + auto stat_tree_l (*static_cast (sink->to_object ())); + stat_tree_l.put ("stat_duration_seconds", node.stats.last_reset ().count ()); + response (stat_tree_l); + } + else + { + response_errors (); + } +} + +void nano::rpc_handler::stats_clear () +{ + node.stats.clear (); + response_l.put ("success", ""); + response (response_l); +} + +void nano::rpc_handler::stop () +{ + rpc_control_impl (); + if (!ec) + { + response_l.put ("success", ""); + } + response_errors (); + if (!ec) + { + rpc.stop (); + node.stop (); + } +} + +void nano::rpc_handler::unchecked () +{ + auto count (count_optional_impl ()); + if (!ec) + { + boost::property_tree::ptree unchecked; + auto transaction (node.store.tx_begin_read ()); + for (auto i (node.store.unchecked_begin (transaction)), n (node.store.unchecked_end ()); i != n && unchecked.size () < count; ++i) + { + nano::unchecked_info info (i->second); + std::string contents; + info.block->serialize_json (contents); + unchecked.put (info.block->hash ().to_string (), contents); + } + response_l.add_child ("blocks", unchecked); + } + response_errors (); +} + +void nano::rpc_handler::unchecked_clear () +{ + rpc_control_impl (); + if (!ec) + { + auto transaction (node.store.tx_begin_write ()); + node.store.unchecked_clear (transaction); + response_l.put ("success", ""); + } + response_errors (); +} + +void nano::rpc_handler::unchecked_get () +{ + const bool json_block_l = request.get ("json_block", false); + auto hash (hash_impl ()); + if (!ec) + { + auto transaction (node.store.tx_begin_read ()); + for (auto i (node.store.unchecked_begin (transaction)), n (node.store.unchecked_end ()); i != n; ++i) + { + nano::unchecked_key key (i->first); + if (key.hash == hash) + { + nano::unchecked_info info (i->second); + response_l.put ("modified_timestamp", std::to_string (info.modified)); + + if (json_block_l) + { + boost::property_tree::ptree block_node_l; + info.block->serialize_json (block_node_l); + response_l.add_child ("contents", block_node_l); + } + else + { + std::string contents; + info.block->serialize_json (contents); + response_l.put ("contents", contents); + } + break; + } + } + if (response_l.empty ()) + { + ec = nano::error_blocks::not_found; + } + } + response_errors (); +} + +void nano::rpc_handler::unchecked_keys () +{ + const bool json_block_l = request.get ("json_block", false); + auto count (count_optional_impl ()); + nano::uint256_union key (0); + boost::optional hash_text (request.get_optional ("key")); + if (!ec && hash_text.is_initialized ()) + { + if (key.decode_hex (hash_text.get ())) + { + ec = nano::error_rpc::bad_key; + } + } + if (!ec) + { + boost::property_tree::ptree unchecked; + auto transaction (node.store.tx_begin_read ()); + for (auto i (node.store.unchecked_begin (transaction, nano::unchecked_key (key, 0))), n (node.store.unchecked_end ()); i != n && unchecked.size () < count; ++i) + { + boost::property_tree::ptree entry; + nano::unchecked_info info (i->second); + entry.put ("key", nano::block_hash (i->first.key ()).to_string ()); + entry.put ("hash", info.block->hash ().to_string ()); + entry.put ("modified_timestamp", std::to_string (info.modified)); + if (json_block_l) + { + boost::property_tree::ptree block_node_l; + info.block->serialize_json (block_node_l); + entry.add_child ("contents", block_node_l); + } + else + { + std::string contents; + info.block->serialize_json (contents); + entry.put ("contents", contents); + } + unchecked.push_back (std::make_pair ("", entry)); + } + response_l.add_child ("unchecked", unchecked); + } + 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 ()); + response_errors (); +} + +void nano::rpc_handler::version () +{ + response_l.put ("rpc_version", "1"); + response_l.put ("store_version", std::to_string (node.store_version ())); + response_l.put ("protocol_version", std::to_string (nano::protocol_version)); + if (NANO_VERSION_PATCH == 0) + { + response_l.put ("node_vendor", boost::str (boost::format ("Nano %1%") % NANO_MAJOR_MINOR_VERSION)); + } + else + { + response_l.put ("node_vendor", boost::str (boost::format ("Nano %1%") % NANO_MAJOR_MINOR_RC_VERSION)); + } + response_errors (); +} + +void nano::rpc_handler::validate_account_number () +{ + std::string account_text (request.get ("account")); + nano::uint256_union account; + auto error (account.decode_account (account_text)); + response_l.put ("valid", error ? "0" : "1"); + response_errors (); +} + +void nano::rpc_handler::wallet_add () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + if (!ec) + { + std::string key_text (request.get ("key")); + nano::raw_key key; + if (!key.data.decode_hex (key_text)) + { + const bool generate_work = request.get ("work", true); + auto pub (wallet->insert_adhoc (key, generate_work)); + if (!pub.is_zero ()) + { + response_l.put ("account", pub.to_account ()); + } + else + { + ec = nano::error_common::wallet_locked; + } + } + else + { + ec = nano::error_common::bad_private_key; + } + } + response_errors (); +} + +void nano::rpc_handler::wallet_add_watch () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + if (!ec) + { + auto transaction (node.wallets.tx_begin_write ()); + if (wallet->store.valid_password (transaction)) + { + for (auto & accounts : request.get_child ("accounts")) + { + auto account (account_impl (accounts.second.data ())); + if (!ec) + { + wallet->insert_watch (transaction, account); + } + } + response_l.put ("success", ""); + } + else + { + ec = nano::error_common::wallet_locked; + } + } + response_errors (); +} + +void nano::rpc_handler::wallet_info () +{ + auto wallet (wallet_impl ()); + if (!ec) + { + nano::uint128_t balance (0); + nano::uint128_t pending (0); + uint64_t count (0); + uint64_t deterministic_count (0); + uint64_t adhoc_count (0); + auto transaction (node.wallets.tx_begin_read ()); + auto block_transaction (node.store.tx_begin_read ()); + for (auto i (wallet->store.begin (transaction)), n (wallet->store.end ()); i != n; ++i) + { + nano::account account (i->first); + balance = balance + node.ledger.account_balance (block_transaction, account); + pending = pending + node.ledger.account_pending (block_transaction, account); + nano::key_type key_type (wallet->store.key_type (i->second)); + if (key_type == nano::key_type::deterministic) + { + deterministic_count++; + } + else if (key_type == nano::key_type::adhoc) + { + adhoc_count++; + } + count++; + } + uint32_t deterministic_index (wallet->store.deterministic_index_get (transaction)); + response_l.put ("balance", balance.convert_to ()); + response_l.put ("pending", pending.convert_to ()); + response_l.put ("accounts_count", std::to_string (count)); + response_l.put ("deterministic_count", std::to_string (deterministic_count)); + response_l.put ("adhoc_count", std::to_string (adhoc_count)); + response_l.put ("deterministic_index", std::to_string (deterministic_index)); + } + response_errors (); +} + +void nano::rpc_handler::wallet_balances () +{ + auto wallet (wallet_impl ()); + auto threshold (threshold_optional_impl ()); + if (!ec) + { + boost::property_tree::ptree balances; + auto transaction (node.wallets.tx_begin_read ()); + auto block_transaction (node.store.tx_begin_read ()); + for (auto i (wallet->store.begin (transaction)), n (wallet->store.end ()); i != n; ++i) + { + nano::account account (i->first); + nano::uint128_t balance = node.ledger.account_balance (block_transaction, account); + if (balance >= threshold.number ()) + { + boost::property_tree::ptree entry; + nano::uint128_t pending = node.ledger.account_pending (block_transaction, account); + entry.put ("balance", balance.convert_to ()); + entry.put ("pending", pending.convert_to ()); + balances.push_back (std::make_pair (account.to_account (), entry)); + } + } + response_l.add_child ("balances", balances); + } + response_errors (); +} + +void nano::rpc_handler::wallet_change_seed () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + if (!ec) + { + std::string seed_text (request.get ("seed")); + nano::raw_key seed; + if (!seed.data.decode_hex (seed_text)) + { + auto count (static_cast (count_optional_impl (0))); + auto transaction (node.wallets.tx_begin_write ()); + if (wallet->store.valid_password (transaction)) + { + nano::public_key account (wallet->change_seed (transaction, seed, count)); + response_l.put ("success", ""); + response_l.put ("last_restored_account", account.to_account ()); + auto index (wallet->store.deterministic_index_get (transaction)); + assert (index > 0); + response_l.put ("restored_count", std::to_string (index)); + } + else + { + ec = nano::error_common::wallet_locked; + } + } + else + { + ec = nano::error_common::bad_seed; + } + } + response_errors (); +} + +void nano::rpc_handler::wallet_contains () +{ + auto account (account_impl ()); + auto wallet (wallet_impl ()); + if (!ec) + { + auto transaction (node.wallets.tx_begin_read ()); + auto exists (wallet->store.find (transaction, account) != wallet->store.end ()); + response_l.put ("exists", exists ? "1" : "0"); + } + response_errors (); +} + +void nano::rpc_handler::wallet_create () +{ + rpc_control_impl (); + if (!ec) + { + nano::raw_key seed; + auto seed_text (request.get_optional ("seed")); + if (seed_text.is_initialized () && seed.data.decode_hex (seed_text.get ())) + { + ec = nano::error_common::bad_seed; + } + if (!ec) + { + nano::keypair wallet_id; + auto wallet (node.wallets.create (wallet_id.pub)); + auto existing (node.wallets.items.find (wallet_id.pub)); + if (existing != node.wallets.items.end ()) + { + response_l.put ("wallet", wallet_id.pub.to_string ()); + } + else + { + ec = nano::error_common::wallet_lmdb_max_dbs; + } + if (!ec && seed_text.is_initialized ()) + { + auto transaction (node.wallets.tx_begin_write ()); + nano::public_key account (wallet->change_seed (transaction, seed)); + response_l.put ("last_restored_account", account.to_account ()); + auto index (wallet->store.deterministic_index_get (transaction)); + assert (index > 0); + response_l.put ("restored_count", std::to_string (index)); + } + } + } + response_errors (); +} + +void nano::rpc_handler::wallet_destroy () +{ + rpc_control_impl (); + if (!ec) + { + std::string wallet_text (request.get ("wallet")); + nano::uint256_union wallet; + if (!wallet.decode_hex (wallet_text)) + { + auto existing (node.wallets.items.find (wallet)); + if (existing != node.wallets.items.end ()) + { + node.wallets.destroy (wallet); + bool destroyed (node.wallets.items.find (wallet) == node.wallets.items.end ()); + response_l.put ("destroyed", destroyed ? "1" : "0"); + } + else + { + ec = nano::error_common::wallet_not_found; + } + } + else + { + ec = nano::error_common::bad_wallet_number; + } + } + response_errors (); +} + +void nano::rpc_handler::wallet_export () +{ + auto wallet (wallet_impl ()); + if (!ec) + { + auto transaction (node.wallets.tx_begin_read ()); + std::string json; + wallet->store.serialize_json (transaction, json); + response_l.put ("json", json); + } + response_errors (); +} + +void nano::rpc_handler::wallet_frontiers () +{ + auto wallet (wallet_impl ()); + if (!ec) + { + boost::property_tree::ptree frontiers; + auto transaction (node.wallets.tx_begin_read ()); + auto block_transaction (node.store.tx_begin_read ()); + for (auto i (wallet->store.begin (transaction)), n (wallet->store.end ()); i != n; ++i) + { + nano::account account (i->first); + auto latest (node.ledger.latest (block_transaction, account)); + if (!latest.is_zero ()) + { + frontiers.put (account.to_account (), latest.to_string ()); + } + } + response_l.add_child ("frontiers", frontiers); + } + response_errors (); +} + +void nano::rpc_handler::wallet_history () +{ + uint64_t modified_since (1); + boost::optional modified_since_text (request.get_optional ("modified_since")); + if (modified_since_text.is_initialized ()) + { + if (decode_unsigned (modified_since_text.get (), modified_since)) + { + ec = nano::error_rpc::invalid_timestamp; + } + } + auto wallet (wallet_impl ()); + if (!ec) + { + std::multimap> entries; + auto transaction (node.wallets.tx_begin_read ()); + auto block_transaction (node.store.tx_begin_read ()); + for (auto i (wallet->store.begin (transaction)), n (wallet->store.end ()); i != n; ++i) + { + nano::account account (i->first); + nano::account_info info; + if (!node.store.account_get (block_transaction, account, info)) + { + auto timestamp (info.modified); + auto hash (info.head); + while (timestamp >= modified_since && !hash.is_zero ()) + { + nano::block_sideband sideband; + auto block (node.store.block_get (block_transaction, hash, &sideband)); + timestamp = sideband.timestamp; + if (block != nullptr && timestamp >= modified_since) + { + boost::property_tree::ptree entry; + history_visitor visitor (*this, false, block_transaction, entry, hash); + block->visit (visitor); + if (!entry.empty ()) + { + entry.put ("block_account", account.to_account ()); + entry.put ("hash", hash.to_string ()); + entry.put ("local_timestamp", std::to_string (timestamp)); + entries.insert (std::make_pair (timestamp, entry)); + } + hash = block->previous (); + } + else + { + hash.clear (); + } + } + } + } + boost::property_tree::ptree history; + for (auto i (entries.begin ()), n (entries.end ()); i != n; ++i) + { + history.push_back (std::make_pair ("", i->second)); + } + response_l.add_child ("history", history); + } + response_errors (); +} + +void nano::rpc_handler::wallet_key_valid () +{ + auto wallet (wallet_impl ()); + if (!ec) + { + auto transaction (node.wallets.tx_begin_read ()); + auto valid (wallet->store.valid_password (transaction)); + response_l.put ("valid", valid ? "1" : "0"); + } + response_errors (); +} + +void nano::rpc_handler::wallet_ledger () +{ + const bool representative = request.get ("representative", false); + const bool weight = request.get ("weight", false); + const bool pending = request.get ("pending", false); + uint64_t modified_since (0); + boost::optional modified_since_text (request.get_optional ("modified_since")); + if (modified_since_text.is_initialized ()) + { + modified_since = strtoul (modified_since_text.get ().c_str (), NULL, 10); + } + auto wallet (wallet_impl ()); + if (!ec) + { + boost::property_tree::ptree accounts; + auto transaction (node.wallets.tx_begin_read ()); + auto block_transaction (node.store.tx_begin_read ()); + for (auto i (wallet->store.begin (transaction)), n (wallet->store.end ()); i != n; ++i) + { + nano::account account (i->first); + nano::account_info info; + if (!node.store.account_get (block_transaction, account, info)) + { + if (info.modified >= modified_since) + { + boost::property_tree::ptree entry; + entry.put ("frontier", info.head.to_string ()); + entry.put ("open_block", info.open_block.to_string ()); + entry.put ("representative_block", info.rep_block.to_string ()); + std::string balance; + nano::uint128_union (info.balance).encode_dec (balance); + entry.put ("balance", balance); + entry.put ("modified_timestamp", std::to_string (info.modified)); + entry.put ("block_count", std::to_string (info.block_count)); + if (representative) + { + auto block (node.store.block_get (block_transaction, info.rep_block)); + assert (block != nullptr); + entry.put ("representative", block->representative ().to_account ()); + } + if (weight) + { + auto account_weight (node.ledger.weight (block_transaction, account)); + entry.put ("weight", account_weight.convert_to ()); + } + if (pending) + { + auto account_pending (node.ledger.account_pending (block_transaction, account)); + entry.put ("pending", account_pending.convert_to ()); + } + accounts.push_back (std::make_pair (account.to_account (), entry)); + } + } + } + response_l.add_child ("accounts", accounts); + } + response_errors (); +} + +void nano::rpc_handler::wallet_lock () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + if (!ec) + { + nano::raw_key empty; + empty.data.clear (); + wallet->store.password.value_set (empty); + response_l.put ("locked", "1"); + node.logger.try_log ("Wallet locked"); + } + response_errors (); +} + +void nano::rpc_handler::wallet_pending () +{ + auto wallet (wallet_impl ()); + auto count (count_optional_impl ()); + auto threshold (threshold_optional_impl ()); + const bool source = request.get ("source", false); + const bool min_version = request.get ("min_version", false); + const bool include_active = request.get ("include_active", false); + if (!ec) + { + boost::property_tree::ptree pending; + auto transaction (node.wallets.tx_begin_read ()); + auto block_transaction (node.store.tx_begin_read ()); + for (auto i (wallet->store.begin (transaction)), n (wallet->store.end ()); i != n; ++i) + { + nano::account account (i->first); + boost::property_tree::ptree peers_l; + for (auto ii (node.store.pending_begin (block_transaction, nano::pending_key (account, 0))); nano::pending_key (ii->first).account == account && peers_l.size () < count; ++ii) + { + nano::pending_key key (ii->first); + if (include_active || node.ledger.block_confirmed (block_transaction, key.hash)) + { + if (threshold.is_zero () && !source) + { + boost::property_tree::ptree entry; + entry.put ("", key.hash.to_string ()); + peers_l.push_back (std::make_pair ("", entry)); + } + else + { + nano::pending_info info (ii->second); + if (info.amount.number () >= threshold.number ()) + { + if (source || min_version) + { + boost::property_tree::ptree pending_tree; + pending_tree.put ("amount", info.amount.number ().convert_to ()); + if (source) + { + pending_tree.put ("source", info.source.to_account ()); + } + if (min_version) + { + pending_tree.put ("min_version", info.epoch == nano::epoch::epoch_1 ? "1" : "0"); + } + peers_l.add_child (key.hash.to_string (), pending_tree); + } + else + { + peers_l.put (key.hash.to_string (), info.amount.number ().convert_to ()); + } + } + } + } + } + if (!peers_l.empty ()) + { + pending.add_child (account.to_account (), peers_l); + } + } + response_l.add_child ("blocks", pending); + } + response_errors (); +} + +void nano::rpc_handler::wallet_representative () +{ + auto wallet (wallet_impl ()); + if (!ec) + { + auto transaction (node.wallets.tx_begin_read ()); + response_l.put ("representative", wallet->store.representative (transaction).to_account ()); + } + response_errors (); +} + +void nano::rpc_handler::wallet_representative_set () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + if (!ec) + { + std::string representative_text (request.get ("representative")); + nano::account representative; + if (!representative.decode_account (representative_text)) + { + bool update_existing_accounts (request.get ("update_existing_accounts", false)); + { + auto transaction (node.wallets.tx_begin_write ()); + if (wallet->store.valid_password (transaction) || !update_existing_accounts) + { + wallet->store.representative_set (transaction, representative); + response_l.put ("set", "1"); + } + else + { + ec = nano::error_common::wallet_locked; + } + } + // Change representative for all wallet accounts + if (!ec && update_existing_accounts) + { + std::vector accounts; + { + auto transaction (node.wallets.tx_begin_read ()); + auto block_transaction (node.store.tx_begin_read ()); + for (auto i (wallet->store.begin (transaction)), n (wallet->store.end ()); i != n; ++i) + { + nano::account account (i->first); + nano::account_info info; + if (!node.store.account_get (block_transaction, account, info)) + { + auto block (node.store.block_get (block_transaction, info.rep_block)); + assert (block != nullptr); + if (block->representative () != representative) + { + accounts.push_back (account); + } + } + } + } + for (auto & account : accounts) + { + // clang-format off + wallet->change_async (account, representative, [](std::shared_ptr) {}, 0, false); + // clang-format on + } + } + } + else + { + ec = nano::error_rpc::bad_representative_number; + } + } + response_errors (); +} + +void nano::rpc_handler::wallet_republish () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + auto count (count_impl ()); + if (!ec) + { + boost::property_tree::ptree blocks; + std::deque> republish_bundle; + auto transaction (node.wallets.tx_begin_read ()); + auto block_transaction (node.store.tx_begin_read ()); + for (auto i (wallet->store.begin (transaction)), n (wallet->store.end ()); i != n; ++i) + { + nano::account account (i->first); + auto latest (node.ledger.latest (block_transaction, account)); + std::shared_ptr block; + std::vector hashes; + while (!latest.is_zero () && hashes.size () < count) + { + hashes.push_back (latest); + block = node.store.block_get (block_transaction, latest); + latest = block->previous (); + } + std::reverse (hashes.begin (), hashes.end ()); + for (auto & hash : hashes) + { + block = node.store.block_get (block_transaction, hash); + republish_bundle.push_back (std::move (block)); + boost::property_tree::ptree entry; + entry.put ("", hash.to_string ()); + blocks.push_back (std::make_pair ("", entry)); + } + } + node.network.flood_block_batch (republish_bundle, 25); + response_l.add_child ("blocks", blocks); + } + response_errors (); +} + +void nano::rpc_handler::wallet_work_get () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + if (!ec) + { + boost::property_tree::ptree works; + auto transaction (node.wallets.tx_begin_read ()); + for (auto i (wallet->store.begin (transaction)), n (wallet->store.end ()); i != n; ++i) + { + nano::account account (i->first); + uint64_t work (0); + auto error_work (wallet->store.work_get (transaction, account, work)); + (void)error_work; + works.put (account.to_account (), nano::to_string_hex (work)); + } + response_l.add_child ("works", works); + } + response_errors (); +} + +void nano::rpc_handler::work_generate () +{ + rpc_control_impl (); + auto hash (hash_impl ()); + uint64_t difficulty (node.network_params.network.publish_threshold); + boost::optional difficulty_text (request.get_optional ("difficulty")); + if (!ec && difficulty_text.is_initialized ()) + { + if (nano::from_string_hex (difficulty_text.get (), difficulty)) + { + ec = nano::error_rpc::bad_difficulty_format; + } + } + if (!ec && difficulty > rpc.config.max_work_generate_difficulty) + { + ec = nano::error_rpc::difficulty_limit; + } + if (!ec) + { + bool use_peers (request.get_optional ("use_peers") == true); + auto rpc_l (shared_from_this ()); + auto callback = [rpc_l](boost::optional const & work_a) { + if (work_a) + { + boost::property_tree::ptree response_l; + response_l.put ("work", nano::to_string_hex (work_a.value ())); + rpc_l->response (response_l); + } + else + { + error_response (rpc_l->response, "Cancelled"); + } + }; + if (!use_peers) + { + node.work.generate (hash, callback, difficulty); + } + else + { + node.work_generate (hash, callback, difficulty); + } + } + // Because of callback + if (ec) + { + response_errors (); + } +} + +void nano::rpc_handler::work_cancel () +{ + rpc_control_impl (); + auto hash (hash_impl ()); + if (!ec) + { + node.work.cancel (hash); + } + response_errors (); +} + +void nano::rpc_handler::work_get () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + auto account (account_impl ()); + if (!ec) + { + auto transaction (node.wallets.tx_begin_read ()); + wallet_account_impl (transaction, wallet, account); + if (!ec) + { + uint64_t work (0); + auto error_work (wallet->store.work_get (transaction, account, work)); + (void)error_work; + response_l.put ("work", nano::to_string_hex (work)); + } + } + response_errors (); +} + +void nano::rpc_handler::work_set () +{ + rpc_control_impl (); + auto wallet (wallet_impl ()); + auto account (account_impl ()); + auto work (work_optional_impl ()); + if (!ec) + { + auto transaction (node.wallets.tx_begin_write ()); + wallet_account_impl (transaction, wallet, account); + if (!ec) + { + wallet->store.work_put (transaction, account, work); + response_l.put ("success", ""); + } + } + response_errors (); +} + +void nano::rpc_handler::work_validate () +{ + auto hash (hash_impl ()); + auto work (work_optional_impl ()); + uint64_t difficulty (node.network_params.network.publish_threshold); + boost::optional difficulty_text (request.get_optional ("difficulty")); + if (!ec && difficulty_text.is_initialized ()) + { + if (nano::from_string_hex (difficulty_text.get (), difficulty)) + { + ec = nano::error_rpc::bad_difficulty_format; + } + } + if (!ec) + { + uint64_t result_difficulty (0); + bool invalid (nano::work_validate (hash, work, &result_difficulty)); + bool valid (!invalid && result_difficulty >= difficulty); + response_l.put ("valid", valid ? "1" : "0"); + } + response_errors (); +} + +void nano::rpc_handler::work_peer_add () +{ + rpc_control_impl (); + if (!ec) + { + std::string address_text = request.get ("address"); + std::string port_text = request.get ("port"); + uint16_t port; + if (!nano::parse_port (port_text, port)) + { + node.config.work_peers.push_back (std::make_pair (address_text, port)); + response_l.put ("success", ""); + } + else + { + ec = nano::error_common::invalid_port; + } + } + response_errors (); +} + +void nano::rpc_handler::work_peers () +{ + rpc_control_impl (); + if (!ec) + { + boost::property_tree::ptree work_peers_l; + for (auto i (node.config.work_peers.begin ()), n (node.config.work_peers.end ()); i != n; ++i) + { + boost::property_tree::ptree entry; + entry.put ("", boost::str (boost::format ("%1%:%2%") % i->first % i->second)); + work_peers_l.push_back (std::make_pair ("", entry)); + } + response_l.add_child ("work_peers", work_peers_l); + } + response_errors (); +} + +void nano::rpc_handler::work_peers_clear () +{ + rpc_control_impl (); + if (!ec) + { + node.config.work_peers.clear (); + response_l.put ("success", ""); + } + response_errors (); +} + +namespace +{ +std::string filter_request (boost::property_tree::ptree tree_a) +{ + // Replace password + boost::optional password_text (tree_a.get_optional ("password")); + if (password_text.is_initialized ()) + { + tree_a.put ("password", "password"); + } + // Save first 2 symbols of wallet, key, seed + boost::optional wallet_text (tree_a.get_optional ("wallet")); + if (wallet_text.is_initialized () && wallet_text.get ().length () > 2) + { + tree_a.put ("wallet", wallet_text.get ().replace (wallet_text.get ().begin () + 2, wallet_text.get ().end (), wallet_text.get ().length () - 2, 'X')); + } + boost::optional key_text (tree_a.get_optional ("key")); + if (key_text.is_initialized () && key_text.get ().length () > 2) + { + tree_a.put ("key", key_text.get ().replace (key_text.get ().begin () + 2, key_text.get ().end (), key_text.get ().length () - 2, 'X')); + } + boost::optional seed_text (tree_a.get_optional ("seed")); + if (seed_text.is_initialized () && seed_text.get ().length () > 2) + { + tree_a.put ("seed", seed_text.get ().replace (seed_text.get ().begin () + 2, seed_text.get ().end (), seed_text.get ().length () - 2, 'X')); + } + std::string result; + std::stringstream stream; + boost::property_tree::write_json (stream, tree_a, false); + result = stream.str (); + // removing std::endl + if (result.length () > 1) + { + result.pop_back (); + } + return result; +} +} + +void nano::rpc_handler::process_request () +{ + try + { + auto max_depth_exceeded (false); + auto max_depth_possible (0); + for (auto ch : body) + { + if (ch == '[' || ch == '{') + { + if (max_depth_possible >= rpc.config.max_json_depth) + { + max_depth_exceeded = true; + break; + } + ++max_depth_possible; + } + } + if (max_depth_exceeded) + { + error_response (response, "Max JSON depth exceeded"); + } + else + { + std::stringstream istream (body); + boost::property_tree::read_json (istream, request); + std::string action (request.get ("action")); + if (node.config.logging.log_rpc ()) + { + rpc.node.logger.always_log (boost::str (boost::format ("%1% ") % request_id), filter_request (request)); + } + + auto no_arg_func_iter = rpc_handler_no_arg_funcs.find (action); + if (no_arg_func_iter != rpc_handler_no_arg_funcs.cend ()) + { + // First try the map of options with no arguments + no_arg_func_iter->second (this); + } + else + { + // Try the rest of the options + if (action == "chain") + { + chain (); + } + else if (action == "successors") + { + chain (true); + } + else if (action == "history") + { + request.put ("head", request.get ("hash")); + account_history (); + } + else if (action == "knano_from_raw" || action == "krai_from_raw") + { + mnano_from_raw (nano::kxrb_ratio); + } + else if (action == "knano_to_raw" || action == "krai_to_raw") + { + mnano_to_raw (nano::kxrb_ratio); + } + else if (action == "nano_from_raw" || action == "rai_from_raw") + { + mnano_from_raw (nano::xrb_ratio); + } + else if (action == "nano_to_raw" || action == "rai_to_raw") + { + mnano_to_raw (nano::xrb_ratio); + } + else if (action == "mnano_from_raw" || action == "mrai_from_raw") + { + mnano_from_raw (); + } + else if (action == "mnano_to_raw" || action == "mrai_to_raw") + { + mnano_to_raw (); + } + else if (action == "password_valid") + { + password_valid (); + } + else if (action == "wallet_locked") + { + password_valid (true); + } + else + { + error_response (response, "Unknown command"); + } + } + } + } + catch (std::runtime_error const &) + { + error_response (response, "Unable to parse JSON"); + } + catch (...) + { + error_response (response, "Internal server error in RPC"); + } +} + +void nano::error_response (std::function response_a, std::string const & message_a) +{ + boost::property_tree::ptree response_l; + response_l.put ("error", message_a); + response_a (response_l); +} + +namespace +{ +rpc_handler_no_arg_func_map create_rpc_handler_no_arg_func_map () +{ + rpc_handler_no_arg_func_map no_arg_funcs; + no_arg_funcs.emplace ("account_balance", &nano::rpc_handler::account_balance); + no_arg_funcs.emplace ("account_block_count", &nano::rpc_handler::account_block_count); + no_arg_funcs.emplace ("account_count", &nano::rpc_handler::account_count); + no_arg_funcs.emplace ("account_create", &nano::rpc_handler::account_create); + no_arg_funcs.emplace ("account_get", &nano::rpc_handler::account_get); + no_arg_funcs.emplace ("account_history", &nano::rpc_handler::account_history); + no_arg_funcs.emplace ("account_info", &nano::rpc_handler::account_info); + no_arg_funcs.emplace ("account_key", &nano::rpc_handler::account_key); + no_arg_funcs.emplace ("account_list", &nano::rpc_handler::account_list); + no_arg_funcs.emplace ("account_move", &nano::rpc_handler::account_move); + no_arg_funcs.emplace ("account_remove", &nano::rpc_handler::account_remove); + no_arg_funcs.emplace ("account_representative", &nano::rpc_handler::account_representative); + no_arg_funcs.emplace ("account_representative_set", &nano::rpc_handler::account_representative_set); + no_arg_funcs.emplace ("account_weight", &nano::rpc_handler::account_weight); + no_arg_funcs.emplace ("accounts_balances", &nano::rpc_handler::accounts_balances); + no_arg_funcs.emplace ("accounts_create", &nano::rpc_handler::accounts_create); + no_arg_funcs.emplace ("accounts_frontiers", &nano::rpc_handler::accounts_frontiers); + no_arg_funcs.emplace ("accounts_pending", &nano::rpc_handler::accounts_pending); + no_arg_funcs.emplace ("available_supply", &nano::rpc_handler::available_supply); + no_arg_funcs.emplace ("block_info", &nano::rpc_handler::block_info); + no_arg_funcs.emplace ("block", &nano::rpc_handler::block_info); + no_arg_funcs.emplace ("block_confirm", &nano::rpc_handler::block_confirm); + no_arg_funcs.emplace ("blocks", &nano::rpc_handler::blocks); + no_arg_funcs.emplace ("blocks_info", &nano::rpc_handler::blocks_info); + no_arg_funcs.emplace ("block_account", &nano::rpc_handler::block_account); + no_arg_funcs.emplace ("block_count", &nano::rpc_handler::block_count); + no_arg_funcs.emplace ("block_count_type", &nano::rpc_handler::block_count_type); + no_arg_funcs.emplace ("block_create", &nano::rpc_handler::block_create); + no_arg_funcs.emplace ("block_hash", &nano::rpc_handler::block_hash); + no_arg_funcs.emplace ("bootstrap", &nano::rpc_handler::bootstrap); + no_arg_funcs.emplace ("bootstrap_any", &nano::rpc_handler::bootstrap_any); + no_arg_funcs.emplace ("bootstrap_lazy", &nano::rpc_handler::bootstrap_lazy); + no_arg_funcs.emplace ("bootstrap_status", &nano::rpc_handler::bootstrap_status); + no_arg_funcs.emplace ("delegators", &nano::rpc_handler::delegators); + no_arg_funcs.emplace ("delegators_count", &nano::rpc_handler::delegators_count); + no_arg_funcs.emplace ("deterministic_key", &nano::rpc_handler::deterministic_key); + no_arg_funcs.emplace ("confirmation_active", &nano::rpc_handler::confirmation_active); + no_arg_funcs.emplace ("confirmation_history", &nano::rpc_handler::confirmation_history); + no_arg_funcs.emplace ("confirmation_info", &nano::rpc_handler::confirmation_info); + no_arg_funcs.emplace ("confirmation_quorum", &nano::rpc_handler::confirmation_quorum); + no_arg_funcs.emplace ("frontiers", &nano::rpc_handler::frontiers); + no_arg_funcs.emplace ("frontier_count", &nano::rpc_handler::account_count); + no_arg_funcs.emplace ("keepalive", &nano::rpc_handler::keepalive); + no_arg_funcs.emplace ("key_create", &nano::rpc_handler::key_create); + no_arg_funcs.emplace ("key_expand", &nano::rpc_handler::key_expand); + no_arg_funcs.emplace ("ledger", &nano::rpc_handler::ledger); + no_arg_funcs.emplace ("node_id", &nano::rpc_handler::node_id); + no_arg_funcs.emplace ("node_id_delete", &nano::rpc_handler::node_id_delete); + no_arg_funcs.emplace ("password_change", &nano::rpc_handler::password_change); + no_arg_funcs.emplace ("password_enter", &nano::rpc_handler::password_enter); + no_arg_funcs.emplace ("wallet_unlock", &nano::rpc_handler::password_enter); + no_arg_funcs.emplace ("payment_begin", &nano::rpc_handler::payment_begin); + no_arg_funcs.emplace ("payment_init", &nano::rpc_handler::payment_init); + no_arg_funcs.emplace ("payment_end", &nano::rpc_handler::payment_end); + no_arg_funcs.emplace ("payment_wait", &nano::rpc_handler::payment_wait); + no_arg_funcs.emplace ("peers", &nano::rpc_handler::peers); + no_arg_funcs.emplace ("pending", &nano::rpc_handler::pending); + no_arg_funcs.emplace ("pending_exists", &nano::rpc_handler::pending_exists); + no_arg_funcs.emplace ("process", &nano::rpc_handler::process); + no_arg_funcs.emplace ("receive", &nano::rpc_handler::receive); + no_arg_funcs.emplace ("receive_minimum", &nano::rpc_handler::receive_minimum); + no_arg_funcs.emplace ("receive_minimum_set", &nano::rpc_handler::receive_minimum_set); + no_arg_funcs.emplace ("representatives", &nano::rpc_handler::representatives); + no_arg_funcs.emplace ("representatives_online", &nano::rpc_handler::representatives_online); + no_arg_funcs.emplace ("republish", &nano::rpc_handler::republish); + no_arg_funcs.emplace ("search_pending", &nano::rpc_handler::search_pending); + no_arg_funcs.emplace ("search_pending_all", &nano::rpc_handler::search_pending_all); + no_arg_funcs.emplace ("send", &nano::rpc_handler::send); + no_arg_funcs.emplace ("sign", &nano::rpc_handler::sign); + no_arg_funcs.emplace ("stats", &nano::rpc_handler::stats); + no_arg_funcs.emplace ("stats_clear", &nano::rpc_handler::stats_clear); + no_arg_funcs.emplace ("stop", &nano::rpc_handler::stop); + no_arg_funcs.emplace ("unchecked", &nano::rpc_handler::unchecked); + no_arg_funcs.emplace ("unchecked_clear", &nano::rpc_handler::unchecked_clear); + no_arg_funcs.emplace ("unchecked_get", &nano::rpc_handler::unchecked_get); + no_arg_funcs.emplace ("unchecked_keys", &nano::rpc_handler::unchecked_keys); + no_arg_funcs.emplace ("unopened", &nano::rpc_handler::unopened); + no_arg_funcs.emplace ("uptime", &nano::rpc_handler::uptime); + no_arg_funcs.emplace ("validate_account_number", &nano::rpc_handler::validate_account_number); + no_arg_funcs.emplace ("version", &nano::rpc_handler::version); + no_arg_funcs.emplace ("wallet_add", &nano::rpc_handler::wallet_add); + no_arg_funcs.emplace ("wallet_add_watch", &nano::rpc_handler::wallet_add_watch); + no_arg_funcs.emplace ("wallet_balances", &nano::rpc_handler::wallet_balances); + no_arg_funcs.emplace ("wallet_change_seed", &nano::rpc_handler::wallet_change_seed); + no_arg_funcs.emplace ("wallet_contains", &nano::rpc_handler::wallet_contains); + no_arg_funcs.emplace ("wallet_create", &nano::rpc_handler::wallet_create); + no_arg_funcs.emplace ("wallet_destroy", &nano::rpc_handler::wallet_destroy); + no_arg_funcs.emplace ("wallet_export", &nano::rpc_handler::wallet_export); + no_arg_funcs.emplace ("wallet_frontiers", &nano::rpc_handler::wallet_frontiers); + no_arg_funcs.emplace ("wallet_history", &nano::rpc_handler::wallet_history); + no_arg_funcs.emplace ("wallet_info", &nano::rpc_handler::wallet_info); + no_arg_funcs.emplace ("wallet_balance_total", &nano::rpc_handler::wallet_info); + no_arg_funcs.emplace ("wallet_key_valid", &nano::rpc_handler::wallet_key_valid); + no_arg_funcs.emplace ("wallet_ledger", &nano::rpc_handler::wallet_ledger); + no_arg_funcs.emplace ("wallet_lock", &nano::rpc_handler::wallet_lock); + no_arg_funcs.emplace ("wallet_pending", &nano::rpc_handler::wallet_pending); + no_arg_funcs.emplace ("wallet_representative", &nano::rpc_handler::wallet_representative); + no_arg_funcs.emplace ("wallet_representative_set", &nano::rpc_handler::wallet_representative_set); + no_arg_funcs.emplace ("wallet_republish", &nano::rpc_handler::wallet_republish); + no_arg_funcs.emplace ("wallet_work_get", &nano::rpc_handler::wallet_work_get); + no_arg_funcs.emplace ("work_generate", &nano::rpc_handler::work_generate); + no_arg_funcs.emplace ("work_cancel", &nano::rpc_handler::work_cancel); + no_arg_funcs.emplace ("work_get", &nano::rpc_handler::work_get); + no_arg_funcs.emplace ("work_set", &nano::rpc_handler::work_set); + no_arg_funcs.emplace ("work_validate", &nano::rpc_handler::work_validate); + no_arg_funcs.emplace ("work_peer_add", &nano::rpc_handler::work_peer_add); + no_arg_funcs.emplace ("work_peers", &nano::rpc_handler::work_peers); + no_arg_funcs.emplace ("work_peers_clear", &nano::rpc_handler::work_peers_clear); + return no_arg_funcs; +} + +void construct_json (nano::seq_con_info_component * component, boost::property_tree::ptree & parent) +{ + // We are a leaf node, print name and exit + if (!component->is_composite ()) + { + auto & leaf_info = static_cast (component)->get_info (); + boost::property_tree::ptree child; + child.put ("count", leaf_info.count); + child.put ("size", leaf_info.count * leaf_info.sizeof_element); + parent.add_child (leaf_info.name, child); + return; + } + + auto composite = static_cast (component); + + boost::property_tree::ptree current; + for (auto & child : composite->get_children ()) + { + construct_json (child.get (), current); + } + + parent.add_child (composite->get_name (), current); +} +} diff --git a/nano/node/rpc.hpp b/nano/rpc/rpc_handler.hpp similarity index 59% rename from nano/node/rpc.hpp rename to nano/rpc/rpc_handler.hpp index 2917d3fb..9dcb8cf1 100644 --- a/nano/node/rpc.hpp +++ b/nano/rpc/rpc_handler.hpp @@ -1,90 +1,17 @@ #pragma once -#include -#include -#include -#include #include +#include #include -#include -#include -#include -#include -#include -#include -#include +#include +#include namespace nano { -void error_response (std::function response_a, std::string const & message_a); class node; -enum class payment_status -{ - not_a_status, - unknown, - nothing, // Timeout and nothing was received - //insufficient, // Timeout and not enough was received - //over, // More than requested received - //success_fork, // Amount received but it involved a fork - success // Amount received -}; -class wallet; -class payment_observer; -class rpc -{ -public: - rpc (boost::asio::io_context &, nano::node &, nano::rpc_config const &); - virtual ~rpc () = default; +class rpc; +void error_response (std::function response_a, std::string const & message_a); - /** - * Start serving RPC requests if \p rpc_enabled_a, otherwise this will only - * add a block observer since requests may still arrive via IPC. - */ - void start (bool rpc_enabled_a = true); - void add_block_observer (); - virtual void accept (); - void stop (); - void observer_action (nano::account const &); - boost::asio::ip::tcp::acceptor acceptor; - std::mutex mutex; - std::unordered_map> payment_observers; - nano::rpc_config config; - nano::node & node; - bool on; -}; -class rpc_connection : public std::enable_shared_from_this -{ -public: - rpc_connection (nano::node &, nano::rpc &); - virtual ~rpc_connection () = default; - virtual void parse_connection (); - virtual void write_completion_handler (std::shared_ptr rpc_connection); - virtual void prepare_head (unsigned version, boost::beast::http::status status = boost::beast::http::status::ok); - virtual void write_result (std::string body, unsigned version, boost::beast::http::status status = boost::beast::http::status::ok); - void read (); - std::shared_ptr node; - nano::rpc & rpc; - boost::asio::ip::tcp::socket socket; - boost::beast::flat_buffer buffer; - boost::beast::http::response res; - std::atomic_flag responded; -}; -class payment_observer : public std::enable_shared_from_this -{ -public: - payment_observer (std::function const &, nano::rpc &, nano::account const &, nano::amount const &); - ~payment_observer (); - void start (uint64_t); - void observe (); - void complete (nano::payment_status); - std::mutex mutex; - std::condition_variable condition; - nano::rpc & rpc; - nano::account account; - nano::amount amount; - std::function response; - std::atomic_flag completed; -}; class rpc_handler : public std::enable_shared_from_this { public: @@ -222,6 +149,4 @@ public: uint64_t offset_optional_impl (uint64_t = 0); bool rpc_control_impl (); }; -/** Returns the correct RPC implementation based on TLS configuration */ -std::unique_ptr get_rpc (boost::asio::io_context & io_ctx_a, nano::node & node_a, nano::rpc_config const & config_a); } diff --git a/nano/node/rpc_secure.cpp b/nano/rpc/rpc_secure.cpp similarity index 73% rename from nano/node/rpc_secure.cpp rename to nano/rpc/rpc_secure.cpp index 4a56c0a2..151fcd4a 100644 --- a/nano/node/rpc_secure.cpp +++ b/nano/rpc/rpc_secure.cpp @@ -1,6 +1,6 @@ -#include #include -#include +#include +#include bool nano::rpc_secure::on_verify_certificate (bool preverified, boost::asio::ssl::verify_context & ctx) { @@ -114,45 +114,3 @@ void nano::rpc_secure::accept () } }); } - -nano::rpc_connection_secure::rpc_connection_secure (nano::node & node_a, nano::rpc_secure & rpc_a) : -nano::rpc_connection (node_a, rpc_a), -stream (socket, rpc_a.ssl_context) -{ -} - -void nano::rpc_connection_secure::parse_connection () -{ - // Perform the SSL handshake - auto this_l = std::static_pointer_cast (shared_from_this ()); - stream.async_handshake (boost::asio::ssl::stream_base::server, - [this_l](auto & ec) { - this_l->handle_handshake (ec); - }); -} - -void nano::rpc_connection_secure::on_shutdown (const boost::system::error_code & error) -{ - // No-op. We initiate the shutdown (since the RPC server kills the connection after each request) - // and we'll thus get an expected EOF error. If the client disconnects, a short-read error will be expected. -} - -void nano::rpc_connection_secure::handle_handshake (const boost::system::error_code & error) -{ - if (!error) - { - read (); - } - else - { - node->logger.always_log ("TLS: Handshake error: ", error.message ()); - } -} - -void nano::rpc_connection_secure::write_completion_handler (std::shared_ptr rpc) -{ - auto rpc_connection_secure = boost::polymorphic_pointer_downcast (rpc); - rpc_connection_secure->stream.async_shutdown ([rpc_connection_secure](auto const & ec_shutdown) { - rpc_connection_secure->on_shutdown (ec_shutdown); - }); -} diff --git a/nano/node/rpc_secure.hpp b/nano/rpc/rpc_secure.hpp similarity index 57% rename from nano/node/rpc_secure.hpp rename to nano/rpc/rpc_secure.hpp index c2e81e9b..820fc3af 100644 --- a/nano/node/rpc_secure.hpp +++ b/nano/rpc/rpc_secure.hpp @@ -1,7 +1,7 @@ #pragma once #include #include -#include +#include namespace nano { @@ -28,23 +28,4 @@ public: /** The context needs to be shared between sessions to make resumption work */ boost::asio::ssl::context ssl_context; }; - -/** - * Specialization of nano::rpc_connection for establishing TLS connections. - * Handshakes with client certificates are supported. - */ -class rpc_connection_secure : public rpc_connection -{ -public: - rpc_connection_secure (nano::node &, nano::rpc_secure &); - void parse_connection () override; - void write_completion_handler (std::shared_ptr rpc) override; - /** The TLS handshake callback */ - void handle_handshake (const boost::system::error_code & error); - /** The TLS async shutdown callback */ - void on_shutdown (const boost::system::error_code & error); - -private: - boost::asio::ssl::stream stream; -}; } diff --git a/nano/rpc_test/CMakeLists.txt b/nano/rpc_test/CMakeLists.txt new file mode 100644 index 00000000..b13a227d --- /dev/null +++ b/nano/rpc_test/CMakeLists.txt @@ -0,0 +1,13 @@ +add_executable (rpc_test + entry.cpp + rpc.cpp) + +target_link_libraries(rpc_test gtest gtest_main rpc) + +target_compile_definitions(rpc_test + PUBLIC + -DACTIVE_NETWORK=${ACTIVE_NETWORK} + PRIVATE + -DNANO_VERSION_MAJOR=${CPACK_PACKAGE_VERSION_MAJOR} + -DNANO_VERSION_MINOR=${CPACK_PACKAGE_VERSION_MINOR} + -DNANO_VERSION_PATCH=${CPACK_PACKAGE_VERSION_PATCH}) \ No newline at end of file diff --git a/nano/rpc_test/entry.cpp b/nano/rpc_test/entry.cpp new file mode 100644 index 00000000..d28e5051 --- /dev/null +++ b/nano/rpc_test/entry.cpp @@ -0,0 +1,13 @@ +#include + +namespace nano +{ +void force_nano_test_network (); +} + +int main (int argc, char ** argv) +{ + nano::force_nano_test_network (); + testing::InitGoogleTest (&argc, argv); + return RUN_ALL_TESTS (); +} diff --git a/nano/rpc_test/rpc.cpp b/nano/rpc_test/rpc.cpp new file mode 100644 index 00000000..238dff9c --- /dev/null +++ b/nano/rpc_test/rpc.cpp @@ -0,0 +1,5123 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +class test_response +{ +public: + test_response (boost::property_tree::ptree const & request_a, nano::rpc & rpc_a, boost::asio::io_context & io_ctx) : + request (request_a), + sock (io_ctx), + status (0) + { + sock.async_connect (nano::tcp_endpoint (boost::asio::ip::address_v6::loopback (), rpc_a.config.port), [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; + int status; +}; + +TEST (rpc, account_balance) +{ + 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", "account_balance"); + request.put ("account", nano::test_genesis_key.pub.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); + std::string balance_text (response.json.get ("balance")); + ASSERT_EQ ("340282366920938463463374607431768211455", balance_text); + std::string pending_text (response.json.get ("pending")); + ASSERT_EQ ("0", pending_text); +} + +TEST (rpc, account_block_count) +{ + 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", "account_block_count"); + request.put ("account", nano::test_genesis_key.pub.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); + std::string block_count_text (response.json.get ("block_count")); + ASSERT_EQ ("1", block_count_text); +} + +TEST (rpc, account_create) +{ + 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", "account_create"); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + test_response response0 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response0.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response0.status); + auto account_text0 (response0.json.get ("account")); + nano::uint256_union account0; + ASSERT_FALSE (account0.decode_account (account_text0)); + ASSERT_TRUE (system.wallet (0)->exists (account0)); + uint64_t max_index (std::numeric_limits::max ()); + request.put ("index", max_index); + test_response response1 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + auto account_text1 (response1.json.get ("account")); + nano::uint256_union account1; + ASSERT_FALSE (account1.decode_account (account_text1)); + ASSERT_TRUE (system.wallet (0)->exists (account1)); + request.put ("index", max_index + 1); + test_response response2 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + ASSERT_EQ (response2.json.get ("error"), "Invalid index"); +} + +TEST (rpc, account_weight) +{ + nano::keypair key; + nano::system system (24000, 1); + nano::block_hash latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto & node1 (*system.nodes[0]); + nano::change_block block (latest, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + ASSERT_EQ (nano::process_result::progress, node1.process (block).code); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "account_weight"); + request.put ("account", key.pub.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); + std::string balance_text (response.json.get ("weight")); + ASSERT_EQ ("340282366920938463463374607431768211455", balance_text); +} + +TEST (rpc, wallet_contains) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + boost::property_tree::ptree request; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "wallet_contains"); + request.put ("account", nano::test_genesis_key.pub.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); + std::string exists_text (response.json.get ("exists")); + ASSERT_EQ ("1", exists_text); +} + +TEST (rpc, wallet_doesnt_contain) +{ + 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; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "wallet_contains"); + request.put ("account", nano::test_genesis_key.pub.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); + std::string exists_text (response.json.get ("exists")); + ASSERT_EQ ("0", exists_text); +} + +TEST (rpc, validate_account_number) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + boost::property_tree::ptree request; + request.put ("action", "validate_account_number"); + request.put ("account", nano::test_genesis_key.pub.to_account ()); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + std::string exists_text (response.json.get ("valid")); + ASSERT_EQ ("1", exists_text); +} + +TEST (rpc, validate_account_invalid) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + std::string account; + nano::test_genesis_key.pub.encode_account (account); + account[0] ^= 0x1; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + boost::property_tree::ptree request; + request.put ("action", "validate_account_number"); + request.put ("account", 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); + std::string exists_text (response.json.get ("valid")); + ASSERT_EQ ("0", exists_text); +} + +TEST (rpc, send) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + boost::property_tree::ptree request; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "send"); + request.put ("source", nano::test_genesis_key.pub.to_account ()); + request.put ("destination", nano::test_genesis_key.pub.to_account ()); + request.put ("amount", "100"); + system.deadline_set (10s); + boost::thread thread2 ([&system]() { + while (system.nodes[0]->balance (nano::test_genesis_key.pub) == nano::genesis_amount) + { + ASSERT_NO_ERROR (system.poll ()); + } + }); + test_response response (request, rpc, system.io_ctx); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + std::string block_text (response.json.get ("block")); + nano::block_hash block; + ASSERT_FALSE (block.decode_hex (block_text)); + ASSERT_TRUE (system.nodes[0]->ledger.block_exists (block)); + ASSERT_EQ (system.nodes[0]->latest (nano::test_genesis_key.pub), block); + thread2.join (); +} + +TEST (rpc, send_fail) +{ + 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; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "send"); + request.put ("source", nano::test_genesis_key.pub.to_account ()); + request.put ("destination", nano::test_genesis_key.pub.to_account ()); + request.put ("amount", "100"); + std::atomic done (false); + system.deadline_set (10s); + boost::thread thread2 ([&system, &done]() { + while (!done) + { + ASSERT_NO_ERROR (system.poll ()); + } + }); + test_response response (request, rpc, system.io_ctx); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + done = true; + ASSERT_EQ (response.json.get ("error"), "Account not found in wallet"); + thread2.join (); +} + +TEST (rpc, send_work) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + boost::property_tree::ptree request; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "send"); + request.put ("source", nano::test_genesis_key.pub.to_account ()); + request.put ("destination", nano::test_genesis_key.pub.to_account ()); + request.put ("amount", "100"); + request.put ("work", "1"); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (10s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (response.json.get ("error"), "Invalid work"); + request.erase ("work"); + request.put ("work", nano::to_string_hex (system.nodes[0]->work_generate_blocking (system.nodes[0]->latest (nano::test_genesis_key.pub)))); + test_response response2 (request, rpc, system.io_ctx); + system.deadline_set (10s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + std::string block_text (response2.json.get ("block")); + nano::block_hash block; + ASSERT_FALSE (block.decode_hex (block_text)); + ASSERT_TRUE (system.nodes[0]->ledger.block_exists (block)); + ASSERT_EQ (system.nodes[0]->latest (nano::test_genesis_key.pub), block); +} + +TEST (rpc, send_idempotent) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + boost::property_tree::ptree request; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "send"); + request.put ("source", nano::test_genesis_key.pub.to_account ()); + request.put ("destination", nano::account (0).to_account ()); + request.put ("amount", (nano::genesis_amount - (nano::genesis_amount / 4)).convert_to ()); + request.put ("id", "123abc"); + 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); + std::string block_text (response.json.get ("block")); + nano::block_hash block; + ASSERT_FALSE (block.decode_hex (block_text)); + ASSERT_TRUE (system.nodes[0]->ledger.block_exists (block)); + ASSERT_EQ (system.nodes[0]->balance (nano::test_genesis_key.pub), nano::genesis_amount / 4); + test_response response2 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + ASSERT_EQ ("", response2.json.get ("error", "")); + ASSERT_EQ (block_text, response2.json.get ("block")); + ASSERT_EQ (system.nodes[0]->balance (nano::test_genesis_key.pub), nano::genesis_amount / 4); + request.erase ("id"); + request.put ("id", "456def"); + test_response response3 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response3.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response3.status); + ASSERT_EQ (response3.json.get ("error"), "Insufficient balance"); +} + +TEST (rpc, stop) +{ + 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", "stop"); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + }; +} + +TEST (rpc, wallet_add) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + nano::keypair key1; + std::string key_text; + key1.prv.data.encode_hex (key_text); + boost::property_tree::ptree request; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "wallet_add"); + request.put ("key", key_text); + 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); + std::string account_text1 (response.json.get ("account")); + ASSERT_EQ (account_text1, key1.pub.to_account ()); + ASSERT_TRUE (system.wallet (0)->exists (key1.pub)); +} + +TEST (rpc, wallet_password_valid) +{ + 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; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "password_valid"); + 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); + std::string account_text1 (response.json.get ("valid")); + ASSERT_EQ (account_text1, "1"); +} + +TEST (rpc, wallet_password_change) +{ + 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; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "password_change"); + request.put ("password", "test"); + 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); + std::string account_text1 (response.json.get ("changed")); + ASSERT_EQ (account_text1, "1"); + auto transaction (system.wallet (0)->wallets.tx_begin (true)); + ASSERT_TRUE (system.wallet (0)->store.valid_password (transaction)); + ASSERT_TRUE (system.wallet (0)->enter_password (transaction, "")); + ASSERT_FALSE (system.wallet (0)->store.valid_password (transaction)); + ASSERT_FALSE (system.wallet (0)->enter_password (transaction, "test")); + ASSERT_TRUE (system.wallet (0)->store.valid_password (transaction)); +} + +TEST (rpc, wallet_password_enter) +{ + nano::system system (24000, 1); + nano::raw_key password_l; + password_l.data.clear (); + system.deadline_set (10s); + while (password_l.data == 0) + { + ASSERT_NO_ERROR (system.poll ()); + system.wallet (0)->store.password.value (password_l); + } + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "password_enter"); + request.put ("password", ""); + 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); + std::string account_text1 (response.json.get ("valid")); + ASSERT_EQ (account_text1, "1"); +} + +TEST (rpc, wallet_representative) +{ + 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; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "wallet_representative"); + 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); + std::string account_text1 (response.json.get ("representative")); + ASSERT_EQ (account_text1, nano::genesis_account.to_account ()); +} + +TEST (rpc, wallet_representative_set) +{ + 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; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + nano::keypair key; + request.put ("action", "wallet_representative_set"); + request.put ("representative", key.pub.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 transaction (system.nodes[0]->wallets.tx_begin ()); + ASSERT_EQ (key.pub, system.nodes[0]->wallets.items.begin ()->second->store.representative (transaction)); +} + +TEST (rpc, wallet_representative_set_force) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + rpc.start (); + boost::property_tree::ptree request; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + nano::keypair key; + request.put ("action", "wallet_representative_set"); + request.put ("representative", key.pub.to_account ()); + request.put ("update_existing_accounts", true); + 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 transaction (system.nodes[0]->wallets.tx_begin ()); + ASSERT_EQ (key.pub, system.nodes[0]->wallets.items.begin ()->second->store.representative (transaction)); + } + nano::account representative (0); + while (representative != key.pub) + { + auto transaction (system.nodes[0]->store.tx_begin_read ()); + nano::account_info info; + if (!system.nodes[0]->store.account_get (transaction, nano::test_genesis_key.pub, info)) + { + auto block (system.nodes[0]->store.block_get (transaction, info.rep_block)); + assert (block != nullptr); + representative = block->representative (); + } + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (rpc, account_list) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + nano::keypair key2; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key2.prv); + boost::property_tree::ptree request; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "account_list"); + 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_node (response.json.get_child ("accounts")); + std::vector accounts; + for (auto i (accounts_node.begin ()), j (accounts_node.end ()); i != j; ++i) + { + auto account (i->second.get ("")); + nano::uint256_union number; + ASSERT_FALSE (number.decode_account (account)); + accounts.push_back (number); + } + ASSERT_EQ (2, accounts.size ()); + for (auto i (accounts.begin ()), j (accounts.end ()); i != j; ++i) + { + ASSERT_TRUE (system.wallet (0)->exists (*i)); + } +} + +TEST (rpc, wallet_key_valid) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + boost::property_tree::ptree request; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "wallet_key_valid"); + 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); + std::string exists_text (response.json.get ("valid")); + ASSERT_EQ ("1", exists_text); +} + +TEST (rpc, wallet_create) +{ + 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", "wallet_create"); + 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); + std::string wallet_text (response.json.get ("wallet")); + nano::uint256_union wallet_id; + ASSERT_FALSE (wallet_id.decode_hex (wallet_text)); + ASSERT_NE (system.nodes[0]->wallets.items.end (), system.nodes[0]->wallets.items.find (wallet_id)); +} + +TEST (rpc, wallet_create_seed) +{ + nano::system system (24000, 1); + nano::keypair seed; + nano::raw_key prv; + nano::deterministic_key (seed.pub, 0, prv.data); + auto pub (nano::pub_key (prv.data)); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "wallet_create"); + request.put ("seed", seed.pub.to_string ()); + test_response response (request, rpc, system.io_ctx); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + std::string wallet_text (response.json.get ("wallet")); + nano::uint256_union wallet_id; + ASSERT_FALSE (wallet_id.decode_hex (wallet_text)); + auto existing (system.nodes[0]->wallets.items.find (wallet_id)); + ASSERT_NE (system.nodes[0]->wallets.items.end (), existing); + { + auto transaction (system.nodes[0]->wallets.tx_begin_read ()); + nano::raw_key seed0; + existing->second->store.seed (seed0, transaction); + ASSERT_EQ (seed.pub, seed0.data); + } + auto account_text (response.json.get ("last_restored_account")); + nano::uint256_union account; + ASSERT_FALSE (account.decode_account (account_text)); + ASSERT_TRUE (existing->second->exists (account)); + ASSERT_EQ (pub, account); + ASSERT_EQ ("1", response.json.get ("restored_count")); +} + +TEST (rpc, wallet_export) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + boost::property_tree::ptree request; + request.put ("action", "wallet_export"); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + 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); + std::string wallet_json (response.json.get ("json")); + bool error (false); + auto transaction (system.nodes[0]->wallets.tx_begin (true)); + nano::kdf kdf; + nano::wallet_store store (error, kdf, transaction, nano::genesis_account, 1, "0", wallet_json); + ASSERT_FALSE (error); + ASSERT_TRUE (store.exists (transaction, nano::test_genesis_key.pub)); +} + +TEST (rpc, wallet_destroy) +{ + nano::system system (24000, 1); + auto wallet_id (system.nodes[0]->wallets.items.begin ()->first); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + boost::property_tree::ptree request; + request.put ("action", "wallet_destroy"); + request.put ("wallet", wallet_id.to_string ()); + 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); + ASSERT_EQ (system.nodes[0]->wallets.items.end (), system.nodes[0]->wallets.items.find (wallet_id)); +} + +TEST (rpc, account_move) +{ + nano::system system (24000, 1); + auto wallet_id (system.nodes[0]->wallets.items.begin ()->first); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + auto destination (system.wallet (0)); + nano::keypair key; + destination->insert_adhoc (nano::test_genesis_key.prv); + nano::keypair source_id; + auto source (system.nodes[0]->wallets.create (source_id.pub)); + source->insert_adhoc (key.prv); + boost::property_tree::ptree request; + request.put ("action", "account_move"); + request.put ("wallet", wallet_id.to_string ()); + request.put ("source", source_id.pub.to_string ()); + boost::property_tree::ptree keys; + boost::property_tree::ptree entry; + entry.put ("", key.pub.to_account ()); + keys.push_back (std::make_pair ("", entry)); + request.add_child ("accounts", keys); + 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); + ASSERT_EQ ("1", response.json.get ("moved")); + ASSERT_TRUE (destination->exists (key.pub)); + ASSERT_TRUE (destination->exists (nano::test_genesis_key.pub)); + auto transaction (system.nodes[0]->wallets.tx_begin ()); + ASSERT_EQ (source->store.end (), source->store.begin (transaction)); +} + +TEST (rpc, block) +{ + 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", "block"); + request.put ("hash", system.nodes[0]->latest (nano::genesis_account).to_string ()); + 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 contents (response.json.get ("contents")); + ASSERT_FALSE (contents.empty ()); + ASSERT_TRUE (response.json.get ("confirmed")); // Genesis block is confirmed by default +} + +TEST (rpc, block_account) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + nano::genesis genesis; + boost::property_tree::ptree request; + request.put ("action", "block_account"); + request.put ("hash", genesis.hash ().to_string ()); + 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); + std::string account_text (response.json.get ("account")); + nano::account account; + ASSERT_FALSE (account.decode_account (account_text)); +} + +TEST (rpc, chain) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::keypair key; + auto genesis (system.nodes[0]->latest (nano::test_genesis_key.pub)); + ASSERT_FALSE (genesis.is_zero ()); + auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, 1)); + ASSERT_NE (nullptr, block); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "chain"); + request.put ("block", block->hash ().to_string ()); + request.put ("count", std::to_string (std::numeric_limits::max ())); + 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 & blocks_node (response.json.get_child ("blocks")); + std::vector blocks; + for (auto i (blocks_node.begin ()), n (blocks_node.end ()); i != n; ++i) + { + blocks.push_back (nano::block_hash (i->second.get (""))); + } + ASSERT_EQ (2, blocks.size ()); + ASSERT_EQ (block->hash (), blocks[0]); + ASSERT_EQ (genesis, blocks[1]); +} + +TEST (rpc, chain_limit) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::keypair key; + auto genesis (system.nodes[0]->latest (nano::test_genesis_key.pub)); + ASSERT_FALSE (genesis.is_zero ()); + auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, 1)); + ASSERT_NE (nullptr, block); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "chain"); + request.put ("block", block->hash ().to_string ()); + 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 & blocks_node (response.json.get_child ("blocks")); + std::vector blocks; + for (auto i (blocks_node.begin ()), n (blocks_node.end ()); i != n; ++i) + { + blocks.push_back (nano::block_hash (i->second.get (""))); + } + ASSERT_EQ (1, blocks.size ()); + ASSERT_EQ (block->hash (), blocks[0]); +} + +TEST (rpc, chain_offset) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::keypair key; + auto genesis (system.nodes[0]->latest (nano::test_genesis_key.pub)); + ASSERT_FALSE (genesis.is_zero ()); + auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, 1)); + ASSERT_NE (nullptr, block); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "chain"); + request.put ("block", block->hash ().to_string ()); + request.put ("count", std::to_string (std::numeric_limits::max ())); + request.put ("offset", 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 & blocks_node (response.json.get_child ("blocks")); + std::vector blocks; + for (auto i (blocks_node.begin ()), n (blocks_node.end ()); i != n; ++i) + { + blocks.push_back (nano::block_hash (i->second.get (""))); + } + ASSERT_EQ (1, blocks.size ()); + ASSERT_EQ (genesis, blocks[0]); +} + +TEST (rpc, frontier) +{ + nano::system system (24000, 1); + std::unordered_map source; + { + auto transaction (system.nodes[0]->store.tx_begin (true)); + for (auto i (0); i < 1000; ++i) + { + nano::keypair key; + source[key.pub] = key.prv.data; + system.nodes[0]->store.account_put (transaction, key.pub, nano::account_info (key.prv.data, 0, 0, 0, 0, 0, 0, nano::epoch::epoch_0)); + } + } + nano::keypair key; + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "frontiers"); + request.put ("account", nano::account (0).to_account ()); + request.put ("count", std::to_string (std::numeric_limits::max ())); + 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 & frontiers_node (response.json.get_child ("frontiers")); + std::unordered_map frontiers; + for (auto i (frontiers_node.begin ()), j (frontiers_node.end ()); i != j; ++i) + { + nano::account account; + account.decode_account (i->first); + nano::block_hash frontier; + frontier.decode_hex (i->second.get ("")); + frontiers[account] = frontier; + } + ASSERT_EQ (1, frontiers.erase (nano::test_genesis_key.pub)); + ASSERT_EQ (source, frontiers); +} + +TEST (rpc, frontier_limited) +{ + nano::system system (24000, 1); + std::unordered_map source; + { + auto transaction (system.nodes[0]->store.tx_begin (true)); + for (auto i (0); i < 1000; ++i) + { + nano::keypair key; + source[key.pub] = key.prv.data; + system.nodes[0]->store.account_put (transaction, key.pub, nano::account_info (key.prv.data, 0, 0, 0, 0, 0, 0, nano::epoch::epoch_0)); + } + } + nano::keypair key; + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "frontiers"); + request.put ("account", nano::account (0).to_account ()); + request.put ("count", std::to_string (100)); + 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 & frontiers_node (response.json.get_child ("frontiers")); + ASSERT_EQ (100, frontiers_node.size ()); +} + +TEST (rpc, frontier_startpoint) +{ + nano::system system (24000, 1); + std::unordered_map source; + { + auto transaction (system.nodes[0]->store.tx_begin (true)); + for (auto i (0); i < 1000; ++i) + { + nano::keypair key; + source[key.pub] = key.prv.data; + system.nodes[0]->store.account_put (transaction, key.pub, nano::account_info (key.prv.data, 0, 0, 0, 0, 0, 0, nano::epoch::epoch_0)); + } + } + nano::keypair key; + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "frontiers"); + request.put ("account", source.begin ()->first.to_account ()); + request.put ("count", std::to_string (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 & frontiers_node (response.json.get_child ("frontiers")); + ASSERT_EQ (1, frontiers_node.size ()); + ASSERT_EQ (source.begin ()->first.to_account (), frontiers_node.begin ()->first); +} + +TEST (rpc, history) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto change (system.wallet (0)->change_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub)); + ASSERT_NE (nullptr, change); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, send); + auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, receive); + auto node0 (system.nodes[0]); + nano::genesis genesis; + nano::state_block usend (nano::genesis_account, node0->latest (nano::genesis_account), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + nano::state_block ureceive (nano::genesis_account, usend.hash (), nano::genesis_account, nano::genesis_amount, usend.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + nano::state_block uchange (nano::genesis_account, ureceive.hash (), nano::keypair ().pub, nano::genesis_amount, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + { + auto transaction (node0->store.tx_begin (true)); + ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction, usend).code); + ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction, ureceive).code); + ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction, uchange).code); + } + nano::rpc rpc (system.io_ctx, *node0, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "history"); + request.put ("hash", uchange.hash ().to_string ()); + request.put ("count", 100); + 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); + std::vector> history_l; + auto & history_node (response.json.get_child ("history")); + for (auto i (history_node.begin ()), n (history_node.end ()); i != n; ++i) + { + history_l.push_back (std::make_tuple (i->second.get ("type"), i->second.get ("account"), i->second.get ("amount"), i->second.get ("hash"))); + } + ASSERT_EQ (5, history_l.size ()); + ASSERT_EQ ("receive", std::get<0> (history_l[0])); + ASSERT_EQ (ureceive.hash ().to_string (), std::get<3> (history_l[0])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[0])); + ASSERT_EQ (nano::Gxrb_ratio.convert_to (), std::get<2> (history_l[0])); + ASSERT_EQ (5, history_l.size ()); + ASSERT_EQ ("send", std::get<0> (history_l[1])); + ASSERT_EQ (usend.hash ().to_string (), std::get<3> (history_l[1])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[1])); + ASSERT_EQ (nano::Gxrb_ratio.convert_to (), std::get<2> (history_l[1])); + ASSERT_EQ ("receive", std::get<0> (history_l[2])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[2])); + ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), std::get<2> (history_l[2])); + ASSERT_EQ (receive->hash ().to_string (), std::get<3> (history_l[2])); + ASSERT_EQ ("send", std::get<0> (history_l[3])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[3])); + ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), std::get<2> (history_l[3])); + ASSERT_EQ (send->hash ().to_string (), std::get<3> (history_l[3])); + ASSERT_EQ ("receive", std::get<0> (history_l[4])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[4])); + ASSERT_EQ (nano::genesis_amount.convert_to (), std::get<2> (history_l[4])); + ASSERT_EQ (genesis.hash ().to_string (), std::get<3> (history_l[4])); +} + +TEST (rpc, account_history) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto change (system.wallet (0)->change_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub)); + ASSERT_NE (nullptr, change); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, send); + auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, receive); + auto node0 (system.nodes[0]); + nano::genesis genesis; + nano::state_block usend (nano::genesis_account, node0->latest (nano::genesis_account), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::genesis_account, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + nano::state_block ureceive (nano::genesis_account, usend.hash (), nano::genesis_account, nano::genesis_amount, usend.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + nano::state_block uchange (nano::genesis_account, ureceive.hash (), nano::keypair ().pub, nano::genesis_amount, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + { + auto transaction (node0->store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction, usend).code); + ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction, ureceive).code); + ASSERT_EQ (nano::process_result::progress, node0->ledger.process (transaction, uchange).code); + } + nano::rpc rpc (system.io_ctx, *node0, nano::rpc_config (true)); + rpc.start (); + { + boost::property_tree::ptree request; + request.put ("action", "account_history"); + request.put ("account", nano::genesis_account.to_account ()); + request.put ("count", 100); + test_response response (request, rpc, system.io_ctx); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + std::vector> history_l; + auto & history_node (response.json.get_child ("history")); + for (auto i (history_node.begin ()), n (history_node.end ()); i != n; ++i) + { + history_l.push_back (std::make_tuple (i->second.get ("type"), i->second.get ("account"), i->second.get ("amount"), i->second.get ("hash"), i->second.get ("height"))); + } + + ASSERT_EQ (5, history_l.size ()); + ASSERT_EQ ("receive", std::get<0> (history_l[0])); + ASSERT_EQ (ureceive.hash ().to_string (), std::get<3> (history_l[0])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[0])); + ASSERT_EQ (nano::Gxrb_ratio.convert_to (), std::get<2> (history_l[0])); + ASSERT_EQ ("6", std::get<4> (history_l[0])); // change block (height 7) is skipped by account_history since "raw" is not set + ASSERT_EQ ("send", std::get<0> (history_l[1])); + ASSERT_EQ (usend.hash ().to_string (), std::get<3> (history_l[1])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[1])); + ASSERT_EQ (nano::Gxrb_ratio.convert_to (), std::get<2> (history_l[1])); + ASSERT_EQ ("5", std::get<4> (history_l[1])); + ASSERT_EQ ("receive", std::get<0> (history_l[2])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[2])); + ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), std::get<2> (history_l[2])); + ASSERT_EQ (receive->hash ().to_string (), std::get<3> (history_l[2])); + ASSERT_EQ ("4", std::get<4> (history_l[2])); + ASSERT_EQ ("send", std::get<0> (history_l[3])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[3])); + ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), std::get<2> (history_l[3])); + ASSERT_EQ (send->hash ().to_string (), std::get<3> (history_l[3])); + ASSERT_EQ ("3", std::get<4> (history_l[3])); + ASSERT_EQ ("receive", std::get<0> (history_l[4])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[4])); + ASSERT_EQ (nano::genesis_amount.convert_to (), std::get<2> (history_l[4])); + ASSERT_EQ (genesis.hash ().to_string (), std::get<3> (history_l[4])); + ASSERT_EQ ("1", std::get<4> (history_l[4])); // change block (height 2) is skipped + } + // Test count and reverse + { + boost::property_tree::ptree request; + request.put ("action", "account_history"); + request.put ("account", nano::genesis_account.to_account ()); + request.put ("reverse", true); + request.put ("count", 1); + test_response response (request, rpc, system.io_ctx); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto & history_node (response.json.get_child ("history")); + ASSERT_EQ (1, history_node.size ()); + ASSERT_EQ ("1", history_node.begin ()->second.get ("height")); + ASSERT_EQ (change->hash ().to_string (), response.json.get ("next")); + } + + // Test filtering + auto account2 (system.wallet (0)->deterministic_insert ()); + auto send2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, account2, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, send2); + auto receive2 (system.wallet (0)->receive_action (static_cast (*send2), account2, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, receive2); + { + boost::property_tree::ptree request; + request.put ("action", "account_history"); + request.put ("account", nano::test_genesis_key.pub.to_account ()); + boost::property_tree::ptree other_account; + other_account.put ("", account2.to_account ()); + boost::property_tree::ptree filtered_accounts; + filtered_accounts.push_back (std::make_pair ("", other_account)); + request.add_child ("account_filter", filtered_accounts); + request.put ("count", 100); + test_response response (request, rpc, system.io_ctx); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + auto history_node (response.json.get_child ("history")); + ASSERT_EQ (history_node.size (), 1); + } +} + +TEST (rpc, history_count) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto change (system.wallet (0)->change_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub)); + ASSERT_NE (nullptr, change); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, send); + auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, receive); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "history"); + request.put ("hash", receive->hash ().to_string ()); + 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 & history_node (response.json.get_child ("history")); + ASSERT_EQ (1, history_node.size ()); +} + +TEST (rpc, process_block) +{ + nano::system system (24000, 1); + nano::keypair key; + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto & node1 (*system.nodes[0]); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "process"); + std::string json; + send.serialize_json (json); + request.put ("block", json); + 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); + system.deadline_set (10s); + while (system.nodes[0]->latest (nano::test_genesis_key.pub) != send.hash ()) + { + ASSERT_NO_ERROR (system.poll ()); + } + std::string send_hash (response.json.get ("hash")); + ASSERT_EQ (send.hash ().to_string (), send_hash); +} + +TEST (rpc, process_block_no_work) +{ + nano::system system (24000, 1); + nano::keypair key; + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto & node1 (*system.nodes[0]); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + send.block_work_set (0); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "process"); + std::string json; + send.serialize_json (json); + request.put ("block", json); + 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); + ASSERT_FALSE (response.json.get ("error", "").empty ()); +} + +TEST (rpc, process_republish) +{ + nano::system system (24000, 2); + nano::keypair key; + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto & node1 (*system.nodes[0]); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "process"); + std::string json; + send.serialize_json (json); + request.put ("block", json); + 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); + system.deadline_set (10s); + while (system.nodes[1]->latest (nano::test_genesis_key.pub) != send.hash ()) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (rpc, process_subtype_send) +{ + nano::system system (24000, 2); + nano::keypair key; + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto & node1 (*system.nodes[0]); + nano::state_block send (nano::genesis_account, latest, nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "process"); + std::string json; + send.serialize_json (json); + request.put ("block", json); + request.put ("subtype", "receive"); + 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); + std::error_code ec (nano::error_rpc::invalid_subtype_balance); + ASSERT_EQ (response.json.get ("error"), ec.message ()); + request.put ("subtype", "change"); + test_response response2 (request, rpc, system.io_ctx); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + ASSERT_EQ (response2.json.get ("error"), ec.message ()); + request.put ("subtype", "send"); + test_response response3 (request, rpc, system.io_ctx); + while (response3.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response3.status); + ASSERT_EQ (send.hash ().to_string (), response3.json.get ("hash")); + system.deadline_set (10s); + while (system.nodes[1]->latest (nano::test_genesis_key.pub) != send.hash ()) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (rpc, process_subtype_open) +{ + nano::system system (24000, 2); + nano::keypair key; + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto & node1 (*system.nodes[0]); + nano::state_block send (nano::genesis_account, latest, nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + { + auto transaction (node1.store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, send).code); + } + node1.active.start (std::make_shared (send)); + nano::state_block open (key.pub, 0, key.pub, nano::Gxrb_ratio, send.hash (), key.prv, key.pub, node1.work_generate_blocking (key.pub)); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "process"); + std::string json; + open.serialize_json (json); + request.put ("block", json); + request.put ("subtype", "send"); + 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); + std::error_code ec (nano::error_rpc::invalid_subtype_balance); + ASSERT_EQ (response.json.get ("error"), ec.message ()); + request.put ("subtype", "epoch"); + test_response response2 (request, rpc, system.io_ctx); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + ASSERT_EQ (response2.json.get ("error"), ec.message ()); + request.put ("subtype", "open"); + test_response response3 (request, rpc, system.io_ctx); + while (response3.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response3.status); + ASSERT_EQ (open.hash ().to_string (), response3.json.get ("hash")); + system.deadline_set (10s); + while (system.nodes[1]->latest (key.pub) != open.hash ()) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (rpc, process_subtype_receive) +{ + nano::system system (24000, 2); + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto & node1 (*system.nodes[0]); + nano::state_block send (nano::genesis_account, latest, nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + { + auto transaction (node1.store.tx_begin_write ()); + ASSERT_EQ (nano::process_result::progress, node1.ledger.process (transaction, send).code); + } + node1.active.start (std::make_shared (send)); + nano::state_block receive (nano::test_genesis_key.pub, send.hash (), nano::test_genesis_key.pub, nano::genesis_amount, send.hash (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (send.hash ())); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "process"); + std::string json; + receive.serialize_json (json); + request.put ("block", json); + request.put ("subtype", "send"); + 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); + std::error_code ec (nano::error_rpc::invalid_subtype_balance); + ASSERT_EQ (response.json.get ("error"), ec.message ()); + request.put ("subtype", "open"); + test_response response2 (request, rpc, system.io_ctx); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + ec = nano::error_rpc::invalid_subtype_previous; + ASSERT_EQ (response2.json.get ("error"), ec.message ()); + request.put ("subtype", "receive"); + test_response response3 (request, rpc, system.io_ctx); + while (response3.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response3.status); + ASSERT_EQ (receive.hash ().to_string (), response3.json.get ("hash")); + system.deadline_set (10s); + while (system.nodes[1]->latest (nano::test_genesis_key.pub) != receive.hash ()) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (rpc, keepalive) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto node1 (std::make_shared (init1, system.io_ctx, 24001, nano::unique_path (), system.alarm, system.logging, system.work)); + node1->start (); + system.nodes.push_back (node1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "keepalive"); + auto address (boost::str (boost::format ("%1%") % node1->network.endpoint ().address ())); + auto port (boost::str (boost::format ("%1%") % node1->network.endpoint ().port ())); + request.put ("address", address); + request.put ("port", port); + ASSERT_EQ (nullptr, system.nodes[0]->network.udp_channels.channel (node1->network.endpoint ())); + ASSERT_EQ (0, system.nodes[0]->network.size ()); + 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); + system.deadline_set (10s); + while (system.nodes[0]->network.udp_channels.channel (node1->network.endpoint ()) == nullptr) + { + ASSERT_EQ (0, system.nodes[0]->network.size ()); + ASSERT_NO_ERROR (system.poll ()); + } + node1->stop (); +} + +TEST (rpc, payment_init) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto node1 (system.nodes[0]); + nano::keypair wallet_id; + auto wallet (node1->wallets.create (wallet_id.pub)); + ASSERT_TRUE (node1->wallets.items.find (wallet_id.pub) != node1->wallets.items.end ()); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "payment_init"); + request.put ("wallet", wallet_id.pub.to_string ()); + 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); + ASSERT_EQ ("Ready", response.json.get ("status")); +} + +TEST (rpc, payment_begin_end) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto node1 (system.nodes[0]); + nano::keypair wallet_id; + auto wallet (node1->wallets.create (wallet_id.pub)); + ASSERT_TRUE (node1->wallets.items.find (wallet_id.pub) != node1->wallets.items.end ()); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "payment_begin"); + request1.put ("wallet", wallet_id.pub.to_string ()); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + auto account_text (response1.json.get ("account")); + nano::uint256_union account; + ASSERT_FALSE (account.decode_account (account_text)); + ASSERT_TRUE (wallet->exists (account)); + nano::block_hash root1; + { + auto transaction (node1->store.tx_begin ()); + root1 = node1->ledger.latest_root (transaction, account); + } + uint64_t work (0); + while (!nano::work_validate (root1, work)) + { + ++work; + ASSERT_LT (work, 50); + } + system.deadline_set (10s); + while (nano::work_validate (root1, work)) + { + auto ec = system.poll (); + auto transaction (wallet->wallets.tx_begin ()); + ASSERT_FALSE (wallet->store.work_get (transaction, account, work)); + ASSERT_NO_ERROR (ec); + } + ASSERT_EQ (wallet->free_accounts.end (), wallet->free_accounts.find (account)); + boost::property_tree::ptree request2; + request2.put ("action", "payment_end"); + request2.put ("wallet", wallet_id.pub.to_string ()); + request2.put ("account", account.to_account ()); + test_response response2 (request2, rpc, system.io_ctx); + system.deadline_set (5s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + ASSERT_TRUE (wallet->exists (account)); + ASSERT_NE (wallet->free_accounts.end (), wallet->free_accounts.find (account)); + rpc.stop (); + system.stop (); +} + +TEST (rpc, payment_end_nonempty) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto node1 (system.nodes[0]); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto transaction (node1->wallets.tx_begin ()); + system.wallet (0)->init_free_accounts (transaction); + auto wallet_id (node1->wallets.items.begin ()->first); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "payment_end"); + request1.put ("wallet", wallet_id.to_string ()); + request1.put ("account", nano::test_genesis_key.pub.to_account ()); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + ASSERT_FALSE (response1.json.get ("error", "").empty ()); +} + +TEST (rpc, payment_zero_balance) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto node1 (system.nodes[0]); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto transaction (node1->wallets.tx_begin ()); + system.wallet (0)->init_free_accounts (transaction); + auto wallet_id (node1->wallets.items.begin ()->first); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "payment_begin"); + request1.put ("wallet", wallet_id.to_string ()); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + auto account_text (response1.json.get ("account")); + nano::uint256_union account; + ASSERT_FALSE (account.decode_account (account_text)); + ASSERT_NE (nano::test_genesis_key.pub, account); +} + +TEST (rpc, payment_begin_reuse) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto node1 (system.nodes[0]); + nano::keypair wallet_id; + auto wallet (node1->wallets.create (wallet_id.pub)); + ASSERT_TRUE (node1->wallets.items.find (wallet_id.pub) != node1->wallets.items.end ()); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "payment_begin"); + request1.put ("wallet", wallet_id.pub.to_string ()); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + auto account_text (response1.json.get ("account")); + nano::uint256_union account; + ASSERT_FALSE (account.decode_account (account_text)); + ASSERT_TRUE (wallet->exists (account)); + ASSERT_EQ (wallet->free_accounts.end (), wallet->free_accounts.find (account)); + boost::property_tree::ptree request2; + request2.put ("action", "payment_end"); + request2.put ("wallet", wallet_id.pub.to_string ()); + request2.put ("account", account.to_account ()); + test_response response2 (request2, rpc, system.io_ctx); + system.deadline_set (5s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + ASSERT_TRUE (wallet->exists (account)); + ASSERT_NE (wallet->free_accounts.end (), wallet->free_accounts.find (account)); + test_response response3 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response3.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response3.status); + auto account2_text (response1.json.get ("account")); + nano::uint256_union account2; + ASSERT_FALSE (account2.decode_account (account2_text)); + ASSERT_EQ (account, account2); +} + +TEST (rpc, payment_begin_locked) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto node1 (system.nodes[0]); + nano::keypair wallet_id; + auto wallet (node1->wallets.create (wallet_id.pub)); + { + auto transaction (wallet->wallets.tx_begin (true)); + wallet->store.rekey (transaction, "1"); + ASSERT_TRUE (wallet->store.attempt_password (transaction, "")); + } + ASSERT_TRUE (node1->wallets.items.find (wallet_id.pub) != node1->wallets.items.end ()); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "payment_begin"); + request1.put ("wallet", wallet_id.pub.to_string ()); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + ASSERT_FALSE (response1.json.get ("error", "").empty ()); +} + +TEST (rpc, payment_wait) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto node1 (system.nodes[0]); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "payment_wait"); + request1.put ("account", key.pub.to_account ()); + request1.put ("amount", nano::amount (nano::Mxrb_ratio).to_string_dec ()); + request1.put ("timeout", "100"); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + ASSERT_EQ ("nothing", response1.json.get ("status")); + request1.put ("timeout", "100000"); + system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Mxrb_ratio); + system.alarm.add (std::chrono::steady_clock::now () + std::chrono::milliseconds (500), [&]() { + system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Mxrb_ratio); + }); + test_response response2 (request1, rpc, system.io_ctx); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + ASSERT_EQ ("success", response2.json.get ("status")); + request1.put ("amount", nano::amount (nano::Mxrb_ratio * 2).to_string_dec ()); + test_response response3 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response3.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response3.status); + ASSERT_EQ ("success", response2.json.get ("status")); +} + +TEST (rpc, peers) +{ + nano::system system (24000, 2); + nano::endpoint endpoint (boost::asio::ip::address_v6::from_string ("fc00::1"), 4000); + system.nodes[0]->network.udp_channels.insert (endpoint, nano::protocol_version); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "peers"); + 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 & peers_node (response.json.get_child ("peers")); + ASSERT_EQ (2, peers_node.size ()); + ASSERT_EQ (std::to_string (nano::protocol_version), peers_node.get ("UDP: [::1]:24001")); + // Previously "[::ffff:80.80.80.80]:4000", but IPv4 address cause "No such node thrown in the test body" issue with peers_node.get + std::stringstream endpoint_text; + endpoint_text << endpoint; + ASSERT_EQ (std::to_string (nano::protocol_version), peers_node.get ("UDP: " + endpoint_text.str ())); +} + +TEST (rpc, peers_node_id) +{ + nano::system system (24000, 2); + nano::endpoint endpoint (boost::asio::ip::address_v6::from_string ("fc00::1"), 4000); + system.nodes[0]->network.udp_channels.insert (endpoint, nano::protocol_version); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "peers"); + request.put ("peer_details", true); + 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 & peers_node (response.json.get_child ("peers")); + ASSERT_EQ (2, peers_node.size ()); + auto tree1 (peers_node.get_child ("UDP: [::1]:24001")); + ASSERT_EQ (std::to_string (nano::protocol_version), tree1.get ("protocol_version")); + ASSERT_EQ (system.nodes[1]->node_id.pub.to_account (), tree1.get ("node_id")); + std::stringstream endpoint_text; + endpoint_text << endpoint; + auto tree2 (peers_node.get_child ("UDP: " + endpoint_text.str ())); + ASSERT_EQ (std::to_string (nano::protocol_version), tree2.get ("protocol_version")); + ASSERT_EQ ("", tree2.get ("node_id")); +} + +TEST (rpc, pending) +{ + nano::system system (24000, 1); + nano::keypair key1; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto block1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key1.pub, 100)); + system.deadline_set (5s); + while (system.nodes[0]->active.active (*block1)) + { + ASSERT_NO_ERROR (system.poll ()); + } + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "pending"); + request.put ("account", key1.pub.to_account ()); + request.put ("count", "100"); + { + 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 & blocks_node (response.json.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 + { + 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 & blocks_node (response.json.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 + { + 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 & blocks_node (response.json.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"); + { + test_response response (request, rpc, system.io_ctx); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response.status); + auto & blocks_node (response.json.get_child ("blocks")); + ASSERT_EQ (0, blocks_node.size ()); + } + request.put ("threshold", "0"); + request.put ("source", "true"); + request.put ("min_version", "true"); + { + 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 & blocks_node (response.json.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::test_genesis_key.pub); + } +} + +TEST (rpc_config, serialization) +{ + nano::rpc_config config1; + config1.address = boost::asio::ip::address_v6::any (); + config1.port = 10; + config1.enable_control = true; + nano::jsonconfig tree; + config1.serialize_json (tree); + nano::rpc_config config2; + ASSERT_NE (config2.address, config1.address); + ASSERT_NE (config2.port, config1.port); + ASSERT_NE (config2.enable_control, config1.enable_control); + bool upgraded{ false }; + config2.deserialize_json (upgraded, tree); + ASSERT_EQ (config2.address, config1.address); + ASSERT_EQ (config2.port, config1.port); + ASSERT_EQ (config2.enable_control, config1.enable_control); +} + +TEST (rpc, search_pending) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto wallet (system.nodes[0]->wallets.items.begin ()->first.to_string ()); + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::send_block block (latest, nano::test_genesis_key.pub, nano::genesis_amount - system.nodes[0]->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.nodes[0]->work_generate_blocking (latest)); + { + auto transaction (system.nodes[0]->store.tx_begin (true)); + ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, block).code); + } + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "search_pending"); + request.put ("wallet", wallet); + 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); + system.deadline_set (10s); + while (system.nodes[0]->balance (nano::test_genesis_key.pub) != nano::genesis_amount) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (rpc, version) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto node1 (system.nodes[0]); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "version"); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + ASSERT_EQ ("1", response1.json.get ("rpc_version")); + ASSERT_EQ (200, response1.status); + { + auto transaction (system.nodes[0]->store.tx_begin ()); + ASSERT_EQ (std::to_string (node1->store.version_get (transaction)), response1.json.get ("store_version")); + } + ASSERT_EQ (std::to_string (nano::protocol_version), response1.json.get ("protocol_version")); + if (NANO_VERSION_PATCH == 0) + { + ASSERT_EQ (boost::str (boost::format ("Nano %1%") % NANO_MAJOR_MINOR_VERSION), response1.json.get ("node_vendor")); + } + else + { + ASSERT_EQ (boost::str (boost::format ("Nano %1%") % NANO_MAJOR_MINOR_RC_VERSION), response1.json.get ("node_vendor")); + } + auto headers (response1.resp.base ()); + auto allow (headers.at ("Allow")); + auto content_type (headers.at ("Content-Type")); + auto access_control_allow_origin (headers.at ("Access-Control-Allow-Origin")); + auto access_control_allow_methods (headers.at ("Access-Control-Allow-Methods")); + auto access_control_allow_headers (headers.at ("Access-Control-Allow-Headers")); + auto connection (headers.at ("Connection")); + ASSERT_EQ ("POST, OPTIONS", allow); + ASSERT_EQ ("application/json", content_type); + ASSERT_EQ ("*", access_control_allow_origin); + ASSERT_EQ (allow, access_control_allow_methods); + ASSERT_EQ ("Accept, Accept-Language, Content-Language, Content-Type", access_control_allow_headers); + ASSERT_EQ ("close", connection); +} + +TEST (rpc, work_generate) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto node1 (system.nodes[0]); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + nano::block_hash hash1 (1); + boost::property_tree::ptree request1; + request1.put ("action", "work_generate"); + request1.put ("hash", hash1.to_string ()); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + auto work1 (response1.json.get ("work")); + uint64_t work2; + ASSERT_FALSE (nano::from_string_hex (work1, work2)); + ASSERT_FALSE (nano::work_validate (hash1, work2)); +} + +TEST (rpc, work_generate_difficulty) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto node1 (system.nodes[0]); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + nano::block_hash hash1 (1); + uint64_t difficulty1 (0xfff0000000000000); + boost::property_tree::ptree request1; + request1.put ("action", "work_generate"); + request1.put ("hash", hash1.to_string ()); + request1.put ("difficulty", nano::to_string_hex (difficulty1)); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (10s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + auto work_text1 (response1.json.get ("work")); + uint64_t work1; + ASSERT_FALSE (nano::from_string_hex (work_text1, work1)); + uint64_t result_difficulty1; + ASSERT_FALSE (nano::work_validate (hash1, work1, &result_difficulty1)); + ASSERT_GE (result_difficulty1, difficulty1); + uint64_t difficulty2 (0xffff000000000000); + request1.put ("difficulty", nano::to_string_hex (difficulty2)); + test_response response2 (request1, rpc, system.io_ctx); + system.deadline_set (20s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + auto work_text2 (response2.json.get ("work")); + uint64_t work2; + ASSERT_FALSE (nano::from_string_hex (work_text2, work2)); + uint64_t result_difficulty2; + ASSERT_FALSE (nano::work_validate (hash1, work2, &result_difficulty2)); + ASSERT_GE (result_difficulty2, difficulty2); + uint64_t difficulty3 (rpc.config.max_work_generate_difficulty + 1); + request1.put ("difficulty", nano::to_string_hex (difficulty3)); + test_response response3 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response3.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response3.status); + std::error_code ec (nano::error_rpc::difficulty_limit); + ASSERT_EQ (response3.json.get ("error"), ec.message ()); +} + +TEST (rpc, work_cancel) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + nano::block_hash hash1 (1); + boost::property_tree::ptree request1; + request1.put ("action", "work_cancel"); + request1.put ("hash", hash1.to_string ()); + std::atomic done (false); + system.deadline_set (10s); + while (!done) + { + system.work.generate (hash1, [&done](boost::optional work_a) { + done = !work_a; + }); + test_response response1 (request1, rpc, system.io_ctx); + std::error_code ec; + while (response1.status == 0) + { + ec = system.poll (); + } + ASSERT_EQ (200, response1.status); + ASSERT_NO_ERROR (ec); + } +} + +TEST (rpc, work_peer_bad) +{ + nano::system system (24000, 2); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + node2.config.work_peers.push_back (std::make_pair (boost::asio::ip::address_v6::any ().to_string (), 0)); + nano::block_hash hash1 (1); + std::atomic work (0); + node2.work_generate (hash1, [&work](uint64_t work_a) { + work = work_a; + }); + system.deadline_set (5s); + while (nano::work_validate (hash1, work)) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (rpc, work_peer_one) +{ + nano::system system (24000, 2); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + auto & node2 (*system.nodes[1]); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + node2.config.work_peers.push_back (std::make_pair (node1.network.endpoint ().address ().to_string (), rpc.config.port)); + nano::keypair key1; + uint64_t work (0); + node2.work_generate (key1.pub, [&work](uint64_t work_a) { + work = work_a; + }); + system.deadline_set (5s); + while (nano::work_validate (key1.pub, work)) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (rpc, work_peer_many) +{ + nano::system system1 (24000, 1); + nano::system system2 (24001, 1); + nano::system system3 (24002, 1); + nano::system system4 (24003, 1); + nano::node_init init1; + auto & node1 (*system1.nodes[0]); + auto & node2 (*system2.nodes[0]); + auto & node3 (*system3.nodes[0]); + auto & node4 (*system4.nodes[0]); + nano::keypair key; + nano::rpc_config config2 (true); + config2.port += 0; + nano::rpc rpc2 (system2.io_ctx, node2, config2); + rpc2.start (); + nano::rpc_config config3 (true); + config3.port += 1; + nano::rpc rpc3 (system3.io_ctx, node3, config3); + rpc3.start (); + nano::rpc_config config4 (true); + config4.port += 2; + nano::rpc rpc4 (system4.io_ctx, node4, config4); + rpc4.start (); + node1.config.work_peers.push_back (std::make_pair (node2.network.endpoint ().address ().to_string (), rpc2.config.port)); + node1.config.work_peers.push_back (std::make_pair (node3.network.endpoint ().address ().to_string (), rpc3.config.port)); + node1.config.work_peers.push_back (std::make_pair (node4.network.endpoint ().address ().to_string (), rpc4.config.port)); + for (auto i (0); i < 10; ++i) + { + nano::keypair key1; + uint64_t work (0); + node1.work_generate (key1.pub, [&work](uint64_t work_a) { + work = work_a; + }); + while (nano::work_validate (key1.pub, work)) + { + system1.poll (); + system2.poll (); + system3.poll (); + system4.poll (); + } + } +} + +TEST (rpc, block_count) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "block_count"); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + ASSERT_EQ ("1", response1.json.get ("count")); + ASSERT_EQ ("0", response1.json.get ("unchecked")); +} + +TEST (rpc, frontier_count) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "frontier_count"); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + ASSERT_EQ ("1", response1.json.get ("count")); +} + +TEST (rpc, account_count) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "account_count"); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + ASSERT_EQ ("1", response1.json.get ("count")); +} + +TEST (rpc, available_supply) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "available_supply"); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + ASSERT_EQ ("0", response1.json.get ("available")); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::keypair key; + auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, 1)); + test_response response2 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + ASSERT_EQ ("1", response2.json.get ("available")); + auto block2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, 0, 100)); // Sending to burning 0 account + test_response response3 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response3.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response3.status); + ASSERT_EQ ("1", response3.json.get ("available")); +} + +TEST (rpc, mrai_to_raw) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "mrai_to_raw"); + request1.put ("amount", "1"); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + ASSERT_EQ (nano::Mxrb_ratio.convert_to (), response1.json.get ("amount")); +} + +TEST (rpc, mrai_from_raw) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "mrai_from_raw"); + request1.put ("amount", nano::Mxrb_ratio.convert_to ()); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + ASSERT_EQ ("1", response1.json.get ("amount")); +} + +TEST (rpc, krai_to_raw) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "krai_to_raw"); + request1.put ("amount", "1"); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + ASSERT_EQ (nano::kxrb_ratio.convert_to (), response1.json.get ("amount")); +} + +TEST (rpc, krai_from_raw) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "krai_from_raw"); + request1.put ("amount", nano::kxrb_ratio.convert_to ()); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + ASSERT_EQ ("1", response1.json.get ("amount")); +} + +TEST (rpc, nano_to_raw) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "nano_to_raw"); + request1.put ("amount", "1"); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + ASSERT_EQ (nano::xrb_ratio.convert_to (), response1.json.get ("amount")); +} + +TEST (rpc, nano_from_raw) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request1; + request1.put ("action", "nano_from_raw"); + request1.put ("amount", nano::xrb_ratio.convert_to ()); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + ASSERT_EQ ("1", response1.json.get ("amount")); +} + +TEST (rpc, account_representative) +{ + 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; + std::string wallet; + request.put ("account", nano::genesis_account.to_account ()); + request.put ("action", "account_representative"); + 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); + std::string account_text1 (response.json.get ("representative")); + ASSERT_EQ (account_text1, nano::genesis_account.to_account ()); +} + +TEST (rpc, account_representative_set) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + nano::keypair rep; + request.put ("account", nano::genesis_account.to_account ()); + request.put ("representative", rep.pub.to_account ()); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("action", "account_representative_set"); + 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); + std::string block_text1 (response.json.get ("block")); + nano::block_hash hash; + ASSERT_FALSE (hash.decode_hex (block_text1)); + ASSERT_FALSE (hash.is_zero ()); + auto transaction (system.nodes[0]->store.tx_begin ()); + ASSERT_TRUE (system.nodes[0]->store.block_exists (transaction, hash)); + ASSERT_EQ (rep.pub, system.nodes[0]->store.block_get (transaction, hash)->representative ()); +} + +TEST (rpc, bootstrap) +{ + nano::system system0 (24000, 1); + nano::system system1 (24001, 1); + auto latest (system1.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::send_block send (latest, nano::genesis_account, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system1.nodes[0]->work_generate_blocking (latest)); + { + auto transaction (system1.nodes[0]->store.tx_begin (true)); + ASSERT_EQ (nano::process_result::progress, system1.nodes[0]->ledger.process (transaction, send).code); + } + nano::rpc rpc (system0.io_ctx, *system0.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "bootstrap"); + request.put ("address", "::ffff:127.0.0.1"); + request.put ("port", system1.nodes[0]->network.endpoint ().port ()); + test_response response (request, rpc, system0.io_ctx); + while (response.status == 0) + { + system0.poll (); + } + system1.deadline_set (10s); + while (system0.nodes[0]->latest (nano::genesis_account) != system1.nodes[0]->latest (nano::genesis_account)) + { + ASSERT_NO_ERROR (system0.poll ()); + ASSERT_NO_ERROR (system1.poll ()); + } +} + +TEST (rpc, account_remove) +{ + nano::system system0 (24000, 1); + auto key1 (system0.wallet (0)->deterministic_insert ()); + ASSERT_TRUE (system0.wallet (0)->exists (key1)); + nano::rpc rpc (system0.io_ctx, *system0.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "account_remove"); + request.put ("wallet", system0.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("account", key1.to_account ()); + test_response response (request, rpc, system0.io_ctx); + while (response.status == 0) + { + system0.poll (); + } + ASSERT_FALSE (system0.wallet (0)->exists (key1)); +} + +TEST (rpc, representatives) +{ + nano::system system0 (24000, 1); + nano::rpc rpc (system0.io_ctx, *system0.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "representatives"); + test_response response (request, rpc, system0.io_ctx); + while (response.status == 0) + { + system0.poll (); + } + ASSERT_EQ (200, response.status); + auto & representatives_node (response.json.get_child ("representatives")); + std::vector representatives; + for (auto i (representatives_node.begin ()), n (representatives_node.end ()); i != n; ++i) + { + nano::account account; + ASSERT_FALSE (account.decode_account (i->first)); + representatives.push_back (account); + } + ASSERT_EQ (1, representatives.size ()); + ASSERT_EQ (nano::genesis_account, representatives[0]); +} + +TEST (rpc, wallet_change_seed) +{ + nano::system system0 (24000, 1); + nano::keypair seed; + { + auto transaction (system0.nodes[0]->wallets.tx_begin ()); + nano::raw_key seed0; + system0.wallet (0)->store.seed (seed0, transaction); + ASSERT_NE (seed.pub, seed0.data); + } + nano::raw_key prv; + nano::deterministic_key (seed.pub, 0, prv.data); + auto pub (nano::pub_key (prv.data)); + nano::rpc rpc (system0.io_ctx, *system0.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "wallet_change_seed"); + request.put ("wallet", system0.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("seed", seed.pub.to_string ()); + test_response response (request, rpc, system0.io_ctx); + system0.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system0.poll ()); + } + ASSERT_EQ (200, response.status); + { + auto transaction (system0.nodes[0]->wallets.tx_begin ()); + nano::raw_key seed0; + system0.wallet (0)->store.seed (seed0, transaction); + ASSERT_EQ (seed.pub, seed0.data); + } + auto account_text (response.json.get ("last_restored_account")); + nano::uint256_union account; + ASSERT_FALSE (account.decode_account (account_text)); + ASSERT_TRUE (system0.wallet (0)->exists (account)); + ASSERT_EQ (pub, account); + ASSERT_EQ ("1", response.json.get ("restored_count")); +} + +TEST (rpc, wallet_frontiers) +{ + nano::system system0 (24000, 1); + system0.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::rpc rpc (system0.io_ctx, *system0.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "wallet_frontiers"); + request.put ("wallet", system0.nodes[0]->wallets.items.begin ()->first.to_string ()); + test_response response (request, rpc, system0.io_ctx); + while (response.status == 0) + { + system0.poll (); + } + ASSERT_EQ (200, response.status); + auto & frontiers_node (response.json.get_child ("frontiers")); + std::vector frontiers; + for (auto i (frontiers_node.begin ()), n (frontiers_node.end ()); i != n; ++i) + { + frontiers.push_back (nano::block_hash (i->second.get (""))); + } + ASSERT_EQ (1, frontiers.size ()); + ASSERT_EQ (system0.nodes[0]->latest (nano::genesis_account), frontiers[0]); +} + +TEST (rpc, work_validate) +{ + nano::network_constants constants; + nano::system system (24000, 1); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + nano::block_hash hash (1); + uint64_t work1 (node1.work_generate_blocking (hash)); + boost::property_tree::ptree request; + request.put ("action", "work_validate"); + request.put ("hash", hash.to_string ()); + request.put ("work", nano::to_string_hex (work1)); + test_response response1 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + std::string validate_text1 (response1.json.get ("valid")); + ASSERT_EQ ("1", validate_text1); + uint64_t work2 (0); + request.put ("work", nano::to_string_hex (work2)); + test_response response2 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + std::string validate_text2 (response2.json.get ("valid")); + ASSERT_EQ ("0", validate_text2); + uint64_t result_difficulty; + ASSERT_FALSE (nano::work_validate (hash, work1, &result_difficulty)); + ASSERT_GE (result_difficulty, constants.publish_threshold); + request.put ("work", nano::to_string_hex (work1)); + request.put ("difficulty", nano::to_string_hex (result_difficulty)); + test_response response3 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response3.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response3.status); + bool validate3 (response3.json.get ("valid")); + ASSERT_TRUE (validate3); + uint64_t difficulty4 (0xfff0000000000000); + request.put ("work", nano::to_string_hex (work1)); + request.put ("difficulty", nano::to_string_hex (difficulty4)); + test_response response4 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response4.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response4.status); + bool validate4 (response4.json.get ("valid")); + ASSERT_EQ (result_difficulty >= difficulty4, validate4); + uint64_t work3 (node1.work_generate_blocking (hash, difficulty4)); + request.put ("work", nano::to_string_hex (work3)); + test_response response5 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response5.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response5.status); + bool validate5 (response5.json.get ("valid")); + ASSERT_TRUE (validate5); +} + +TEST (rpc, successors) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::keypair key; + auto genesis (system.nodes[0]->latest (nano::test_genesis_key.pub)); + ASSERT_FALSE (genesis.is_zero ()); + auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, 1)); + ASSERT_NE (nullptr, block); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "successors"); + request.put ("block", genesis.to_string ()); + request.put ("count", std::to_string (std::numeric_limits::max ())); + 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 & blocks_node (response.json.get_child ("blocks")); + std::vector blocks; + for (auto i (blocks_node.begin ()), n (blocks_node.end ()); i != n; ++i) + { + blocks.push_back (nano::block_hash (i->second.get (""))); + } + ASSERT_EQ (2, blocks.size ()); + ASSERT_EQ (genesis, blocks[0]); + ASSERT_EQ (block->hash (), blocks[1]); + // RPC chain "reverse" option + request.put ("action", "chain"); + request.put ("reverse", "true"); + test_response response2 (request, rpc, system.io_ctx); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + ASSERT_EQ (response.json, response2.json); +} + +TEST (rpc, bootstrap_any) +{ + nano::system system0 (24000, 1); + nano::system system1 (24001, 1); + auto latest (system1.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::send_block send (latest, nano::genesis_account, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system1.nodes[0]->work_generate_blocking (latest)); + { + auto transaction (system1.nodes[0]->store.tx_begin (true)); + ASSERT_EQ (nano::process_result::progress, system1.nodes[0]->ledger.process (transaction, send).code); + } + nano::rpc rpc (system0.io_ctx, *system0.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "bootstrap_any"); + test_response response (request, rpc, system0.io_ctx); + while (response.status == 0) + { + system0.poll (); + } + std::string success (response.json.get ("success")); + ASSERT_TRUE (success.empty ()); +} + +TEST (rpc, republish) +{ + nano::system system (24000, 2); + nano::keypair key; + nano::genesis genesis; + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto & node1 (*system.nodes[0]); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + system.nodes[0]->process (send); + nano::open_block open (send.hash (), key.pub, key.pub, key.prv, key.pub, node1.work_generate_blocking (key.pub)); + ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (open).code); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "republish"); + request.put ("hash", send.hash ().to_string ()); + 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); + system.deadline_set (10s); + while (system.nodes[1]->balance (nano::test_genesis_key.pub) == nano::genesis_amount) + { + ASSERT_NO_ERROR (system.poll ()); + } + auto & blocks_node (response.json.get_child ("blocks")); + std::vector blocks; + for (auto i (blocks_node.begin ()), n (blocks_node.end ()); i != n; ++i) + { + blocks.push_back (nano::block_hash (i->second.get (""))); + } + ASSERT_EQ (1, blocks.size ()); + ASSERT_EQ (send.hash (), blocks[0]); + + request.put ("hash", genesis.hash ().to_string ()); + request.put ("count", 1); + test_response response1 (request, rpc, system.io_ctx); + system.deadline_set (5s); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + blocks_node = response1.json.get_child ("blocks"); + blocks.clear (); + for (auto i (blocks_node.begin ()), n (blocks_node.end ()); i != n; ++i) + { + blocks.push_back (nano::block_hash (i->second.get (""))); + } + ASSERT_EQ (1, blocks.size ()); + ASSERT_EQ (genesis.hash (), blocks[0]); + + request.put ("hash", open.hash ().to_string ()); + request.put ("sources", 2); + test_response response2 (request, rpc, system.io_ctx); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + blocks_node = response2.json.get_child ("blocks"); + blocks.clear (); + for (auto i (blocks_node.begin ()), n (blocks_node.end ()); i != n; ++i) + { + blocks.push_back (nano::block_hash (i->second.get (""))); + } + ASSERT_EQ (3, blocks.size ()); + ASSERT_EQ (genesis.hash (), blocks[0]); + ASSERT_EQ (send.hash (), blocks[1]); + ASSERT_EQ (open.hash (), blocks[2]); +} + +TEST (rpc, deterministic_key) +{ + nano::system system0 (24000, 1); + nano::raw_key seed; + { + auto transaction (system0.nodes[0]->wallets.tx_begin ()); + system0.wallet (0)->store.seed (seed, transaction); + } + nano::account account0 (system0.wallet (0)->deterministic_insert ()); + nano::account account1 (system0.wallet (0)->deterministic_insert ()); + nano::account account2 (system0.wallet (0)->deterministic_insert ()); + nano::rpc rpc (system0.io_ctx, *system0.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "deterministic_key"); + request.put ("seed", seed.data.to_string ()); + request.put ("index", "0"); + test_response response0 (request, rpc, system0.io_ctx); + while (response0.status == 0) + { + system0.poll (); + } + ASSERT_EQ (200, response0.status); + std::string validate_text (response0.json.get ("account")); + ASSERT_EQ (account0.to_account (), validate_text); + request.put ("index", "2"); + test_response response1 (request, rpc, system0.io_ctx); + while (response1.status == 0) + { + system0.poll (); + } + ASSERT_EQ (200, response1.status); + validate_text = response1.json.get ("account"); + ASSERT_NE (account1.to_account (), validate_text); + ASSERT_EQ (account2.to_account (), validate_text); +} + +TEST (rpc, accounts_balances) +{ + 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", "accounts_balances"); + boost::property_tree::ptree entry; + boost::property_tree::ptree peers_l; + entry.put ("", nano::test_genesis_key.pub.to_account ()); + peers_l.push_back (std::make_pair ("", entry)); + request.add_child ("accounts", peers_l); + 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); + for (auto & balances : response.json.get_child ("balances")) + { + std::string account_text (balances.first); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), account_text); + std::string balance_text (balances.second.get ("balance")); + ASSERT_EQ ("340282366920938463463374607431768211455", balance_text); + std::string pending_text (balances.second.get ("pending")); + ASSERT_EQ ("0", pending_text); + } +} + +TEST (rpc, accounts_frontiers) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "accounts_frontiers"); + boost::property_tree::ptree entry; + boost::property_tree::ptree peers_l; + entry.put ("", nano::test_genesis_key.pub.to_account ()); + peers_l.push_back (std::make_pair ("", entry)); + request.add_child ("accounts", peers_l); + 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); + for (auto & frontiers : response.json.get_child ("frontiers")) + { + std::string account_text (frontiers.first); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), account_text); + std::string frontier_text (frontiers.second.get ("")); + ASSERT_EQ (system.nodes[0]->latest (nano::genesis_account), frontier_text); + } +} + +TEST (rpc, accounts_pending) +{ + nano::system system (24000, 1); + nano::keypair key1; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto block1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key1.pub, 100)); + system.deadline_set (5s); + while (system.nodes[0]->active.active (*block1)) + { + ASSERT_NO_ERROR (system.poll ()); + } + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "accounts_pending"); + boost::property_tree::ptree entry; + boost::property_tree::ptree peers_l; + entry.put ("", key1.pub.to_account ()); + peers_l.push_back (std::make_pair ("", entry)); + request.add_child ("accounts", peers_l); + request.put ("count", "100"); + { + 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); + for (auto & blocks : response.json.get_child ("blocks")) + { + std::string account_text (blocks.first); + ASSERT_EQ (key1.pub.to_account (), account_text); + nano::block_hash hash1 (blocks.second.begin ()->second.get ("")); + ASSERT_EQ (block1->hash (), hash1); + } + } + request.put ("sorting", "true"); // Sorting test + { + 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); + for (auto & blocks : response.json.get_child ("blocks")) + { + std::string account_text (blocks.first); + ASSERT_EQ (key1.pub.to_account (), account_text); + nano::block_hash hash1 (blocks.second.begin ()->first); + ASSERT_EQ (block1->hash (), hash1); + std::string amount (blocks.second.begin ()->second.get ("")); + ASSERT_EQ ("100", amount); + } + } + request.put ("threshold", "100"); // Threshold test + { + 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); + std::unordered_map blocks; + for (auto & pending : response.json.get_child ("blocks")) + { + std::string account_text (pending.first); + ASSERT_EQ (key1.pub.to_account (), account_text); + for (auto i (pending.second.begin ()), j (pending.second.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 ()); + } + } + ASSERT_EQ (blocks[block1->hash ()], 100); + } + request.put ("source", "true"); + { + 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); + std::unordered_map amounts; + std::unordered_map sources; + for (auto & pending : response.json.get_child ("blocks")) + { + std::string account_text (pending.first); + ASSERT_EQ (key1.pub.to_account (), account_text); + for (auto i (pending.second.begin ()), j (pending.second.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 (amounts[block1->hash ()], 100); + ASSERT_EQ (sources[block1->hash ()], nano::test_genesis_key.pub); + } +} + +TEST (rpc, blocks) +{ + 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", "blocks"); + boost::property_tree::ptree entry; + boost::property_tree::ptree peers_l; + entry.put ("", system.nodes[0]->latest (nano::genesis_account).to_string ()); + peers_l.push_back (std::make_pair ("", entry)); + request.add_child ("hashes", peers_l); + 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); + for (auto & blocks : response.json.get_child ("blocks")) + { + std::string hash_text (blocks.first); + ASSERT_EQ (system.nodes[0]->latest (nano::genesis_account).to_string (), hash_text); + std::string blocks_text (blocks.second.get ("")); + ASSERT_FALSE (blocks_text.empty ()); + } +} + +TEST (rpc, wallet_info) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::keypair key; + system.wallet (0)->insert_adhoc (key.prv); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, 1)); + nano::account account (system.wallet (0)->deterministic_insert ()); + { + auto transaction (system.nodes[0]->wallets.tx_begin (true)); + system.wallet (0)->store.erase (transaction, account); + } + account = system.wallet (0)->deterministic_insert (); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "wallet_info"); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + 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); + std::string balance_text (response.json.get ("balance")); + ASSERT_EQ ("340282366920938463463374607431768211454", balance_text); + std::string pending_text (response.json.get ("pending")); + ASSERT_EQ ("1", pending_text); + std::string count_text (response.json.get ("accounts_count")); + ASSERT_EQ ("3", count_text); + std::string adhoc_count (response.json.get ("adhoc_count")); + ASSERT_EQ ("2", adhoc_count); + std::string deterministic_count (response.json.get ("deterministic_count")); + ASSERT_EQ ("1", deterministic_count); + std::string index_text (response.json.get ("deterministic_index")); + ASSERT_EQ ("2", index_text); +} + +TEST (rpc, wallet_balances) +{ + nano::system system0 (24000, 1); + system0.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::rpc rpc (system0.io_ctx, *system0.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "wallet_balances"); + request.put ("wallet", system0.nodes[0]->wallets.items.begin ()->first.to_string ()); + test_response response (request, rpc, system0.io_ctx); + while (response.status == 0) + { + system0.poll (); + } + ASSERT_EQ (200, response.status); + for (auto & balances : response.json.get_child ("balances")) + { + std::string account_text (balances.first); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), account_text); + std::string balance_text (balances.second.get ("balance")); + ASSERT_EQ ("340282366920938463463374607431768211455", balance_text); + std::string pending_text (balances.second.get ("pending")); + ASSERT_EQ ("0", pending_text); + } + nano::keypair key; + system0.wallet (0)->insert_adhoc (key.prv); + auto send (system0.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, 1)); + request.put ("threshold", "2"); + test_response response1 (request, rpc, system0.io_ctx); + while (response1.status == 0) + { + system0.poll (); + } + ASSERT_EQ (200, response1.status); + for (auto & balances : response1.json.get_child ("balances")) + { + std::string account_text (balances.first); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), account_text); + std::string balance_text (balances.second.get ("balance")); + ASSERT_EQ ("340282366920938463463374607431768211454", balance_text); + std::string pending_text (balances.second.get ("pending")); + ASSERT_EQ ("0", pending_text); + } +} + +TEST (rpc, pending_exists) +{ + nano::system system (24000, 1); + nano::keypair key1; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto hash0 (system.nodes[0]->latest (nano::genesis_account)); + auto block1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key1.pub, 100)); + system.deadline_set (5s); + while (system.nodes[0]->active.active (*block1)) + { + ASSERT_NO_ERROR (system.poll ()); + } + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "pending_exists"); + request.put ("hash", hash0.to_string ()); + test_response response0 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response0.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response0.status); + std::string exists_text (response0.json.get ("exists")); + ASSERT_EQ ("0", exists_text); + request.put ("hash", block1->hash ().to_string ()); + test_response response1 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + std::string exists_text1 (response1.json.get ("exists")); + ASSERT_EQ ("1", exists_text1); +} + +TEST (rpc, wallet_pending) +{ + nano::system system0 (24000, 1); + nano::keypair key1; + system0.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system0.wallet (0)->insert_adhoc (key1.prv); + auto block1 (system0.wallet (0)->send_action (nano::test_genesis_key.pub, key1.pub, 100)); + auto iterations (0); + while (system0.nodes[0]->active.active (*block1)) + { + system0.poll (); + ++iterations; + ASSERT_LT (iterations, 200); + } + nano::rpc rpc (system0.io_ctx, *system0.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "wallet_pending"); + request.put ("wallet", system0.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("count", "100"); + test_response response (request, rpc, system0.io_ctx); + while (response.status == 0) + { + system0.poll (); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ (1, response.json.get_child ("blocks").size ()); + for (auto & pending : response.json.get_child ("blocks")) + { + std::string account_text (pending.first); + ASSERT_EQ (key1.pub.to_account (), account_text); + nano::block_hash hash1 (pending.second.begin ()->second.get ("")); + ASSERT_EQ (block1->hash (), hash1); + } + request.put ("threshold", "100"); // Threshold test + test_response response0 (request, rpc, system0.io_ctx); + while (response0.status == 0) + { + system0.poll (); + } + ASSERT_EQ (200, response0.status); + std::unordered_map blocks; + ASSERT_EQ (1, response0.json.get_child ("blocks").size ()); + for (auto & pending : response0.json.get_child ("blocks")) + { + std::string account_text (pending.first); + ASSERT_EQ (key1.pub.to_account (), account_text); + for (auto i (pending.second.begin ()), j (pending.second.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"); + test_response response1 (request, rpc, system0.io_ctx); + while (response1.status == 0) + { + system0.poll (); + } + ASSERT_EQ (200, response1.status); + auto & pending1 (response1.json.get_child ("blocks")); + ASSERT_EQ (0, pending1.size ()); + request.put ("threshold", "0"); + request.put ("source", "true"); + request.put ("min_version", "true"); + test_response response2 (request, rpc, system0.io_ctx); + while (response2.status == 0) + { + system0.poll (); + } + ASSERT_EQ (200, response2.status); + std::unordered_map amounts; + std::unordered_map sources; + ASSERT_EQ (1, response0.json.get_child ("blocks").size ()); + for (auto & pending : response2.json.get_child ("blocks")) + { + std::string account_text (pending.first); + ASSERT_EQ (key1.pub.to_account (), account_text); + for (auto i (pending.second.begin ()), j (pending.second.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::test_genesis_key.pub); +} + +TEST (rpc, receive_minimum) +{ + 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", "receive_minimum"); + 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); + std::string amount (response.json.get ("amount")); + ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), amount); +} + +TEST (rpc, receive_minimum_set) +{ + 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", "receive_minimum_set"); + request.put ("amount", "100"); + ASSERT_NE (system.nodes[0]->config.receive_minimum.to_string_dec (), "100"); + 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); + std::string success (response.json.get ("success")); + ASSERT_TRUE (success.empty ()); + ASSERT_EQ (system.nodes[0]->config.receive_minimum.to_string_dec (), "100"); +} + +TEST (rpc, work_get) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->work_cache_blocking (nano::test_genesis_key.pub, system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "work_get"); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("account", nano::test_genesis_key.pub.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); + std::string work_text (response.json.get ("work")); + uint64_t work (1); + auto transaction (system.nodes[0]->wallets.tx_begin ()); + system.nodes[0]->wallets.items.begin ()->second->store.work_get (transaction, nano::genesis_account, work); + ASSERT_EQ (nano::to_string_hex (work), work_text); +} + +TEST (rpc, wallet_work_get) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->work_cache_blocking (nano::test_genesis_key.pub, system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "wallet_work_get"); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + 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 transaction (system.nodes[0]->wallets.tx_begin ()); + for (auto & works : response.json.get_child ("works")) + { + std::string account_text (works.first); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), account_text); + std::string work_text (works.second.get ("")); + uint64_t work (1); + system.nodes[0]->wallets.items.begin ()->second->store.work_get (transaction, nano::genesis_account, work); + ASSERT_EQ (nano::to_string_hex (work), work_text); + } +} + +TEST (rpc, work_set) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + uint64_t work0 (100); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "work_set"); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("account", nano::test_genesis_key.pub.to_account ()); + request.put ("work", nano::to_string_hex (work0)); + 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); + std::string success (response.json.get ("success")); + ASSERT_TRUE (success.empty ()); + uint64_t work1 (1); + auto transaction (system.nodes[0]->wallets.tx_begin ()); + system.nodes[0]->wallets.items.begin ()->second->store.work_get (transaction, nano::genesis_account, work1); + ASSERT_EQ (work1, work0); +} + +TEST (rpc, search_pending_all) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::send_block block (latest, nano::test_genesis_key.pub, nano::genesis_amount - system.nodes[0]->config.receive_minimum.number (), nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.nodes[0]->work_generate_blocking (latest)); + { + auto transaction (system.nodes[0]->store.tx_begin (true)); + ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, block).code); + } + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "search_pending_all"); + 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); + system.deadline_set (10s); + while (system.nodes[0]->balance (nano::test_genesis_key.pub) != nano::genesis_amount) + { + ASSERT_NO_ERROR (system.poll ()); + } +} + +TEST (rpc, wallet_republish) +{ + nano::system system (24000, 1); + nano::genesis genesis; + nano::keypair key; + while (key.pub < nano::test_genesis_key.pub) + { + nano::keypair key1; + key.pub = key1.pub; + key.prv.data = key1.prv.data; + } + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + auto & node1 (*system.nodes[0]); + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + system.nodes[0]->process (send); + nano::open_block open (send.hash (), key.pub, key.pub, key.prv, key.pub, node1.work_generate_blocking (key.pub)); + ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (open).code); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "wallet_republish"); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + 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 & blocks_node (response.json.get_child ("blocks")); + std::vector blocks; + for (auto i (blocks_node.begin ()), n (blocks_node.end ()); i != n; ++i) + { + blocks.push_back (nano::block_hash (i->second.get (""))); + } + ASSERT_EQ (2, blocks.size ()); + ASSERT_EQ (send.hash (), blocks[0]); + ASSERT_EQ (open.hash (), blocks[1]); +} + +TEST (rpc, delegators) +{ + nano::system system (24000, 1); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + auto & node1 (*system.nodes[0]); + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + system.nodes[0]->process (send); + nano::open_block open (send.hash (), nano::test_genesis_key.pub, key.pub, key.prv, key.pub, node1.work_generate_blocking (key.pub)); + ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (open).code); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "delegators"); + request.put ("account", nano::test_genesis_key.pub.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 & delegators_node (response.json.get_child ("delegators")); + boost::property_tree::ptree delegators; + for (auto i (delegators_node.begin ()), n (delegators_node.end ()); i != n; ++i) + { + delegators.put ((i->first), (i->second.get (""))); + } + ASSERT_EQ (2, delegators.size ()); + ASSERT_EQ ("100", delegators.get (nano::test_genesis_key.pub.to_account ())); + ASSERT_EQ ("340282366920938463463374607431768211355", delegators.get (key.pub.to_account ())); +} + +TEST (rpc, delegators_count) +{ + nano::system system (24000, 1); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + auto & node1 (*system.nodes[0]); + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + system.nodes[0]->process (send); + nano::open_block open (send.hash (), nano::test_genesis_key.pub, key.pub, key.prv, key.pub, node1.work_generate_blocking (key.pub)); + ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (open).code); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "delegators_count"); + request.put ("account", nano::test_genesis_key.pub.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); + std::string count (response.json.get ("count")); + ASSERT_EQ ("2", count); +} + +TEST (rpc, account_info) +{ + nano::system system (24000, 1); + nano::keypair key; + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + auto & node1 (*system.nodes[0]); + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + system.nodes[0]->process (send); + auto time (nano::seconds_since_epoch ()); + + { + auto transaction = system.nodes[0]->store.tx_begin_write (); + nano::account_info account_info; + ASSERT_FALSE (system.nodes[0]->store.account_get (transaction, nano::test_genesis_key.pub, account_info)); + account_info.confirmation_height = 1; + system.nodes[0]->store.account_put (transaction, nano::test_genesis_key.pub, account_info); + } + + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "account_info"); + request.put ("account", nano::test_genesis_key.pub.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); + std::string frontier (response.json.get ("frontier")); + ASSERT_EQ (send.hash ().to_string (), frontier); + std::string open_block (response.json.get ("open_block")); + ASSERT_EQ (genesis.hash ().to_string (), open_block); + std::string representative_block (response.json.get ("representative_block")); + ASSERT_EQ (genesis.hash ().to_string (), representative_block); + std::string balance (response.json.get ("balance")); + ASSERT_EQ ("100", balance); + std::string modified_timestamp (response.json.get ("modified_timestamp")); + ASSERT_LT (std::abs ((long)time - stol (modified_timestamp)), 5); + std::string block_count (response.json.get ("block_count")); + ASSERT_EQ ("2", block_count); + std::string confirmation_height (response.json.get ("confirmation_height")); + ASSERT_EQ ("1", confirmation_height); + ASSERT_EQ (0, response.json.get ("account_version")); + boost::optional weight (response.json.get_optional ("weight")); + ASSERT_FALSE (weight.is_initialized ()); + boost::optional pending (response.json.get_optional ("pending")); + ASSERT_FALSE (pending.is_initialized ()); + boost::optional representative (response.json.get_optional ("representative")); + ASSERT_FALSE (representative.is_initialized ()); + // Test for optional values + request.put ("weight", "true"); + request.put ("pending", "1"); + request.put ("representative", "1"); + test_response response2 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + std::string weight2 (response2.json.get ("weight")); + ASSERT_EQ ("100", weight2); + std::string pending2 (response2.json.get ("pending")); + ASSERT_EQ ("0", pending2); + std::string representative2 (response2.json.get ("representative")); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), representative2); +} + +/** Make sure we can use json block literals instead of string as input */ +TEST (rpc, json_block_input) +{ + nano::system system (24000, 1); + nano::keypair key; + auto & node1 (*system.nodes[0]); + nano::state_block send (nano::genesis_account, node1.latest (nano::test_genesis_key.pub), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "sign"); + request.put ("json_block", "true"); + system.wallet (0)->insert_adhoc (key.prv); + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("account", key.pub.to_account ()); + boost::property_tree::ptree json; + send.serialize_json (json); + request.add_child ("block", json); + test_response response (request, rpc, system.io_ctx); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + + bool json_error{ false }; + nano::state_block block (json_error, response.json.get_child ("block")); + ASSERT_FALSE (json_error); + + ASSERT_FALSE (nano::validate_message (key.pub, send.hash (), block.block_signature ())); + ASSERT_NE (block.block_signature (), send.block_signature ()); + ASSERT_EQ (block.hash (), send.hash ()); +} + +/** Make sure we can receive json block literals instead of string as output */ +TEST (rpc, json_block_output) +{ + nano::system system (24000, 1); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + auto & node1 (*system.nodes[0]); + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + system.nodes[0]->process (send); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "block_info"); + request.put ("json_block", "true"); + request.put ("hash", send.hash ().to_string ()); + 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); + + // Make sure contents contains a valid JSON subtree instread of stringified json + bool json_error{ false }; + nano::send_block send_from_json (json_error, response.json.get_child ("contents")); + ASSERT_FALSE (json_error); +} + +TEST (rpc, blocks_info) +{ + 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", "blocks_info"); + boost::property_tree::ptree entry; + boost::property_tree::ptree peers_l; + entry.put ("", system.nodes[0]->latest (nano::genesis_account).to_string ()); + peers_l.push_back (std::make_pair ("", entry)); + request.add_child ("hashes", peers_l); + 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); + for (auto & blocks : response.json.get_child ("blocks")) + { + std::string hash_text (blocks.first); + ASSERT_EQ (system.nodes[0]->latest (nano::genesis_account).to_string (), hash_text); + std::string account_text (blocks.second.get ("block_account")); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), account_text); + std::string amount_text (blocks.second.get ("amount")); + ASSERT_EQ (nano::genesis_amount.convert_to (), amount_text); + std::string blocks_text (blocks.second.get ("contents")); + ASSERT_FALSE (blocks_text.empty ()); + boost::optional pending (blocks.second.get_optional ("pending")); + ASSERT_FALSE (pending.is_initialized ()); + boost::optional source (blocks.second.get_optional ("source_account")); + ASSERT_FALSE (source.is_initialized ()); + std::string balance_text (blocks.second.get ("balance")); + ASSERT_EQ (nano::genesis_amount.convert_to (), balance_text); + ASSERT_TRUE (blocks.second.get ("confirmed")); // Genesis block is confirmed by default + } + // Test for optional values + request.put ("source", "true"); + request.put ("pending", "1"); + test_response response2 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + for (auto & blocks : response2.json.get_child ("blocks")) + { + std::string source (blocks.second.get ("source_account")); + ASSERT_EQ ("0", source); + std::string pending (blocks.second.get ("pending")); + ASSERT_EQ ("0", pending); + } +} + +TEST (rpc, blocks_info_subtype) +{ + nano::system system (24000, 1); + auto & node1 (*system.nodes[0]); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, nano::Gxrb_ratio)); + ASSERT_NE (nullptr, send); + auto receive (system.wallet (0)->receive_action (*send, key.pub, nano::Gxrb_ratio)); + ASSERT_NE (nullptr, receive); + auto change (system.wallet (0)->change_action (nano::test_genesis_key.pub, key.pub)); + ASSERT_NE (nullptr, change); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "blocks_info"); + boost::property_tree::ptree peers_l; + boost::property_tree::ptree entry; + entry.put ("", send->hash ().to_string ()); + peers_l.push_back (std::make_pair ("", entry)); + entry.put ("", receive->hash ().to_string ()); + peers_l.push_back (std::make_pair ("", entry)); + entry.put ("", change->hash ().to_string ()); + peers_l.push_back (std::make_pair ("", entry)); + request.add_child ("hashes", peers_l); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + auto & blocks (response.json.get_child ("blocks")); + ASSERT_EQ (3, blocks.size ()); + auto send_subtype (blocks.get_child (send->hash ().to_string ()).get ("subtype")); + ASSERT_EQ (send_subtype, "send"); + auto receive_subtype (blocks.get_child (receive->hash ().to_string ()).get ("subtype")); + ASSERT_EQ (receive_subtype, "receive"); + auto change_subtype (blocks.get_child (change->hash ().to_string ()).get ("subtype")); + ASSERT_EQ (change_subtype, "change"); +} + +TEST (rpc, work_peers_all) +{ + nano::system system (24000, 1); + nano::node_init init1; + auto & node1 (*system.nodes[0]); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "work_peer_add"); + request.put ("address", "::1"); + request.put ("port", "0"); + 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); + std::string success (response.json.get ("success", "")); + ASSERT_TRUE (success.empty ()); + boost::property_tree::ptree request1; + request1.put ("action", "work_peers"); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + auto & peers_node (response1.json.get_child ("work_peers")); + std::vector peers; + for (auto i (peers_node.begin ()), n (peers_node.end ()); i != n; ++i) + { + peers.push_back (i->second.get ("")); + } + ASSERT_EQ (1, peers.size ()); + ASSERT_EQ ("::1:0", peers[0]); + boost::property_tree::ptree request2; + request2.put ("action", "work_peers_clear"); + test_response response2 (request2, rpc, system.io_ctx); + system.deadline_set (5s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + success = response2.json.get ("success", ""); + ASSERT_TRUE (success.empty ()); + test_response response3 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response3.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response3.status); + peers_node = response3.json.get_child ("work_peers"); + ASSERT_EQ (0, peers_node.size ()); +} + +TEST (rpc, block_count_type) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, send); + auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, receive); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "block_count_type"); + 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); + std::string send_count (response.json.get ("send")); + ASSERT_EQ ("0", send_count); + std::string receive_count (response.json.get ("receive")); + ASSERT_EQ ("0", receive_count); + std::string open_count (response.json.get ("open")); + ASSERT_EQ ("1", open_count); + std::string change_count (response.json.get ("change")); + ASSERT_EQ ("0", change_count); + std::string state_count (response.json.get ("state")); + ASSERT_EQ ("2", state_count); +} + +TEST (rpc, ledger) +{ + nano::system system (24000, 1); + nano::keypair key; + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + auto & node1 (*system.nodes[0]); + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + system.nodes[0]->process (send); + nano::open_block open (send.hash (), nano::test_genesis_key.pub, key.pub, key.prv, key.pub, node1.work_generate_blocking (key.pub)); + ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (open).code); + auto time (nano::seconds_since_epoch ()); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "ledger"); + request.put ("sorting", "1"); + 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 ()); + } + for (auto & accounts : response.json.get_child ("accounts")) + { + std::string account_text (accounts.first); + ASSERT_EQ (key.pub.to_account (), account_text); + std::string frontier (accounts.second.get ("frontier")); + ASSERT_EQ (open.hash ().to_string (), frontier); + std::string open_block (accounts.second.get ("open_block")); + ASSERT_EQ (open.hash ().to_string (), open_block); + std::string representative_block (accounts.second.get ("representative_block")); + ASSERT_EQ (open.hash ().to_string (), representative_block); + std::string balance_text (accounts.second.get ("balance")); + ASSERT_EQ ("340282366920938463463374607431768211355", balance_text); + std::string modified_timestamp (accounts.second.get ("modified_timestamp")); + ASSERT_LT (std::abs ((long)time - stol (modified_timestamp)), 5); + std::string block_count (accounts.second.get ("block_count")); + ASSERT_EQ ("1", block_count); + boost::optional weight (accounts.second.get_optional ("weight")); + ASSERT_FALSE (weight.is_initialized ()); + boost::optional pending (accounts.second.get_optional ("pending")); + ASSERT_FALSE (pending.is_initialized ()); + boost::optional representative (accounts.second.get_optional ("representative")); + ASSERT_FALSE (representative.is_initialized ()); + } + // Test for optional values + request.put ("weight", "1"); + request.put ("pending", "1"); + request.put ("representative", "true"); + test_response response2 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + for (auto & accounts : response2.json.get_child ("accounts")) + { + boost::optional weight (accounts.second.get_optional ("weight")); + ASSERT_TRUE (weight.is_initialized ()); + ASSERT_EQ ("0", weight.get ()); + boost::optional pending (accounts.second.get_optional ("pending")); + ASSERT_TRUE (pending.is_initialized ()); + ASSERT_EQ ("0", pending.get ()); + boost::optional representative (accounts.second.get_optional ("representative")); + ASSERT_TRUE (representative.is_initialized ()); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), representative.get ()); + } +} + +TEST (rpc, accounts_create) +{ + 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", "accounts_create"); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("count", "8"); + 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")); + for (auto i (accounts.begin ()), n (accounts.end ()); i != n; ++i) + { + std::string account_text (i->second.get ("")); + nano::uint256_union account; + ASSERT_FALSE (account.decode_account (account_text)); + ASSERT_TRUE (system.wallet (0)->exists (account)); + } + ASSERT_EQ (8, accounts.size ()); +} + +TEST (rpc, block_create) +{ + nano::system system (24000, 1); + nano::keypair key; + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + auto & node1 (*system.nodes[0]); + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto send_work = node1.work_generate_blocking (latest); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, send_work); + auto open_work = node1.work_generate_blocking (key.pub); + nano::open_block open (send.hash (), nano::test_genesis_key.pub, key.pub, key.prv, key.pub, open_work); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "block_create"); + request.put ("type", "send"); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("account", nano::test_genesis_key.pub.to_account ()); + request.put ("previous", latest.to_string ()); + request.put ("amount", "340282366920938463463374607431768211355"); + request.put ("destination", key.pub.to_account ()); + request.put ("work", nano::to_string_hex (send_work)); + 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); + std::string send_hash (response.json.get ("hash")); + ASSERT_EQ (send.hash ().to_string (), send_hash); + auto send_text (response.json.get ("block")); + boost::property_tree::ptree block_l; + std::stringstream block_stream (send_text); + boost::property_tree::read_json (block_stream, block_l); + auto send_block (nano::deserialize_block_json (block_l)); + ASSERT_EQ (send.hash (), send_block->hash ()); + system.nodes[0]->process (send); + boost::property_tree::ptree request1; + request1.put ("action", "block_create"); + request1.put ("type", "open"); + std::string key_text; + key.prv.data.encode_hex (key_text); + request1.put ("key", key_text); + request1.put ("representative", nano::test_genesis_key.pub.to_account ()); + request1.put ("source", send.hash ().to_string ()); + request1.put ("work", nano::to_string_hex (open_work)); + test_response response1 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response1.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response1.status); + std::string open_hash (response1.json.get ("hash")); + ASSERT_EQ (open.hash ().to_string (), open_hash); + auto open_text (response1.json.get ("block")); + std::stringstream block_stream1 (open_text); + boost::property_tree::read_json (block_stream1, block_l); + auto open_block (nano::deserialize_block_json (block_l)); + ASSERT_EQ (open.hash (), open_block->hash ()); + ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (open).code); + request1.put ("representative", key.pub.to_account ()); + test_response response2 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response2.status); + std::string open2_hash (response2.json.get ("hash")); + ASSERT_NE (open.hash ().to_string (), open2_hash); // different blocks with wrong representative + auto change_work = node1.work_generate_blocking (open.hash ()); + nano::change_block change (open.hash (), key.pub, key.prv, key.pub, change_work); + request1.put ("type", "change"); + request1.put ("work", nano::to_string_hex (change_work)); + test_response response4 (request1, rpc, system.io_ctx); + system.deadline_set (5s); + while (response4.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response4.status); + std::string change_hash (response4.json.get ("hash")); + ASSERT_EQ (change.hash ().to_string (), change_hash); + auto change_text (response4.json.get ("block")); + std::stringstream block_stream4 (change_text); + boost::property_tree::read_json (block_stream4, block_l); + auto change_block (nano::deserialize_block_json (block_l)); + ASSERT_EQ (change.hash (), change_block->hash ()); + ASSERT_EQ (nano::process_result::progress, node1.process (change).code); + nano::send_block send2 (send.hash (), key.pub, 0, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (send.hash ())); + ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (send2).code); + boost::property_tree::ptree request2; + request2.put ("action", "block_create"); + request2.put ("type", "receive"); + request2.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request2.put ("account", key.pub.to_account ()); + request2.put ("source", send2.hash ().to_string ()); + request2.put ("previous", change.hash ().to_string ()); + request2.put ("work", nano::to_string_hex (node1.work_generate_blocking (change.hash ()))); + test_response response5 (request2, rpc, system.io_ctx); + system.deadline_set (5s); + while (response5.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ (200, response5.status); + std::string receive_hash (response4.json.get ("hash")); + auto receive_text (response5.json.get ("block")); + std::stringstream block_stream5 (change_text); + boost::property_tree::read_json (block_stream5, block_l); + auto receive_block (nano::deserialize_block_json (block_l)); + ASSERT_EQ (receive_hash, receive_block->hash ().to_string ()); + system.nodes[0]->process_active (std::move (receive_block)); + latest = system.nodes[0]->latest (key.pub); + ASSERT_EQ (receive_hash, latest.to_string ()); +} + +TEST (rpc, block_create_state) +{ + nano::system system (24000, 1); + nano::keypair key; + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + boost::property_tree::ptree request; + request.put ("action", "block_create"); + request.put ("type", "state"); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("account", nano::test_genesis_key.pub.to_account ()); + request.put ("previous", genesis.hash ().to_string ()); + request.put ("representative", nano::test_genesis_key.pub.to_account ()); + request.put ("balance", (nano::genesis_amount - nano::Gxrb_ratio).convert_to ()); + request.put ("link", key.pub.to_account ()); + request.put ("work", nano::to_string_hex (system.nodes[0]->work_generate_blocking (genesis.hash ()))); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + 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); + std::string state_hash (response.json.get ("hash")); + auto state_text (response.json.get ("block")); + std::stringstream block_stream (state_text); + boost::property_tree::ptree block_l; + boost::property_tree::read_json (block_stream, block_l); + auto state_block (nano::deserialize_block_json (block_l)); + ASSERT_NE (nullptr, state_block); + ASSERT_EQ (nano::block_type::state, state_block->type ()); + ASSERT_EQ (state_hash, state_block->hash ().to_string ()); + auto process_result (system.nodes[0]->process (*state_block)); + ASSERT_EQ (nano::process_result::progress, process_result.code); +} + +TEST (rpc, block_create_state_open) +{ + nano::system system (24000, 1); + nano::keypair key; + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send_block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); + ASSERT_NE (nullptr, send_block); + boost::property_tree::ptree request; + request.put ("action", "block_create"); + request.put ("type", "state"); + request.put ("key", key.prv.data.to_string ()); + request.put ("account", key.pub.to_account ()); + request.put ("previous", 0); + request.put ("representative", nano::test_genesis_key.pub.to_account ()); + request.put ("balance", nano::Gxrb_ratio.convert_to ()); + request.put ("link", send_block->hash ().to_string ()); + request.put ("work", nano::to_string_hex (system.nodes[0]->work_generate_blocking (send_block->hash ()))); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + 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); + std::string state_hash (response.json.get ("hash")); + auto state_text (response.json.get ("block")); + std::stringstream block_stream (state_text); + boost::property_tree::ptree block_l; + boost::property_tree::read_json (block_stream, block_l); + auto state_block (nano::deserialize_block_json (block_l)); + ASSERT_NE (nullptr, state_block); + ASSERT_EQ (nano::block_type::state, state_block->type ()); + ASSERT_EQ (state_hash, state_block->hash ().to_string ()); + ASSERT_TRUE (system.nodes[0]->latest (key.pub).is_zero ()); + auto process_result (system.nodes[0]->process (*state_block)); + ASSERT_EQ (nano::process_result::progress, process_result.code); + ASSERT_FALSE (system.nodes[0]->latest (key.pub).is_zero ()); +} + +// Missing "work" parameter should cause work to be generated for us. +TEST (rpc, block_create_state_request_work) +{ + nano::genesis genesis; + + // Test work generation for state blocks both with and without previous (in the latter + // case, the account will be used for work generation) + std::vector previous_test_input{ genesis.hash ().to_string (), std::string ("0") }; + for (auto previous : previous_test_input) + { + nano::system system (24000, 1); + nano::keypair key; + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + boost::property_tree::ptree request; + request.put ("action", "block_create"); + request.put ("type", "state"); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("account", nano::test_genesis_key.pub.to_account ()); + request.put ("representative", nano::test_genesis_key.pub.to_account ()); + request.put ("balance", (nano::genesis_amount - nano::Gxrb_ratio).convert_to ()); + request.put ("link", key.pub.to_account ()); + request.put ("previous", previous); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + 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); + boost::property_tree::ptree block_l; + std::stringstream block_stream (response.json.get ("block")); + boost::property_tree::read_json (block_stream, block_l); + auto block (nano::deserialize_block_json (block_l)); + ASSERT_NE (nullptr, block); + ASSERT_FALSE (nano::work_validate (*block)); + } +} + +TEST (rpc, block_hash) +{ + nano::system system (24000, 1); + nano::keypair key; + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + auto & node1 (*system.nodes[0]); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "block_hash"); + std::string json; + send.serialize_json (json); + request.put ("block", json); + 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); + std::string send_hash (response.json.get ("hash")); + ASSERT_EQ (send.hash ().to_string (), send_hash); +} + +TEST (rpc, wallet_lock) +{ + 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; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + { + auto transaction (system.wallet (0)->wallets.tx_begin ()); + ASSERT_TRUE (system.wallet (0)->store.valid_password (transaction)); + } + request.put ("wallet", wallet); + request.put ("action", "wallet_lock"); + 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); + std::string account_text1 (response.json.get ("locked")); + ASSERT_EQ (account_text1, "1"); + auto transaction (system.wallet (0)->wallets.tx_begin ()); + ASSERT_FALSE (system.wallet (0)->store.valid_password (transaction)); +} + +TEST (rpc, wallet_locked) +{ + 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; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "wallet_locked"); + 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); + std::string account_text1 (response.json.get ("locked")); + ASSERT_EQ (account_text1, "0"); +} + +TEST (rpc, wallet_create_fail) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + auto node = system.nodes[0]; + // lmdb_max_dbs should be removed once the wallet store is refactored to support more wallets. + for (int i = 0; i < 127; i++) + { + nano::keypair key; + node->wallets.create (key.pub); + } + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "wallet_create"); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + ASSERT_EQ ("Failed to create wallet. Increase lmdb_max_dbs in node config", response.json.get ("error")); +} + +TEST (rpc, wallet_ledger) +{ + nano::system system (24000, 1); + nano::keypair key; + nano::genesis genesis; + system.wallet (0)->insert_adhoc (key.prv); + auto & node1 (*system.nodes[0]); + auto latest (system.nodes[0]->latest (nano::test_genesis_key.pub)); + nano::send_block send (latest, key.pub, 100, nano::test_genesis_key.prv, nano::test_genesis_key.pub, node1.work_generate_blocking (latest)); + system.nodes[0]->process (send); + nano::open_block open (send.hash (), nano::test_genesis_key.pub, key.pub, key.prv, key.pub, node1.work_generate_blocking (key.pub)); + ASSERT_EQ (nano::process_result::progress, system.nodes[0]->process (open).code); + auto time (nano::seconds_since_epoch ()); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "wallet_ledger"); + request.put ("wallet", system.nodes[0]->wallets.items.begin ()->first.to_string ()); + request.put ("sorting", "1"); + 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 ()); + } + for (auto & accounts : response.json.get_child ("accounts")) + { + std::string account_text (accounts.first); + ASSERT_EQ (key.pub.to_account (), account_text); + std::string frontier (accounts.second.get ("frontier")); + ASSERT_EQ (open.hash ().to_string (), frontier); + std::string open_block (accounts.second.get ("open_block")); + ASSERT_EQ (open.hash ().to_string (), open_block); + std::string representative_block (accounts.second.get ("representative_block")); + ASSERT_EQ (open.hash ().to_string (), representative_block); + std::string balance_text (accounts.second.get ("balance")); + ASSERT_EQ ("340282366920938463463374607431768211355", balance_text); + std::string modified_timestamp (accounts.second.get ("modified_timestamp")); + ASSERT_LT (std::abs ((long)time - stol (modified_timestamp)), 5); + std::string block_count (accounts.second.get ("block_count")); + ASSERT_EQ ("1", block_count); + boost::optional weight (accounts.second.get_optional ("weight")); + ASSERT_FALSE (weight.is_initialized ()); + boost::optional pending (accounts.second.get_optional ("pending")); + ASSERT_FALSE (pending.is_initialized ()); + boost::optional representative (accounts.second.get_optional ("representative")); + ASSERT_FALSE (representative.is_initialized ()); + } + // Test for optional values + request.put ("weight", "true"); + request.put ("pending", "1"); + request.put ("representative", "false"); + test_response response2 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + for (auto & accounts : response2.json.get_child ("accounts")) + { + boost::optional weight (accounts.second.get_optional ("weight")); + ASSERT_TRUE (weight.is_initialized ()); + ASSERT_EQ ("0", weight.get ()); + boost::optional pending (accounts.second.get_optional ("pending")); + ASSERT_TRUE (pending.is_initialized ()); + ASSERT_EQ ("0", pending.get ()); + boost::optional representative (accounts.second.get_optional ("representative")); + ASSERT_FALSE (representative.is_initialized ()); + } +} + +TEST (rpc, wallet_add_watch) +{ + 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; + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "wallet_add_watch"); + boost::property_tree::ptree entry; + boost::property_tree::ptree peers_l; + entry.put ("", nano::test_genesis_key.pub.to_account ()); + peers_l.push_back (std::make_pair ("", entry)); + request.add_child ("accounts", peers_l); + 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); + std::string success (response.json.get ("success")); + ASSERT_TRUE (success.empty ()); + ASSERT_TRUE (system.wallet (0)->exists (nano::test_genesis_key.pub)); +} + +TEST (rpc, online_reps) +{ + nano::system system (24000, 2); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + ASSERT_TRUE (system.nodes[1]->online_reps.online_stake () == system.nodes[1]->config.online_weight_minimum.number ()); + auto send_block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); + ASSERT_NE (nullptr, send_block); + system.deadline_set (10s); + while (system.nodes[1]->online_reps.list ().empty ()) + { + ASSERT_NO_ERROR (system.poll ()); + } + nano::rpc rpc (system.io_ctx, *system.nodes[1], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "representatives_online"); + 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 representatives (response.json.get_child ("representatives")); + auto item (representatives.begin ()); + ASSERT_NE (representatives.end (), item); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), item->second.get ("")); + boost::optional weight (item->second.get_optional ("weight")); + ASSERT_FALSE (weight.is_initialized ()); + while (system.nodes[1]->block (send_block->hash ()) == nullptr) + { + ASSERT_NO_ERROR (system.poll ()); + } + //Test weight option + request.put ("weight", "true"); + test_response response2 (request, rpc, system.io_ctx); + while (response2.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + auto representatives2 (response2.json.get_child ("representatives")); + auto item2 (representatives2.begin ()); + ASSERT_NE (representatives2.end (), item2); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), item2->first); + auto weight2 (item2->second.get ("weight")); + ASSERT_EQ (system.nodes[1]->weight (nano::test_genesis_key.pub).convert_to (), weight2); + //Test accounts filter + auto new_rep (system.wallet (1)->deterministic_insert ()); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, new_rep, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, send); + while (system.nodes[1]->block (send->hash ()) == nullptr) + { + ASSERT_NO_ERROR (system.poll ()); + } + auto receive (system.wallet (1)->receive_action (*send, new_rep, system.nodes[0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, receive); + while (system.nodes[1]->block (receive->hash ()) == nullptr) + { + ASSERT_NO_ERROR (system.poll ()); + } + auto change (system.wallet (0)->change_action (nano::test_genesis_key.pub, new_rep)); + ASSERT_NE (nullptr, change); + while (system.nodes[1]->block (change->hash ()) == nullptr) + { + ASSERT_NO_ERROR (system.poll ()); + } + system.deadline_set (5s); + while (system.nodes[1]->online_reps.list ().size () != 2) + { + ASSERT_NO_ERROR (system.poll ()); + } + boost::property_tree::ptree child_rep; + child_rep.put ("", new_rep.to_account ()); + boost::property_tree::ptree filtered_accounts; + filtered_accounts.push_back (std::make_pair ("", child_rep)); + request.add_child ("accounts", filtered_accounts); + test_response response3 (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response3.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + auto representatives3 (response3.json.get_child ("representatives")); + auto item3 (representatives3.begin ()); + ASSERT_NE (representatives3.end (), item3); + ASSERT_EQ (new_rep.to_account (), item3->first); + ASSERT_EQ (representatives3.size (), 1); + system.nodes[1]->stop (); +} + +TEST (rpc, confirmation_history) +{ + nano::system system (24000, 1); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + ASSERT_TRUE (system.nodes[0]->active.list_confirmed ().empty ()); + auto block (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); + system.deadline_set (10s); + while (system.nodes[0]->active.list_confirmed ().empty ()) + { + ASSERT_NO_ERROR (system.poll ()); + } + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "confirmation_history"); + 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 representatives (response.json.get_child ("confirmations")); + auto item (representatives.begin ()); + ASSERT_NE (representatives.end (), item); + auto hash (item->second.get ("hash")); + auto tally (item->second.get ("tally")); + ASSERT_FALSE (item->second.get ("duration", "").empty ()); + ASSERT_FALSE (item->second.get ("time", "").empty ()); + ASSERT_EQ (block->hash ().to_string (), hash); + nano::amount tally_num; + tally_num.decode_dec (tally); + assert (tally_num == nano::genesis_amount || tally_num == (nano::genesis_amount - nano::Gxrb_ratio)); + system.stop (); +} + +TEST (rpc, confirmation_history_hash) +{ + nano::system system (24000, 1); + nano::keypair key; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + ASSERT_TRUE (system.nodes[0]->active.list_confirmed ().empty ()); + auto send1 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); + auto send2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); + auto send3 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, nano::Gxrb_ratio)); + system.deadline_set (10s); + while (system.nodes[0]->active.list_confirmed ().size () != 3) + { + ASSERT_NO_ERROR (system.poll ()); + } + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "confirmation_history"); + request.put ("hash", send2->hash ().to_string ()); + 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 representatives (response.json.get_child ("confirmations")); + ASSERT_EQ (representatives.size (), 1); + auto item (representatives.begin ()); + ASSERT_NE (representatives.end (), item); + auto hash (item->second.get ("hash")); + auto tally (item->second.get ("tally")); + ASSERT_FALSE (item->second.get ("duration", "").empty ()); + ASSERT_FALSE (item->second.get ("time", "").empty ()); + ASSERT_EQ (send2->hash ().to_string (), hash); + nano::amount tally_num; + tally_num.decode_dec (tally); + assert (tally_num == nano::genesis_amount || tally_num == (nano::genesis_amount - nano::Gxrb_ratio) || tally_num == (nano::genesis_amount - 2 * nano::Gxrb_ratio) || tally_num == (nano::genesis_amount - 3 * nano::Gxrb_ratio)); + system.stop (); +} + +TEST (rpc, block_confirm) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto send1 (std::make_shared (nano::test_genesis_key.pub, genesis.hash (), nano::test_genesis_key.pub, nano::genesis_amount - nano::Gxrb_ratio, nano::test_genesis_key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.nodes[0]->work_generate_blocking (genesis.hash ()))); + { + auto transaction (system.nodes[0]->store.tx_begin (true)); + ASSERT_EQ (nano::process_result::progress, system.nodes[0]->ledger.process (transaction, *send1).code); + } + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "block_confirm"); + request.put ("hash", send1->hash ().to_string ()); + 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); + ASSERT_EQ ("1", response.json.get ("started")); +} + +TEST (rpc, block_confirm_absent) +{ + nano::system system (24000, 1); + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "block_confirm"); + request.put ("hash", "0"); + 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); + ASSERT_EQ ("Block not found", response.json.get ("error")); +} + +TEST (rpc, node_id) +{ + 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", "node_id"); + 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 transaction (system.nodes[0]->store.tx_begin_read ()); + nano::keypair node_id (system.nodes[0]->store.get_node_id (transaction)); + ASSERT_EQ (node_id.prv.data.to_string (), response.json.get ("private")); + ASSERT_EQ (node_id.pub.to_account (), response.json.get ("as_account")); +} + +TEST (rpc, node_id_delete) +{ + nano::system system (24000, 1); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + { + auto transaction (system.nodes[0]->store.tx_begin_write ()); + nano::keypair node_id (system.nodes[0]->store.get_node_id (transaction)); + ASSERT_EQ (node_id.pub.to_string (), system.nodes[0]->node_id.pub.to_string ()); + } + boost::property_tree::ptree request; + request.put ("action", "node_id_delete"); + 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); + ASSERT_EQ ("1", response.json.get ("deleted")); + auto transaction (system.nodes[0]->store.tx_begin_write ()); + nano::keypair node_id (system.nodes[0]->store.get_node_id (transaction)); + ASSERT_NE (node_id.pub.to_string (), system.nodes[0]->node_id.pub.to_string ()); +} + +TEST (rpc, stats_clear) +{ + nano::system system (24000, 1); + nano::keypair key; + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + system.nodes[0]->stats.inc (nano::stat::type::ledger, nano::stat::dir::in); + ASSERT_EQ (1, system.nodes[0]->stats.count (nano::stat::type::ledger, nano::stat::dir::in)); + boost::property_tree::ptree request; + request.put ("action", "stats_clear"); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + ASSERT_NO_ERROR (system.poll ()); + } + std::string success (response.json.get ("success")); + ASSERT_TRUE (success.empty ()); + ASSERT_EQ (0, system.nodes[0]->stats.count (nano::stat::type::ledger, nano::stat::dir::in)); + 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); + 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); + nano::rpc rpc (system.io_ctx, *system.nodes[0], nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "uptime"); + std::this_thread::sleep_for (std::chrono::seconds (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); + ASSERT_LE (1, response.json.get ("seconds")); +} + +TEST (rpc, wallet_history) +{ + nano::system system (24000, 1); + auto node0 (system.nodes[0]); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto timestamp1 (nano::seconds_since_epoch ()); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, node0->config.receive_minimum.number ())); + ASSERT_NE (nullptr, send); + std::this_thread::sleep_for (std::chrono::milliseconds (1000)); + auto timestamp2 (nano::seconds_since_epoch ()); + auto receive (system.wallet (0)->receive_action (*send, nano::test_genesis_key.pub, node0->config.receive_minimum.number ())); + ASSERT_NE (nullptr, receive); + nano::keypair key; + std::this_thread::sleep_for (std::chrono::milliseconds (1000)); + auto timestamp3 (nano::seconds_since_epoch ()); + auto send2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, node0->config.receive_minimum.number ())); + ASSERT_NE (nullptr, send2); + system.deadline_set (10s); + nano::rpc rpc (system.io_ctx, *node0, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "wallet_history"); + request.put ("wallet", node0->wallets.items.begin ()->first.to_string ()); + 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); + std::vector> history_l; + auto & history_node (response.json.get_child ("history")); + for (auto i (history_node.begin ()), n (history_node.end ()); i != n; ++i) + { + history_l.push_back (std::make_tuple (i->second.get ("type"), i->second.get ("account"), i->second.get ("amount"), i->second.get ("hash"), i->second.get ("block_account"), i->second.get ("local_timestamp"))); + } + ASSERT_EQ (4, history_l.size ()); + ASSERT_EQ ("send", std::get<0> (history_l[0])); + ASSERT_EQ (key.pub.to_account (), std::get<1> (history_l[0])); + ASSERT_EQ (node0->config.receive_minimum.to_string_dec (), std::get<2> (history_l[0])); + ASSERT_EQ (send2->hash ().to_string (), std::get<3> (history_l[0])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<4> (history_l[0])); + ASSERT_EQ (std::to_string (timestamp3), std::get<5> (history_l[0])); + ASSERT_EQ ("receive", std::get<0> (history_l[1])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[1])); + ASSERT_EQ (node0->config.receive_minimum.to_string_dec (), std::get<2> (history_l[1])); + ASSERT_EQ (receive->hash ().to_string (), std::get<3> (history_l[1])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<4> (history_l[1])); + ASSERT_EQ (std::to_string (timestamp2), std::get<5> (history_l[1])); + ASSERT_EQ ("send", std::get<0> (history_l[2])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[2])); + ASSERT_EQ (node0->config.receive_minimum.to_string_dec (), std::get<2> (history_l[2])); + ASSERT_EQ (send->hash ().to_string (), std::get<3> (history_l[2])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<4> (history_l[2])); + ASSERT_EQ (std::to_string (timestamp1), std::get<5> (history_l[2])); + // Genesis block + ASSERT_EQ ("receive", std::get<0> (history_l[3])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[3])); + ASSERT_EQ (nano::genesis_amount.convert_to (), std::get<2> (history_l[3])); + ASSERT_EQ (genesis.hash ().to_string (), std::get<3> (history_l[3])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<4> (history_l[3])); +} + +TEST (rpc, sign_hash) +{ + nano::system system (24000, 1); + nano::keypair key; + auto & node1 (*system.nodes[0]); + nano::state_block send (nano::genesis_account, node1.latest (nano::test_genesis_key.pub), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "sign"); + request.put ("hash", send.hash ().to_string ()); + request.put ("key", key.prv.data.to_string ()); + test_response response (request, rpc, system.io_ctx); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + std::error_code ec (nano::error_rpc::sign_hash_disabled); + ASSERT_EQ (response.json.get ("error"), ec.message ()); + rpc.config.enable_sign_hash = true; + test_response response2 (request, rpc, system.io_ctx); + while (response2.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response2.status); + nano::signature signature; + std::string signature_text (response2.json.get ("signature")); + ASSERT_FALSE (signature.decode_hex (signature_text)); + ASSERT_FALSE (nano::validate_message (key.pub, send.hash (), signature)); +} + +TEST (rpc, sign_block) +{ + nano::system system (24000, 1); + nano::keypair key; + auto & node1 (*system.nodes[0]); + nano::state_block send (nano::genesis_account, node1.latest (nano::test_genesis_key.pub), nano::genesis_account, nano::genesis_amount - nano::Gxrb_ratio, key.pub, nano::test_genesis_key.prv, nano::test_genesis_key.pub, 0); + nano::rpc rpc (system.io_ctx, node1, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "sign"); + system.wallet (0)->insert_adhoc (key.prv); + std::string wallet; + system.nodes[0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("account", key.pub.to_account ()); + std::string json; + send.serialize_json (json); + request.put ("block", json); + test_response response (request, rpc, system.io_ctx); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + auto contents (response.json.get ("block")); + boost::property_tree::ptree block_l; + std::stringstream block_stream (contents); + boost::property_tree::read_json (block_stream, block_l); + auto block (nano::deserialize_block_json (block_l)); + ASSERT_FALSE (nano::validate_message (key.pub, send.hash (), block->block_signature ())); + ASSERT_NE (block->block_signature (), send.block_signature ()); + ASSERT_EQ (block->hash (), send.hash ()); +} + +TEST (rpc, memory_stats) +{ + nano::system system (24000, 1); + auto node = system.nodes.front (); + nano::rpc rpc (system.io_ctx, *node, nano::rpc_config (true)); + + // Preliminary test adding to the vote uniquer and checking json output is correct + nano::keypair key; + auto block (std::make_shared (0, 0, 0, 0, 0, key.prv, key.pub, 0)); + std::vector hashes; + hashes.push_back (block->hash ()); + auto vote (std::make_shared (key.pub, key.prv, 0, hashes)); + node->vote_uniquer.unique (vote); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "stats"); + request.put ("type", "objects"); + 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); + + ASSERT_EQ (response.json.get_child ("node").get_child ("vote_uniquer").get_child ("votes").get ("count"), "1"); +} + +TEST (rpc, block_confirmed) +{ + nano::system system (24000, 1); + auto node = system.nodes.front (); + nano::rpc rpc (system.io_ctx, *node, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "block_info"); + request.put ("hash", "bad_hash1337"); + test_response response (request, rpc, system.io_ctx); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + ASSERT_EQ ("Invalid block hash", response.json.get ("error")); + + request.put ("hash", "0"); + test_response response1 (request, rpc, system.io_ctx); + while (response1.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response1.status); + ASSERT_EQ ("Block not found", response1.json.get ("error")); + + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + nano::keypair key; + system.wallet (0)->insert_adhoc (key.prv); + + // Open an account directly in the ledger + { + auto transaction = node->store.tx_begin_write (); + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + nano::send_block send1 (latest, key.pub, 300, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, send1).code); + + nano::open_block open1 (send1.hash (), nano::genesis_account, key.pub, key.prv, key.pub, system.work.generate (key.pub)); + ASSERT_EQ (nano::process_result::progress, node->ledger.process (transaction, open1).code); + } + + // This should not be confirmed + nano::block_hash latest (node->latest (nano::test_genesis_key.pub)); + request.put ("hash", latest.to_string ()); + test_response response2 (request, rpc, system.io_ctx); + while (response2.status == 0) + { + system.poll (); + } + + ASSERT_EQ (200, response2.status); + ASSERT_FALSE (response2.json.get ("confirmed")); + + // Create and process a new send block + auto send = std::make_shared (latest, key.pub, 10, nano::test_genesis_key.prv, nano::test_genesis_key.pub, system.work.generate (latest)); + node->process_active (send); + node->block_processor.flush (); + + // Wait until it has been confirmed by the network + system.deadline_set (10s); + while (true) + { + auto transaction = node->store.tx_begin_read (); + if (node->ledger.block_confirmed (transaction, send->hash ())) + { + break; + } + + ASSERT_NO_ERROR (system.poll ()); + } + + // Requesting confirmation for this should now succeed + request.put ("hash", send->hash ().to_string ()); + test_response response3 (request, rpc, system.io_ctx); + while (response3.status == 0) + { + system.poll (); + } + + ASSERT_EQ (200, response3.status); + ASSERT_TRUE (response3.json.get ("confirmed")); +}