From 2fd0540ed412f5b1fa9436de36995a01303ce87a Mon Sep 17 00:00:00 2001 From: Howard Hinnant Date: Tue, 25 Oct 2016 16:08:48 -0400 Subject: [PATCH] Recognize ripplerpc 2.0 requests and respond in kind: * Force jtx to request/receive the 2.0 API * Force the JSON and WebSocket tests to use 2.0 API * This specifically allows the Websocket to create 2.0 json/ripple and get back a 2.0 response. * Add test for malformed json2 * Add check for parse failure * Add check for params to be in array form. * Correct type-o discovered in tests due to stricter checking. * Add API version to the WSClient & JSONRPCClient test * Update source.dox with more headers --- docs/source.dox | 4 + src/ripple/app/paths/PathRequest.cpp | 6 +- src/ripple/net/impl/RPCCall.cpp | 75 +++- src/ripple/protocol/JsonFields.h | 2 + src/ripple/rpc/impl/RPCHandler.cpp | 12 +- src/ripple/rpc/impl/ServerHandlerImp.cpp | 36 +- src/ripple/shamap/impl/SHAMap.cpp | 5 - src/ripple/test/AbstractClient.h | 3 + src/ripple/test/JSONRPCClient.h | 2 +- src/ripple/test/WSClient.h | 2 +- src/ripple/test/impl/JSONRPCClient.cpp | 25 +- src/ripple/test/impl/WSClient.cpp | 25 +- src/ripple/test/jtx/impl/Env.cpp | 18 +- src/test/app/Offer_test.cpp | 6 + src/test/rpc/AccountInfo_test.cpp | 131 ++++++ src/test/rpc/AccountLinesRPC_test.cpp | 515 ++++++++++++++++++++++- src/test/rpc/Book_test.cpp | 163 ++++++- src/test/rpc/GatewayBalances_test.cpp | 6 + src/test/rpc/RobustTransaction_test.cpp | 132 +++++- src/test/rpc/Subscribe_test.cpp | 72 ++++ 20 files changed, 1178 insertions(+), 62 deletions(-) diff --git a/docs/source.dox b/docs/source.dox index 7e589ff8f..58acc58cf 100644 --- a/docs/source.dox +++ b/docs/source.dox @@ -105,6 +105,10 @@ WARN_LOGFILE = INPUT = \ \ ../src/ripple/protocol/STObject.h \ + ../src/ripple/protocol/JsonFields.h \ + ../src/ripple/test/AbstractClient.h \ + ../src/ripple/test/JSONRPCClient.h \ + ../src/ripple/test/WSClient.h \ INPUT_ENCODING = UTF-8 diff --git a/src/ripple/app/paths/PathRequest.cpp b/src/ripple/app/paths/PathRequest.cpp index 9a44079dc..8fc7f1f7a 100644 --- a/src/ripple/app/paths/PathRequest.cpp +++ b/src/ripple/app/paths/PathRequest.cpp @@ -434,8 +434,8 @@ int PathRequest::parseJson (Json::Value const& jvParams) } } - if (jvParams.isMember ("id")) - jvId = jvParams["id"]; + if (jvParams.isMember (jss::id)) + jvId = jvParams[jss::id]; return PFR_PJ_NOCHANGE; } @@ -649,7 +649,7 @@ Json::Value PathRequest::doUpdate( newStatus[jss::full_reply] = ! fast; if (jvId) - newStatus["id"] = jvId; + newStatus[jss::id] = jvId; bool loaded = app_.getFeeTrack().isLoadedLocal(); diff --git a/src/ripple/net/impl/RPCCall.cpp b/src/ripple/net/impl/RPCCall.cpp index 4e82b53dc..8b3455664 100644 --- a/src/ripple/net/impl/RPCCall.cpp +++ b/src/ripple/net/impl/RPCCall.cpp @@ -487,7 +487,7 @@ private: if (!jvRequest.isObject ()) return rpcError (rpcINVALID_PARAMS); - jvRequest["method"] = jvParams[0u]; + jvRequest[jss::method] = jvParams[0u]; return jvRequest; } @@ -495,6 +495,53 @@ private: return rpcError (rpcINVALID_PARAMS); } + bool isValidJson2(Json::Value const& jv) + { + if (jv.isObject()) + { + if (jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0" && + jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0" && + jv.isMember(jss::id) && jv.isMember(jss::method)) + { + if (jv.isMember(jss::params) && + !(jv[jss::params].isArray() || jv[jss::params].isNull())) + return false; + return true; + } + } + return false; + } + + Json::Value parseJson2(Json::Value const& jvParams) + { + Json::Reader reader; + Json::Value jv; + bool valid_parse = reader.parse(jvParams[0u].asString(), jv); + if (valid_parse && isValidJson2(jv)) + { + Json::Value jv1{Json::objectValue}; + if (jv.isMember(jss::params)) + { + auto const& params = jv[jss::params][0u]; + for (auto i = params.begin(); i != params.end(); ++i) + jv1[i.key().asString()] = *i; + } + jv1[jss::jsonrpc] = jv[jss::jsonrpc]; + jv1[jss::ripplerpc] = jv[jss::ripplerpc]; + jv1[jss::id] = jv[jss::id]; + jv1[jss::method] = jv[jss::method]; + return jv1; + } + auto jv_error = rpcError(rpcINVALID_PARAMS); + if (jv.isMember(jss::jsonrpc)) + jv_error[jss::jsonrpc] = jv[jss::jsonrpc]; + if (jv.isMember(jss::ripplerpc)) + jv_error[jss::ripplerpc] = jv[jss::ripplerpc]; + if (jv.isMember(jss::id)) + jv_error[jss::id] = jv[jss::id]; + return jv_error; + } + // ledger [id|index|current|closed|validated] [full|tx] Json::Value parseLedger (Json::Value const& jvParams) { @@ -1003,6 +1050,7 @@ public: { "gateway_balances", &RPCParser::parseGatewayBalances , 1, -1 }, { "get_counts", &RPCParser::parseGetCounts, 0, 1 }, { "json", &RPCParser::parseJson, 2, 2 }, + { "json2", &RPCParser::parseJson2, 1, 1 }, { "ledger", &RPCParser::parseLedger, 0, 2 }, { "ledger_accept", &RPCParser::parseAsIs, 0, 0 }, { "ledger_closed", &RPCParser::parseAsIs, 0, 0 }, @@ -1181,7 +1229,7 @@ rpcCmdLineToJson (std::vector const& args, retParams = Json::Value (Json::objectValue); - retParams["method"] = args[0]; + retParams[jss::method] = args[0]; retParams[jss::params] = jvRpcParams; jvRequest = rpParser.parseCommand (args[0], jvRpcParams, true); @@ -1210,6 +1258,12 @@ cmdLineToJSONRPC (std::vector const& args, beast::Journal j) auto& paramsArray = Json::setArray (jv, jss::params); paramsArray.append (paramsObj); } + if (paramsObj.isMember(jss::jsonrpc)) + jv[jss::jsonrpc] = paramsObj[jss::jsonrpc]; + if (paramsObj.isMember(jss::ripplerpc)) + jv[jss::ripplerpc] = paramsObj[jss::ripplerpc]; + if (paramsObj.isMember(jss::id)) + jv[jss::id] = paramsObj[jss::id]; return jv; } @@ -1267,6 +1321,12 @@ rpcClient(std::vector const& args, jvParams.append (jvRequest); + if (jvRequest.isMember(jss::params)) + { + auto const& params = jvRequest[jss::params]; + assert(params.size() == 1); + jvParams.append(params[0u]); + } { boost::asio::io_service isService; RPCCall::fromNetwork ( @@ -1276,8 +1336,8 @@ rpcClient(std::vector const& args, setup.client.user, setup.client.password, "", - jvRequest.isMember ("method") // Allow parser to rewrite method. - ? jvRequest["method"].asString () : args[0], + jvRequest.isMember (jss::method) // Allow parser to rewrite method. + ? jvRequest[jss::method].asString () : args[0], jvParams, // Parsed, execute. setup.client.secure != 0, // Use SSL config.quiet(), @@ -1331,6 +1391,13 @@ rpcClient(std::vector const& args, nRet = rpcINTERNAL; } + if (jvRequest.isMember(jss::jsonrpc)) + jvOutput[jss::jsonrpc] = jvRequest[jss::jsonrpc]; + if (jvRequest.isMember(jss::ripplerpc)) + jvOutput[jss::ripplerpc] = jvRequest[jss::ripplerpc]; + if (jvRequest.isMember(jss::id)) + jvOutput[jss::id] = jvRequest[jss::id]; + return { nRet, std::move(jvOutput) }; } diff --git a/src/ripple/protocol/JsonFields.h b/src/ripple/protocol/JsonFields.h index c7bf72601..a8f15c7da 100644 --- a/src/ripple/protocol/JsonFields.h +++ b/src/ripple/protocol/JsonFields.h @@ -213,6 +213,7 @@ JSS ( ip ); // in: Connect, out: OverlayImpl JSS ( issuer ); // in: RipplePathFind, Subscribe, // Unsubscribe, BookOffers // out: paths/Node, STPathSet, STAmount +JSS ( jsonrpc ); // json version JSS ( key ); // out: WalletSeed JSS ( key_type ); // in/out: WalletPropose, TransactionSign JSS ( latency ); // out: PeerImp @@ -350,6 +351,7 @@ JSS ( response ); // websocket JSS ( result ); // RPC JSS ( ripple_lines ); // out: NetworkOPs JSS ( ripple_state ); // in: LedgerEntr +JSS ( ripplerpc ); // ripple RPC version JSS ( role ); // out: Ping.cpp JSS ( rt_accounts ); // in: Subscribe, Unsubscribe JSS ( sanity ); // out: PeerImp diff --git a/src/ripple/rpc/impl/RPCHandler.cpp b/src/ripple/rpc/impl/RPCHandler.cpp index d97fb0feb..d0bcc342b 100644 --- a/src/ripple/rpc/impl/RPCHandler.cpp +++ b/src/ripple/rpc/impl/RPCHandler.cpp @@ -124,10 +124,18 @@ error_code_i fillHandler (Context& context, } } - if (!context.params.isMember ("command")) + if (!context.params.isMember(jss::command) && !context.params.isMember(jss::method)) return rpcCOMMAND_MISSING; + if (context.params.isMember(jss::command) && context.params.isMember(jss::method)) + { + if (context.params[jss::command].asString() != + context.params[jss::method].asString()) + return rpcUNKNOWN_COMMAND; + } - std::string strCommand = context.params[jss::command].asString (); + std::string strCommand = context.params.isMember(jss::command) ? + context.params[jss::command].asString() : + context.params[jss::method].asString(); JLOG (context.j.trace()) << "COMMAND:" << strCommand; JLOG (context.j.trace()) << "REQUEST:" << context.params; diff --git a/src/ripple/rpc/impl/ServerHandlerImp.cpp b/src/ripple/rpc/impl/ServerHandlerImp.cpp index 9ba8869e7..c72f418d4 100644 --- a/src/ripple/rpc/impl/ServerHandlerImp.cpp +++ b/src/ripple/rpc/impl/ServerHandlerImp.cpp @@ -404,7 +404,9 @@ ServerHandlerImp::processSession( // Requests without "command" are invalid. Json::Value jr(Json::objectValue); - if (! jv.isMember (jss::command)) + if ((!jv.isMember(jss::command) && !jv.isMember(jss::method)) || + (jv.isMember(jss::command) && jv.isMember(jss::method) && + jv[jss::command].asString() != jv[jss::method].asString())) { jr[jss::type] = jss::response; jr[jss::status] = jss::error; @@ -412,13 +414,19 @@ ServerHandlerImp::processSession( jr[jss::request] = jv; if (jv.isMember (jss::id)) jr[jss::id] = jv[jss::id]; + if (jv.isMember(jss::jsonrpc)) + jr[jss::jsonrpc] = jv[jss::jsonrpc]; + if (jv.isMember(jss::jsonrpc)) + jr[jss::ripplerpc] = jv[jss::ripplerpc]; is->getConsumer().charge(Resource::feeInvalidRPC); return jr; } Resource::Charge loadType = Resource::feeReferenceRPC; - auto required = RPC::roleRequired(jv[jss::command].asString()); + auto required = RPC::roleRequired(jv.isMember(jss::command) ? + jv[jss::command].asString() : + jv[jss::method].asString()); auto role = requestRole( required, session->port(), @@ -475,6 +483,10 @@ ServerHandlerImp::processSession( if (jv.isMember(jss::id)) jr[jss::id] = jv[jss::id]; + if (jv.isMember(jss::jsonrpc)) + jr[jss::jsonrpc] = jv[jss::jsonrpc]; + if (jv.isMember(jss::jsonrpc)) + jr[jss::ripplerpc] = jv[jss::ripplerpc]; jr[jss::type] = jss::response; return jr; } @@ -539,8 +551,8 @@ ServerHandlerImp::processRequest (Port const& port, // // VFALCO NOTE Except that "id" isn't included in the following errors. // - Json::Value const& id = jsonRPC ["id"]; - Json::Value const& method = jsonRPC ["method"]; + Json::Value const& id = jsonRPC [jss::id]; + Json::Value const& method = jsonRPC [jss::method]; if (! method) { HTTPReply (400, "Null method", output, rpcJ); @@ -555,12 +567,12 @@ ServerHandlerImp::processRequest (Port const& port, /* ---------------------------------------------------------------------- */ auto role = Role::FORBID; auto required = RPC::roleRequired(id.asString()); - if (jsonRPC.isMember("params") && - jsonRPC["params"].isArray() && - jsonRPC["params"].size() > 0 && - jsonRPC["params"][Json::UInt(0)].isObject()) + if (jsonRPC.isMember(jss::params) && + jsonRPC[jss::params].isArray() && + jsonRPC[jss::params].size() > 0 && + jsonRPC[jss::params][Json::UInt(0)].isObject()) { - role = requestRole(required, port, jsonRPC["params"][Json::UInt(0)], + role = requestRole(required, port, jsonRPC[jss::params][Json::UInt(0)], remoteIPAddress, user); } else @@ -671,6 +683,12 @@ ServerHandlerImp::processRequest (Port const& port, Json::Value reply (Json::objectValue); reply[jss::result] = std::move (result); + if (jsonRPC.isMember(jss::jsonrpc)) + reply[jss::jsonrpc] = jsonRPC[jss::jsonrpc]; + if (jsonRPC.isMember(jss::ripplerpc)) + reply[jss::ripplerpc] = jsonRPC[jss::ripplerpc]; + if (jsonRPC.isMember(jss::id)) + reply[jss::id] = jsonRPC[jss::id]; auto response = to_string (reply); rpc_time_.notify (static_cast ( diff --git a/src/ripple/shamap/impl/SHAMap.cpp b/src/ripple/shamap/impl/SHAMap.cpp index 3876e7c7c..c329b78d6 100644 --- a/src/ripple/shamap/impl/SHAMap.cpp +++ b/src/ripple/shamap/impl/SHAMap.cpp @@ -262,11 +262,6 @@ SHAMap::fetchNodeFromDB (SHAMapHash const& hash) const { auto root = std::dynamic_pointer_cast(root_); assert(root); -if (!root->isEmpty()) -{ - std::cerr << "isv2 = " << isv2 << '\n'; - std::cerr << "is_v2() = " << is_v2() << '\n'; -} assert(root->isEmpty()); if (isv2) { diff --git a/src/ripple/test/AbstractClient.h b/src/ripple/test/AbstractClient.h index d15cea8ba..55dbf9afd 100644 --- a/src/ripple/test/AbstractClient.h +++ b/src/ripple/test/AbstractClient.h @@ -53,6 +53,9 @@ public: Json::Value invoke(std::string const& cmd, Json::Value const& params = {}) = 0; + + /// Get RPC 1.0 or RPC 2.0 + virtual unsigned version() const = 0; }; } // test diff --git a/src/ripple/test/JSONRPCClient.h b/src/ripple/test/JSONRPCClient.h index 28ce3543e..e937c159c 100644 --- a/src/ripple/test/JSONRPCClient.h +++ b/src/ripple/test/JSONRPCClient.h @@ -29,7 +29,7 @@ namespace test { /** Returns a client using JSON-RPC over HTTP/S. */ std::unique_ptr -makeJSONRPCClient(Config const& cfg); +makeJSONRPCClient(Config const& cfg, unsigned rpc_version = 2); } // test } // ripple diff --git a/src/ripple/test/WSClient.h b/src/ripple/test/WSClient.h index dabf3ceab..c00de5fd8 100644 --- a/src/ripple/test/WSClient.h +++ b/src/ripple/test/WSClient.h @@ -47,7 +47,7 @@ public: /** Returns a client operating through WebSockets/S. */ std::unique_ptr -makeWSClient(Config const& cfg, bool v2 = true); +makeWSClient(Config const& cfg, bool v2 = true, unsigned rpc_version = 2); } // test } // ripple diff --git a/src/ripple/test/impl/JSONRPCClient.cpp b/src/ripple/test/impl/JSONRPCClient.cpp index 587bf3a88..fefc30b58 100644 --- a/src/ripple/test/impl/JSONRPCClient.cpp +++ b/src/ripple/test/impl/JSONRPCClient.cpp @@ -16,11 +16,11 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== - #include #include #include #include +#include #include #include #include @@ -76,12 +76,14 @@ class JSONRPCClient : public AbstractClient boost::asio::ip::tcp::socket stream_; beast::streambuf bin_; beast::streambuf bout_; + unsigned rpc_version_; public: explicit - JSONRPCClient(Config const& cfg) + JSONRPCClient(Config const& cfg, unsigned rpc_version) : ep_(getEndpoint(cfg)) , stream_(ios_) + , rpc_version_(rpc_version) { stream_.connect(ep_); } @@ -115,10 +117,16 @@ public: ep_.address().to_string() + ":" + std::to_string(ep_.port())); { Json::Value jr; - jr["method"] = cmd; + jr[jss::method] = cmd; + if (rpc_version_ == 2) + { + jr[jss::jsonrpc] = "2.0"; + jr[jss::ripplerpc] = "2.0"; + jr[jss::id] = 5; + } if(params) { - Json::Value& ja = jr["params"] = Json::arrayValue; + Json::Value& ja = jr[jss::params] = Json::arrayValue; ja.append(params); } req.body = to_string(jr); @@ -138,12 +146,17 @@ public: jv["status"] = jv["result"]["status"]; return jv; } + + unsigned version() const override + { + return rpc_version_; + } }; std::unique_ptr -makeJSONRPCClient(Config const& cfg) +makeJSONRPCClient(Config const& cfg, unsigned rpc_version) { - return std::make_unique(cfg); + return std::make_unique(cfg, rpc_version); } } // test diff --git a/src/ripple/test/impl/WSClient.cpp b/src/ripple/test/impl/WSClient.cpp index 596039d3c..d4de122ba 100644 --- a/src/ripple/test/impl/WSClient.cpp +++ b/src/ripple/test/impl/WSClient.cpp @@ -107,13 +107,16 @@ class WSClientImpl : public WSClient std::condition_variable cv_; std::list> msgs_; + unsigned rpc_version_; + public: - WSClientImpl(Config const& cfg, bool v2) + WSClientImpl(Config const& cfg, bool v2, unsigned rpc_version) : work_(ios_) , strand_(ios_) , thread_([&]{ ios_.run(); }) , stream_(ios_) , ws_(stream_) + , rpc_version_(rpc_version) { auto const ep = getEndpoint(cfg, v2); stream_.connect(ep); @@ -143,7 +146,15 @@ public: Json::Value jp; if(params) jp = params; - jp[jss::command] = cmd; + if (rpc_version_ == 2) + { + jp[jss::method] = cmd; + jp[jss::jsonrpc] = "2.0"; + jp[jss::ripplerpc] = "2.0"; + jp[jss::id] = 5; + } + else + jp[jss::command] = cmd; auto const s = to_string(jp); ws_.write_frame(true, buffer(s)); } @@ -167,7 +178,6 @@ public: ret[jss::status] = jss::error; return ret; } - if ((*jv).isMember(jss::status) && (*jv).isMember(jss::result)) (*jv)[jss::result][jss::status] = @@ -221,6 +231,11 @@ public: return std::move(m->jv); } + unsigned version() const override + { + return rpc_version_; + } + private: void on_read_msg(error_code const& ec) @@ -254,9 +269,9 @@ private: }; std::unique_ptr -makeWSClient(Config const& cfg, bool v2) +makeWSClient(Config const& cfg, bool v2, unsigned rpc_version) { - return std::make_unique(cfg, v2); + return std::make_unique(cfg, v2, rpc_version); } } // test diff --git a/src/ripple/test/jtx/impl/Env.cpp b/src/ripple/test/jtx/impl/Env.cpp index e04a7d717..8a11969a2 100644 --- a/src/ripple/test/jtx/impl/Env.cpp +++ b/src/ripple/test/jtx/impl/Env.cpp @@ -537,9 +537,21 @@ Env::st (JTx const& jt) Json::Value Env::do_rpc(std::vector const& args) { - auto const jv = cmdLineToJSONRPC(args, journal); - return client().invoke(jv["method"].asString(), - jv["params"][0U]); + auto jv = cmdLineToJSONRPC(args, journal); + if (!jv.isMember(jss::jsonrpc)) + { + jv[jss::jsonrpc] = "2.0"; + jv[jss::ripplerpc] = "2.0"; + jv[jss::id] = 5; + } + auto response = client().invoke(jv[jss::method].asString(), jv[jss::params][0U]); + if (jv.isMember(jss::jsonrpc)) + { + response[jss::jsonrpc] = jv[jss::jsonrpc]; + response[jss::ripplerpc] = jv[jss::ripplerpc]; + response[jss::id] = jv[jss::id]; + } + return response; } } // jtx diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index 0a2ed9d41..0dc1800c0 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -1744,6 +1744,12 @@ public: auto jrr = wsc->invoke("submit", payment); BEAST_EXPECT(jrr[jss::status] == "success"); BEAST_EXPECT(jrr[jss::result][jss::engine_result] == "tesSUCCESS"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jrr.isMember(jss::jsonrpc) && jrr[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jrr.isMember(jss::ripplerpc) && jrr[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jrr.isMember(jss::id) && jrr[jss::id] == 5); + } jrr = ledgerEntryState (env, alice, gw, "XTS"); BEAST_EXPECT(jrr[jss::node][sfBalance.fieldName][jss::value] == "-101"); diff --git a/src/test/rpc/AccountInfo_test.cpp b/src/test/rpc/AccountInfo_test.cpp index 0f55e387a..7ab924529 100644 --- a/src/test/rpc/AccountInfo_test.cpp +++ b/src/test/rpc/AccountInfo_test.cpp @@ -161,10 +161,141 @@ public: } } + // Test the "signer_lists" argument in account_info, version 2 API. + void testSignerListsV2() + { + using namespace jtx; + Env env(*this, features(featureMultiSign)); + Account const alice {"alice"}; + env.fund(XRP(1000), alice); + + auto const withoutSigners = std::string ("{ ") + + "\"jsonrpc\": \"2.0\", " + "\"ripplerpc\": \"2.0\", " + "\"id\": 5, " + "\"method\": \"account_info\", " + "\"params\": [{ " + "\"account\": \"" + alice.human() + "\"}]}"; + + auto const withSigners = std::string ("{ ") + + "\"jsonrpc\": \"2.0\", " + "\"ripplerpc\": \"2.0\", " + "\"id\": 5, " + "\"method\": \"account_info\", " + "\"params\": [{ " + "\"account\": \"" + alice.human() + "\", " + + "\"signer_lists\": true }]}"; + // Alice has no SignerList yet. + { + // account_info without the "signer_lists" argument. + auto const info = env.rpc ("json2", withoutSigners); + BEAST_EXPECT(info.isMember(jss::result) && + info[jss::result].isMember(jss::account_data)); + BEAST_EXPECT(! info[jss::result][jss::account_data]. + isMember (jss::signer_lists)); + BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 5); + } + { + // account_info with the "signer_lists" argument. + auto const info = env.rpc ("json2", withSigners); + BEAST_EXPECT(info.isMember(jss::result) && + info[jss::result].isMember(jss::account_data)); + auto const& data = info[jss::result][jss::account_data]; + BEAST_EXPECT(data.isMember (jss::signer_lists)); + auto const& signerLists = data[jss::signer_lists]; + BEAST_EXPECT(signerLists.isArray()); + BEAST_EXPECT(signerLists.size() == 0); + BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 5); + } + + // Give alice a SignerList. + Account const bogie {"bogie"}; + + Json::Value const smallSigners = signers(alice, 2, { { bogie, 3 } }); + env(smallSigners); + { + // account_info without the "signer_lists" argument. + auto const info = env.rpc ("json2", withoutSigners); + BEAST_EXPECT(info.isMember(jss::result) && + info[jss::result].isMember(jss::account_data)); + BEAST_EXPECT(! info[jss::result][jss::account_data]. + isMember (jss::signer_lists)); + BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 5); + } + { + // account_info with the "signer_lists" argument. + auto const info = env.rpc ("json2", withSigners); + BEAST_EXPECT(info.isMember(jss::result) && + info[jss::result].isMember(jss::account_data)); + auto const& data = info[jss::result][jss::account_data]; + BEAST_EXPECT(data.isMember (jss::signer_lists)); + auto const& signerLists = data[jss::signer_lists]; + BEAST_EXPECT(signerLists.isArray()); + BEAST_EXPECT(signerLists.size() == 1); + auto const& signers = signerLists[0u]; + BEAST_EXPECT(signers.isObject()); + BEAST_EXPECT(signers[sfSignerQuorum.jsonName] == 2); + auto const& signerEntries = signers[sfSignerEntries.jsonName]; + BEAST_EXPECT(signerEntries.size() == 1); + auto const& entry0 = signerEntries[0u][sfSignerEntry.jsonName]; + BEAST_EXPECT(entry0[sfSignerWeight.jsonName] == 3); + BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 5); + } + + // Give alice a big signer list + Account const demon {"demon"}; + Account const ghost {"ghost"}; + Account const haunt {"haunt"}; + Account const jinni {"jinni"}; + Account const phase {"phase"}; + Account const shade {"shade"}; + Account const spook {"spook"}; + + Json::Value const bigSigners = signers(alice, 4, { + {bogie, 1}, {demon, 1}, {ghost, 1}, {haunt, 1}, + {jinni, 1}, {phase, 1}, {shade, 1}, {spook, 1}, }); + env(bigSigners); + { + // account_info with the "signer_lists" argument. + auto const info = env.rpc ("json2", withSigners); + BEAST_EXPECT(info.isMember(jss::result) && + info[jss::result].isMember(jss::account_data)); + auto const& data = info[jss::result][jss::account_data]; + BEAST_EXPECT(data.isMember (jss::signer_lists)); + auto const& signerLists = data[jss::signer_lists]; + BEAST_EXPECT(signerLists.isArray()); + BEAST_EXPECT(signerLists.size() == 1); + auto const& signers = signerLists[0u]; + BEAST_EXPECT(signers.isObject()); + BEAST_EXPECT(signers[sfSignerQuorum.jsonName] == 4); + auto const& signerEntries = signers[sfSignerEntries.jsonName]; + BEAST_EXPECT(signerEntries.size() == 8); + for (unsigned i = 0u; i < 8; ++i) + { + auto const& entry = signerEntries[i][sfSignerEntry.jsonName]; + BEAST_EXPECT(entry.size() == 2); + BEAST_EXPECT(entry.isMember(sfAccount.jsonName)); + BEAST_EXPECT(entry[sfSignerWeight.jsonName] == 1); + } + BEAST_EXPECT(info.isMember(jss::jsonrpc) && info[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(info.isMember(jss::ripplerpc) && info[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(info.isMember(jss::id) && info[jss::id] == 5); + } + } + void run() { testErrors(); testSignerLists(); + testSignerListsV2(); } }; diff --git a/src/test/rpc/AccountLinesRPC_test.cpp b/src/test/rpc/AccountLinesRPC_test.cpp index e942db4e2..eb58a94a1 100644 --- a/src/test/rpc/AccountLinesRPC_test.cpp +++ b/src/test/rpc/AccountLinesRPC_test.cpp @@ -169,7 +169,7 @@ public: // which case the hash wins. auto const lines = env.rpc ("json", "account_lines", R"({"account": ")" + alice.human() + R"(", )" - R"("ledger_hash": ")" + to_string(ledger4Info.hash) + R"("})" + R"("ledger_hash": ")" + to_string(ledger4Info.hash) + R"(", )" R"("ledger_index": )" + std::to_string(ledger58Info.seq) + "}"); BEAST_EXPECT(lines[jss::result][jss::lines].isArray()); BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26); @@ -346,10 +346,523 @@ public: RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]); } + // test API V2 + void testAccountLines2() + { + using namespace test::jtx; + Env env(*this); + { + // account_lines with mal-formed json2 (missing id field). + auto const lines = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0")" + " }"); + BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); + } + { + // account_lines with no account. + auto const lines = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5)" + " }"); + BEAST_EXPECT(lines[jss::result][jss::error_message] == + RPC::missing_field_error(jss::account)[jss::error_message]); + BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); + } + { + // account_lines with a malformed account. + auto const lines = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": )" + R"("n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj"}]})"); + BEAST_EXPECT(lines[jss::result][jss::error_message] == + RPC::make_error(rpcBAD_SEED)[jss::error_message]); + BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); + } + Account const alice {"alice"}; + { + // account_lines on an unfunded account. + auto const lines = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"("}]})"); + BEAST_EXPECT(lines[jss::result][jss::error_message] == + RPC::make_error(rpcACT_NOT_FOUND)[jss::error_message]); + BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); + } + env.fund(XRP(10000), alice); + env.close(); + LedgerInfo const ledger3Info = env.closed()->info(); + BEAST_EXPECT(ledger3Info.seq == 3); + + { + // alice is funded but has no lines. An empty array is returned. + auto const lines = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"("}]})"); + BEAST_EXPECT(lines[jss::result][jss::lines].isArray()); + BEAST_EXPECT(lines[jss::result][jss::lines].size() == 0); + BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); + } + { + // Specify a ledger that doesn't exist. + auto const lines = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"(", )" + R"("ledger_index": "nonsense"}]})"); + BEAST_EXPECT(lines[jss::result][jss::error_message] == + "ledgerIndexMalformed"); + BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); + } + { + // Specify a different ledger that doesn't exist. + auto const lines = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"(", )" + R"("ledger_index": 50000}]})"); + BEAST_EXPECT(lines[jss::result][jss::error_message] == + "ledgerNotFound"); + BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); + } + // Create trust lines to share with alice. + Account const gw1 {"gw1"}; + env.fund(XRP(10000), gw1); + std::vector gw1Currencies; + + for (char c = 0; c <= ('Z' - 'A'); ++c) + { + // gw1 currencies have names "YAA" -> "YAZ". + gw1Currencies.push_back( + gw1[std::string("YA") + static_cast('A' + c)]); + IOU const& gw1Currency = gw1Currencies.back(); + + // Establish trust lines. + env(trust(alice, gw1Currency(100 + c))); + env(pay(gw1, alice, gw1Currency(50 + c))); + } + env.close(); + LedgerInfo const ledger4Info = env.closed()->info(); + BEAST_EXPECT(ledger4Info.seq == 4); + + // Add another set of trust lines in another ledger so we can see + // differences in historic ledgers. + Account const gw2 {"gw2"}; + env.fund(XRP(10000), gw2); + + // gw2 requires authorization. + env(fset(gw2, asfRequireAuth)); + env.close(); + std::vector gw2Currencies; + + for (char c = 0; c <= ('Z' - 'A'); ++c) + { + // gw2 currencies have names "ZAA" -> "ZAZ". + gw2Currencies.push_back( + gw2[std::string("ZA") + static_cast('A' + c)]); + IOU const& gw2Currency = gw2Currencies.back(); + + // Establish trust lines. + env(trust(alice, gw2Currency(200 + c))); + env(trust(gw2, gw2Currency(0), alice, tfSetfAuth)); + env.close(); + env(pay(gw2, alice, gw2Currency(100 + c))); + env.close(); + + // Set flags on gw2 trust lines so we can look for them. + env(trust(alice, gw2Currency(0), gw2, tfSetNoRipple | tfSetFreeze)); + } + env.close(); + LedgerInfo const ledger58Info = env.closed()->info(); + BEAST_EXPECT(ledger58Info.seq == 58); + + // A re-usable test for historic ledgers. + auto testAccountLinesHistory = + [this, &env](Account const& account, LedgerInfo const& info, int count) + { + // Get account_lines by ledger index. + auto const linesSeq = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + account.human() + R"(", )" + R"("ledger_index": )" + std::to_string(info.seq) + "}]}"); + BEAST_EXPECT(linesSeq[jss::result][jss::lines].isArray()); + BEAST_EXPECT(linesSeq[jss::result][jss::lines].size() == count); + BEAST_EXPECT(linesSeq.isMember(jss::jsonrpc) && linesSeq[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(linesSeq.isMember(jss::ripplerpc) && linesSeq[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(linesSeq.isMember(jss::id) && linesSeq[jss::id] == 5); + + // Get account_lines by ledger hash. + auto const linesHash = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + account.human() + R"(", )" + R"("ledger_hash": ")" + to_string(info.hash) + R"("}]})"); + BEAST_EXPECT(linesHash[jss::result][jss::lines].isArray()); + BEAST_EXPECT(linesHash[jss::result][jss::lines].size() == count); + BEAST_EXPECT(linesHash.isMember(jss::jsonrpc) && linesHash[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(linesHash.isMember(jss::ripplerpc) && linesHash[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(linesHash.isMember(jss::id) && linesHash[jss::id] == 5); + }; + + // Alice should have no trust lines in ledger 3. + testAccountLinesHistory (alice, ledger3Info, 0); + + // Alice should have 26 trust lines in ledger 4. + testAccountLinesHistory (alice, ledger4Info, 26); + + // Alice should have 52 trust lines in ledger 58. + testAccountLinesHistory (alice, ledger58Info, 52); + + { + // Surprisingly, it's valid to specify both index and hash, in + // which case the hash wins. + auto const lines = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"(", )" + R"("ledger_hash": ")" + to_string(ledger4Info.hash) + R"(", )" + R"("ledger_index": )" + std::to_string(ledger58Info.seq) + "}]}"); + BEAST_EXPECT(lines[jss::result][jss::lines].isArray()); + BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26); + BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); + } + { + // alice should have 52 trust lines in the current ledger. + auto const lines = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"("}]})"); + BEAST_EXPECT(lines[jss::result][jss::lines].isArray()); + BEAST_EXPECT(lines[jss::result][jss::lines].size() == 52); + BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); + } + { + // alice should have 26 trust lines with gw1. + auto const lines = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"(", )" + R"("peer": ")" + gw1.human() + R"("}]})"); + BEAST_EXPECT(lines[jss::result][jss::lines].isArray()); + BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26); + BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); + } + { + // Use a malformed peer. + auto const lines = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"(", )" + R"("peer": )" + R"("n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj"}]})"); + BEAST_EXPECT(lines[jss::result][jss::error_message] == + RPC::make_error(rpcBAD_SEED)[jss::error_message]); + BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); + } + { + // A negative limit should fail. + auto const lines = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"(", )" + R"("limit": -1}]})"); + BEAST_EXPECT(lines[jss::result][jss::error_message] == + RPC::expected_field_message(jss::limit, "unsigned integer")); + BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); + } + { + // Limit the response to 1 trust line. + auto const linesA = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"(", )" + R"("limit": 1}]})"); + BEAST_EXPECT(linesA[jss::result][jss::lines].isArray()); + BEAST_EXPECT(linesA[jss::result][jss::lines].size() == 1); + BEAST_EXPECT(linesA.isMember(jss::jsonrpc) && linesA[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(linesA.isMember(jss::ripplerpc) && linesA[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(linesA.isMember(jss::id) && linesA[jss::id] == 5); + + // Pick up from where the marker left off. We should get 51. + auto marker = linesA[jss::result][jss::marker].asString(); + auto const linesB = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"(", )" + R"("marker": ")" + marker + R"("}]})"); + BEAST_EXPECT(linesB[jss::result][jss::lines].isArray()); + BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 51); + BEAST_EXPECT(linesB.isMember(jss::jsonrpc) && linesB[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(linesB.isMember(jss::ripplerpc) && linesB[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(linesB.isMember(jss::id) && linesB[jss::id] == 5); + + // Go again from where the marker left off, but set a limit of 3. + auto const linesC = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"(", )" + R"("limit": 3, )" + R"("marker": ")" + marker + R"("}]})"); + BEAST_EXPECT(linesC[jss::result][jss::lines].isArray()); + BEAST_EXPECT(linesC[jss::result][jss::lines].size() == 3); + BEAST_EXPECT(linesC.isMember(jss::jsonrpc) && linesC[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(linesC.isMember(jss::ripplerpc) && linesC[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(linesC.isMember(jss::id) && linesC[jss::id] == 5); + + // Mess with the marker so it becomes bad and check for the error. + marker[5] = marker[5] == '7' ? '8' : '7'; + auto const linesD = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"(", )" + R"("marker": ")" + marker + R"("}]})"); + BEAST_EXPECT(linesD[jss::result][jss::error_message] == + RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]); + BEAST_EXPECT(linesD.isMember(jss::jsonrpc) && linesD[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(linesD.isMember(jss::ripplerpc) && linesD[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(linesD.isMember(jss::id) && linesD[jss::id] == 5); + } + { + // A non-string marker should also fail. + auto const lines = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"(", )" + R"("marker": true}]})"); + BEAST_EXPECT(lines[jss::result][jss::error_message] == + RPC::expected_field_message(jss::marker, "string")); + BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); + } + { + // Check that the flags we expect from alice to gw2 are present. + auto const lines = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"(", )" + R"("limit": 1, )" + R"("peer": ")" + gw2.human() + R"("}]})"); + auto const& line = lines[jss::result][jss::lines][0u]; + BEAST_EXPECT(line[jss::freeze].asBool() == true); + BEAST_EXPECT(line[jss::no_ripple].asBool() == true); + BEAST_EXPECT(line[jss::peer_authorized].asBool() == true); + BEAST_EXPECT(lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::ripplerpc) && lines[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5); + } + { + // Check that the flags we expect from gw2 to alice are present. + auto const linesA = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + gw2.human() + R"(", )" + R"("limit": 1, )" + R"("peer": ")" + alice.human() + R"("}]})"); + auto const& lineA = linesA[jss::result][jss::lines][0u]; + BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true); + BEAST_EXPECT(lineA[jss::no_ripple_peer].asBool() == true); + BEAST_EXPECT(lineA[jss::authorized].asBool() == true); + BEAST_EXPECT(linesA.isMember(jss::jsonrpc) && linesA[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(linesA.isMember(jss::ripplerpc) && linesA[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(linesA.isMember(jss::id) && linesA[jss::id] == 5); + + // Continue from the returned marker to make sure that works. + BEAST_EXPECT(linesA[jss::result].isMember(jss::marker)); + auto const marker = linesA[jss::result][jss::marker].asString(); + auto const linesB = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + gw2.human() + R"(", )" + R"("limit": 25, )" + R"("marker": ")" + marker + R"(", )" + R"("peer": ")" + alice.human() + R"("}]})"); + BEAST_EXPECT(linesB[jss::result][jss::lines].isArray()); + BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 25); + BEAST_EXPECT(! linesB[jss::result].isMember(jss::marker)); + BEAST_EXPECT(linesB.isMember(jss::jsonrpc) && linesB[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(linesB.isMember(jss::ripplerpc) && linesB[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(linesB.isMember(jss::id) && linesB[jss::id] == 5); + } + } + + // test API V2 + void testAccountLineDelete2() + { + using namespace test::jtx; + Env env(*this); + + // The goal here is to observe account_lines marker behavior if the + // entry pointed at by a returned marker is removed from the ledger. + // + // It isn't easy to explicitly delete a trust line, so we do so in a + // round-about fashion. It takes 4 actors: + // o Gateway gw1 issues USD + // o alice offers to buy 100 USD for 100 XRP. + // o becky offers to sell 100 USD for 100 XRP. + // There will now be an inferred trustline between alice and gw1. + // o alice pays her 100 USD to cheri. + // alice should now have no USD and no trustline to gw1. + Account const alice {"alice"}; + Account const becky {"becky"}; + Account const cheri {"cheri"}; + Account const gw1 {"gw1"}; + Account const gw2 {"gw2"}; + env.fund(XRP(10000), alice, becky, cheri, gw1, gw2); + env.close(); + + auto const USD = gw1["USD"]; + auto const EUR = gw2["EUR"]; + env(trust(alice, EUR(200))); + env(trust(becky, USD(200))); + env(trust(cheri, USD(200))); + env.close(); + + // becky gets 100 USD from gw1. + env(pay(gw1, becky, USD(100))); + env.close(); + + // alice offers to buy 100 USD for 100 XRP. + env(offer(alice, USD(100), XRP(100))); + env.close(); + + // becky offers to buy 100 XRP for 100 USD. + env(offer(becky, XRP(100), USD(100))); + env.close(); + + // Get account_lines for alice. Limit at 1, so we get a marker. + auto const linesBeg = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"(", )" + R"("limit": 1}]})"); + BEAST_EXPECT(linesBeg[jss::result][jss::lines][0u][jss::currency] == "EUR"); + BEAST_EXPECT(linesBeg[jss::result].isMember(jss::marker)); + BEAST_EXPECT(linesBeg.isMember(jss::jsonrpc) && linesBeg[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(linesBeg.isMember(jss::ripplerpc) && linesBeg[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(linesBeg.isMember(jss::id) && linesBeg[jss::id] == 5); + + // alice pays 100 USD to cheri. + env(pay(alice, cheri, USD(100))); + env.close(); + + // Since alice paid all her USD to cheri, alice should no longer + // have a trust line to gw1. So the old marker should now be invalid. + auto const linesEnd = env.rpc ("json2", "{ " + R"("method" : "account_lines",)" + R"("jsonrpc" : "2.0",)" + R"("ripplerpc" : "2.0",)" + R"("id" : 5,)" + R"("params": [ )" + R"({"account": ")" + alice.human() + R"(", )" + R"("marker": ")" + + linesBeg[jss::result][jss::marker].asString() + R"("}]})"); + BEAST_EXPECT(linesEnd[jss::result][jss::error_message] == + RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]); + BEAST_EXPECT(linesEnd.isMember(jss::jsonrpc) && linesEnd[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(linesEnd.isMember(jss::ripplerpc) && linesEnd[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(linesEnd.isMember(jss::id) && linesEnd[jss::id] == 5); + } + void run () { testAccountLines(); testAccountLineDelete(); + testAccountLines2(); + testAccountLineDelete2(); } }; diff --git a/src/test/rpc/Book_test.cpp b/src/test/rpc/Book_test.cpp index 86570f920..da9890ade 100644 --- a/src/test/rpc/Book_test.cpp +++ b/src/test/rpc/Book_test.cpp @@ -72,6 +72,12 @@ public: } auto jv = wsc->invoke("subscribe", books); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } if(! BEAST_EXPECT(jv[jss::status] == "success")) return; BEAST_EXPECT(jv[jss::result].isMember(jss::offers) && @@ -106,8 +112,14 @@ public: } // RPC unsubscribe - BEAST_EXPECT(wsc->invoke("unsubscribe", - books)[jss::status] == "success"); + auto jv = wsc->invoke("unsubscribe", books); + BEAST_EXPECT(jv[jss::status] == "success"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } } void @@ -143,6 +155,12 @@ public: } auto jv = wsc->invoke("subscribe", books); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } if(! BEAST_EXPECT(jv[jss::status] == "success")) return; BEAST_EXPECT(jv[jss::result].isMember(jss::offers) && @@ -181,8 +199,14 @@ public: } // RPC unsubscribe - BEAST_EXPECT(wsc->invoke("unsubscribe", - books)[jss::status] == "success"); + auto jv = wsc->invoke("unsubscribe", books); + BEAST_EXPECT(jv[jss::status] == "success"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } } void @@ -210,6 +234,12 @@ public: } auto jv = wsc->invoke("subscribe", books); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } if(! BEAST_EXPECT(jv[jss::status] == "success")) return; BEAST_EXPECT(jv[jss::result].isMember(jss::asks) && @@ -254,8 +284,14 @@ public: } // RPC unsubscribe - BEAST_EXPECT(wsc->invoke("unsubscribe", - books)[jss::status] == "success"); + auto jv = wsc->invoke("unsubscribe", books); + BEAST_EXPECT(jv[jss::status] == "success"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } } void @@ -292,6 +328,12 @@ public: } auto jv = wsc->invoke("subscribe", books); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } if(! BEAST_EXPECT(jv[jss::status] == "success")) return; BEAST_EXPECT(jv[jss::result].isMember(jss::asks) && @@ -344,8 +386,14 @@ public: } // RPC unsubscribe - BEAST_EXPECT(wsc->invoke("unsubscribe", - books)[jss::status] == "success"); + auto jv = wsc->invoke("unsubscribe", books); + BEAST_EXPECT(jv[jss::status] == "success"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } } void @@ -382,6 +430,12 @@ public: } auto jv = wsc->invoke("subscribe", books); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } if(! BEAST_EXPECT(jv[jss::status] == "success")) return; BEAST_EXPECT(jv[jss::result].isMember(jss::offers) && @@ -441,8 +495,14 @@ public: } // RPC unsubscribe - BEAST_EXPECT(wsc->invoke("unsubscribe", - books)[jss::status] == "success"); + auto jv = wsc->invoke("unsubscribe", books); + BEAST_EXPECT(jv[jss::status] == "success"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } } void @@ -496,6 +556,12 @@ public: } auto jv = wsc->invoke("subscribe", books); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } if(! BEAST_EXPECT(jv[jss::status] == "success")) return; BEAST_EXPECT(jv[jss::result].isMember(jss::offers) && @@ -563,8 +629,14 @@ public: } // RPC unsubscribe - BEAST_EXPECT(wsc->invoke("unsubscribe", - books)[jss::status] == "success"); + auto jv = wsc->invoke("unsubscribe", books); + BEAST_EXPECT(jv[jss::status] == "success"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } } void @@ -603,6 +675,12 @@ public: } auto jv = wsc->invoke("subscribe", books); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } if(! BEAST_EXPECT(jv[jss::status] == "success")) return; BEAST_EXPECT(jv[jss::result].isMember(jss::asks) && @@ -681,8 +759,14 @@ public: } // RPC unsubscribe - BEAST_EXPECT(wsc->invoke("unsubscribe", - books)[jss::status] == "success"); + auto jv = wsc->invoke("unsubscribe", books); + BEAST_EXPECT(jv[jss::status] == "success"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } } void @@ -739,6 +823,12 @@ public: } auto jv = wsc->invoke("subscribe", books); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } if(! BEAST_EXPECT(jv[jss::status] == "success")) return; BEAST_EXPECT(jv[jss::result].isMember(jss::asks) && @@ -833,8 +923,14 @@ public: } // RPC unsubscribe - BEAST_EXPECT(wsc->invoke("unsubscribe", - books)[jss::status] == "success"); + auto jv = wsc->invoke("unsubscribe", books); + BEAST_EXPECT(jv[jss::status] == "success"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } } void @@ -863,6 +959,12 @@ public: } auto jv = wsc->invoke("subscribe", books); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } if(! BEAST_EXPECT(jv[jss::status] == "success")) return; BEAST_EXPECT(jv[jss::result].isMember(jss::offers) && @@ -886,7 +988,15 @@ public: jvParams[jss::ledger_index] = "validated"; jvParams[jss::taker_gets][jss::currency] = "USD"; jvParams[jss::taker_gets][jss::issuer] = gw.human(); - auto jrr = wsc->invoke("book_offers", jvParams)[jss::result]; + + auto jv = wsc->invoke("book_offers", jvParams); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } + auto jrr = jv[jss::result]; BEAST_EXPECT(jrr[jss::offers].isArray()); BEAST_EXPECT(jrr[jss::offers].size() == 1); @@ -927,7 +1037,14 @@ public: t[jss::TakerPays] == XRP(2000).value().getJson(0); })); - jrr = wsc->invoke("book_offers", jvParams)[jss::result]; + jv = wsc->invoke("book_offers", jvParams); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } + jrr = jv[jss::result]; BEAST_EXPECT(jrr[jss::offers].isArray()); BEAST_EXPECT(jrr[jss::offers].size() == 2); @@ -946,8 +1063,14 @@ public: BEAST_EXPECT(jrNextOffer[jss::owner_funds] == "50"); BEAST_EXPECT(jrNextOffer[jss::quality] == "400000000"); - BEAST_EXPECT(wsc->invoke("unsubscribe", - books)[jss::status] == "success"); + jv = wsc->invoke("unsubscribe", books); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } + BEAST_EXPECT(jv[jss::status] == "success"); } void diff --git a/src/test/rpc/GatewayBalances_test.cpp b/src/test/rpc/GatewayBalances_test.cpp index 4f5664e37..6f9f4ccc7 100644 --- a/src/test/rpc/GatewayBalances_test.cpp +++ b/src/test/rpc/GatewayBalances_test.cpp @@ -86,6 +86,12 @@ public: auto jv = wsc->invoke("gateway_balances", qry); expect(jv[jss::status] == "success"); + if (wsc->version() == 2) + { + expect(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + expect(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + expect(jv.isMember(jss::id) && jv[jss::id] == 5); + } auto const& result = jv[jss::result]; expect(result[jss::account] == alice.human()); diff --git a/src/test/rpc/RobustTransaction_test.cpp b/src/test/rpc/RobustTransaction_test.cpp index 677bd6325..b6e6c1210 100644 --- a/src/test/rpc/RobustTransaction_test.cpp +++ b/src/test/rpc/RobustTransaction_test.cpp @@ -45,6 +45,12 @@ public: jv[jss::streams].append("transactions"); jv = wsc->invoke("subscribe", jv); BEAST_EXPECT(jv[jss::status] == "success"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } } { @@ -54,6 +60,12 @@ public: payment[jss::tx_json] = pay("alice", "bob", XRP(1)); payment[jss::tx_json][sfLastLedgerSequence.fieldName] = 1; auto jv = wsc->invoke("submit", payment); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tefMAX_LEDGER"); @@ -62,6 +74,12 @@ public: payment[jss::tx_json][sfSequence.fieldName] = env.seq("alice") - 1; jv = wsc->invoke("submit", payment); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tefPAST_SEQ"); @@ -69,6 +87,12 @@ public: payment[jss::tx_json][sfSequence.fieldName] = env.seq("alice") + 1; jv = wsc->invoke("submit", payment); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::result][jss::engine_result] == "terPRE_SEQ"); @@ -76,6 +100,12 @@ public: payment[jss::tx_json][sfSequence.fieldName] = env.seq("alice"); jv = wsc->invoke("submit", payment); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS"); @@ -84,6 +114,12 @@ public: // Finalize transactions jv = wsc->invoke("ledger_accept"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::result].isMember( jss::ledger_current_index)); } @@ -115,6 +151,12 @@ public: jv[jss::streams] = Json::arrayValue; jv[jss::streams].append("transactions"); jv = wsc->invoke("unsubscribe", jv); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } } @@ -145,6 +187,12 @@ public: jv[jss::secret] = toBase58(generateSeed("alice")); jv[jss::tx_json] = pay("alice", "bob", XRP(1)); jv = wsc->invoke("submit", jv); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS"); @@ -163,6 +211,12 @@ public: jv[jss::ledger_index_max] = -1; wsc = makeWSClient(env.app().config()); jv = wsc->invoke("account_tx", jv); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } // Check balance auto ff = jv[jss::result][jss::transactions][0u][jss::meta] @@ -189,11 +243,23 @@ public: jv[jss::secret] = toBase58(generateSeed("alice")); jv[jss::tx_json] = pay("alice", "bob", XRP(1)); jv = wsc->invoke("submit", jv); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS"); // Finalize transaction jv = wsc->invoke("ledger_accept"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::result].isMember( jss::ledger_current_index)); @@ -208,13 +274,26 @@ public: jv[jss::streams] = Json::arrayValue; jv[jss::streams].append("ledger"); jv = wsc->invoke("subscribe", jv); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } // Close ledgers for(auto i = 0; i < 8; ++i) { - BEAST_EXPECT(wsc->invoke("ledger_accept")[jss::result]. + auto jv = wsc->invoke("ledger_accept"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } + BEAST_EXPECT(jv[jss::result]. isMember(jss::ledger_current_index)); // Wait for the jobqueue to process everything @@ -233,6 +312,12 @@ public: jv[jss::streams] = Json::arrayValue; jv[jss::streams].append("ledger"); jv = wsc->invoke("unsubscribe", jv); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } } @@ -246,13 +331,26 @@ public: jv[jss::streams] = Json::arrayValue; jv[jss::streams].append("ledger"); jv = wsc->invoke("subscribe", jv); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } // Close ledgers for (auto i = 0; i < 2; ++i) { - BEAST_EXPECT(wsc->invoke("ledger_accept")[jss::result]. + auto jv = wsc->invoke("ledger_accept"); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } + BEAST_EXPECT(jv[jss::result]. isMember(jss::ledger_current_index)); // Wait for the jobqueue to process everything @@ -271,6 +369,12 @@ public: jv[jss::streams] = Json::arrayValue; jv[jss::streams].append("ledger"); jv = wsc->invoke("unsubscribe", jv); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } } @@ -283,6 +387,12 @@ public: jv[jss::ledger_index_max] = -1; wsc = makeWSClient(env.app().config()); jv = wsc->invoke("account_tx", jv); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } // Check balance auto ff = jv[jss::result][jss::transactions][0u][jss::meta] @@ -310,6 +420,12 @@ public: jv[jss::accounts_proposed].append( Account("alice").human()); jv = wsc->invoke("subscribe", jv); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } @@ -320,6 +436,12 @@ public: jv[jss::tx_json] = fset("alice", 0); jv[jss::tx_json][jss::Fee] = 10; jv = wsc->invoke("submit", jv); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::result][jss::engine_result] == "tesSUCCESS"); } @@ -341,6 +463,12 @@ public: jv[jss::accounts_proposed].append( Account("alice").human()); jv = wsc->invoke("unsubscribe", jv); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } } diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index ce2c87cac..e566082cb 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -42,6 +42,12 @@ public: stream[jss::streams] = Json::arrayValue; stream[jss::streams].append("server"); auto jv = wsc->invoke("subscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } @@ -63,6 +69,12 @@ public: { // RPC unsubscribe auto jv = wsc->invoke("unsubscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } @@ -91,6 +103,12 @@ public: stream[jss::streams] = Json::arrayValue; stream[jss::streams].append("ledger"); auto jv = wsc->invoke("subscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::result][jss::ledger_index] == 2); } @@ -120,6 +138,12 @@ public: // RPC unsubscribe auto jv = wsc->invoke("unsubscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } @@ -136,6 +160,12 @@ public: stream[jss::streams] = Json::arrayValue; stream[jss::streams].append("transactions"); auto jv = wsc->invoke("subscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } @@ -186,6 +216,12 @@ public: { // RPC unsubscribe auto jv = wsc->invoke("unsubscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } @@ -195,6 +231,12 @@ public: stream[jss::accounts] = Json::arrayValue; stream[jss::accounts].append(Account("alice").human()); auto jv = wsc->invoke("subscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } @@ -228,6 +270,12 @@ public: // RPC unsubscribe auto jv = wsc->invoke("unsubscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } @@ -243,11 +291,23 @@ public: stream[jss::streams] = Json::arrayValue; stream[jss::streams].append("manifests"); auto jv = wsc->invoke("subscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } // RPC unsubscribe auto jv = wsc->invoke("unsubscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } @@ -295,6 +355,12 @@ public: stream[jss::streams] = Json::arrayValue; stream[jss::streams].append("validations"); auto jv = wsc->invoke("subscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); } @@ -323,6 +389,12 @@ public: // RPC unsubscribe auto jv = wsc->invoke("unsubscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } BEAST_EXPECT(jv[jss::status] == "success"); }