diff --git a/conan.lock b/conan.lock index 419353199..9f3cb7b85 100644 --- a/conan.lock +++ b/conan.lock @@ -3,7 +3,7 @@ "requires": [ "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708", "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688", - "xrpl-rpc-spec/0.1.11#0b522590471c035e42c94fa74e279816%1789396974.280666", + "xrpl-rpc-spec/0.1.12#7c4f5e8a178202fff9a9f529beaa94b4%1789487052.480839", "xrpl/3.4.0-rc1#19678cbb46117ef8a669558ad19d6a1f%1789050823.813662", "sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447", "spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1782736610.443882", diff --git a/conanfile.py b/conanfile.py index fc254044c..83fe26443 100644 --- a/conanfile.py +++ b/conanfile.py @@ -17,7 +17,7 @@ class ClioConan(ConanFile): "fmt/12.1.0", "libbacktrace/cci.20210118", "spdlog/1.17.0", - "xrpl-rpc-spec/0.1.11", + "xrpl-rpc-spec/0.1.12", "xrpl/3.4.0-rc1", ] diff --git a/src/rpc/CredentialHelpers.cpp b/src/rpc/CredentialHelpers.cpp index f89f904f1..be54bb244 100644 --- a/src/rpc/CredentialHelpers.cpp +++ b/src/rpc/CredentialHelpers.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -27,9 +26,9 @@ #include #include #include -#include #include #include +#include namespace rpc::credentials { @@ -99,7 +98,7 @@ parseAuthorizeCredentials(boost::json::array const& jv) std::expected fetchCredentialArray( - std::optional const& credID, + std::vector const& credIDs, xrpl::AccountID const& srcAcc, BackendInterface const& backend, xrpl::LedgerHeader const& info, @@ -107,21 +106,11 @@ fetchCredentialArray( ) { xrpl::STArray authCreds; - std::unordered_set elems; - for (auto const& elem : *credID) { // NOLINT(bugprone-unchecked-optional-access) - ASSERT( - elem.is_string(), "should already be checked in validators.hpp that elem is a string." - ); - - if (elems.contains(elem.as_string())) + std::unordered_set seen; + for (auto const& credHash : credIDs) { + if (seen.contains(credHash)) return Error{Status{RippledError::RpcBadCredentials, "duplicates in credentials."}}; - elems.insert(elem.as_string()); - - xrpl::uint256 credHash; - ASSERT( - credHash.parseHex(boost::json::value_to(elem)), - "should already be checked in validators.hpp that elem is a uint256 hex" - ); + seen.insert(credHash); auto const credKeylet = xrpl::keylet::credential(credHash).key; auto const credLedgerObject = backend.fetchLedgerObject(credKeylet, info.seq, yield); diff --git a/src/rpc/CredentialHelpers.hpp b/src/rpc/CredentialHelpers.hpp index 1cf27e652..58cefd946 100644 --- a/src/rpc/CredentialHelpers.hpp +++ b/src/rpc/CredentialHelpers.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -17,6 +18,7 @@ #include #include #include +#include namespace rpc::credentials { @@ -52,7 +54,7 @@ parseAuthorizeCredentials(boost::json::array const& jv); /** * @brief Get Array of Credential objects * - * @param credID Array of CredentialID's to parse + * @param credIDs The credential IDs to look up * @param srcAcc The Source Account * @param backend backend interface * @param info The ledger header @@ -61,7 +63,7 @@ parseAuthorizeCredentials(boost::json::array const& jv); */ std::expected fetchCredentialArray( - std::optional const& credID, + std::vector const& credIDs, xrpl::AccountID const& srcAcc, BackendInterface const& backend, xrpl::LedgerHeader const& info, diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index f6777f003..0315d3ad7 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -1784,47 +1784,4 @@ toJsonWithBinaryTx(data::TransactionAndMetadata const& txnPlusMeta, std::uint32_ return obj; } -std::optional -parseDelegateType(boost::json::value const& delegateType) -{ - if (not delegateType.is_string()) - return {}; - - auto const& type = delegateType.as_string(); - - if (type == JS(authorizer)) - return DelegateFilter::Role::Authorizer; - if (type == JS(actor)) - return DelegateFilter::Role::Actor; - - return {}; -} - -std::optional -parseDelegateFilter(boost::json::object const& delegateObject) -{ - if (!delegateObject.contains(JS(delegate_filter))) - return {}; - - auto const& filterVal = delegateObject.at(JS(delegate_filter)); - if (!filterVal.is_string()) - return {}; - - auto const delegateTypeOpt = parseDelegateType(filterVal.as_string()); - if (!delegateTypeOpt.has_value()) - return {}; - - std::optional counterParty; - if (delegateObject.contains(JS(counter_party))) { - auto const& counterpartyVal = delegateObject.at(JS(counter_party)); - - if (!counterpartyVal.is_string()) - return {}; - - counterParty = counterpartyVal.as_string(); - } - - return DelegateFilter{*delegateTypeOpt, std::move(counterParty)}; -} - } // namespace rpc diff --git a/src/rpc/RPCHelpers.hpp b/src/rpc/RPCHelpers.hpp index 0e10dfbf0..2b2c338bd 100644 --- a/src/rpc/RPCHelpers.hpp +++ b/src/rpc/RPCHelpers.hpp @@ -930,22 +930,4 @@ getDeliveredAmount( uint32_t date ); -/** - * @brief Parse the delegate type from a JSON value - * - * @param delegateType The JSON value containing the delegate type string - * @return The parsed delegate type or std::nullopt if the input is invalid or not a string - */ -std::optional -parseDelegateType(boost::json::value const& delegateType); - -/** - * @brief Parse a delegate filter object from JSON - * - * @param delegateObject The JSON object containing the delegate filter input from user - * @return The constructed DelegateFilter or std::nullopt if parsing fails - */ -std::optional -parseDelegateFilter(boost::json::object const& delegateObject); - } // namespace rpc diff --git a/src/rpc/common/JsonBool.hpp b/src/rpc/common/JsonBool.hpp deleted file mode 100644 index 6ec80883c..000000000 --- a/src/rpc/common/JsonBool.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -namespace rpc { - -/** - * @brief A wrapper around bool that allows conversion from any JSON value. - * - */ -using JsonBool = rpc::spec::JsonBool; - -} // namespace rpc diff --git a/src/rpc/common/Types.hpp b/src/rpc/common/Types.hpp index d8697f5af..396d14d10 100644 --- a/src/rpc/common/Types.hpp +++ b/src/rpc/common/Types.hpp @@ -14,7 +14,6 @@ #include #include -#include #include #include @@ -175,35 +174,6 @@ struct AccountCursor { } }; -/** - * @brief A delegate object used filter account_tx by specific delegate accounts - */ -struct DelegateFilter { - /** - * @brief A delegate type used in delegate filter - */ - enum class Role : std::uint8_t { - Actor, /**< This account is the *active* sender, acting on behalf of another party. - * e.g., Account A in "A sends payment to B on behalf of C." */ - - Authorizer /**< This account is the *passive* party whose funds are being moved from. - * e.g., Account C in "A sends payment to B on behalf of C." */ - }; - - /** - * @brief Construct a delegate filter - * @param role The delegate type to filter by - * @param counterParty The optional counterparty to further narrow the filter by - */ - explicit DelegateFilter(Role role, std::optional counterParty = std::nullopt) - : delegateType(role), counterParty(std::move(counterParty)) - { - } - - Role delegateType; - std::optional counterParty; -}; - /** * @brief Convert an empty output to a JSON object * diff --git a/src/rpc/common/Validators.cpp b/src/rpc/common/Validators.cpp index bc31299fa..c6b7ca529 100644 --- a/src/rpc/common/Validators.cpp +++ b/src/rpc/common/Validators.cpp @@ -108,25 +108,6 @@ CustomValidator CustomValidators::ledgerIndexValidator = return MaybeError{}; }}; -CustomValidator CustomValidators::ledgerTypeValidator = - CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { - if (!value.is_string()) { - return Error{Status{ - RippledError::RpcInvalidParams, fmt::format("Invalid field '{}', not string.", key) - }}; - } - - auto const type = - rpc::spec::ledgerEntryTypeFromStr(boost::json::value_to(value)); - if (type == xrpl::ltANY) { - return Error{ - Status{RippledError::RpcInvalidParams, fmt::format("Invalid field '{}'.", key)} - }; - } - - return MaybeError{}; - }}; - CustomValidator CustomValidators::accountValidator = CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { if (!value.is_string()) @@ -418,34 +399,4 @@ CustomValidator CustomValidators::authorizeCredentialValidator = return MaybeError{}; }}; -CustomValidator CustomValidators::delegateValidator = - CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError { - if (not value.is_object()) - return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotObject"}}; - - auto const& delegate = value.as_object(); - if (!delegate.contains(JS(delegate_filter))) { - return Error{Status{ - RippledError::RpcInvalidParams, "Field 'delegate_filter' is required but missing." - }}; - } - - if (!parseDelegateType(delegate.at(JS(delegate_filter))).has_value()) { - return Error{Status{ - RippledError::RpcInvalidParams, - "Field 'delegate_filter' value must be 'actor' or 'authorizer'." - }}; - } - - if (delegate.contains(JS(counter_party)) && - !accountValidator.verify(delegate, JS(counter_party))) { - return Error{Status{ - RippledError::RpcActMalformed, - "Field 'counter_party' value must be a valid account." - }}; - } - - return MaybeError{}; - }}; - } // namespace rpc::validation diff --git a/src/rpc/common/Validators.hpp b/src/rpc/common/Validators.hpp index 7b8afce90..38a04f302 100644 --- a/src/rpc/common/Validators.hpp +++ b/src/rpc/common/Validators.hpp @@ -488,14 +488,6 @@ struct CustomValidators final { */ static CustomValidator ledgerIndexValidator; - /** - * @brief Provides a validator for ledger type. - * - * A type accepts canonical names of ledger entry types (case insensitive) or short names. - * Used by ledger_data. - */ - static CustomValidator ledgerTypeValidator; - /** * @brief Provides a commonly used validator for accounts. * @@ -608,13 +600,6 @@ struct CustomValidators final { * object lookup in ledger_entry. */ static CustomValidator credentialTypeValidator; - - /** - * @brief Provides a validator for validating filtering by delegation. - * - * Used by account_tx if user wants to filter by delegation. - */ - static CustomValidator delegateValidator; }; /** diff --git a/src/rpc/filters/TransactionFilter.hpp b/src/rpc/filters/TransactionFilter.hpp index ffe541cbc..de27a1b3b 100644 --- a/src/rpc/filters/TransactionFilter.hpp +++ b/src/rpc/filters/TransactionFilter.hpp @@ -3,15 +3,22 @@ #include "data/Types.hpp" #include "rpc/common/Types.hpp" +#include #include #include namespace rpc { +/** + * @brief The delegate filter used to narrow account_tx by delegation. + */ +using DelegateFilter = rpc::spec::handlers::account_tx::DelegateFilter; + /** * @brief Interface for filtering transactions. */ +// TODO: consider moving to rpc-spec later on class TransactionFilter { public: /** diff --git a/src/rpc/filters/impl/DelegateTransactionsFilter.cpp b/src/rpc/filters/impl/DelegateTransactionsFilter.cpp index d960a497a..a4719d8ba 100644 --- a/src/rpc/filters/impl/DelegateTransactionsFilter.cpp +++ b/src/rpc/filters/impl/DelegateTransactionsFilter.cpp @@ -1,7 +1,6 @@ #include "rpc/filters/impl/DelegateTransactionsFilter.hpp" #include "data/Types.hpp" -#include "rpc/common/Types.hpp" #include "rpc/filters/TransactionFilter.hpp" #include diff --git a/src/rpc/handlers/AccountTx.cpp b/src/rpc/handlers/AccountTx.cpp index afacae392..08b9d3f7f 100644 --- a/src/rpc/handlers/AccountTx.cpp +++ b/src/rpc/handlers/AccountTx.cpp @@ -3,7 +3,6 @@ #include "data/Types.hpp" #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" -#include "rpc/common/JsonBool.hpp" #include "rpc/common/Types.hpp" #include "rpc/filters/TransactionFilter.hpp" #include "rpc/filters/impl/DelegateTransactionsFilter.hpp" @@ -19,7 +18,7 @@ #include #include #include -#include +#include #include #include #include @@ -45,26 +44,26 @@ AccountTxHandler::process(AccountTxHandler::Input const& input, Context const& c auto [minIndex, maxIndex] = *range; // NOLINT(bugprone-unchecked-optional-access) if (input.ledgerIndexMin) { + // NOLINTBEGIN(bugprone-unchecked-optional-access) if (ctx.apiVersion > 1u && - (input.ledgerIndexMin > - range->maxSequence || // NOLINT(bugprone-unchecked-optional-access) - input.ledgerIndexMin < - range->minSequence)) { // NOLINT(bugprone-unchecked-optional-access) + (input.ledgerIndexMin > range->maxSequence || + input.ledgerIndexMin < range->minSequence)) { return Error{Status{RippledError::RpcLgrIdxMalformed, "ledgerSeqMinOutOfRange"}}; } + // NOLINTEND(bugprone-unchecked-optional-access) if (static_cast(*input.ledgerIndexMin) > minIndex) minIndex = *input.ledgerIndexMin; } if (input.ledgerIndexMax) { + // NOLINTBEGIN(bugprone-unchecked-optional-access) if (ctx.apiVersion > 1u && - (input.ledgerIndexMax > - range->maxSequence || // NOLINT(bugprone-unchecked-optional-access) - input.ledgerIndexMax < - range->minSequence)) { // NOLINT(bugprone-unchecked-optional-access) + (input.ledgerIndexMax > range->maxSequence || + input.ledgerIndexMax < range->minSequence)) { return Error{Status{RippledError::RpcLgrIdxMalformed, "ledgerSeqMaxOutOfRange"}}; } + // NOLINTEND(bugprone-unchecked-optional-access) if (static_cast(*input.ledgerIndexMax) < maxIndex) maxIndex = *input.ledgerIndexMax; @@ -77,7 +76,7 @@ AccountTxHandler::process(AccountTxHandler::Input const& input, Context const& c return Error{Status{RippledError::RpcInvalidLgrRange}}; } - if (input.ledgerHash || input.ledgerIndex || input.usingValidatedLedger) { + if (not input.ledger.isUnspecified()) { if (ctx.apiVersion > 1u && (input.ledgerIndexMax || input.ledgerIndexMin)) { return Error{Status{RippledError::RpcInvalidParams, "containsLedgerSpecifierAndRange"}}; } @@ -85,11 +84,10 @@ AccountTxHandler::process(AccountTxHandler::Input const& input, Context const& c if (!input.ledgerIndexMax && !input.ledgerIndexMin) { // mimic rippled, when both range and index specified, respect the range. // take ledger from ledgerHash or ledgerIndex only when range is not specified - auto const expectedLgrInfo = getLedgerHeaderFromHashOrSeq( + auto const expectedLgrInfo = getLedgerHeaderFromLedgerSpecifier( *sharedPtrBackend_, ctx.yield, - input.ledgerHash, - input.ledgerIndex, + input.ledger, range->maxSequence // NOLINT(bugprone-unchecked-optional-access) ); @@ -115,20 +113,15 @@ AccountTxHandler::process(AccountTxHandler::Input const& input, Context const& c } } - auto const accountID = accountFromStringStrict(input.account); - std::optional txFilter; if (input.delegateFilter) { - txFilter.emplace( - *input.delegateFilter, - *accountID // NOLINT(bugprone-unchecked-optional-access) - ); + txFilter.emplace(*input.delegateFilter, input.account); } auto const limit = input.limit.value_or(kLimitDefault); auto const [txnsAndCursor, timeDiff] = util::timed([&]() { return sharedPtrBackend_->fetchAccountTransactions( - *accountID, limit, input.forward, cursor, ctx.yield + input.account, limit, input.forward, cursor, ctx.yield ); }); @@ -141,10 +134,6 @@ AccountTxHandler::process(AccountTxHandler::Input const& input, Context const& c if (retCursor) response.marker = {.ledger = retCursor->ledgerSequence, .seq = retCursor->transactionIndex}; - std::optional mptIssuanceFilter; - if (input.mptIssuanceId) - mptIssuanceFilter = xrpl::uint192{input.mptIssuanceId->c_str()}; - for (auto const& txnPlusMeta : blobs) { // over the range if ((txnPlusMeta.ledgerSequence < minIndex && !input.forward) || @@ -167,10 +156,10 @@ AccountTxHandler::process(AccountTxHandler::Input const& input, Context const& c boost::json::object obj; // Skip all Txns where the specified filter mpt_id doesn't match the query - if (mptIssuanceFilter) { + if (input.mptIssuanceId) { auto const [sttx, txMeta] = deserializeTxPlusMeta(txnPlusMeta, txnPlusMeta.ledgerSequence); - if (!util::referencesMptIssuance(*txMeta, *sttx, *mptIssuanceFilter)) + if (!util::referencesMptIssuance(*txMeta, *sttx, *input.mptIssuanceId)) continue; } @@ -244,7 +233,7 @@ AccountTxHandler::process(AccountTxHandler::Input const& input, Context const& c } response.limit = input.limit; - response.account = xrpl::to_string(*accountID); // NOLINT(bugprone-unchecked-optional-access) + response.account = xrpl::to_string(input.account); response.ledgerIndexMin = minIndex; response.ledgerIndexMax = maxIndex; @@ -273,12 +262,12 @@ tag_invoke( jv.as_object()[JS(limit)] = *(output.limit); } +} // namespace rpc + +namespace rpc::spec::handlers::account_tx { + void -tag_invoke( - boost::json::value_from_tag, - boost::json::value& jv, - AccountTxHandler::Marker const& marker -) +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker) { jv = { {JS(ledger), marker.ledger}, @@ -286,68 +275,4 @@ tag_invoke( }; } -AccountTxHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto input = AccountTxHandler::Input{}; - auto const& jsonObject = jv.as_object(); - - input.account = boost::json::value_to(jsonObject.at(JS(account))); - - if (jsonObject.contains(JS(ledger_index_min)) && - util::integralValueAs(jsonObject.at(JS(ledger_index_min))) != -1) - input.ledgerIndexMin = util::integralValueAs(jsonObject.at(JS(ledger_index_min))); - - if (jsonObject.contains(JS(ledger_index_max)) && - util::integralValueAs(jsonObject.at(JS(ledger_index_max))) != -1) - input.ledgerIndexMax = util::integralValueAs(jsonObject.at(JS(ledger_index_max))); - - if (jsonObject.contains(JS(ledger_hash))) - input.ledgerHash = boost::json::value_to(jsonObject.at(JS(ledger_hash))); - - if (jsonObject.contains(JS(ledger_index))) { - auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index))); - if (expectedLedgerIndex.has_value()) { - input.ledgerIndex = *expectedLedgerIndex; - } else { - // could not get the latest validated ledger seq here, using this flag to indicate that - input.usingValidatedLedger = true; - } - } - - if (jsonObject.contains(JS(binary))) - input.binary = boost::json::value_to(jsonObject.at(JS(binary))); - - if (jsonObject.contains(JS(forward))) - input.forward = boost::json::value_to(jsonObject.at(JS(forward))); - - if (jsonObject.contains(JS(limit))) - input.limit = util::integralValueAs(jsonObject.at(JS(limit))); - - if (jsonObject.contains(JS(marker))) { - input.marker = AccountTxHandler::Marker{ - .ledger = util::integralValueAs( - jsonObject.at(JS(marker)).as_object().at(JS(ledger)) - ), - .seq = - util::integralValueAs(jsonObject.at(JS(marker)).as_object().at(JS(seq))) - }; - } - - if (jsonObject.contains("tx_type")) { - input.transactionTypeInLowercase = - boost::json::value_to(jsonObject.at("tx_type")); - } - - if (jsonObject.contains(JS(delegate))) - input.delegateFilter = parseDelegateFilter(jsonObject.at(JS(delegate)).as_object()); - - if (jsonObject.contains(JS(mpt_issuance_id))) { - input.mptIssuanceId = - boost::json::value_to(jsonObject.at(JS(mpt_issuance_id))); - } - - return input; -} - -} // namespace rpc +} // namespace rpc::spec::handlers::account_tx diff --git a/src/rpc/handlers/AccountTx.hpp b/src/rpc/handlers/AccountTx.hpp index 389f37dbf..60f6d5190 100644 --- a/src/rpc/handlers/AccountTx.hpp +++ b/src/rpc/handlers/AccountTx.hpp @@ -2,30 +2,22 @@ #include "data/BackendInterface.hpp" #include "etl/ETLServiceInterface.hpp" -#include "rpc/Errors.hpp" -#include "rpc/JS.hpp" -#include "rpc/common/JsonBool.hpp" -#include "rpc/common/MetaProcessors.hpp" -#include "rpc/common/Modifiers.hpp" -#include "rpc/common/Specs.hpp" #include "rpc/common/Types.hpp" -#include "rpc/common/Validators.hpp" #include "util/log/Logger.hpp" #include #include #include #include -#include -#include -#include -#include +#include +#include #include +#include #include #include #include -#include +#include namespace rpc { @@ -35,23 +27,17 @@ namespace rpc { * * For more details see: https://xrpl.org/account_tx.html */ -class AccountTxHandler { +class AccountTxHandler : public rpc::spec::HandlerFor { util::Logger log_{"RPC"}; std::shared_ptr sharedPtrBackend_; std::shared_ptr etl_; public: - static constexpr auto kLimitMin = 1; - static constexpr auto kLimitMax = 1000; - static constexpr auto kLimitDefault = 200; + static constexpr auto kLimitMin = rpc::spec::handlers::account_tx::kLimitMin; + static constexpr auto kLimitMax = rpc::spec::handlers::account_tx::kLimitMax; + static constexpr auto kLimitDefault = rpc::spec::handlers::account_tx::kLimitDefault; - /** - * @brief A struct to hold the marker data - */ - struct Marker { - uint32_t ledger; - uint32_t seq; - }; + using Marker = rpc::spec::handlers::account_tx::Marker; /** * @brief A struct to hold the output data of the command @@ -68,27 +54,6 @@ public: bool validated = true; }; - /** - * @brief A struct to hold the input data for the command - */ - struct Input { - std::string account; - // You must use at least one of the following fields in your request: - // ledger_index, ledger_hash, ledger_index_min, or ledger_index_max. - std::optional ledgerHash; - std::optional ledgerIndex; - std::optional ledgerIndexMin; - std::optional ledgerIndexMax; - bool usingValidatedLedger = false; - JsonBool binary{false}; - JsonBool forward{false}; - std::optional limit; - std::optional marker; - std::optional transactionTypeInLowercase; - std::optional delegateFilter; - std::optional mptIssuanceId; - }; - using Result = HandlerReturnType; /** @@ -105,61 +70,6 @@ public: { } - /** - * @brief Returns the API specification for the command - * - * @param apiVersion The api version to return the spec for - * @return The spec for the given apiVersion - */ - static RpcSpecConstRef - spec([[maybe_unused]] uint32_t apiVersion) - { - // TODO: goes away when account_tx moves to the shared spec, where the tx_type - // validator calls this internally. - auto const& typesKeysInLowercase = rpc::spec::txTypesInLowercase(); - static auto const kRpcSpecForV1 = RpcSpec{ - {JS(account), validation::Required{}, validation::CustomValidators::accountValidator}, - {JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator}, - {JS(ledger_index), validation::CustomValidators::ledgerIndexValidator}, - {JS(ledger_index_min), validation::Type{}}, - {JS(ledger_index_max), validation::Type{}}, - {JS(ctid), validation::Type{}}, - {JS(limit), - validation::Type{}, - validation::Min(1u), - modifiers::Clamp{kLimitMin, kLimitMax}}, - {JS(marker), - meta::WithCustomError{ - validation::Type{}, - Status{RippledError::RpcInvalidParams, "invalidMarker"}, - }, - meta::Section{ - {JS(ledger), validation::Required{}, validation::Type{}}, - {JS(seq), validation::Required{}, validation::Type{}}, - }}, - { - "tx_type", - validation::Type{}, - modifiers::ToLower{}, - validation::OneOf( - typesKeysInLowercase.cbegin(), typesKeysInLowercase.cend() - ), - }, - {JS(delegate), validation::CustomValidators::delegateValidator}, - {JS(mpt_issuance_id), validation::CustomValidators::uint192HexStringValidator}, - }; - - static auto const kRpcSpec = RpcSpec{ - kRpcSpecForV1, - { - {JS(binary), validation::Type{}}, - {JS(forward), validation::Type{}}, - } - }; - - return apiVersion == 1 ? kRpcSpecForV1 : kRpcSpec; - } - /** * @brief Process the AccountTx command * @@ -179,23 +89,13 @@ private: */ friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); - - /** - * @brief Convert a JSON object to Input type - * - * @param jv The JSON object to convert - * @return Input parsed from the JSON object - */ - friend Input - tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); - - /** - * @brief Convert the Marker to a JSON object - * - * @param [out] jv The JSON object to convert to - * @param marker The marker to convert - */ - friend void - tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker); }; + } // namespace rpc + +namespace rpc::spec::handlers::account_tx { + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker); + +} // namespace rpc::spec::handlers::account_tx diff --git a/src/rpc/handlers/DepositAuthorized.cpp b/src/rpc/handlers/DepositAuthorized.cpp index 5ba93019d..e23a9b7ff 100644 --- a/src/rpc/handlers/DepositAuthorized.cpp +++ b/src/rpc/handlers/DepositAuthorized.cpp @@ -5,16 +5,16 @@ #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" #include "util/Assert.hpp" -#include "util/JsonUtils.hpp" #include #include #include +#include #include -#include #include #include #include +#include #include #include #include @@ -24,6 +24,8 @@ #include #include +#include +#include #include #include @@ -38,11 +40,10 @@ DepositAuthorizedHandler::process( auto const range = sharedPtrBackend_->fetchLedgerRange(); ASSERT(range.has_value(), "DepositAuthorized ledger range must be available"); - auto const expectedLgrInfo = getLedgerHeaderFromHashOrSeq( + auto const expectedLgrInfo = getLedgerHeaderFromLedgerSpecifier( *sharedPtrBackend_, ctx.yield, - input.ledgerHash, - input.ledgerIndex, + input.ledger, range->maxSequence // NOLINT(bugprone-unchecked-optional-access) ); @@ -50,21 +51,17 @@ DepositAuthorizedHandler::process( return Error{expectedLgrInfo.error()}; auto const& lgrInfo = *expectedLgrInfo; - auto const sourceAccountID = accountFromStringStrict(input.sourceAccount); - auto const destinationAccountID = accountFromStringStrict(input.destinationAccount); + auto const& sourceAccountID = input.sourceAccount; + auto const& destinationAccountID = input.destinationAccount; auto const srcAccountLedgerObject = sharedPtrBackend_->fetchLedgerObject( - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - xrpl::keylet::account(*sourceAccountID).key, - lgrInfo.seq, - ctx.yield + xrpl::keylet::account(sourceAccountID).key, lgrInfo.seq, ctx.yield ); if (!srcAccountLedgerObject) return Error{Status{RippledError::RpcSrcActNotFound, "source_accountNotFound"}}; - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - auto const dstKeylet = xrpl::keylet::account(*destinationAccountID).key; + auto const dstKeylet = xrpl::keylet::account(destinationAccountID).key; auto const dstAccountLedgerObject = sharedPtrBackend_->fetchLedgerObject(dstKeylet, lgrInfo.seq, ctx.yield); @@ -91,11 +88,7 @@ DepositAuthorizedHandler::process( return Error{Status{RippledError::RpcInvalidParams, "credential array too long."}}; } auto const credArray = credentials::fetchCredentialArray( - input.credentials, - *sourceAccountID, // NOLINT(bugprone-unchecked-optional-access) - *sharedPtrBackend_, - lgrInfo, - ctx.yield + *creds, sourceAccountID, *sharedPtrBackend_, lgrInfo, ctx.yield ); if (!credArray.has_value()) return Error{std::move(credArray).error()}; @@ -115,52 +108,26 @@ DepositAuthorizedHandler::process( "should already be checked above that there is no duplicate" ); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - hashKey = xrpl::keylet::depositPreauth(*destinationAccountID, sortedAuthCreds).key; + hashKey = xrpl::keylet::depositPreauth(destinationAccountID, sortedAuthCreds).key; } else { - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - hashKey = xrpl::keylet::depositPreauth(*destinationAccountID, *sourceAccountID).key; + hashKey = xrpl::keylet::depositPreauth(destinationAccountID, sourceAccountID).key; } depositAuthorized = sharedPtrBackend_->fetchLedgerObject(hashKey, lgrInfo.seq, ctx.yield).has_value(); } - response.sourceAccount = input.sourceAccount; - response.destinationAccount = input.destinationAccount; + response.sourceAccount = xrpl::to_string(sourceAccountID); + response.destinationAccount = xrpl::to_string(destinationAccountID); response.ledgerHash = xrpl::strHex(lgrInfo.hash); response.ledgerIndex = lgrInfo.seq; response.depositAuthorized = depositAuthorized; if (credentialsPresent) - response.credentials = *input.credentials; + response.credentials = creds; return response; } -DepositAuthorizedHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto input = DepositAuthorizedHandler::Input{}; - auto const& jsonObject = jv.as_object(); - - input.sourceAccount = boost::json::value_to(jv.at(JS(source_account))); - input.destinationAccount = boost::json::value_to(jv.at(JS(destination_account))); - - if (jsonObject.contains(JS(ledger_hash))) - input.ledgerHash = boost::json::value_to(jv.at(JS(ledger_hash))); - - if (jsonObject.contains(JS(ledger_index))) { - auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index))); - if (expectedLedgerIndex.has_value()) - input.ledgerIndex = *expectedLedgerIndex; - } - - if (jsonObject.contains(JS(credentials))) - input.credentials = boost::json::value_to(jv.at(JS(credentials))); - - return input; -} - void tag_invoke( boost::json::value_from_tag, @@ -176,8 +143,11 @@ tag_invoke( {JS(ledger_index), output.ledgerIndex}, {JS(validated), output.validated} }; - if (output.credentials) - jv.as_object()[JS(credentials)] = *output.credentials; + if (output.credentials.has_value()) { + jv.as_object()[JS(credentials)] = *output.credentials // + | std::views::transform([](auto const& cred) { return xrpl::strHex(cred); }) // + | std::ranges::to(); + } } } // namespace rpc diff --git a/src/rpc/handlers/DepositAuthorized.hpp b/src/rpc/handlers/DepositAuthorized.hpp index 20999738c..8907a49de 100644 --- a/src/rpc/handlers/DepositAuthorized.hpp +++ b/src/rpc/handlers/DepositAuthorized.hpp @@ -1,21 +1,23 @@ #pragma once #include "data/BackendInterface.hpp" -#include "rpc/JS.hpp" -#include "rpc/common/Specs.hpp" #include "rpc/common/Types.hpp" -#include "rpc/common/Validators.hpp" #include #include #include +#include +#include +#include #include -#include #include +#include #include #include #include +#include +#include namespace rpc { @@ -28,7 +30,8 @@ namespace rpc { * * For more details see: https://xrpl.org/deposit_authorized.html */ -class DepositAuthorizedHandler { +class DepositAuthorizedHandler + : public rpc::spec::HandlerFor { // dependencies std::shared_ptr const sharedPtrBackend_; @@ -43,23 +46,12 @@ public: std::string destinationAccount; std::string ledgerHash; uint32_t ledgerIndex{}; - std::optional credentials; + std::optional> credentials; // validated should be sent via framework bool validated = true; }; - /** - * @brief A struct to hold the input data for the command - */ - struct Input { - std::string sourceAccount; - std::string destinationAccount; - std::optional ledgerHash; - std::optional ledgerIndex; - std::optional credentials; - }; - using Result = HandlerReturnType; /** @@ -72,30 +64,6 @@ public: { } - /** - * @brief Returns the API specification for the command - * - * @param apiVersion The api version to return the spec for - * @return The spec for the given apiVersion - */ - static RpcSpecConstRef - spec([[maybe_unused]] uint32_t apiVersion) - { - static auto const kRpcSpec = RpcSpec{ - {JS(source_account), - validation::Required{}, - validation::CustomValidators::accountValidator}, - {JS(destination_account), - validation::Required{}, - validation::CustomValidators::accountValidator}, - {JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator}, - {JS(ledger_index), validation::CustomValidators::ledgerIndexValidator}, - {JS(credentials), validation::Type{}, validation::Hex256ItemType()} - }; - - return kRpcSpec; - } - /** * @brief Process the DepositAuthorized command * @@ -115,14 +83,6 @@ private: */ friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); - - /** - * @brief Convert a JSON object to Input type - * - * @param jv The JSON object to convert - * @return Input parsed from the JSON object - */ - friend Input - tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; + } // namespace rpc diff --git a/src/rpc/handlers/GetAggregatePrice.cpp b/src/rpc/handlers/GetAggregatePrice.cpp index 4d464a1f8..b3958e5f1 100644 --- a/src/rpc/handlers/GetAggregatePrice.cpp +++ b/src/rpc/handlers/GetAggregatePrice.cpp @@ -3,9 +3,7 @@ #include "rpc/JS.hpp" #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" -#include "util/AccountUtils.hpp" #include "util/Assert.hpp" -#include "util/JsonUtils.hpp" #include #include @@ -13,10 +11,9 @@ #include #include #include -#include +#include #include #include -#include #include #include #include @@ -47,11 +44,10 @@ GetAggregatePriceHandler::process( auto const range = sharedPtrBackend_->fetchLedgerRange(); ASSERT(range.has_value(), "GetAggregatePrice's ledger range must be available"); - auto const expectedLgrInfo = getLedgerHeaderFromHashOrSeq( + auto const expectedLgrInfo = getLedgerHeaderFromLedgerSpecifier( *sharedPtrBackend_, ctx.yield, - input.ledgerHash, - input.ledgerIndex, + input.ledger, range->maxSequence // NOLINT(bugprone-unchecked-optional-access) ); @@ -86,9 +82,8 @@ GetAggregatePriceHandler::process( series.begin(), series.end(), [&](xrpl::STObject const& o) -> bool { - return o.getFieldCurrency(xrpl::sfBaseAsset).getText() == - input.baseAsset and - o.getFieldCurrency(xrpl::sfQuoteAsset).getText() == input.quoteAsset and + return o.getFieldCurrency(xrpl::sfBaseAsset) == input.baseAsset and + o.getFieldCurrency(xrpl::sfQuoteAsset) == input.quoteAsset and o.isFieldPresent(xrpl::sfAssetPrice); } ); @@ -249,46 +244,6 @@ GetAggregatePriceHandler::tracebackOracleObject( } } -GetAggregatePriceHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto input = GetAggregatePriceHandler::Input{}; - auto const& jsonObject = jv.as_object(); - - if (jsonObject.contains(JS(ledger_hash))) - input.ledgerHash = boost::json::value_to(jv.at(JS(ledger_hash))); - - if (jsonObject.contains(JS(ledger_index))) { - auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index))); - if (expectedLedgerIndex.has_value()) - input.ledgerIndex = *expectedLedgerIndex; - } - - for (auto const& oracle : jsonObject.at(JS(oracles)).as_array()) { - input.oracles.push_back( - GetAggregatePriceHandler::Oracle{ - .documentId = boost::json::value_to( - oracle.as_object().at(JS(oracle_document_id)) - ), - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - .account = *util::parseBase58Wrapper( - boost::json::value_to(oracle.as_object().at(JS(account))) - ) - } - ); - } - input.baseAsset = boost::json::value_to(jv.at(JS(base_asset))); - input.quoteAsset = boost::json::value_to(jv.at(JS(quote_asset))); - - if (jsonObject.contains(JS(trim))) - input.trim = util::integralValueAs(jv.at(JS(trim))); - - if (jsonObject.contains(JS(time_threshold))) - input.timeThreshold = util::integralValueAs(jv.at(JS(time_threshold))); - - return input; -} - void tag_invoke( boost::json::value_from_tag, diff --git a/src/rpc/handlers/GetAggregatePrice.hpp b/src/rpc/handlers/GetAggregatePrice.hpp index fcadeb3e5..577e2247c 100644 --- a/src/rpc/handlers/GetAggregatePrice.hpp +++ b/src/rpc/handlers/GetAggregatePrice.hpp @@ -2,28 +2,27 @@ #include "data/BackendInterface.hpp" #include "rpc/Errors.hpp" -#include "rpc/JS.hpp" -#include "rpc/common/MetaProcessors.hpp" -#include "rpc/common/Modifiers.hpp" -#include "rpc/common/Specs.hpp" #include "rpc/common/Types.hpp" -#include "rpc/common/Validators.hpp" #include #include #include +#include +#include +#include #include #include #include #include -#include +#include #include +#include #include #include #include #include -#include +#include #include namespace rpc { @@ -31,7 +30,8 @@ namespace rpc { /** *@brief The get_aggregate_price method. */ -class GetAggregatePriceHandler { +class GetAggregatePriceHandler + : public rpc::spec::HandlerFor { std::shared_ptr sharedPtrBackend_; public: @@ -58,27 +58,6 @@ public: bool validated = true; }; - /** - * @brief A struct to hold the input oracle data - */ - struct Oracle { - std::uint32_t documentId{0}; - xrpl::AccountID account; - }; - - /** - * @brief A struct to hold the input data for the command - */ - struct Input { - std::optional ledgerHash; - std::optional ledgerIndex; - std::vector oracles; // valid range is 1-200 - std::string baseAsset; - std::string quoteAsset; - std::optional timeThreshold; - std::optional trim; // valid range is 1-25 - }; - using Result = HandlerReturnType; /** @@ -91,83 +70,6 @@ public: { } - /** - * @brief Returns the API specification for the command - * - * @param apiVersion The api version to return the spec for - * @return The spec for the given apiVersion - */ - static RpcSpecConstRef - spec([[maybe_unused]] uint32_t apiVersion) - { - static constexpr auto kOraclesMax = 200; - - static auto const kOraclesValidator = modifiers::CustomModifier{ - [](boost::json::value& value, std::string_view) -> MaybeError { - if (!value.is_array() or value.as_array().empty() or - value.as_array().size() > kOraclesMax) - return Error{Status{RippledError::RpcOracleMalformed}}; - - for (auto& oracle : value.as_array()) { - if (!oracle.is_object() or - !oracle.as_object().contains(JS(oracle_document_id)) or - !oracle.as_object().contains(JS(account))) - return Error{Status{RippledError::RpcOracleMalformed}}; - - auto maybeError = validation::Type{}.verify( - oracle, JS(oracle_document_id) - ); - if (!maybeError) - return maybeError; - - maybeError = modifiers::ToNumber::modify(oracle, JS(oracle_document_id)); - if (!maybeError) - return maybeError; - - maybeError = validation::CustomValidators::accountBase58Validator.verify( - oracle.as_object(), JS(account) - ); - if (!maybeError) - return Error{Status{RippledError::RpcInvalidParams}}; - }; - - return MaybeError{}; - } - }; - - static auto const kRpcSpec = RpcSpec{ - {JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator}, - {JS(ledger_index), validation::CustomValidators::ledgerIndexValidator}, - // validate quoteAsset and base_asset in accordance to the currency code found in XRPL - // doc: - // https://xrpl.org/docs/references/protocol/data-types/currency-formats#currency-codes - // usually Clio returns rpcMALFORMED_CURRENCY , return InvalidParam here just to mimic - // rippled - {JS(base_asset), - validation::Required{}, - meta::WithCustomError{ - validation::CustomValidators::currencyValidator, - Status(RippledError::RpcInvalidParams) - }}, - {JS(quote_asset), - validation::Required{}, - meta::WithCustomError{ - validation::CustomValidators::currencyValidator, - Status(RippledError::RpcInvalidParams) - }}, - {JS(oracles), validation::Required{}, kOraclesValidator}, - // note: Unlike `rippled`, Clio only supports UInt as input, no string, no `null`, etc. - {JS(time_threshold), validation::Type{}}, - { - JS(trim), - validation::Type{}, - validation::Between{1, 25}, - } - }; - - return kRpcSpec; - } - /** * @brief Process the GetAggregatePrice command * @@ -200,15 +102,6 @@ private: */ friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); - - /** - * @brief Convert a JSON object to Input type - * - * @param jv The JSON object to convert - * @return Input parsed from the JSON object - */ - friend Input - tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; } // namespace rpc diff --git a/src/rpc/handlers/LedgerData.cpp b/src/rpc/handlers/LedgerData.cpp index 872dba02b..3a2435328 100644 --- a/src/rpc/handlers/LedgerData.cpp +++ b/src/rpc/handlers/LedgerData.cpp @@ -5,15 +5,13 @@ #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" #include "util/Assert.hpp" -#include "util/JsonUtils.hpp" #include "util/log/Logger.hpp" #include #include #include -#include #include -#include +#include #include #include #include @@ -27,8 +25,10 @@ #include #include #include +#include #include #include +#include #include namespace rpc { @@ -36,21 +36,24 @@ namespace rpc { LedgerDataHandler::Result LedgerDataHandler::process(Input const& input, Context const& ctx) const { - // marker must be int if outOfOrder is true - if (input.outOfOrder && input.marker) + using rpc::spec::handlers::ledger_data::MarkerValue; + + auto const* uint256Marker = input.marker ? std::get_if(&*input.marker) : nullptr; + auto const* diffMarker = input.marker ? std::get_if(&*input.marker) : nullptr; + + if (input.outOfOrder && uint256Marker != nullptr) return Error{Status{RippledError::RpcInvalidParams, "outOfOrderMarkerNotInt"}}; - if (!input.outOfOrder && input.diffMarker) + if (!input.outOfOrder && diffMarker != nullptr) return Error{Status{RippledError::RpcInvalidParams, "markerNotString"}}; auto const range = sharedPtrBackend_->fetchLedgerRange(); ASSERT(range.has_value(), "LedgerData's ledger range must be available"); - auto const expectedLgrInfo = getLedgerHeaderFromHashOrSeq( + auto const expectedLgrInfo = getLedgerHeaderFromLedgerSpecifier( *sharedPtrBackend_, ctx.yield, - input.ledgerHash, - input.ledgerIndex, + input.ledger, range->maxSequence // NOLINT(bugprone-unchecked-optional-access) ); @@ -62,11 +65,11 @@ LedgerDataHandler::process(Input const& input, Context const& ctx) const Output output; // no marker -> first call, return header information - if ((!input.marker) && (!input.diffMarker)) { + if (not input.marker.has_value()) { output.header = toJson(lgrInfo, input.binary, ctx.apiVersion); } else { - if (input.marker && - !sharedPtrBackend_->fetchLedgerObject(*(input.marker), lgrInfo.seq, ctx.yield)) + if (uint256Marker != nullptr && + !sharedPtrBackend_->fetchLedgerObject(*uint256Marker, lgrInfo.seq, ctx.yield)) return Error{Status{RippledError::RpcInvalidParams, "markerDoesNotExist"}}; } @@ -76,9 +79,9 @@ LedgerDataHandler::process(Input const& input, Context const& ctx) const auto const start = std::chrono::system_clock::now(); std::vector results; - if (input.diffMarker) { + if (diffMarker != nullptr) { // keep the same logic as previous implementation - auto diff = sharedPtrBackend_->fetchLedgerDiff(*(input.diffMarker), ctx.yield); + auto diff = sharedPtrBackend_->fetchLedgerDiff(*diffMarker, ctx.yield); std::vector keys; for (auto& [key, object] : diff) { @@ -94,17 +97,19 @@ LedgerDataHandler::process(Input const& input, Context const& ctx) const results.push_back({.key = keys[i], .blob = std::move(obj)}); } - if (*(input.diffMarker) > lgrInfo.seq) - output.diffMarker = *(input.diffMarker) - 1; + if (*diffMarker > lgrInfo.seq) + output.diffMarker = *diffMarker - 1; } else { - // limit's limitation is different based on binary or json - // framework can not handler the check right now, adjust the value here - auto const limit = std::min( - input.limit, - input.binary ? LedgerDataHandler::kLimitBinary : LedgerDataHandler::kLimitJson - ); + auto const maxLimit = + input.binary ? LedgerDataHandler::kLimitBinary : LedgerDataHandler::kLimitJson; + auto const limit = std::min(input.limit.value_or(maxLimit), maxLimit); + + std::optional pageMarker; + if (uint256Marker != nullptr) + pageMarker = *uint256Marker; + auto page = sharedPtrBackend_->fetchLedgerPage( - input.marker, lgrInfo.seq, limit, input.outOfOrder, ctx.yield + pageMarker, lgrInfo.seq, limit, input.outOfOrder, ctx.yield ); results = std::move(page.objects); @@ -178,48 +183,4 @@ tag_invoke( jv = std::move(obj); } -LedgerDataHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto input = LedgerDataHandler::Input{}; - auto const& jsonObject = jv.as_object(); - - if (jsonObject.contains(JS(binary))) { - input.binary = jsonObject.at(JS(binary)).as_bool(); - input.limit = - input.binary ? LedgerDataHandler::kLimitBinary : LedgerDataHandler::kLimitJson; - } - - if (jsonObject.contains(JS(limit))) - input.limit = util::integralValueAs(jsonObject.at(JS(limit))); - - if (jsonObject.contains("out_of_order")) - input.outOfOrder = jsonObject.at("out_of_order").as_bool(); - - if (jsonObject.contains(JS(marker))) { - if (jsonObject.at(JS(marker)).is_string()) { - input.marker = - xrpl::uint256{boost::json::value_to(jsonObject.at(JS(marker))).data()}; - } else { - input.diffMarker = util::integralValueAs(jsonObject.at(JS(marker))); - } - } - - if (jsonObject.contains(JS(ledger_hash))) - input.ledgerHash = boost::json::value_to(jsonObject.at(JS(ledger_hash))); - - if (jsonObject.contains(JS(ledger_index))) { - auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index))); - if (expectedLedgerIndex.has_value()) - input.ledgerIndex = *expectedLedgerIndex; - } - - if (jsonObject.contains(JS(type))) { - input.type = - rpc::spec::ledgerEntryTypeFromStr(boost::json::value_to(jv.at(JS(type)))); - } - - return input; -} - } // namespace rpc diff --git a/src/rpc/handlers/LedgerData.hpp b/src/rpc/handlers/LedgerData.hpp index f6f434b7f..85bb0c7fc 100644 --- a/src/rpc/handlers/LedgerData.hpp +++ b/src/rpc/handlers/LedgerData.hpp @@ -1,26 +1,25 @@ #pragma once #include "data/BackendInterface.hpp" -#include "rpc/JS.hpp" -#include "rpc/common/Checkers.hpp" -#include "rpc/common/MetaProcessors.hpp" -#include "rpc/common/Specs.hpp" +#include "rpc/Errors.hpp" #include "rpc/common/Types.hpp" -#include "rpc/common/Validators.hpp" #include "util/log/Logger.hpp" #include #include #include #include +#include +#include #include -#include -#include +#include #include +#include #include #include #include +#include namespace rpc { @@ -30,15 +29,15 @@ namespace rpc { * * For more details see: https://xrpl.org/ledger_data.html */ -class LedgerDataHandler { +class LedgerDataHandler : public rpc::spec::HandlerFor { // dependencies std::shared_ptr sharedPtrBackend_; util::Logger log_{"RPC"}; public: // constants - static constexpr uint32_t kLimitBinary = 2048; - static constexpr uint32_t kLimitJson = 256; + static constexpr auto kLimitBinary = rpc::spec::handlers::ledger_data::kLimitBinary; + static constexpr auto kLimitJson = rpc::spec::handlers::ledger_data::kLimitJson; /** * @brief A struct to hold the output data of the command @@ -54,23 +53,6 @@ public: bool validated = true; }; - /** - * @brief A struct to hold the input data for the command - * - * @note `outOfOrder` is only for Clio, there is no document, traverse via seq diff (outOfOrder - * implementation is copied from old rpc handler) - */ - struct Input { - std::optional ledgerHash; - std::optional ledgerIndex; - bool binary = false; - uint32_t limit = LedgerDataHandler::kLimitJson; // max 256 for json ; 2048 for binary - std::optional marker; - std::optional diffMarker; - bool outOfOrder = false; - xrpl::LedgerEntryType type = xrpl::LedgerEntryType::ltANY; - }; - using Result = HandlerReturnType; /** @@ -83,30 +65,6 @@ public: { } - /** - * @brief Returns the API specification for the command - * - * @param apiVersion The api version to return the spec for - * @return The spec for the given apiVersion - */ - static RpcSpecConstRef - spec([[maybe_unused]] uint32_t apiVersion) - { - static auto const kRpcSpec = RpcSpec{ - {JS(binary), validation::Type{}}, - {"out_of_order", validation::Type{}}, - {JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator}, - {JS(ledger_index), validation::CustomValidators::ledgerIndexValidator}, - {JS(limit), validation::Type{}, validation::Min(1u)}, - {JS(marker), - validation::Type{}, - meta::IfType{validation::CustomValidators::uint256HexStringValidator}}, - {JS(type), validation::CustomValidators::ledgerTypeValidator}, - {JS(ledger), check::Deprecated{}}, - }; - return kRpcSpec; - } - /** * @brief Process the LedgerData command * @@ -126,14 +84,6 @@ private: */ friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); - - /** - * @brief Convert a JSON object to Input type - * - * @param jv The JSON object to convert - * @return Input parsed from the JSON object - */ - friend Input - tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; + } // namespace rpc diff --git a/src/rpc/handlers/MPTokenIssuanceHistory.cpp b/src/rpc/handlers/MPTokenIssuanceHistory.cpp index b03d6b78c..5b0d4196a 100644 --- a/src/rpc/handlers/MPTokenIssuanceHistory.cpp +++ b/src/rpc/handlers/MPTokenIssuanceHistory.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -56,7 +57,7 @@ MPTokenIssuanceHistoryHandler::process( if (not range.has_value()) return Error{range.error()}; - auto const mptIssuanceID = xrpl::uint192{input.mptIssuanceID.c_str()}; + auto const& mptIssuanceID = input.mptIssuanceId; auto const [page, timeDiff] = util::timed([&] { return fetchTransactions(input, ctx, mptIssuanceID, *range); }); @@ -120,13 +121,13 @@ MPTokenIssuanceHistoryHandler::resolveSequenceRange(Input const& input, Context if (resolved.min > resolved.max) return Error{Status{RippledError::RpcLgrIdxsInvalid}}; - if (input.ledgerHash.has_value() || input.ledgerIndex.has_value()) { + if (not input.ledger.isUnspecified()) { // rippled does not have this check if (input.ledgerIndexMax.has_value() || input.ledgerIndexMin.has_value()) return Error{Status{RippledError::RpcInvalidParams, "containsLedgerSpecifierAndRange"}}; - auto const expectedLgrInfo = getLedgerHeaderFromHashOrSeq( - *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, dbMaxSeq + auto const expectedLgrInfo = getLedgerHeaderFromLedgerSpecifier( + *sharedPtrBackend_, ctx.yield, input.ledger, dbMaxSeq ); if (not expectedLgrInfo.has_value()) @@ -163,13 +164,8 @@ MPTokenIssuanceHistoryHandler::fetchTransactions( // tx_type is applied post-fetch, as account_tx does. if (input.account.has_value()) { - auto const account = accountFromStringStrict(*input.account); - if (not account.has_value()) { - ASSERT(false, "Account must be decodable after spec validation"); - std::unreachable(); - } return sharedPtrBackend_->fetchAccountMPTokenIssuanceTransactions( - mptIssuanceID, *account, limit, input.forward, startCursor, ctx.yield + mptIssuanceID, *input.account, limit, input.forward, startCursor, ctx.yield ); } @@ -199,7 +195,7 @@ MPTokenIssuanceHistoryHandler::processTransactionsPage( if (txnPlusMeta.transaction.empty() || txnPlusMeta.metadata.empty()) { LOG(log_.warn()) << "Skipping index entry with no matching transaction record; " "mpt_issuance_id = " - << input.mptIssuanceID; + << xrpl::to_string(input.mptIssuanceId); continue; } @@ -304,12 +300,12 @@ tag_invoke( jv.as_object()[JS(limit)] = *(output.limit); } +} // namespace rpc + +namespace rpc::spec::handlers::mptoken_issuance_history { + void -tag_invoke( - boost::json::value_from_tag, - boost::json::value& jv, - MPTokenIssuanceHistoryHandler::Marker const& marker -) +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker) { jv = { {JS(ledger), marker.ledger}, @@ -317,62 +313,4 @@ tag_invoke( }; } -MPTokenIssuanceHistoryHandler::Input -tag_invoke( - boost::json::value_to_tag, - boost::json::value const& jv -) -{ - auto const& jsonObject = jv.as_object(); - auto input = MPTokenIssuanceHistoryHandler::Input{}; - - input.mptIssuanceID = boost::json::value_to(jsonObject.at(JS(mpt_issuance_id))); - - if (jsonObject.contains(JS(account))) - input.account = boost::json::value_to(jsonObject.at(JS(account))); - - if (jsonObject.contains("tx_type")) { - input.transactionTypeInLowercase = - boost::json::value_to(jsonObject.at("tx_type")); - } - - if (jsonObject.contains(JS(ledger_index_min)) && - util::integralValueAs(jsonObject.at(JS(ledger_index_min))) != -1) - input.ledgerIndexMin = util::integralValueAs(jsonObject.at(JS(ledger_index_min))); - - if (jsonObject.contains(JS(ledger_index_max)) && - util::integralValueAs(jsonObject.at(JS(ledger_index_max))) != -1) - input.ledgerIndexMax = util::integralValueAs(jsonObject.at(JS(ledger_index_max))); - - if (jsonObject.contains(JS(ledger_hash))) - input.ledgerHash = boost::json::value_to(jsonObject.at(JS(ledger_hash))); - - if (jsonObject.contains(JS(ledger_index))) { - auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index))); - if (expectedLedgerIndex.has_value()) - input.ledgerIndex = *expectedLedgerIndex; - } - - if (jsonObject.contains(JS(binary))) - input.binary = jsonObject.at(JS(binary)).as_bool(); - - if (jsonObject.contains(JS(forward))) - input.forward = jsonObject.at(JS(forward)).as_bool(); - - if (jsonObject.contains(JS(limit))) - input.limit = util::integralValueAs(jsonObject.at(JS(limit))); - - if (jsonObject.contains(JS(marker))) { - input.marker = MPTokenIssuanceHistoryHandler::Marker{ - .ledger = util::integralValueAs( - jsonObject.at(JS(marker)).as_object().at(JS(ledger)) - ), - .seq = - util::integralValueAs(jsonObject.at(JS(marker)).as_object().at(JS(seq))) - }; - } - - return input; -} - -} // namespace rpc +} // namespace rpc::spec::handlers::mptoken_issuance_history diff --git a/src/rpc/handlers/MPTokenIssuanceHistory.hpp b/src/rpc/handlers/MPTokenIssuanceHistory.hpp index 3d0732bed..ae33144f8 100644 --- a/src/rpc/handlers/MPTokenIssuanceHistory.hpp +++ b/src/rpc/handlers/MPTokenIssuanceHistory.hpp @@ -2,23 +2,16 @@ #include "data/BackendInterface.hpp" #include "data/Types.hpp" -#include "rpc/Errors.hpp" -#include "rpc/JS.hpp" -#include "rpc/common/MetaProcessors.hpp" -#include "rpc/common/Modifiers.hpp" -#include "rpc/common/Specs.hpp" #include "rpc/common/Types.hpp" -#include "rpc/common/Validators.hpp" #include "util/log/Logger.hpp" #include #include #include #include -#include +#include +#include #include -#include -#include #include #include @@ -37,7 +30,8 @@ namespace rpc { * @note This is a Clio-only method. Requests fail with `notReady` until the issuance-history * backfill reports `Migrated`, so partial history is never served. */ -class MPTokenIssuanceHistoryHandler { +class MPTokenIssuanceHistoryHandler + : public rpc::spec::HandlerFor { util::Logger log_{"RPC"}; std::shared_ptr sharedPtrBackend_; @@ -49,9 +43,10 @@ class MPTokenIssuanceHistoryHandler { std::shared_ptr migrated_ = std::make_shared(false); public: - static constexpr auto kLimitMin = 1; - static constexpr auto kLimitMax = 100; - static constexpr auto kLimitDefault = 50; + static constexpr auto kLimitMin = rpc::spec::handlers::mptoken_issuance_history::kLimitMin; + static constexpr auto kLimitMax = rpc::spec::handlers::mptoken_issuance_history::kLimitMax; + static constexpr auto kLimitDefault = + rpc::spec::handlers::mptoken_issuance_history::kLimitDefault; /** * @brief The name used to query the issuance-history migrator's status. @@ -63,10 +58,7 @@ public: /** * @brief A struct to hold the marker data. */ - struct Marker { - uint32_t ledger; - uint32_t seq; - }; + using Marker = rpc::spec::handlers::mptoken_issuance_history::Marker; /** * @brief A struct to hold the output data of the command. @@ -83,26 +75,6 @@ public: bool validated = true; }; - /** - * @brief A struct to hold the input data for the command. - * - * @note When no ledger selector is provided, the request uses the backend's full available - * ledger range. - */ - struct Input { - std::string mptIssuanceID; - std::optional account; - std::optional transactionTypeInLowercase; - std::optional ledgerHash; - std::optional ledgerIndex; - std::optional ledgerIndexMin; - std::optional ledgerIndexMax; - bool binary = false; - bool forward = false; - std::optional limit; - std::optional marker; - }; - using Result = HandlerReturnType; /** @@ -115,55 +87,6 @@ public: { } - /** - * @brief Returns the API specification for the command. - * - * @param apiVersion The api version to return the spec for. - * @return The spec for the given apiVersion. - */ - static RpcSpecConstRef - spec([[maybe_unused]] uint32_t apiVersion) - { - // TODO: goes away when mptoken_issuance_history moves to the shared spec, where the - // validator calls this internally. - auto const& typesKeysInLowercase = rpc::spec::txTypesInLowercase(); - static auto const kRpcSpec = RpcSpec{ - {JS(mpt_issuance_id), - validation::Required{}, - validation::CustomValidators::uint192HexStringValidator}, - {JS(account), validation::CustomValidators::accountValidator}, - { - "tx_type", - validation::Type{}, - modifiers::ToLower{}, - validation::OneOf( - typesKeysInLowercase.cbegin(), typesKeysInLowercase.cend() - ), - }, - {JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator}, - {JS(ledger_index), validation::CustomValidators::ledgerIndexValidator}, - {JS(ledger_index_min), validation::Type{}}, - {JS(ledger_index_max), validation::Type{}}, - {JS(binary), validation::Type{}}, - {JS(forward), validation::Type{}}, - {JS(limit), - validation::Type{}, - validation::Min(1u), - modifiers::Clamp{kLimitMin, kLimitMax}}, - {JS(marker), - meta::WithCustomError{ - validation::Type{}, - Status{RippledError::RpcInvalidParams, "invalidMarker"} - }, - meta::Section{ - {JS(ledger), validation::Required{}, validation::Type{}}, - {JS(seq), validation::Required{}, validation::Type{}}, - }}, - }; - - return kRpcSpec; - } - /** * @brief Process the MPTokenIssuanceHistory command. * @@ -295,24 +218,13 @@ private: */ friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); - - /** - * @brief Convert a JSON object to Input type. - * - * @param jv The JSON object to convert. - * @return Input parsed from the JSON object. - */ - friend Input - tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); - - /** - * @brief Convert the Marker to a JSON object. - * - * @param [out] jv The JSON object to convert to. - * @param marker The marker to convert. - */ - friend void - tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker); }; } // namespace rpc + +namespace rpc::spec::handlers::mptoken_issuance_history { + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker); + +} // namespace rpc::spec::handlers::mptoken_issuance_history diff --git a/src/rpc/handlers/NFTHistory.cpp b/src/rpc/handlers/NFTHistory.cpp index f2229f721..22edb5c4d 100644 --- a/src/rpc/handlers/NFTHistory.cpp +++ b/src/rpc/handlers/NFTHistory.cpp @@ -5,7 +5,6 @@ #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" #include "util/Assert.hpp" -#include "util/JsonUtils.hpp" #include "util/Profiler.hpp" #include "util/log/Logger.hpp" @@ -13,8 +12,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -45,7 +44,7 @@ NFTHistoryHandler::process(NFTHistoryHandler::Input const& input, Context const& return Error{Status{RippledError::RpcLgrIdxMalformed, "ledgerSeqMinOutOfRange"}}; // NOLINTEND(bugprone-unchecked-optional-access) - minIndex = *input.ledgerIndexMin; + minIndex = static_cast(*input.ledgerIndexMin); } if (input.ledgerIndexMax) { @@ -54,23 +53,22 @@ NFTHistoryHandler::process(NFTHistoryHandler::Input const& input, Context const& return Error{Status{RippledError::RpcLgrIdxMalformed, "ledgerSeqMaxOutOfRange"}}; // NOLINTEND(bugprone-unchecked-optional-access) - maxIndex = *input.ledgerIndexMax; + maxIndex = static_cast(*input.ledgerIndexMax); } if (minIndex > maxIndex) return Error{Status{RippledError::RpcLgrIdxsInvalid}}; - if (input.ledgerHash || input.ledgerIndex) { + if (not input.ledger.isUnspecified()) { // rippled does not have this check if (input.ledgerIndexMax || input.ledgerIndexMin) { return Error{Status{RippledError::RpcInvalidParams, "containsLedgerSpecifierAndRange"}}; } - auto const expectedLgrInfo = getLedgerHeaderFromHashOrSeq( + auto const expectedLgrInfo = getLedgerHeaderFromLedgerSpecifier( *sharedPtrBackend_, ctx.yield, - input.ledgerHash, - input.ledgerIndex, + input.ledger, range->maxSequence // NOLINT(bugprone-unchecked-optional-access) ); @@ -94,7 +92,7 @@ NFTHistoryHandler::process(NFTHistoryHandler::Input const& input, Context const& } auto const limit = input.limit.value_or(kLimitDefault); - auto const tokenID = xrpl::uint256{input.nftID.c_str()}; + auto const& tokenID = input.nftID; auto const [txnsAndCursor, timeDiff] = util::timed([&]() { return sharedPtrBackend_->fetchNFTTransactions( @@ -185,12 +183,12 @@ tag_invoke( jv.as_object()[JS(limit)] = *(output.limit); } +} // namespace rpc + +namespace rpc::spec::handlers::nft_history { + void -tag_invoke( - boost::json::value_from_tag, - boost::json::value& jv, - NFTHistoryHandler::Marker const& marker -) +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker) { jv = { {JS(ledger), marker.ledger}, @@ -198,51 +196,4 @@ tag_invoke( }; } -NFTHistoryHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto const& jsonObject = jv.as_object(); - auto input = NFTHistoryHandler::Input{}; - - input.nftID = boost::json::value_to(jsonObject.at(JS(nft_id))); - - if (jsonObject.contains(JS(ledger_index_min)) && - util::integralValueAs(jsonObject.at(JS(ledger_index_min))) != -1) - input.ledgerIndexMin = util::integralValueAs(jsonObject.at(JS(ledger_index_min))); - - if (jsonObject.contains(JS(ledger_index_max)) && - util::integralValueAs(jsonObject.at(JS(ledger_index_max))) != -1) - input.ledgerIndexMax = util::integralValueAs(jsonObject.at(JS(ledger_index_max))); - - if (jsonObject.contains(JS(ledger_hash))) - input.ledgerHash = boost::json::value_to(jsonObject.at(JS(ledger_hash))); - - if (jsonObject.contains(JS(ledger_index))) { - auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index))); - if (expectedLedgerIndex.has_value()) - input.ledgerIndex = *expectedLedgerIndex; - } - - if (jsonObject.contains(JS(binary))) - input.binary = jsonObject.at(JS(binary)).as_bool(); - - if (jsonObject.contains(JS(forward))) - input.forward = jsonObject.at(JS(forward)).as_bool(); - - if (jsonObject.contains(JS(limit))) - input.limit = util::integralValueAs(jsonObject.at(JS(limit))); - - if (jsonObject.contains(JS(marker))) { - input.marker = NFTHistoryHandler::Marker{ - .ledger = util::integralValueAs( - jsonObject.at(JS(marker)).as_object().at(JS(ledger)) - ), - .seq = - util::integralValueAs(jsonObject.at(JS(marker)).as_object().at(JS(seq))) - }; - } - - return input; -} - -} // namespace rpc +} // namespace rpc::spec::handlers::nft_history diff --git a/src/rpc/handlers/NFTHistory.hpp b/src/rpc/handlers/NFTHistory.hpp index a4eba808d..c0cd5a106 100644 --- a/src/rpc/handlers/NFTHistory.hpp +++ b/src/rpc/handlers/NFTHistory.hpp @@ -2,25 +2,22 @@ #include "data/BackendInterface.hpp" #include "rpc/Errors.hpp" -#include "rpc/JS.hpp" -#include "rpc/common/MetaProcessors.hpp" -#include "rpc/common/Modifiers.hpp" -#include "rpc/common/Specs.hpp" #include "rpc/common/Types.hpp" -#include "rpc/common/Validators.hpp" #include "util/log/Logger.hpp" #include #include #include #include -#include -#include +#include +#include #include +#include #include #include #include +#include namespace rpc { @@ -30,23 +27,19 @@ namespace rpc { * * For more details see: https://xrpl.org/nft_history.html#nft_history */ -class NFTHistoryHandler { +class NFTHistoryHandler : public rpc::spec::HandlerFor { util::Logger log_{"RPC"}; std::shared_ptr sharedPtrBackend_; public: - static constexpr auto kLimitMin = 1; - static constexpr auto kLimitMax = 100; - static constexpr auto kLimitDefault = 50; + static constexpr auto kLimitMin = rpc::spec::handlers::nft_history::kLimitMin; + static constexpr auto kLimitMax = rpc::spec::handlers::nft_history::kLimitMax; + static constexpr auto kLimitDefault = rpc::spec::handlers::nft_history::kLimitDefault; /** * @brief A struct to hold the marker data */ - // TODO: this marker is same as account_tx, reuse in future - struct Marker { - uint32_t ledger; - uint32_t seq; - }; + using Marker = rpc::spec::handlers::nft_history::Marker; /** * @brief A struct to hold the output data of the command @@ -63,23 +56,6 @@ public: bool validated = true; }; - /** - * @brief A struct to hold the input data for the command - */ - struct Input { - std::string nftID; - // You must use at least one of the following fields in your request: - // ledger_index, ledger_hash, ledger_index_min, or ledger_index_max. - std::optional ledgerHash; - std::optional ledgerIndex; - std::optional ledgerIndexMin; - std::optional ledgerIndexMax; - bool binary = false; - bool forward = false; - std::optional limit; - std::optional marker; - }; - using Result = HandlerReturnType; /** @@ -92,43 +68,6 @@ public: { } - /** - * @brief Returns the API specification for the command - * - * @param apiVersion The api version to return the spec for - * @return The spec for the given apiVersion - */ - static RpcSpecConstRef - spec([[maybe_unused]] uint32_t apiVersion) - { - static auto const kRpcSpec = RpcSpec{ - {JS(nft_id), - validation::Required{}, - validation::CustomValidators::uint256HexStringValidator}, - {JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator}, - {JS(ledger_index), validation::CustomValidators::ledgerIndexValidator}, - {JS(ledger_index_min), validation::Type{}}, - {JS(ledger_index_max), validation::Type{}}, - {JS(binary), validation::Type{}}, - {JS(forward), validation::Type{}}, - {JS(limit), - validation::Type{}, - validation::Min(1u), - modifiers::Clamp{kLimitMin, kLimitMax}}, - {JS(marker), - meta::WithCustomError{ - validation::Type{}, - Status{RippledError::RpcInvalidParams, "invalidMarker"} - }, - meta::Section{ - {JS(ledger), validation::Required{}, validation::Type{}}, - {JS(seq), validation::Required{}, validation::Type{}}, - }}, - }; - - return kRpcSpec; - } - /** * @brief Process the NFTHistory command * @@ -148,24 +87,13 @@ private: */ friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); - - /** - * @brief Convert a JSON object to Input type - * - * @param jv The JSON object to convert - * @return Input parsed from the JSON object - */ - friend Input - tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); - - /** - * @brief Convert the Marker to a JSON object - * - * @param [out] jv The JSON object to convert to - * @param marker The marker to convert - */ - friend void - tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker); }; } // namespace rpc + +namespace rpc::spec::handlers::nft_history { + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Marker const& marker); + +} // namespace rpc::spec::handlers::nft_history diff --git a/src/rpc/handlers/VaultInfo.cpp b/src/rpc/handlers/VaultInfo.cpp index 480e8b153..3e8f614e4 100644 --- a/src/rpc/handlers/VaultInfo.cpp +++ b/src/rpc/handlers/VaultInfo.cpp @@ -5,13 +5,11 @@ #include "rpc/RPCHelpers.hpp" #include "rpc/common/Types.hpp" #include "util/Assert.hpp" -#include "util/JsonUtils.hpp" #include #include #include #include -#include #include #include #include @@ -22,10 +20,9 @@ #include #include -#include +#include #include #include -#include #include namespace rpc { @@ -46,7 +43,7 @@ validate(VaultInfoHandler::Input const& input) bool const hasOwner = input.owner.has_value(); bool const hasSeq = input.tnxSequence.has_value(); - // Only valid combinations: (vaultID) or (owner + ledgerIndex) + // Only valid combinations: (vaultID) or (owner + seq) // NOLINTNEXTLINE(readability-simplify-boolean-expr) return (hasVaultId && !hasOwner && !hasSeq) || (!hasVaultId && hasOwner && hasSeq); } @@ -68,11 +65,10 @@ VaultInfoHandler::process(VaultInfoHandler::Input const& input, Context const& c auto const range = sharedPtrBackend_->fetchLedgerRange(); ASSERT(range.has_value(), "VaultInfo's ledger range must be available"); - auto const expectedLgrInfo = getLedgerHeaderFromHashOrSeq( + auto const expectedLgrInfo = getLedgerHeaderFromLedgerSpecifier( *sharedPtrBackend_, ctx.yield, - std::nullopt, - input.ledgerIndex, + input.ledger, range->maxSequence // NOLINT(bugprone-unchecked-optional-access) ); @@ -84,12 +80,11 @@ VaultInfoHandler::process(VaultInfoHandler::Input const& input, Context const& c // Extract the vault keylet based on input auto const vaultKeylet = [&]() -> std::expected { if (input.owner && input.tnxSequence) { - auto const accountStr = *input.owner; - auto const accountID = accountFromStringStrict(accountStr); + auto const& accountID = *input.owner; // checks that account exists { - auto const accountKeylet = xrpl::keylet::account(*accountID); + auto const accountKeylet = xrpl::keylet::account(accountID); auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(accountKeylet.key, lgrInfo.seq, ctx.yield); @@ -97,13 +92,10 @@ VaultInfoHandler::process(VaultInfoHandler::Input const& input, Context const& c return std::unexpected{Status{RippledError::RpcEntryNotFound}}; } - return xrpl::keylet::vault(*accountID, xrpl::SeqProxy::rawSequence(*input.tnxSequence)); + return xrpl::keylet::vault(accountID, xrpl::SeqProxy::rawSequence(*input.tnxSequence)); } - xrpl::uint256 nodeIndex; - if (nodeIndex.parseHex(*input.vaultID)) - return xrpl::keylet::vault(nodeIndex); - return std::unexpected{Status{RippledError::RpcEntryNotFound}}; + return xrpl::keylet::vault(*input.vaultID); }(); if (not vaultKeylet.has_value()) @@ -157,28 +149,4 @@ tag_invoke( }; } -VaultInfoHandler::Input -tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) -{ - auto input = VaultInfoHandler::Input{}; - auto const& jsonObject = jv.as_object(); - - if (jsonObject.contains(JS(owner))) - input.owner = jsonObject.at(JS(owner)).as_string(); - - if (jsonObject.contains(JS(seq))) - input.tnxSequence = util::integralValueAs(jsonObject.at(JS(seq))); - - if (jsonObject.contains(JS(vault_id))) - input.vaultID = jsonObject.at(JS(vault_id)).as_string(); - - if (jsonObject.contains(JS(ledger_index))) { - auto const expectedLedgerIndex = util::getLedgerIndex(jsonObject.at(JS(ledger_index))); - if (expectedLedgerIndex.has_value()) - input.ledgerIndex = *expectedLedgerIndex; - } - - return input; -} - } // namespace rpc diff --git a/src/rpc/handlers/VaultInfo.hpp b/src/rpc/handlers/VaultInfo.hpp index c2a04818c..9a7a3a2b4 100644 --- a/src/rpc/handlers/VaultInfo.hpp +++ b/src/rpc/handlers/VaultInfo.hpp @@ -1,30 +1,22 @@ #pragma once #include "data/BackendInterface.hpp" -#include "rpc/Errors.hpp" -#include "rpc/JS.hpp" -#include "rpc/common/MetaProcessors.hpp" -#include "rpc/common/Specs.hpp" #include "rpc/common/Types.hpp" -#include "rpc/common/Validators.hpp" #include -#include #include -#include -#include +#include +#include #include #include -#include -#include namespace rpc { /** * @brief The vault_info command retrieves information about a vault, currency, shares etc. */ -class VaultInfoHandler { +class VaultInfoHandler : public rpc::spec::HandlerFor { std::shared_ptr sharedPtrBackend_; public: @@ -35,16 +27,6 @@ public: */ VaultInfoHandler(std::shared_ptr sharedPtrBackend); - /** - * @brief A struct to hold the input data for the command - */ - struct Input { - std::optional vaultID; - std::optional owner; - std::optional tnxSequence; - std::optional ledgerIndex; - }; - /** * @brief A struct to hold the output data for the command */ @@ -56,36 +38,6 @@ public: using Result = HandlerReturnType; - /** - * @brief Returns the API specification for the command - * - * @param apiVersion The api version to return the spec for - * @return The spec for the given apiVersion - */ - static RpcSpecConstRef - spec([[maybe_unused]] uint32_t apiVersion) - { - static auto const kRpcSpec = RpcSpec{ - {JS(vault_id), - meta::WithCustomError{ - validation::CustomValidators::uint256HexStringValidator, - Status(ClioError::RpcMalformedRequest) - }}, - {JS(owner), - meta::WithCustomError{ - validation::CustomValidators::accountBase58Validator, - Status(ClioError::RpcMalformedRequest, "OwnerNotHexString") - }}, - {JS(seq), - meta::WithCustomError{ - validation::Type{}, Status(ClioError::RpcMalformedRequest) - }}, - {JS(ledger_index), validation::CustomValidators::ledgerIndexValidator}, - }; - - return kRpcSpec; - } - /** * @brief Process the VaultInfo command * @@ -105,15 +57,6 @@ private: */ friend void tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); - - /** - * @brief Convert a JSON object to Input type - * - * @param jv The JSON object to convert - * @return Input parsed from the JSON object - */ - friend Input - tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); }; } // namespace rpc diff --git a/tests/unit/rpc/JsonBoolTests.cpp b/tests/unit/rpc/JsonBoolTests.cpp index 95a546cb3..f3805b3d3 100644 --- a/tests/unit/rpc/JsonBoolTests.cpp +++ b/tests/unit/rpc/JsonBoolTests.cpp @@ -1,9 +1,9 @@ -#include "rpc/common/JsonBool.hpp" #include "util/NameGenerator.hpp" #include #include #include +#include #include #include @@ -78,5 +78,5 @@ TEST_P(JsonBoolTests, Parse) auto const testBundle = GetParam(); auto const jv = boost::json::parse(testBundle.json).as_object(); ASSERT_TRUE(jv.contains("test_bool")); - EXPECT_EQ(testBundle.expectedBool, value_to(jv.at("test_bool")).value); + EXPECT_EQ(testBundle.expectedBool, value_to(jv.at("test_bool")).value); } diff --git a/tests/unit/rpc/RPCHelpersTests.cpp b/tests/unit/rpc/RPCHelpersTests.cpp index 990e00286..0529adbc6 100644 --- a/tests/unit/rpc/RPCHelpersTests.cpp +++ b/tests/unit/rpc/RPCHelpersTests.cpp @@ -837,110 +837,6 @@ TEST_F(RPCHelpersTest, FetchAndCheckAnyFlagExists_TrustLineIsFrozenAndCheckFreez }); } -TEST_F(RPCHelpersTest, ParseDelegateType) -{ - auto result = parseDelegateType(boost::json::value("authorizer")); - ASSERT_TRUE(result.has_value()); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - EXPECT_EQ(*result, DelegateFilter::Role::Authorizer); - - result = parseDelegateType(boost::json::value("actor")); - ASSERT_TRUE(result.has_value()); - // NOLINTNEXTLINE(bugprone-unchecked-optional-access) - EXPECT_EQ(*result, DelegateFilter::Role::Actor); - - // invalid types - result = parseDelegateType(boost::json::value("invalid_type")); - EXPECT_FALSE(result.has_value()); - - result = parseDelegateType(boost::json::value(123)); - EXPECT_FALSE(result.has_value()); - - result = parseDelegateType(boost::json::value(true)); - EXPECT_FALSE(result.has_value()); -} - -TEST_F(RPCHelpersTest, ParseDelegateFilter_Success) -{ - // only delegate agent is valid - { - auto const jsonStr = R"JSON({ - "delegate_filter": "authorizer" - })JSON"; - auto const json = boost::json::parse(jsonStr).as_object(); - - auto const result = parseDelegateFilter(json); - ASSERT_TRUE(result.has_value()); - // NOLINTBEGIN(bugprone-unchecked-optional-access) - EXPECT_EQ(result->delegateType, DelegateFilter::Role::Authorizer); - EXPECT_FALSE(result->counterParty.has_value()); - // NOLINTEND(bugprone-unchecked-optional-access) - } - - // delegate agent + counter_party is valid - { - auto const jsonStr = fmt::format( - R"JSON({{ - "delegate_filter": "actor", - "counter_party": "{}" - }})JSON", - kAccount2 - ); - auto const json = boost::json::parse(jsonStr).as_object(); - - auto const result = parseDelegateFilter(json); - ASSERT_TRUE(result.has_value()); - // NOLINTBEGIN(bugprone-unchecked-optional-access) - EXPECT_EQ(result->delegateType, DelegateFilter::Role::Actor); - ASSERT_TRUE(result->counterParty.has_value()); - EXPECT_EQ(*result->counterParty, kAccount2); - // NOLINTEND(bugprone-unchecked-optional-access) - } -} - -TEST_F(RPCHelpersTest, ParseDelegateFilter_Failures) -{ - // Missing required "delegate_filter" key - { - auto const jsonStr = fmt::format( - R"JSON({{ - "counter_party": "{}" - }})JSON", - kAccount2 - ); - auto const json = boost::json::parse(jsonStr).as_object(); - EXPECT_FALSE(parseDelegateFilter(json).has_value()); - } - - // "delegate_filter" is not a string (it's an integer) - { - auto const jsonStr = R"JSON({ - "delegate_filter": 123 - })JSON"; - auto const json = boost::json::parse(jsonStr).as_object(); - EXPECT_FALSE(parseDelegateFilter(json).has_value()); - } - - // "delegate_filter" is a string but invalid value - { - auto const jsonStr = R"JSON({ - "delegate_filter": "random_string" - })JSON"; - auto const json = boost::json::parse(jsonStr).as_object(); - EXPECT_FALSE(parseDelegateFilter(json).has_value()); - } - - // "counter_party" exists but is not a string (it's a number) - { - auto const jsonStr = R"JSON({ - "delegate_filter": "authorizer", - "counter_party": 9999 - })JSON"; - auto const json = boost::json::parse(jsonStr).as_object(); - EXPECT_FALSE(parseDelegateFilter(json).has_value()); - } -} - namespace { xrpl::MPTIssue diff --git a/tests/unit/rpc/filters/impl/DelegateTransactionsFilterTests.cpp b/tests/unit/rpc/filters/impl/DelegateTransactionsFilterTests.cpp index 157388e48..86c297167 100644 --- a/tests/unit/rpc/filters/impl/DelegateTransactionsFilterTests.cpp +++ b/tests/unit/rpc/filters/impl/DelegateTransactionsFilterTests.cpp @@ -1,5 +1,5 @@ #include "data/Types.hpp" -#include "rpc/common/Types.hpp" +#include "rpc/filters/TransactionFilter.hpp" #include "rpc/filters/impl/DelegateTransactionsFilter.hpp" #include "util/TestObject.hpp" @@ -36,7 +36,9 @@ protected: TEST_F(DelegateTransactionFilterTest, ReturnsFalseIfNoDelegateField) { - DelegateFilter const filterParams{DelegateFilter::Role::Authorizer}; + DelegateFilter const filterParams{ + .delegateType = DelegateFilter::Role::Authorizer, .counterParty = std::nullopt + }; DelegateTransactionFilter const filter(filterParams, kAccountOwner); // Create standard tx (no delegate field) using standard TestObject helper @@ -57,7 +59,9 @@ TEST_F(DelegateTransactionFilterTest, ReturnsFalseIfNoDelegateField) TEST_F(DelegateTransactionFilterTest, RoleAuthorizer_MatchesWhenUserIsSigner) { - DelegateFilter const filterParams{DelegateFilter::Role::Authorizer}; + DelegateFilter const filterParams{ + .delegateType = DelegateFilter::Role::Authorizer, .counterParty = std::nullopt + }; DelegateTransactionFilter const filter(filterParams, kAccountDelegator); auto blob = createBlob(to_string(kAccountOwner), to_string(kAccountDelegator)); @@ -70,7 +74,9 @@ TEST_F(DelegateTransactionFilterTest, RoleAuthorizer_MatchesWhenUserIsSigner) TEST_F(DelegateTransactionFilterTest, RoleAuthorizer_FailsWhenUserIsNotSigner) { - DelegateFilter const filterParams{DelegateFilter::Role::Authorizer}; + DelegateFilter const filterParams{ + .delegateType = DelegateFilter::Role::Authorizer, .counterParty = std::nullopt + }; DelegateTransactionFilter const filter(filterParams, kAccountDestination); auto blob = createBlob(to_string(kAccountOwner), to_string(kAccountDelegator)); @@ -81,7 +87,9 @@ TEST_F(DelegateTransactionFilterTest, RoleAuthorizer_FailsWhenUserIsNotSigner) TEST_F(DelegateTransactionFilterTest, RoleAuthorizer_WithCounterparty_Match) { - DelegateFilter const filterParams{DelegateFilter::Role::Authorizer, to_string(kAccountOwner)}; + DelegateFilter const filterParams{ + .delegateType = DelegateFilter::Role::Authorizer, .counterParty = to_string(kAccountOwner) + }; DelegateTransactionFilter const filter(filterParams, kAccountDelegator); auto blob = createBlob(to_string(kAccountOwner), to_string(kAccountDelegator)); @@ -95,7 +103,8 @@ TEST_F(DelegateTransactionFilterTest, RoleAuthorizer_WithCounterparty_Match) TEST_F(DelegateTransactionFilterTest, RoleAuthorizer_WithCounterparty_Mismatch) { DelegateFilter const filterParams{ - DelegateFilter::Role::Authorizer, to_string(kAccountDestination) + .delegateType = DelegateFilter::Role::Authorizer, + .counterParty = to_string(kAccountDestination) }; DelegateTransactionFilter const filter(filterParams, kAccountDelegator); @@ -107,7 +116,9 @@ TEST_F(DelegateTransactionFilterTest, RoleAuthorizer_WithCounterparty_Mismatch) TEST_F(DelegateTransactionFilterTest, RoleActor_MatchesWhenUserIsOwner) { - DelegateFilter const filterParams{DelegateFilter::Role::Actor}; + DelegateFilter const filterParams{ + .delegateType = DelegateFilter::Role::Actor, .counterParty = std::nullopt + }; DelegateTransactionFilter const filter(filterParams, kAccountOwner); auto blob = createBlob(to_string(kAccountOwner), to_string(kAccountDelegator)); @@ -120,7 +131,9 @@ TEST_F(DelegateTransactionFilterTest, RoleActor_MatchesWhenUserIsOwner) TEST_F(DelegateTransactionFilterTest, RoleActor_FailsWhenUserIsNotOwner) { - DelegateFilter const filterParams{DelegateFilter::Role::Actor}; + DelegateFilter const filterParams{ + .delegateType = DelegateFilter::Role::Actor, .counterParty = std::nullopt + }; DelegateTransactionFilter const filter(filterParams, kAccountDestination); auto blob = createBlob(to_string(kAccountOwner), to_string(kAccountDelegator)); @@ -131,7 +144,9 @@ TEST_F(DelegateTransactionFilterTest, RoleActor_FailsWhenUserIsNotOwner) TEST_F(DelegateTransactionFilterTest, RoleActor_WithCounterparty_Match) { - DelegateFilter const filterParams{DelegateFilter::Role::Actor, to_string(kAccountDelegator)}; + DelegateFilter const filterParams{ + .delegateType = DelegateFilter::Role::Actor, .counterParty = to_string(kAccountDelegator) + }; DelegateTransactionFilter const filter(filterParams, kAccountOwner); auto blob = createBlob(to_string(kAccountOwner), to_string(kAccountDelegator)); @@ -144,7 +159,9 @@ TEST_F(DelegateTransactionFilterTest, RoleActor_WithCounterparty_Match) TEST_F(DelegateTransactionFilterTest, RoleActor_WithCounterparty_Mismatch) { - DelegateFilter const filterParams{DelegateFilter::Role::Actor, to_string(kAccountDestination)}; + DelegateFilter const filterParams{ + .delegateType = DelegateFilter::Role::Actor, .counterParty = to_string(kAccountDestination) + }; DelegateTransactionFilter const filter(filterParams, kAccountOwner); auto blob = createBlob(to_string(kAccountOwner), to_string(kAccountDelegator)); diff --git a/tests/unit/rpc/handlers/AccountTxTests.cpp b/tests/unit/rpc/handlers/AccountTxTests.cpp index a24f302e0..7043d1043 100644 --- a/tests/unit/rpc/handlers/AccountTxTests.cpp +++ b/tests/unit/rpc/handlers/AccountTxTests.cpp @@ -119,21 +119,21 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest, .testJson = R"JSON({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_index": "x"})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledgerIndexMalformed" + .expectedErrorMessage = "Invalid field 'ledger_index', not string or number." }, AccountTxParamTestCaseBundle{ .testName = "ledger_hashInvalid", .testJson = R"JSON({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": "x"})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledger_hashMalformed" + .expectedErrorMessage = "Invalid field 'ledger_hash'." }, AccountTxParamTestCaseBundle{ .testName = "ledger_hashNotString", .testJson = R"JSON({"account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "ledger_hash": 123})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledger_hashNotString" + .expectedErrorMessage = "Invalid field 'ledger_hash', not string." }, AccountTxParamTestCaseBundle{ .testName = "limitNotInt", @@ -453,7 +453,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest, "mpt_issuance_id": "xxx" })JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "mpt_issuance_idMalformed" + .expectedErrorMessage = "Invalid field 'mpt_issuance_id'." }, AccountTxParamTestCaseBundle{ .testName = "MPTIssuanceIdNotString", @@ -462,7 +462,7 @@ struct AccountTxParameterTest : public RPCAccountTxHandlerTest, "mpt_issuance_id": 12 })JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "mpt_issuance_idNotString" + .expectedErrorMessage = "Invalid field 'mpt_issuance_id'." } }; }; diff --git a/tests/unit/rpc/handlers/AllHandlerTests.cpp b/tests/unit/rpc/handlers/AllHandlerTests.cpp index b12faa9e6..3f2e35426 100644 --- a/tests/unit/rpc/handlers/AllHandlerTests.cpp +++ b/tests/unit/rpc/handlers/AllHandlerTests.cpp @@ -178,7 +178,7 @@ AccountTxHandler::Input createInput() { AccountTxHandler::Input input{}; - input.account = kAccount; + input.account = getAccountIdWithString(kAccount); return input; } @@ -255,7 +255,7 @@ VaultInfoHandler::Input createInput() { VaultInfoHandler::Input input{}; - input.vaultID = kVaultId; + input.vaultID = xrpl::uint256{kVaultId}; return input; } diff --git a/tests/unit/rpc/handlers/CredentialHelpersTests.cpp b/tests/unit/rpc/handlers/CredentialHelpersTests.cpp index b11935bb5..e10482746 100644 --- a/tests/unit/rpc/handlers/CredentialHelpersTests.cpp +++ b/tests/unit/rpc/handlers/CredentialHelpersTests.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #include #include #include +#include using namespace rpc; using namespace testing; @@ -101,7 +103,7 @@ class CredentialHelperTest : public util::prometheus::WithPrometheus, TEST_F(CredentialHelperTest, GetInvalidCredentialArray) { - boost::json::array credentialsArray = {kCredentialId}; + std::vector const credentialsArray{xrpl::uint256{kCredentialId}}; auto const info = createLedgerHeader(kIndex1, 30); util::spawn(ctx_, [&](boost::asio::yield_context yield) { @@ -128,7 +130,7 @@ TEST_F(CredentialHelperTest, GetValidCredentialArray) .WillByDefault(Return(credLedgerObject.getSerializer().peekData())); EXPECT_CALL(*backend_, doFetchLedgerObject).Times(1); - boost::json::array credentialsArray = {kCredentialId}; + std::vector const credentialsArray{xrpl::uint256{kCredentialId}}; xrpl::STArray expectedAuthCreds; xrpl::STObject credential(xrpl::sfCredential); diff --git a/tests/unit/rpc/handlers/DepositAuthorizedTests.cpp b/tests/unit/rpc/handlers/DepositAuthorizedTests.cpp index 457a3fd97..b774fd4c7 100644 --- a/tests/unit/rpc/handlers/DepositAuthorizedTests.cpp +++ b/tests/unit/rpc/handlers/DepositAuthorizedTests.cpp @@ -133,7 +133,7 @@ generateTestValuesForParametersTest() "ledger_hash": "x" })JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledger_hashMalformed", + .expectedErrorMessage = "Invalid field 'ledger_hash'.", }, { .testName = "LedgerHashNotString", @@ -143,7 +143,7 @@ generateTestValuesForParametersTest() "ledger_hash": 123 })JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledger_hashNotString", + .expectedErrorMessage = "Invalid field 'ledger_hash', not string.", }, { .testName = "LedgerIndexNotInt", @@ -153,7 +153,7 @@ generateTestValuesForParametersTest() "ledger_index": "x" })JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledgerIndexMalformed", + .expectedErrorMessage = "Invalid field 'ledger_index', not string or number.", }, { .testName = "CredentialsNotArray", diff --git a/tests/unit/rpc/handlers/GetAggregatePriceTests.cpp b/tests/unit/rpc/handlers/GetAggregatePriceTests.cpp index 4a0855082..5c39c284a 100644 --- a/tests/unit/rpc/handlers/GetAggregatePriceTests.cpp +++ b/tests/unit/rpc/handlers/GetAggregatePriceTests.cpp @@ -95,19 +95,19 @@ generateTestValuesForParametersTest() .testName = "ledger_indexInvalid", .testJson = R"JSON({"ledger_index": "x"})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledgerIndexMalformed" + .expectedErrorMessage = "Invalid field 'ledger_index', not string or number." }, GetAggregatePriceParamTestCaseBundle{ .testName = "ledger_hashInvalid", .testJson = R"JSON({"ledger_hash": "x"})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledger_hashMalformed" + .expectedErrorMessage = "Invalid field 'ledger_hash'." }, GetAggregatePriceParamTestCaseBundle{ .testName = "ledger_hashNotString", .testJson = R"JSON({"ledger_hash": 123})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledger_hashNotString" + .expectedErrorMessage = "Invalid field 'ledger_hash', not string." }, GetAggregatePriceParamTestCaseBundle{ .testName = "no_oracles", diff --git a/tests/unit/rpc/handlers/LedgerDataTests.cpp b/tests/unit/rpc/handlers/LedgerDataTests.cpp index 3bf7ed676..a3a912e85 100644 --- a/tests/unit/rpc/handlers/LedgerDataTests.cpp +++ b/tests/unit/rpc/handlers/LedgerDataTests.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -66,19 +67,19 @@ generateTestValuesForParametersTest() .testName = "ledger_indexInvalid", .testJson = R"JSON({"ledger_index": "x"})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledgerIndexMalformed" + .expectedErrorMessage = "Invalid field 'ledger_index', not string or number." }, LedgerDataParamTestCaseBundle{ .testName = "ledger_hashInvalid", .testJson = R"JSON({"ledger_hash": "x"})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledger_hashMalformed" + .expectedErrorMessage = "Invalid field 'ledger_hash'." }, LedgerDataParamTestCaseBundle{ .testName = "ledger_hashNotString", .testJson = R"JSON({"ledger_hash": 123})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledger_hashNotString" + .expectedErrorMessage = "Invalid field 'ledger_hash', not string." }, LedgerDataParamTestCaseBundle{ .testName = "binaryNotBool", @@ -108,7 +109,7 @@ generateTestValuesForParametersTest() .testName = "markerInvalid", .testJson = R"JSON({"marker": "xxx"})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "markerMalformed" + .expectedErrorMessage = "Invalid field 'marker'." }, LedgerDataParamTestCaseBundle{ .testName = "markerOutOfOrder", @@ -703,6 +704,43 @@ TEST_F(RPCLedgerDataHandlerTest, Binary) }); } +// The default page size depends on binary vs json, so an omitted `limit` must fall back to +// kLimitBinary here rather than the json default. Mirrors xrpld's pageLength(isBinary). +TEST_F(RPCLedgerDataHandlerTest, BinaryNoLimitUsesBinaryDefault) +{ + EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1); + ON_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _)) + .WillByDefault(Return(createLedgerHeader(kLedgerHash, kRangeMax))); + + auto limit = LedgerDataHandler::kLimitBinary + 1; + std::vector bbs; + + EXPECT_CALL(*backend_, doFetchSuccessorKey).Times(LedgerDataHandler::kLimitBinary); + ON_CALL(*backend_, doFetchSuccessorKey(_, kRangeMax, _)) + .WillByDefault(Return(xrpl::uint256{kIndex2})); + + while ((limit--) != 0u) { + auto const line = createRippleStateLedgerObject( + "USD", kAccount2, 10, kAccount, 100, kAccount2, 200, kTxnId, 123 + ); + bbs.push_back(line.getSerializer().peekData()); + } + + ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs)); + EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1); + + runSpawn([&, this](auto yield) { + auto const handler = AnyHandler{LedgerDataHandler{backend_}}; + auto const req = boost::json::parse(R"JSON({"binary": true})JSON"); + auto const output = handler.process(req, Context{yield}); + ASSERT_TRUE(output); + EXPECT_EQ( + output.result->as_object().at("state").as_array().size(), + LedgerDataHandler::kLimitBinary + ); + }); +} + TEST_F(RPCLedgerDataHandlerTest, BinaryLimitMoreThanMax) { EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1); @@ -891,7 +929,7 @@ TEST(RPCLedgerDataHandlerSpecTest, DeprecatedFields) {"ledger", "some"} }; auto const spec = LedgerDataHandler::spec(2); - auto const warnings = spec.check(json); + auto const warnings = rpc::spec::toJsonArray(spec.check(json)); ASSERT_EQ(warnings.size(), 1); ASSERT_TRUE(warnings[0].is_object()); auto const& warning = warnings[0].as_object(); diff --git a/tests/unit/rpc/handlers/MPTokenIssuanceHistoryTests.cpp b/tests/unit/rpc/handlers/MPTokenIssuanceHistoryTests.cpp index f2ceed962..d8a9eaf8d 100644 --- a/tests/unit/rpc/handlers/MPTokenIssuanceHistoryTests.cpp +++ b/tests/unit/rpc/handlers/MPTokenIssuanceHistoryTests.cpp @@ -84,7 +84,7 @@ generateTestValuesForParametersTest() .testName = "MalformedMptIssuanceID", .testJson = R"JSON({"mpt_issuance_id": "NOTAHEXSTRING"})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "mpt_issuance_idMalformed" + .expectedErrorMessage = "Invalid field 'mpt_issuance_id'." }, MPTokenIssuanceHistoryParamTestCaseBundle{ .testName = "BinaryNotBool", @@ -158,21 +158,21 @@ generateTestValuesForParametersTest() .testJson = R"JSON({"mpt_issuance_id": "000004C463C52827307480341125DA0577DEFC38405B0E3E", "ledger_index": "x"})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledgerIndexMalformed" + .expectedErrorMessage = "Invalid field 'ledger_index', not string or number." }, MPTokenIssuanceHistoryParamTestCaseBundle{ .testName = "LedgerHashInvalid", .testJson = R"JSON({"mpt_issuance_id": "000004C463C52827307480341125DA0577DEFC38405B0E3E", "ledger_hash": "x"})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledger_hashMalformed" + .expectedErrorMessage = "Invalid field 'ledger_hash'." }, MPTokenIssuanceHistoryParamTestCaseBundle{ .testName = "LedgerHashNotString", .testJson = R"JSON({"mpt_issuance_id": "000004C463C52827307480341125DA0577DEFC38405B0E3E", "ledger_hash": 123})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledger_hashNotString" + .expectedErrorMessage = "Invalid field 'ledger_hash', not string." }, MPTokenIssuanceHistoryParamTestCaseBundle{ .testName = "LimitNotInt", @@ -1855,14 +1855,18 @@ TEST_F(RPCMPTokenIssuanceHistoryHandlerTest, LedgerHashWithOnlyLedgerIndexMax) }); } -TEST_F(RPCMPTokenIssuanceHistoryHandlerTest, LedgerIndexValidatedStringIsNotASpecifier) +// `ledger_index` selects a single ledger, and "validated" is just a shortcut naming one, so +// the search range collapses to it. Matches account_tx here and in xrpld (AccountTx.cpp +// getLedgerRange() sets uLedgerMin = uLedgerMax = seq for any non-range specifier). +TEST_F(RPCMPTokenIssuanceHistoryHandlerTest, LedgerIndexValidatedStringSelectsThatLedger) { - // "validated" resolves to no concrete index, so the request keeps the full ledger range. - auto const transactions = genTransactions(kMinSeq + 1, kMaxSeq - 1); + auto const transactions = genTransactions(kMaxSeq, kMaxSeq - 1); auto const transCursor = TransactionsAndCursor{.txns = transactions, .cursor = TransactionsCursor{12, 34}}; ON_CALL(*backend_, fetchMPTokenIssuanceTransactions).WillByDefault(Return(transCursor)); - EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(0); + + auto const ledgerHeader = createLedgerHeader(kLedgerHash, kMaxSeq); + EXPECT_CALL(*backend_, fetchLedgerBySequence(kMaxSeq, _)).WillOnce(Return(ledgerHeader)); EXPECT_CALL(*backend_, fetchLedgerByHash).Times(0); runSpawn([&, this](auto yield) { @@ -1878,9 +1882,8 @@ TEST_F(RPCMPTokenIssuanceHistoryHandlerTest, LedgerIndexValidatedStringIsNotASpe ); auto const output = handler.process(req, Context{yield}); ASSERT_TRUE(output); - EXPECT_EQ(output.result->at("ledger_index_min").as_uint64(), kMinSeq); + EXPECT_EQ(output.result->at("ledger_index_min").as_uint64(), kMaxSeq); EXPECT_EQ(output.result->at("ledger_index_max").as_uint64(), kMaxSeq); - EXPECT_EQ(output.result->at("transactions").as_array().size(), 2); }); } diff --git a/tests/unit/rpc/handlers/NFTHistoryTests.cpp b/tests/unit/rpc/handlers/NFTHistoryTests.cpp index e0d9f271f..4867bf2b3 100644 --- a/tests/unit/rpc/handlers/NFTHistoryTests.cpp +++ b/tests/unit/rpc/handlers/NFTHistoryTests.cpp @@ -96,21 +96,21 @@ generateTestValuesForParametersTest() .testJson = R"JSON({"nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_index": "x"})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledgerIndexMalformed" + .expectedErrorMessage = "Invalid field 'ledger_index', not string or number." }, NFTHistoryParamTestCaseBundle{ .testName = "ledger_hashInvalid", .testJson = R"JSON({"nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_hash": "x"})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledger_hashMalformed" + .expectedErrorMessage = "Invalid field 'ledger_hash'." }, NFTHistoryParamTestCaseBundle{ .testName = "ledger_hashNotString", .testJson = R"JSON({"nft_id": "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004", "ledger_hash": 123})JSON", .expectedError = "invalidParams", - .expectedErrorMessage = "ledger_hashNotString" + .expectedErrorMessage = "Invalid field 'ledger_hash', not string." }, NFTHistoryParamTestCaseBundle{ .testName = "limitNotInt", @@ -908,6 +908,38 @@ TEST_F(RPCNFTHistoryHandlerTest, SpecificLedgerIndex) }); } +// "validated" is a shortcut naming a single ledger, so the search range collapses to it +// rather than staying at the full available range. Matches account_tx and xrpld's +// AccountTx.cpp getLedgerRange(), which sets uLedgerMin = uLedgerMax for any non-range +// specifier (the shortcuts included). +TEST_F(RPCNFTHistoryHandlerTest, SpecificLedgerIndexValidated) +{ + auto const transactions = genTransactions(kMaxSeq, kMaxSeq - 1); + auto const transCursor = + TransactionsAndCursor{.txns = transactions, .cursor = TransactionsCursor{12, 34}}; + ON_CALL(*backend_, fetchNFTTransactions).WillByDefault(Return(transCursor)); + + auto const ledgerHeader = createLedgerHeader(kLedgerHash, kMaxSeq); + EXPECT_CALL(*backend_, fetchLedgerBySequence(kMaxSeq, _)).WillOnce(Return(ledgerHeader)); + + runSpawn([&, this](auto yield) { + auto const handler = AnyHandler{NFTHistoryHandler{backend_}}; + static auto const kInput = boost::json::parse( + fmt::format( + R"JSON({{ + "nft_id": "{}", + "ledger_index": "validated" + }})JSON", + kNftId + ) + ); + auto const output = handler.process(kInput, Context{yield}); + ASSERT_TRUE(output); + EXPECT_EQ(output.result->at("ledger_index_min").as_uint64(), kMaxSeq); + EXPECT_EQ(output.result->at("ledger_index_max").as_uint64(), kMaxSeq); + }); +} + TEST_F(RPCNFTHistoryHandlerTest, SpecificNonexistLedgerIntIndex) { EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1); diff --git a/tests/unit/rpc/handlers/VaultInfoTests.cpp b/tests/unit/rpc/handlers/VaultInfoTests.cpp index a7910500c..782f2129c 100644 --- a/tests/unit/rpc/handlers/VaultInfoTests.cpp +++ b/tests/unit/rpc/handlers/VaultInfoTests.cpp @@ -98,9 +98,9 @@ generateTestValuesForParametersTest() "owner": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", "seq": "asdf" })JSON", - .expectedError = "malformedRequest", - .expectedErrorCode = ClioError::RpcMalformedRequest, - .expectedErrorMessage = "Malformed request." + .expectedError = "invalidParams", + .expectedErrorCode = RippledError::RpcInvalidParams, + .expectedErrorMessage = "Invalid field 'seq', not a positive 32-bit integer." }, VaultInfoParamTestCaseBundle{ .testName = "OwnerNotAString", @@ -108,9 +108,9 @@ generateTestValuesForParametersTest() "owner": true, "seq": 3 })JSON", - .expectedError = "malformedRequest", - .expectedErrorCode = ClioError::RpcMalformedRequest, - .expectedErrorMessage = "OwnerNotHexString" + .expectedError = "actMalformed", + .expectedErrorCode = RippledError::RpcActMalformed, + .expectedErrorMessage = "Invalid field 'owner', not AccountID." }, VaultInfoParamTestCaseBundle{ .testName = "OwnerNotAHexString", @@ -118,27 +118,27 @@ generateTestValuesForParametersTest() "owner": "asdf", "seq": 3 })JSON", - .expectedError = "malformedRequest", - .expectedErrorCode = ClioError::RpcMalformedRequest, - .expectedErrorMessage = "OwnerNotHexString" + .expectedError = "actMalformed", + .expectedErrorCode = RippledError::RpcActMalformed, + .expectedErrorMessage = "Invalid field 'owner', not AccountID." }, VaultInfoParamTestCaseBundle{ .testName = "vaultIDNotString", .testJson = R"JSON({ "vault_id": 3 })JSON", - .expectedError = "malformedRequest", - .expectedErrorCode = ClioError::RpcMalformedRequest, - .expectedErrorMessage = "Malformed request." + .expectedError = "invalidParams", + .expectedErrorCode = RippledError::RpcInvalidParams, + .expectedErrorMessage = "Invalid field 'vault_id', not hex string." }, VaultInfoParamTestCaseBundle{ .testName = "vaultIDNotHex256", .testJson = R"JSON({ "vault_id": "idk" })JSON", - .expectedError = "malformedRequest", - .expectedErrorCode = ClioError::RpcMalformedRequest, - .expectedErrorMessage = "Malformed request." + .expectedError = "invalidParams", + .expectedErrorCode = RippledError::RpcInvalidParams, + .expectedErrorMessage = "Invalid field 'vault_id', not hex string." }, VaultInfoParamTestCaseBundle{ .testName = "vaultIDWithOwner",