chore: Commits for 2.9.0-b2 (#3226)

This commit is contained in:
Alex Kremer
2026-09-23 14:22:58 +01:00
committed by GitHub
129 changed files with 2662 additions and 8779 deletions

View File

@@ -3,8 +3,8 @@
"requires": [
"zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708",
"xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688",
"xrpl-rpc-spec/0.1.7#774d2f93c4b48a1523d8d5a94a2b082a%1787768955.574105",
"xrpl/3.4.0-rc1#19678cbb46117ef8a669558ad19d6a1f%1789050823.813662",
"xrpl-rpc-spec/0.1.17#ddfbc1f89da7797b6506fb9ee9488e17%1790136770.803908",
"xrpl/3.4.0#e06b127b3ba92806a43fc7a53fc05fd3%1789577552.219154",
"sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447",
"spdlog/1.17.0#bcbaaf7147bda6ad24ffbd1ac3d7142c%1782736610.443882",
"soci/4.0.3#e726491a03468795453f7c83fc924a96%1782392402.679521",

View File

@@ -17,8 +17,8 @@ class ClioConan(ConanFile):
"fmt/12.1.0",
"libbacktrace/cci.20210118",
"spdlog/1.17.0",
"xrpl-rpc-spec/0.1.7",
"xrpl/3.4.0-rc1",
"xrpl-rpc-spec/0.1.17",
"xrpl/3.4.0",
]
default_options = {

View File

@@ -140,15 +140,10 @@ struct Amendments {
REGISTER(fixCleanup3_1_3);
REGISTER(fixCleanup3_2_0);
REGISTER(ConfidentialTransfer);
// These amendments are added because of libxrpl 3.3.0, but they are not yet supported in Clio.
REGISTER(LendingProtocolV1_1);
REGISTER(BatchV1_1);
REGISTER(Sponsor);
REGISTER(fixCleanup3_3_0);
// These amendments are added because of libxrpl 3.4.0, but they are not necessarily supported
// in Clio yet
REGISTER(fixCleanup3_4_0);
// Obsolete but supported by libxrpl

View File

@@ -16,9 +16,6 @@ target_sources(
CredentialHelpers.cpp
Counters.cpp
WorkQueue.cpp
common/Specs.cpp
common/Validators.cpp
common/MetaProcessors.cpp
common/impl/APIVersionParser.cpp
common/impl/HandlerProvider.cpp
common/impl/HandlerRegistry.cpp
@@ -61,4 +58,7 @@ target_sources(
handlers/VaultInfo.cpp
)
rpcspec_generate_instantiations(OUT_VAR rpcspec_instantiations)
target_sources(clio_rpc PRIVATE ${rpcspec_instantiations})
target_link_libraries(clio_rpc PUBLIC clio_util clio_data rpcspec::rpcspec)

View File

@@ -5,7 +5,6 @@
#include <boost/asio/spawn.hpp>
#include <boost/json/array.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/StringUtilities.h>
@@ -27,9 +26,9 @@
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>
namespace rpc::credentials {
@@ -99,7 +98,7 @@ parseAuthorizeCredentials(boost::json::array const& jv)
std::expected<xrpl::STArray, Status>
fetchCredentialArray(
std::optional<boost::json::array> const& credID,
std::vector<xrpl::uint256> const& credIDs,
xrpl::AccountID const& srcAcc,
BackendInterface const& backend,
xrpl::LedgerHeader const& info,
@@ -107,21 +106,11 @@ fetchCredentialArray(
)
{
xrpl::STArray authCreds;
std::unordered_set<std::string_view> 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<xrpl::uint256, xrpl::uint256::hasher> 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<std::string>(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);

View File

@@ -6,6 +6,7 @@
#include <boost/asio/spawn.hpp>
#include <boost/json/array.hpp>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/LedgerHeader.h>
@@ -17,6 +18,7 @@
#include <optional>
#include <set>
#include <utility>
#include <vector>
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<xrpl::STArray, Status>
fetchCredentialArray(
std::optional<boost::json::array> const& credID,
std::vector<xrpl::uint256> const& credIDs,
xrpl::AccountID const& srcAcc,
BackendInterface const& backend,
xrpl::LedgerHeader const& info,

View File

@@ -4,27 +4,74 @@
The RPC subsystem is where the common framework for handling incoming JSON requests is implemented.
Request validation and deserialisation are **not** defined here. They live in the shared
[`xrpl-rpc-spec`](https://github.com/XRPLF/rpc-spec) library, consumed as `<rpcspec/...>`, so that
one spec per RPC method can be shared verbatim between Clio and xrpld. Clio's job is to supply the
handler body and the response shape.
## Components
See the [common](https://github.com/XRPLF/clio/blob/develop/src/rpc/common) subfolder.
- **AnyHandler**: The type-erased wrapper that allows for storing different handlers in one map/vector.
- **RpcSpec/FieldSpec**: The RPC specification classes, used to specify how incoming JSON is to be validated before it's parsed and passed on to individual handler implementations.
- **Validators/Modifiers**: A bunch of supported validators and modifiers that can be specified as requirements for each `FieldSpec` to make up the final `RpcSpec` of any given RPC handler.
- **Concepts**: The `SomeHandler` concept and its parts, which define what a handler must provide.
- **HandlerRegistry**: The table mapping a method name to its handler factory, plus whether the
method is Clio-only (and therefore never forwarded to xrpld).
From the spec library:
- **`rpc::spec::HandlerFor<Input>`**: Base class supplying the static `parseInput` and `spec`
entry points a handler needs. It resolves the versioned spec for `Input` through ADL, so a
handler names only its `Input` type.
- **`rpcspec/handlers/<method>/Types.hpp`**: The strongly-typed `Input` struct for a method
(`xrpl::AccountID`, `xrpl::uint256`, `LedgerSpecifier`, ... rather than `std::string`).
- **`rpcspec/handlers/<method>/Spec.hpp`**: The consteval spec declaring that method's fields,
their validators and their converters.
## Implementing a handler
See [tests/unit/rpc](https://github.com/XRPLF/clio/tree/develop/tests/unit/rpc) for examples.
See the existing handlers in [src/rpc/handlers](https://github.com/XRPLF/clio/tree/develop/src/rpc/handlers)
for examples; `NFTInfo` is a small one.
Handlers need to fulfil the requirements specified by the `SomeHandler` concept (see `rpc/common/Concepts.hpp`):
Handlers need to fulfil the requirements specified by the `SomeHandler` concept (see
`rpc/common/Concepts.hpp`):
- Expose types:
- `Input` - The POD struct which acts as input for the handler
- Derive from `rpc::spec::HandlerFor<rpc::spec::handlers::<method>::Input>`. This supplies:
- `Output` - The POD struct which acts as output of a valid handler invocation
- `Input` — the strongly-typed input struct, owned by the spec library rather than declared here
- Have a `spec(uint32_t)` member function returning a const reference to an `RpcSpec` describing the JSON input for the specified API version.
- `static parseInput(boost::json::value const&, uint32_t apiVersion)` — validates and
deserialises in one pass, returning `std::expected<Input, Status>`
- Have a `process(Input)` member function that operates on `Input` POD and returns `HandlerReturnType<Output>`
- `static spec(uint32_t apiVersion)` — returns a type-erased `rpc::spec::RpcSpecView`
- Implement `value_from` and `value_to` support using `tag_invoke` as per `boost::json` documentation for these functions.
If the method takes no input at all, skip the base class and expose only `process(Context const&)`.
- Expose an `Output` POD struct which acts as output of a valid handler invocation.
- Have a `process(Input const&, Context const&)` member function returning
`HandlerReturnType<Output>`. Cross-field checks that cannot be expressed in the spec belong here.
- Implement `value_from` support for `Output` using `tag_invoke` as per `boost::json`
documentation. A `value_to` for `Input` is **not** needed — the spec deserialises it.
- Register the method in `rpc/common/impl/HandlerRegistry.cpp`.
If the method has no spec yet, add `Types.hpp` and `Spec.hpp` for it in the spec library first.
> [!IMPORTANT]
> Do not hand-write `template struct rpc::spec::HandlerFor<...>;`, and do not include
> `<rpcspec/HandlerForDefs.hpp>` or `<rpcspec/handlers/*/Spec.hpp>` from a handler. The explicit
> instantiations are generated by `rpcspec_generate_instantiations()` in `src/rpc/CMakeLists.txt`,
> one translation unit per method that has a `Spec.hpp`. A useful side effect is that every spec is
> compiled by Clio's build, so spec-side mistakes surface here.
## Error messages
A method's error codes and messages are part of its wire contract: clients parse them, so an
altered `error`, `error_code` or `error_message` is a breaking change. If a test disagrees with the
code, fix the spec rather than the expectation.
Clio and xrpld do not always word the same failure identically. The spec library expresses those
differences per server rather than unifying early — `ifServerClio()` / `ifServerXrpld()` for
whole validators, and the `RPCSPEC_IS_CLIO` / `RPCSPEC_IS_XRPLD` macros for anything finer.

View File

@@ -569,12 +569,12 @@ getLedgerHeaderFromLedgerSpecifier(
return *maybeLgrInfo;
}
if (resolved.isShortcut()) {
auto const shortcut = std::get<rpc::spec::LedgerShortcut>(resolved.value);
ASSERT(
shortcut == rpc::spec::LedgerShortcut::Validated,
"current/closed ledgers must be forwarded before dispatch"
);
// `current` and `closed` name ledgers Clio does not hold. Forwarding diverts them for the
// methods xrpld can answer; Clio-only methods are not forwarded, so they reach here.
if (resolved.isShortcut() and
std::get<rpc::spec::LedgerShortcut>(resolved.value) !=
rpc::spec::LedgerShortcut::Validated) {
return std::unexpected{Status{RippledError::RpcInvalidParams, "ledgerIndexMalformed"}};
}
auto const ledgerSequence = resolved.isSequence() ? std::get<uint32_t>(resolved.value) : maxSeq;
@@ -1677,7 +1677,7 @@ parseBook(boost::json::object const& request)
}
if (payCurrency == getCurrency && payIssuer == getIssuer)
return std::unexpected{Status{RippledError::RpcBadMarket, "badMarket"}};
return std::unexpected{Status{RippledError::RpcBadMarket}};
std::optional<xrpl::uint256> domainID;
if (request.contains("domain")) {
@@ -1784,47 +1784,4 @@ toJsonWithBinaryTx(data::TransactionAndMetadata const& txnPlusMeta, std::uint32_
return obj;
}
std::optional<DelegateFilter::Role>
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<DelegateFilter>
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<std::string> 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

View File

@@ -313,8 +313,12 @@ getLedgerHeaderFromHashOrSeq(
* only serves validated data. An unspecified ledger resolves via
* @c LedgerSpecifier::resolved(), which the spec library fixes to @c validated for Clio.
*
* @c current and @c closed cannot reach here: @ref specifiesCurrentOrClosedLedger forwards
* those upstream before dispatch.
* @c current and @c closed name ledgers Clio does not hold, and yield @c invalidParams with
* @c ledgerIndexMalformed.
* @ref specifiesCurrentOrClosedLedger diverts such requests to xrpld before dispatch, but only
* for methods xrpld can answer: @c ForwardingProxy::shouldForward returns early for Clio-only
* methods, so those arrive here with the shortcut intact. The spec does not reject the two
* shortcuts either, since xrpld needs them.
*
* @param backend The backend to use
* @param yield The coroutine context
@@ -930,22 +934,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<DelegateFilter::Role>
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<DelegateFilter>
parseDelegateFilter(boost::json::object const& delegateObject);
} // namespace rpc

View File

@@ -1,120 +0,0 @@
#pragma once
#include "rpc/Errors.hpp"
#include "rpc/common/ValidationHelpers.hpp"
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <fmt/format.h>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace rpc::check {
/**
* @brief Warning that checks can return
*/
struct Warning {
/**
* @brief Construct a new Warning object
*
* @param code The warning code
* @param message The warning message
*/
Warning(WarningCode code, std::string message)
: warningCode(code), extraMessage(std::move(message))
{
}
bool
operator==(Warning const& other) const = default;
WarningCode warningCode;
std::string extraMessage;
};
using Warnings = std::vector<Warning>;
/**
* @brief Check for a deprecated fields
*/
template <typename... T>
class Deprecated;
/**
* @brief Check if a field is deprecated
*/
template <>
class Deprecated<> final {
public:
/**
* @brief Check if a field is deprecated
*
* @param value The json value to check
* @param key The key to check
* @return A warning if the field is deprecated or std::nullopt otherwise
*/
[[nodiscard]] static std::optional<Warning>
check(boost::json::value const& value, std::string_view key)
{
if (value.is_object() and value.as_object().contains(key)) {
return Warning{
WarningCode::WarnRpcDeprecated, fmt::format("Field '{}' is deprecated.", key)
};
}
return std::nullopt;
}
};
/**
* @brief Check if a value of a field is deprecated
* @tparam T The type of the field
*/
template <typename T>
class Deprecated<T> final {
T value_;
public:
/**
* @brief Construct a new Deprecated object
*
* @param val The value that is deprecated
*/
Deprecated(T val) : value_(std::move(val))
{
}
/**
* @brief Check if a value of a field is deprecated
*
* @param value The json value to check
* @param key The key to check
* @return A warning if the field is deprecated or std::nullopt otherwise
*/
[[nodiscard]] std::optional<Warning>
check(boost::json::value const& value, std::string_view key) const
{
if (value.is_object() and value.as_object().contains(key) and
validation::checkType<T>(value.as_object().at(key))) {
using boost::json::value_to;
auto const res = value_to<T>(value.as_object().at(key));
if (value_ == res) {
return Warning{
WarningCode::WarnRpcDeprecated,
fmt::format("Value '{}' for field '{}' is deprecated", value_, key)
};
}
}
return std::nullopt;
}
};
/**
* @brief Deduction guide for Deprecated
*/
template <typename... T>
Deprecated(T&&...) -> Deprecated<T...>;
} // namespace rpc::check

View File

@@ -1,7 +1,6 @@
#pragma once
#include "rpc/Errors.hpp"
#include "rpc/common/Checkers.hpp"
#include "rpc/common/Types.hpp"
#include <boost/json/value.hpp>
@@ -18,38 +17,6 @@
namespace rpc {
struct RpcSpec;
/**
* @brief Specifies what a requirement used with @ref rpc::FieldSpec must provide.
*/
template <typename T>
concept SomeRequirement = requires(T a, boost::json::value lval) {
{ a.verify(lval, std::string{}) } -> std::same_as<MaybeError>;
};
/**
* @brief Specifies what a modifier used with @ref rpc::FieldSpec must provide.
*/
template <typename T>
concept SomeModifier = requires(T a, boost::json::value lval) {
{ a.modify(lval, std::string{}) } -> std::same_as<MaybeError>;
};
/**
* @brief Specifies what a check used with @ref rpc::FieldSpec must provide.
*/
template <typename T>
concept SomeCheck = requires(T a, boost::json::value lval) {
{ a.check(lval, std::string{}) } -> std::same_as<std::optional<check::Warning>>;
};
/**
* @brief The requirements of a processor to be used with @ref rpc::FieldSpec.
*/
template <typename T>
concept SomeProcessor = (SomeRequirement<T> or SomeModifier<T>);
/**
* @brief A process function that expects both some Input and a Context.
*/
@@ -67,25 +34,12 @@ concept SomeContextProcessWithoutInput = requires(T a, T::Output out, Context co
{ a.process(ctx) } -> std::same_as<HandlerReturnType<decltype(out)>>;
};
/**
* @brief Specifies what a Handler with Input must provide.
*/
template <typename T>
concept SomeHandlerWithInput = requires(T a, uint32_t version) {
{ a.spec(version) } -> std::same_as<RpcSpec const&>;
} and SomeContextProcessWithInput<T> and boost::json::has_value_to<typename T::Input>::value;
/**
* @brief Specifies what a Handler validated by the shared consteval spec must provide.
*
* Such a handler inherits @c rpc::spec::HandlerFor<Input> from the spec library, which
* supplies a static @c parseInput (validate and deserialise in one pass) and a static
* @c spec returning a type-erased @c RpcSpecView. Presence of @c parseInput is what
* selects this path over @c SomeHandlerWithInput.
*
* The two input paths are mutually exclusive by construction: a legacy handler returns
* @c RpcSpec @c const& from a non-static @c spec and needs a @c value_to for its Input,
* neither of which holds here. @c kIsSingleInputPath asserts that below.
* @c spec returning a type-erased @c RpcSpecView.
*/
template <typename T>
concept SomeHandlerWithTypedInput = requires(uint32_t version, boost::json::value jv) {
@@ -100,21 +54,11 @@ concept SomeHandlerWithTypedInput = requires(uint32_t version, boost::json::valu
template <typename T>
concept SomeHandlerWithoutInput = SomeContextProcessWithoutInput<T>;
/**
* @brief True when @p T does not straddle the legacy and typed input paths.
*
* Guards the @c if @c constexpr chain in @c DefaultProcessor - were a handler to satisfy
* both, the dispatch order alone would silently decide which spec ran.
*/
template <typename T>
constexpr bool kIsSingleInputPath = not(SomeHandlerWithInput<T> and SomeHandlerWithTypedInput<T>);
/**
* @brief Specifies what a Handler type must provide.
*/
template <typename T>
concept SomeHandler =
(SomeHandlerWithInput<T> or SomeHandlerWithTypedInput<T> or SomeHandlerWithoutInput<T>) and
concept SomeHandler = (SomeHandlerWithTypedInput<T> or SomeHandlerWithoutInput<T>) and
boost::json::has_value_from<typename T::Output>::value;
} // namespace rpc

View File

@@ -1,13 +0,0 @@
#pragma once
#include <rpcspec/JsonBool.hpp>
namespace rpc {
/**
* @brief A wrapper around bool that allows conversion from any JSON value.
*
*/
using JsonBool = rpc::spec::JsonBool;
} // namespace rpc

View File

@@ -1,54 +0,0 @@
#include "rpc/common/MetaProcessors.hpp"
#include "rpc/common/Types.hpp"
#include <boost/json/value.hpp>
#include <rpcspec/Errors.hpp>
#include <string_view>
namespace rpc::meta {
[[nodiscard]] MaybeError
Section::verify(boost::json::value& value, std::string_view key) const
{
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
auto& res = value.as_object().at(key);
// if it is not a json object, let other validators fail
if (!res.is_object())
return {};
for (auto const& spec : specs_) {
if (auto const ret = spec.process(res); not ret)
return Error{ret.error()};
}
return {};
}
[[nodiscard]] MaybeError
ValidateArrayAt::verify(boost::json::value& value, std::string_view key) const
{
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
if (not value.as_object().at(key).is_array())
return Error{Status{RippledError::RpcInvalidParams}};
auto& arr = value.as_object().at(key).as_array();
if (idx_ >= arr.size())
return Error{Status{RippledError::RpcInvalidParams}};
auto& res = arr.at(idx_);
for (auto const& spec : specs_) {
if (auto const ret = spec.process(res); not ret)
return Error{ret.error()};
}
return {};
}
} // namespace rpc::meta

View File

@@ -1,224 +0,0 @@
#pragma once
#include "rpc/Errors.hpp"
#include "rpc/common/Concepts.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include <boost/json/value.hpp>
#include <fmt/format.h>
#include <cstddef>
#include <functional>
#include <initializer_list>
#include <optional>
#include <string_view>
#include <utility>
#include <vector>
namespace rpc::meta {
/**
* @brief A meta-processor that acts as a spec for a sub-object/section.
*/
class Section final {
std::vector<FieldSpec> specs_;
public:
/**
* @brief Construct new section validator from a list of specs.
*
* @param specs List of specs @ref FieldSpec
*/
explicit Section(std::initializer_list<FieldSpec> specs) : specs_{specs}
{
}
/**
* @brief Verify that the JSON value representing the section is valid according to the given
* specs.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the section from the outer object
* @return Possibly an error
*/
[[nodiscard]] MaybeError
verify(boost::json::value& value, std::string_view key) const;
};
/**
* @brief A meta-processor that specifies a list of specs to run against the object at the given
* index in the array.
*/
class ValidateArrayAt final {
std::size_t idx_;
std::vector<FieldSpec> specs_;
public:
/**
* @brief Constructs a processor that validates the specified element of a JSON array.
*
* @param idx The index inside the array to validate
* @param specs The specifications to validate against
*/
ValidateArrayAt(std::size_t idx, std::initializer_list<FieldSpec> specs)
: idx_{idx}, specs_{specs}
{
}
/**
* @brief Verify that the JSON array element at given index is valid according the stored specs.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the array from the outer object
* @return Possibly an error
*/
[[nodiscard]] MaybeError
verify(boost::json::value& value, std::string_view key) const;
};
/**
* @brief A meta-processor that specifies a list of requirements to run against when the type
* matches the template parameter.
*/
template <typename Type>
class IfType final {
public:
/**
* @brief Constructs a validator that validates the specs if the type matches.
* @param requirements The requirements to validate against
*/
template <SomeRequirement... Requirements>
explicit IfType(Requirements&&... requirements)
: processor_(
[... r = std::forward<Requirements>(
requirements
)](boost::json::value& j, std::string_view key) -> MaybeError {
std::optional<Status> firstFailure = std::nullopt;
// the check logic is the same as fieldspec
(
[&j, &key, &firstFailure, req = &r]() {
if (firstFailure)
return;
if (auto const res = req->verify(j, key); not res)
firstFailure = res.error();
}(),
...);
if (firstFailure)
return Error{*firstFailure};
return {};
}
)
{
}
IfType(IfType const&) = default;
IfType(IfType&&) = default;
/**
* @brief Verify that the element is valid according to the stored requirements when type
* matches.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the element from the outer object
* @return Possibly an error
*/
[[nodiscard]] MaybeError
verify(boost::json::value& value, std::string_view key) const
{
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
if (not rpc::validation::checkType<Type>(value.as_object().at(key)))
return {}; // ignore if type does not match
return processor_(value, key);
}
private:
std::function<MaybeError(boost::json::value&, std::string_view)> processor_;
};
/**
* @brief A meta-processor that wraps a validator and produces a custom error in case the wrapped
* validator fails.
*/
template <typename RequirementOrModifierType>
requires SomeRequirement<RequirementOrModifierType> or SomeModifier<RequirementOrModifierType>
class WithCustomError final {
RequirementOrModifierType reqOrModifier_;
Status error_;
public:
/**
* @brief Constructs a validator that calls the given validator `req` and returns a custom error
* `err` in case `req` fails.
*
* @param reqOrModifier The requirement to validate against
* @param err The custom error to return in case `req` fails
*/
WithCustomError(RequirementOrModifierType reqOrModifier, Status err)
: reqOrModifier_{std::move(reqOrModifier)}, error_{std::move(err)}
{
}
/**
* @brief Runs the stored validator and produces a custom error if the wrapped validator fails.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the element from the outer object
* @return Possibly an error
*/
[[nodiscard]] MaybeError
verify(boost::json::value const& value, std::string_view key) const
requires SomeRequirement<RequirementOrModifierType>
{
if (auto const res = reqOrModifier_.verify(value, key); not res)
return Error{error_};
return {};
}
/**
* @brief Runs the stored validator and produces a custom error if the wrapped validator fails.
* This is an overload for the requirement which can modify the value. Such as IfType.
*
* @param value The JSON value representing the outer object, this value can be modified by the
* requirement inside
* @param key The key used to retrieve the element from the outer object
* @return Possibly an error
*/
[[nodiscard]] MaybeError
verify(boost::json::value& value, std::string_view key) const
requires SomeRequirement<RequirementOrModifierType>
{
if (auto const res = reqOrModifier_.verify(value, key); not res)
return Error{error_};
return {};
}
/**
* @brief Runs the stored modifier and produces a custom error if the wrapped modifier fails.
*
* @param value The JSON value representing the outer object. This value can be modified by the
* modifier.
* @param key The key used to retrieve the element from the outer object
* @return Possibly an error
*/
MaybeError
modify(boost::json::value& value, std::string_view key) const
requires SomeModifier<RequirementOrModifierType>
{
if (auto const res = reqOrModifier_.modify(value, key); not res)
return Error{error_};
return {};
}
};
} // namespace rpc::meta

View File

@@ -1,161 +0,0 @@
#pragma once
#include "rpc/Errors.hpp"
#include "rpc/common/Types.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <xrpl/protocol/ErrorCodes.h>
#include <concepts>
#include <exception>
#include <functional>
#include <string>
#include <string_view>
namespace rpc::modifiers {
/**
* @brief Clamp value between min and max.
*/
template <typename Type>
class Clamp final {
Type min_;
Type max_;
public:
/**
* @brief Construct the modifier storing min and max values.
*
* @param min
* @param max
*/
explicit Clamp(Type min, Type max) : min_{min}, max_{max}
{
}
/**
* @brief Clamp the value to stored min and max values.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the modified value from the outer object
* @return Possibly an error
*/
[[nodiscard]] MaybeError
modify(boost::json::value& value, std::string_view key) const
{
using boost::json::value_to;
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
// clamp to min_ and max_
auto const oldValue = value_to<Type>(value.as_object().at(key));
value.as_object()[key] = std::clamp<Type>(oldValue, min_, max_);
return {};
}
};
/**
* @brief Convert input string to lower case.
*
* Note: the conversion is only performed if the input value is a string.
*/
struct ToLower final {
/**
* @brief Update the input string to lower case.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the modified value from the outer object
* @return Possibly an error
*/
[[nodiscard]] static MaybeError
modify(boost::json::value& value, std::string_view key)
{
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
if (not value.as_object().at(key).is_string())
return {}; // ignore for non-string types
value.as_object()[key] =
util::toLower(boost::json::value_to<std::string>(value.as_object().at(key)));
return {};
}
};
/**
* @brief Convert input string to integer.
*
* Note: the conversion is only performed if the input value is a string.
*/
struct ToNumber final {
/**
* @brief Update the input string to integer if it can be converted to integer by stoi.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the modified value from the outer object
* @return Possibly an error
*/
[[nodiscard]] static MaybeError
modify(boost::json::value& value, std::string_view key)
{
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
if (not value.as_object().at(key).is_string())
return {}; // ignore for non-string types
auto const strInt = boost::json::value_to<std::string>(value.as_object().at(key));
if (strInt.contains('.'))
return Error{Status{RippledError::RpcInvalidParams}}; // maybe a float
try {
value.as_object()[key] = std::stoi(strInt);
} catch (std::exception& e) {
return Error{Status{RippledError::RpcInvalidParams}};
}
return {};
}
};
/**
* @brief Customised modifier allowing user define how to modify input in provided callable.
*/
class CustomModifier final {
std::function<MaybeError(boost::json::value&, std::string_view)> modifier_;
public:
/**
* @brief Constructs a custom modifier from any supported callable.
*
* @tparam Fn The type of callable
* @param fn The callable/function object
*/
template <typename Fn>
requires std::invocable<Fn, boost::json::value&, std::string_view>
explicit CustomModifier(Fn&& fn) : modifier_{std::forward<Fn>(fn)}
{
}
/**
* @brief Modify the JSON value according to the custom modifier function stored.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the tested value from the outer object
* @return Any compatible user-provided error if modify/verify failed; otherwise no error is
* returned
*/
[[nodiscard]] MaybeError
modify(boost::json::value& value, std::string_view key) const
{
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
return modifier_(value.as_object().at(key), key);
};
};
} // namespace rpc::modifiers

View File

@@ -1,63 +0,0 @@
#include "rpc/common/Specs.hpp"
#include "rpc/common/Checkers.hpp"
#include "rpc/common/Types.hpp"
#include <boost/json/array.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/Errors.hpp>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
namespace rpc {
[[nodiscard]] MaybeError
FieldSpec::process(boost::json::value& value) const
{
return processor_(value);
}
[[nodiscard]] check::Warnings
FieldSpec::check(boost::json::value const& value) const
{
return checker_(value);
}
[[nodiscard]] MaybeError
RpcSpec::process(boost::json::value& value) const
{
for (auto const& field : fields_) {
if (auto ret = field.process(value); not ret)
return Error{ret.error()};
}
return {};
}
[[nodiscard]] boost::json::array
RpcSpec::check(boost::json::value const& value) const
{
std::unordered_map<WarningCode, std::vector<std::string>> warnings;
for (auto const& field : fields_) {
auto fieldWarnings = field.check(value);
for (auto& fw : fieldWarnings) {
warnings[fw.warningCode].push_back(std::move(fw.extraMessage));
}
}
boost::json::array result;
for (auto const& [code, messages] : warnings) {
auto warningObject = makeWarning(code);
auto& warningMessage = warningObject["message"].as_string();
for (auto const& message : messages) {
warningMessage.append(" ").append(message);
}
result.push_back(std::move(warningObject));
}
return result;
}
} // namespace rpc

View File

@@ -1,132 +0,0 @@
#pragma once
#include "rpc/common/Checkers.hpp"
#include "rpc/common/Concepts.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/impl/Factories.hpp"
#include <boost/json/array.hpp>
#include <boost/json/value.hpp>
#include <initializer_list>
#include <string>
#include <utility>
#include <vector>
namespace rpc {
/**
* @brief Represents a Specification for one field of an RPC command.
*/
struct FieldSpec final {
/**
* @brief Construct a field specification out of a set of processors.
*
* @tparam Processors The types of processors
* @param key The key in a JSON object that the field validates
* @param processors The processors, each of them have to fulfil the @ref rpc::SomeProcessor
* concept
*/
template <SomeProcessor... Processors>
FieldSpec(std::string const& key, Processors&&... processors)
: processor_{
impl::makeFieldProcessor<Processors...>(key, std::forward<Processors>(processors)...)
}
, checker_{impl::kEmptyFieldChecker}
{
}
/**
* @brief Construct a field specification out of a set of checkers.
*
* @tparam Checks The types of checkers
* @param key The key in a JSON object that the field validates
* @param checks The checks, each of them have to fulfil the @ref rpc::SomeCheck concept
*/
template <SomeCheck... Checks>
FieldSpec(std::string const& key, Checks&&... checks)
: processor_{impl::kEmptyFieldProcessor}
, checker_{impl::makeFieldChecker<Checks...>(key, std::forward<Checks>(checks)...)}
{
}
/**
* @brief Processes the passed JSON value using the stored processors.
*
* @param value The JSON value to validate and/or modify
* @return Nothing on success; Status on error
*/
[[nodiscard]] MaybeError
process(boost::json::value& value) const;
/**
* @brief Checks the passed JSON value using the stored checkers.
*
* @param value The JSON value to validate
* @return A vector of warnings (empty if no warnings)
*/
[[nodiscard]] check::Warnings
check(boost::json::value const& value) const;
private:
impl::FieldSpecProcessor processor_;
impl::FieldChecker checker_;
};
/**
* @brief Represents a Specification of an entire RPC command.
*
* Note: this should really be all constexpr and handlers would expose
* static constexpr RpcSpec spec instead. Maybe some day in the future.
*/
struct RpcSpec final {
/**
* @brief Construct a full RPC request specification.
*
* @param fields The fields of the RPC specification @ref FieldSpec
*/
RpcSpec(std::initializer_list<FieldSpec> fields) : fields_{fields}
{
}
/**
* @brief Construct a full RPC request specification from another spec and additional fields.
*
* @param other The other spec to copy fields from
* @param additionalFields The additional fields to add to the spec
*/
RpcSpec(RpcSpec const& other, std::initializer_list<FieldSpec> additionalFields)
: fields_{other.fields_}
{
for (auto& f : additionalFields)
fields_.push_back(f);
}
/**
* @brief Processes the passed JSON value using the stored field specs.
*
* @param value The JSON value to validate and/or modify
* @return Nothing on success; Status on error
*/
[[nodiscard]] MaybeError
process(boost::json::value& value) const;
/**
* @brief Checks the passed JSON value using the stored field specs.
*
* @param value The JSON value to validate
* @return JSON array of warnings (empty if no warnings)
*/
[[nodiscard]] boost::json::array
check(boost::json::value const& value) const;
private:
std::vector<FieldSpec> fields_;
};
/**
* @brief An alias for a const reference to @ref RpcSpec.
*/
using RpcSpecConstRef = RpcSpec const&;
} // namespace rpc

View File

@@ -14,7 +14,6 @@
#include <cstdint>
#include <expected>
#include <optional>
#include <string>
#include <utility>
@@ -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<std::string> counterParty = std::nullopt)
: delegateType(role), counterParty(std::move(counterParty))
{
}
Role delegateType;
std::optional<std::string> counterParty;
};
/**
* @brief Convert an empty output to a JSON object
*

View File

@@ -1,117 +0,0 @@
#pragma once
#include <boost/json/array.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <concepts>
#include <cstdint>
#include <limits>
#include <string>
#include <type_traits>
namespace rpc::validation {
namespace impl {
template <std::unsigned_integral Expected>
void
clampAs(boost::json::value& value)
{
if (value.is_uint64()) {
auto const valueUint = value.as_uint64();
if (valueUint > static_cast<uint64_t>(std::numeric_limits<Expected>::max()))
value = std::numeric_limits<Expected>::max();
} else if (value.is_int64()) {
auto const valueInt = value.as_int64();
if (valueInt > static_cast<int64_t>(std::numeric_limits<Expected>::max()))
value = std::numeric_limits<Expected>::max();
}
}
template <std::signed_integral Expected>
void
clampAs(boost::json::value& value)
{
if (value.is_uint64()) {
auto const valueUint = value.as_uint64();
if (valueUint > static_cast<uint64_t>(std::numeric_limits<Expected>::max()))
value = std::numeric_limits<Expected>::max();
} else if (value.is_int64()) {
auto const valueInt = value.as_int64();
if (valueInt > static_cast<int64_t>(std::numeric_limits<Expected>::max())) {
value = std::numeric_limits<Expected>::max();
} else if (valueInt < static_cast<int64_t>(std::numeric_limits<Expected>::min())) {
value = std::numeric_limits<Expected>::min();
}
}
}
} // namespace impl
/**
* @brief Check that the type is the same as what was expected.
*
* @tparam Expected The expected type that value should be convertible to
* @param value The json value to check the type of
* @return true if convertible; false otherwise
*/
template <typename Expected>
[[nodiscard]] bool
checkType(boost::json::value const& value)
{
auto hasError = false;
if constexpr (std::is_same_v<Expected, bool>) {
if (not value.is_bool())
hasError = true;
} else if constexpr (std::is_same_v<Expected, std::string>) {
if (not value.is_string())
hasError = true;
} else if constexpr (std::is_same_v<Expected, double> or std::is_same_v<Expected, float>) {
if (not value.is_double())
hasError = true;
} else if constexpr (std::is_same_v<Expected, boost::json::array>) {
if (not value.is_array())
hasError = true;
} else if constexpr (std::is_same_v<Expected, boost::json::object>) {
if (not value.is_object())
hasError = true;
} else if constexpr (
std::is_convertible_v<Expected, uint64_t> or std::is_convertible_v<Expected, int64_t>
) {
if (not value.is_int64() && not value.is_uint64())
hasError = true;
// if the type specified is unsigned, it should not be negative
if constexpr (std::is_unsigned_v<Expected>) {
if (value.is_int64() and value.as_int64() < 0)
hasError = true;
}
}
return not hasError;
}
/**
* @brief Check that the type is the same as what was expected optionally clamping it into range.
*
* This is used to automatically clamp the value into the range available to the specified type. It
* is needed in order to avoid Min, Max and other validators throw "not exact" error from Boost.Json
* library if the value does not fit in the specified type.
*
* @tparam Expected The expected type that value should be convertible to
* @param value The json value to check the type of
* @return true if convertible; false otherwise
*/
template <typename Expected>
[[nodiscard]] bool
checkTypeAndClamp(boost::json::value& value)
{
if (not checkType<Expected>(value))
return false; // fails basic type check
if constexpr (std::is_integral_v<Expected> and not std::is_same_v<Expected, bool>)
impl::clampAs<Expected>(value);
return true;
}
} // namespace rpc::validation

View File

@@ -1,451 +0,0 @@
#include "rpc/common/Validators.hpp"
#include "rpc/JS.hpp"
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/AccountUtils.hpp"
#include "util/TimeUtils.hpp"
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <fmt/format.h>
#include <rpcspec/Errors.hpp>
#include <rpcspec/LedgerTypes.hpp>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
#include <charconv>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <unordered_set>
namespace rpc::validation {
[[nodiscard]] MaybeError
Required::verify(boost::json::value const& value, std::string_view key)
{
if (not value.is_object() or not value.as_object().contains(key)) {
return Error{Status{
RippledError::RpcInvalidParams, "Required field '" + std::string{key} + "' missing"
}};
}
return {};
}
[[nodiscard]] MaybeError
TimeFormatValidator::verify(boost::json::value const& value, std::string_view key) const
{
using boost::json::value_to;
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
if (not value.as_object().at(key).is_string())
return Error{Status{RippledError::RpcInvalidParams}};
auto const ret =
util::systemTpFromUtcStr(value_to<std::string>(value.as_object().at(key)), format_);
if (!ret)
return Error{Status{RippledError::RpcInvalidParams}};
return {};
}
[[nodiscard]] MaybeError
CustomValidator::verify(boost::json::value const& value, std::string_view key) const
{
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
return validator_(value.as_object().at(key), key);
}
[[nodiscard]] bool
checkIsU32Numeric(std::string_view sv)
{
uint32_t unused = 0;
auto [_, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), unused);
return ec == std::errc();
}
CustomValidator CustomValidators::uint160HexStringValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
return makeHexStringValidator<xrpl::uint160>(value, key);
}};
CustomValidator CustomValidators::uint192HexStringValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
return makeHexStringValidator<xrpl::uint192>(value, key);
}};
CustomValidator CustomValidators::uint256HexStringValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
return makeHexStringValidator<xrpl::uint256>(value, key);
}};
CustomValidator CustomValidators::ledgerIndexValidator =
CustomValidator{[](boost::json::value const& value, std::string_view /* key */) -> MaybeError {
auto err = Error{Status{RippledError::RpcInvalidParams, "ledgerIndexMalformed"}};
if (!value.is_string() && !(value.is_uint64() || value.is_int64()))
return err;
if (value.is_string() && value.as_string() != "validated" &&
!checkIsU32Numeric(boost::json::value_to<std::string>(value)))
return err;
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<std::string>(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())
return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotString"}};
// TODO: we are using accountFromStringStrict from RPCHelpers, after we
// remove all old handler, this function can be moved to here
if (!accountFromStringStrict(boost::json::value_to<std::string>(value)))
return Error{Status{RippledError::RpcActMalformed, std::string(key) + "Malformed"}};
return MaybeError{};
}};
CustomValidator CustomValidators::accountBase58Validator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_string())
return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotString"}};
auto const account =
util::parseBase58Wrapper<xrpl::AccountID>(boost::json::value_to<std::string>(value));
if (!account || account->isZero())
return Error{Status{ClioError::RpcMalformedAddress}};
return MaybeError{};
}};
CustomValidator CustomValidators::accountMarkerValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_string())
return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotString"}};
// TODO: we are using parseAccountCursor from RPCHelpers, after we
// remove all old handler, this function can be moved to here
if (!parseAccountCursor(boost::json::value_to<std::string>(value))) {
// align with the current error message
return Error{Status{RippledError::RpcInvalidParams, "Malformed cursor."}};
}
return MaybeError{};
}};
CustomValidator CustomValidators::accountTypeValidator =
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::accountOwnedLedgerTypeFromStr(boost::json::value_to<std::string>(value));
if (type == xrpl::ltANY) {
return Error{
Status{RippledError::RpcInvalidParams, fmt::format("Invalid field '{}'.", key)}
};
}
return MaybeError{};
}};
CustomValidator CustomValidators::currencyValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_string())
return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotString"}};
auto const currencyStr = boost::json::value_to<std::string>(value);
if (currencyStr.empty())
return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "IsEmpty"}};
xrpl::Currency currency;
if (!xrpl::toCurrency(currency, currencyStr))
return Error{Status{ClioError::RpcMalformedCurrency, "malformedCurrency"}};
return MaybeError{};
}};
CustomValidator CustomValidators::issuerValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_string())
return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotString"}};
xrpl::AccountID issuer;
// TODO: need to align with the error
if (!xrpl::toIssuer(issuer, boost::json::value_to<std::string>(value))) {
return Error{Status{
RippledError::RpcInvalidParams, fmt::format("Invalid field '{}', bad issuer.", key)
}};
}
if (issuer == xrpl::noAccount()) {
return Error{Status{
RippledError::RpcInvalidParams,
fmt::format("Invalid field '{}', bad issuer account one.", key)
}};
}
return MaybeError{};
}};
CustomValidator CustomValidators::bookTakerValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_object())
return {}; // type is validated separately by Type<object>
auto const& obj = value.as_object();
bool const hasCurrency = obj.contains("currency");
bool const hasMptId = obj.contains("mpt_issuance_id");
if (!hasCurrency && !hasMptId) {
return Error{Status{
RippledError::RpcInvalidParams, fmt::format("Missing field '{}.currency'.", key)
}};
}
if (hasMptId && (hasCurrency || obj.contains("issuer"))) {
return Error{
Status{RippledError::RpcInvalidParams, fmt::format("Invalid field '{}'.", key)}
};
}
// Wrong type -> invalidParams (xrpld's validateTakerJSON), checked here before the
// per-field validators so they can own bad *values* -> dst/srcAmtMalformed.
if ((hasCurrency && !obj.at(JS(currency)).is_string()) ||
(hasMptId && !obj.at(JS(mpt_issuance_id)).is_string())) {
return Error{Status{
RippledError::RpcInvalidParams,
fmt::format("Invalid field '{}.currency', not string.", key)
}};
}
return MaybeError{};
}};
CustomValidator CustomValidators::subscribeStreamValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_array())
return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotArray"}};
static std::unordered_set<std::string> const kValidStreams = {
"ledger",
"transactions",
"transactions_proposed",
"book_changes",
"manifests",
"validations"
};
static std::unordered_set<std::string> const kNotSupportStreams = {
"peer_status", "consensus", "server"
};
for (auto const& v : value.as_array()) {
if (!v.is_string())
return Error{Status{RippledError::RpcInvalidParams, "streamNotString"}};
if (kNotSupportStreams.contains(boost::json::value_to<std::string>(v)))
return Error{Status{RippledError::RpcNotSupported}};
if (not kValidStreams.contains(boost::json::value_to<std::string>(v)))
return Error{Status{RippledError::RpcStreamMalformed}};
}
return MaybeError{};
}};
CustomValidator CustomValidators::subscribeAccountsValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_array())
return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotArray"}};
if (value.as_array().empty())
return Error{Status{RippledError::RpcActMalformed, std::string(key) + " malformed."}};
for (auto const& v : value.as_array()) {
auto obj = boost::json::object();
auto const keyItem = std::string(key) + "'sItem";
obj[keyItem] = v;
if (auto err = accountValidator.verify(obj, keyItem); !err)
return err;
}
return MaybeError{};
}};
CustomValidator CustomValidators::currencyIssueValidator =
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"}};
try {
parseIssue(value.as_object());
} catch (std::runtime_error const&) {
return Error{Status{ClioError::RpcMalformedRequest}};
}
return MaybeError{};
}};
CustomValidator CustomValidators::credentialTypeValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (not value.is_string()) {
return Error{Status{
ClioError::RpcMalformedAuthorizedCredentials, std::string(key) + " NotString"
}};
}
auto const& credTypeHex = xrpl::strUnHex(value.as_string());
if (!credTypeHex.has_value()) {
return Error{Status{
ClioError::RpcMalformedAuthorizedCredentials, std::string(key) + " NotHexString"
}};
}
if (credTypeHex->empty()) {
return Error{
Status{ClioError::RpcMalformedAuthorizedCredentials, std::string(key) + " is empty"}
};
}
if (credTypeHex->size() > xrpl::kMaxCredentialTypeLength) {
return Error{Status{
ClioError::RpcMalformedAuthorizedCredentials,
std::string(key) + " greater than max length"
}};
}
return MaybeError{};
}};
CustomValidator CustomValidators::authorizeCredentialValidator =
CustomValidator{[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (not value.is_array())
return Error{Status{ClioError::RpcMalformedRequest, std::string(key) + " not array"}};
auto const& authCred = value.as_array();
if (authCred.empty()) {
return Error{Status{
ClioError::RpcMalformedAuthorizedCredentials,
fmt::format("Requires at least one element in authorized_credentials array.")
}};
}
if (authCred.size() > xrpl::kMaxCredentialsArraySize) {
return Error{Status{
ClioError::RpcMalformedAuthorizedCredentials,
fmt::format(
"Max {} number of credentials in authorized_credentials array",
xrpl::kMaxCredentialsArraySize
)
}};
}
for (auto const& credObj : value.as_array()) {
if (!credObj.is_object()) {
return Error{Status{
ClioError::RpcMalformedAuthorizedCredentials,
"authorized_credentials elements in array are not objects."
}};
}
auto const& obj = credObj.as_object();
if (!obj.contains("issuer")) {
return Error{Status{
ClioError::RpcMalformedAuthorizedCredentials,
"Field 'Issuer' is required but missing."
}};
}
// don't want to change issuer error message to be about credentials
if (!accountBase58Validator.verify(credObj, "issuer")) {
return Error{
Status{ClioError::RpcMalformedAuthorizedCredentials, "issuer NotString"}
};
}
if (!obj.contains("credential_type")) {
return Error{Status{
ClioError::RpcMalformedAuthorizedCredentials,
"Field 'CredentialType' is required but missing."
}};
}
if (auto const err = credentialTypeValidator.verify(credObj, "credential_type"); !err)
return err;
}
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

View File

@@ -1,654 +0,0 @@
#pragma once
#include "rpc/Errors.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/ValidationHelpers.hpp"
#include <boost/json/array.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <fmt/format.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <concepts>
#include <ctime>
#include <functional>
#include <initializer_list>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace rpc::validation {
/**
* @brief A validator that simply requires a field to be present.
*/
struct Required final {
/**
* @brief Verify that the JSON value is present and not null.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the tested value from the outer object
* @return An error if validation failed; otherwise no error is returned
*/
[[nodiscard]] static MaybeError
verify(boost::json::value const& value, std::string_view key);
};
/**
* @brief A validator that forbids a field to be present.
*
* If there is a value provided, it will forbid the field only when the value equals.
* If there is no value provided, it will forbid the field when the field shows up.
*/
template <typename... T>
class NotSupported;
/**
* @brief A specialized NotSupported validator that forbids a field to be present when the value
* equals the given value.
*/
template <typename T>
class NotSupported<T> final {
T value_;
public:
/**
* @brief Constructs a new NotSupported validator.
*
* @param val The value to store and verify against
*/
NotSupported(T val) : value_(val)
{
}
/**
* @brief Verify whether the field is supported or not.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the tested value from the outer object
* @return `RippledError::RpcNotSupported` if the value matched; otherwise no error is returned
*/
[[nodiscard]] MaybeError
verify(boost::json::value const& value, std::string_view key) const
{
if (value.is_object() and value.as_object().contains(key)) {
using boost::json::value_to;
auto const res = value_to<T>(value.as_object().at(key));
if (value_ == res) {
return Error{Status{
RippledError::RpcNotSupported,
fmt::format("Not supported field '{}'s value '{}'", std::string{key}, res)
}};
}
}
return {};
}
};
/**
* @brief A specialized NotSupported validator that forbids a field to be present.
*/
template <>
class NotSupported<> final {
public:
/**
* @brief Verify whether the field is supported or not.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the tested value from the outer object
* @return `RippledError::RpcNotSupported` if the field is found; otherwise no error is
* returned
*/
[[nodiscard]] static MaybeError
verify(boost::json::value const& value, std::string_view key)
{
if (value.is_object() and value.as_object().contains(key)) {
return Error{Status{
RippledError::RpcNotSupported, "Not supported field '" + std::string{key} + '\''
}};
}
return {};
}
};
/**
* @brief Deduction guide to avoid having to specify the template arguments.
*/
template <typename... T>
NotSupported(T&&... t) -> NotSupported<T...>;
/**
* @brief Validates that the type of the value is one of the given types.
*/
template <typename... Types>
struct Type final {
/**
* @brief Verify that the JSON value is (one) of specified type(s).
* @note The value itself can only change for integral types and only if the value is outside of
* the range of the expected integer type (see checkTypeAndClamp).
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the tested value from the outer object
* @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is
* returned
*/
[[nodiscard]] MaybeError
verify(boost::json::value& value, std::string_view key) const
{
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. If field is supposed to exist, let 'required' fail instead
auto& res = value.as_object().at(key);
auto const convertible = (checkTypeAndClamp<Types>(res) || ...);
if (not convertible)
return Error{Status{RippledError::RpcInvalidParams}};
return {};
}
};
/**
* @brief Validate that value is between specified min and max.
*/
template <typename Type>
class Between final {
Type min_;
Type max_;
public:
/**
* @brief Construct the validator storing min and max values.
*
* @param min
* @param max
*/
explicit Between(Type min, Type max) : min_{min}, max_{max}
{
}
/**
* @brief Verify that the JSON value is within a certain range.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the tested value from the outer object
* @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is
* returned
*/
[[nodiscard]] MaybeError
verify(boost::json::value const& value, std::string_view key) const
{
using boost::json::value_to;
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
auto const res = value_to<Type>(value.as_object().at(key));
// TODO: may want a way to make this code more generic (e.g. use a free
// function that can be overridden for this comparison)
if (res < min_ || res > max_)
return Error{Status{RippledError::RpcInvalidParams}};
return {};
}
};
/**
* @brief Validate that value is equal or greater than the specified min.
*/
template <typename Type>
class Min final {
Type min_;
public:
/**
* @brief Construct the validator storing min value.
*
* @param min
*/
explicit Min(Type min) : min_{min}
{
}
/**
* @brief Verify that the JSON value is not smaller than min
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the tested value from the outer object
* @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is
* returned
*/
[[nodiscard]] MaybeError
verify(boost::json::value const& value, std::string_view key) const
{
using boost::json::value_to;
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
auto const res = value_to<Type>(value.as_object().at(key));
if (res < min_)
return Error{Status{RippledError::RpcInvalidParams}};
return {};
}
};
/**
* @brief Validate that value is not greater than max.
*/
template <typename Type>
class Max final {
Type max_;
public:
/**
* @brief Construct the validator storing max value.
*
* @param max
*/
explicit Max(Type max) : max_{max}
{
}
/**
* @brief Verify that the JSON value is not greater than max.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the tested value from the outer object
* @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is
* returned
*/
[[nodiscard]] MaybeError
verify(boost::json::value const& value, std::string_view key) const
{
using boost::json::value_to;
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
auto const res = value_to<Type>(value.as_object().at(key));
if (res > max_)
return Error{Status{RippledError::RpcInvalidParams}};
return {};
}
};
/**
* @brief Validate that value can be converted to time according to the given format.
*/
class TimeFormatValidator final {
std::string format_;
public:
/**
* @brief Construct the validator storing format value.
*
* @param format The format to use for time conversion
*/
explicit TimeFormatValidator(std::string format) : format_{std::move(format)}
{
}
/**
* @brief Verify that the JSON value is valid formatted time.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the tested value from the outer object
* @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is
* returned
*/
[[nodiscard]] MaybeError
verify(boost::json::value const& value, std::string_view key) const;
};
/**
* @brief Validates that the value is equal to the one passed in.
*/
template <typename Type>
class EqualTo final {
Type original_;
public:
/**
* @brief Construct the validator with stored original value.
*
* @param original The original value to store
*/
explicit EqualTo(Type original) : original_{std::move(original)}
{
}
/**
* @brief Verify that the JSON value is equal to the stored original.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the tested value from the outer object
* @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is
* returned
*/
[[nodiscard]] MaybeError
verify(boost::json::value const& value, std::string_view key) const
{
using boost::json::value_to;
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
auto const res = value_to<Type>(value.as_object().at(key));
if (res != original_)
return Error{Status{RippledError::RpcInvalidParams}};
return {};
}
};
/**
* @brief Deduction guide to help disambiguate what it means to EqualTo a "string" without
* specifying the type.
*/
EqualTo(char const*) -> EqualTo<std::string>;
/**
* @brief Validates that the value is one of the values passed in.
*/
template <typename Type>
class OneOf final {
std::vector<Type> options_;
public:
/**
* @brief Construct the validator with stored options of initializer list.
*
* @param options The list of allowed options
*/
explicit OneOf(std::initializer_list<Type> options) : options_{options}
{
}
/**
* @brief Construct the validator with stored options of other container.
*
* @param begin,end the range to copy the elements from
*/
template <typename InputIt>
explicit OneOf(InputIt begin, InputIt end) : options_{begin, end}
{
}
/**
* @brief Verify that the JSON value is one of the stored options.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the tested value from the outer object
* @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is
* returned
*/
[[nodiscard]] MaybeError
verify(boost::json::value const& value, std::string_view key) const
{
using boost::json::value_to;
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. field does not exist, let 'required' fail instead
auto const res = value_to<Type>(value.as_object().at(key));
if (std::find(std::begin(options_), std::end(options_), res) == std::end(options_)) {
return Error{
Status{RippledError::RpcInvalidParams, fmt::format("Invalid field '{}'.", key)}
};
}
return {};
}
};
/**
* @brief Deduction guide to help disambiguate what it means to OneOf a few "strings" without
* specifying the type.
*/
OneOf(std::initializer_list<char const*>) -> OneOf<std::string>;
/**
* @brief A meta-validator that allows to specify a custom validation function.
*/
class CustomValidator final {
std::function<MaybeError(boost::json::value const&, std::string_view)> validator_;
public:
/**
* @brief Constructs a custom validator from any supported callable.
*
* @tparam Fn The type of callable
* @param fn The callable/function object
*/
template <typename Fn>
requires std::invocable<Fn, boost::json::value const&, std::string_view>
explicit CustomValidator(Fn&& fn) : validator_{std::forward<Fn>(fn)}
{
}
/**
* @brief Verify that the JSON value is valid according to the custom validation function
* stored.
*
* @param value The JSON value representing the outer object
* @param key The key used to retrieve the tested value from the outer object
* @return Any compatible user-provided error if validation failed; otherwise no error is
* returned
*/
[[nodiscard]] MaybeError
verify(boost::json::value const& value, std::string_view key) const;
};
/**
* @brief Helper function to check if input value is an uint32 number or not.
*
* @param sv The input value as a string_view
* @return true if the string can be converted to a uint32; false otherwise
*/
[[nodiscard]] bool
checkIsU32Numeric(std::string_view sv);
template <class HexType>
requires(
std::is_same_v<HexType, xrpl::uint160> || std::is_same_v<HexType, xrpl::uint192> ||
std::is_same_v<HexType, xrpl::uint256>
)
MaybeError
makeHexStringValidator(boost::json::value const& value, std::string_view key)
{
if (!value.is_string())
return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotString"}};
HexType parsedInt;
if (!parsedInt.parseHex(value.as_string().c_str()))
return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "Malformed"}};
return MaybeError{};
}
/**
* @brief A group of custom validation functions
*/
struct CustomValidators final {
/**
* @brief Provides a commonly used validator for ledger index.
*
* LedgerIndex must be a string or an int. If the specified LedgerIndex is a string, its value
* must be either "validated" or a valid integer value represented as a string.
*/
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.
*
* Account must be a string and the converted public key is valid.
*/
static CustomValidator accountValidator;
/**
* @brief Provides a commonly used validator for accounts.
*
* Account must be a string and can convert to base58.
*/
static CustomValidator accountBase58Validator;
/**
* @brief Provides a commonly used validator for markers.
*
* A marker is composed of a comma-separated index and a start hint.
* The former will be read as hex, and the latter can be cast to uint64.
*/
static CustomValidator accountMarkerValidator;
/**
* @brief Provides a validator for account type.
*
* A type accepts canonical names of owned ledger entry types (case insensitive) or short names.
* Used by account_objects.
*/
static CustomValidator accountTypeValidator;
/**
* @brief Provides a commonly used validator for uint160(AccountID) hex string.
*
* It must be a string and also a decodable hex.
* AccountID uses this validator.
*/
static CustomValidator uint160HexStringValidator;
/**
* @brief Provides a commonly used validator for uint192 hex string.
*
* It must be a string and also a decodable hex.
* MPTIssuanceID uses this validator.
*/
static CustomValidator uint192HexStringValidator;
/**
* @brief Provides a commonly used validator for uint256 hex string.
*
* It must be a string and also a decodable hex.
* Transaction index, ledger hash all use this validator.
*/
static CustomValidator uint256HexStringValidator;
/**
* @brief Provides a commonly used validator for currency, including standard currency code and
* token code.
*/
static CustomValidator currencyValidator;
/**
* @brief Provides a commonly used validator for issuer type.
*
* It must be a hex string or base58 string.
*/
static CustomValidator issuerValidator;
/**
* @brief Provides a validator for validating streams used in subscribe/unsubscribe.
*/
static CustomValidator subscribeStreamValidator;
/**
* @brief Provides a validator for validating accounts used in subscribe/unsubscribe.
*/
static CustomValidator subscribeAccountsValidator;
/**
* @brief Validates an asset (xrpl::Issue).
*
* Used by amm_info.
*/
static CustomValidator currencyIssueValidator;
/**
* @brief Validates a book taker object (`taker_gets`/`taker_pays`).
*
* The object must specify an asset as either a `currency` (optionally with an `issuer`) or an
* `mpt_issuance_id`, but not both, and `mpt_issuance_id` must not be combined with `issuer`.
* Mirrors `xrpld`'s `validateTakerJSON`: a missing asset yields `Missing field
* '<field>.currency'.`, conflicting fields yield `Invalid field '<field>'.`, and a present but
* non-string `currency`/`mpt_issuance_id` yields `Invalid field '<field>.currency', not
* string.` (all `invalidParams`). The field name is taken from the validated key.
*
* Used by book_offers.
*/
static CustomValidator bookTakerValidator;
/**
* @brief Provides a validator for validating authorized_credentials json array.
*
* Used by deposit_preauth.
*/
static CustomValidator authorizeCredentialValidator;
/**
* @brief Provides a validator for validating credential_type.
*
* Used by AuthorizeCredentialValidator in deposit_preauth and by the credential
* 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;
};
/**
* @brief Validates that the elements of the array is of type Hex256 uint
*/
struct Hex256ItemType final {
/**
* @brief Validates given the prerequisite that the type of the json value is an array,
* verifies all values within the array is of uint256 hash
*
* @param value the value to verify
* @param key The key used to retrieve the tested value from the outer object
* @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is
* returned
*/
[[nodiscard]] static MaybeError
verify(boost::json::value const& value, std::string_view key)
{
if (not value.is_object() or not value.as_object().contains(key))
return {}; // ignore. If field is supposed to exist, let 'required' fail instead
auto const& res = value.as_object().at(key);
// loop through each item in the array and make sure it is uint256 hex string
for (auto const& elem : res.as_array()) {
xrpl::uint256 num;
if (!elem.is_string() || !num.parseHex(elem.as_string())) {
return Error{
Status{RippledError::RpcInvalidParams, "Item is not a valid uint256 type."}
};
}
}
return {};
}
};
} // namespace rpc::validation

