From a7438eccc50d7a8d11d5dee202caec6502f77867 Mon Sep 17 00:00:00 2001 From: Bart Date: Fri, 14 Feb 2025 11:12:19 -0500 Subject: [PATCH] Support canonical ledger entry names (#5271) This change enhances the filtering in the ledger, ledger_data, and account_objects methods by also supporting filtering by the canonical name of the LedgerEntryType using case-insensitive matching. --- API-CHANGELOG.md | 1 + src/test/rpc/RPCHelpers_test.cpp | 92 +++++++++++++++++++++++++++++ src/xrpld/rpc/detail/RPCHelpers.cpp | 24 ++++---- 3 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 src/test/rpc/RPCHelpers_test.cpp diff --git a/API-CHANGELOG.md b/API-CHANGELOG.md index fda03c2d0..0d5d8a819 100644 --- a/API-CHANGELOG.md +++ b/API-CHANGELOG.md @@ -90,6 +90,7 @@ As of 2025-01-28, version 2.4.0 is in development. You can use a pre-release ver ### Additions and bugfixes in 2.4.0 - `ledger_entry`: `state` is added an alias for `ripple_state`. +- `ledger_entry`: Enables case-insensitive filtering by canonical name in addition to case-sensitive filtering by RPC name. - `validators`: Added new field `validator_list_threshold` in response. - `simulate`: A new RPC that executes a [dry run of a transaction submission](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0069d-simulate#2-rpc-simulate) - Signing methods autofill fees better and properly handle transactions that don't have a base fee, and will also autofill the `NetworkID` field. diff --git a/src/test/rpc/RPCHelpers_test.cpp b/src/test/rpc/RPCHelpers_test.cpp new file mode 100644 index 000000000..11a0ef787 --- /dev/null +++ b/src/test/rpc/RPCHelpers_test.cpp @@ -0,0 +1,92 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012, 2013 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 + +namespace ripple { +namespace test { + +class RPCHelpers_test : public beast::unit_test::suite +{ +public: + void + testChooseLedgerEntryType() + { + testcase("ChooseLedgerEntryType"); + + // Test no type. + Json::Value tx = Json::objectValue; + auto result = RPC::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == RPC::Status::OK); + BEAST_EXPECT(result.second == 0); + + // Test empty type. + tx[jss::type] = ""; + result = RPC::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == RPC::Status{rpcINVALID_PARAMS}); + BEAST_EXPECT(result.second == 0); + + // Test type using canonical name in mixedcase. + tx[jss::type] = "MPTokenIssuance"; + result = RPC::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == RPC::Status::OK); + BEAST_EXPECT(result.second == ltMPTOKEN_ISSUANCE); + + // Test type using canonical name in lowercase. + tx[jss::type] = "mptokenissuance"; + result = RPC::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == RPC::Status::OK); + BEAST_EXPECT(result.second == ltMPTOKEN_ISSUANCE); + + // Test type using RPC name with exact match. + tx[jss::type] = "mpt_issuance"; + result = RPC::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == RPC::Status::OK); + BEAST_EXPECT(result.second == ltMPTOKEN_ISSUANCE); + + // Test type using RPC name with inexact match. + tx[jss::type] = "MPT_Issuance"; + result = RPC::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == RPC::Status{rpcINVALID_PARAMS}); + BEAST_EXPECT(result.second == 0); + + // Test invalid type. + tx[jss::type] = 1234; + result = RPC::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == RPC::Status{rpcINVALID_PARAMS}); + BEAST_EXPECT(result.second == 0); + + // Test unknown type. + tx[jss::type] = "unknown"; + result = RPC::chooseLedgerEntryType(tx); + BEAST_EXPECT(result.first == RPC::Status{rpcINVALID_PARAMS}); + BEAST_EXPECT(result.second == 0); + } + + void + run() override + { + testChooseLedgerEntryType(); + } +}; + +BEAST_DEFINE_TESTSUITE(RPCHelpers, app, ripple); + +} // namespace test +} // namespace ripple diff --git a/src/xrpld/rpc/detail/RPCHelpers.cpp b/src/xrpld/rpc/detail/RPCHelpers.cpp index 5717e47a6..e5dbb2ef4 100644 --- a/src/xrpld/rpc/detail/RPCHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCHelpers.cpp @@ -35,8 +35,7 @@ #include #include - -#include +#include #include #include @@ -1058,18 +1057,19 @@ chooseLedgerEntryType(Json::Value const& params) std::pair result{RPC::Status::OK, ltANY}; if (params.isMember(jss::type)) { - static constexpr auto types = - std::to_array>({ + static constexpr auto types = std::to_array< + std::tuple>({ #pragma push_macro("LEDGER_ENTRY") #undef LEDGER_ENTRY -#define LEDGER_ENTRY(tag, value, name, rpcName, fields) {jss::rpcName, tag}, +#define LEDGER_ENTRY(tag, value, name, rpcName, fields) \ + {jss::name, jss::rpcName, tag}, #include #undef LEDGER_ENTRY #pragma pop_macro("LEDGER_ENTRY") - }); + }); auto const& p = params[jss::type]; if (!p.isString()) @@ -1082,10 +1082,14 @@ chooseLedgerEntryType(Json::Value const& params) return result; } + // Use the passed in parameter to find a ledger type based on matching + // against the canonical name (case-insensitive) or the RPC name + // (case-sensitive). auto const filter = p.asString(); - auto iter = std::find_if( - types.begin(), types.end(), [&filter](decltype(types.front())& t) { - return t.first == filter; + const auto iter = + std::ranges::find_if(types, [&filter](decltype(types.front())& t) { + return boost::iequals(std::get<0>(t), filter) || + std::get<1>(t) == filter; }); if (iter == types.end()) { @@ -1097,7 +1101,7 @@ chooseLedgerEntryType(Json::Value const& params) "type"); return result; } - result.second = iter->second; + result.second = std::get<2>(*iter); } return result; }