feat: Add mptoken_issuance_history RPC (#3141)

feat: add mptoken_issuance_history RPC

Summary

Adds mptoken_issuance_history, a Clio-only method that returns the
transaction history for a given MPT issuance — the MPT-scoped sibling of
nft_history. You can optionally filter by account and/or tx_type.

The index tables, backend fetch methods, and live ETL indexing landed
earlier; this wires up the handler on top of them.

The handler

- Routes to fetchMPTokenIssuanceTransactions or
fetchAccountMPTokenIssuanceTransactions depending on whether account is
set. When tx_type is given, it filters on TransactionType post-fetch,
exactly like account_tx.
- Follows the nft_history/account_tx conventions for ledger ranges,
markers, binary, forward, and limit ([1,100], default 50), including
api-version response branching.
- Gated on backfill completion so it never returns partial history:
until this node's MPTTransactionHistoryMigrator reports Migrated,
requests get notReady with a message pointing at the --migrate command.
Once migrated, the result is cached and the check is skipped — the
method turns on automatically, no restart.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan
2026-08-18 09:43:06 -07:00
committed by GitHub
parent 62428a2d91
commit e8e0478333
7 changed files with 2876 additions and 0 deletions

View File

@@ -44,6 +44,7 @@ target_sources(
handlers/LedgerIndex.cpp
handlers/LedgerRange.cpp
handlers/MPTHolders.cpp
handlers/MPTokenIssuanceHistory.cpp
handlers/NFTsByIssuer.cpp
handlers/NFTBuyOffers.cpp
handlers/NFTHistory.cpp

View File

@@ -35,6 +35,7 @@ handledRpcs()
"ledger_index",
"ledger_range",
"mpt_holders",
"mptoken_issuance_history",
"nfts_by_issuer",
"nft_history",
"nft_buy_offers",

View File

@@ -30,6 +30,7 @@
#include "rpc/handlers/LedgerIndex.hpp"
#include "rpc/handlers/LedgerRange.hpp"
#include "rpc/handlers/MPTHolders.hpp"
#include "rpc/handlers/MPTokenIssuanceHistory.hpp"
#include "rpc/handlers/NFTBuyOffers.hpp"
#include "rpc/handlers/NFTHistory.hpp"
#include "rpc/handlers/NFTInfo.hpp"
@@ -91,6 +92,8 @@ ProductionHandlerProvider::ProductionHandlerProvider(
{"ledger_range", {.handler = LedgerRangeHandler{backend}}},
{"mpt_holders",
{.handler = MPTHoldersHandler{backend}, .isClioOnly = true}}, // clio only
{"mptoken_issuance_history",
{.handler = MPTokenIssuanceHistoryHandler{backend}, .isClioOnly = true}}, // clio only
{"nfts_by_issuer",
{.handler = NFTsByIssuerHandler{backend}, .isClioOnly = true}}, // clio only
{"nft_history",

View File

@@ -0,0 +1,378 @@
#include "rpc/handlers/MPTokenIssuanceHistory.hpp"
#include "data/Types.hpp"
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#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"
#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 <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/LedgerHeader.h>
#include <xrpl/protocol/jss.h>
#include <atomic>
#include <cstdint>
#include <expected>
#include <limits>
#include <optional>
#include <string>
#include <utility>
namespace rpc {
namespace {
/**
* @brief The migrator status that reports a completed backfill.
*
* This is a literal to keep migration headers out of RPC; it must match the string form of
* `migration::MigratorStatus::Status::Migrated`.
*/
constexpr auto kMigratedStatus = "Migrated";
} // namespace
MPTokenIssuanceHistoryHandler::Result
MPTokenIssuanceHistoryHandler::process(
MPTokenIssuanceHistoryHandler::Input const& input,
Context const& ctx
) const
{
if (auto const available = verifyHistoryAvailable(ctx); not available.has_value())
return Error{available.error()};
auto const range = resolveSequenceRange(input, ctx);
if (not range.has_value())
return Error{range.error()};
auto const mptIssuanceID = xrpl::uint192{input.mptIssuanceID.c_str()};
auto const [page, timeDiff] =
util::timed([&] { return fetchTransactions(input, ctx, mptIssuanceID, *range); });
LOG(log_.info()) << "db fetch took " << timeDiff
<< " milliseconds - num blobs = " << page.txns.size();
Output response;
response.mptIssuanceID = xrpl::to_string(mptIssuanceID);
response.ledgerIndexMin = range->min;
response.ledgerIndexMax = range->max;
response.limit = input.limit;
processTransactionsPage(input, ctx, *range, page, response);
return response;
}
MaybeError
MPTokenIssuanceHistoryHandler::verifyHistoryAvailable(Context const& ctx) const
{
if (migrated_->load(std::memory_order_relaxed))
return {};
auto const statusString = sharedPtrBackend_->fetchMigratorStatus(kMigratorName, ctx.yield);
if (statusString.has_value() and *statusString == kMigratedStatus) {
migrated_->store(true, std::memory_order_relaxed);
return {};
}
// Fail closed: partial history must never be served.
return Error{Status{
RippledError::RpcNotReady,
"mptoken_issuance_history is not available on this server because the required "
"transaction-history backfill has not completed."
}};
}
std::expected<MPTokenIssuanceHistoryHandler::SequenceRange, Status>
MPTokenIssuanceHistoryHandler::resolveSequenceRange(Input const& input, Context const& ctx) const
{
auto const ledgerRange = sharedPtrBackend_->fetchLedgerRange();
ASSERT(ledgerRange.has_value(), "MPTokenIssuanceHistory's ledger range must be available");
auto const [dbMinSeq, dbMaxSeq] = *ledgerRange; // NOLINT(bugprone-unchecked-optional-access)
auto resolved = SequenceRange{.min = dbMinSeq, .max = dbMaxSeq};
if (input.ledgerIndexMin.has_value()) {
if (dbMaxSeq < input.ledgerIndexMin || dbMinSeq > input.ledgerIndexMin)
return Error{Status{RippledError::RpcLgrIdxMalformed, "ledgerSeqMinOutOfRange"}};
resolved.min = *input.ledgerIndexMin;
}
if (input.ledgerIndexMax.has_value()) {
if (dbMaxSeq < input.ledgerIndexMax || dbMinSeq > input.ledgerIndexMax)
return Error{Status{RippledError::RpcLgrIdxMalformed, "ledgerSeqMaxOutOfRange"}};
resolved.max = *input.ledgerIndexMax;
}
if (resolved.min > resolved.max)
return Error{Status{RippledError::RpcLgrIdxsInvalid}};
if (input.ledgerHash.has_value() || input.ledgerIndex.has_value()) {
// 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
);
if (not expectedLgrInfo.has_value())
return Error{expectedLgrInfo.error()};
resolved.max = resolved.min = expectedLgrInfo->seq;
}
return resolved;
}
data::TransactionsAndCursor
MPTokenIssuanceHistoryHandler::fetchTransactions(
Input const& input,
Context const& ctx,
xrpl::uint192 const& mptIssuanceID,
SequenceRange range
) const
{
// Construct the database cursor as {ledgerSequence, transactionIndex}.
auto const startCursor = [&]() -> data::TransactionsCursor {
if (input.marker.has_value())
return {input.marker->ledger, input.marker->seq};
// Forward iteration starts at the first possible transaction in the lowest ledger.
if (input.forward)
return {range.min, 0};
// Reverse iteration starts after all possible transactions in the highest ledger.
return {range.max, std::numeric_limits<int32_t>::max()};
}();
auto const limit = input.limit.value_or(kLimitDefault);
// 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
);
}
return sharedPtrBackend_->fetchMPTokenIssuanceTransactions(
mptIssuanceID, limit, input.forward, startCursor, ctx.yield
);
}
void
MPTokenIssuanceHistoryHandler::processTransactionsPage(
Input const& input,
Context const& ctx,
SequenceRange range,
data::TransactionsAndCursor const& page,
Output& response
) const
{
if (page.cursor.has_value()) {
response.marker = {
.ledger = page.cursor->ledgerSequence, .seq = page.cursor->transactionIndex
};
}
for (auto const& txnPlusMeta : page.txns) {
// A hash with no matching Transactions row yields a default-constructed record in-position.
// Skip it before the range check so it neither shortens the page nor disturbs the marker.
if (txnPlusMeta.transaction.empty() || txnPlusMeta.metadata.empty()) {
LOG(log_.warn()) << "Skipping index entry with no matching transaction record; "
"mpt_issuance_id = "
<< input.mptIssuanceID;
continue;
}
// Stop once iteration passes the far edge of the requested range.
if ((txnPlusMeta.ledgerSequence < range.min && !input.forward) ||
(txnPlusMeta.ledgerSequence > range.max && input.forward)) {
response.marker = std::nullopt;
break;
}
if (txnPlusMeta.ledgerSequence > range.max && !input.forward) {
LOG(log_.debug()) << "Skipping over transactions from incomplete ledger";
continue;
}
if (auto obj = transactionToJsonIfTypeMatches(txnPlusMeta, input, ctx); obj.has_value())
response.transactions.push_back(std::move(*obj));
}
}
std::optional<boost::json::object>
MPTokenIssuanceHistoryHandler::transactionToJsonIfTypeMatches(
data::TransactionAndMetadata const& txnPlusMeta,
Input const& input,
Context const& ctx
) const
{
// The type filter needs the expanded form to read TransactionType, even for binary output.
if (!input.binary || input.transactionTypeInLowercase.has_value()) {
auto [txn, meta] = toExpandedJson(txnPlusMeta, ctx.apiVersion);
if (txn.contains(JS(TransactionType)) && input.transactionTypeInLowercase.has_value() &&
util::toLower(boost::json::value_to<std::string>(txn[JS(TransactionType)])) !=
*input.transactionTypeInLowercase)
return std::nullopt;
if (!input.binary)
return expandedTransactionToJson(std::move(txn), std::move(meta), txnPlusMeta, ctx);
}
auto obj = toJsonWithBinaryTx(txnPlusMeta, ctx.apiVersion);
obj[JS(ledger_index)] = txnPlusMeta.ledgerSequence;
obj[JS(date)] = txnPlusMeta.date;
obj[JS(validated)] = true;
return obj;
}
boost::json::object
MPTokenIssuanceHistoryHandler::expandedTransactionToJson(
boost::json::object txn,
boost::json::object meta,
data::TransactionAndMetadata const& txnPlusMeta,
Context const& ctx
) const
{
auto const txKey = ctx.apiVersion > 1u ? JS(tx_json) : JS(tx);
boost::json::object obj;
obj[JS(meta)] = std::move(meta);
obj[txKey] = std::move(txn);
obj[txKey].as_object()[JS(ledger_index)] = txnPlusMeta.ledgerSequence;
obj[txKey].as_object()[JS(date)] = txnPlusMeta.date;
if (ctx.apiVersion > 1u) {
obj[JS(ledger_index)] = txnPlusMeta.ledgerSequence;
if (obj[txKey].as_object().contains(JS(hash))) {
obj[JS(hash)] = obj[txKey].at(JS(hash));
obj[txKey].as_object().erase(JS(hash));
}
if (auto const lgrInfo =
sharedPtrBackend_->fetchLedgerBySequence(txnPlusMeta.ledgerSequence, ctx.yield);
lgrInfo.has_value()) {
obj[JS(close_time_iso)] = xrpl::toStringIso(lgrInfo->closeTime);
obj[JS(ledger_hash)] = xrpl::strHex(lgrInfo->hash);
}
}
obj[JS(validated)] = true;
return obj;
}
void
tag_invoke(
boost::json::value_from_tag,
boost::json::value& jv,
MPTokenIssuanceHistoryHandler::Output const& output
)
{
jv = {
{JS(mpt_issuance_id), output.mptIssuanceID},
{JS(ledger_index_min), output.ledgerIndexMin},
{JS(ledger_index_max), output.ledgerIndexMax},
{JS(transactions), output.transactions},
{JS(validated), output.validated},
};
if (output.marker.has_value())
jv.as_object()[JS(marker)] = boost::json::value_from(*(output.marker));
if (output.limit.has_value())
jv.as_object()[JS(limit)] = *(output.limit);
}
void
tag_invoke(
boost::json::value_from_tag,
boost::json::value& jv,
MPTokenIssuanceHistoryHandler::Marker const& marker
)
{
jv = {
{JS(ledger), marker.ledger},
{JS(seq), marker.seq},
};
}
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

View File

@@ -0,0 +1,316 @@
#pragma once
#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/TxUtils.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/basics/base_uint.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/jss.h>
#include <atomic>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <string>
#include <utility>
namespace rpc {
/**
* @brief The mptoken_issuance_history command returns past transactions associated with the queried
* MPTokenIssuance, optionally filtered by an affected account and/or transaction type.
*
* @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 {
util::Logger log_{"RPC"};
std::shared_ptr<BackendInterface> sharedPtrBackend_;
/**
* @brief Whether the issuance-history backfill has completed.
*
* The status is monotonic, so the terminal result is cached across handler copies.
*/
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;
/**
* @brief The name used to query the issuance-history migrator's status.
*
* This is a literal to keep Cassandra migration headers out of RPC.
*/
static constexpr char const* kMigratorName = "MPTokenIssuanceHistoryMigrator";
/**
* @brief A struct to hold the marker data.
*/
struct Marker {
uint32_t ledger;
uint32_t seq;
};
/**
* @brief A struct to hold the output data of the command.
*/
struct Output {
std::string mptIssuanceID;
uint32_t ledgerIndexMin{0};
uint32_t ledgerIndexMax{0};
std::optional<uint32_t> limit;
std::optional<Marker> marker;
/** @todo Use a domain-specific type instead of JSON. */
boost::json::array transactions;
/** @todo Send validated through the RPC framework. */
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>;
/**
* @brief Construct a new MPTokenIssuanceHistoryHandler object.
*
* @param sharedPtrBackend The backend to use.
*/
explicit MPTokenIssuanceHistoryHandler(std::shared_ptr<BackendInterface> sharedPtrBackend)
: sharedPtrBackend_(std::move(sharedPtrBackend))
{
}
/**
* @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& typesKeysInLowercase = util::getTxTypesInLowercase();
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.
*
* @param input The input data for the command.
* @param ctx The context of the request.
* @return The result of the operation.
*/
[[nodiscard]] Result
process(Input const& input, Context const& ctx) const;
private:
/**
* @brief The inclusive range of ledger sequences a request is restricted to.
*/
struct SequenceRange {
uint32_t min;
uint32_t max;
};
/**
* @brief Check that the transaction-history backfill has completed.
*
* The terminal Migrated status is cached, so the backend is only consulted until the backfill
* reports completion.
*
* @param ctx The context of the request.
* @return An empty result if the history is complete; an error otherwise.
*/
[[nodiscard]] MaybeError
verifyHistoryAvailable(Context const& ctx) const;
/**
* @brief Resolve the ledger sequence range to search from the request's ledger specifiers.
*
* Starts from the server's full ledger range, then narrows it by ledger_index_min /
* ledger_index_max, or collapses it to a single sequence if ledger_hash / ledger_index is
* given.
*
* @param input The input data for the command.
* @param ctx The context of the request.
* @return The resolved range if the specifiers are valid; an error otherwise.
*/
[[nodiscard]] std::expected<SequenceRange, Status>
resolveSequenceRange(Input const& input, Context const& ctx) const;
/**
* @brief Fetch one page of transactions for the issuance, optionally restricted to an account.
*
* When the request has no marker, a forward page starts at the lower bound of @p range and a
* reverse page starts after the highest possible transaction at its upper bound.
*
* @param input The input data for the command.
* @param ctx The context of the request.
* @param mptIssuanceID The MPTokenIssuance ID to fetch transactions for.
* @param range The resolved ledger sequence range.
* @return The fetched transactions and the cursor to resume from.
*/
[[nodiscard]] data::TransactionsAndCursor
fetchTransactions(
Input const& input,
Context const& ctx,
xrpl::uint192 const& mptIssuanceID,
SequenceRange range
) const;
/**
* @brief Process a fetched transaction page into the response.
*
* Appends transactions that pass the range and type filters. The response marker is taken from
* the fetched page, then cleared if the page runs past the requested range because there is
* nothing left to page through.
*
* @param input The input data for the command.
* @param ctx The context of the request.
* @param range The resolved ledger sequence range.
* @param page The transactions fetched from the database and the cursor to resume from.
* @param [out] response The response to append to.
*/
void
processTransactionsPage(
Input const& input,
Context const& ctx,
SequenceRange range,
data::TransactionsAndCursor const& page,
Output& response
) const;
/**
* @brief Convert one transaction to its JSON representation, unless its type is filtered out.
*
* @param txnPlusMeta The transaction and its metadata.
* @param input The input data for the command.
* @param ctx The context of the request.
* @return The JSON representation, or nullopt if the transaction's type does not match the
* requested one.
*/
[[nodiscard]] std::optional<boost::json::object>
transactionToJsonIfTypeMatches(
data::TransactionAndMetadata const& txnPlusMeta,
Input const& input,
Context const& ctx
) const;
/**
* @brief Assemble the API-specific JSON of an expanded transaction and its metadata.
*
* For API v2 and later, the transaction is enriched with ledger information when the
* corresponding ledger header is available.
*
* @param txn The expanded transaction JSON.
* @param meta The expanded metadata JSON.
* @param txnPlusMeta The transaction and its metadata the JSON was expanded from.
* @param ctx The context of the request.
* @return The JSON representation of the transaction.
*/
[[nodiscard]] boost::json::object
expandedTransactionToJson(
boost::json::object txn,
boost::json::object meta,
data::TransactionAndMetadata const& txnPlusMeta,
Context const& ctx
) const;
/**
* @brief Convert the Output to a JSON object.
*
* @param [out] jv The JSON object to convert to.
* @param output The output to convert.
*/
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

View File

@@ -128,6 +128,7 @@ target_sources(
rpc/handlers/LedgerRangeTests.cpp
rpc/handlers/LedgerTests.cpp
rpc/handlers/MPTHoldersTests.cpp
rpc/handlers/MPTokenIssuanceHistoryTests.cpp
rpc/handlers/NFTBuyOffersTests.cpp
rpc/handlers/NFTHistoryTests.cpp
rpc/handlers/NFTInfoTests.cpp

File diff suppressed because it is too large Load Diff