mirror of
https://github.com/XRPLF/clio.git
synced 2026-09-27 15:38:04 +00:00
refactor: Migrate handlers to rpc-spec (F) (#3212)
This commit is contained in:
@@ -1,266 +1,423 @@
|
||||
#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)
|
||||
{
|
||||
if (entry.dirRoot.has_value() && 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 (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 +428,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 +445,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 +461,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 +475,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 +487,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
|
||||
|
||||
@@ -207,7 +207,7 @@ LedgerEntryHandler::Input
|
||||
createInput<LedgerEntryHandler>()
|
||||
{
|
||||
LedgerEntryHandler::Input input{};
|
||||
input.index = kIndex1;
|
||||
input.index = xrpl::uint256{kIndex1};
|
||||
return input;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <rpcspec/Errors.hpp>
|
||||
#include <rpcspec/WarningsToJson.hpp>
|
||||
#include <xrpl/basics/Blob.h>
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/StringUtilities.h>
|
||||
@@ -128,7 +129,7 @@ generateTestValuesForParametersTest()
|
||||
"ledger_index": "wrong"
|
||||
})JSON",
|
||||
.expectedError = "invalidParams",
|
||||
.expectedErrorMessage = "ledgerIndexMalformed"
|
||||
.expectedErrorMessage = "Invalid field 'ledger_index', not string or number."
|
||||
},
|
||||
|
||||
ParamTestCaseBundle{
|
||||
@@ -837,7 +838,7 @@ generateTestValuesForParametersTest()
|
||||
}
|
||||
})JSON",
|
||||
.expectedError = "invalidParams",
|
||||
.expectedErrorMessage = "dir_rootMalformed"
|
||||
.expectedErrorMessage = "Invalid field 'dir_root'."
|
||||
},
|
||||
|
||||
ParamTestCaseBundle{
|
||||
@@ -848,7 +849,7 @@ generateTestValuesForParametersTest()
|
||||
}
|
||||
})JSON",
|
||||
.expectedError = "invalidParams",
|
||||
.expectedErrorMessage = "dir_rootNotString"
|
||||
.expectedErrorMessage = "Invalid field 'dir_root'."
|
||||
},
|
||||
|
||||
ParamTestCaseBundle{
|
||||
@@ -3730,6 +3731,191 @@ TEST_F(RPCLedgerEntryTest, UnexpectedLedgerType)
|
||||
});
|
||||
}
|
||||
|
||||
// Each variant field's hex-key arm sets its own expectedType, so a wrong constant there would
|
||||
// silently accept the wrong object type.
|
||||
struct LedgerEntryHexKeyTypeTestBundle {
|
||||
std::string testName;
|
||||
std::string field;
|
||||
xrpl::LedgerEntryType expectedType;
|
||||
};
|
||||
|
||||
struct LedgerEntryHexKeyTypeTest : public RPCLedgerEntryTest,
|
||||
public WithParamInterface<LedgerEntryHexKeyTypeTestBundle> {};
|
||||
|
||||
TEST_P(LedgerEntryHexKeyTypeTest, WrongLedgerTypeForHexKey)
|
||||
{
|
||||
auto const ledgerHeader = createLedgerHeader(kLedgerHash, kRangeMax);
|
||||
EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _))
|
||||
.WillRepeatedly(Return(ledgerHeader));
|
||||
|
||||
// a valid, deserializable object of a type that none of these fields expect
|
||||
auto const ledgerEntry =
|
||||
createPaymentChannelLedgerObject(kAccount, kAccount2, 100, 200, 300, kIndex1, 400);
|
||||
EXPECT_CALL(*backend_, doFetchLedgerObject(xrpl::uint256{kIndex1}, kRangeMax, _))
|
||||
.WillRepeatedly(Return(ledgerEntry.getSerializer().peekData()));
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{LedgerEntryHandler{backend_}};
|
||||
auto const req =
|
||||
boost::json::parse(fmt::format(R"JSON({{"{}": "{}"}})JSON", GetParam().field, kIndex1));
|
||||
auto const output = handler.process(req, Context{yield});
|
||||
ASSERT_FALSE(output);
|
||||
auto const err = rpc::makeError(output.result.error());
|
||||
EXPECT_EQ(err.at("error").as_string(), "unexpectedLedgerType");
|
||||
});
|
||||
}
|
||||
|
||||
TEST_P(LedgerEntryHexKeyTypeTest, MatchingLedgerTypeForHexKeyAccepted)
|
||||
{
|
||||
auto const ledgerHeader = createLedgerHeader(kLedgerHash, kRangeMax);
|
||||
EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _))
|
||||
.WillRepeatedly(Return(ledgerHeader));
|
||||
|
||||
// a bare object of exactly the type this field's hex key implies
|
||||
xrpl::STLedgerEntry const ledgerEntry{GetParam().expectedType, xrpl::uint256{kIndex1}};
|
||||
EXPECT_CALL(*backend_, doFetchLedgerObject(xrpl::uint256{kIndex1}, kRangeMax, _))
|
||||
.WillRepeatedly(Return(ledgerEntry.getSerializer().peekData()));
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{LedgerEntryHandler{backend_}};
|
||||
auto const req =
|
||||
boost::json::parse(fmt::format(R"JSON({{"{}": "{}"}})JSON", GetParam().field, kIndex1));
|
||||
auto const output = handler.process(req, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(output.result->as_object().at("index").as_string(), kIndex1);
|
||||
});
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
RPCLedgerEntryHexKeyTypeGroup,
|
||||
LedgerEntryHexKeyTypeTest,
|
||||
ValuesIn({
|
||||
// kHexLocators table entries
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "NftOffer",
|
||||
.field = "nft_offer",
|
||||
.expectedType = xrpl::ltNFTOKEN_OFFER
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "SignerList",
|
||||
.field = "signer_list",
|
||||
.expectedType = xrpl::ltSIGNER_LIST
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Amendments",
|
||||
.field = "amendments",
|
||||
.expectedType = xrpl::ltAMENDMENTS
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Fee",
|
||||
.field = "fee",
|
||||
.expectedType = xrpl::ltFEE_SETTINGS
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Hashes",
|
||||
.field = "hashes",
|
||||
.expectedType = xrpl::ltLEDGER_HASHES
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Nunl",
|
||||
.field = "nunl",
|
||||
.expectedType = xrpl::ltNEGATIVE_UNL
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Check",
|
||||
.field = "check",
|
||||
.expectedType = xrpl::ltCHECK
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "NftPage",
|
||||
.field = "nft_page",
|
||||
.expectedType = xrpl::ltNFTOKEN_PAGE
|
||||
},
|
||||
// variant fields' hex arms
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Directory",
|
||||
.field = "directory",
|
||||
.expectedType = xrpl::ltDIR_NODE
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Offer",
|
||||
.field = "offer",
|
||||
.expectedType = xrpl::ltOFFER
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Escrow",
|
||||
.field = "escrow",
|
||||
.expectedType = xrpl::ltESCROW
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "DepositPreauth",
|
||||
.field = "deposit_preauth",
|
||||
.expectedType = xrpl::ltDEPOSIT_PREAUTH
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Ticket",
|
||||
.field = "ticket",
|
||||
.expectedType = xrpl::ltTICKET
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Amm",
|
||||
.field = "amm",
|
||||
.expectedType = xrpl::ltAMM
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Oracle",
|
||||
.field = "oracle",
|
||||
.expectedType = xrpl::ltORACLE
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Credential",
|
||||
.field = "credential",
|
||||
.expectedType = xrpl::ltCREDENTIAL
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Mptoken",
|
||||
.field = "mptoken",
|
||||
.expectedType = xrpl::ltMPTOKEN
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "PermissionedDomain",
|
||||
.field = "permissioned_domain",
|
||||
.expectedType = xrpl::ltPERMISSIONED_DOMAIN
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Vault",
|
||||
.field = "vault",
|
||||
.expectedType = xrpl::ltVAULT
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "LoanBroker",
|
||||
.field = "loan_broker",
|
||||
.expectedType = xrpl::ltLOAN_BROKER
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Loan",
|
||||
.field = "loan",
|
||||
.expectedType = xrpl::ltLOAN
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "Delegate",
|
||||
.field = "delegate",
|
||||
.expectedType = xrpl::ltDELEGATE
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "XChainOwnedClaimId",
|
||||
.field = "xchain_owned_claim_id",
|
||||
.expectedType = xrpl::ltXCHAIN_OWNED_CLAIM_ID
|
||||
},
|
||||
LedgerEntryHexKeyTypeTestBundle{
|
||||
.testName = "XChainOwnedCreateAccountClaimId",
|
||||
.field = "xchain_owned_create_account_claim_id",
|
||||
.expectedType = xrpl::ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID
|
||||
},
|
||||
}),
|
||||
tests::util::kNameGenerator
|
||||
);
|
||||
|
||||
TEST_F(RPCLedgerEntryTest, LedgerNotExistViaIntSequence)
|
||||
{
|
||||
EXPECT_CALL(*backend_, fetchLedgerBySequence(kRangeMax, _))
|
||||
@@ -3835,7 +4021,7 @@ TEST(RPCLedgerEntrySpecTest, DeprecatedFields)
|
||||
{
|
||||
boost::json::value const json{{"ledger", 2}};
|
||||
auto const spec = LedgerEntryHandler::spec(2);
|
||||
auto const warnings = spec.check(json);
|
||||
auto const warnings = rpc::spec::toJsonArray(spec.check(json));
|
||||
ASSERT_EQ(warnings.size(), 1);
|
||||
ASSERT_TRUE(warnings[0].is_object());
|
||||
auto const& warning = warnings[0].as_object();
|
||||
|
||||
Reference in New Issue
Block a user