View File

@@ -1,86 +0,0 @@
#pragma once
#include "rpc/Errors.hpp"
#include "rpc/common/Checkers.hpp"
#include "rpc/common/Concepts.hpp"
#include "rpc/common/Types.hpp"
#include "util/UnsupportedType.hpp"
#include <boost/json/array.hpp>
#include <boost/json/value.hpp>
#include <expected>
#include <functional>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace rpc::impl {
using FieldSpecProcessor = std::function<MaybeError(boost::json::value&)>;
static FieldSpecProcessor const kEmptyFieldProcessor = [](boost::json::value&) -> MaybeError {
return {};
};
template <SomeProcessor... Processors>
[[nodiscard]] FieldSpecProcessor
makeFieldProcessor(std::string const& key, Processors&&... procs)
{
return [key, ... proc = std::forward<Processors>(procs)](boost::json::value& j) -> MaybeError {
std::optional<Status> firstFailure = std::nullopt;
// This expands in order of Requirements and stops evaluating after first failure which is
// stored in `firstFailure` and can be checked later on to see whether the verification
// failed as a whole or not.
(
[&j, &key, &firstFailure, req = &proc]() {
if (firstFailure)
return; // already failed earlier - skip
if constexpr (SomeRequirement<decltype(*req)>) {
if (auto const res = req->verify(j, key); not res)
firstFailure = res.error();
} else if constexpr (SomeModifier<decltype(*req)>) {
if (auto const res = req->modify(j, key); not res)
firstFailure = res.error();
} else {
static_assert(util::Unsupported<decltype(*req)>);
}
}(),
...);
if (firstFailure)
return std::unexpected{std::move(firstFailure).value()};
return {};
};
}
using FieldChecker = std::function<check::Warnings(boost::json::value const&)>;
static FieldChecker const kEmptyFieldChecker = [](boost::json::value const&) -> check::Warnings {
return {};
};
template <SomeCheck... Checks>
[[nodiscard]] FieldChecker
makeFieldChecker(std::string const& key, Checks&&... checks)
{
return [key,
... checks =
std::forward<Checks>(checks)](boost::json::value const& j) -> check::Warnings {
check::Warnings warnings;
// This expands in order of Checks and collects all warnings into a WarningsCollection
(
[&j, &key, &warnings, req = &checks]() {
if (auto res = req->check(j, key); res)
warnings.push_back(*std::move(res));
}(),
...);
return warnings;
};
}
} // namespace rpc::impl

View File

