mirror of
https://github.com/XRPLF/clio.git
synced 2026-10-02 01:48:23 +00:00
chore: Commits for 2.9.0-b2 (#3226)
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user