diff --git a/src/ripple/app/ledger/LedgerToJson.h b/src/ripple/app/ledger/LedgerToJson.h index d115bd4db..5753ceb73 100644 --- a/src/ripple/app/ledger/LedgerToJson.h +++ b/src/ripple/app/ledger/LedgerToJson.h @@ -31,17 +31,12 @@ namespace ripple { struct LedgerFill { - LedgerFill (ReadView const& l, int o = 0) + LedgerFill (ReadView const& l, int o = 0, std::vector q = {}, + LedgerEntryType t = ltINVALID) : ledger (l) , options (o) - { - } - - LedgerFill(ReadView const& l, int o, - std::vector q) - : ledger(l) - , options(o) - , txQueue(q) + , txQueue(std::move(q)) + , type (t) { } @@ -58,6 +53,7 @@ struct LedgerFill ReadView const& ledger; int options; std::vector txQueue; + LedgerEntryType type; }; /** Given a Ledger and options, fill a Json::Object or Json::Value with a diff --git a/src/ripple/app/ledger/impl/LedgerToJson.cpp b/src/ripple/app/ledger/impl/LedgerToJson.cpp index 3ec931607..1e6190220 100644 --- a/src/ripple/app/ledger/impl/LedgerToJson.cpp +++ b/src/ripple/app/ledger/impl/LedgerToJson.cpp @@ -170,16 +170,19 @@ void fillJsonState(Object& json, LedgerFill const& fill) for(auto const& sle : ledger.sles) { - if (binary) + if (fill.type == ltINVALID || sle->getType () == fill.type) { - auto&& obj = appendObject(array); - obj[jss::hash] = to_string(sle->key()); - obj[jss::tx_blob] = serializeHex(*sle); + if (binary) + { + auto&& obj = appendObject(array); + obj[jss::hash] = to_string(sle->key()); + obj[jss::tx_blob] = serializeHex(*sle); + } + else if (expanded) + array.append(sle->getJson(0)); + else + array.append(to_string(sle->key())); } - else if (expanded) - array.append(sle->getJson(0)); - else - array.append(to_string(sle->key())); } } diff --git a/src/ripple/protocol/LedgerFormats.h b/src/ripple/protocol/LedgerFormats.h index d7bb895ea..a80d5d944 100644 --- a/src/ripple/protocol/LedgerFormats.h +++ b/src/ripple/protocol/LedgerFormats.h @@ -21,6 +21,7 @@ #define RIPPLE_PROTOCOL_LEDGERFORMATS_H_INCLUDED #include +#include namespace ripple { @@ -163,6 +164,9 @@ private: void addCommonFields (Item& item); }; +std::pair + chooseLedgerEntryType(Json::Value const& params); + } // ripple #endif diff --git a/src/ripple/protocol/impl/LedgerFormats.cpp b/src/ripple/protocol/impl/LedgerFormats.cpp index 978555d55..5db5913e7 100644 --- a/src/ripple/protocol/impl/LedgerFormats.cpp +++ b/src/ripple/protocol/impl/LedgerFormats.cpp @@ -18,7 +18,12 @@ //============================================================================== #include +#include +#include #include +#include +#include +#include namespace ripple { @@ -165,4 +170,49 @@ LedgerFormats::getInstance () return instance; } +std::pair +chooseLedgerEntryType(Json::Value const& params) +{ + std::pair result{RPC::Status::OK, ltINVALID}; + if (params.isMember(jss::type)) + { + static + std::array, 9> const types + {{ + { jss::account, ltACCOUNT_ROOT }, + { jss::amendments, ltAMENDMENTS }, + { jss::directory, ltDIR_NODE }, + { jss::fee, ltFEE_SETTINGS }, + { jss::hashes, ltLEDGER_HASHES }, + { jss::offer, ltOFFER }, + { jss::signer_list, ltSIGNER_LIST }, + { jss::state, ltRIPPLE_STATE }, + { jss::ticket, ltTICKET } + }}; + + auto const& p = params[jss::type]; + if (!p.isString()) + { + result.first = RPC::Status{rpcINVALID_PARAMS, + "Invalid field 'type', not string."}; + assert(result.first.type() == RPC::Status::Type::error_code_i); + return result; + } + + auto const filter = p.asString (); + auto iter = std::find_if (types.begin (), types.end (), + [&filter](decltype (types.front ())& t) + { return t.first == filter; }); + if (iter == types.end ()) + { + result.first = RPC::Status{rpcINVALID_PARAMS, + "Invalid field 'type'."}; + assert(result.first.type() == RPC::Status::Type::error_code_i); + return result; + } + result.second = iter->second; + } + return result; +} + } // ripple diff --git a/src/ripple/rpc/Status.h b/src/ripple/rpc/Status.h index 00769c00e..586bc888a 100644 --- a/src/ripple/rpc/Status.h +++ b/src/ripple/rpc/Status.h @@ -43,7 +43,7 @@ public: using Code = int; using Strings = std::vector ; - static const Code OK = 0; + static constexpr Code OK = 0; Status () = default; diff --git a/src/ripple/rpc/handlers/AccountObjects.cpp b/src/ripple/rpc/handlers/AccountObjects.cpp index 206f70ef9..a1931a083 100644 --- a/src/ripple/rpc/handlers/AccountObjects.cpp +++ b/src/ripple/rpc/handlers/AccountObjects.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -72,36 +73,12 @@ Json::Value doAccountObjects (RPC::Context& context) if (! ledger->exists(keylet::account (accountID))) return rpcError (rpcACT_NOT_FOUND); - auto type = ltINVALID; - if (params.isMember (jss::type)) + auto type = chooseLedgerEntryType(params); + if (type.first) { - static - std::array, 9> const - types - {{ - { jss::account, ltACCOUNT_ROOT }, - { jss::amendments, ltAMENDMENTS }, - { jss::directory, ltDIR_NODE }, - { jss::fee, ltFEE_SETTINGS }, - { jss::hashes, ltLEDGER_HASHES }, - { jss::offer, ltOFFER }, - { jss::signer_list, ltSIGNER_LIST }, - { jss::state, ltRIPPLE_STATE }, - { jss::ticket, ltTICKET } - }}; - - auto const& p = params[jss::type]; - if (! p.isString ()) - return RPC::expected_field_error (jss::type, "string"); - - auto const filter = p.asString (); - auto iter = std::find_if (types.begin (), types.end (), - [&filter](decltype (types.front ())& t) - { return t.first == filter; }); - if (iter == types.end ()) - return RPC::invalid_field_error (jss::type); - - type = iter->second; + result.clear(); + type.first.inject(result); + return result; } unsigned int limit; @@ -131,7 +108,7 @@ Json::Value doAccountObjects (RPC::Context& context) return RPC::invalid_field_error (jss::marker); } - if (! RPC::getAccountObjects (*ledger, accountID, type, + if (! RPC::getAccountObjects (*ledger, accountID, type.second, dirIndex, entryIndex, limit, result)) { result[jss::account_objects] = Json::arrayValue; diff --git a/src/ripple/rpc/handlers/LedgerData.cpp b/src/ripple/rpc/handlers/LedgerData.cpp index 89877012d..9cb0a8fbc 100644 --- a/src/ripple/rpc/handlers/LedgerData.cpp +++ b/src/ripple/rpc/handlers/LedgerData.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,7 @@ namespace ripple { // limit: integer, maximum number of entries // marker: opaque, resume point // binary: boolean, format +// type: string // optional, defaults to all ledger node types // Outputs: // ledger_hash: chosen ledger's hash // ledger_index: chosen ledger's index @@ -84,6 +86,13 @@ Json::Value doLedgerData (RPC::Context& context) LedgerFill::Options::binary : 0)); } + auto type = chooseLedgerEntryType(params); + if (type.first) + { + jvResult.clear(); + type.first.inject(jvResult); + return jvResult; + } Json::Value& nodes = jvResult[jss::state]; auto e = lpLedger->sles.end(); @@ -98,16 +107,19 @@ Json::Value doLedgerData (RPC::Context& context) break; } - if (isBinary) + if (type.second == ltINVALID || sle->getType () == type.second) { - Json::Value& entry = nodes.append (Json::objectValue); - entry[jss::data] = serializeHex(*sle); - entry[jss::index] = to_string(sle->key()); - } - else - { - Json::Value& entry = nodes.append (sle->getJson (0)); - entry[jss::index] = to_string(sle->key()); + if (isBinary) + { + Json::Value& entry = nodes.append (Json::objectValue); + entry[jss::data] = serializeHex(*sle); + entry[jss::index] = to_string(sle->key()); + } + else + { + Json::Value& entry = nodes.append (sle->getJson (0)); + entry[jss::index] = to_string(sle->key()); + } } } diff --git a/src/ripple/rpc/handlers/LedgerHandler.cpp b/src/ripple/rpc/handlers/LedgerHandler.cpp index 857617a05..2f959c3e9 100644 --- a/src/ripple/rpc/handlers/LedgerHandler.cpp +++ b/src/ripple/rpc/handlers/LedgerHandler.cpp @@ -55,6 +55,10 @@ Status LedgerHandler::check() bool const binary = params[jss::binary].asBool(); bool const owner_funds = params[jss::owner_funds].asBool(); bool const queue = params[jss::queue].asBool(); + auto type = chooseLedgerEntryType(params); + if (type.first) + return type.first; + type_ = type.second; options_ = (full ? LedgerFill::full : 0) | (expand ? LedgerFill::expand : 0) diff --git a/src/ripple/rpc/handlers/LedgerHandler.h b/src/ripple/rpc/handlers/LedgerHandler.h index a2306b7c0..4fcd9c37a 100644 --- a/src/ripple/rpc/handlers/LedgerHandler.h +++ b/src/ripple/rpc/handlers/LedgerHandler.h @@ -76,6 +76,7 @@ private: std::vector queueTxs_; Json::Value result_; int options_ = 0; + LedgerEntryType type_; }; //////////////////////////////////////////////////////////////////////////////// @@ -89,7 +90,7 @@ void LedgerHandler::writeResult (Object& value) if (ledger_) { Json::copyFrom (value, result_); - addJson (value, {*ledger_, options_, queueTxs_}); + addJson (value, {*ledger_, options_, queueTxs_, type_}); } else { diff --git a/src/ripple/rpc/impl/RPCHandler.cpp b/src/ripple/rpc/impl/RPCHandler.cpp index 0e35a621f..7e891b861 100644 --- a/src/ripple/rpc/impl/RPCHandler.cpp +++ b/src/ripple/rpc/impl/RPCHandler.cpp @@ -139,7 +139,6 @@ error_code_i fillHandler (Context& context, JLOG (context.j.trace()) << "COMMAND:" << strCommand; JLOG (context.j.trace()) << "REQUEST:" << context.params; - auto handler = getHandler(strCommand); if (!handler) diff --git a/src/ripple/rpc/impl/Status.cpp b/src/ripple/rpc/impl/Status.cpp index 167d08895..0b8442dfd 100644 --- a/src/ripple/rpc/impl/Status.cpp +++ b/src/ripple/rpc/impl/Status.cpp @@ -23,6 +23,8 @@ namespace ripple { namespace RPC { +constexpr Status::Code Status::OK; + std::string Status::codeString () const { if (!*this) diff --git a/src/test/rpc/LedgerData_test.cpp b/src/test/rpc/LedgerData_test.cpp index 8546930f6..ae8d9b478 100644 --- a/src/test/rpc/LedgerData_test.cpp +++ b/src/test/rpc/LedgerData_test.cpp @@ -18,6 +18,7 @@ //============================================================================== #include +#include #include #include @@ -58,7 +59,6 @@ public: } env.close(); - // no limit specified // with no limit specified, we get the max_limit if the total number of // accounts is greater than max, which it is here Json::Value jvParams; @@ -101,7 +101,6 @@ public: } env.close(); - // no limit specified // with no limit specified, we should get all of our fund entries // plus three more related to the gateway setup Json::Value jvParams; @@ -189,7 +188,6 @@ public: } env.close(); - // no limit specified // with no limit specified, we should get all of our fund entries // plus three more related to the gateway setup Json::Value jvParams; @@ -264,6 +262,151 @@ public: } } + void testLedgerType() + { + // Put a bunch of different LedgerEntryTypes into a ledger + using namespace test::jtx; + Env env { *this, envconfig(validator, ""), + features(featureMultiSign, featureTickets) }; + Account const gw { "gateway" }; + auto const USD = gw["USD"]; + env.fund(XRP(100000), gw); + + int const num_accounts = 10; + + for (auto i = 0; i < num_accounts; i++) + { + Account const bob { std::string("bob") + std::to_string(i) }; + env.fund(XRP(1000), bob); + } + env(offer(Account{"bob0"}, USD(100), XRP(100))); + env.trust(Account{"bob2"}["USD"](100), Account{"bob3"}); + + auto majorities = getMajorityAmendments(*env.closed()); + for (int i = 0; i <= 256; ++i) + { + env.close(); + majorities = getMajorityAmendments(*env.closed()); + if (!majorities.empty()) + break; + } + env(signers(Account{"bob0"}, 1, {{Account{"bob1"}, 1}, {Account{"bob2"}, 1}})); + env(ticket::create(env.master)); + env.close(); + + // Now fetch each type + + { // jvParams[jss::type] = "account"; + Json::Value jvParams; + jvParams[jss::ledger_index] = "current"; + jvParams[jss::type] = "account"; + auto const jrr = env.rpc ( "json", "ledger_data", + boost::lexical_cast(jvParams)) [jss::result]; + BEAST_EXPECT( checkArraySize(jrr[jss::state], 12) ); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT( j["LedgerEntryType"] == "AccountRoot" ); + } + + { // jvParams[jss::type] = "amendments"; + Json::Value jvParams; + jvParams[jss::ledger_index] = "current"; + jvParams[jss::type] = "amendments"; + auto const jrr = env.rpc ( "json", "ledger_data", + boost::lexical_cast(jvParams)) [jss::result]; + BEAST_EXPECT( checkArraySize(jrr[jss::state], 1) ); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT( j["LedgerEntryType"] == "Amendments" ); + } + + { // jvParams[jss::type] = "directory"; + Json::Value jvParams; + jvParams[jss::ledger_index] = "current"; + jvParams[jss::type] = "directory"; + auto const jrr = env.rpc ( "json", "ledger_data", + boost::lexical_cast(jvParams)) [jss::result]; + BEAST_EXPECT( checkArraySize(jrr[jss::state], 5) ); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT( j["LedgerEntryType"] == "DirectoryNode" ); + } + + { // jvParams[jss::type] = "fee"; + Json::Value jvParams; + jvParams[jss::ledger_index] = "current"; + jvParams[jss::type] = "fee"; + auto const jrr = env.rpc ( "json", "ledger_data", + boost::lexical_cast(jvParams)) [jss::result]; + BEAST_EXPECT( checkArraySize(jrr[jss::state], 1) ); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT( j["LedgerEntryType"] == "FeeSettings" ); + } + + { // jvParams[jss::type] = "hashes"; + Json::Value jvParams; + jvParams[jss::ledger_index] = "current"; + jvParams[jss::type] = "hashes"; + auto const jrr = env.rpc ( "json", "ledger_data", + boost::lexical_cast(jvParams)) [jss::result]; + BEAST_EXPECT( checkArraySize(jrr[jss::state], 2) ); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT( j["LedgerEntryType"] == "LedgerHashes" ); + } + + { // jvParams[jss::type] = "offer"; + Json::Value jvParams; + jvParams[jss::ledger_index] = "current"; + jvParams[jss::type] = "offer"; + auto const jrr = env.rpc ( "json", "ledger_data", + boost::lexical_cast(jvParams)) [jss::result]; + BEAST_EXPECT( checkArraySize(jrr[jss::state], 1) ); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT( j["LedgerEntryType"] == "Offer" ); + } + + { // jvParams[jss::type] = "signer_list"; + Json::Value jvParams; + jvParams[jss::ledger_index] = "current"; + jvParams[jss::type] = "signer_list"; + auto const jrr = env.rpc ( "json", "ledger_data", + boost::lexical_cast(jvParams)) [jss::result]; + BEAST_EXPECT( checkArraySize(jrr[jss::state], 1) ); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT( j["LedgerEntryType"] == "SignerList" ); + } + + { // jvParams[jss::type] = "state"; + Json::Value jvParams; + jvParams[jss::ledger_index] = "current"; + jvParams[jss::type] = "state"; + auto const jrr = env.rpc ( "json", "ledger_data", + boost::lexical_cast(jvParams)) [jss::result]; + BEAST_EXPECT( checkArraySize(jrr[jss::state], 1) ); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT( j["LedgerEntryType"] == "RippleState" ); + } + + { // jvParams[jss::type] = "ticket"; + Json::Value jvParams; + jvParams[jss::ledger_index] = "current"; + jvParams[jss::type] = "ticket"; + auto const jrr = env.rpc ( "json", "ledger_data", + boost::lexical_cast(jvParams)) [jss::result]; + BEAST_EXPECT( checkArraySize(jrr[jss::state], 1) ); + for (auto const& j : jrr[jss::state]) + BEAST_EXPECT( j["LedgerEntryType"] == "Ticket" ); + } + + { // jvParams[jss::type] = "misspelling"; + Json::Value jvParams; + jvParams[jss::ledger_index] = "current"; + jvParams[jss::type] = "misspelling"; + auto const jrr = env.rpc ( "json", "ledger_data", + boost::lexical_cast(jvParams)) [jss::result]; + BEAST_EXPECT( jrr.isMember("error") ); + BEAST_EXPECT( jrr["error"] == "invalidParams" ); + BEAST_EXPECT( jrr["error_message"] == "Invalid field 'type'." ); + } + } + void run() { testCurrentLedgerToLimits(true); @@ -272,6 +415,7 @@ public: testBadInput(); testMarkerFollow(); testLedgerHeader(); + testLedgerType(); } }; diff --git a/src/test/rpc/LedgerRPC_test.cpp b/src/test/rpc/LedgerRPC_test.cpp index e5d6316af..f6ecbd214 100644 --- a/src/test/rpc/LedgerRPC_test.cpp +++ b/src/test/rpc/LedgerRPC_test.cpp @@ -652,6 +652,44 @@ class LedgerRPC_test : public beast::unit_test::suite } + void testLedgerAccountsType() + { + testcase("Ledger Request, Accounts Option"); + using namespace test::jtx; + + Env env {*this}; + + env.close(); + + std::string index; + { + Json::Value jvParams; + jvParams[jss::ledger_index] = 3u; + jvParams[jss::accounts] = true; + jvParams[jss::expand] = true; + jvParams[jss::type] = "hashes"; + auto const jrr = env.rpc ( "json", "ledger", to_string(jvParams) ) [jss::result]; + BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState)); + BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray()); + BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 1u); + BEAST_EXPECT(jrr[jss::ledger][jss::accountState][0u]["LedgerEntryType"] + == "LedgerHashes"); + index = jrr[jss::ledger][jss::accountState][0u]["index"].asString(); + } + { + Json::Value jvParams; + jvParams[jss::ledger_index] = 3u; + jvParams[jss::accounts] = true; + jvParams[jss::expand] = false; + jvParams[jss::type] = "hashes"; + auto const jrr = env.rpc ( "json", "ledger", to_string(jvParams) ) [jss::result]; + BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState)); + BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray()); + BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 1u); + BEAST_EXPECT(jrr[jss::ledger][jss::accountState][0u] == index); + } + } + public: void run () { @@ -668,6 +706,7 @@ public: testLookupLedger(); testNoQueue(); testQueue(); + testLedgerAccountsType(); } };