@@ -20,20 +20,12 @@ struct DefaultProcessor final {
) const
{
using boost::json::value_from;
using boost::json::value_to;
static_assert(
kIsSingleInputPath<HandlerType>,
"handler satisfies both the legacy and the typed input path; dispatch would be "
"decided by the order of the branches below rather than by the handler"
);
static_assert(
SomeHandlerWithTypedInput<HandlerType> or SomeHandlerWithInput<HandlerType> or
SomeHandlerWithoutInput<HandlerType>,
SomeHandlerWithTypedInput<HandlerType> or SomeHandlerWithoutInput<HandlerType>,
"handler matches none of the branches below"
);
// New `rpc-spec`-based handler
if constexpr (SomeHandlerWithTypedInput<HandlerType>) {
auto input = HandlerType::parseInput(value, ctx.apiVersion);
auto warnings = rpc::spec::toJsonArray(HandlerType::spec(ctx.apiVersion).check(value));
@@ -49,26 +41,6 @@ struct DefaultProcessor final {
return ReturnType{value_from(std::move(ret).value()), std::move(warnings)};
}
if constexpr (SomeHandlerWithInput<HandlerType>) {
// Old spec-based handler: first we run validation against specified API version
// TODO: This will be eventually removed once fully migraded to new rpc-spec system.
auto const spec = handler.spec(ctx.apiVersion);
auto warnings = spec.check(value);
auto input = value; // copy here, spec require mutable data
if (auto const ret = spec.process(input); not ret.has_value())
return ReturnType{Error{ret.error()}, std::move(warnings)}; // forward Status
auto const inData = value_to<typename HandlerType::Input>(input);
auto ret = handler.process(inData, ctx);
// real handler is given expected Input, not json
if (not ret.has_value())
return ReturnType{Error{std::move(ret).error()}, std::move(warnings)};
return ReturnType{value_from(std::move(ret).value()), std::move(warnings)};
}
if constexpr (SomeHandlerWithoutInput<HandlerType>) {
// no input to pass, ignore the value
auto const ret = handler.process(ctx);

View File

@@ -3,15 +3,22 @@
#include "data/Types.hpp"
#include "rpc/common/Types.hpp"
#include <rpcspec/handlers/account_tx/Types.hpp>
#include <xrpl/protocol/AccountID.h>
#include <optional>
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:
/**

View File

@@ -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 <xrpl/protocol/AccountID.h>

View File

@@ -4,18 +4,13 @@
#include "rpc/AMMHelpers.hpp"
#include "rpc/JS.hpp"
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/MetaProcessors.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <date/date.h>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/base_uint.h>
@@ -34,10 +29,7 @@
#include <xrpl/protocol/jss.h>
#include <chrono>
#include <cstdint>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
namespace {
@@ -80,11 +72,10 @@ AMMInfoHandler::process(AMMInfoHandler::Input const& input, Context const& ctx)
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "AMMInfo'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)
);
@@ -228,67 +219,6 @@ AMMInfoHandler::process(AMMInfoHandler::Input const& input, Context const& ctx)
return response;
}
RpcSpecConstRef
AMMInfoHandler::spec([[maybe_unused]] uint32_t apiVersion)
{
static auto const kStringIssueValidator = validation::CustomValidator{
[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (not value.is_string()) {
return Error{
Status{RippledError::RpcInvalidParams, std::string(key) + "NotString"}
};
}
try {
xrpl::issueFromJson(boost::json::value_to<std::string>(value));
} catch (std::runtime_error const&) {
return Error{Status{RippledError::RpcIssueMalformed}};
}
return MaybeError{};
}
};
static auto const kRpcSpec = RpcSpec{
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(asset),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{},
Status(RippledError::RpcIssueMalformed)
},
meta::IfType<std::string>{kStringIssueValidator},
meta::IfType<boost::json::object>{
meta::WithCustomError{
validation::CustomValidators::currencyIssueValidator,
Status(RippledError::RpcIssueMalformed)
},
}},
{JS(asset2),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{},
Status(RippledError::RpcIssueMalformed)
},
meta::IfType<std::string>{kStringIssueValidator},
meta::IfType<boost::json::object>{
meta::WithCustomError{
validation::CustomValidators::currencyIssueValidator,
Status(RippledError::RpcIssueMalformed)
},
}},
{JS(amm_account),
meta::WithCustomError{
validation::CustomValidators::accountValidator, Status(RippledError::RpcActMalformed)
}},
{JS(account),
meta::WithCustomError{
validation::CustomValidators::accountValidator, Status(RippledError::RpcActMalformed)
}},
};
return kRpcSpec;
}
void
tag_invoke(
boost::json::value_from_tag,
@@ -324,39 +254,4 @@ tag_invoke(
};
}
AMMInfoHandler::Input
tag_invoke(boost::json::value_to_tag<AMMInfoHandler::Input>, boost::json::value const& jv)
{
auto input = AMMInfoHandler::Input{};
auto const& jsonObject = jv.as_object();
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(jv.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(asset)))
input.issue1 = parseIssue(jsonObject.at(JS(asset)).as_object());
if (jsonObject.contains(JS(asset2)))
input.issue2 = parseIssue(jsonObject.at(JS(asset2)).as_object());
if (jsonObject.contains(JS(account))) {
input.accountID =
accountFromStringStrict(boost::json::value_to<std::string>(jsonObject.at(JS(account))));
}
if (jsonObject.contains(JS(amm_account))) {
input.ammAccount = accountFromStringStrict(
boost::json::value_to<std::string>(jsonObject.at(JS(amm_account)))
);
}
return input;
}
} // namespace rpc

View File

@@ -2,18 +2,19 @@
#include "data/AmendmentCenterInterface.hpp"
#include "data/BackendInterface.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Issue.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/amm_info/Types.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
namespace rpc {
@@ -22,7 +23,7 @@ namespace rpc {
*
* For more info see: https://xrpl.org/amm_info.html
*/
class AMMInfoHandler {
class AMMInfoHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::amm_info::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
std::shared_ptr<data::AmendmentCenterInterface const> amendmentCenter_;
@@ -47,18 +48,6 @@ public:
bool validated = true;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::optional<xrpl::AccountID> accountID;
std::optional<xrpl::AccountID> ammAccount;
xrpl::Issue issue1 = xrpl::noIssue();
xrpl::Issue issue2 = xrpl::noIssue();
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
};
using Result = HandlerReturnType<Output>;
/**
@@ -75,15 +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);
/**
* @brief Process the AMMInfo command
*
@@ -103,15 +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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -4,12 +4,10 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
@@ -23,7 +21,6 @@
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol/tokens.h>
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
@@ -74,11 +71,10 @@ AccountChannelsHandler::process(
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "AccountChannel'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,20 +82,15 @@ AccountChannelsHandler::process(
return Error{expectedLgrInfo.error()};
auto const& lgrInfo = *expectedLgrInfo;
auto const accountID = accountFromStringStrict(input.account);
auto const& accountID = input.account;
auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
xrpl::keylet::account(*accountID).key,
lgrInfo.seq,
ctx.yield
xrpl::keylet::account(accountID).key, lgrInfo.seq, ctx.yield
);
if (!accountLedgerObject)
return Error{Status{RippledError::RpcActNotFound}};
auto const destAccountID = input.destinationAccount
? accountFromStringStrict(*input.destinationAccount)
: std::optional<xrpl::AccountID>{};
auto const& destAccountID = input.destinationAccount;
Output response;
auto const addToResponse = [&](xrpl::SLE const sle) {
@@ -113,7 +104,7 @@ AccountChannelsHandler::process(
auto const expectedNext = traverseOwnedNodes(
*sharedPtrBackend_,
*accountID, // NOLINT(bugprone-unchecked-optional-access)
accountID,
lgrInfo.seq,
input.limit,
input.marker,
@@ -124,7 +115,7 @@ AccountChannelsHandler::process(
if (not expectedNext.has_value())
return Error{expectedNext.error()};
response.account = input.account;
response.account = xrpl::to_string(accountID);
response.limit = input.limit;
response.ledgerHash = xrpl::strHex(lgrInfo.hash);
response.ledgerIndex = lgrInfo.seq;
@@ -136,37 +127,6 @@ AccountChannelsHandler::process(
return response;
}
AccountChannelsHandler::Input
tag_invoke(boost::json::value_to_tag<AccountChannelsHandler::Input>, boost::json::value const& jv)
{
auto input = AccountChannelsHandler::Input{};
auto const& jsonObject = jv.as_object();
input.account = boost::json::value_to<std::string>(jv.at(JS(account)));
if (jsonObject.contains(JS(limit)))
input.limit = util::integralValueAs<uint32_t>(jv.at(JS(limit)));
if (jsonObject.contains(JS(marker)))
input.marker = boost::json::value_to<std::string>(jv.at(JS(marker)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
if (jsonObject.contains(JS(destination_account))) {
input.destinationAccount =
boost::json::value_to<std::string>(jv.at(JS(destination_account)));
}
if (jsonObject.contains(JS(ledger_index))) {
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
if (expectedLedgerIndex.has_value())
input.ledgerIndex = *expectedLedgerIndex;
}
return input;
}
void
tag_invoke(
boost::json::value_from_tag,

View File

@@ -1,21 +1,19 @@
#pragma once
#include "data/BackendInterface.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Modifiers.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/account_channels/Types.hpp>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace rpc {
@@ -27,14 +25,15 @@ namespace rpc {
*
* For more details see: https://xrpl.org/account_channels.html
*/
class AccountChannelsHandler {
class AccountChannelsHandler
: public rpc::spec::HandlerFor<rpc::spec::handlers::account_channels::Input> {
// dependencies
std::shared_ptr<BackendInterface> const sharedPtrBackend_;
public:
static constexpr auto kLimitMin = 10;
static constexpr auto kLimitMax = 400;
static constexpr auto kLimitDefault = 200;
static constexpr auto kLimitMin = rpc::spec::handlers::account_channels::kLimitMin;
static constexpr auto kLimitMax = rpc::spec::handlers::account_channels::kLimitMax;
static constexpr auto kLimitDefault = rpc::spec::handlers::account_channels::kLimitDefault;
/**
* @brief A struct to hold data for one channel response
@@ -70,18 +69,6 @@ public:
std::optional<std::string> marker;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::string account;
std::optional<std::string> destinationAccount;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
uint32_t limit = kLimitDefault;
std::optional<std::string> marker;
};
using Result = HandlerReturnType<Output>;
/**
@@ -94,32 +81,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(account), validation::Required{}, validation::CustomValidators::accountValidator},
{JS(destination_account),
validation::Type<std::string>{},
validation::CustomValidators::accountValidator},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
modifiers::Clamp<int32_t>{kLimitMin, kLimitMax}},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(marker), validation::CustomValidators::accountMarkerValidator},
};
return kRpcSpec;
}
/**
* @brief Process the AccountChannels command
*
@@ -143,15 +104,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<Input>, boost::json::value const& jv);
/**
* @brief Convert the ChannelResponse to a JSON object
*

View File

@@ -4,11 +4,9 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/Indexes.h>
@@ -33,11 +31,10 @@ AccountCurrenciesHandler::process(
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "AccountCurrencies' 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)
);
@@ -45,13 +42,10 @@ AccountCurrenciesHandler::process(
return Error{expectedLgrInfo.error()};
auto const& lgrInfo = *expectedLgrInfo;
auto const accountID = accountFromStringStrict(input.account);
auto const& accountID = input.account;
auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
xrpl::keylet::account(*accountID).key,
lgrInfo.seq,
ctx.yield
xrpl::keylet::account(accountID).key, lgrInfo.seq, ctx.yield
);
if (!accountLedgerObject)
return Error{Status{RippledError::RpcActNotFound}};
@@ -88,7 +82,7 @@ AccountCurrenciesHandler::process(
// traverse all owned nodes, limit->max, marker->empty
traverseOwnedNodes(
*sharedPtrBackend_,
*accountID, // NOLINT(bugprone-unchecked-optional-access)
accountID,
lgrInfo.seq,
std::numeric_limits<std::uint32_t>::max(),
{},
@@ -120,24 +114,4 @@ tag_invoke(
};
}
AccountCurrenciesHandler::Input
tag_invoke(boost::json::value_to_tag<AccountCurrenciesHandler::Input>, boost::json::value const& jv)
{
auto input = AccountCurrenciesHandler::Input{};
auto const& jsonObject = jv.as_object();
input.account = boost::json::value_to<std::string>(jv.at(JS(account)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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;
}
return input;
}
} // namespace rpc

View File

@@ -1,19 +1,15 @@
#pragma once
#include "data/BackendInterface.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Checkers.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/account_currencies/Types.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <set>
#include <string>
@@ -25,7 +21,8 @@ namespace rpc {
*
* For more details see: https://xrpl.org/account_currencies.html
*/
class AccountCurrenciesHandler {
class AccountCurrenciesHandler
: public rpc::spec::HandlerFor<rpc::spec::handlers::account_currencies::Input> {
// dependencies
std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -42,15 +39,6 @@ public:
bool validated = true;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::string account;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
};
using Result = HandlerReturnType<Output>;
/**
@@ -63,26 +51,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(account), validation::Required{}, validation::CustomValidators::accountValidator},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{"account_index", check::Deprecated{}},
{JS(strict), check::Deprecated{}}
};
return kRpcSpec;
}
/**
* @brief Process the AccountCurrencies command
*
@@ -102,15 +70,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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -3,16 +3,13 @@
#include "data/AmendmentCenter.hpp"
#include "rpc/JS.hpp"
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/JsonBool.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/strHex.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
@@ -46,11 +43,10 @@ AccountInfoHandler::process(AccountInfoHandler::Input const& input, Context cons
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "AccountInfo'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)
);
@@ -58,10 +54,8 @@ AccountInfoHandler::process(AccountInfoHandler::Input const& input, Context cons
return Error{expectedLgrInfo.error()};
auto const& lgrInfo = *expectedLgrInfo;
auto const accountStr = input.account.value_or(input.ident.value_or(""));
auto const accountID = accountFromStringStrict(accountStr);
auto const accountKeylet =
xrpl::keylet::account(*accountID); // NOLINT(bugprone-unchecked-optional-access)
auto const accountID = input.account ? *input.account : *input.ident;
auto const accountKeylet = xrpl::keylet::account(accountID);
auto const accountLedgerObject =
sharedPtrBackend_->fetchLedgerObject(accountKeylet.key, lgrInfo.seq, ctx.yield);
@@ -99,8 +93,7 @@ AccountInfoHandler::process(AccountInfoHandler::Input const& input, Context cons
if (input.signerLists) {
// We put the SignerList in an array because of an anticipated
// future when we support multiple signer lists on one account.
auto const signersKey =
xrpl::keylet::signerList(*accountID); // NOLINT(bugprone-unchecked-optional-access)
auto const signersKey = xrpl::keylet::signerList(accountID);
// This code will need to be revisited if in the future we
// support multiple SignerLists on one account.
@@ -203,31 +196,4 @@ tag_invoke(
}
}
AccountInfoHandler::Input
tag_invoke(boost::json::value_to_tag<AccountInfoHandler::Input>, boost::json::value const& jv)
{
auto input = AccountInfoHandler::Input{};
auto const& jsonObject = jv.as_object();
if (jsonObject.contains(JS(ident)))
input.ident = boost::json::value_to<std::string>(jsonObject.at(JS(ident)));
if (jsonObject.contains(JS(account)))
input.account = boost::json::value_to<std::string>(jsonObject.at(JS(account)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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(signer_lists)))
input.signerLists = boost::json::value_to<JsonBool>(jsonObject.at(JS(signer_lists)));
return input;
}
} // namespace rpc

View File

@@ -2,17 +2,13 @@
#include "data/AmendmentCenterInterface.hpp"
#include "data/BackendInterface.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Checkers.hpp"
#include "rpc/common/JsonBool.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/account_info/Types.hpp>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <memory>
@@ -28,7 +24,7 @@ namespace rpc {
*
* For more details see: https://xrpl.org/account_info.html
*/
class AccountInfoHandler {
class AccountInfoHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::account_info::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
std::shared_ptr<data::AmendmentCenterInterface const> amendmentCenter_;
@@ -49,20 +45,6 @@ public:
bool validated = true;
};
/**
* @brief A struct to hold the input data for the command
*
* `queue` is not available in Reporting mode
* `ident` is deprecated, keep it for now, in line with rippled
*/
struct Input {
std::optional<std::string> account;
std::optional<std::string> ident;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
JsonBool signerLists{false};
};
using Result = HandlerReturnType<Output>;
/**
@@ -79,31 +61,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 kRpcSpecV1 = RpcSpec{
{JS(account), validation::CustomValidators::accountValidator},
{JS(ident), validation::CustomValidators::accountValidator},
{JS(ident), check::Deprecated{}},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(ledger), check::Deprecated{}},
{JS(strict), check::Deprecated{}}
};
static auto const kRpcSpec =
RpcSpec{kRpcSpecV1, {{JS(signer_lists), validation::Type<bool>{}}}};
return apiVersion == 1 ? kRpcSpecV1 : kRpcSpec;
}
/**
* @brief Process the AccountInfo command
*
@@ -123,15 +80,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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -4,12 +4,10 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/AccountID.h>
@@ -22,7 +20,6 @@
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
@@ -122,11 +119,10 @@ AccountLinesHandler::process(AccountLinesHandler::Input const& input, Context co
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "AccountLines' 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)
);
@@ -134,19 +130,15 @@ AccountLinesHandler::process(AccountLinesHandler::Input const& input, Context co
return Error{expectedLgrInfo.error()};
auto const& lgrInfo = *expectedLgrInfo;
auto const accountID = accountFromStringStrict(input.account);
auto const& accountID = input.account;
auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
xrpl::keylet::account(*accountID).key,
lgrInfo.seq,
ctx.yield
xrpl::keylet::account(accountID).key, lgrInfo.seq, ctx.yield
);
if (not accountLedgerObject)
return Error{Status{RippledError::RpcActNotFound}};
auto const peerAccountID =
input.peer ? accountFromStringStrict(*(input.peer)) : std::optional<xrpl::AccountID>{};
auto const& peerAccountID = input.peer;
Output response;
response.lines.reserve(input.limit);
@@ -163,13 +155,13 @@ AccountLinesHandler::process(AccountLinesHandler::Input const& input, Context co
}
if (not ignore)
addLine(response.lines, sle, *accountID, peerAccountID);
addLine(response.lines, sle, accountID, peerAccountID);
}
};
auto const expectedNext = traverseOwnedNodes(
*sharedPtrBackend_,
*accountID, // NOLINT(bugprone-unchecked-optional-access)
accountID,
lgrInfo.seq,
input.limit,
input.marker,
@@ -182,7 +174,7 @@ AccountLinesHandler::process(AccountLinesHandler::Input const& input, Context co
auto const nextMarker = *expectedNext;
response.account = input.account;
response.account = xrpl::to_string(accountID);
response.limit = input.limit; // not documented,
// https://github.com/XRPLF/xrpl-dev-portal/issues/1838
response.ledgerHash = xrpl::strHex(lgrInfo.hash);
@@ -194,37 +186,6 @@ AccountLinesHandler::process(AccountLinesHandler::Input const& input, Context co
return response;
}
AccountLinesHandler::Input
tag_invoke(boost::json::value_to_tag<AccountLinesHandler::Input>, boost::json::value const& jv)
{
auto input = AccountLinesHandler::Input{};
auto const& jsonObject = jv.as_object();
input.account = boost::json::value_to<std::string>(jv.at(JS(account)));
if (jsonObject.contains(JS(limit)))
input.limit = util::integralValueAs<uint32_t>(jv.at(JS(limit)));
if (jsonObject.contains(JS(marker)))
input.marker = boost::json::value_to<std::string>(jv.at(JS(marker)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(jv.at(JS(ledger_hash)));
if (jsonObject.contains(JS(peer)))
input.peer = boost::json::value_to<std::string>(jv.at(JS(peer)));
if (jsonObject.contains(JS(ignore_default)))
input.ignoreDefault = jv.at(JS(ignore_default)).as_bool();
if (jsonObject.contains(JS(ledger_index))) {
auto const expectedLedgerIndex = util::getLedgerIndex(jv.at(JS(ledger_index)));
if (expectedLedgerIndex.has_value())
input.ledgerIndex = *expectedLedgerIndex;
}
return input;
}
void
tag_invoke(
boost::json::value_from_tag,

View File

@@ -1,26 +1,20 @@
#pragma once
#include "data/BackendInterface.hpp"
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Checkers.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 <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/account_lines/Types.hpp>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace rpc {
@@ -31,14 +25,15 @@ namespace rpc {
*
* For more details see: https://xrpl.org/account_lines.html
*/
class AccountLinesHandler {
class AccountLinesHandler
: public rpc::spec::HandlerFor<rpc::spec::handlers::account_lines::Input> {
// dependencies
std::shared_ptr<BackendInterface> const sharedPtrBackend_;
public:
static constexpr auto kLimitMin = 10;
static constexpr auto kLimitMax = 400;
static constexpr auto kLimitDefault = 200;
static constexpr auto kLimitMin = rpc::spec::handlers::account_lines::kLimitMin;
static constexpr auto kLimitMax = rpc::spec::handlers::account_lines::kLimitMax;
static constexpr auto kLimitDefault = rpc::spec::handlers::account_lines::kLimitDefault;
/**
* @brief A struct to hold data for one line response
@@ -74,20 +69,6 @@ public:
uint32_t limit{};
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::string account;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
std::optional<std::string> peer;
bool ignoreDefault = false; // TODO: document
// https://github.com/XRPLF/xrpl-dev-portal/issues/1839
uint32_t limit = kLimitDefault;
std::optional<std::string> marker;
};
using Result = HandlerReturnType<Output>;
/**
@@ -100,42 +81,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(account),
validation::Required{},
meta::WithCustomError{
validation::CustomValidators::accountValidator,
Status(RippledError::RpcActMalformed)
}},
{JS(peer),
meta::WithCustomError{
validation::CustomValidators::accountValidator,
Status(RippledError::RpcActMalformed)
}},
{JS(ignore_default), validation::Type<bool>{}},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
modifiers::Clamp<int32_t>{kLimitMin, kLimitMax}},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(marker), validation::CustomValidators::accountMarkerValidator},
{JS(ledger), check::Deprecated{}},
{"peer_index", check::Deprecated{}},
};
return kRpcSpec;
}
/**
* @brief Process the AccountLines command
*
@@ -165,15 +110,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<Input>, boost::json::value const& jv);
/**
* @brief Convert the LineResponse to a JSON object
*

View File

@@ -4,12 +4,10 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
@@ -124,11 +122,10 @@ AccountMPTokenIssuancesHandler::process(
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "AccountMPTokenIssuances' 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)
);
@@ -136,12 +133,9 @@ AccountMPTokenIssuancesHandler::process(
return Error{expectedLgrInfo.error()};
auto const& lgrInfo = *expectedLgrInfo;
auto const accountID = accountFromStringStrict(input.account);
auto const& accountID = input.account;
auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
xrpl::keylet::account(*accountID).key,
lgrInfo.seq,
ctx.yield
xrpl::keylet::account(accountID).key, lgrInfo.seq, ctx.yield
);
if (not accountLedgerObject.has_value())
@@ -152,13 +146,13 @@ AccountMPTokenIssuancesHandler::process(
auto const addToResponse = [&](xrpl::SLE const& sle) {
if (sle.getType() == xrpl::ltMPTOKEN_ISSUANCE) {
addMPTokenIssuance(response.issuances, sle, *accountID);
addMPTokenIssuance(response.issuances, sle, accountID);
}
};
auto const expectedNext = traverseOwnedNodes(
*sharedPtrBackend_,
*accountID, // NOLINT(bugprone-unchecked-optional-access)
accountID,
lgrInfo.seq,
input.limit,
input.marker,
@@ -171,7 +165,7 @@ AccountMPTokenIssuancesHandler::process(
auto const nextMarker = *expectedNext;
response.account = input.account;
response.account = xrpl::to_string(accountID);
response.limit = input.limit;
response.ledgerHash = xrpl::strHex(lgrInfo.hash);
@@ -183,35 +177,6 @@ AccountMPTokenIssuancesHandler::process(
return response;
}
AccountMPTokenIssuancesHandler::Input
tag_invoke(
boost::json::value_to_tag<AccountMPTokenIssuancesHandler::Input>,
boost::json::value const& jv
)
{
auto input = AccountMPTokenIssuancesHandler::Input{};
auto const& jsonObject = jv.as_object();
input.account = boost::json::value_to<std::string>(jv.at(JS(account)));
if (jsonObject.contains(JS(limit)))
input.limit = util::integralValueAs<uint32_t>(jv.at(JS(limit)));
if (jsonObject.contains(JS(marker)))
input.marker = boost::json::value_to<std::string>(jv.at(JS(marker)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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;
}
return input;
}
void
tag_invoke(
boost::json::value_from_tag,

View File

@@ -1,26 +1,20 @@
#pragma once
#include "data/BackendInterface.hpp"
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Checkers.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 <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/account_mptoken_issuances/Types.hpp>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace rpc {
@@ -29,14 +23,16 @@ namespace rpc {
* @brief The account_mptoken_issuances method returns information about all MPTokenIssuance objects
* the account has created.
*/
class AccountMPTokenIssuancesHandler {
class AccountMPTokenIssuancesHandler
: public rpc::spec::HandlerFor<rpc::spec::handlers::account_mptoken_issuances::Input> {
// dependencies
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
static constexpr auto kLimitMin = 10;
static constexpr auto kLimitMax = 400;
static constexpr auto kLimitDefault = 200;
static constexpr auto kLimitMin = rpc::spec::handlers::account_mptoken_issuances::kLimitMin;
static constexpr auto kLimitMax = rpc::spec::handlers::account_mptoken_issuances::kLimitMax;
static constexpr auto kLimitDefault =
rpc::spec::handlers::account_mptoken_issuances::kLimitDefault;
/**
* @brief A struct to hold data for one MPTokenIssuance response.
@@ -92,17 +88,6 @@ public:
uint32_t limit{};
};
/**
* @brief A struct to hold the input data for the command.
*/
struct Input {
std::string account;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
uint32_t limit = kLimitDefault;
std::optional<std::string> marker;
};
using Result = HandlerReturnType<Output>;
/**
@@ -115,35 +100,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 API version.
*/
static RpcSpecConstRef
spec([[maybe_unused]] uint32_t apiVersion)
{
static auto const kRpcSpec = RpcSpec{
{JS(account),
validation::Required{},
meta::WithCustomError{
validation::CustomValidators::accountValidator,
Status(RippledError::RpcActMalformed)
}},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
modifiers::Clamp<int32_t>{kLimitMin, kLimitMax}},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(marker), validation::CustomValidators::accountMarkerValidator},
{JS(ledger), check::Deprecated{}},
};
return kRpcSpec;
}
/**
* @brief Process the AccountMPTokenIssuances command.
*
@@ -172,15 +128,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<Input>, boost::json::value const& jv);
/**
* @brief Convert the MPTokenIssuanceResponse to a JSON object
*

View File

@@ -4,13 +4,11 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_from.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/AccountID.h>
@@ -88,11 +86,10 @@ AccountMPTokensHandler::process(
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "AccountMPTokens' 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)
);
@@ -100,12 +97,9 @@ AccountMPTokensHandler::process(
return Error{expectedLgrInfo.error()};
auto const& lgrInfo = *expectedLgrInfo;
auto const accountID = accountFromStringStrict(input.account);
auto const& accountID = input.account;
auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
xrpl::keylet::account(*accountID).key,
lgrInfo.seq,
ctx.yield
xrpl::keylet::account(accountID).key, lgrInfo.seq, ctx.yield
);
if (not accountLedgerObject.has_value())
@@ -122,7 +116,7 @@ AccountMPTokensHandler::process(
auto const expectedNext = traverseOwnedNodes(
*sharedPtrBackend_,
*accountID, // NOLINT(bugprone-unchecked-optional-access)
accountID,
lgrInfo.seq,
input.limit,
input.marker,
@@ -135,7 +129,7 @@ AccountMPTokensHandler::process(
auto const& nextMarker = *expectedNext;
response.account = input.account;
response.account = xrpl::to_string(accountID);
response.limit = input.limit;
response.ledgerHash = xrpl::strHex(lgrInfo.hash);
@@ -147,32 +141,6 @@ AccountMPTokensHandler::process(
return response;
}
AccountMPTokensHandler::Input
tag_invoke(boost::json::value_to_tag<AccountMPTokensHandler::Input>, boost::json::value const& jv)
{
AccountMPTokensHandler::Input input{};
auto const& jsonObject = jv.as_object();
input.account = boost::json::value_to<std::string>(jv.at(JS(account)));
if (jsonObject.contains(JS(limit)))
input.limit = util::integralValueAs<uint32_t>(jv.at(JS(limit)));
if (jsonObject.contains(JS(marker)))
input.marker = boost::json::value_to<std::string>(jv.at(JS(marker)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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;
}
return input;
}
void
tag_invoke(
boost::json::value_from_tag,

View File

@@ -1,25 +1,19 @@
#pragma once
#include "data/BackendInterface.hpp"
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Checkers.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 <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/account_mptokens/Types.hpp>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace rpc {
@@ -28,14 +22,15 @@ namespace rpc {
* @brief The account_mptokens method returns information about the MPTokens the account currently
* holds.
*/
class AccountMPTokensHandler {
class AccountMPTokensHandler
: public rpc::spec::HandlerFor<rpc::spec::handlers::account_mptokens::Input> {
// dependencies
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
static constexpr auto kLimitMin = 10;
static constexpr auto kLimitMax = 400;
static constexpr auto kLimitDefault = 200;
static constexpr auto kLimitMin = rpc::spec::handlers::account_mptokens::kLimitMin;
static constexpr auto kLimitMax = rpc::spec::handlers::account_mptokens::kLimitMax;
static constexpr auto kLimitDefault = rpc::spec::handlers::account_mptokens::kLimitDefault;
/**
* @brief A struct to hold data for one MPToken response.
@@ -71,17 +66,6 @@ public:
uint32_t limit{};
};
/**
* @brief A struct to hold the input data for the command.
*/
struct Input {
std::string account;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
uint32_t limit = kLimitDefault;
std::optional<std::string> marker;
};
using Result = HandlerReturnType<Output>;
/**
@@ -94,35 +78,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 API version.
*/
static RpcSpecConstRef
spec([[maybe_unused]] uint32_t apiVersion)
{
static auto const kRpcSpec = RpcSpec{
{JS(account),
validation::Required{},
meta::WithCustomError{
validation::CustomValidators::accountValidator,
Status(RippledError::RpcActMalformed)
}},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
modifiers::Clamp<int32_t>{kLimitMin, kLimitMax}},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(marker), validation::CustomValidators::accountMarkerValidator},
{JS(ledger), check::Deprecated{}},
};
return kRpcSpec;
}
/**
* @brief Process the AccountMPTokens command.
*
@@ -147,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<Input>, boost::json::value const& jv);
/**
* @brief Convert the MPTokenResponse to a JSON object
*

View File

@@ -4,11 +4,9 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
@@ -35,11 +33,10 @@ AccountNFTsHandler::process(AccountNFTsHandler::Input const& input, Context cons
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "AccountNFT'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)
);
@@ -47,27 +44,22 @@ AccountNFTsHandler::process(AccountNFTsHandler::Input const& input, Context cons
return Error{expectedLgrInfo.error()};
auto const& lgrInfo = *expectedLgrInfo;
auto const accountID = accountFromStringStrict(input.account);
auto const& accountID = input.account;
auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
xrpl::keylet::account(*accountID).key,
lgrInfo.seq,
ctx.yield
xrpl::keylet::account(accountID).key, lgrInfo.seq, ctx.yield
);
if (!accountLedgerObject)
return Error{Status{RippledError::RpcActNotFound}};
auto response = Output{};
response.account = input.account;
response.account = xrpl::to_string(accountID);
response.limit = input.limit;
response.ledgerHash = xrpl::strHex(lgrInfo.hash);
response.ledgerIndex = lgrInfo.seq;
// if a marker was passed, start at the page specified in marker. Else, start at the max page
auto const pageKey = input.marker ? xrpl::uint256{input.marker->c_str()}
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
: xrpl::keylet::nftokenPageMax(*accountID).key;
auto const pageKey = input.marker ? *input.marker : xrpl::keylet::nftokenPageMax(accountID).key;
auto const blob = sharedPtrBackend_->fetchLedgerObject(pageKey, lgrInfo.seq, ctx.yield);
if (!blob) {
@@ -152,30 +144,4 @@ tag_invoke(
jv.as_object()[JS(marker)] = *output.marker;
}
AccountNFTsHandler::Input
tag_invoke(boost::json::value_to_tag<AccountNFTsHandler::Input>, boost::json::value const& jv)
{
auto input = AccountNFTsHandler::Input{};
auto const& jsonObject = jv.as_object();
input.account = boost::json::value_to<std::string>(jsonObject.at(JS(account)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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(limit)))
input.limit = util::integralValueAs<uint32_t>(jsonObject.at(JS(limit)));
if (jsonObject.contains(JS(marker)))
input.marker = boost::json::value_to<std::string>(jsonObject.at(JS(marker)));
return input;
}
} // namespace rpc

View File

@@ -1,21 +1,19 @@
#pragma once
#include "data/BackendInterface.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Modifiers.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/account_nfts/Types.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
namespace rpc {
@@ -24,13 +22,13 @@ namespace rpc {
*
* For more details see: https://xrpl.org/account_nfts.html
*/
class AccountNFTsHandler {
class AccountNFTsHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::account_nfts::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
static constexpr auto kLimitMin = 20;
static constexpr auto kLimitMax = 400;
static constexpr auto kLimitDefault = 100;
static constexpr auto kLimitMin = rpc::spec::handlers::account_nfts::kLimitMin;
static constexpr auto kLimitMax = rpc::spec::handlers::account_nfts::kLimitMax;
static constexpr auto kLimitDefault = rpc::spec::handlers::account_nfts::kLimitDefault;
/**
* @brief A struct to hold the output data of the command
@@ -46,17 +44,6 @@ public:
bool validated = true;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::string account;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
uint32_t limit = kLimitDefault; // Limit the number of token pages to retrieve. [20,400]
std::optional<std::string> marker;
};
using Result = HandlerReturnType<Output>;
/**
@@ -69,29 +56,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(account), validation::Required{}, validation::CustomValidators::accountValidator},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(marker), validation::CustomValidators::uint256HexStringValidator},
{JS(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
modifiers::Clamp<int32_t>{kLimitMin, kLimitMax}},
};
return kRpcSpec;
}
/**
* @brief Process the AccountNFTs command
*
@@ -111,15 +75,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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -4,24 +4,23 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include "util/LedgerUtils.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <rpcspec/LedgerTypes.hpp>
#include <xrpl/basics/strHex.h>
#include <xrpl/ledger/helpers/SponsorHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <optional>
#include <string>
@@ -37,11 +36,10 @@ AccountObjectsHandler::process(AccountObjectsHandler::Input const& input, Contex
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "AccountObject'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)
);
@@ -49,12 +47,9 @@ AccountObjectsHandler::process(AccountObjectsHandler::Input const& input, Contex
return Error{expectedLgrInfo.error()};
auto const& lgrInfo = *expectedLgrInfo;
auto const accountID = accountFromStringStrict(input.account);
auto const& accountID = input.account;
auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
xrpl::keylet::account(*accountID).key,
lgrInfo.seq,
ctx.yield
xrpl::keylet::account(accountID).key, lgrInfo.seq, ctx.yield
);
if (!accountLedgerObject)
@@ -78,8 +73,40 @@ AccountObjectsHandler::process(AccountObjectsHandler::Input const& input, Contex
typeFilter = {*input.type};
}
auto const sponsorField = [](xrpl::SLE const& sle,
xrpl::SField const& field) -> std::optional<xrpl::AccountID> {
if (sle.isFieldPresent(field))
return sle.getAccountID(field);
return std::nullopt;
};
auto const sponsorOf = [&sponsorField](xrpl::SLE const& sle) {
// A trust line records a sponsor per side, so either one makes it sponsored.
if (sle.getType() == xrpl::ltRIPPLE_STATE) {
if (auto const high = sponsorField(sle, xrpl::sfHighSponsor); high.has_value())
return high;
return sponsorField(sle, xrpl::sfLowSponsor);
}
// NFTokenPage is not in isLedgerEntrySupportedBySponsorship's allowlist, so that
// predicate alone would report every page as unsponsored; xrpld reads pages on a
// separate path for the same reason. No page can actually carry sfSponsor today --
// SponsorshipTransfer::preclaim rejects unsupported types with tecNO_PERMISSION -- so
// this only starts to matter if NFTokenPage is added to that allowlist upstream.
if (sle.getType() == xrpl::ltNFTOKEN_PAGE or xrpl::isLedgerEntrySupportedBySponsorship(sle))
return sponsorField(sle, xrpl::sfSponsor);
return std::optional<xrpl::AccountID>{};
};
Output response;
auto const addToResponse = [&](xrpl::SLE&& sle) {
if (input.sponsored.has_value() and
sponsorOf(sle).has_value() != static_cast<bool>(*input.sponsored)) {
return true;
}
if (not typeFilter or
std::find(std::begin(*typeFilter), std::end(*typeFilter), sle.getType()) !=
std::end(*typeFilter)) {
@@ -90,7 +117,7 @@ AccountObjectsHandler::process(AccountObjectsHandler::Input const& input, Contex
auto const expectedNext = traverseOwnedNodes(
*sharedPtrBackend_,
*accountID, // NOLINT(bugprone-unchecked-optional-access)
accountID,
lgrInfo.seq,
input.limit,
input.marker,
@@ -105,7 +132,7 @@ AccountObjectsHandler::process(AccountObjectsHandler::Input const& input, Contex
response.ledgerHash = xrpl::strHex(lgrInfo.hash);
response.ledgerIndex = lgrInfo.seq;
response.limit = input.limit;
response.account = input.account;
response.account = xrpl::to_string(accountID);
auto const& nextMarker = *expectedNext;
@@ -143,39 +170,4 @@ tag_invoke(
jv.as_object()[JS(marker)] = *(output.marker);
}
AccountObjectsHandler::Input
tag_invoke(boost::json::value_to_tag<AccountObjectsHandler::Input>, boost::json::value const& jv)
{
auto input = AccountObjectsHandler::Input{};
auto const& jsonObject = jv.as_object();
input.account = boost::json::value_to<std::string>(jv.at(JS(account)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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(type))) {
input.type = rpc::spec::accountOwnedLedgerTypeFromStr(
boost::json::value_to<std::string>(jv.at(JS(type)))
);
}
if (jsonObject.contains(JS(limit)))
input.limit = util::integralValueAs<uint32_t>(jv.at(JS(limit)));
if (jsonObject.contains(JS(marker)))
input.marker = boost::json::value_to<std::string>(jv.at(JS(marker)));
if (jsonObject.contains(JS(deletion_blockers_only)))
input.deletionBlockersOnly = jsonObject.at(JS(deletion_blockers_only)).as_bool();
return input;
}
} // namespace rpc

View File

@@ -1,23 +1,19 @@
#pragma once
#include "data/BackendInterface.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Modifiers.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/account_objects/Types.hpp>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
namespace rpc {
@@ -29,14 +25,15 @@ namespace rpc {
*
* For more details see: https://xrpl.org/account_objects.html
*/
class AccountObjectsHandler {
class AccountObjectsHandler
: public rpc::spec::HandlerFor<rpc::spec::handlers::account_objects::Input> {
// dependencies
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
static constexpr auto kLimitMin = 10;
static constexpr auto kLimitMax = 400;
static constexpr auto kLimitDefault = 200;
static constexpr auto kLimitMin = rpc::spec::handlers::account_objects::kLimitMin;
static constexpr auto kLimitMax = rpc::spec::handlers::account_objects::kLimitMax;
static constexpr auto kLimitDefault = rpc::spec::handlers::account_objects::kLimitDefault;
/**
* @brief A struct to hold the output data of the command
@@ -51,19 +48,6 @@ public:
bool validated = true;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::string account;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
uint32_t limit = kLimitDefault; // [10,400]
std::optional<std::string> marker;
std::optional<xrpl::LedgerEntryType> type;
bool deletionBlockersOnly = false;
};
using Result = HandlerReturnType<Output>;
/**
@@ -76,31 +60,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(account), validation::Required{}, validation::CustomValidators::accountValidator},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
modifiers::Clamp<int32_t>(kLimitMin, kLimitMax)},
{JS(type), validation::CustomValidators::accountTypeValidator},
{JS(marker), validation::CustomValidators::accountMarkerValidator},
{JS(deletion_blockers_only), validation::Type<bool>{}},
};
return kRpcSpec;
}
/**
* @brief Process the AccountObjects command
*
@@ -120,15 +79,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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -4,12 +4,10 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_from.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/AccountID.h>
@@ -22,7 +20,6 @@
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <string>
#include <vector>
@@ -52,11 +49,10 @@ AccountOffersHandler::process(AccountOffersHandler::Input const& input, Context
{
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "AccountOffer'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)
);
@@ -64,19 +60,16 @@ AccountOffersHandler::process(AccountOffersHandler::Input const& input, Context
return Error{expectedLgrInfo.error()};
auto const& lgrInfo = *expectedLgrInfo;
auto const accountID = accountFromStringStrict(input.account);
auto const& accountID = input.account;
auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
xrpl::keylet::account(*accountID).key,
lgrInfo.seq,
ctx.yield
xrpl::keylet::account(accountID).key, lgrInfo.seq, ctx.yield
);
if (!accountLedgerObject)
return Error{Status{RippledError::RpcActNotFound}};
Output response;
response.account = xrpl::to_string(*accountID); // NOLINT(bugprone-unchecked-optional-access)
response.account = xrpl::to_string(accountID);
response.ledgerHash = xrpl::strHex(lgrInfo.hash);
response.ledgerIndex = lgrInfo.seq;
@@ -89,7 +82,7 @@ AccountOffersHandler::process(AccountOffersHandler::Input const& input, Context
auto const expectedNext = traverseOwnedNodes(
*sharedPtrBackend_,
*accountID, // NOLINT(bugprone-unchecked-optional-access)
accountID,
lgrInfo.seq,
input.limit,
input.marker,
@@ -161,30 +154,4 @@ tag_invoke(
convertAmount(JS(taker_gets), offer.takerGets);
}
AccountOffersHandler::Input
tag_invoke(boost::json::value_to_tag<AccountOffersHandler::Input>, boost::json::value const& jv)
{
auto input = AccountOffersHandler::Input{};
auto const& jsonObject = jv.as_object();
input.account = boost::json::value_to<std::string>(jsonObject.at(JS(account)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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(limit)))
input.limit = util::integralValueAs<uint32_t>(jsonObject.at(JS(limit)));
if (jsonObject.contains(JS(marker)))
input.marker = boost::json::value_to<std::string>(jsonObject.at(JS(marker)));
return input;
}
} // namespace rpc

View File

@@ -1,23 +1,20 @@
#pragma once
#include "data/BackendInterface.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Checkers.hpp"
#include "rpc/common/Modifiers.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/account_offers/Types.hpp>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace rpc {
@@ -27,13 +24,14 @@ namespace rpc {
*
* For more details see: https://xrpl.org/account_offers.html
*/
class AccountOffersHandler {
class AccountOffersHandler
: public rpc::spec::HandlerFor<rpc::spec::handlers::account_offers::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
static constexpr auto kLimitMin = 10;
static constexpr auto kLimitMax = 400;
static constexpr auto kLimitDefault = 200;
static constexpr auto kLimitMin = rpc::spec::handlers::account_offers::kLimitMin;
static constexpr auto kLimitMax = rpc::spec::handlers::account_offers::kLimitMax;
static constexpr auto kLimitDefault = rpc::spec::handlers::account_offers::kLimitDefault;
/**
* @brief A struct to hold data for one offer response
@@ -60,17 +58,6 @@ public:
bool validated = true;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::string account;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
uint32_t limit = kLimitDefault;
std::optional<std::string> marker;
};
using Result = HandlerReturnType<Output>;
/**
@@ -83,31 +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 auto const kRpcSpec = RpcSpec{
{JS(account), validation::Required{}, validation::CustomValidators::accountValidator},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(marker), validation::CustomValidators::accountMarkerValidator},
{JS(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
modifiers::Clamp<int32_t>{kLimitMin, kLimitMax}},
{JS(ledger), check::Deprecated{}},
{JS(strict), check::Deprecated{}},
};
return kRpcSpec;
}
/**
* @brief Process the AccountOffers command
*
@@ -131,15 +93,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<Input>, boost::json::value const& jv);
/**
* @brief Convert the Offer to a JSON object
*

View File

@@ -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 <boost/json/value_from.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/base_uint.h>
#include <rpcspec/handlers/account_tx/Types.hpp>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/AccountID.h>
@@ -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<std::uint32_t>(*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<std::uint32_t>(*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<rpc::DelegateTransactionFilter> 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<xrpl::uint192> 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<AccountTxHandler::Input>, boost::json::value const& jv)
{
auto input = AccountTxHandler::Input{};
auto const& jsonObject = jv.as_object();
input.account = boost::json::value_to<std::string>(jsonObject.at(JS(account)));
if (jsonObject.contains(JS(ledger_index_min)) &&
util::integralValueAs<int32_t>(jsonObject.at(JS(ledger_index_min))) != -1)
input.ledgerIndexMin = util::integralValueAs<int32_t>(jsonObject.at(JS(ledger_index_min)));
if (jsonObject.contains(JS(ledger_index_max)) &&
util::integralValueAs<int32_t>(jsonObject.at(JS(ledger_index_max))) != -1)
input.ledgerIndexMax = util::integralValueAs<int32_t>(jsonObject.at(JS(ledger_index_max)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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<JsonBool>(jsonObject.at(JS(binary)));
if (jsonObject.contains(JS(forward)))
input.forward = boost::json::value_to<JsonBool>(jsonObject.at(JS(forward)));
if (jsonObject.contains(JS(limit)))
input.limit = util::integralValueAs<uint32_t>(jsonObject.at(JS(limit)));
if (jsonObject.contains(JS(marker))) {
input.marker = AccountTxHandler::Marker{
.ledger = util::integralValueAs<uint32_t>(
jsonObject.at(JS(marker)).as_object().at(JS(ledger))
),
.seq =
util::integralValueAs<uint32_t>(jsonObject.at(JS(marker)).as_object().at(JS(seq)))
};
}
if (jsonObject.contains("tx_type")) {
input.transactionTypeInLowercase =
boost::json::value_to<std::string>(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<std::string>(jsonObject.at(JS(mpt_issuance_id)));
}
return input;
}
} // namespace rpc
} // namespace rpc::spec::handlers::account_tx

View File

@@ -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 <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/TxTypes.hpp>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/TxFormats.h>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/account_tx/Types.hpp>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <string>
#include <unordered_set>
#include <utility>
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<rpc::spec::handlers::account_tx::Input> {
util::Logger log_{"RPC"};
std::shared_ptr<BackendInterface> sharedPtrBackend_;
std::shared_ptr<etl::ETLServiceInterface const> 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<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
std::optional<int32_t> ledgerIndexMin;
std::optional<int32_t> ledgerIndexMax;
bool usingValidatedLedger = false;
JsonBool binary{false};
JsonBool forward{false};
std::optional<uint32_t> limit;
std::optional<Marker> marker;
std::optional<std::string> transactionTypeInLowercase;
std::optional<DelegateFilter> delegateFilter;
std::optional<std::string> mptIssuanceId;
};
using Result = HandlerReturnType<Output>;
/**
@@ -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<int32_t>{}},
{JS(ledger_index_max), validation::Type<int32_t>{}},
{JS(ctid), validation::Type<std::string>{}},
{JS(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
modifiers::Clamp<int32_t>{kLimitMin, kLimitMax}},
{JS(marker),
meta::WithCustomError{
validation::Type<boost::json::object>{},
Status{RippledError::RpcInvalidParams, "invalidMarker"},
},
meta::Section{
{JS(ledger), validation::Required{}, validation::Type<uint32_t>{}},
{JS(seq), validation::Required{}, validation::Type<uint32_t>{}},
}},
{
"tx_type",
validation::Type<std::string>{},
modifiers::ToLower{},
validation::OneOf<std::string>(
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<bool>{}},
{JS(forward), validation::Type<bool>{}},
}
};
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<Input>, 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

View File

@@ -6,12 +6,10 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/LedgerHeader.h>
@@ -28,11 +26,10 @@ BookChangesHandler::process(BookChangesHandler::Input const& input, Context cons
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "BookChanges' 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)
);
@@ -71,24 +68,6 @@ tag_invoke(
};
}
BookChangesHandler::Input
tag_invoke(boost::json::value_to_tag<BookChangesHandler::Input>, boost::json::value const& jv)
{
auto input = BookChangesHandler::Input{};
auto const& jsonObject = jv.as_object();
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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;
}
return input;
}
[[nodiscard]] boost::json::object
computeBookChanges(
xrpl::LedgerHeader const& lgrInfo,

View File

@@ -2,19 +2,17 @@
#include "data/BackendInterface.hpp"
#include "rpc/BookChangesHelper.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/book_changes/Types.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace rpc {
@@ -24,7 +22,7 @@ namespace rpc {
*
* This API is not documented in the rippled API documentation.
*/
class BookChangesHandler {
class BookChangesHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::book_changes::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
@@ -39,16 +37,6 @@ public:
bool validated = true;
};
/**
* @brief A struct to hold the input data for the command
*
* @note Clio does not implement `deletion_blockers_only`
*/
struct Input {
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
};
using Result = HandlerReturnType<Output>;
/**
@@ -61,23 +49,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(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
};
return kRpcSpec;
}
/**
* @brief Process the BookChanges command
*
@@ -97,15 +68,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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -4,25 +4,17 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <string>
namespace rpc {
@@ -30,7 +22,7 @@ namespace rpc {
BookOffersHandler::Result
BookOffersHandler::process(Input const& input, Context const& ctx) const
{
auto bookMaybe = parseBook(input.paysAsset, input.getsAsset, input.domain);
auto bookMaybe = parseBook(input.takerPays, input.takerGets, input.domain);
if (!bookMaybe.has_value())
return Error{bookMaybe.error()};
@@ -38,11 +30,10 @@ BookOffersHandler::process(Input const& input, Context const& ctx) const
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "BookOffer'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)
);
@@ -87,56 +78,4 @@ tag_invoke(
};
}
BookOffersHandler::Input
tag_invoke(boost::json::value_to_tag<BookOffersHandler::Input>, boost::json::value const& jv)
{
auto input = BookOffersHandler::Input{};
auto const& jsonObject = jv.as_object();
auto const parseAsset = [](boost::json::object const& obj) -> xrpl::Asset {
if (obj.contains(JS(mpt_issuance_id))) {
xrpl::MPTID mptid;
auto const parsed =
mptid.parseHex(boost::json::value_to<std::string>(obj.at(JS(mpt_issuance_id))));
ASSERT(parsed, "mpt_issuance_id is guaranteed to be valid hex by the spec validator");
return xrpl::MPTIssue{mptid};
}
xrpl::Issue issue;
auto const currencyParsed = xrpl::toCurrency(
issue.currency, boost::json::value_to<std::string>(obj.at(JS(currency)))
);
ASSERT(currencyParsed, "currency is guaranteed to be valid by the spec validator");
if (obj.contains(JS(issuer))) {
auto const issuerParsed = xrpl::toIssuer(
issue.account, boost::json::value_to<std::string>(obj.at(JS(issuer)))
);
ASSERT(issuerParsed, "issuer is guaranteed to be valid by the spec validator");
}
return issue;
};
input.getsAsset = parseAsset(jv.at(JS(taker_gets)).as_object());
input.paysAsset = parseAsset(jv.at(JS(taker_pays)).as_object());
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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(taker)))
input.taker = accountFromStringStrict(boost::json::value_to<std::string>(jv.at(JS(taker))));
if (jsonObject.contains(JS(domain)))
input.domain = boost::json::value_to<std::string>(jv.at(JS(domain)));
if (jsonObject.contains(JS(limit)))
input.limit = util::integralValueAs<uint32_t>(jv.at(JS(limit)));
return input;
}
} // namespace rpc

View File

@@ -2,28 +2,17 @@
#include "data/AmendmentCenterInterface.hpp"
#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 <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/book_offers/Types.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
@@ -35,14 +24,14 @@ namespace rpc {
*
* For more details see: https://xrpl.org/book_offers.html
*/
class BookOffersHandler {
class BookOffersHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::book_offers::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
std::shared_ptr<data::AmendmentCenterInterface const> amendmentCenter_;
public:
static constexpr auto kLimitMin = 1;
static constexpr auto kLimitMax = 100;
static constexpr auto kLimitDefault = 60;
static constexpr auto kLimitMin = rpc::spec::handlers::book_offers::kLimitMin;
static constexpr auto kLimitMax = rpc::spec::handlers::book_offers::kLimitMax;
static constexpr auto kLimitDefault = rpc::spec::handlers::book_offers::kLimitDefault;
/**
* @brief A struct to hold the output data of the command
@@ -54,22 +43,6 @@ public:
bool validated = true;
};
/**
* @brief A struct to hold the input data for the command
*
* @note The taker is not really used in both Clio and `rippled`, both of them return all the
* offers regardless of the funding status
*/
struct Input {
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
uint32_t limit = kLimitDefault;
std::optional<xrpl::AccountID> taker;
xrpl::Asset paysAsset = xrpl::xrpIssue();
xrpl::Asset getsAsset = xrpl::xrpIssue();
std::optional<std::string> domain;
};
using Result = HandlerReturnType<Output>;
/**
@@ -86,84 +59,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(taker_gets),
validation::Required{},
validation::Type<boost::json::object>{},
validation::CustomValidators::bookTakerValidator,
meta::Section{
{JS(currency),
meta::WithCustomError{
validation::CustomValidators::currencyValidator,
Status(RippledError::RpcDstAmtMalformed)
}},
{JS(mpt_issuance_id),
meta::WithCustomError{
validation::CustomValidators::uint192HexStringValidator,
Status(RippledError::RpcDstAmtMalformed)
}},
{JS(issuer),
meta::WithCustomError{
validation::CustomValidators::issuerValidator,
Status(RippledError::RpcDstIsrMalformed)
}}
}},
{JS(taker_pays),
validation::Required{},
validation::Type<boost::json::object>{},
validation::CustomValidators::bookTakerValidator,
meta::Section{
{JS(currency),
meta::WithCustomError{
validation::CustomValidators::currencyValidator,
Status(RippledError::RpcSrcCurMalformed)
}},
{JS(mpt_issuance_id),
meta::WithCustomError{
validation::CustomValidators::uint192HexStringValidator,
Status(RippledError::RpcSrcCurMalformed)
}},
{JS(issuer),
meta::WithCustomError{
validation::CustomValidators::issuerValidator,
Status(RippledError::RpcSrcIsrMalformed)
}}
}},
// return INVALID_PARAMS if account format is wrong for "taker"
{JS(taker),
meta::WithCustomError{
validation::CustomValidators::accountValidator,
Status(RippledError::RpcInvalidParams, "Invalid field 'taker'.")
}},
{JS(domain),
meta::WithCustomError{
validation::Type<std::string>{},
Status(RippledError::RpcDomainMalformed, "Unable to parse domain.")
},
meta::WithCustomError{
validation::CustomValidators::uint256HexStringValidator,
Status(RippledError::RpcDomainMalformed, "Unable to parse domain.")
}},
{JS(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
modifiers::Clamp<int32_t>{kLimitMin, kLimitMax}},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
};
return kRpcSpec;
}
/**
* @brief Process the BookOffers command
*
@@ -183,14 +78,5 @@ 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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -5,16 +5,16 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/string.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/LedgerHeader.h>
@@ -24,6 +24,8 @@
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/jss.h>
#include <optional>
#include <ranges>
#include <string>
#include <utility>
@@ -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<DepositAuthorizedHandler::Input>, boost::json::value const& jv)
{
auto input = DepositAuthorizedHandler::Input{};
auto const& jsonObject = jv.as_object();
input.sourceAccount = boost::json::value_to<std::string>(jv.at(JS(source_account)));
input.destinationAccount = boost::json::value_to<std::string>(jv.at(JS(destination_account)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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<boost::json::array>(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<boost::json::array>();
}
}
} // namespace rpc

View File

@@ -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 <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/deposit_authorized/Types.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
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<rpc::spec::handlers::deposit_authorized::Input> {
// dependencies
std::shared_ptr<BackendInterface> const sharedPtrBackend_;
@@ -43,23 +46,12 @@ public:
std::string destinationAccount;
std::string ledgerHash;
uint32_t ledgerIndex{};
std::optional<boost::json::array> credentials;
std::optional<std::vector<xrpl::uint256>> 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<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
std::optional<boost::json::array> credentials;
};
using Result = HandlerReturnType<Output>;
/**
@@ -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<boost::json::array>{}, 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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -3,16 +3,11 @@
#include "data/Types.hpp"
#include "rpc/JS.hpp"
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/MetaProcessors.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
@@ -21,7 +16,6 @@
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <map>
#include <ranges>
@@ -40,11 +34,10 @@ FeatureHandler::process(FeatureHandler::Input const& input, Context const& ctx)
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "Feature'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)
);
@@ -99,25 +92,6 @@ FeatureHandler::process(FeatureHandler::Input const& input, Context const& ctx)
};
}
RpcSpecConstRef
FeatureHandler::spec([[maybe_unused]] uint32_t apiVersion)
{
static RpcSpec const kRpcSpec = {
{JS(feature), validation::Type<std::string>{}},
{JS(vetoed),
meta::WithCustomError{
validation::NotSupported{},
Status(
RippledError::RpcNoPermission,
"The admin portion of feature API is not available through Clio."
)
}},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
};
return kRpcSpec;
}
void
tag_invoke(
boost::json::value_from_tag,
@@ -157,25 +131,4 @@ tag_invoke(
};
}
FeatureHandler::Input
tag_invoke(boost::json::value_to_tag<FeatureHandler::Input>, boost::json::value const& jv)
{
auto input = FeatureHandler::Input{};
auto const jsonObject = jv.as_object();
if (jsonObject.contains(JS(feature)))
input.feature = jv.at(JS(feature)).as_string();
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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;
}
return input;
}
} // namespace rpc

View File

@@ -2,38 +2,29 @@
#include "data/AmendmentCenterInterface.hpp"
#include "data/BackendInterface.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/feature/Types.hpp>
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>
namespace rpc {
/**
* @brief Contains common functionality for handling the `server_info` command
*/
class FeatureHandler {
class FeatureHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::feature::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
std::shared_ptr<data::AmendmentCenterInterface const> amendmentCenter_;
public:
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
std::optional<std::string> feature;
};
/**
* @brief A struct to hold the output data of the command
*/
@@ -76,15 +67,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);
/**
* @brief Process the Feature command
*
@@ -116,15 +98,6 @@ private:
*/
friend void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output::Feature const& feature);
/**
* @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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -4,13 +4,11 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/strHex.h>
#include <xrpl/beast/utility/Zero.h>
@@ -25,9 +23,7 @@
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <limits>
#include <map>
#include <stdexcept>
@@ -47,11 +43,10 @@ GatewayBalancesHandler::process(
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "GatewayBalances' 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)
);
@@ -60,12 +55,9 @@ GatewayBalancesHandler::process(
// check account
auto const& lgrInfo = *expectedLgrInfo;
auto const accountID = accountFromStringStrict(input.account);
auto const& accountID = input.account;
auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
xrpl::keylet::account(*accountID).key,
lgrInfo.seq,
ctx.yield
xrpl::keylet::account(accountID).key, lgrInfo.seq, ctx.yield
);
if (!accountLedgerObject)
@@ -158,7 +150,7 @@ GatewayBalancesHandler::process(
// traverse all owned nodes, limit->max, marker->empty
auto const ret = traverseOwnedNodes(
*sharedPtrBackend_,
*accountID, // NOLINT(bugprone-unchecked-optional-access)
accountID,
lgrInfo.seq,
std::numeric_limits<std::uint32_t>::max(),
{},
@@ -169,7 +161,7 @@ GatewayBalancesHandler::process(
if (!ret.has_value())
return Error{ret.error()};
output.accountID = input.account;
output.accountID = xrpl::to_string(accountID);
output.ledgerHash = xrpl::strHex(lgrInfo.hash);
output.ledgerIndex = lgrInfo.seq;
@@ -241,43 +233,4 @@ tag_invoke(
jv = std::move(obj);
}
GatewayBalancesHandler::Input
tag_invoke(boost::json::value_to_tag<GatewayBalancesHandler::Input>, boost::json::value const& jv)
{
auto input = GatewayBalancesHandler::Input{};
auto const& jsonObject = jv.as_object();
input.account = boost::json::value_to<std::string>(jv.at(JS(account)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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(hotwallet))) {
if (jsonObject.at(JS(hotwallet)).is_string()) {
input.hotWallets.insert(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
*accountFromStringStrict(boost::json::value_to<std::string>(jv.at(JS(hotwallet))))
);
} else {
auto const& hotWallets = jv.at(JS(hotwallet)).as_array();
std::ranges::transform(
hotWallets,
std::inserter(input.hotWallets, input.hotWallets.begin()),
[](auto const& hotWallet) {
return *accountFromStringStrict(boost::json::value_to<std::string>(hotWallet));
}
);
}
}
return input;
}
} // namespace rpc

View File

@@ -1,31 +1,22 @@
#pragma once
#include "data/BackendInterface.hpp"
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include "util/AccountUtils.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/gateway_balances/Types.hpp>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/PublicKey.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol/tokens.h>
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace rpc {
@@ -38,7 +29,8 @@ namespace rpc {
*
* For more details see: https://xrpl.org/gateway_balances.html#gateway_balances
*/
class GatewayBalancesHandler {
class GatewayBalancesHandler
: public rpc::spec::HandlerFor<rpc::spec::handlers::gateway_balances::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
@@ -59,16 +51,6 @@ public:
bool validated = true;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::string account;
std::set<xrpl::AccountID> hotWallets;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
};
using Result = HandlerReturnType<Output>;
/**
@@ -81,67 +63,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)
{
auto const getHotWalletValidator = [](RippledError errCode) {
return validation::CustomValidator{
[errCode](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_string() && !value.is_array())
return Error{Status{errCode, std::string(key) + "NotStringOrArray"}};
// wallet needs to be an valid accountID or public key
auto const wallets =
value.is_array() ? value.as_array() : boost::json::array{value};
auto const getAccountID = [](auto const& j) -> std::optional<xrpl::AccountID> {
if (j.is_string()) {
auto const pk = util::parseBase58Wrapper<xrpl::PublicKey>(
xrpl::TokenType::AccountPublic,
boost::json::value_to<std::string>(j)
);
if (pk)
return xrpl::calcAccountID(*pk);
return util::parseBase58Wrapper<xrpl::AccountID>(
boost::json::value_to<std::string>(j)
);
}
return {};
};
for (auto const& wallet : wallets) {
if (!getAccountID(wallet))
return Error{Status{errCode, std::string(key) + "Malformed"}};
}
return MaybeError{};
}
};
};
static auto const kSpecCommon = RpcSpec{
{JS(account), validation::Required{}, validation::CustomValidators::accountValidator},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator}
};
static auto const kSpecV1 = RpcSpec{
kSpecCommon, {{JS(hotwallet), getHotWalletValidator(xrpl::RpcInvalidHotwallet)}}
};
static auto const kSpecV2 =
RpcSpec{kSpecCommon, {{JS(hotwallet), getHotWalletValidator(xrpl::RpcInvalidParams)}}};
return apiVersion == 1 ? kSpecV1 : kSpecV2;
}
/**
* @brief Process the GatewayBalances command
*
@@ -161,15 +82,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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -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 <boost/asio/spawn.hpp>
#include <boost/bimap/bimap.hpp>
@@ -13,10 +11,9 @@
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/handlers/get_aggregate_price/Types.hpp>
#include <xrpl/basics/Number.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
@@ -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<GetAggregatePriceHandler::Input>, 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<std::string>(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<std::uint64_t>(
oracle.as_object().at(JS(oracle_document_id))
),
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
.account = *util::parseBase58Wrapper<xrpl::AccountID>(
boost::json::value_to<std::string>(oracle.as_object().at(JS(account)))
)
}
);
}
input.baseAsset = boost::json::value_to<std::string>(jv.at(JS(base_asset)));
input.quoteAsset = boost::json::value_to<std::string>(jv.at(JS(quote_asset)));
if (jsonObject.contains(JS(trim)))
input.trim = util::integralValueAs<uint8_t>(jv.at(JS(trim)));
if (jsonObject.contains(JS(time_threshold)))
input.timeThreshold = util::integralValueAs<uint32_t>(jv.at(JS(time_threshold)));
return input;
}
void
tag_invoke(
boost::json::value_from_tag,

View File

@@ -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 <boost/asio/spawn.hpp>
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/get_aggregate_price/Types.hpp>
#include <xrpl/basics/Number.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/STAmount.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/jss.h>
#include <cstddef>
#include <cstdint>
#include <expected>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace rpc {
@@ -31,7 +30,8 @@ namespace rpc {
/**
*@brief The get_aggregate_price method.
*/
class GetAggregatePriceHandler {
class GetAggregatePriceHandler
: public rpc::spec::HandlerFor<rpc::spec::handlers::get_aggregate_price::Input> {
std::shared_ptr<BackendInterface> 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<std::string> ledgerHash;
std::optional<std::uint32_t> ledgerIndex;
std::vector<Oracle> oracles; // valid range is 1-200
std::string baseAsset;
std::string quoteAsset;
std::optional<std::uint32_t> timeThreshold;
std::optional<std::uint8_t> trim; // valid range is 1-25
};
using Result = HandlerReturnType<Output>;
/**
@@ -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<std::uint32_t, std::string>{}.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<std::uint32_t>{}},
{
JS(trim),
validation::Type<std::uint8_t>{},
validation::Between<std::uint8_t>{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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -5,7 +5,6 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
@@ -13,7 +12,6 @@
#include <boost/json/object.hpp>
#include <boost/json/string.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/Issue.h>
@@ -36,11 +34,10 @@ LedgerHandler::process(LedgerHandler::Input const& input, Context const& ctx) co
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "LedgerHandler'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)
);
@@ -61,7 +58,7 @@ LedgerHandler::process(LedgerHandler::Input const& input, Context const& ctx) co
auto const expandTxJsonV1 = [&](data::TransactionAndMetadata const& tx) {
if (!input.binary) {
auto [txn, meta] = toExpandedJson(tx, ctx.apiVersion);
auto [txn, meta] = toExpandedJson(tx, ctx.apiVersion, NFTokenjson::ENABLE);
txn[JS(metaData)] = std::move(meta);
return txn;
}
@@ -71,7 +68,7 @@ LedgerHandler::process(LedgerHandler::Input const& input, Context const& ctx) co
auto const isoTimeStr = xrpl::toStringIso(lgrInfo.closeTime);
auto const expandTxJsonV2 = [&](data::TransactionAndMetadata const& tx) {
auto [txn, meta] = toExpandedJson(tx, ctx.apiVersion);
auto [txn, meta] = toExpandedJson(tx, ctx.apiVersion, NFTokenjson::ENABLE);
if (!input.binary) {
boost::json::object entry;
entry[JS(validated)] = true;
@@ -189,37 +186,4 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, LedgerHandler::O
};
}
LedgerHandler::Input
tag_invoke(boost::json::value_to_tag<LedgerHandler::Input>, boost::json::value const& jv)
{
auto input = LedgerHandler::Input{};
auto const& jsonObject = jv.as_object();
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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(transactions)))
input.transactions = jv.at(JS(transactions)).as_bool();
if (jsonObject.contains(JS(binary)))
input.binary = jv.at(JS(binary)).as_bool();
if (jsonObject.contains(JS(expand)))
input.expand = jv.at(JS(expand)).as_bool();
if (jsonObject.contains(JS(owner_funds)))
input.ownerFunds = jv.at(JS(owner_funds)).as_bool();
if (jsonObject.contains("diff"))
input.diff = jv.at("diff").as_bool();
return input;
}
} // namespace rpc

View File

@@ -2,20 +2,16 @@
#include "data/AmendmentCenterInterface.hpp"
#include "data/BackendInterface.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Checkers.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/ledger/Types.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
@@ -26,7 +22,7 @@ namespace rpc {
*
* For more details see: https://xrpl.org/ledger.html
*/
class LedgerHandler {
class LedgerHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::ledger::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
std::shared_ptr<data::AmendmentCenterInterface const> amendmentCenter_;
@@ -42,31 +38,6 @@ public:
bool validated = true;
};
/**
* @brief A struct to hold the input data for the command
*
* Clio does not support:
* - queue
*
* And the following are deprecated altogether:
* - full
* - accounts
* - ledger
* - type
*
* Clio will throw an error when `queue`, `full` or `accounts` is set to `true`.
* @see https://github.com/XRPLF/clio/issues/603 and https://github.com/XRPLF/clio/issues/1537
*/
struct Input {
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
bool binary = false;
bool expand = false;
bool ownerFunds = false;
bool transactions = false;
bool diff = false;
};
using Result = HandlerReturnType<Output>;
/**
@@ -84,35 +55,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(full), validation::Type<bool>{}, validation::NotSupported{true}},
{JS(full), check::Deprecated{}},
{JS(accounts), validation::Type<bool>{}, validation::NotSupported{true}},
{JS(accounts), check::Deprecated{}},
{JS(owner_funds), validation::Type<bool>{}},
{JS(queue), validation::Type<bool>{}, validation::NotSupported{true}},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(transactions), validation::Type<bool>{}},
{JS(expand), validation::Type<bool>{}},
{JS(binary), validation::Type<bool>{}},
{"diff", validation::Type<bool>{}},
{JS(ledger), check::Deprecated{}},
{JS(type), check::Deprecated{}},
};
return kRpcSpec;
}
/**
* @brief Process the Ledger command
*
@@ -132,14 +74,5 @@ 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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -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 <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <rpcspec/LedgerTypes.hpp>
#include <rpcspec/handlers/ledger_data/Types.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/LedgerFormats.h>
@@ -27,8 +25,10 @@
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>
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<xrpl::uint256>(&*input.marker) : nullptr;
auto const* diffMarker = input.marker ? std::get_if<uint32_t>(&*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<data::LedgerObject> 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<xrpl::uint256> 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<xrpl::uint256> 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<LedgerDataHandler::Input>, 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<uint32_t>(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<std::string>(jsonObject.at(JS(marker))).data()};
} else {
input.diffMarker = util::integralValueAs<uint32_t>(jsonObject.at(JS(marker)));
}
}
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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<std::string>(jv.at(JS(type))));
}
return input;
}
} // namespace rpc

View File

@@ -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 <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/ledger_data/Types.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <string>
#include <utility>
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<rpc::spec::handlers::ledger_data::Input> {
// dependencies
std::shared_ptr<BackendInterface> 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<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
bool binary = false;
uint32_t limit = LedgerDataHandler::kLimitJson; // max 256 for json ; 2048 for binary
std::optional<xrpl::uint256> marker;
std::optional<uint32_t> diffMarker;
bool outOfOrder = false;
xrpl::LedgerEntryType type = xrpl::LedgerEntryType::ltANY;
};
using Result = HandlerReturnType<Output>;
/**
@@ -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<bool>{}},
{"out_of_order", validation::Type<bool>{}},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(limit), validation::Type<uint32_t>{}, validation::Min(1u)},
{JS(marker),
validation::Type<uint32_t, std::string>{},
meta::IfType<std::string>{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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -1,266 +1,436 @@
#include "rpc/handlers/LedgerEntry.hpp"
#include "rpc/CredentialHelpers.hpp"
#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 <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <rpcspec/handlers/ledger_entry/Types.hpp>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/json/json_value.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <xrpl/protocol/SeqProxy.h>
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/jss.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <expected>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <set>
#include <utility>
#include <variant>
#include <vector>
namespace rpc {
namespace {
namespace le = rpc::spec::handlers::ledger_entry;
using Input = LedgerEntryHandler::Input;
/**
* @brief The ledger key a request resolves to, plus the entry type it must have.
*
* @c expectedType is @c ltANY when the key was computed from a precise keylet, since the
* type is then implied by construction and needs no verification after the read.
*/
struct Locator {
xrpl::uint256 key;
xrpl::LedgerEntryType expectedType = xrpl::ltANY;
};
/**
* @brief A resolved Locator, or the Status explaining why the request could not name one.
*/
using LocatorOrStatus = std::expected<Locator, Status>;
xrpl::STXChainBridge
makeBridge(le::BridgeSpec const& spec)
{
return xrpl::STXChainBridge{
spec.lockingChainDoor, spec.lockingChainIssue, spec.issuingChainDoor, spec.issuingChainIssue
};
}
/**
* @brief Resolve a locator field that accepts either a hex key or an entry object.
*
* A raw hex key only names the entry, so @p type is recorded and verified after the read.
* The object form computes a precise keylet via @p fromEntry and needs no verification.
*
* @param field The variant field to resolve
* @param type The entry type implied by the hex form
* @param fromEntry Computes the locator from the object form
* @return The resolved locator, or a Status describing why it could not be
*/
template <typename Entry, typename FromEntry>
LocatorOrStatus
locatorFrom(
std::variant<xrpl::uint256, Entry> const& field,
xrpl::LedgerEntryType type,
FromEntry&& fromEntry
)
{
if (auto const* hash = std::get_if<xrpl::uint256>(&field))
return Locator{.key = *hash, .expectedType = type};
return std::forward<FromEntry>(fromEntry)(std::get<Entry>(field));
}
/**
* @brief One kHexLocators entry: the Input field carrying the key, and the type it implies.
*/
struct HexLocator {
std::optional<xrpl::uint256> Input::* field;
xrpl::LedgerEntryType type;
};
/**
* @brief Locator fields that carry a ledger key directly, each implying its entry type.
*
* These are pure data: the field is the key, and naming the field names the type. Kept in
* request-field order so the search order matches the rest of resolveLocator().
*/
constexpr auto kHexLocators = std::to_array<HexLocator>({
{.field = &Input::check, .type = xrpl::ltCHECK},
{.field = &Input::paymentChannel, .type = xrpl::ltPAYCHAN},
{.field = &Input::nftPage, .type = xrpl::ltNFTOKEN_PAGE},
{.field = &Input::nftOffer, .type = xrpl::ltNFTOKEN_OFFER},
{.field = &Input::signerList, .type = xrpl::ltSIGNER_LIST},
{.field = &Input::amendments, .type = xrpl::ltAMENDMENTS},
{.field = &Input::fee, .type = xrpl::ltFEE_SETTINGS},
{.field = &Input::hashes, .type = xrpl::ltLEDGER_HASHES},
{.field = &Input::nunl, .type = xrpl::ltNEGATIVE_UNL},
});
LocatorOrStatus
directoryLocator(le::DirectoryEntry const& entry)
{
// xrpld folds these into a single "exactly one of" check; Clio reports them separately.
// This should be unified after xrpld is migrated to rpc-spec.
if (entry.dirRoot.has_value() and entry.owner.has_value()) {
return std::unexpected{
Status{RippledError::RpcInvalidParams, "mayNotSpecifyBothDirRootAndOwner"}
};
}
if (not entry.dirRoot.has_value() and not entry.owner.has_value())
return std::unexpected{Status{RippledError::RpcInvalidParams, "missingOwnerOrDirRoot"}};
auto const subIndex = entry.subIndex.value_or(0);
if (entry.dirRoot.has_value())
return Locator{.key = xrpl::keylet::page(*entry.dirRoot, subIndex).key};
return Locator{.key = xrpl::keylet::page(xrpl::keylet::ownerDir(*entry.owner), subIndex).key};
}
LocatorOrStatus
depositPreauthLocator(le::DepositPreauthEntry const& entry)
{
// Exactly one of authorized or authorized_credentials MUST exist.
if (entry.authorized.has_value() == entry.authorizedCredentials.has_value()) {
return std::unexpected{Status{
ClioError::RpcMalformedRequest, "Must have one of authorized or authorized_credentials."
}};
}
if (entry.authorized.has_value())
return Locator{.key = xrpl::keylet::depositPreauth(entry.owner, *entry.authorized).key};
std::set<std::pair<xrpl::AccountID, xrpl::Slice>> authCreds;
// Keep the decoded credential-type bytes alive while the Slices that reference them
// are used to build the keylet.
std::vector<xrpl::Blob> buffers;
buffers.reserve(entry.authorizedCredentials->size());
for (auto const& cred : *entry.authorizedCredentials) {
auto const decoded = xrpl::strUnHex(cred.credentialType);
ASSERT(decoded.has_value(), "credential_type is hex-validated by the spec");
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
buffers.push_back(*decoded);
authCreds.emplace(cred.issuer, xrpl::Slice(buffers.back().data(), buffers.back().size()));
}
if (authCreds.size() != entry.authorizedCredentials->size()) {
return std::unexpected{
Status{ClioError::RpcMalformedAuthorizedCredentials, "duplicates in credentials."}
};
}
return Locator{.key = xrpl::keylet::depositPreauth(entry.owner, authCreds).key};
}
LocatorOrStatus
credentialLocator(le::CredentialEntry const& entry)
{
auto const credType = xrpl::strUnHex(entry.credentialType);
ASSERT(credType.has_value(), "credential_type is hex-validated by the spec");
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
auto const credSlice = xrpl::Slice(credType->data(), credType->size());
return Locator{.key = xrpl::keylet::credential(entry.subject, entry.issuer, credSlice).key};
}
/**
* @brief Resolve the `bridge` locator, the only one needing a second request field.
*
* @param bridge The `bridge` field
* @param bridgeAccount The `bridge_account` field, which is required alongside it
* @return The resolved locator, or a Status describing why it could not be
*/
LocatorOrStatus
bridgeLocator(le::BridgeSpec const& bridge, std::optional<xrpl::AccountID> const& bridgeAccount)
{
if (not bridgeAccount.has_value())
return std::unexpected{Status{ClioError::RpcMalformedRequest}};
auto const stBridge = makeBridge(bridge);
auto const chainType =
xrpl::STXChainBridge::srcChain(*bridgeAccount == bridge.lockingChainDoor);
if (*bridgeAccount != stBridge.door(chainType))
return std::unexpected{Status{ClioError::RpcMalformedRequest}};
return Locator{.key = xrpl::keylet::bridge(stBridge, chainType).key};
}
/**
* @brief Work out which ledger key the request asks for.
*
* The spec admits one locator field per request; fields are searched in request-field order
* so that a malformed multi-locator request resolves deterministically.
*
* @param input The request input
* @param apiVersion The API version, which selects the error for a request with no locator
* @return The resolved locator, or a Status describing why it could not be
*/
LocatorOrStatus
resolveLocator(Input const& input, uint32_t apiVersion)
{
if (input.index.has_value()) {
// A raw key names no entry type, so nothing is verified after the read.
if (input.index->isZero())
return std::unexpected{Status{RippledError::RpcEntryNotFound}};
return Locator{.key = *input.index};
}
if (input.accountRoot.has_value())
return Locator{.key = xrpl::keylet::account(*input.accountRoot).key};
if (input.did.has_value())
return Locator{.key = xrpl::keylet::did(*input.did).key};
for (auto const& [field, type] : kHexLocators) {
if (auto const& hexKey = input.*field; hexKey.has_value())
return Locator{.key = *hexKey, .expectedType = type};
}
if (input.mptIssuance.has_value())
return Locator{.key = xrpl::keylet::mptokenIssuance(*input.mptIssuance).key};
if (input.directory.has_value())
return locatorFrom(*input.directory, xrpl::ltDIR_NODE, directoryLocator);
if (input.offer.has_value()) {
return locatorFrom(*input.offer, xrpl::ltOFFER, [](le::OfferEntry const& entry) {
return LocatorOrStatus{Locator{
.key =
xrpl::keylet::offer(entry.account, xrpl::SeqProxy::rawSequence(entry.seq)).key
}};
});
}
if (input.rippleStateAccount.has_value()) {
auto const& state = *input.rippleStateAccount;
return Locator{
.key = xrpl::keylet::trustLine(state.accounts[0], state.accounts[1], state.currency).key
};
}
if (input.escrow.has_value()) {
return locatorFrom(*input.escrow, xrpl::ltESCROW, [](le::EscrowEntry const& entry) {
return LocatorOrStatus{Locator{
.key = xrpl::keylet::escrow(entry.owner, xrpl::SeqProxy::rawSequence(entry.seq)).key
}};
});
}
if (input.depositPreauth.has_value())
return locatorFrom(*input.depositPreauth, xrpl::ltDEPOSIT_PREAUTH, depositPreauthLocator);
if (input.ticket.has_value()) {
return locatorFrom(*input.ticket, xrpl::ltTICKET, [](le::TicketEntry const& entry) {
return LocatorOrStatus{Locator{
.key =
xrpl::keylet::ticket(entry.account, xrpl::SeqProxy::rawTicket(entry.ticketSeq))
.key
}};
});
}
if (input.amm.has_value()) {
return locatorFrom(*input.amm, xrpl::ltAMM, [](le::AmmEntry const& entry) {
return LocatorOrStatus{
Locator{.key = xrpl::keylet::amm(entry.asset, entry.asset2).key}
};
});
}
if (input.bridge.has_value())
return bridgeLocator(*input.bridge, input.bridgeAccount);
if (input.xchainOwnedClaimId.has_value()) {
return locatorFrom(
*input.xchainOwnedClaimId,
xrpl::ltXCHAIN_OWNED_CLAIM_ID,
[](le::XChainClaimIdEntry const& entry) {
return LocatorOrStatus{Locator{
.key = xrpl::keylet::xChainClaimID(makeBridge(entry.bridge), entry.claimId).key
}};
}
);
}
if (input.xchainOwnedCreateAccountClaimId.has_value()) {
return locatorFrom(
*input.xchainOwnedCreateAccountClaimId,
xrpl::ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID,
[](le::XChainClaimIdEntry const& entry) {
return LocatorOrStatus{Locator{
.key = xrpl::keylet::xChainCreateAccountClaimID(
makeBridge(entry.bridge), entry.claimId
)
.key
}};
}
);
}
if (input.oracle.has_value()) {
return locatorFrom(*input.oracle, xrpl::ltORACLE, [](le::OracleEntry const& entry) {
return LocatorOrStatus{
Locator{.key = xrpl::keylet::oracle(entry.account, entry.oracleDocumentId).key}
};
});
}
if (input.credential.has_value())
return locatorFrom(*input.credential, xrpl::ltCREDENTIAL, credentialLocator);
if (input.mptoken.has_value()) {
return locatorFrom(*input.mptoken, xrpl::ltMPTOKEN, [](le::MptokenEntry const& entry) {
return LocatorOrStatus{
Locator{.key = xrpl::keylet::mptoken(entry.mptIssuanceId, entry.account).key}
};
});
}
if (input.permissionedDomain.has_value()) {
return locatorFrom(
*input.permissionedDomain,
xrpl::ltPERMISSIONED_DOMAIN,
[](le::PermissionedDomainEntry const& entry) {
return LocatorOrStatus{Locator{
.key = xrpl::keylet::permissionedDomain(
entry.account, xrpl::SeqProxy::rawSequence(entry.seq)
)
.key
}};
}
);
}
if (input.vault.has_value()) {
return locatorFrom(*input.vault, xrpl::ltVAULT, [](le::VaultEntry const& entry) {
return LocatorOrStatus{Locator{
.key = xrpl::keylet::vault(entry.owner, xrpl::SeqProxy::rawSequence(entry.seq)).key
}};
});
}
if (input.loanBroker.has_value()) {
return locatorFrom(
*input.loanBroker, xrpl::ltLOAN_BROKER, [](le::LoanBrokerEntry const& entry) {
return LocatorOrStatus{Locator{
.key = xrpl::keylet::loanBroker(
entry.owner, xrpl::SeqProxy::rawSequence(entry.seq)
)
.key
}};
}
);
}
if (input.loan.has_value()) {
return locatorFrom(*input.loan, xrpl::ltLOAN, [](le::LoanEntry const& entry) {
return LocatorOrStatus{Locator{
.key = xrpl::keylet::loan(
entry.loanBrokerId, xrpl::SeqProxy::rawSequence(entry.loanSeq)
)
.key
}};
});
}
if (input.delegate.has_value()) {
return locatorFrom(*input.delegate, xrpl::ltDELEGATE, [](le::DelegateEntry const& entry) {
return LocatorOrStatus{
Locator{.key = xrpl::keylet::delegate(entry.account, entry.authorize).key}
};
});
}
if (input.sponsorship.has_value()) {
return locatorFrom(
*input.sponsorship, xrpl::ltSPONSORSHIP, [](le::SponsorshipEntry const& entry) {
return LocatorOrStatus{
Locator{.key = xrpl::keylet::sponsorship(entry.sponsor, entry.sponsee).key}
};
}
);
}
if (apiVersion == 1u)
return std::unexpected{Status{ClioError::RpcUnknownOption}};
return std::unexpected{
Status{RippledError::RpcInvalidParams, "No ledger_entry params provided."}
};
}
} // namespace
LedgerEntryHandler::Result
LedgerEntryHandler::process(LedgerEntryHandler::Input const& input, Context const& ctx) const
{
xrpl::uint256 key;
auto const locator = resolveLocator(input, ctx.apiVersion);
if (not locator.has_value())
return Error{locator.error()};
if (input.index) {
key = xrpl::uint256{std::string_view(*(input.index))};
if (key.isZero())
return Error{Status{RippledError::RpcEntryNotFound}};
} else if (input.accountRoot) {
key = xrpl::keylet::account(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
*util::parseBase58Wrapper<xrpl::AccountID>(*(input.accountRoot))
)
.key;
} else if (input.did) {
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
key = xrpl::keylet::did(*util::parseBase58Wrapper<xrpl::AccountID>(*(input.did))).key;
} else if (input.directory) {
auto const expectedkey = composeKeyFromDirectory(*input.directory);
if (!expectedkey.has_value())
return Error{expectedkey.error()};
auto const key = locator->key;
auto const expectedType = locator->expectedType;
key = expectedkey.value(); // std::expected, not optional
} else if (input.offer) {
auto const id = util::parseBase58Wrapper<xrpl::AccountID>(
boost::json::value_to<std::string>(input.offer->at(JS(account)))
);
// NOLINTBEGIN(bugprone-unchecked-optional-access)
key = xrpl::keylet::offer(
*id,
xrpl::SeqProxy::rawSequence(
boost::json::value_to<std::uint32_t>(input.offer->at(JS(seq)))
)
)
.key;
// NOLINTEND(bugprone-unchecked-optional-access)
} else if (input.rippleStateAccount) {
auto const id1 =
util::parseBase58Wrapper<xrpl::AccountID>(boost::json::value_to<std::string>(
input.rippleStateAccount->at(JS(accounts)).as_array().at(0)
));
auto const id2 =
util::parseBase58Wrapper<xrpl::AccountID>(boost::json::value_to<std::string>(
input.rippleStateAccount->at(JS(accounts)).as_array().at(1)
));
auto const currency = xrpl::toCurrency(
boost::json::value_to<std::string>(input.rippleStateAccount->at(JS(currency)))
);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
key = xrpl::keylet::trustLine(*id1, *id2, currency).key;
} else if (input.escrow) {
auto const id = util::parseBase58Wrapper<xrpl::AccountID>(
boost::json::value_to<std::string>(input.escrow->at(JS(owner)))
);
key = xrpl::keylet::escrow(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
*id,
xrpl::SeqProxy::rawSequence(
util::integralValueAs<uint32_t>(input.escrow->at(JS(seq)))
)
)
.key;
} else if (input.depositPreauth) {
auto const owner = util::parseBase58Wrapper<xrpl::AccountID>(
boost::json::value_to<std::string>(input.depositPreauth->at(JS(owner)))
);
// Only one of authorize or authorized_credentials MUST exist;
if (input.depositPreauth->contains(JS(authorized)) ==
input.depositPreauth->contains(JS(authorized_credentials))) {
return Error{Status{
ClioError::RpcMalformedRequest,
"Must have one of authorized or authorized_credentials."
}};
}
if (input.depositPreauth->contains(JS(authorized))) {
auto const authorized = util::parseBase58Wrapper<xrpl::AccountID>(
boost::json::value_to<std::string>(input.depositPreauth->at(JS(authorized)))
);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
key = xrpl::keylet::depositPreauth(*owner, *authorized).key;
} else {
auto const authorizedCredentials = rpc::credentials::parseAuthorizeCredentials(
input.depositPreauth->at(JS(authorized_credentials)).as_array()
);
auto const authCreds = credentials::createAuthCredentials(authorizedCredentials);
if (authCreds.size() != authorizedCredentials.size()) {
return Error{Status{
ClioError::RpcMalformedAuthorizedCredentials, "duplicates in credentials."
}};
}
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
key = xrpl::keylet::depositPreauth(*owner, authCreds).key;
}
} else if (input.ticket) {
auto const id = util::parseBase58Wrapper<xrpl::AccountID>(
boost::json::value_to<std::string>(input.ticket->at(JS(account)))
);
key = xrpl::keylet::ticket(
*id, // NOLINT(bugprone-unchecked-optional-access)
xrpl::SeqProxy::rawTicket(
util::integralValueAs<uint32_t>(input.ticket->at(JS(ticket_seq)))
)
)
.key;
} else if (input.amm) {
auto const getIssuerFromJson = [](auto const& assetJson) {
// the field check has been done in validator
auto const currency =
xrpl::toCurrency(boost::json::value_to<std::string>(assetJson.at(JS(currency))));
if (xrpl::isXRP(currency)) {
return xrpl::xrpIssue();
}
auto const issuer = util::parseBase58Wrapper<xrpl::AccountID>(
boost::json::value_to<std::string>(assetJson.at(JS(issuer)))
);
return xrpl::Issue{currency, *issuer};
};
key = xrpl::keylet::amm(
getIssuerFromJson(input.amm->at(JS(asset))),
getIssuerFromJson(input.amm->at(JS(asset2)))
)
.key;
} else if (input.bridge) {
if (!input.bridgeAccount && !input.chainClaimId && !input.createAccountClaimId)
return Error{Status{ClioError::RpcMalformedRequest}};
if (input.bridgeAccount) {
auto const bridgeAccount =
util::parseBase58Wrapper<xrpl::AccountID>(*(input.bridgeAccount));
auto const chainType =
xrpl::STXChainBridge::srcChain(bridgeAccount == input.bridge->lockingChainDoor());
if (bridgeAccount != input.bridge->door(chainType))
return Error{Status{ClioError::RpcMalformedRequest}};
key = xrpl::keylet::bridge(input.bridge->value(), chainType).key;
} else if (input.chainClaimId) {
key = xrpl::keylet::xChainClaimID(input.bridge->value(), *input.chainClaimId).key;
} else {
key = xrpl::keylet::xChainCreateAccountClaimID(
input.bridge->value(), *input.createAccountClaimId
)
.key;
}
} else if (input.oracleNode) {
key = *input.oracleNode;
} else if (input.credential) {
key = *input.credential;
} else if (input.mptIssuance) {
auto const mptIssuanceID = xrpl::uint192{std::string_view(*(input.mptIssuance))};
key = xrpl::keylet::mptokenIssuance(mptIssuanceID).key;
} else if (input.mptoken) {
auto const holder = xrpl::parseBase58<xrpl::AccountID>(
boost::json::value_to<std::string>(input.mptoken->at(JS(account)))
);
auto const mptIssuanceID = xrpl::uint192{std::string_view(
boost::json::value_to<std::string>(input.mptoken->at(JS(mpt_issuance_id)))
)};
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
key = xrpl::keylet::mptoken(mptIssuanceID, *holder).key;
} else if (input.permissionedDomain) {
auto const account = xrpl::parseBase58<xrpl::AccountID>(
boost::json::value_to<std::string>(input.permissionedDomain->at(JS(account)))
);
auto const seq = util::integralValueAs<uint32_t>(input.permissionedDomain->at(JS(seq)));
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
key = xrpl::keylet::permissionedDomain(*account, xrpl::SeqProxy::rawSequence(seq)).key;
} else if (input.vault) {
auto const account = xrpl::parseBase58<xrpl::AccountID>(
boost::json::value_to<std::string>(input.vault->at(JS(owner)))
);
auto const seq = util::integralValueAs<uint32_t>(input.vault->at(JS(seq)));
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
key = xrpl::keylet::vault(*account, xrpl::SeqProxy::rawSequence(seq)).key;
} else if (input.loanBroker) {
auto const account = xrpl::parseBase58<xrpl::AccountID>(
boost::json::value_to<std::string>(input.loanBroker->at(JS(owner)))
);
auto const seq = util::integralValueAs<uint32_t>(input.loanBroker->at(JS(seq)));
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
key = xrpl::keylet::loanBroker(*account, xrpl::SeqProxy::rawSequence(seq)).key;
} else if (input.loan) {
auto const id = xrpl::uint256{
boost::json::value_to<std::string>(input.loan->at(JS(loan_broker_id))).data()
};
auto const seq = util::integralValueAs<uint32_t>(input.loan->at(JS(loan_seq)));
key = xrpl::keylet::loan(id, xrpl::SeqProxy::rawSequence(seq)).key;
} else if (input.delegate) {
auto const account = xrpl::parseBase58<xrpl::AccountID>(
boost::json::value_to<std::string>(input.delegate->at(JS(account)))
);
auto const authorize = xrpl::parseBase58<xrpl::AccountID>(
boost::json::value_to<std::string>(input.delegate->at(JS(authorize)))
);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
key = xrpl::keylet::delegate(*account, *authorize).key;
} else {
// Must specify 1 of the following fields to indicate what type
if (ctx.apiVersion == 1)
return Error{Status{ClioError::RpcUnknownOption}};
return Error{Status{RippledError::RpcInvalidParams, "No ledger_entry params provided."}};
}
// check ledger exists
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "LedgerEntry'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)
);
@@ -271,15 +441,15 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input const& input, Context cons
auto output = LedgerEntryHandler::Output{};
auto ledgerObject = sharedPtrBackend_->fetchLedgerObject(key, lgrInfo.seq, ctx.yield);
if (!ledgerObject || ledgerObject->empty()) {
if (not ledgerObject.has_value() or ledgerObject->empty()) {
if (not input.includeDeleted)
return Error{Status{RippledError::RpcEntryNotFound}};
auto const deletedSeq =
sharedPtrBackend_->fetchLedgerObjectSeq(key, lgrInfo.seq, ctx.yield);
if (!deletedSeq)
if (not deletedSeq.has_value())
return Error{Status{RippledError::RpcEntryNotFound}};
ledgerObject = sharedPtrBackend_->fetchLedgerObject(key, *deletedSeq - 1, ctx.yield);
if (!ledgerObject || ledgerObject->empty())
if (not ledgerObject.has_value() or ledgerObject->empty())
return Error{Status{RippledError::RpcEntryNotFound}};
output.deletedLedgerIndex = deletedSeq;
}
@@ -288,7 +458,7 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input const& input, Context cons
xrpl::SerialIter{ledgerObject->data(), ledgerObject->size()}, key
};
if (input.expectedType != xrpl::ltANY && sle.getType() != input.expectedType)
if (expectedType != xrpl::ltANY && sle.getType() != expectedType)
return Error{Status{RippledError::RpcUnexpectedLedgerType}};
output.index = xrpl::strHex(key);
@@ -304,38 +474,6 @@ LedgerEntryHandler::process(LedgerEntryHandler::Input const& input, Context cons
return output;
}
std::expected<xrpl::uint256, Status>
LedgerEntryHandler::composeKeyFromDirectory(boost::json::object const& directory) noexcept
{
// can not specify both dir_root and owner.
if (directory.contains(JS(dir_root)) && directory.contains(JS(owner))) {
return std::unexpected{
Status{RippledError::RpcInvalidParams, "mayNotSpecifyBothDirRootAndOwner"}
};
}
// at least one should available
if (!(directory.contains(JS(dir_root)) || directory.contains(JS(owner))))
return std::unexpected{Status{RippledError::RpcInvalidParams, "missingOwnerOrDirRoot"}};
uint64_t const subIndex = directory.contains(JS(sub_index))
? boost::json::value_to<uint64_t>(directory.at(JS(sub_index)))
: 0;
if (directory.contains(JS(dir_root))) {
xrpl::uint256 const uDirRoot{
boost::json::value_to<std::string>(directory.at(JS(dir_root))).data()
};
return xrpl::keylet::page(uDirRoot, subIndex).key;
}
auto const ownerID = util::parseBase58Wrapper<xrpl::AccountID>(
boost::json::value_to<std::string>(directory.at(JS(owner)))
);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
return xrpl::keylet::page(xrpl::keylet::ownerDir(*ownerID), subIndex).key;
}
void
tag_invoke(
boost::json::value_from_tag,
@@ -350,10 +488,10 @@ tag_invoke(
{JS(index), output.index},
};
if (output.deletedLedgerIndex)
if (output.deletedLedgerIndex.has_value())
object["deleted_ledger_index"] = *(output.deletedLedgerIndex);
if (output.nodeBinary) {
if (output.nodeBinary.has_value()) {
object[JS(node_binary)] = *(output.nodeBinary);
} else {
object[JS(node)] = *(output.node); // NOLINT(bugprone-unchecked-optional-access)
@@ -362,168 +500,4 @@ tag_invoke(
jv = std::move(object);
}
LedgerEntryHandler::Input
tag_invoke(boost::json::value_to_tag<LedgerEntryHandler::Input>, boost::json::value const& jv)
{
auto input = LedgerEntryHandler::Input{};
auto const& jsonObject = jv.as_object();
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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(binary)))
input.binary = jv.at(JS(binary)).as_bool();
// check all the potential index
static auto const kIndexFieldTypeMap = std::unordered_map<std::string, xrpl::LedgerEntryType>{
{JS(index), xrpl::ltANY},
{JS(directory), xrpl::ltDIR_NODE},
{JS(offer), xrpl::ltOFFER},
{JS(check), xrpl::ltCHECK},
{JS(escrow), xrpl::ltESCROW},
{JS(payment_channel), xrpl::ltPAYCHAN},
{JS(deposit_preauth), xrpl::ltDEPOSIT_PREAUTH},
{JS(ticket), xrpl::ltTICKET},
{JS(nft_page), xrpl::ltNFTOKEN_PAGE},
{JS(amm), xrpl::ltAMM},
{JS(xchain_owned_create_account_claim_id), xrpl::ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID},
{JS(xchain_owned_claim_id), xrpl::ltXCHAIN_OWNED_CLAIM_ID},
{JS(oracle), xrpl::ltORACLE},
{JS(credential), xrpl::ltCREDENTIAL},
{JS(mptoken), xrpl::ltMPTOKEN},
{JS(permissioned_domain), xrpl::ltPERMISSIONED_DOMAIN},
{JS(vault), xrpl::ltVAULT},
{JS(loan_broker), xrpl::ltLOAN_BROKER},
{JS(loan), xrpl::ltLOAN},
{JS(delegate), xrpl::ltDELEGATE},
{JS(amendments), xrpl::ltAMENDMENTS},
{JS(fee), xrpl::ltFEE_SETTINGS},
{JS(hashes), xrpl::ltLEDGER_HASHES},
{JS(nft_offer), xrpl::ltNFTOKEN_OFFER},
{JS(nunl), xrpl::ltNEGATIVE_UNL},
{JS(signer_list), xrpl::ltSIGNER_LIST},
};
auto const parseBridgeFromJson = [](boost::json::value const& bridgeJson) {
auto const lockingDoor =
*util::parseBase58Wrapper<xrpl::AccountID>(boost::json::value_to<std::string>(
bridgeJson.at(xrpl::sfLockingChainDoor.getJsonName().cStr())
));
auto const issuingDoor =
*util::parseBase58Wrapper<xrpl::AccountID>(boost::json::value_to<std::string>(
bridgeJson.at(xrpl::sfIssuingChainDoor.getJsonName().cStr())
));
auto const lockingIssue =
parseIssue(bridgeJson.at(xrpl::sfLockingChainIssue.getJsonName().cStr()).as_object());
auto const issuingIssue =
parseIssue(bridgeJson.at(xrpl::sfIssuingChainIssue.getJsonName().cStr()).as_object());
return xrpl::STXChainBridge{lockingDoor, lockingIssue, issuingDoor, issuingIssue};
};
auto const parseOracleFromJson = [](boost::json::value const& json) {
auto const account = util::parseBase58Wrapper<xrpl::AccountID>(
boost::json::value_to<std::string>(json.at(JS(account)))
);
auto const documentId = boost::json::value_to<uint32_t>(json.at(JS(oracle_document_id)));
return xrpl::keylet::oracle(*account, documentId).key;
};
auto const parseCredentialFromJson =
[](boost::json::value const& json) -> std::optional<xrpl::uint256> {
auto const subject = util::parseBase58Wrapper<xrpl::AccountID>(
boost::json::value_to<std::string>(json.at(JS(subject)))
);
auto const issuer = util::parseBase58Wrapper<xrpl::AccountID>(
boost::json::value_to<std::string>(json.at(JS(issuer)))
);
auto const credTypeOpt =
xrpl::strUnHex(boost::json::value_to<std::string>(json.at(JS(credential_type))));
return credTypeOpt.transform([&](xrpl::Blob const& credType) -> xrpl::uint256 {
return xrpl::keylet::credential(
*subject, *issuer, xrpl::Slice(credType.data(), credType.size())
)
.key;
});
};
auto const indexFieldType =
std::ranges::find_if(kIndexFieldTypeMap, [&jsonObject](auto const& pair) {
auto const& [field, _] = pair;
return jsonObject.contains(field) && jsonObject.at(field).is_string();
});
if (indexFieldType != kIndexFieldTypeMap.end()) {
input.index = boost::json::value_to<std::string>(jv.at(indexFieldType->first));
input.expectedType = indexFieldType->second;
}
// check if request for account root
else if (jsonObject.contains(JS(account_root))) {
input.accountRoot = boost::json::value_to<std::string>(jv.at(JS(account_root)));
} else if (jsonObject.contains(JS(did))) {
input.did = boost::json::value_to<std::string>(jv.at(JS(did)));
} else if (jsonObject.contains(JS(mpt_issuance))) {
input.mptIssuance = boost::json::value_to<std::string>(jv.at(JS(mpt_issuance)));
}
// no need to check if_object again, validator only allows string or object
else if (jsonObject.contains(JS(directory))) {
input.directory = jv.at(JS(directory)).as_object();
} else if (jsonObject.contains(JS(offer))) {
input.offer = jv.at(JS(offer)).as_object();
} else if (jsonObject.contains(JS(ripple_state))) {
input.rippleStateAccount = jv.at(JS(ripple_state)).as_object();
} else if (jsonObject.contains(JS(escrow))) {
input.escrow = jv.at(JS(escrow)).as_object();
} else if (jsonObject.contains(JS(deposit_preauth))) {
input.depositPreauth = jv.at(JS(deposit_preauth)).as_object();
} else if (jsonObject.contains(JS(ticket))) {
input.ticket = jv.at(JS(ticket)).as_object();
} else if (jsonObject.contains(JS(amm))) {
input.amm = jv.at(JS(amm)).as_object();
} else if (jsonObject.contains(JS(bridge))) {
input.bridge.emplace(parseBridgeFromJson(jv.at(JS(bridge))));
if (jsonObject.contains(JS(bridge_account)))
input.bridgeAccount = boost::json::value_to<std::string>(jv.at(JS(bridge_account)));
} else if (jsonObject.contains(JS(xchain_owned_claim_id))) {
input.bridge.emplace(parseBridgeFromJson(jv.at(JS(xchain_owned_claim_id))));
input.chainClaimId = boost::json::value_to<std::int32_t>(
jv.at(JS(xchain_owned_claim_id)).at(JS(xchain_owned_claim_id))
);
} else if (jsonObject.contains(JS(xchain_owned_create_account_claim_id))) {
input.bridge.emplace(parseBridgeFromJson(jv.at(JS(xchain_owned_create_account_claim_id))));
input.createAccountClaimId =
boost::json::value_to<std::int32_t>(jv.at(JS(xchain_owned_create_account_claim_id))
.at(JS(xchain_owned_create_account_claim_id)));
} else if (jsonObject.contains(JS(oracle))) {
input.oracleNode = parseOracleFromJson(jv.at(JS(oracle)));
} else if (jsonObject.contains(JS(credential))) {
input.credential = parseCredentialFromJson(jv.at(JS(credential)));
} else if (jsonObject.contains(JS(mptoken))) {
input.mptoken = jv.at(JS(mptoken)).as_object();
} else if (jsonObject.contains(JS(permissioned_domain))) {
input.permissionedDomain = jv.at(JS(permissioned_domain)).as_object();
} else if (jsonObject.contains(JS(vault))) {
input.vault = jv.at(JS(vault)).as_object();
} else if (jsonObject.contains(JS(loan_broker))) {
input.loanBroker = jv.at(JS(loan_broker)).as_object();
} else if (jsonObject.contains(JS(loan))) {
input.loan = jv.at(JS(loan)).as_object();
} else if (jsonObject.contains(JS(delegate))) {
input.delegate = jv.at(JS(delegate)).as_object();
}
if (jsonObject.contains("include_deleted"))
input.includeDeleted = jv.at("include_deleted").as_bool();
return input;
}
} // namespace rpc

View File

@@ -1,38 +1,18 @@
#pragma once
#include "data/BackendInterface.hpp"
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Checkers.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/AccountUtils.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/beast/core/LexicalCast.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STXChainBridge.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol/tokens.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/ledger_entry/Types.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <variant>
#include <utility>
namespace rpc {
@@ -42,7 +22,7 @@ namespace rpc {
*
* For more details see: https://xrpl.org/ledger_entry.html
*/
class LedgerEntryHandler {
class LedgerEntryHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::ledger_entry::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
@@ -59,47 +39,6 @@ public:
bool validated = true;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
bool binary = false;
// id of this ledger entry: 256 bits hex string
std::optional<std::string> index;
// index can be extracted from payment_channel, check, escrow, offer
// etc, expectedType is used to save the type of index
xrpl::LedgerEntryType expectedType = xrpl::ltANY;
// account id to address account root object
std::optional<std::string> accountRoot;
// account id to address did object
std::optional<std::string> did;
// mpt issuance id to address mptIssuance object
std::optional<std::string> mptIssuance;
// TODO: extract into custom objects, remove json from Input
std::optional<boost::json::object> directory;
std::optional<boost::json::object> offer;
std::optional<boost::json::object> rippleStateAccount;
std::optional<boost::json::object> escrow;
std::optional<boost::json::object> depositPreauth;
std::optional<boost::json::object> ticket;
std::optional<boost::json::object> amm;
std::optional<boost::json::object> mptoken;
std::optional<boost::json::object> permissionedDomain;
std::optional<boost::json::object> vault;
std::optional<boost::json::object> loanBroker;
std::optional<boost::json::object> loan;
std::optional<xrpl::STXChainBridge> bridge;
std::optional<std::string> bridgeAccount;
std::optional<uint32_t> chainClaimId;
std::optional<uint32_t> createAccountClaimId;
std::optional<xrpl::uint256> oracleNode;
std::optional<xrpl::uint256> credential;
std::optional<boost::json::object> delegate;
bool includeDeleted = false;
};
using Result = HandlerReturnType<Output>;
/**
@@ -112,449 +51,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)
{
// Validator only works in this handler
// The accounts array must have two different elements
// Each element must be a valid address
static auto const kRippleStateAccountsCheck = validation::CustomValidator{
[](boost::json::value const& value, std::string_view /* key */) -> MaybeError {
if (!value.is_array() || value.as_array().size() != 2 ||
!value.as_array()[0].is_string() || !value.as_array()[1].is_string() ||
value.as_array()[0].as_string() == value.as_array()[1].as_string()) {
return Error{Status{RippledError::RpcInvalidParams, "malformedAccounts"}};
}
auto const id1 = util::parseBase58Wrapper<xrpl::AccountID>(
boost::json::value_to<std::string>(value.as_array()[0])
);
auto const id2 = util::parseBase58Wrapper<xrpl::AccountID>(
boost::json::value_to<std::string>(value.as_array()[1])
);
if (!id1 || !id2)
return Error{Status{ClioError::RpcMalformedAddress, "malformedAddresses"}};
return MaybeError{};
}
};
static auto const kMalformedRequestHexStringValidator = meta::WithCustomError{
validation::CustomValidators::uint256HexStringValidator,
Status(ClioError::RpcMalformedRequest)
};
static auto const kMalformedRequestIntValidator = meta::WithCustomError{
validation::Type<uint32_t>{}, Status(ClioError::RpcMalformedRequest)
};
static auto const kBridgeJsonValidator = meta::WithCustomError{
meta::IfType<boost::json::object>{meta::Section{
{xrpl::sfLockingChainDoor.getJsonName().cStr(),
validation::Required{},
validation::CustomValidators::accountBase58Validator},
{xrpl::sfIssuingChainDoor.getJsonName().cStr(),
validation::Required{},
validation::CustomValidators::accountBase58Validator},
{xrpl::sfLockingChainIssue.getJsonName().cStr(),
validation::Required{},
validation::CustomValidators::currencyIssueValidator},
{xrpl::sfIssuingChainIssue.getJsonName().cStr(),
validation::Required{},
validation::CustomValidators::currencyIssueValidator},
}},
Status(ClioError::RpcMalformedRequest)
};
static auto const kRpcSpec = RpcSpec{
{JS(binary), validation::Type<bool>{}},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(index), kMalformedRequestHexStringValidator},
{JS(account_root), validation::CustomValidators::accountBase58Validator},
{JS(did), validation::CustomValidators::accountBase58Validator},
{JS(check), kMalformedRequestHexStringValidator},
{JS(deposit_preauth),
validation::Type<std::string, boost::json::object>{},
meta::IfType<std::string>{kMalformedRequestHexStringValidator},
meta::IfType<boost::json::object>{
meta::Section{
{JS(owner),
validation::Required{},
meta::WithCustomError{
validation::CustomValidators::accountBase58Validator,
Status(ClioError::RpcMalformedOwner)
}},
{JS(authorized), validation::CustomValidators::accountBase58Validator},
{JS(authorized_credentials),
validation::CustomValidators::authorizeCredentialValidator}
},
}},
{JS(directory),
validation::Type<std::string, boost::json::object>{},
meta::IfType<std::string>{kMalformedRequestHexStringValidator},
meta::IfType<boost::json::object>{meta::Section{
{JS(owner), validation::CustomValidators::accountBase58Validator},
{JS(dir_root), validation::CustomValidators::uint256HexStringValidator},
{JS(sub_index), kMalformedRequestIntValidator}
}}},
{JS(escrow),
validation::Type<std::string, boost::json::object>{},
meta::IfType<std::string>{kMalformedRequestHexStringValidator},
meta::IfType<boost::json::object>{
meta::Section{
{JS(owner),
validation::Required{},
meta::WithCustomError{
validation::CustomValidators::accountBase58Validator,
Status(ClioError::RpcMalformedOwner)
}},
{JS(seq), validation::Required{}, kMalformedRequestIntValidator},
},
}},
{JS(offer),
validation::Type<std::string, boost::json::object>{},
meta::IfType<std::string>{kMalformedRequestHexStringValidator},
meta::IfType<boost::json::object>{
meta::Section{
{JS(account),
validation::Required{},
validation::CustomValidators::accountBase58Validator},
{JS(seq), validation::Required{}, kMalformedRequestIntValidator},
},
}},
{JS(payment_channel), kMalformedRequestHexStringValidator},
{JS(ripple_state),
validation::Type<boost::json::object>{},
meta::Section{
{JS(accounts), validation::Required{}, kRippleStateAccountsCheck},
{JS(currency),
validation::Required{},
validation::CustomValidators::currencyValidator},
}},
{JS(ticket),
validation::Type<std::string, boost::json::object>{},
meta::IfType<std::string>{kMalformedRequestHexStringValidator},
meta::IfType<boost::json::object>{
meta::Section{
{JS(account),
validation::Required{},
validation::CustomValidators::accountBase58Validator},
{JS(ticket_seq), validation::Required{}, kMalformedRequestIntValidator},
},
}},
{JS(nft_page), kMalformedRequestHexStringValidator},
{JS(amm),
validation::Type<std::string, boost::json::object>{},
meta::IfType<std::string>{kMalformedRequestHexStringValidator},
meta::IfType<boost::json::object>{
meta::Section{
{JS(asset),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::Type<boost::json::object>{},
Status(ClioError::RpcMalformedRequest)
},
validation::CustomValidators::currencyIssueValidator},
{JS(asset2),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::Type<boost::json::object>{},
Status(ClioError::RpcMalformedRequest)
},
validation::CustomValidators::currencyIssueValidator},
},
}},
{JS(bridge),
meta::WithCustomError{
validation::Type<boost::json::object>{}, Status(ClioError::RpcMalformedRequest)
},
kBridgeJsonValidator},
{JS(bridge_account),
meta::WithCustomError{
validation::CustomValidators::accountBase58Validator,
Status(ClioError::RpcMalformedRequest)
}},
{JS(xchain_owned_claim_id),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{},
Status(ClioError::RpcMalformedRequest)
},
meta::IfType<std::string>{kMalformedRequestHexStringValidator},
kBridgeJsonValidator,
meta::WithCustomError{
meta::IfType<boost::json::object>{meta::Section{
{JS(xchain_owned_claim_id),
validation::Required{},
validation::Type<uint32_t>{}}
}},
Status(ClioError::RpcMalformedRequest)
}},
{JS(xchain_owned_create_account_claim_id),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{},
Status(ClioError::RpcMalformedRequest)
},
meta::IfType<std::string>{kMalformedRequestHexStringValidator},
kBridgeJsonValidator,
meta::WithCustomError{
meta::IfType<boost::json::object>{meta::Section{
{JS(xchain_owned_create_account_claim_id),
validation::Required{},
validation::Type<uint32_t>{}}
}},
Status(ClioError::RpcMalformedRequest)
}},
{JS(oracle),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{},
Status(ClioError::RpcMalformedRequest)
},
meta::IfType<std::string>{meta::WithCustomError{
kMalformedRequestHexStringValidator, Status(ClioError::RpcMalformedAddress)
}},
meta::IfType<boost::json::object>{meta::Section{
{JS(account),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::CustomValidators::accountBase58Validator,
Status(ClioError::RpcMalformedAddress)
}},
// note: Unlike `rippled`, Clio only supports UInt as input, no string, no `null`,
// etc.:
{JS(oracle_document_id),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::Type<uint32_t, std::string>{},
Status(ClioError::RpcMalformedOracleDocumentId)
},
meta::WithCustomError{
modifiers::ToNumber{}, Status(ClioError::RpcMalformedOracleDocumentId)
}},
}}},
{JS(credential),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{},
Status(ClioError::RpcMalformedRequest)
},
meta::IfType<std::string>{meta::WithCustomError{
kMalformedRequestHexStringValidator, Status(ClioError::RpcMalformedAddress)
}},
meta::IfType<boost::json::object>{meta::Section{
{JS(subject),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::CustomValidators::accountBase58Validator,
Status(ClioError::RpcMalformedAddress)
}},
{JS(issuer),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::CustomValidators::accountBase58Validator,
Status(ClioError::RpcMalformedAddress)
}},
{
JS(credential_type),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::Type<std::string>{}, Status(ClioError::RpcMalformedRequest)
},
validation::CustomValidators::credentialTypeValidator,
},
}}},
{JS(mpt_issuance),
meta::WithCustomError{
validation::CustomValidators::uint192HexStringValidator,
Status(ClioError::RpcMalformedRequest)
}},
{JS(mptoken),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{},
Status(ClioError::RpcMalformedRequest)
},
meta::IfType<std::string>{kMalformedRequestHexStringValidator},
meta::IfType<boost::json::object>{
meta::Section{
{
JS(account),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::CustomValidators::accountBase58Validator,
Status(ClioError::RpcMalformedAddress)
},
},
{
JS(mpt_issuance_id),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::CustomValidators::uint192HexStringValidator,
Status(ClioError::RpcMalformedRequest)
},
},
},
}},
{JS(permissioned_domain),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{},
Status(ClioError::RpcMalformedRequest)
},
meta::IfType<std::string>{kMalformedRequestHexStringValidator},
meta::IfType<boost::json::object>{meta::Section{
{JS(seq),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::Type<uint32_t>{}, Status(ClioError::RpcMalformedRequest)
}},
{
JS(account),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::CustomValidators::accountBase58Validator,
Status(ClioError::RpcMalformedAddress)
},
},
}}},
{JS(vault),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{},
Status(ClioError::RpcMalformedRequest)
},
meta::IfType<std::string>{kMalformedRequestHexStringValidator},
meta::IfType<boost::json::object>{meta::Section{
{JS(seq),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::Type<uint32_t>{}, Status(ClioError::RpcMalformedRequest)
}},
{
JS(owner),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::CustomValidators::accountBase58Validator,
Status(ClioError::RpcMalformedOwner)
},
},
}}},
{JS(loan_broker),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{},
Status(ClioError::RpcMalformedRequest)
},
meta::IfType<std::string>{kMalformedRequestHexStringValidator},
meta::IfType<boost::json::object>{meta::Section{
{JS(seq),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::Type<uint32_t>{}, Status(ClioError::RpcMalformedRequest)
}},
{
JS(owner),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::CustomValidators::accountBase58Validator,
Status(ClioError::RpcMalformedOwner)
},
},
}}},
{JS(loan),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{},
Status(ClioError::RpcMalformedRequest)
},
meta::IfType<std::string>{kMalformedRequestHexStringValidator},
meta::IfType<boost::json::object>{meta::Section{
{JS(loan_seq),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::Type<uint32_t>{}, Status(ClioError::RpcMalformedRequest)
}},
{
JS(loan_broker_id),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::CustomValidators::uint256HexStringValidator,
Status(ClioError::RpcMalformedRequest)
},
},
}}},
{JS(delegate),
meta::WithCustomError{
validation::Type<std::string, boost::json::object>{},
Status(ClioError::RpcMalformedRequest)
},
meta::IfType<std::string>{kMalformedRequestHexStringValidator},
meta::IfType<boost::json::object>{meta::Section{
{JS(account),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::CustomValidators::accountBase58Validator,
Status(ClioError::RpcMalformedAddress)
}},
{JS(authorize),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcMalformedRequest)
},
meta::WithCustomError{
validation::CustomValidators::accountBase58Validator,
Status(ClioError::RpcMalformedAddress)
}}
}}},
{JS(amendments), kMalformedRequestHexStringValidator},
{JS(fee), kMalformedRequestHexStringValidator},
{JS(hashes), kMalformedRequestHexStringValidator},
{JS(nft_offer), kMalformedRequestHexStringValidator},
{JS(nunl), kMalformedRequestHexStringValidator},
{JS(signer_list), kMalformedRequestHexStringValidator},
{JS(ledger), check::Deprecated{}},
{"include_deleted", validation::Type<bool>{}},
};
return kRpcSpec;
}
/**
* @brief Process the LedgerEntry command
*
@@ -566,11 +62,6 @@ public:
process(Input const& input, Context const& ctx) const;
private:
// dir_root and owner can not be both empty or filled at the same time
// This function will return an error if this is the case
static std::expected<xrpl::uint256, Status>
composeKeyFromDirectory(boost::json::object const& directory) noexcept;
/**
* @brief Convert the Output to a JSON object
*
@@ -579,15 +70,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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -41,13 +41,7 @@ LedgerIndexHandler::process(LedgerIndexHandler::Input const& input, Context cons
if (!input.date)
return fillOutputByIndex(maxIndex);
auto const convertISOTimeStrToTicks = [](std::string const& isoTimeStr) {
auto const systemTime = util::systemTpFromUtcStr(isoTimeStr, kDateFormat);
// systemTime must be valid after validation passed
return systemTime->time_since_epoch().count();
};
auto const ticks = convertISOTimeStrToTicks(*input.date);
auto const ticks = input.date->time_since_epoch().count();
auto const earlierThan = [&](std::uint32_t ledgerIndex) {
auto const header = sharedPtrBackend_->fetchLedgerBySequence(ledgerIndex, ctx.yield);
@@ -75,17 +69,6 @@ LedgerIndexHandler::process(LedgerIndexHandler::Input const& input, Context cons
return fillOutputByIndex(maxIndex);
}
LedgerIndexHandler::Input
tag_invoke(boost::json::value_to_tag<LedgerIndexHandler::Input>, boost::json::value const& jv)
{
auto input = LedgerIndexHandler::Input{};
if (jv.as_object().contains(JS(date)))
input.date = jv.at(JS(date)).as_string();
return input;
}
void
tag_invoke(
boost::json::value_from_tag,

View File

@@ -1,19 +1,17 @@
#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 <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/ledger_index/Types.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
namespace rpc {
@@ -21,9 +19,8 @@ namespace rpc {
* @brief The ledger_index method fetches the latest closed ledger before the given date.
*
*/
class LedgerIndexHandler {
class LedgerIndexHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::ledger_index::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
static constexpr auto kDateFormat = "%Y-%m-%dT%TZ";
public:
/**
@@ -35,13 +32,6 @@ public:
std::string closeTimeIso;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::optional<std::string> date;
};
using Result = HandlerReturnType<Output>;
/**
@@ -54,23 +44,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(date),
validation::Type<std::string>{},
validation::TimeFormatValidator{kDateFormat}},
};
return kRpcSpec;
}
/**
* @brief Process the LedgerIndex command
*
@@ -90,15 +63,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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -1,10 +1,10 @@
#include "rpc/handlers/MPTHolders.hpp"
#include "data/Types.hpp"
#include "rpc/JS.hpp"
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
@@ -22,25 +22,96 @@
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <optional>
#include <string>
#include <algorithm>
#include <vector>
using namespace xrpl;
namespace rpc {
namespace {
/**
* @brief Serialize a single MPToken ledger object blob into the mpt_holders JSON.
*
* @param mptID The MPTokenIssuance ID the holder belongs to.
* @param mpt The serialized MPToken ledger object.
* @return The holder entry as a JSON object.
*/
boost::json::object
mpTokenToJson(xrpl::uint192 const& mptID, data::Blob const& mpt)
{
xrpl::STLedgerEntry const sle{xrpl::SerialIter{mpt.data(), mpt.size()}, xrpl::uint256{}};
auto const mptokenKey = keylet::mptoken(mptID, sle[xrpl::sfAccount]).key;
boost::json::object mptJson;
mptJson[JS(account)] = toBase58(sle[xrpl::sfAccount]);
mptJson[JS(flags)] = sle.getFlags();
mptJson[JS(mpt_amount)] = toBoostJson(
xrpl::STUInt64{xrpl::sfMPTAmount, sle[xrpl::sfMPTAmount]}.getJson(JsonOptions::Values::None)
);
mptJson[JS(mptoken_index)] = xrpl::to_string(mptokenKey);
if (sle.isFieldPresent(xrpl::sfLockedAmount)) {
mptJson["locked_amount"] = toBoostJson(
xrpl::STUInt64{xrpl::sfLockedAmount, sle[xrpl::sfLockedAmount]}.getJson(
JsonOptions::Values::None
)
);
}
if (sle.isFieldPresent(xrpl::sfConfidentialBalanceInbox)) {
mptJson[JS(confidential_balance_inbox)] =
xrpl::strHex(sle.getFieldVL(xrpl::sfConfidentialBalanceInbox));
}
if (sle.isFieldPresent(xrpl::sfConfidentialBalanceSpending)) {
mptJson[JS(confidential_balance_spending)] =
xrpl::strHex(sle.getFieldVL(xrpl::sfConfidentialBalanceSpending));
}
if (sle.isFieldPresent(xrpl::sfConfidentialBalanceVersion))
mptJson[JS(confidential_balance_version)] = sle[xrpl::sfConfidentialBalanceVersion];
if (sle.isFieldPresent(xrpl::sfIssuerEncryptedBalance)) {
mptJson[JS(issuer_encrypted_balance)] =
xrpl::strHex(sle.getFieldVL(xrpl::sfIssuerEncryptedBalance));
}
if (sle.isFieldPresent(xrpl::sfAuditorEncryptedBalance)) {
mptJson[JS(auditor_encrypted_balance)] =
xrpl::strHex(sle.getFieldVL(xrpl::sfAuditorEncryptedBalance));
}
if (sle.isFieldPresent(xrpl::sfHolderEncryptionKey)) {
mptJson[JS(holder_encryption_key)] =
xrpl::strHex(sle.getFieldVL(xrpl::sfHolderEncryptionKey));
}
return mptJson;
}
} // namespace
MPTHoldersHandler::Result
MPTHoldersHandler::process(MPTHoldersHandler::Input const& input, Context const& ctx) const
{
// Account-list filter is a bounded, unpaginated lookup by key: marker/limit does not make sense
// alongside it.
if (input.accounts) {
if (input.marker.has_value())
return Error{Status{RippledError::RpcInvalidParams, "accountsWithMarker"}};
if (input.limit.has_value())
return Error{Status{RippledError::RpcInvalidParams, "accountsWithLimit"}};
}
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "MPTHolder'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)
);
if (not expectedLgrInfo.has_value())
@@ -48,7 +119,7 @@ MPTHoldersHandler::process(MPTHoldersHandler::Input const& input, Context const&
auto const& lgrInfo = *expectedLgrInfo;
auto const limit = input.limit.value_or(MPTHoldersHandler::kLimitDefault);
auto const mptID = xrpl::uint192{input.mptID.c_str()};
auto const& mptID = input.mptID;
auto const issuanceLedgerObject = sharedPtrBackend_->fetchLedgerObject(
xrpl::keylet::mptokenIssuance(mptID).key, lgrInfo.seq, ctx.yield
@@ -56,73 +127,38 @@ MPTHoldersHandler::process(MPTHoldersHandler::Input const& input, Context const&
if (!issuanceLedgerObject)
return Error{Status{RippledError::RpcObjectNotFound, "objectNotFound"}};
std::optional<xrpl::AccountID> cursor;
if (input.marker)
cursor = xrpl::AccountID{input.marker->c_str()};
auto const dbResponse =
sharedPtrBackend_->fetchMPTHolders(mptID, limit, cursor, lgrInfo.seq, ctx.yield);
auto output = MPTHoldersHandler::Output{};
output.mptID = to_string(mptID);
output.limit = limit;
output.ledgerIndex = lgrInfo.seq;
boost::json::array const mpts;
for (auto const& mpt : dbResponse.mptokens) {
xrpl::STLedgerEntry const sle{
xrpl::SerialIter{mpt.data(), mpt.size()}, keylet::mptokenIssuance(mptID).key
};
boost::json::object mptJson;
mptJson[JS(account)] = toBase58(sle[xrpl::sfAccount]);
mptJson[JS(flags)] = sle.getFlags();
mptJson[JS(mpt_amount)] = toBoostJson(
xrpl::STUInt64{xrpl::sfMPTAmount, sle[xrpl::sfMPTAmount]}.getJson(
JsonOptions::Values::None
)
);
mptJson[JS(mptoken_index)] =
xrpl::to_string(xrpl::keylet::mptoken(mptID, sle[xrpl::sfAccount]).key);
if (sle.isFieldPresent(xrpl::sfLockedAmount)) {
mptJson["locked_amount"] = toBoostJson(
xrpl::STUInt64{xrpl::sfLockedAmount, sle[xrpl::sfLockedAmount]}.getJson(
JsonOptions::Values::None
)
);
// Account-list filter: bounded lookup by key. Duplicates are dropped in first-seen
// order, non-holders are omitted, and no paging marker is produced.
if (input.accounts) {
std::vector<xrpl::uint256> keys;
keys.reserve(input.accounts->size());
for (auto const& accountID : *input.accounts) {
auto const key = xrpl::keylet::mptoken(mptID, accountID).key;
if (not std::ranges::contains(keys, key))
keys.push_back(key);
}
if (sle.isFieldPresent(xrpl::sfConfidentialBalanceInbox)) {
mptJson[JS(confidential_balance_inbox)] =
xrpl::strHex(sle.getFieldVL(xrpl::sfConfidentialBalanceInbox));
auto const mptObjects = sharedPtrBackend_->fetchLedgerObjects(keys, lgrInfo.seq, ctx.yield);
for (auto const& mpt : mptObjects) {
if (not mpt.empty())
output.mpts.push_back(mpTokenToJson(mptID, mpt));
}
if (sle.isFieldPresent(xrpl::sfConfidentialBalanceSpending)) {
mptJson[JS(confidential_balance_spending)] =
xrpl::strHex(sle.getFieldVL(xrpl::sfConfidentialBalanceSpending));
}
if (sle.isFieldPresent(xrpl::sfConfidentialBalanceVersion))
mptJson[JS(confidential_balance_version)] = sle[xrpl::sfConfidentialBalanceVersion];
if (sle.isFieldPresent(xrpl::sfIssuerEncryptedBalance)) {
mptJson[JS(issuer_encrypted_balance)] =
xrpl::strHex(sle.getFieldVL(xrpl::sfIssuerEncryptedBalance));
}
if (sle.isFieldPresent(xrpl::sfAuditorEncryptedBalance)) {
mptJson[JS(auditor_encrypted_balance)] =
xrpl::strHex(sle.getFieldVL(xrpl::sfAuditorEncryptedBalance));
}
if (sle.isFieldPresent(xrpl::sfHolderEncryptionKey)) {
mptJson[JS(holder_encryption_key)] =
xrpl::strHex(sle.getFieldVL(xrpl::sfHolderEncryptionKey));
}
output.mpts.push_back(mptJson);
return output;
}
output.limit = limit;
auto const dbResponse =
sharedPtrBackend_->fetchMPTHolders(mptID, limit, input.marker, lgrInfo.seq, ctx.yield);
for (auto const& mpt : dbResponse.mptokens)
output.mpts.push_back(mpTokenToJson(mptID, mpt));
if (dbResponse.cursor.has_value())
output.marker = strHex(*dbResponse.cursor);
@@ -138,39 +174,16 @@ tag_invoke(
{
jv = {
{JS(mpt_issuance_id), output.mptID},
{JS(limit), output.limit},
{JS(ledger_index), output.ledgerIndex},
{"mptokens", output.mpts},
{JS(validated), output.validated},
};
if (output.limit.has_value())
jv.as_object()[JS(limit)] = *(output.limit);
if (output.marker.has_value())
jv.as_object()[JS(marker)] = *(output.marker);
}
MPTHoldersHandler::Input
tag_invoke(boost::json::value_to_tag<MPTHoldersHandler::Input>, boost::json::value const& jv)
{
auto const& jsonObject = jv.as_object();
MPTHoldersHandler::Input input;
input.mptID = jsonObject.at(JS(mpt_issuance_id)).as_string().c_str();
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = jsonObject.at(JS(ledger_hash)).as_string().c_str();
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(limit)))
input.limit = util::integralValueAs<uint32_t>(jsonObject.at(JS(limit)));
if (jsonObject.contains(JS(marker)))
input.marker = jsonObject.at(JS(marker)).as_string().c_str();
return input;
}
} // namespace rpc

View File

@@ -1,35 +1,39 @@
#pragma once
#include "data/BackendInterface.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Modifiers.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/mpt_holders/Types.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
namespace rpc {
/**
* @brief The mpt_holders command asks the Clio server for all holders of a particular
* @brief The mpt_holders command asks the Clio server for holders of a particular
* MPTokenIssuance.
*
* When `accounts` is provided, those accounts are looked up directly by
* `keylet::mptoken` instead of scanning the holder index. Duplicate accounts are
* collapsed in first-seen order and non-holders are omitted. This filtered mode is
* not paginated, so `marker` and `limit` are rejected.
*/
class MPTHoldersHandler {
class MPTHoldersHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::mpt_holders::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
static constexpr auto kLimitMin = 1;
static constexpr auto kLimitMax = 100;
static constexpr auto kLimitDefault = 50;
static constexpr auto kLimitMin = rpc::spec::handlers::mpt_holders::kLimitMin;
static constexpr auto kLimitMax = rpc::spec::handlers::mpt_holders::kLimitMax;
static constexpr auto kLimitDefault = rpc::spec::handlers::mpt_holders::kLimitDefault;
static constexpr auto kMaxAccounts = rpc::spec::handlers::mpt_holders::kMaxAccounts;
/**
* @brief A struct to hold the output data of the command
@@ -39,19 +43,8 @@ public:
uint32_t ledgerIndex;
std::string mptID;
bool validated = true;
uint32_t limit;
std::optional<std::string> marker;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::string mptID;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
std::optional<std::string> marker;
std::optional<uint32_t> limit;
std::optional<std::string> marker;
};
using Result = HandlerReturnType<Output>;
@@ -66,31 +59,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(mpt_issuance_id),
validation::Required{},
validation::CustomValidators::uint192HexStringValidator},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
modifiers::Clamp<int32_t>{kLimitMin, kLimitMax}},
{JS(marker), validation::CustomValidators::uint160HexStringValidator},
};
return kRpcSpec;
}
/**
* @brief Process the MPTHolders command
*
@@ -110,14 +78,5 @@ 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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -15,6 +15,7 @@
#include <boost/json/value_from.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <rpcspec/handlers/mptoken_issuance_history/Types.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/strHex.h>
@@ -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<MPTokenIssuanceHistoryHandler::Input>,
boost::json::value const& jv
)
{
auto const& jsonObject = jv.as_object();
auto input = MPTokenIssuanceHistoryHandler::Input{};
input.mptIssuanceID = boost::json::value_to<std::string>(jsonObject.at(JS(mpt_issuance_id)));
if (jsonObject.contains(JS(account)))
input.account = boost::json::value_to<std::string>(jsonObject.at(JS(account)));
if (jsonObject.contains("tx_type")) {
input.transactionTypeInLowercase =
boost::json::value_to<std::string>(jsonObject.at("tx_type"));
}
if (jsonObject.contains(JS(ledger_index_min)) &&
util::integralValueAs<int32_t>(jsonObject.at(JS(ledger_index_min))) != -1)
input.ledgerIndexMin = util::integralValueAs<uint32_t>(jsonObject.at(JS(ledger_index_min)));
if (jsonObject.contains(JS(ledger_index_max)) &&
util::integralValueAs<int32_t>(jsonObject.at(JS(ledger_index_max))) != -1)
input.ledgerIndexMax = util::integralValueAs<uint32_t>(jsonObject.at(JS(ledger_index_max)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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<uint32_t>(jsonObject.at(JS(limit)));
if (jsonObject.contains(JS(marker))) {
input.marker = MPTokenIssuanceHistoryHandler::Marker{
.ledger = util::integralValueAs<uint32_t>(
jsonObject.at(JS(marker)).as_object().at(JS(ledger))
),
.seq =
util::integralValueAs<uint32_t>(jsonObject.at(JS(marker)).as_object().at(JS(seq)))
};
}
return input;
}
} // namespace rpc
} // namespace rpc::spec::handlers::mptoken_issuance_history

View File

@@ -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 <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/TxTypes.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/mptoken_issuance_history/Types.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
#include <atomic>
#include <cstdint>
@@ -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<rpc::spec::handlers::mptoken_issuance_history::Input> {
util::Logger log_{"RPC"};
std::shared_ptr<BackendInterface> sharedPtrBackend_;
@@ -49,9 +43,10 @@ class MPTokenIssuanceHistoryHandler {
std::shared_ptr<std::atomic_bool> migrated_ = std::make_shared<std::atomic_bool>(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<std::string> account;
std::optional<std::string> transactionTypeInLowercase;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
std::optional<int32_t> ledgerIndexMin;
std::optional<int32_t> ledgerIndexMax;
bool binary = false;
bool forward = false;
std::optional<uint32_t> limit;
std::optional<Marker> marker;
};
using Result = HandlerReturnType<Output>;
/**
@@ -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<std::string>{},
modifiers::ToLower{},
validation::OneOf<std::string>(
typesKeysInLowercase.cbegin(), typesKeysInLowercase.cend()
),
},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(ledger_index_min), validation::Type<int32_t>{}},
{JS(ledger_index_max), validation::Type<int32_t>{}},
{JS(binary), validation::Type<bool>{}},
{JS(forward), validation::Type<bool>{}},
{JS(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
modifiers::Clamp<int32_t>{kLimitMin, kLimitMax}},
{JS(marker),
meta::WithCustomError{
validation::Type<boost::json::object>{},
Status{RippledError::RpcInvalidParams, "invalidMarker"}
},
meta::Section{
{JS(ledger), validation::Required{}, validation::Type<uint32_t>{}},
{JS(seq), validation::Required{}, validation::Type<uint32_t>{}},
}},
};
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<Input>, 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

View File

@@ -2,7 +2,6 @@
#include "rpc/common/Types.hpp"
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Indexes.h>
using namespace xrpl;
@@ -12,7 +11,7 @@ namespace rpc {
NFTBuyOffersHandler::Result
NFTBuyOffersHandler::process(NFTBuyOffersHandler::Input const& input, Context const& ctx) const
{
auto const tokenID = uint256{input.nftID.c_str()};
auto const& tokenID = input.nftID;
auto const directory = keylet::nftBuys(tokenID);
return iterateOfferDirectory(input, tokenID, directory, ctx.yield);

View File

@@ -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 <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_from.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <rpcspec/handlers/nft_history/Types.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/strHex.h>
@@ -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<uint32_t>(*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<uint32_t>(*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<NFTHistoryHandler::Input>, boost::json::value const& jv)
{
auto const& jsonObject = jv.as_object();
auto input = NFTHistoryHandler::Input{};
input.nftID = boost::json::value_to<std::string>(jsonObject.at(JS(nft_id)));
if (jsonObject.contains(JS(ledger_index_min)) &&
util::integralValueAs<int32_t>(jsonObject.at(JS(ledger_index_min))) != -1)
input.ledgerIndexMin = util::integralValueAs<uint32_t>(jsonObject.at(JS(ledger_index_min)));
if (jsonObject.contains(JS(ledger_index_max)) &&
util::integralValueAs<int32_t>(jsonObject.at(JS(ledger_index_max))) != -1)
input.ledgerIndexMax = util::integralValueAs<uint32_t>(jsonObject.at(JS(ledger_index_max)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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<uint32_t>(jsonObject.at(JS(limit)));
if (jsonObject.contains(JS(marker))) {
input.marker = NFTHistoryHandler::Marker{
.ledger = util::integralValueAs<uint32_t>(
jsonObject.at(JS(marker)).as_object().at(JS(ledger))
),
.seq =
util::integralValueAs<uint32_t>(jsonObject.at(JS(marker)).as_object().at(JS(seq)))
};
}
return input;
}
} // namespace rpc
} // namespace rpc::spec::handlers::nft_history

View File

@@ -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 <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/nft_history/Types.hpp>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <string>
#include <utility>
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<rpc::spec::handlers::nft_history::Input> {
util::Logger log_{"RPC"};
std::shared_ptr<BackendInterface> 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<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
std::optional<int32_t> ledgerIndexMin;
std::optional<int32_t> ledgerIndexMax;
bool binary = false;
bool forward = false;
std::optional<uint32_t> limit;
std::optional<Marker> marker;
};
using Result = HandlerReturnType<Output>;
/**
@@ -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<int32_t>{}},
{JS(ledger_index_max), validation::Type<int32_t>{}},
{JS(binary), validation::Type<bool>{}},
{JS(forward), validation::Type<bool>{}},
{JS(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
modifiers::Clamp<int32_t>{kLimitMin, kLimitMax}},
{JS(marker),
meta::WithCustomError{
validation::Type<boost::json::object>{},
Status{RippledError::RpcInvalidParams, "invalidMarker"}
},
meta::Section{
{JS(ledger), validation::Required{}, validation::Type<uint32_t>{}},
{JS(seq), validation::Required{}, validation::Type<uint32_t>{}},
}},
};
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<Input>, 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

View File

@@ -4,14 +4,11 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/LedgerHeader.h>
@@ -27,15 +24,14 @@ namespace rpc {
NFTInfoHandler::Result
NFTInfoHandler::process(NFTInfoHandler::Input const& input, Context const& ctx) const
{
auto const tokenID = xrpl::uint256{input.nftID.c_str()};
auto const& tokenID = input.nftID;
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "NFTInfo'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)
);
@@ -93,24 +89,4 @@ tag_invoke(
};
}
NFTInfoHandler::Input
tag_invoke(boost::json::value_to_tag<NFTInfoHandler::Input>, boost::json::value const& jv)
{
auto const& jsonObject = jv.as_object();
auto input = NFTInfoHandler::Input{};
input.nftID = boost::json::value_to<std::string>(jsonObject.at(JS(nft_id)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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;
}
return input;
}
} // namespace rpc

View File

@@ -1,19 +1,17 @@
#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 <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/nft_info/Types.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
namespace rpc {
@@ -22,7 +20,7 @@ namespace rpc {
*
* For more details see: https://xrpl.org/nft_info.html
*/
class NFTInfoHandler {
class NFTInfoHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::nft_info::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
@@ -46,15 +44,6 @@ public:
bool validated = true;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::string nftID;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
};
using Result = HandlerReturnType<Output>;
/**
@@ -67,26 +56,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},
};
return kRpcSpec;
}
/**
* @brief Process the NFTInfo command
*
@@ -106,15 +75,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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -4,15 +4,14 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/asio/spawn.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
@@ -74,11 +73,10 @@ NFTOffersHandlerBase::iterateOfferDirectory(
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "NFTOffersCommon's ledger range must be available");
auto const expectedLgrInfo = getLedgerHeaderFromHashOrSeq(
auto const expectedLgrInfo = getLedgerHeaderFromLedgerSpecifier(
*sharedPtrBackend_,
yield,
input.ledgerHash,
input.ledgerIndex,
input.ledger,
range->maxSequence // NOLINT(bugprone-unchecked-optional-access)
);
@@ -91,14 +89,15 @@ NFTOffersHandlerBase::iterateOfferDirectory(
if (not sharedPtrBackend_->fetchLedgerObject(directory.key, lgrInfo.seq, yield))
return Error{Status{RippledError::RpcObjectNotFound, "notFound"}};
auto output = Output{.nftID = input.nftID, .offers = {}, .limit = {}, .marker = {}};
auto output =
Output{.nftID = xrpl::strHex(input.nftID), .offers = {}, .limit = {}, .marker = {}};
auto offers = std::vector<xrpl::SLE>{};
auto reserve = input.limit;
auto cursor = uint256{};
auto startHint = uint64_t{0ul};
if (input.marker) {
cursor = uint256(input.marker->c_str());
cursor = *input.marker;
// We have a start point. Use limit - 1 from the result and use the very last one for the
// resume.
@@ -181,30 +180,4 @@ tag_invoke(
jv = std::move(object);
}
NFTOffersHandlerBase::Input
tag_invoke(boost::json::value_to_tag<NFTOffersHandlerBase::Input>, boost::json::value const& jv)
{
auto input = NFTOffersHandlerBase::Input{};
auto const& jsonObject = jv.as_object();
input.nftID = boost::json::value_to<std::string>(jsonObject.at(JS(nft_id)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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(marker)))
input.marker = boost::json::value_to<std::string>(jsonObject.at(JS(marker)));
if (jsonObject.contains(JS(limit)))
input.limit = util::integralValueAs<uint32_t>(jsonObject.at(JS(limit)));
return input;
}
} // namespace rpc

View File

@@ -1,24 +1,22 @@
#pragma once
#include "data/BackendInterface.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Modifiers.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include <boost/asio/spawn.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/nft_offers_common/Types.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace rpc {
@@ -26,13 +24,14 @@ namespace rpc {
/**
* @brief Contains common functionality for handling the `nft_offers` command
*/
class NFTOffersHandlerBase {
class NFTOffersHandlerBase
: public rpc::spec::HandlerFor<rpc::spec::handlers::nft_offers_common::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
static constexpr auto kLimitMin = 50;
static constexpr auto kLimitMax = 500;
static constexpr auto kLimitDefault = 250;
static constexpr auto kLimitMin = rpc::spec::handlers::nft_offers_common::kLimitMin;
static constexpr auto kLimitMax = rpc::spec::handlers::nft_offers_common::kLimitMax;
static constexpr auto kLimitDefault = rpc::spec::handlers::nft_offers_common::kLimitDefault;
/**
* @brief A struct to hold the output data of the command
@@ -47,17 +46,6 @@ public:
std::optional<std::string> marker;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::string nftID;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
uint32_t limit = kLimitDefault;
std::optional<std::string> marker;
};
using Result = HandlerReturnType<Output>;
/**
@@ -70,31 +58,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(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
modifiers::Clamp<int32_t>{kLimitMin, kLimitMax}},
{JS(marker), validation::CustomValidators::uint256HexStringValidator},
};
return kRpcSpec;
}
protected:
/**
* @brief Iterate the NFT offer directory
@@ -122,15 +85,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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -2,7 +2,6 @@
#include "rpc/common/Types.hpp"
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Indexes.h>
using namespace xrpl;
@@ -12,7 +11,7 @@ namespace rpc {
NFTSellOffersHandler::Result
NFTSellOffersHandler::process(NFTSellOffersHandler::Input const& input, Context const& ctx) const
{
auto const tokenID = uint256{input.nftID.c_str()};
auto const& tokenID = input.nftID;
auto const directory = keylet::nftSells(tokenID);
return iterateOfferDirectory(input, tokenID, directory, ctx.yield);

View File

@@ -4,14 +4,11 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Indexes.h>
@@ -19,7 +16,6 @@
#include <xrpl/protocol/jss.h>
#include <xrpl/protocol/nft.h>
#include <cstdint>
#include <optional>
#include <string>
@@ -33,11 +29,10 @@ NFTsByIssuerHandler::process(NFTsByIssuerHandler::Input const& input, Context co
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "NFTsByIssuer'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)
);
if (not expectedLgrInfo.has_value())
@@ -45,36 +40,25 @@ NFTsByIssuerHandler::process(NFTsByIssuerHandler::Input const& input, Context co
auto const& lgrInfo = *expectedLgrInfo;
auto const limit = input.limit.value_or(NFTsByIssuerHandler::kLimitDefault);
auto const limit = input.limit;
auto const issuer = accountFromStringStrict(input.issuer);
auto const& issuer = input.issuer;
auto const accountLedgerObject = sharedPtrBackend_->fetchLedgerObject(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
xrpl::keylet::account(*issuer).key,
lgrInfo.seq,
ctx.yield
xrpl::keylet::account(issuer).key, lgrInfo.seq, ctx.yield
);
if (!accountLedgerObject)
return Error{Status{RippledError::RpcActNotFound}};
std::optional<uint256> cursor;
if (input.marker)
cursor = uint256{input.marker->c_str()};
auto const cursor = input.marker;
auto const dbResponse = sharedPtrBackend_->fetchNFTsByIssuer(
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
*issuer,
input.nftTaxon,
lgrInfo.seq,
limit,
cursor,
ctx.yield
issuer, input.nftTaxon, lgrInfo.seq, limit, cursor, ctx.yield
);
auto output = NFTsByIssuerHandler::Output{};
output.issuer = toBase58(*issuer); // NOLINT(bugprone-unchecked-optional-access)
output.issuer = toBase58(issuer);
output.limit = limit;
output.ledgerIndex = lgrInfo.seq;
output.nftTaxon = input.nftTaxon;
@@ -125,32 +109,4 @@ tag_invoke(
jv.as_object()[JS(nft_taxon)] = *(output.nftTaxon);
}
NFTsByIssuerHandler::Input
tag_invoke(boost::json::value_to_tag<NFTsByIssuerHandler::Input>, boost::json::value const& jv)
{
auto const& jsonObject = jv.as_object();
NFTsByIssuerHandler::Input input;
input.issuer = boost::json::value_to<std::string>(jsonObject.at(JS(issuer)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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(limit)))
input.limit = util::integralValueAs<uint32_t>(jsonObject.at(JS(limit)));
if (jsonObject.contains(JS(nft_taxon)))
input.nftTaxon = util::integralValueAs<uint32_t>(jsonObject.at(JS(nft_taxon)));
if (jsonObject.contains(JS(marker)))
input.marker = boost::json::value_to<std::string>(jsonObject.at(JS(marker)));
return input;
}
} // namespace rpc

View File

@@ -1,34 +1,33 @@
#pragma once
#include "data/BackendInterface.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Modifiers.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/nfts_by_issuer/Types.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
namespace rpc {
/**
* @brief Handler for the `nfts_by_issuer` command
*/
class NFTsByIssuerHandler {
class NFTsByIssuerHandler
: public rpc::spec::HandlerFor<rpc::spec::handlers::nfts_by_issuer::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
static constexpr auto kLimitMin = 1;
static constexpr auto kLimitMax = 100;
static constexpr auto kLimitDefault = 50;
static constexpr auto kLimitMin = rpc::spec::handlers::nfts_by_issuer::kLimitMin;
static constexpr auto kLimitMax = rpc::spec::handlers::nfts_by_issuer::kLimitMax;
static constexpr auto kLimitDefault = rpc::spec::handlers::nfts_by_issuer::kLimitDefault;
/**
* @brief A struct to hold the output data of the command
@@ -43,18 +42,6 @@ public:
std::optional<std::string> marker;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::string issuer;
std::optional<uint32_t> nftTaxon;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
std::optional<std::string> marker;
std::optional<uint32_t> limit;
};
using Result = HandlerReturnType<Output>;
/**
@@ -67,30 +54,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(issuer), validation::Required{}, validation::CustomValidators::accountValidator},
{JS(nft_taxon), validation::Type<uint32_t>{}},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(limit),
validation::Type<uint32_t>{},
validation::Min(1u),
modifiers::Clamp<int32_t>{kLimitMin, kLimitMax}},
{JS(marker), validation::CustomValidators::uint256HexStringValidator},
};
return kRpcSpec;
}
/**
* @brief Process the NFTsByIssuer command
*
@@ -110,14 +73,5 @@ 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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -2,16 +2,13 @@
#include "rpc/JS.hpp"
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/JsonBool.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <fmt/format.h>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/strHex.h>
@@ -43,11 +40,10 @@ NoRippleCheckHandler::process(NoRippleCheckHandler::Input const& input, Context
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "NoRippleCheck'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)
);
@@ -55,9 +51,8 @@ NoRippleCheckHandler::process(NoRippleCheckHandler::Input const& input, Context
return Error{expectedLgrInfo.error()};
auto const& lgrInfo = *expectedLgrInfo;
auto const accountID = accountFromStringStrict(input.account);
auto const keylet =
xrpl::keylet::account(*accountID).key; // NOLINT(bugprone-unchecked-optional-access)
auto const& accountID = input.account;
auto const keylet = xrpl::keylet::account(accountID).key;
auto const accountObj = sharedPtrBackend_->fetchLedgerObject(keylet, lgrInfo.seq, ctx.yield);
if (!accountObj)
@@ -94,8 +89,7 @@ NoRippleCheckHandler::process(NoRippleCheckHandler::Input const& input, Context
output.problems.emplace_back("You should immediately set your default ripple flag");
if (input.transactions) {
auto tx =
getBaseTx(*accountID, accountSeq++); // NOLINT(bugprone-unchecked-optional-access)
auto tx = getBaseTx(accountID, accountSeq++);
tx[JS(TransactionType)] = "AccountSet";
tx[JS(SetFlag)] = xrpl::asfDefaultRipple;
output.transactions->push_back(tx);
@@ -106,7 +100,7 @@ NoRippleCheckHandler::process(NoRippleCheckHandler::Input const& input, Context
traverseOwnedNodes(
*sharedPtrBackend_,
*accountID, // NOLINT(bugprone-unchecked-optional-access)
accountID,
lgrInfo.seq,
std::numeric_limits<std::uint32_t>::max(),
{},
@@ -154,7 +148,7 @@ NoRippleCheckHandler::process(NoRippleCheckHandler::Input const& input, Context
xrpl::Issue{limitAmount.get<xrpl::Issue>().currency, peer}
);
auto tx = getBaseTx(*accountID, accountSeq++);
auto tx = getBaseTx(accountID, accountSeq++);
tx[JS(TransactionType)] = "TrustSet";
tx[JS(LimitAmount)] =
@@ -176,33 +170,6 @@ NoRippleCheckHandler::process(NoRippleCheckHandler::Input const& input, Context
return output;
}
NoRippleCheckHandler::Input
tag_invoke(boost::json::value_to_tag<NoRippleCheckHandler::Input>, boost::json::value const& jv)
{
auto input = NoRippleCheckHandler::Input{};
auto const& jsonObject = jv.as_object();
input.account = boost::json::value_to<std::string>(jsonObject.at(JS(account)));
input.roleGateway = jsonObject.at(JS(role)).as_string() == "gateway";
if (jsonObject.contains(JS(limit)))
input.limit = util::integralValueAs<uint32_t>(jsonObject.at(JS(limit)));
if (jsonObject.contains(JS(transactions)))
input.transactions = boost::json::value_to<JsonBool>(jsonObject.at(JS(transactions)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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;
}
return input;
}
void
tag_invoke(
boost::json::value_from_tag,

View File

@@ -1,25 +1,19 @@
#pragma once
#include "data/BackendInterface.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 <boost/json/array.hpp>
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/noripple_check/Types.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
namespace rpc {
@@ -32,7 +26,8 @@ namespace rpc {
*
* For more details see: https://xrpl.org/noripple_check.html
*/
class NoRippleCheckHandler {
class NoRippleCheckHandler
: public rpc::spec::HandlerFor<rpc::spec::handlers::noripple_check::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
@@ -52,18 +47,6 @@ public:
bool validated = true;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::string account;
bool roleGateway = false;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
uint32_t limit = kLimitDefault;
JsonBool transactions{false};
};
using Result = HandlerReturnType<Output>;
/**
@@ -76,41 +59,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 kRpcSpecV1 = RpcSpec{
{JS(account), validation::Required{}, validation::CustomValidators::accountValidator},
{JS(role),
validation::Required{},
meta::WithCustomError{
validation::OneOf{"gateway", "user"},
Status{RippledError::RpcInvalidParams, "role field is invalid"}
}},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
{JS(limit),
validation::Type<uint32_t>(),
validation::Min(1u),
modifiers::Clamp<int32_t>{kLimitMin, kLimitMax}}
};
static auto const kRpcSpec = RpcSpec{
kRpcSpecV1,
{
{JS(transactions), validation::Type<bool>()},
}
};
return apiVersion == 1 ? kRpcSpecV1 : kRpcSpec;
}
/**
* @brief Process the NoRippleCheck command
*
@@ -130,14 +78,5 @@ 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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -5,9 +5,7 @@
#include "etl/ETLServiceInterface.hpp"
#include "etl/LoadBalancerInterface.hpp"
#include "feed/SubscriptionManagerInterface.hpp"
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/build/Build.hpp"
@@ -16,10 +14,12 @@
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <fmt/format.h>
#include <rpcspec/Errors.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/server_info/Types.hpp>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/BuildInfo.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Fees.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/jss.h>
@@ -31,6 +31,7 @@
#include <memory>
#include <optional>
#include <string>
#include <utility>
namespace rpc {
class Counters;
@@ -44,7 +45,8 @@ namespace rpc {
* @tparam CountersType The type of the counters
*/
template <typename CountersType>
class BaseServerInfoHandler {
class BaseServerInfoHandler
: public rpc::spec::HandlerFor<rpc::spec::handlers::server_info::Input> {
static constexpr auto kBackendCountersKey = "backend_counters";
std::shared_ptr<BackendInterface> backend_;
@@ -54,13 +56,6 @@ class BaseServerInfoHandler {
std::reference_wrapper<CountersType const> counters_;
public:
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
bool backendCounters = false;
};
/**
* @brief A struct to hold the admin section of the output
*/
@@ -147,19 +142,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 RpcSpec const kRpcSpec = {};
return kRpcSpec;
}
/**
* @brief Process the ServerInfo command
*
@@ -254,12 +236,10 @@ private:
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, InfoSection const& info)
{
using boost::json::value_from;
using xrpl::to_string;
jv = {
{JS(complete_ledgers), info.completeLedgers},
{JS(load_factor), info.loadFactor},
{JS(time), to_string(std::chrono::floor<std::chrono::microseconds>(info.time))},
{JS(time), xrpl::to_string(std::chrono::floor<std::chrono::microseconds>(info.time))},
{JS(uptime), info.uptime.count()},
{"clio_version", info.clioVersion},
{"libxrpl_version", info.xrplVersion},
@@ -328,17 +308,6 @@ private:
{"successor_hit_rate", cache.successorHitRate},
};
}
friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv)
{
auto input = BaseServerInfoHandler::Input{};
auto const jsonObject = jv.as_object();
if (jsonObject.contains(kBackendCountersKey) &&
jsonObject.at(kBackendCountersKey).is_bool())
input.backendCounters = jv.at(kBackendCountersKey).as_bool();
return input;
}
};
/**

View File

@@ -7,11 +7,7 @@
#include "feed/Types.hpp"
#include "rpc/JS.hpp"
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Checkers.hpp"
#include "rpc/common/MetaProcessors.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include "util/Assert.hpp"
#include <boost/asio/spawn.hpp>
@@ -19,17 +15,15 @@
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <rpcspec/handlers/subscribe/Types.hpp>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
@@ -46,62 +40,6 @@ SubscribeHandler::SubscribeHandler(
{
}
RpcSpecConstRef
SubscribeHandler::spec([[maybe_unused]] uint32_t apiVersion)
{
static auto const kBooksValidator = validation::CustomValidator{
[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_array()) {
return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotArray"}};
}
for (auto const& book : value.as_array()) {
if (!book.is_object()) {
return Error{
Status{RippledError::RpcInvalidParams, std::string(key) + "ItemNotObject"}
};
}
if (book.as_object().contains("both") && !book.as_object().at("both").is_bool())
return Error{Status{RippledError::RpcInvalidParams, "bothNotBool"}};
if (book.as_object().contains("snapshot") &&
!book.as_object().at("snapshot").is_bool())
return Error{Status{RippledError::RpcInvalidParams, "snapshotNotBool"}};
if (book.as_object().contains("taker")) {
if (auto err =
meta::WithCustomError(
validation::CustomValidators::accountValidator,
Status{RippledError::RpcBadIssuer, "Issuer account malformed."}
)
.verify(book.as_object(), "taker");
!err)
return err;
}
auto const parsedBook = parseBook(book.as_object());
if (!parsedBook)
return Error(parsedBook.error());
}
return MaybeError{};
}
};
static auto const kRpcSpec = RpcSpec{
{JS(streams), validation::CustomValidators::subscribeStreamValidator},
{JS(accounts), validation::CustomValidators::subscribeAccountsValidator},
{JS(accounts_proposed), validation::CustomValidators::subscribeAccountsValidator},
{JS(books), kBooksValidator},
{"user", check::Deprecated{}},
{JS(password), check::Deprecated{}},
{JS(rt_accounts), check::Deprecated{}}
};
return kRpcSpec;
}
SubscribeHandler::Result
SubscribeHandler::process(Input const& input, Context const& ctx) const
{
@@ -131,25 +69,37 @@ SubscribeHandler::process(Input const& input, Context const& ctx) const
boost::json::object
SubscribeHandler::subscribeToStreams(
boost::asio::yield_context yield,
std::vector<std::string> const& streams,
std::vector<StreamType> const& streams,
feed::SubscriberSharedPtr const& session
) const
{
auto response = boost::json::object{};
for (auto const& stream : streams) {
if (stream == "ledger") {
response = subscriptions_->subLedger(yield, session);
} else if (stream == "transactions") {
subscriptions_->subTransactions(session);
} else if (stream == "transactions_proposed") {
subscriptions_->subProposedTransactions(session);
} else if (stream == "validations") {
subscriptions_->subValidation(session);
} else if (stream == "manifests") {
subscriptions_->subManifest(session);
} else if (stream == "book_changes") {
subscriptions_->subBookChanges(session);
switch (stream) {
case StreamType::Ledger:
response = subscriptions_->subLedger(yield, session);
break;
case StreamType::Transactions:
subscriptions_->subTransactions(session);
break;
case StreamType::TransactionsProposed:
subscriptions_->subProposedTransactions(session);
break;
case StreamType::Validations:
subscriptions_->subValidation(session);
break;
case StreamType::Manifests:
subscriptions_->subManifest(session);
break;
case StreamType::BookChanges:
subscriptions_->subBookChanges(session);
break;
case StreamType::Server:
case StreamType::PeerStatus:
case StreamType::Consensus:
// Not served by Clio.
break;
}
}
@@ -158,28 +108,22 @@ SubscribeHandler::subscribeToStreams(
void
SubscribeHandler::subscribeToAccountsProposed(
std::vector<std::string> const& accounts,
std::vector<xrpl::AccountID> const& accounts,
feed::SubscriberSharedPtr const& session
) const
{
for (auto const& account : accounts) {
auto const accountID = accountFromStringStrict(account);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
subscriptions_->subProposedAccount(*accountID, session);
}
for (auto const& account : accounts)
subscriptions_->subProposedAccount(account, session);
}
void
SubscribeHandler::subscribeToAccounts(
std::vector<std::string> const& accounts,
std::vector<xrpl::AccountID> const& accounts,
feed::SubscriberSharedPtr const& session
) const
{
for (auto const& account : accounts) {
auto const accountID = accountFromStringStrict(account);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
subscriptions_->subAccount(*accountID, session);
}
for (auto const& account : accounts)
subscriptions_->subAccount(account, session);
}
void
@@ -263,54 +207,4 @@ tag_invoke(
jv.as_object().emplace(JS(bids), *(output.bids));
}
SubscribeHandler::Input
tag_invoke(boost::json::value_to_tag<SubscribeHandler::Input>, boost::json::value const& jv)
{
auto input = SubscribeHandler::Input{};
auto const& jsonObject = jv.as_object();
if (auto const& streams = jsonObject.find(JS(streams)); streams != jsonObject.end()) {
input.streams = std::vector<std::string>();
for (auto const& stream : streams->value().as_array())
input.streams->push_back(boost::json::value_to<std::string>(stream));
}
if (auto const& accounts = jsonObject.find(JS(accounts)); accounts != jsonObject.end()) {
input.accounts = std::vector<std::string>();
for (auto const& account : accounts->value().as_array())
input.accounts->push_back(boost::json::value_to<std::string>(account));
}
if (auto const& accountsProposed = jsonObject.find(JS(accounts_proposed));
accountsProposed != jsonObject.end()) {
input.accountsProposed = std::vector<std::string>();
for (auto const& account : accountsProposed->value().as_array())
input.accountsProposed->push_back(boost::json::value_to<std::string>(account));
}
if (auto const& books = jsonObject.find(JS(books)); books != jsonObject.end()) {
input.books = std::vector<SubscribeHandler::OrderBook>();
for (auto const& book : books->value().as_array()) {
auto internalBook = SubscribeHandler::OrderBook{};
auto const& bookObject = book.as_object();
if (auto const taker = bookObject.find(JS(taker)); taker != bookObject.end())
internalBook.taker = boost::json::value_to<std::string>(taker->value());
if (auto const both = bookObject.find(JS(both)); both != bookObject.end())
internalBook.both = both->value().as_bool();
if (auto const snapshot = bookObject.find(JS(snapshot)); snapshot != bookObject.end())
internalBook.snapshot = snapshot->value().as_bool();
auto const parsedBookMaybe = parseBook(book.as_object());
ASSERT(parsedBookMaybe.has_value(), "Book parsing failed");
internalBook.book = *parsedBookMaybe;
input.books->push_back(internalBook);
}
}
return input;
}
} // namespace rpc

View File

@@ -4,7 +4,6 @@
#include "data/BackendInterface.hpp"
#include "feed/SubscriptionManagerInterface.hpp"
#include "feed/Types.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include <boost/asio/spawn.hpp>
@@ -12,16 +11,13 @@
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/subscribe/Types.hpp>
#include <xrpl/protocol/AccountID.h>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace rpc {
@@ -33,7 +29,7 @@ namespace rpc {
* For more details see: https://xrpl.org/subscribe.html
*/
class SubscribeHandler {
class SubscribeHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::subscribe::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
std::shared_ptr<data::AmendmentCenterInterface const> amendmentCenter_;
std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions_;
@@ -59,22 +55,12 @@ public:
/**
* @brief A struct to hold the data for one order book
*/
struct OrderBook {
xrpl::Book book;
std::optional<std::string> taker;
bool snapshot = false;
bool both = false;
};
using OrderBook = rpc::spec::handlers::subscribe::OrderBook;
/**
* @brief A struct to hold the input data for the command
* @brief A subscribable stream type.
*/
struct Input {
std::optional<std::vector<std::string>> accounts;
std::optional<std::vector<std::string>> streams;
std::optional<std::vector<std::string>> accountsProposed;
std::optional<std::vector<OrderBook>> books;
};
using StreamType = rpc::spec::handlers::subscribe::StreamType;
using Result = HandlerReturnType<Output>;
@@ -91,15 +77,6 @@ public:
std::shared_ptr<feed::SubscriptionManagerInterface> const& subscriptions
);
/**
* @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);
/**
* @brief Process the Subscribe command
*
@@ -114,19 +91,19 @@ private:
[[nodiscard]] boost::json::object
subscribeToStreams(
boost::asio::yield_context yield,
std::vector<std::string> const& streams,
std::vector<StreamType> const& streams,
feed::SubscriberSharedPtr const& session
) const;
void
subscribeToAccounts(
std::vector<std::string> const& accounts,
std::vector<xrpl::AccountID> const& accounts,
feed::SubscriberSharedPtr const& session
) const;
void
subscribeToAccountsProposed(
std::vector<std::string> const& accounts,
std::vector<xrpl::AccountID> const& accounts,
feed::SubscriberSharedPtr const& session
) const;
@@ -146,15 +123,6 @@ private:
*/
friend void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output);
/**
* @brief Convert json value to input
*
* @param jv The json value to convert from
* @return The input to convert to
*/
friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -4,13 +4,10 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/jss.h>
@@ -29,11 +26,10 @@ TransactionEntryHandler::process(
auto const range = sharedPtrBackend_->fetchLedgerRange();
ASSERT(range.has_value(), "TransactionEntry'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)
);
@@ -44,8 +40,7 @@ TransactionEntryHandler::process(
output.apiVersion = ctx.apiVersion;
output.ledgerHeader = *expectedLgrInfo;
auto const dbRet =
sharedPtrBackend_->fetchTransaction(xrpl::uint256{input.txHash.c_str()}, ctx.yield);
auto const dbRet = sharedPtrBackend_->fetchTransaction(input.txHash, ctx.yield);
// Note: transaction_entry is meant to only search a specified ledger for
// the specified transaction. tx searches the entire range of history. For
// rippled, having two separate commands made sense, as tx would use SQLite
@@ -98,24 +93,4 @@ tag_invoke(
}
}
TransactionEntryHandler::Input
tag_invoke(boost::json::value_to_tag<TransactionEntryHandler::Input>, boost::json::value const& jv)
{
auto input = TransactionEntryHandler::Input{};
auto const& jsonObject = jv.as_object();
input.txHash = boost::json::value_to<std::string>(jv.at(JS(tx_hash)));
if (jsonObject.contains(JS(ledger_hash)))
input.ledgerHash = boost::json::value_to<std::string>(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;
}
return input;
}
} // namespace rpc

View File

@@ -1,23 +1,19 @@
#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 <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/transaction_entry/Types.hpp>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
namespace rpc {
@@ -27,7 +23,8 @@ namespace rpc {
*
* For more details see: https://xrpl.org/transaction_entry.html
*/
class TransactionEntryHandler {
class TransactionEntryHandler
: public rpc::spec::HandlerFor<rpc::spec::handlers::transaction_entry::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
@@ -44,15 +41,6 @@ public:
uint32_t apiVersion;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::string txHash;
std::optional<std::string> ledgerHash;
std::optional<uint32_t> ledgerIndex;
};
using Result = HandlerReturnType<Output>;
/**
@@ -65,28 +53,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(tx_hash),
meta::WithCustomError{
validation::Required{}, Status(ClioError::RpcFieldNotFoundTransaction)
},
validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_hash), validation::CustomValidators::uint256HexStringValidator},
{JS(ledger_index), validation::CustomValidators::ledgerIndexValidator},
};
return kRpcSpec;
}
/**
* @brief Process the transaction_entry command
*
@@ -106,15 +72,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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -3,15 +3,10 @@
#include "data/BackendInterface.hpp"
#include "data/Types.hpp"
#include "etl/ETLServiceInterface.hpp"
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/JsonBool.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/asio/spawn.hpp>
#include <boost/json/conversion.hpp>
@@ -19,10 +14,11 @@
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <fmt/format.h>
#include <xrpl/basics/base_uint.h>
#include <rpcspec/Errors.hpp>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/tx/Types.hpp>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/jss.h>
@@ -39,7 +35,7 @@ namespace rpc {
*
* For more details see: https://xrpl.org/tx.html
*/
class TxHandler {
class TxHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::tx::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
std::shared_ptr<etl::ETLServiceInterface const> etl_;
@@ -68,17 +64,6 @@ public:
bool validated = true;
};
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::optional<std::string> transaction;
std::optional<std::string> ctid;
bool binary = false;
std::optional<uint32_t> minLedger;
std::optional<uint32_t> maxLedger;
};
using Result = HandlerReturnType<Output>;
/**
@@ -95,28 +80,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(uint32_t apiVersion)
{
static RpcSpec const kRpcSpecForV1 = {
{JS(transaction), validation::CustomValidators::uint256HexStringValidator},
{JS(min_ledger), validation::Type<uint32_t>{}},
{JS(max_ledger), validation::Type<uint32_t>{}},
{JS(ctid), validation::Type<std::string>{}},
};
static auto const kRpcSpec =
RpcSpec{kRpcSpecForV1, {{JS(binary), validation::Type<bool>{}}}};
return apiVersion == 1 ? kRpcSpecForV1 : kRpcSpec;
}
/**
* @brief Process the Tx command
*
@@ -170,9 +133,7 @@ public:
dbResponse = fetchTxViaCtid(lgrSeq, txnIdx, ctx.yield);
} else {
dbResponse = sharedPtrBackend_->fetchTransaction(
xrpl::uint256{input.transaction->c_str()}, ctx.yield
);
dbResponse = sharedPtrBackend_->fetchTransaction(*input.transaction, ctx.yield);
}
auto output = TxHandler::Output{.apiVersion = ctx.apiVersion};
@@ -312,32 +273,6 @@ private:
jv = std::move(obj);
}
friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv)
{
auto input = TxHandler::Input{};
auto const& jsonObject = jv.as_object();
if (jsonObject.contains(JS(transaction)))
input.transaction = boost::json::value_to<std::string>(jv.at(JS(transaction)));
if (jsonObject.contains(JS(ctid))) {
input.ctid = boost::json::value_to<std::string>(jv.at(JS(ctid)));
input.ctid = util::toUpper(*input.ctid);
}
if (jsonObject.contains(JS(binary)))
input.binary = boost::json::value_to<JsonBool>(jsonObject.at(JS(binary)));
if (jsonObject.contains(JS(min_ledger)))
input.minLedger = util::integralValueAs<uint32_t>(jv.at(JS(min_ledger)));
if (jsonObject.contains(JS(max_ledger)))
input.maxLedger = util::integralValueAs<uint32_t>(jv.at(JS(max_ledger)));
return input;
}
};
} // namespace rpc

View File

@@ -2,25 +2,14 @@
#include "feed/SubscriptionManagerInterface.hpp"
#include "feed/Types.hpp"
#include "rpc/JS.hpp"
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Checkers.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include "util/Assert.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <rpcspec/Errors.hpp>
#include <rpcspec/handlers/unsubscribe/Types.hpp>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <expected>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
namespace rpc {
@@ -32,47 +21,6 @@ UnsubscribeHandler::UnsubscribeHandler(
{
}
RpcSpecConstRef
UnsubscribeHandler::spec([[maybe_unused]] uint32_t apiVersion)
{
static auto const kBooksValidator = validation::CustomValidator{
[](boost::json::value const& value, std::string_view key) -> MaybeError {
if (!value.is_array()) {
return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotArray"}};
}
for (auto const& book : value.as_array()) {
if (!book.is_object()) {
return Error{
Status{RippledError::RpcInvalidParams, std::string(key) + "ItemNotObject"}
};
}
if (book.as_object().contains("both") && !book.as_object().at("both").is_bool())
return Error{Status{RippledError::RpcInvalidParams, "bothNotBool"}};
auto const parsedBook = parseBook(book.as_object());
if (!parsedBook.has_value())
return Error(parsedBook.error());
}
return MaybeError{};
}
};
static auto const kRpcSpec = RpcSpec{
{JS(streams), validation::CustomValidators::subscribeStreamValidator},
{JS(accounts), validation::CustomValidators::subscribeAccountsValidator},
{JS(accounts_proposed), validation::CustomValidators::subscribeAccountsValidator},
{JS(books), kBooksValidator},
{JS(url), check::Deprecated{}},
{JS(rt_accounts), check::Deprecated{}},
{"rt_transactions", check::Deprecated{}},
};
return kRpcSpec;
}
UnsubscribeHandler::Result
UnsubscribeHandler::process(Input const& input, Context const& ctx) const
{
@@ -93,54 +41,59 @@ UnsubscribeHandler::process(Input const& input, Context const& ctx) const
void
UnsubscribeHandler::unsubscribeFromStreams(
std::vector<std::string> const& streams,
std::vector<StreamType> const& streams,
feed::SubscriberSharedPtr const& session
) const
{
for (auto const& stream : streams) {
if (stream == "ledger") {
subscriptions_->unsubLedger(session);
} else if (stream == "transactions") {
subscriptions_->unsubTransactions(session);
} else if (stream == "transactions_proposed") {
subscriptions_->unsubProposedTransactions(session);
} else if (stream == "validations") {
subscriptions_->unsubValidation(session);
} else if (stream == "manifests") {
subscriptions_->unsubManifest(session);
} else if (stream == "book_changes") {
subscriptions_->unsubBookChanges(session);
} else {
ASSERT(false, "Unknown stream: {}", stream);
switch (stream) {
case StreamType::Ledger:
subscriptions_->unsubLedger(session);
break;
case StreamType::Transactions:
subscriptions_->unsubTransactions(session);
break;
case StreamType::TransactionsProposed:
subscriptions_->unsubProposedTransactions(session);
break;
case StreamType::Validations:
subscriptions_->unsubValidation(session);
break;
case StreamType::Manifests:
subscriptions_->unsubManifest(session);
break;
case StreamType::BookChanges:
subscriptions_->unsubBookChanges(session);
break;
case StreamType::Server:
case StreamType::PeerStatus:
case StreamType::Consensus:
// Not served by Clio.
break;
}
}
}
void
UnsubscribeHandler::unsubscribeFromAccounts(
std::vector<std::string> accounts,
std::vector<xrpl::AccountID> const& accounts,
feed::SubscriberSharedPtr const& session
) const
{
for (auto const& account : accounts) {
auto const accountID = accountFromStringStrict(account);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
subscriptions_->unsubAccount(*accountID, session);
}
for (auto const& account : accounts)
subscriptions_->unsubAccount(account, session);
}
void
UnsubscribeHandler::unsubscribeFromProposedAccounts(
std::vector<std::string> accountsProposed,
std::vector<xrpl::AccountID> const& accountsProposed,
feed::SubscriberSharedPtr const& session
) const
{
for (auto const& account : accountsProposed) {
auto const accountID = accountFromStringStrict(account);
// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
subscriptions_->unsubProposedAccount(*accountID, session);
}
for (auto const& account : accountsProposed)
subscriptions_->unsubProposedAccount(account, session);
}
void
UnsubscribeHandler::unsubscribeFromBooks(
std::vector<OrderBook> const& books,
@@ -155,44 +108,4 @@ UnsubscribeHandler::unsubscribeFromBooks(
}
}
UnsubscribeHandler::Input
tag_invoke(boost::json::value_to_tag<UnsubscribeHandler::Input>, boost::json::value const& jv)
{
auto input = UnsubscribeHandler::Input{};
auto const& jsonObject = jv.as_object();
if (auto const& streams = jsonObject.find(JS(streams)); streams != jsonObject.end()) {
input.streams = std::vector<std::string>();
for (auto const& stream : streams->value().as_array())
input.streams->push_back(boost::json::value_to<std::string>(stream));
}
if (auto const& accounts = jsonObject.find(JS(accounts)); accounts != jsonObject.end()) {
input.accounts = std::vector<std::string>();
for (auto const& account : accounts->value().as_array())
input.accounts->push_back(boost::json::value_to<std::string>(account));
}
if (auto const& accountsProposed = jsonObject.find(JS(accounts_proposed));
accountsProposed != jsonObject.end()) {
input.accountsProposed = std::vector<std::string>();
for (auto const& account : accountsProposed->value().as_array())
input.accountsProposed->push_back(boost::json::value_to<std::string>(account));
}
if (auto const& books = jsonObject.find(JS(books)); books != jsonObject.end()) {
input.books = std::vector<UnsubscribeHandler::OrderBook>();
for (auto const& book : books->value().as_array()) {
auto internalBook = UnsubscribeHandler::OrderBook{};
auto const& bookObject = book.as_object();
if (auto const& both = bookObject.find(JS(both)); both != bookObject.end())
internalBook.both = both->value().as_bool();
auto const parsedBookMaybe = parseBook(book.as_object());
ASSERT(parsedBookMaybe.has_value(), "Invalid book format");
internalBook.book = *parsedBookMaybe;
input.books->push_back(internalBook);
}
}
return input;
}
} // namespace rpc

View File

@@ -2,20 +2,14 @@
#include "feed/SubscriptionManagerInterface.hpp"
#include "feed/Types.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
#include <xrpl/protocol/Book.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/unsubscribe/Types.hpp>
#include <xrpl/protocol/AccountID.h>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace rpc {
@@ -28,27 +22,19 @@ namespace rpc {
* For more details see: https://xrpl.org/unsubscribe.html
*/
class UnsubscribeHandler {
class UnsubscribeHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::unsubscribe::Input> {
std::shared_ptr<feed::SubscriptionManagerInterface> subscriptions_;
public:
/**
* @brief A struct to hold one order book
*/
struct OrderBook {
xrpl::Book book;
bool both = false;
};
using OrderBook = rpc::spec::handlers::unsubscribe::OrderBook;
/**
* @brief A struct to hold the input data for the command
* @brief A subscribable stream type.
*/
struct Input {
std::optional<std::vector<std::string>> accounts;
std::optional<std::vector<std::string>> streams;
std::optional<std::vector<std::string>> accountsProposed;
std::optional<std::vector<OrderBook>> books;
};
using StreamType = rpc::spec::handlers::unsubscribe::StreamType;
using Output = VoidOutput;
using Result = HandlerReturnType<Output>;
@@ -60,15 +46,6 @@ public:
*/
UnsubscribeHandler(std::shared_ptr<feed::SubscriptionManagerInterface> const& subscriptions);
/**
* @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);
/**
* @brief Process the Unsubscribe command
*
@@ -82,19 +59,19 @@ public:
private:
void
unsubscribeFromStreams(
std::vector<std::string> const& streams,
std::vector<StreamType> const& streams,
feed::SubscriberSharedPtr const& session
) const;
void
unsubscribeFromAccounts(
std::vector<std::string> accounts,
std::vector<xrpl::AccountID> const& accounts,
feed::SubscriberSharedPtr const& session
) const;
void
unsubscribeFromProposedAccounts(
std::vector<std::string> accountsProposed,
std::vector<xrpl::AccountID> const& accountsProposed,
feed::SubscriberSharedPtr const& session
) const;
@@ -103,15 +80,6 @@ private:
std::vector<OrderBook> const& books,
feed::SubscriberSharedPtr const& session
) const;
/**
* @brief Convert a JSON object to an Input
*
* @param jv The JSON object to convert
* @return The Input object
*/
friend Input
tag_invoke(boost::json::value_to_tag<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -5,13 +5,11 @@
#include "rpc/RPCHelpers.hpp"
#include "rpc/common/Types.hpp"
#include "util/Assert.hpp"
#include "util/JsonUtils.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <rpcspec/Errors.hpp>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerHeader.h>
@@ -22,10 +20,9 @@
#include <xrpl/protocol/Serializer.h>
#include <xrpl/protocol/jss.h>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <string>
#include <utility>
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);
}
@@ -61,18 +58,16 @@ VaultInfoHandler::VaultInfoHandler(std::shared_ptr<BackendInterface> sharedPtrBa
VaultInfoHandler::Result
VaultInfoHandler::process(VaultInfoHandler::Input const& input, Context const& ctx) const
{
// vault info input must either have owner and sequence, or vault_id only.
if (not validate(input))
return Error{ClioError::RpcMalformedRequest};
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 +79,11 @@ VaultInfoHandler::process(VaultInfoHandler::Input const& input, Context const& c
// Extract the vault keylet based on input
auto const vaultKeylet = [&]() -> std::expected<xrpl::Keylet, Status> {
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 +91,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 +148,4 @@ tag_invoke(
};
}
VaultInfoHandler::Input
tag_invoke(boost::json::value_to_tag<VaultInfoHandler::Input>, 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<uint32_t>(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

View File

@@ -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 <boost/json/conversion.hpp>
#include <boost/json/object.hpp>
#include <boost/json/value.hpp>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/jss.h>
#include <rpcspec/HandlerFor.hpp>
#include <rpcspec/handlers/vault_info/Types.hpp>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
namespace rpc {
/**
* @brief The vault_info command retrieves information about a vault, currency, shares etc.
*/
class VaultInfoHandler {
class VaultInfoHandler : public rpc::spec::HandlerFor<rpc::spec::handlers::vault_info::Input> {
std::shared_ptr<BackendInterface> sharedPtrBackend_;
public:
@@ -35,16 +27,6 @@ public:
*/
VaultInfoHandler(std::shared_ptr<BackendInterface> sharedPtrBackend);
/**
* @brief A struct to hold the input data for the command
*/
struct Input {
std::optional<std::string> vaultID;
std::optional<std::string> owner;
std::optional<uint32_t> tnxSequence;
std::optional<uint32_t> ledgerIndex;
};
/**
* @brief A struct to hold the output data for the command
*/
@@ -56,36 +38,6 @@ public:
using Result = HandlerReturnType<Output>;
/**
* @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<uint32_t>{}, 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<Input>, boost::json::value const& jv);
};
} // namespace rpc

View File

@@ -1,9 +1,7 @@
#pragma once
#include "rpc/Errors.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include <boost/json/conversion.hpp>
#include <boost/json/value.hpp>
@@ -24,30 +22,11 @@
namespace tests::common {
// input data for the test handlers below
struct TestInput {
std::string hello;
std::optional<uint32_t> limit;
};
// output data produced by the test handlers below
struct TestOutput {
std::string computed;
};
// must be implemented as per rpc/common/Concepts.h
inline TestInput
tag_invoke(boost::json::value_to_tag<TestInput>, boost::json::value const& jv)
{
std::optional<uint32_t> optLimit;
if (jv.as_object().contains("limit"))
optLimit = jv.at("limit").as_int64();
return {
.hello = boost::json::value_to<std::string>(jv.as_object().at("hello")), .limit = optLimit
};
}
// must be implemented as per rpc/common/Concepts.h
inline void
tag_invoke(boost::json::value_from_tag, boost::json::value& jv, TestOutput const& output)
@@ -55,33 +34,6 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, TestOutput const
jv = {{"computed", output.computed}};
}
// example handler
class HandlerFake {
public:
using Input = TestInput;
using Output = TestOutput;
using Result = rpc::HandlerReturnType<Output>;
static rpc::RpcSpecConstRef
spec([[maybe_unused]] uint32_t apiVersion)
{
using namespace rpc::validation;
static auto const kRpcSpec = rpc::RpcSpec{
{"hello", Required{}, Type<std::string>{}, EqualTo{"world"}},
{"limit", Type<uint32_t>{}, Between<uint32_t>{0, 100}}, // optional field
};
return kRpcSpec;
}
static Result
process(Input input, [[maybe_unused]] rpc::Context const& ctx)
{
return Output{input.hello + '_' + std::to_string(input.limit.value_or(0))};
}
};
class NoInputHandlerFake {
public:
using Output = TestOutput;
@@ -94,34 +46,6 @@ public:
}
};
// example handler that returns custom error
class FailingHandlerFake {
public:
using Input = TestInput;
using Output = TestOutput;
using Result = rpc::HandlerReturnType<Output>;
static rpc::RpcSpecConstRef
spec([[maybe_unused]] uint32_t apiVersion)
{
using namespace rpc::validation;
static auto const kRpcSpec = rpc::RpcSpec{
{"hello", Required{}, Type<std::string>{}, EqualTo{"world"}},
{"limit", Type<uint32_t>{}, Between<uint32_t>{0u, 100u}}, // optional field
};
return kRpcSpec;
}
static Result
process([[maybe_unused]] Input input, [[maybe_unused]] rpc::Context const& ctx)
{
// always fail
return rpc::Error{rpc::Status{"Very custom error"}};
}
};
struct InOutFake {
std::string something;
@@ -144,15 +68,6 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, InOutFake const&
jv = {{"something", output.something}};
}
struct HandlerMock {
using Input = InOutFake;
using Output = InOutFake;
using Result = rpc::HandlerReturnType<Output>;
MOCK_METHOD(rpc::RpcSpecConstRef, spec, (uint32_t), (const));
MOCK_METHOD(Result, process, (Input, rpc::Context const&), (const));
};
struct HandlerWithoutInputMock {
using Output = InOutFake;
using Result = rpc::HandlerReturnType<Output>;
@@ -164,15 +79,26 @@ struct HandlerWithoutInputMock {
// `specFor` hook, so the fake Input below needs its own namespace to host that hook.
namespace typed_fake {
// input data for TypedHandlerFake; mirrors TestInput so the two paths stay comparable
// input data for TypedHandlerFake
struct TypedInput {
std::string hello;
std::optional<uint32_t> limit;
};
inline constexpr auto kInputSpec = rpc::spec::spec<TypedInput>(
rpc::spec::field("hello", &TypedInput::hello, rpc::spec::required, rpc::spec::asString),
rpc::spec::field("limit", &TypedInput::limit, rpc::spec::asUint32),
rpc::spec::field(
"hello",
&TypedInput::hello,
rpc::spec::required,
rpc::spec::oneOf("world"),
rpc::spec::asString
),
rpc::spec::field(
"limit",
&TypedInput::limit,
rpc::spec::between(uint32_t{0}, uint32_t{100}),
rpc::spec::asUint32
),
rpc::spec::field("old_field", rpc::spec::deprecated)
);

View File

@@ -94,12 +94,9 @@ target_sources(
migration/SpecTests.cpp
# RPC
rpc/APIVersionTests.cpp
rpc/BaseTests.cpp
rpc/CountersTests.cpp
rpc/ErrorTests.cpp
rpc/ForwardingProxyTests.cpp
rpc/common/CheckersTests.cpp
rpc/common/SpecsTests.cpp
rpc/common/TypesTests.cpp
rpc/common/impl/HandlerProviderTests.cpp
rpc/filters/impl/DelegateTransactionsFilterTests.cpp
@@ -146,7 +143,6 @@ target_sources(
rpc/handlers/UnsubscribeTests.cpp
rpc/handlers/VersionHandlerTests.cpp
rpc/handlers/VaultInfoTests.cpp
rpc/JsonBoolTests.cpp
rpc/RPCEngineTests.cpp
rpc/RPCHelpersTests.cpp
rpc/WorkQueueTests.cpp

View File

@@ -1,941 +0,0 @@
#include "rpc/common/MetaProcessors.hpp"
#include "rpc/common/Modifiers.hpp"
#include "rpc/common/Specs.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/ValidationHelpers.hpp"
#include "rpc/common/Validators.hpp"
#include <boost/json/array.hpp>
#include <boost/json/object.hpp>
#include <boost/json/parse.hpp>
#include <boost/json/value.hpp>
#include <fmt/format.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <rpcspec/Errors.hpp>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <cstdint>
#include <limits>
#include <string>
#include <string_view>
using namespace std;
using namespace rpc;
using namespace rpc::validation;
using namespace rpc::meta;
using namespace rpc::modifiers;
class RPCBaseTest : public virtual ::testing::Test {};
TEST_F(RPCBaseTest, CheckTypeString)
{
auto const jString = boost::json::value("a string");
ASSERT_TRUE(checkType<string>(jString));
ASSERT_FALSE(checkType<int>(jString));
}
TEST_F(RPCBaseTest, CheckTypeUint)
{
auto const jUint = boost::json::value(123u);
ASSERT_TRUE(checkType<uint32_t>(jUint));
ASSERT_TRUE(checkType<int32_t>(jUint));
ASSERT_FALSE(checkType<bool>(jUint));
}
TEST_F(RPCBaseTest, CheckTypeInt)
{
auto jInt = boost::json::value(123);
ASSERT_TRUE(checkType<int32_t>(jInt));
ASSERT_TRUE(checkType<uint32_t>(jInt));
ASSERT_FALSE(checkType<bool>(jInt));
jInt = boost::json::value(-123);
ASSERT_TRUE(checkType<int32_t>(jInt));
ASSERT_FALSE(checkType<uint32_t>(jInt)); // Unsigned can't be negative
ASSERT_FALSE(checkType<bool>(jInt));
}
TEST_F(RPCBaseTest, CheckTypeBool)
{
auto const jBool = boost::json::value(true);
ASSERT_TRUE(checkType<bool>(jBool));
ASSERT_FALSE(checkType<int>(jBool));
}
TEST_F(RPCBaseTest, CheckTypeDouble)
{
auto const jDouble = boost::json::value(0.123);
ASSERT_TRUE(checkType<double>(jDouble));
ASSERT_TRUE(checkType<float>(jDouble));
ASSERT_FALSE(checkType<bool>(jDouble));
}
TEST_F(RPCBaseTest, CheckTypeArray)
{
auto const jArr = boost::json::value({1, 2, 3});
ASSERT_TRUE(checkType<boost::json::array>(jArr));
ASSERT_FALSE(checkType<int>(jArr));
}
TEST_F(RPCBaseTest, CheckTypeAndClampValueUnchanged)
{
auto jUint = boost::json::value(123u);
ASSERT_TRUE(checkTypeAndClamp<uint32_t>(jUint));
ASSERT_EQ(jUint.as_uint64(), 123u);
ASSERT_TRUE(checkTypeAndClamp<int32_t>(jUint));
ASSERT_EQ(jUint.as_uint64(), 123u);
auto jInt = boost::json::value(123);
ASSERT_TRUE(checkTypeAndClamp<int32_t>(jInt));
ASSERT_EQ(jInt.as_int64(), 123);
ASSERT_TRUE(checkTypeAndClamp<uint32_t>(jInt));
ASSERT_EQ(jInt.as_int64(), 123);
jInt = boost::json::value(-123);
ASSERT_TRUE(checkTypeAndClamp<int32_t>(jInt));
ASSERT_EQ(jInt.as_int64(), -123);
}
TEST_F(RPCBaseTest, CheckTypeAndClampInvalidValues)
{
auto jInt = boost::json::value(-123);
ASSERT_FALSE(checkTypeAndClamp<uint32_t>(jInt)); // Unsigned can't be negative
}
TEST_F(RPCBaseTest, CheckTypeAndClampOverflow)
{
auto jBigUint = boost::json::value(std::numeric_limits<uint64_t>::max());
ASSERT_TRUE(checkTypeAndClamp<uint32_t>(jBigUint));
ASSERT_EQ(jBigUint.as_uint64(), std::numeric_limits<uint32_t>::max());
auto jBigInt = boost::json::value(std::numeric_limits<int64_t>::max());
ASSERT_TRUE(checkTypeAndClamp<int32_t>(jBigInt));
ASSERT_EQ(jBigInt.as_int64(), std::numeric_limits<int32_t>::max());
}
TEST_F(RPCBaseTest, CheckTypeAndClampUnderflow)
{
auto jLowInt = boost::json::value(std::numeric_limits<int64_t>::min());
ASSERT_TRUE(checkTypeAndClamp<int32_t>(jLowInt));
ASSERT_EQ(jLowInt.as_int64(), std::numeric_limits<int32_t>::min());
}
TEST_F(RPCBaseTest, TypeValidator)
{
auto spec = RpcSpec{
{"uint", Type<uint32_t>{}},
{"int", Type<int32_t>{}},
{"str", Type<string>{}},
{"double", Type<double>{}},
{"bool", Type<bool>{}},
{"arr", Type<boost::json::array>{}},
};
auto passingInput = boost::json::parse(R"JSON({
"uint": 123,
"int": 321,
"str": "a string",
"double": 1.0,
"bool": true,
"arr": []
})JSON");
ASSERT_TRUE(spec.process(passingInput));
{
auto failingInput = boost::json::parse(R"JSON({ "uint": "a string" })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
{
auto failingInput = boost::json::parse(R"JSON({ "int": "a string" })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
{
auto failingInput = boost::json::parse(R"JSON({ "str": 1234 })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
{
auto failingInput = boost::json::parse(R"JSON({ "double": "a string" })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
{
auto failingInput = boost::json::parse(R"JSON({ "bool": "a string" })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
{
auto failingInput = boost::json::parse(R"JSON({ "arr": "a string" })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
}
TEST_F(RPCBaseTest, TypeValidatorMultipleTypes)
{
auto spec = RpcSpec{
// either int or string
{"test", Type<uint32_t, string>{}},
};
auto passingInput = boost::json::parse(R"JSON({ "test": "1234" })JSON");
ASSERT_TRUE(spec.process(passingInput));
auto passingInput2 = boost::json::parse(R"JSON({ "test": 1234 })JSON");
ASSERT_TRUE(spec.process(passingInput2));
auto failingInput = boost::json::parse(R"JSON({ "test": true })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
TEST_F(RPCBaseTest, RequiredValidator)
{
auto spec = RpcSpec{
{"required", Required{}},
};
auto passingInput = boost::json::parse(R"JSON({ "required": "present" })JSON");
ASSERT_TRUE(spec.process(passingInput));
auto passingInput2 = boost::json::parse(R"JSON({ "required": true })JSON");
ASSERT_TRUE(spec.process(passingInput2));
auto failingInput = boost::json::parse(R"JSON({})JSON");
ASSERT_FALSE(spec.process(failingInput));
}
TEST_F(RPCBaseTest, BetweenValidator)
{
auto spec = RpcSpec{
{"amount", Between<uint32_t>{10u, 20u}},
};
auto passingInput = boost::json::parse(R"JSON({ "amount": 15 })JSON");
ASSERT_TRUE(spec.process(passingInput));
auto passingInput2 = boost::json::parse(R"JSON({ "amount": 10 })JSON");
ASSERT_TRUE(spec.process(passingInput2));
auto passingInput3 = boost::json::parse(R"JSON({ "amount": 20 })JSON");
ASSERT_TRUE(spec.process(passingInput3));
auto failingInput = boost::json::parse(R"JSON({ "amount": 9 })JSON");
ASSERT_FALSE(spec.process(failingInput));
auto failingInput2 = boost::json::parse(R"JSON({ "amount": 21 })JSON");
ASSERT_FALSE(spec.process(failingInput2));
}
TEST_F(RPCBaseTest, MinValidator)
{
auto spec = RpcSpec{
{"amount", Min{6}},
};
auto passingInput = boost::json::parse(R"JSON({ "amount": 7 })JSON");
ASSERT_TRUE(spec.process(passingInput));
auto passingInput2 = boost::json::parse(R"JSON({ "amount": 6 })JSON");
ASSERT_TRUE(spec.process(passingInput2));
auto failingInput = boost::json::parse(R"JSON({ "amount": 5 })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
TEST_F(RPCBaseTest, MinValidatorAfterType)
{
auto spec = RpcSpec{
{"amount", Type<std::uint32_t>{}, Min{std::numeric_limits<uint32_t>::max()}},
{"amount2", Type<std::int32_t>{}, Min{std::numeric_limits<int32_t>::max()}},
{"amount3", Type<std::int32_t>{}, Min{std::numeric_limits<int32_t>::min()}},
};
auto bigInput = boost::json::parse(
R"JSON({ "amount": 9999999999, "amount2": 9999999999, "amount3": -9999999999 })JSON"
);
ASSERT_TRUE(spec.process(bigInput)); // type check clamps to type's max/min value
}
TEST_F(RPCBaseTest, MaxValidator)
{
auto spec = RpcSpec{
{"amount", Max{6}},
};
auto passingInput = boost::json::parse(R"JSON({ "amount": 5 })JSON");
ASSERT_TRUE(spec.process(passingInput));
auto passingInput2 = boost::json::parse(R"JSON({ "amount": 6 })JSON");
ASSERT_TRUE(spec.process(passingInput2));
auto failingInput = boost::json::parse(R"JSON({ "amount": 7 })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
TEST_F(RPCBaseTest, MaxValidatorAfterType)
{
auto spec = RpcSpec{
{"amount", Type<std::uint32_t>{}, Max{std::numeric_limits<uint32_t>::max()}},
{"amount2", Type<std::int32_t>{}, Max{std::numeric_limits<int32_t>::max()}},
{"amount3", Type<std::int32_t>{}, Max{std::numeric_limits<int32_t>::min()}},
};
auto bigInput = boost::json::parse(
R"JSON({ "amount": 9999999999, "amount2": 9999999999, "amount3": -9999999999 })JSON"
);
ASSERT_TRUE(spec.process(bigInput)); // type check clamps to type's min/max value
}
TEST_F(RPCBaseTest, OneOfValidator)
{
auto spec = RpcSpec{
{"currency", OneOf{"XRP", "USD"}},
};
auto passingInput = boost::json::parse(R"JSON({ "currency": "XRP" })JSON");
ASSERT_TRUE(spec.process(passingInput));
auto passingInput2 = boost::json::parse(R"JSON({ "currency": "USD" })JSON");
ASSERT_TRUE(spec.process(passingInput2));
auto failingInput = boost::json::parse(R"JSON({ "currency": "PRX" })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
TEST_F(RPCBaseTest, EqualToValidator)
{
auto spec = RpcSpec{
{"exact", EqualTo{"CaseSensitive"}},
};
auto passingInput = boost::json::parse(R"JSON({ "exact": "CaseSensitive" })JSON");
ASSERT_TRUE(spec.process(passingInput));
auto failingInput = boost::json::parse(R"JSON({ "exact": "Different" })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
TEST_F(RPCBaseTest, ArrayAtValidator)
{
auto spec = RpcSpec{
{"arr",
Required{},
Type<boost::json::array>{},
ValidateArrayAt{
0,
{
{"limit", Required{}, Type<uint32_t>{}, Between<uint32_t>{0, 100}},
}
}},
{"arr2",
ValidateArrayAt{
0,
{
{"limit", Required{}, Type<uint32_t>{}, Between<uint32_t>{0, 100}},
}
}},
};
// clang-format on
auto passingInput = boost::json::parse(R"JSON({ "arr": [{"limit": 42}] })JSON");
ASSERT_TRUE(spec.process(passingInput));
auto failingInput = boost::json::parse(R"JSON({ "arr": [{"limit": "not int"}] })JSON");
ASSERT_FALSE(spec.process(failingInput));
failingInput =
boost::json::parse(R"JSON({ "arr": [{"limit": 42}], "arr2": "not array type" })JSON");
ASSERT_FALSE(spec.process(failingInput));
failingInput = boost::json::parse(R"JSON({ "arr": [] })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
TEST_F(RPCBaseTest, IfTypeValidator)
{
auto spec = RpcSpec{
{"mix",
Required{},
Type<std::string, boost::json::object>{},
IfType<boost::json::object>{
Section{{"limit", Required{}, Type<uint32_t>{}, Between<uint32_t>{0, 100}}},
Section{{"limit2", Required{}, Type<uint32_t>{}, Between<uint32_t>{0, 100}}}
},
IfType<std::string>{CustomValidators::uint256HexStringValidator}},
{"mix2",
Section{{"limit", Required{}, Type<uint32_t>{}, Between<uint32_t>{0, 100}}},
Type<std::string, boost::json::object>{}},
};
// if json object pass
auto passingInput = boost::json::parse(R"JSON({ "mix": {"limit": 42, "limit2": 22} })JSON");
ASSERT_TRUE(spec.process(passingInput));
// if string pass
passingInput = boost::json::parse(
R"JSON({ "mix": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC" })JSON"
);
ASSERT_TRUE(spec.process(passingInput));
// if json object fail at first requirement
auto failingInput = boost::json::parse(R"JSON({ "mix": {"limit": "not int"} })JSON");
ASSERT_FALSE(spec.process(failingInput));
// if json object fail at second requirement
failingInput = boost::json::parse(R"JSON({ "mix": {"limit": 22, "limit2": "y"} })JSON");
ASSERT_FALSE(spec.process(failingInput));
// if string fail
failingInput = boost::json::parse(R"JSON({ "mix": "not hash" })JSON");
ASSERT_FALSE(spec.process(failingInput));
// type check fail
failingInput = boost::json::parse(R"JSON({ "mix": 1213 })JSON");
ASSERT_FALSE(spec.process(failingInput));
failingInput =
boost::json::parse(R"JSON({ "mix": {"limit": 42, "limit2": 22}, "mix2": 1213 })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
TEST_F(RPCBaseTest, WithCustomError)
{
auto const spec = RpcSpec{
{"transaction",
WithCustomError{
CustomValidators::uint256HexStringValidator,
rpc::Status{xrpl::RpcBadFeature, "MyCustomError"}
}},
{"other",
WithCustomError{
Type<std::string>{}, rpc::Status{xrpl::RpcAlreadyMultisig, "MyCustomError2"}
}}
};
auto passingInput = boost::json::parse(
R"JSON({ "transaction": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC", "other": "1"})JSON"
);
ASSERT_TRUE(spec.process(passingInput));
auto failingInput = boost::json::parse(
R"JSON({ "transaction": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515B"})JSON"
);
auto err = spec.process(failingInput);
ASSERT_FALSE(err);
ASSERT_EQ(err.error().message, "MyCustomError");
ASSERT_EQ(err.error(), xrpl::RpcBadFeature);
failingInput = boost::json::parse(R"JSON({ "other": 1})JSON");
err = spec.process(failingInput);
ASSERT_FALSE(err);
ASSERT_EQ(err.error().message, "MyCustomError2");
ASSERT_EQ(err.error(), xrpl::RpcAlreadyMultisig);
}
TEST_F(RPCBaseTest, TimeFormatValidator)
{
auto const spec = RpcSpec{
{"date", TimeFormatValidator{"%Y-%m-%dT%H:%M:%SZ"}},
};
auto passingInput = boost::json::parse(R"JSON({ "date": "2023-01-01T00:00:00Z" })JSON");
EXPECT_TRUE(spec.process(passingInput));
passingInput = boost::json::parse("123");
EXPECT_TRUE(spec.process(passingInput));
// key not exists
passingInput = boost::json::parse(R"JSON({ "date1": "2023-01-01T00:00:00Z" })JSON");
EXPECT_TRUE(spec.process(passingInput));
auto failingInput = boost::json::parse(R"JSON({ "date": "2023-01-01-00:00:00" })JSON");
auto err = spec.process(failingInput);
EXPECT_FALSE(err);
EXPECT_EQ(err.error(), xrpl::RpcInvalidParams);
failingInput = boost::json::parse(R"JSON({ "date": "01-01-2024T00:00:00" })JSON");
EXPECT_FALSE(spec.process(failingInput));
failingInput = boost::json::parse(R"JSON({ "date": "2024-01-01T29:00:00" })JSON");
EXPECT_FALSE(spec.process(failingInput));
failingInput = boost::json::parse(R"JSON({ "date": "" })JSON");
EXPECT_FALSE(spec.process(failingInput));
failingInput = boost::json::parse(R"JSON({ "date": 1 })JSON");
err = spec.process(failingInput);
EXPECT_FALSE(err);
EXPECT_EQ(err.error(), xrpl::RpcInvalidParams);
}
TEST_F(RPCBaseTest, CustomValidator)
{
auto customFormatCheck = CustomValidator{
[](boost::json::value const& value, std::string_view /* key */) -> MaybeError {
return value.as_string().size() == 34 ? MaybeError{} : Error{rpc::Status{"Uh oh"}};
}
};
auto spec = RpcSpec{
{"taker", customFormatCheck},
};
auto passingInput =
boost::json::parse(R"JSON({ "taker": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59" })JSON");
ASSERT_TRUE(spec.process(passingInput));
auto failingInput = boost::json::parse(R"JSON({ "taker": "wrongformat" })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
TEST_F(RPCBaseTest, NotSupported)
{
auto spec = RpcSpec{
{"taker", Type<uint32_t>{}, NotSupported{123}},
{"getter", NotSupported{}},
};
auto passingInput = boost::json::parse(R"JSON({ "taker": 2 })JSON");
ASSERT_TRUE(spec.process(passingInput));
auto failingInput = boost::json::parse(R"JSON({ "taker": 123 })JSON");
ASSERT_FALSE(spec.process(failingInput));
failingInput = boost::json::parse(R"JSON({ "taker": 2, "getter": 2 })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
TEST_F(RPCBaseTest, LedgerIndexValidator)
{
auto spec = RpcSpec{
{"ledgerIndex", CustomValidators::ledgerIndexValidator},
};
auto passingInput = boost::json::parse(R"JSON({ "ledgerIndex": "validated" })JSON");
ASSERT_TRUE(spec.process(passingInput));
passingInput = boost::json::parse(R"JSON({ "ledgerIndex": "256" })JSON");
ASSERT_TRUE(spec.process(passingInput));
passingInput = boost::json::parse(R"JSON({ "ledgerIndex": 256 })JSON");
ASSERT_TRUE(spec.process(passingInput));
auto failingInput = boost::json::parse(R"JSON({ "ledgerIndex": "wrongformat" })JSON");
auto err = spec.process(failingInput);
ASSERT_FALSE(err);
ASSERT_EQ(err.error().message, "ledgerIndexMalformed");
failingInput = boost::json::parse(R"JSON({ "ledgerIndex": true })JSON");
err = spec.process(failingInput);
ASSERT_FALSE(err);
ASSERT_EQ(err.error().message, "ledgerIndexMalformed");
}
TEST_F(RPCBaseTest, AccountValidator)
{
auto spec = RpcSpec{
{"account", CustomValidators::accountValidator},
};
auto failingInput = boost::json::parse(R"JSON({ "account": 256 })JSON");
ASSERT_FALSE(spec.process(failingInput));
failingInput =
boost::json::parse(R"JSON({ "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jp" })JSON");
ASSERT_FALSE(spec.process(failingInput));
failingInput = boost::json::parse(
R"JSON({ "account": "02000000000000000000000000000000000000000000000000000000000000000" })JSON"
);
ASSERT_FALSE(spec.process(failingInput));
failingInput =
boost::json::parse(R"JSON({ "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jp?" })JSON");
ASSERT_FALSE(spec.process(failingInput));
auto passingInput =
boost::json::parse(R"JSON({ "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" })JSON");
ASSERT_TRUE(spec.process(passingInput));
passingInput = boost::json::parse(
R"JSON({ "account": "020000000000000000000000000000000000000000000000000000000000000000" })JSON"
);
ASSERT_TRUE(spec.process(passingInput));
}
TEST_F(RPCBaseTest, AccountBase58Validator)
{
auto spec = RpcSpec{
{"account", CustomValidators::accountBase58Validator},
};
auto failingInput = boost::json::parse(R"JSON({ "account": 256 })JSON");
ASSERT_FALSE(spec.process(failingInput));
failingInput =
boost::json::parse(R"JSON({ "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jp" })JSON");
ASSERT_FALSE(spec.process(failingInput));
failingInput = boost::json::parse(
R"JSON({ "account": "020000000000000000000000000000000000000000000000000000000000000000" })JSON"
);
ASSERT_FALSE(spec.process(failingInput));
failingInput =
boost::json::parse(R"JSON({ "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jp?" })JSON");
ASSERT_FALSE(spec.process(failingInput));
auto passingInput =
boost::json::parse(R"JSON({ "account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" })JSON");
ASSERT_TRUE(spec.process(passingInput));
}
TEST_F(RPCBaseTest, AccountMarkerValidator)
{
auto spec = RpcSpec{
{"marker", CustomValidators::accountMarkerValidator},
};
auto failingInput = boost::json::parse(R"JSON({ "marker": 256 })JSON");
ASSERT_FALSE(spec.process(failingInput));
failingInput = boost::json::parse(R"JSON({ "marker": "testtest" })JSON");
ASSERT_FALSE(spec.process(failingInput));
failingInput = boost::json::parse(R"JSON({ "marker": "ABAB1234:1H" })JSON");
ASSERT_FALSE(spec.process(failingInput));
auto passingInput = boost::json::parse(R"JSON({ "account": "ABAB1234:123" })JSON");
ASSERT_TRUE(spec.process(passingInput));
}
TEST_F(RPCBaseTest, Uint160HexStringValidator)
{
auto const spec = RpcSpec{{"marker", CustomValidators::uint160HexStringValidator}};
auto passingInput =
boost::json::parse(R"JSON({ "marker": "F609A18102218C75767209946A77523CBD97E225"})JSON");
ASSERT_TRUE(spec.process(passingInput));
auto failingInput = boost::json::parse(R"JSON({ "marker": 160})JSON");
auto err = spec.process(failingInput);
ASSERT_FALSE(err);
ASSERT_EQ(err.error().message, "markerNotString");
failingInput = boost::json::parse(
R"JSON({ "marker": "F609A18102218C75767209946A77523CBD97E2253515BC"})JSON"
);
err = spec.process(failingInput);
ASSERT_FALSE(err);
ASSERT_EQ(err.error().message, "markerMalformed");
}
TEST_F(RPCBaseTest, Uint192HexStringValidator)
{
auto const spec = RpcSpec{{"mpt_issuance_id", CustomValidators::uint192HexStringValidator}};
auto passingInput = boost::json::parse(
R"JSON({ "mpt_issuance_id": "0000012F27A9DE73EAA1E8831FA253E19030A17E2D038198"})JSON"
);
ASSERT_TRUE(spec.process(passingInput));
auto failingInput = boost::json::parse(R"JSON({ "mpt_issuance_id": 192})JSON");
auto err = spec.process(failingInput);
ASSERT_FALSE(err);
ASSERT_EQ(err.error().message, "mpt_issuance_idNotString");
failingInput = boost::json::parse(
R"JSON({ "mpt_issuance_id": "0000012F27A9DE73EAA1E8831FA253E19030A17E2D038198983515BC"})JSON"
);
err = spec.process(failingInput);
ASSERT_FALSE(err);
ASSERT_EQ(err.error().message, "mpt_issuance_idMalformed");
}
TEST_F(RPCBaseTest, Uint256HexStringValidator)
{
auto const spec = RpcSpec{{"transaction", CustomValidators::uint256HexStringValidator}};
auto passingInput = boost::json::parse(
R"JSON({ "transaction": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC"})JSON"
);
ASSERT_TRUE(spec.process(passingInput));
auto failingInput = boost::json::parse(R"JSON({ "transaction": 256})JSON");
auto err = spec.process(failingInput);
ASSERT_FALSE(err);
ASSERT_EQ(err.error().message, "transactionNotString");
failingInput = boost::json::parse(
R"JSON({ "transaction": "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC"})JSON"
);
err = spec.process(failingInput);
ASSERT_FALSE(err);
ASSERT_EQ(err.error().message, "transactionMalformed");
}
TEST_F(RPCBaseTest, CurrencyValidator)
{
auto const spec = RpcSpec{{"currency", CustomValidators::currencyValidator}};
auto passingInput = boost::json::parse(R"JSON({ "currency": "GBP"})JSON");
ASSERT_TRUE(spec.process(passingInput));
passingInput =
boost::json::parse(R"JSON({ "currency": "0158415500000000C1F76FF6ECB0BAC600000000"})JSON");
ASSERT_TRUE(spec.process(passingInput));
passingInput =
boost::json::parse(R"JSON({ "currency": "0158415500000000c1f76ff6ecb0bac600000000"})JSON");
ASSERT_TRUE(spec.process(passingInput));
for (auto const& currency : {"[]<", ">()", "{}|", "?!@", "#$%", "^&*"}) {
passingInput =
boost::json::parse(fmt::format(R"JSON({{ "currency": "{}" }})JSON", currency));
ASSERT_TRUE(spec.process(passingInput));
}
auto failingInput = boost::json::parse(R"JSON({ "currency": 256})JSON");
auto err = spec.process(failingInput);
ASSERT_FALSE(err);
ASSERT_EQ(err.error().message, "currencyNotString");
failingInput = boost::json::parse(R"JSON({ "currency": "12314"})JSON");
err = spec.process(failingInput);
ASSERT_FALSE(err);
ASSERT_EQ(err.error().message, "malformedCurrency");
}
TEST_F(RPCBaseTest, IssuerValidator)
{
auto const spec = RpcSpec{{"issuer", CustomValidators::issuerValidator}};
auto passingInput =
boost::json::parse(R"JSON({ "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"})JSON");
ASSERT_TRUE(spec.process(passingInput));
auto failingInput = boost::json::parse(R"JSON({ "issuer": 256})JSON");
auto err = spec.process(failingInput);
ASSERT_FALSE(err);
ASSERT_EQ(err.error().message, "issuerNotString");
failingInput = boost::json::parse(
fmt::format(R"JSON({{ "issuer": "{}"}})JSON", toBase58(xrpl::noAccount()))
);
err = spec.process(failingInput);
ASSERT_FALSE(err);
}
TEST_F(RPCBaseTest, BookTakerValidator)
{
auto const spec = RpcSpec{{"taker_gets", CustomValidators::bookTakerValidator}};
// An asset specified by currency, optionally with an issuer, is valid.
auto passingInput = boost::json::parse(R"JSON({ "taker_gets": { "currency": "XRP" }})JSON");
EXPECT_TRUE(spec.process(passingInput));
passingInput = boost::json::parse(
R"JSON({ "taker_gets": { "currency": "USD", "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" }})JSON"
);
EXPECT_TRUE(spec.process(passingInput));
// An asset specified by mpt_issuance_id alone is valid.
passingInput = boost::json::parse(
R"JSON({ "taker_gets": { "mpt_issuance_id": "000004C463C52827307480341125DA0577DEFC38405DBADD" }})JSON"
);
EXPECT_TRUE(spec.process(passingInput));
// Non-object values are deferred to the type validator; the field being absent is deferred to
// the required validator. Both pass here.
passingInput = boost::json::parse(R"JSON({ "taker_gets": "not_an_object" })JSON");
EXPECT_TRUE(spec.process(passingInput));
passingInput = boost::json::parse(R"JSON({ })JSON");
EXPECT_TRUE(spec.process(passingInput));
// Neither currency nor mpt_issuance_id -> invalidParams, reporting the currency field.
auto failingInput = boost::json::parse(R"JSON({ "taker_gets": { }})JSON");
auto err = spec.process(failingInput);
ASSERT_FALSE(err);
EXPECT_TRUE(err.error().code == CombinedError{RippledError::RpcInvalidParams});
EXPECT_EQ(err.error().message, "Missing field 'taker_gets.currency'.");
// currency and mpt_issuance_id are mutually exclusive.
failingInput = boost::json::parse(
R"JSON({ "taker_gets": { "currency": "USD", "mpt_issuance_id": "000004C463C52827307480341125DA0577DEFC38405DBADD" }})JSON"
);
err = spec.process(failingInput);
ASSERT_FALSE(err);
EXPECT_TRUE(err.error().code == CombinedError{RippledError::RpcInvalidParams});
EXPECT_EQ(err.error().message, "Invalid field 'taker_gets'.");
// mpt_issuance_id must not be combined with an issuer.
failingInput = boost::json::parse(
R"JSON({ "taker_gets": { "mpt_issuance_id": "000004C463C52827307480341125DA0577DEFC38405DBADD", "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" }})JSON"
);
err = spec.process(failingInput);
ASSERT_FALSE(err);
EXPECT_TRUE(err.error().code == CombinedError{RippledError::RpcInvalidParams});
EXPECT_EQ(err.error().message, "Invalid field 'taker_gets'.");
// A present-but-non-string currency -> expectedFieldError against '<key>.currency'.
failingInput = boost::json::parse(R"JSON({ "taker_gets": { "currency": 123 }})JSON");
err = spec.process(failingInput);
ASSERT_FALSE(err);
EXPECT_TRUE(err.error().code == CombinedError{RippledError::RpcInvalidParams});
EXPECT_EQ(err.error().message, "Invalid field 'taker_gets.currency', not string.");
// A present-but-non-string mpt_issuance_id is reported the same way (against '.currency',
// matching xrpld's validateTakerJSON).
failingInput = boost::json::parse(R"JSON({ "taker_gets": { "mpt_issuance_id": 123 }})JSON");
err = spec.process(failingInput);
ASSERT_FALSE(err);
EXPECT_TRUE(err.error().code == CombinedError{RippledError::RpcInvalidParams});
EXPECT_EQ(err.error().message, "Invalid field 'taker_gets.currency', not string.");
}
TEST_F(RPCBaseTest, BookTakerValidatorUsesFieldKey)
{
// The error messages must reflect the validated field's key (taker_pays vs taker_gets).
auto const spec = RpcSpec{{"taker_pays", CustomValidators::bookTakerValidator}};
auto failingInput = boost::json::parse(R"JSON({ "taker_pays": { }})JSON");
auto err = spec.process(failingInput);
ASSERT_FALSE(err);
EXPECT_EQ(err.error().message, "Missing field 'taker_pays.currency'.");
failingInput = boost::json::parse(
R"JSON({ "taker_pays": { "mpt_issuance_id": "000004C463C52827307480341125DA0577DEFC38405DBADD", "issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" }})JSON"
);
err = spec.process(failingInput);
ASSERT_FALSE(err);
EXPECT_EQ(err.error().message, "Invalid field 'taker_pays'.");
}
TEST_F(RPCBaseTest, SubscribeStreamValidator)
{
auto const spec = RpcSpec{{"streams", CustomValidators::subscribeStreamValidator}};
auto passingInput = boost::json::parse(
R"JSON({
"streams": [
"ledger",
"transactions_proposed",
"validations",
"transactions",
"manifests",
"transactions",
"book_changes"
]
})JSON"
);
ASSERT_TRUE(spec.process(passingInput));
auto failingInput = boost::json::parse(R"JSON({ "streams": 256})JSON");
auto err = spec.process(failingInput);
ASSERT_FALSE(err);
failingInput = boost::json::parse(R"JSON({ "streams": ["test"]})JSON");
err = spec.process(failingInput);
ASSERT_FALSE(err);
failingInput = boost::json::parse(R"JSON({ "streams": [123]})JSON");
err = spec.process(failingInput);
ASSERT_FALSE(err);
}
TEST_F(RPCBaseTest, SubscribeAccountsValidator)
{
auto const spec = RpcSpec{{"accounts", CustomValidators::subscribeAccountsValidator}};
auto passingInput = boost::json::parse(
R"JSON({ "accounts": ["rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun"]})JSON"
);
ASSERT_TRUE(spec.process(passingInput));
auto failingInput = boost::json::parse(R"JSON({ "accounts": 256})JSON");
auto err = spec.process(failingInput);
ASSERT_FALSE(err);
failingInput = boost::json::parse(R"JSON({ "accounts": ["test"]})JSON");
err = spec.process(failingInput);
ASSERT_FALSE(err);
failingInput = boost::json::parse(R"JSON({ "accounts": [123]})JSON");
err = spec.process(failingInput);
ASSERT_FALSE(err);
}
TEST_F(RPCBaseTest, ClampingModifier)
{
auto spec = RpcSpec{
{"amount", Clamp<uint32_t>{10u, 20u}},
};
auto passingInput = boost::json::parse(R"JSON({ "amount": 15 })JSON");
ASSERT_TRUE(spec.process(passingInput));
auto passingInput2 = boost::json::parse(R"JSON({ "amount": 5 })JSON");
ASSERT_TRUE(spec.process(passingInput2));
ASSERT_EQ(passingInput2.at("amount").as_uint64(), 10u); // clamped
auto passingInput3 = boost::json::parse(R"JSON({ "amount": 25 })JSON");
ASSERT_TRUE(spec.process(passingInput3));
ASSERT_EQ(passingInput3.at("amount").as_uint64(), 20u); // clamped
}
TEST_F(RPCBaseTest, ToLowerModifier)
{
auto spec = RpcSpec{
{"str", ToLower{}},
};
auto passingInput = boost::json::parse(R"JSON({ "str": "TesT" })JSON");
ASSERT_TRUE(spec.process(passingInput));
ASSERT_EQ(passingInput.at("str").as_string(), "test");
auto passingInput2 = boost::json::parse(R"JSON({ "str2": "TesT" })JSON");
ASSERT_TRUE(spec.process(passingInput2)); // no str no problem
auto passingInput3 = boost::json::parse(R"JSON({ "str": "already lower case" })JSON");
ASSERT_TRUE(spec.process(passingInput3));
ASSERT_EQ(passingInput3.at("str").as_string(), "already lower case");
auto passingInput4 = boost::json::parse(R"JSON({ "str": "" })JSON");
ASSERT_TRUE(spec.process(passingInput4)); // empty str no problem
ASSERT_EQ(passingInput4.at("str").as_string(), "");
}
TEST_F(RPCBaseTest, ToNumberModifier)
{
auto const spec = RpcSpec{
{"str", ToNumber{}},
};
auto passingInput = boost::json::parse(R"JSON({ "str": [] })JSON");
ASSERT_TRUE(spec.process(passingInput));
passingInput = boost::json::parse(R"JSON({ "str2": "TesT" })JSON");
ASSERT_TRUE(spec.process(passingInput));
passingInput = boost::json::parse(R"JSON([])JSON");
ASSERT_TRUE(spec.process(passingInput));
passingInput = boost::json::parse(R"JSON({ "str": "123" })JSON");
ASSERT_TRUE(spec.process(passingInput));
ASSERT_EQ(passingInput.at("str").as_int64(), 123);
auto failingInput = boost::json::parse(R"JSON({ "str": "ok" })JSON");
ASSERT_FALSE(spec.process(failingInput));
failingInput = boost::json::parse(R"JSON({ "str": "123.123" })JSON");
ASSERT_FALSE(spec.process(failingInput));
}
TEST_F(RPCBaseTest, CustomModifier)
{
testing::StrictMock<
testing::MockFunction<MaybeError(boost::json::value & value, std::string_view)>>
mockModifier;
auto const customModifier = CustomModifier{mockModifier.AsStdFunction()};
auto const spec = RpcSpec{
{"str", customModifier},
};
EXPECT_CALL(mockModifier, Call).WillOnce(testing::Return(MaybeError{}));
auto passingInput = boost::json::parse(R"JSON({ "str": "sss" })JSON");
ASSERT_TRUE(spec.process(passingInput));
passingInput = boost::json::parse(R"JSON({ "strNotExist": 123 })JSON");
ASSERT_TRUE(spec.process(passingInput));
// not a json object
passingInput = boost::json::parse(R"JSON([])JSON");
ASSERT_TRUE(spec.process(passingInput));
}

View File

@@ -119,6 +119,17 @@ generateTestValuesForParametersTest()
.called = 1,
.isAdmin = isAdmin,
.expected = shouldForward},
// The isClioOnly check short-circuits before the current/closed check, so a Clio-only
// method is NOT forwarded even when it names a ledger Clio does not hold. Its handler has
// to reject the shortcut itself - see getLedgerHeaderFromLedgerSpecifier.
{.testName = "ShouldForwardReturnsFalseIfClioOnlyEvenWithCurrentLedger",
.apiVersion = 2u,
.method = "nft_info",
.testJson = R"JSON({"ledger_index": "current"})JSON",
.mockedIsClioOnly = isClioOnly,
.called = 1,
.isAdmin = !isAdmin,
.expected = !shouldForward},
{.testName = "ShouldForwardReturnsTrueIfCurrentLedgerSpecified",
.apiVersion = 2u,
.method = "anymethod",

View File

@@ -1,82 +0,0 @@
#include "rpc/common/JsonBool.hpp"
#include "util/NameGenerator.hpp"
#include <boost/json/parse.hpp>
#include <boost/json/value_to.hpp>
#include <gtest/gtest.h>
#include <string>
#include <vector>
using namespace rpc;
using namespace testing;
struct JsonBoolTestsCaseBundle {
std::string testName;
std::string json;
bool expectedBool;
};
class JsonBoolTests : public TestWithParam<JsonBoolTestsCaseBundle> {
public:
static auto
generateTestValuesForParametersTest()
{
return std::vector<JsonBoolTestsCaseBundle>{
{.testName = "NullValue",
.json = R"JSON({ "test_bool": null })JSON",
.expectedBool = false},
{.testName = "BoolTrueValue",
.json = R"JSON({ "test_bool": true })JSON",
.expectedBool = true},
{.testName = "BoolFalseValue",
.json = R"JSON({ "test_bool": false })JSON",
.expectedBool = false},
{.testName = "IntTrueValue",
.json = R"JSON({ "test_bool": 1 })JSON",
.expectedBool = true},
{.testName = "IntFalseValue",
.json = R"JSON({ "test_bool": 0 })JSON",
.expectedBool = false},
{.testName = "DoubleTrueValue",
.json = R"JSON({ "test_bool": 0.1 })JSON",
.expectedBool = true},
{.testName = "DoubleFalseValue",
.json = R"JSON({ "test_bool": 0.0 })JSON",
.expectedBool = false},
{.testName = "StringTrueValue",
.json = R"JSON({ "test_bool": "true" })JSON",
.expectedBool = true},
{.testName = "StringFalseValue",
.json = R"JSON({ "test_bool": "false" })JSON",
.expectedBool = true},
{.testName = "ArrayTrueValue",
.json = R"JSON({ "test_bool": [0] })JSON",
.expectedBool = true},
{.testName = "ArrayFalseValue",
.json = R"JSON({ "test_bool": [] })JSON",
.expectedBool = false},
{.testName = "ObjectTrueValue",
.json = R"JSON({ "test_bool": { "key": null } })JSON",
.expectedBool = true},
{.testName = "ObjectFalseValue",
.json = R"JSON({ "test_bool": {} })JSON",
.expectedBool = false}
};
}
};
INSTANTIATE_TEST_CASE_P(
JsonBoolCheckGroup,
JsonBoolTests,
ValuesIn(JsonBoolTests::generateTestValuesForParametersTest()),
tests::util::kNameGenerator
);
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<JsonBool>(jv.at("test_bool")).value);
}

View File

@@ -220,12 +220,12 @@ TEST_P(RPCEngineFlowParameterTest, Test)
} else {
if (testBundle.handlerReturnError) {
EXPECT_CALL(*handlerProvider, getHandler)
.WillOnce(Return(AnyHandler{tests::common::FailingHandlerFake{}}));
.WillOnce(Return(AnyHandler{tests::common::FailingTypedHandlerFake{}}));
EXPECT_CALL(*mockCountersPtr_, rpcErrored(testBundle.method));
EXPECT_CALL(*handlerProvider, contains(testBundle.method)).WillOnce(Return(true));
} else {
EXPECT_CALL(*handlerProvider, getHandler(testBundle.method))
.WillOnce(Return(AnyHandler{tests::common::HandlerFake{}}));
.WillOnce(Return(AnyHandler{tests::common::TypedHandlerFake{}}));
}
}
}
@@ -261,7 +261,7 @@ TEST_F(RPCEngineTest, ThrowDatabaseError)
);
EXPECT_CALL(*backend_, isTooBusy).WillOnce(Return(false));
EXPECT_CALL(*handlerProvider, getHandler(method))
.WillOnce(Return(AnyHandler{tests::common::FailingHandlerFake{}}));
.WillOnce(Return(AnyHandler{tests::common::FailingTypedHandlerFake{}}));
EXPECT_CALL(*mockCountersPtr_, rpcErrored(method)).WillOnce(Throw(data::DatabaseError{}));
EXPECT_CALL(*handlerProvider, contains(method)).WillOnce(Return(true));
EXPECT_CALL(*mockCountersPtr_, onTooBusy());
@@ -293,7 +293,7 @@ TEST_F(RPCEngineTest, ThrowException)
);
EXPECT_CALL(*backend_, isTooBusy).WillOnce(Return(false));
EXPECT_CALL(*handlerProvider, getHandler(method))
.WillOnce(Return(AnyHandler{tests::common::FailingHandlerFake{}}));
.WillOnce(Return(AnyHandler{tests::common::FailingTypedHandlerFake{}}));
EXPECT_CALL(*mockCountersPtr_, rpcErrored(method)).WillOnce(Throw(std::exception{}));
EXPECT_CALL(*handlerProvider, contains(method)).WillOnce(Return(true));
EXPECT_CALL(*mockCountersPtr_, onInternalError());
@@ -486,7 +486,7 @@ TEST_F(RPCEngineTest, NonBareRequestBypassesCache)
EXPECT_CALL(*backend_, isTooBusy).Times(callTime).WillRepeatedly(Return(false));
EXPECT_CALL(*handlerProvider, getHandler)
.Times(callTime)
.WillRepeatedly(Return(AnyHandler{tests::common::HandlerFake{}}));
.WillRepeatedly(Return(AnyHandler{tests::common::TypedHandlerFake{}}));
while (callTime-- != 0) {
runSpawn([&](auto yield) {
@@ -538,7 +538,7 @@ TEST_F(RPCEngineTest, NotCacheIfErrorHappen)
EXPECT_CALL(*backend_, isTooBusy).Times(callTime).WillRepeatedly(Return(false));
EXPECT_CALL(*handlerProvider, getHandler)
.Times(callTime)
.WillRepeatedly(Return(AnyHandler{tests::common::FailingHandlerFake{}}));
.WillRepeatedly(Return(AnyHandler{tests::common::FailingTypedHandlerFake{}}));
EXPECT_CALL(*mockCountersPtr_, rpcErrored(method)).Times(callTime);
EXPECT_CALL(*handlerProvider, isClioOnly).Times(callTime).WillRepeatedly(Return(false));
EXPECT_CALL(*handlerProvider, contains).Times(callTime).WillRepeatedly(Return(true));

View File

@@ -7,7 +7,6 @@
#include "util/AsioContextTestFixture.hpp"
#include "util/LoggerFixtures.hpp"
#include "util/MockAmendmentCenter.hpp"
#include "util/MockAssert.hpp"
#include "util/MockBackendTestFixture.hpp"
#include "util/MockPrometheus.hpp"
#include "util/NameGenerator.hpp"
@@ -707,8 +706,6 @@ TEST_F(RPCHelpersTest, ParseBookBadMarket)
auto const book = rpc::parseBook(usd, usd, std::nullopt);
ASSERT_FALSE(book.has_value());
EXPECT_TRUE(book.error().code == CombinedError{RippledError::RpcBadMarket});
// badMarket carries no explicit message; it renders from the code's default, matching
// rippled's "No such market.".
EXPECT_EQ(rpc::makeError(book.error()).at("error_message").as_string(), "No such market.");
}
@@ -720,8 +717,6 @@ TEST_F(RPCHelpersTest, ParseBookBadMarket)
auto const book = rpc::parseBook(mpt, mpt, std::nullopt);
ASSERT_FALSE(book.has_value());
EXPECT_TRUE(book.error().code == CombinedError{RippledError::RpcBadMarket});
// badMarket carries no explicit message; it renders from the code's default, matching
// rippled's "No such market.".
EXPECT_EQ(rpc::makeError(book.error()).at("error_message").as_string(), "No such market.");
}
}
@@ -774,8 +769,6 @@ TEST_F(RPCHelpersTest, ParseBookCurrencyOverloadDelegates)
);
ASSERT_FALSE(book.has_value());
EXPECT_TRUE(book.error().code == CombinedError{RippledError::RpcBadMarket});
// badMarket carries no explicit message; it renders from the code's default, matching
// rippled's "No such market.".
EXPECT_EQ(rpc::makeError(book.error()).at("error_message").as_string(), "No such market.");
}
}
@@ -837,110 +830,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
@@ -2155,43 +2044,40 @@ TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierValidatedUsesMaxSeq)
});
}
struct RPCHelpersAssertTest : RPCHelpersTest, common::util::WithMockAssert {};
TEST_F(RPCHelpersAssertTest, LedgerHeaderFromSpecifierCurrentAsserts)
// `current` and `closed` name ledgers Clio does not hold. Most methods never reach here because
// ForwardingProxy diverts them to xrpld, but it skips that check for Clio-only methods, so the
// resolver has to reject the shortcut rather than treat it as unreachable.
TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierCurrentRejected)
{
EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(0);
runSpawn([&, this](auto yield) {
EXPECT_CLIO_ASSERT_FAIL_WITH_MESSAGE(
{
[[maybe_unused]] auto const res = getLedgerHeaderFromLedgerSpecifier(
*backend_,
yield,
rpc::spec::LedgerSpecifier{rpc::spec::LedgerShortcut::Current},
kSpecifierRangeMax
);
},
"must be forwarded before dispatch"
auto const res = getLedgerHeaderFromLedgerSpecifier(
*backend_,
yield,
rpc::spec::LedgerSpecifier{rpc::spec::LedgerShortcut::Current},
kSpecifierRangeMax
);
ASSERT_FALSE(res.has_value());
EXPECT_EQ(res.error().code, rpc::CombinedError{rpc::RippledError::RpcInvalidParams});
EXPECT_EQ(res.error().message, "ledgerIndexMalformed");
});
}
TEST_F(RPCHelpersAssertTest, LedgerHeaderFromSpecifierClosedAsserts)
TEST_F(RPCHelpersTest, LedgerHeaderFromSpecifierClosedRejected)
{
EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(0);
runSpawn([&, this](auto yield) {
EXPECT_CLIO_ASSERT_FAIL_WITH_MESSAGE(
{
[[maybe_unused]] auto const res = getLedgerHeaderFromLedgerSpecifier(
*backend_,
yield,
rpc::spec::LedgerSpecifier{rpc::spec::LedgerShortcut::Closed},
kSpecifierRangeMax
);
},
"must be forwarded before dispatch"
auto const res = getLedgerHeaderFromLedgerSpecifier(
*backend_,
yield,
rpc::spec::LedgerSpecifier{rpc::spec::LedgerShortcut::Closed},
kSpecifierRangeMax
);
ASSERT_FALSE(res.has_value());
EXPECT_EQ(res.error().code, rpc::CombinedError{rpc::RippledError::RpcInvalidParams});
EXPECT_EQ(res.error().message, "ledgerIndexMalformed");
});
}

Some files were not shown because too many files have changed in this diff Show More