From 040d7ebb46fb158c8d6488270c14d7919442e5ef Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 25 Jan 2016 18:27:40 -0500 Subject: [PATCH] Refactor Env for AbstractClient: Env is changed to use the AbstractClient interface, which generalizes the transport for submitting client requests to the Env server instance. The JSONRPCClient implementation is added, supporting a simple, synchronous interface. Env is changed to use the JSONRPCClient implementation instead of the built in JSON-RPC client. --- Builds/VisualStudio2015/RippleD.vcxproj | 8 + .../VisualStudio2015/RippleD.vcxproj.filters | 9 + .../app/tests/Transaction_ordering_test.cpp | 15 +- src/ripple/test/AbstractClient.h | 58 +++++++ src/ripple/test/JSONRPCClient.h | 37 ++++ src/ripple/test/impl/JSONRPCClient.cpp | 164 ++++++++++++++++++ src/ripple/test/jtx/Env.h | 43 +++-- src/ripple/test/jtx/impl/Env.cpp | 65 ++++--- src/ripple/unity/test.cpp | 1 + 9 files changed, 349 insertions(+), 51 deletions(-) create mode 100644 src/ripple/test/AbstractClient.h create mode 100644 src/ripple/test/JSONRPCClient.h create mode 100644 src/ripple/test/impl/JSONRPCClient.cpp diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 468675d1e..b2e407700 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -3406,16 +3406,24 @@ + + True True + + True + True + True True + + diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index 4ca49f8f7..8822874aa 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -3915,15 +3915,24 @@ ripple\shamap + + ripple\test + ripple\test ripple\test\impl + + ripple\test\impl + ripple\test\impl + + ripple\test + ripple\test diff --git a/src/ripple/app/tests/Transaction_ordering_test.cpp b/src/ripple/app/tests/Transaction_ordering_test.cpp index c897a6e0a..7536213da 100644 --- a/src/ripple/app/tests/Transaction_ordering_test.cpp +++ b/src/ripple/app/tests/Transaction_ordering_test.cpp @@ -74,13 +74,11 @@ struct Transaction_ordering_test : public beast::unit_test::suite { auto const result = env.rpc("tx", to_string(tx1.stx->getTransactionID())); - expect(result.first == rpcSUCCESS); - expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS"); + expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS"); } { auto const result = env.rpc("tx", to_string(tx2.stx->getTransactionID())); - expect(result.first == rpcSUCCESS); - expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS"); + expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS"); } } @@ -112,13 +110,11 @@ struct Transaction_ordering_test : public beast::unit_test::suite { auto const result = env.rpc("tx", to_string(tx1.stx->getTransactionID())); - expect(result.first == rpcSUCCESS); - expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS"); + expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS"); } { auto const result = env.rpc("tx", to_string(tx2.stx->getTransactionID())); - expect(result.first == rpcSUCCESS); - expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS"); + expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS"); } } @@ -160,8 +156,7 @@ struct Transaction_ordering_test : public beast::unit_test::suite for (auto i = 0; i < 5; ++i) { auto const result = env.rpc("tx", to_string(tx[i].stx->getTransactionID())); - expect(result.first == rpcSUCCESS); - expect(result.second["result"]["meta"]["TransactionResult"] == "tesSUCCESS"); + expect(result["result"]["meta"]["TransactionResult"] == "tesSUCCESS"); } } diff --git a/src/ripple/test/AbstractClient.h b/src/ripple/test/AbstractClient.h new file mode 100644 index 000000000..d11c7a700 --- /dev/null +++ b/src/ripple/test/AbstractClient.h @@ -0,0 +1,58 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2016 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_TEST_ABSTRACTCLIENT_H_INCLUDED +#define RIPPLE_TEST_ABSTRACTCLIENT_H_INCLUDED + +#include + +namespace ripple { +namespace test { + +/* Abstract Ripple Client interface. + + This abstracts the transport layer, allowing + commands to be submitted to a rippled server. +*/ +class AbstractClient +{ +public: + virtual ~AbstractClient() = default; + + /** Submit a command synchronously. + + The arguments to the function and the returned JSON + are in a normalized format, the same whether the client + is using the JSON-RPC over HTTP/S or WebSocket transport. + + @param cmd The command to execute + @param params Json::Value of null or object type + with zero or more key/value pairs. + @return The server response in normalized format. + */ + virtual + Json::Value + invoke(std::string const& cmd, + Json::Value const& params = {}) = 0; +}; + +} // test +} // ripple + +#endif diff --git a/src/ripple/test/JSONRPCClient.h b/src/ripple/test/JSONRPCClient.h new file mode 100644 index 000000000..28ce3543e --- /dev/null +++ b/src/ripple/test/JSONRPCClient.h @@ -0,0 +1,37 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2016 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_TEST_HTTPCLIENT_H_INCLUDED +#define RIPPLE_TEST_HTTPCLIENT_H_INCLUDED + +#include +#include +#include + +namespace ripple { +namespace test { + +/** Returns a client using JSON-RPC over HTTP/S. */ +std::unique_ptr +makeJSONRPCClient(Config const& cfg); + +} // test +} // ripple + +#endif diff --git a/src/ripple/test/impl/JSONRPCClient.cpp b/src/ripple/test/impl/JSONRPCClient.cpp new file mode 100644 index 000000000..2137dc218 --- /dev/null +++ b/src/ripple/test/impl/JSONRPCClient.cpp @@ -0,0 +1,164 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2016 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ripple { +namespace test { + +class JSONRPCClient : public AbstractClient +{ + static + boost::asio::ip::tcp::endpoint + getEndpoint(BasicConfig const& cfg) + { + auto& log = std::cerr; + ParsedPort common; + parse_Port (common, cfg["server"], log); + for (auto const& name : cfg.section("server").values()) + { + if (! cfg.exists(name)) + continue; + ParsedPort pp; + parse_Port(pp, cfg[name], log); + if(pp.protocol.count("http") == 0) + continue; + using boost::asio::ip::address_v4; + if(*pp.ip == address_v4{0x00000000}) + *pp.ip = address_v4{0x7f000001}; + return { *pp.ip, *pp.port }; + } + throw std::runtime_error("Missing HTTP port"); + } + + template + static + std::string + buffer_string (ConstBufferSequence const& b) + { + using namespace boost::asio; + std::string s; + s.resize(buffer_size(b)); + buffer_copy(buffer(&s[0], s.size()), b); + return s; + } + + boost::asio::io_service ios_; + boost::asio::ip::tcp::socket stream_; + boost::asio::streambuf bin_; + beast::asio::streambuf bout_; + +public: + explicit + JSONRPCClient(Config const& cfg) + : stream_(ios_) + { + stream_.connect(getEndpoint(cfg)); + } + + ~JSONRPCClient() override + { + //stream_.shutdown(boost::asio::ip::tcp::socket::shutdown_both); + //stream_.close(); + } + + /* + Return value is an Object type with up to three keys: + status + error + result + */ + Json::Value + invoke(std::string const& cmd, + Json::Value const& params) override + { + std::string s; + { + Json::Value jr; + jr["method"] = cmd; + if(params) + { + Json::Value& ja = jr["params"] = Json::arrayValue; + ja.append(params); + } + s = to_string(jr); + } + + using namespace boost::asio; + using namespace std::string_literals; + std::string r; + r = + "POST / HTTP/1.1\r\n" + "Host: me\r\n" + "Connection: Keep-Alive\r\n"s + + "Content-Type: application/json; charset=UTF-8\r\n"s + + "Content-Length: " + std::to_string(s.size()) + "\r\n" + "\r\n"; + write(stream_, buffer(r)); + write(stream_, buffer(s)); + + read_until(stream_, bin_, "\r\n\r\n"); + beast::asio::streambuf body; + beast::http::message m; + beast::http::parser p( + [&](void const* data, std::size_t size) + { + body.commit(buffer_copy( + body.prepare(size), const_buffer(data, size))); + }, m, false); + + for(;;) + { + auto const result = p.write(bin_.data()); + if (result.first) + throw result.first; + bin_.consume(result.second); + if(p.complete()) + break; + bin_.commit(stream_.read_some( + bin_.prepare(1024))); + } + + Json::Reader jr; + Json::Value jv; + jr.parse(buffer_string(body.data()), jv); + if(jv["result"].isMember("error")) + jv["error"] = jv["result"]["error"]; + if(jv["result"].isMember("status")) + jv["status"] = jv["result"]["status"]; + return jv; + } +}; + +std::unique_ptr +makeJSONRPCClient(Config const& cfg) +{ + return std::make_unique(cfg); +} + +} // test +} // ripple diff --git a/src/ripple/test/jtx/Env.h b/src/ripple/test/jtx/Env.h index 0e330b0ce..99a70d361 100644 --- a/src/ripple/test/jtx/Env.h +++ b/src/ripple/test/jtx/Env.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -36,7 +37,6 @@ #include #include #include -#include #include #include #include @@ -99,6 +99,7 @@ private: std::unique_ptr owned; ManualTimeKeeper* timeKeeper; std::thread thread; + std::unique_ptr client; AppBundle (std::unique_ptr config); AppBundle (Application* app_); @@ -161,7 +162,6 @@ public: { } - Application& app() { @@ -185,6 +185,22 @@ public: return app().timeKeeper().now(); } + /** Returns the connected client. */ + AbstractClient& + client() + { + return *bundle_.client; + } + + /** Execute an RPC command. + + The command is examined and used to build + the correct JSON as per the arguments. + */ + template + Json::Value + rpc(std::string const& cmd, Args&&... args); + /** Returns the current ledger. This is a non-modifiable snapshot of the @@ -423,11 +439,6 @@ public: std::shared_ptr meta(); - /** Execute a client command */ - template - std::pair - rpc (Arg&& arg0, Args&&... args); - private: void fund (bool setDefaultRipple, @@ -534,6 +545,9 @@ protected: uint256 txid_; TER ter_ = tesSUCCESS; + Json::Value + do_rpc(std::vector const& args); + void autofill_sig (JTx& jt); @@ -628,16 +642,13 @@ protected: AccountID, Account> map_; }; -//------------------------------------------------------------------------------ - -template -std::pair -Env::rpc (Arg&& arg0, Args&&... args) +template +Json::Value +Env::rpc(std::string const& cmd, Args&&... args) { - std::vector v({ std::forward(arg0), - std::forward(args)... }); - return rpcClient(v, - app().config(), app().logs()); + std::vector vs{cmd, + std::forward(args)...}; + return do_rpc(vs); } } // jtx diff --git a/src/ripple/test/jtx/impl/Env.cpp b/src/ripple/test/jtx/impl/Env.cpp index af8b72db4..f7aed9ece 100644 --- a/src/ripple/test/jtx/impl/Env.cpp +++ b/src/ripple/test/jtx/impl/Env.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -54,26 +56,29 @@ namespace ripple { namespace test { void -setupConfigForUnitTests (Config& config) +setupConfigForUnitTests (Config& cfg) { - config.overwrite (ConfigSection::nodeDatabase (), "type", "memory"); - config.overwrite (ConfigSection::nodeDatabase (), "path", "main"); - - config.deprecatedClearSection (ConfigSection::importNodeDatabase ()); - config.legacy("database_path", ""); - - config.RUN_STANDALONE = true; - config.QUIET = true; - config.SILENT = true; - config["server"].append("port_peer"); - config["port_peer"].set("ip", "127.0.0.1"); - config["port_peer"].set("port", "8080"); - config["port_peer"].set("protocol", "peer"); - config["server"].append("port_admin"); - config["port_admin"].set("ip", "127.0.0.1"); - config["port_admin"].set("port", "8081"); - config["port_admin"].set("protocol", "http"); - config["port_admin"].set("admin", "127.0.0.1"); + cfg.overwrite (ConfigSection::nodeDatabase (), "type", "memory"); + cfg.overwrite (ConfigSection::nodeDatabase (), "path", "main"); + cfg.deprecatedClearSection (ConfigSection::importNodeDatabase ()); + cfg.legacy("database_path", ""); + cfg.RUN_STANDALONE = true; + cfg.QUIET = true; + cfg.SILENT = true; + cfg["server"].append("port_peer"); + cfg["port_peer"].set("ip", "127.0.0.1"); + cfg["port_peer"].set("port", "8080"); + cfg["port_peer"].set("protocol", "peer"); + cfg["server"].append("port_http"); + cfg["port_http"].set("ip", "127.0.0.1"); + cfg["port_http"].set("port", "8081"); + cfg["port_http"].set("protocol", "http"); + cfg["port_http"].set("admin", "127.0.0.1"); + cfg["server"].append("port_ws"); + cfg["port_ws"].set("ip", "127.0.0.1"); + cfg["port_ws"].set("port", "8082"); + cfg["port_ws"].set("protocol", "ws"); + cfg["port_ws"].set("admin", "127.0.0.1"); } //------------------------------------------------------------------------------ @@ -102,10 +107,13 @@ Env::AppBundle::AppBundle(std::unique_ptr config) app->doStart(); thread = std::thread( [&](){ app->run(); }); + + client = makeJSONRPCClient(app->config()); } Env::AppBundle::~AppBundle() { + client.reset(); app->signalStop(); thread.join(); } @@ -131,8 +139,8 @@ Env::close(NetClock::time_point closeTime, app().getOPs().acceptLedger(consensusDelay); else { - auto const result = rpc("ledger_accept"); - test.expect(result.first == rpcSUCCESS); + rpc("ledger_accept"); + // VFALCO No error check? } bundle_.timeKeeper->set( closed()->info().closeTime); @@ -274,11 +282,10 @@ Env::submit (JTx const& jt) txid_ = jt.stx->getTransactionID(); Serializer s; jt.stx->add(s); - auto const result = rpc("submit", strHex(s.slice())); - if (result.first == rpcSUCCESS && - result.second["result"].isMember("engine_result_code")) + auto const jr = rpc("submit", strHex(s.slice())); + if (jr["result"].isMember("engine_result_code")) ter_ = static_cast( - result.second["result"]["engine_result_code"].asInt()); + jr["result"]["engine_result_code"].asInt()); else ter_ = temINVALID; didApply = isTesSuccess(ter_) || isTecClaim(ter_); @@ -408,6 +415,14 @@ Env::applyFlags() const return flags; } +Json::Value +Env::do_rpc(std::vector const& args) +{ + auto const jv = cmdLineToJSONRPC(args, journal); + return client().invoke(jv["method"].asString(), + jv["params"][0U]); +} + } // jtx } // test diff --git a/src/ripple/unity/test.cpp b/src/ripple/unity/test.cpp index 90dc452e6..4ad8fc39a 100644 --- a/src/ripple/unity/test.cpp +++ b/src/ripple/unity/test.cpp @@ -48,4 +48,5 @@ #include #include +#include #include