mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-20 03:35:55 +00:00
add nft_history and mark certain APIs as clio-only to improve error (#255)
This commit is contained in:
@@ -651,6 +651,9 @@ CassandraBackend::fetchNFTTransactions(
|
||||
static_cast<std::uint32_t>(lgrSeq),
|
||||
static_cast<std::uint32_t>(txnIdx)};
|
||||
|
||||
// Only modify if forward because forward query
|
||||
// (selectNFTTxForward_) orders by ledger/tx sequence >= whereas
|
||||
// reverse query (selectNFTTx_) orders by ledger/tx sequence <.
|
||||
if (forward)
|
||||
++cursor->transactionIndex;
|
||||
}
|
||||
@@ -732,6 +735,9 @@ CassandraBackend::fetchAccountTransactions(
|
||||
static_cast<std::uint32_t>(lgrSeq),
|
||||
static_cast<std::uint32_t>(txnIdx)};
|
||||
|
||||
// Only modify if forward because forward query
|
||||
// (selectAccountTxForward_) orders by ledger/tx sequence >= whereas
|
||||
// reverse query (selectAccountTx_) orders by ledger/tx sequence <.
|
||||
if (forward)
|
||||
++cursor->transactionIndex;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,9 @@ doNFTSellOffers(Context const& context);
|
||||
Result
|
||||
doNFTInfo(Context const& context);
|
||||
|
||||
Result
|
||||
doNFTHistory(Context const& context);
|
||||
|
||||
// ledger methods
|
||||
Result
|
||||
doLedger(Context const& context);
|
||||
|
||||
@@ -168,6 +168,7 @@ struct Handler
|
||||
std::string method;
|
||||
std::function<Result(Context const&)> handler;
|
||||
std::optional<LimitRange> limit;
|
||||
bool isClioOnly = false;
|
||||
};
|
||||
|
||||
class HandlerTable
|
||||
@@ -206,6 +207,12 @@ public:
|
||||
|
||||
return handlerMap_[command].handler;
|
||||
}
|
||||
|
||||
bool
|
||||
isClioOnly(std::string const& command)
|
||||
{
|
||||
return handlerMap_.contains(command) && handlerMap_[command].isClioOnly;
|
||||
}
|
||||
};
|
||||
|
||||
static HandlerTable handlerTable{
|
||||
@@ -224,7 +231,8 @@ static HandlerTable handlerTable{
|
||||
{"ledger", &doLedger, {}},
|
||||
{"ledger_data", &doLedgerData, LimitRange{1, 100, 2048}},
|
||||
{"nft_buy_offers", &doNFTBuyOffers, LimitRange{1, 50, 100}},
|
||||
{"nft_info", &doNFTInfo},
|
||||
{"nft_history", &doNFTHistory, LimitRange{1, 50, 100}, true},
|
||||
{"nft_info", &doNFTInfo, {}, true},
|
||||
{"nft_sell_offers", &doNFTSellOffers, LimitRange{1, 50, 100}},
|
||||
{"ledger_entry", &doLedgerEntry, {}},
|
||||
{"ledger_range", &doLedgerRange, {}},
|
||||
@@ -252,6 +260,12 @@ validHandler(std::string const& method)
|
||||
return handlerTable.contains(method) || forwardCommands.contains(method);
|
||||
}
|
||||
|
||||
bool
|
||||
isClioOnly(std::string const& method)
|
||||
{
|
||||
return handlerTable.isClioOnly(method);
|
||||
}
|
||||
|
||||
Status
|
||||
getLimit(RPC::Context const& context, std::uint32_t& limit)
|
||||
{
|
||||
@@ -287,6 +301,9 @@ shouldForwardToRippled(Context const& ctx)
|
||||
{
|
||||
auto request = ctx.params;
|
||||
|
||||
if (isClioOnly(ctx.method))
|
||||
return false;
|
||||
|
||||
if (forwardCommands.find(ctx.method) != forwardCommands.end())
|
||||
return true;
|
||||
|
||||
|
||||
@@ -226,6 +226,9 @@ buildResponse(Context const& ctx);
|
||||
bool
|
||||
validHandler(std::string const& method);
|
||||
|
||||
bool
|
||||
isClioOnly(std::string const& method);
|
||||
|
||||
Status
|
||||
getLimit(RPC::Context const& context, std::uint32_t& limit);
|
||||
|
||||
|
||||
@@ -268,7 +268,7 @@ getTaker(boost::json::object const& request, ripple::AccountID& takerID)
|
||||
if (request.contains(JS(taker)))
|
||||
{
|
||||
auto parsed = parseTaker(request.at(JS(taker)));
|
||||
if (auto status = std::get_if<Status>(&parsed))
|
||||
if (auto status = std::get_if<Status>(&parsed); status)
|
||||
return *status;
|
||||
else
|
||||
takerID = std::get<ripple::AccountID>(parsed);
|
||||
@@ -808,15 +808,21 @@ traverseOwnedNodes(
|
||||
}
|
||||
auto end = std::chrono::system_clock::now();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Time loading owned directories: "
|
||||
<< ((end - start).count() / 1000000000.0);
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "Time loading owned directories: "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
|
||||
.count()
|
||||
<< " milliseconds";
|
||||
|
||||
start = std::chrono::system_clock::now();
|
||||
auto objects = backend.fetchLedgerObjects(keys, sequence, yield);
|
||||
end = std::chrono::system_clock::now();
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Time loading owned entries: "
|
||||
<< ((end - start).count() / 1000000000.0);
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< "Time loading owned entries: "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(end - start)
|
||||
.count()
|
||||
<< " milliseconds";
|
||||
|
||||
for (auto i = 0; i < objects.size(); ++i)
|
||||
{
|
||||
@@ -1480,4 +1486,211 @@ specifiesCurrentOrClosedLedger(boost::json::object const& request)
|
||||
return false;
|
||||
}
|
||||
|
||||
std::variant<ripple::uint256, Status>
|
||||
getNFTID(boost::json::object const& request)
|
||||
{
|
||||
if (!request.contains(JS(nft_id)))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingTokenID"};
|
||||
|
||||
if (!request.at(JS(nft_id)).is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "tokenIDNotString"};
|
||||
|
||||
ripple::uint256 tokenid;
|
||||
if (!tokenid.parseHex(request.at(JS(nft_id)).as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedTokenID"};
|
||||
|
||||
return tokenid;
|
||||
}
|
||||
|
||||
// TODO - this function is long and shouldn't be responsible for as much as it
|
||||
// is. Split it out into some helper functions.
|
||||
std::variant<Status, boost::json::object>
|
||||
traverseTransactions(
|
||||
Context const& context,
|
||||
std::function<Backend::TransactionsAndCursor(
|
||||
std::shared_ptr<Backend::BackendInterface const> const& backend,
|
||||
std::uint32_t const,
|
||||
bool const,
|
||||
std::optional<Backend::TransactionsCursor> const&,
|
||||
boost::asio::yield_context& yield)> transactionFetcher)
|
||||
{
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
bool const binary = getBool(request, JS(binary), false);
|
||||
bool const forward = getBool(request, JS(forward), false);
|
||||
|
||||
std::optional<Backend::TransactionsCursor> cursor;
|
||||
|
||||
if (request.contains(JS(marker)))
|
||||
{
|
||||
if (!request.at(JS(marker)).is_object())
|
||||
return Status{Error::rpcINVALID_PARAMS, "invalidMarker"};
|
||||
auto const& obj = request.at(JS(marker)).as_object();
|
||||
|
||||
std::optional<std::uint32_t> transactionIndex = {};
|
||||
if (obj.contains(JS(seq)))
|
||||
{
|
||||
if (!obj.at(JS(seq)).is_int64())
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS, "transactionIndexNotInt"};
|
||||
|
||||
transactionIndex =
|
||||
boost::json::value_to<std::uint32_t>(obj.at(JS(seq)));
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t> ledgerIndex = {};
|
||||
if (obj.contains(JS(ledger)))
|
||||
{
|
||||
if (!obj.at(JS(ledger)).is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "ledgerIndexNotInt"};
|
||||
|
||||
ledgerIndex =
|
||||
boost::json::value_to<std::uint32_t>(obj.at(JS(ledger)));
|
||||
}
|
||||
|
||||
if (!transactionIndex || !ledgerIndex)
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingLedgerOrSeq"};
|
||||
|
||||
cursor = {*ledgerIndex, *transactionIndex};
|
||||
}
|
||||
|
||||
auto minIndex = context.range.minSequence;
|
||||
if (request.contains(JS(ledger_index_min)))
|
||||
{
|
||||
auto& min = request.at(JS(ledger_index_min));
|
||||
|
||||
if (!min.is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMinNotNumber"};
|
||||
|
||||
if (min.as_int64() != -1)
|
||||
{
|
||||
if (context.range.maxSequence < min.as_int64() ||
|
||||
context.range.minSequence > min.as_int64())
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS, "ledgerSeqMinOutOfRange"};
|
||||
else
|
||||
minIndex = boost::json::value_to<std::uint32_t>(min);
|
||||
}
|
||||
|
||||
if (forward && !cursor)
|
||||
cursor = {minIndex, 0};
|
||||
}
|
||||
|
||||
auto maxIndex = context.range.maxSequence;
|
||||
if (request.contains(JS(ledger_index_max)))
|
||||
{
|
||||
auto& max = request.at(JS(ledger_index_max));
|
||||
|
||||
if (!max.is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMaxNotNumber"};
|
||||
|
||||
if (max.as_int64() != -1)
|
||||
{
|
||||
if (context.range.maxSequence < max.as_int64() ||
|
||||
context.range.minSequence > max.as_int64())
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS, "ledgerSeqMaxOutOfRange"};
|
||||
else
|
||||
maxIndex = boost::json::value_to<std::uint32_t>(max);
|
||||
}
|
||||
|
||||
if (minIndex > maxIndex)
|
||||
return Status{Error::rpcINVALID_PARAMS, "invalidIndex"};
|
||||
|
||||
if (!forward && !cursor)
|
||||
cursor = {maxIndex, INT32_MAX};
|
||||
}
|
||||
|
||||
if (request.contains(JS(ledger_index)) || request.contains(JS(ledger_hash)))
|
||||
{
|
||||
if (request.contains(JS(ledger_index_max)) ||
|
||||
request.contains(JS(ledger_index_min)))
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"};
|
||||
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v); status)
|
||||
return *status;
|
||||
|
||||
maxIndex = minIndex = std::get<ripple::LedgerInfo>(v).seq;
|
||||
}
|
||||
|
||||
if (!cursor)
|
||||
{
|
||||
if (forward)
|
||||
cursor = {minIndex, 0};
|
||||
else
|
||||
cursor = {maxIndex, INT32_MAX};
|
||||
}
|
||||
|
||||
std::uint32_t limit;
|
||||
if (auto const status = getLimit(context, limit); status)
|
||||
return status;
|
||||
|
||||
if (request.contains(JS(limit)))
|
||||
response[JS(limit)] = limit;
|
||||
|
||||
boost::json::array txns;
|
||||
auto [blobs, retCursor] = transactionFetcher(
|
||||
context.backend, limit, forward, cursor, context.yield);
|
||||
auto serializationStart = std::chrono::system_clock::now();
|
||||
|
||||
if (retCursor)
|
||||
{
|
||||
boost::json::object cursorJson;
|
||||
cursorJson[JS(ledger)] = retCursor->ledgerSequence;
|
||||
cursorJson[JS(seq)] = retCursor->transactionIndex;
|
||||
response[JS(marker)] = cursorJson;
|
||||
}
|
||||
|
||||
for (auto const& txnPlusMeta : blobs)
|
||||
{
|
||||
if (txnPlusMeta.ledgerSequence < minIndex ||
|
||||
txnPlusMeta.ledgerSequence > maxIndex)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< __func__
|
||||
<< " skipping over transactions from incomplete ledger";
|
||||
continue;
|
||||
}
|
||||
|
||||
boost::json::object obj;
|
||||
|
||||
if (!binary)
|
||||
{
|
||||
auto [txn, meta] = toExpandedJson(txnPlusMeta);
|
||||
obj[JS(meta)] = meta;
|
||||
obj[JS(tx)] = txn;
|
||||
obj[JS(tx)].as_object()[JS(ledger_index)] =
|
||||
txnPlusMeta.ledgerSequence;
|
||||
obj[JS(tx)].as_object()[JS(date)] = txnPlusMeta.date;
|
||||
}
|
||||
else
|
||||
{
|
||||
obj[JS(meta)] = ripple::strHex(txnPlusMeta.metadata);
|
||||
obj[JS(tx_blob)] = ripple::strHex(txnPlusMeta.transaction);
|
||||
obj[JS(ledger_index)] = txnPlusMeta.ledgerSequence;
|
||||
obj[JS(date)] = txnPlusMeta.date;
|
||||
}
|
||||
obj[JS(validated)] = true;
|
||||
|
||||
txns.push_back(obj);
|
||||
}
|
||||
|
||||
response[JS(ledger_index_min)] = minIndex;
|
||||
response[JS(ledger_index_max)] = maxIndex;
|
||||
|
||||
response[JS(transactions)] = txns;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info)
|
||||
<< __func__ << " serialization took "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now() - serializationStart)
|
||||
.count()
|
||||
<< " milliseconds";
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
namespace RPC {
|
||||
std::optional<ripple::AccountID>
|
||||
accountFromStringStrict(std::string const& account);
|
||||
|
||||
std::optional<ripple::AccountID>
|
||||
accountFromSeed(std::string const& account);
|
||||
|
||||
@@ -254,5 +255,20 @@ getChannelId(boost::json::object const& request, ripple::uint256& channelId);
|
||||
bool
|
||||
specifiesCurrentOrClosedLedger(boost::json::object const& request);
|
||||
|
||||
std::variant<ripple::uint256, Status>
|
||||
getNFTID(boost::json::object const& request);
|
||||
|
||||
// This function is the driver for both `account_tx` and `nft_tx` and should
|
||||
// be used for any future transaction enumeration APIs.
|
||||
std::variant<Status, boost::json::object>
|
||||
traverseTransactions(
|
||||
Context const& context,
|
||||
std::function<Backend::TransactionsAndCursor(
|
||||
std::shared_ptr<Backend::BackendInterface const> const& backend,
|
||||
std::uint32_t const,
|
||||
bool const,
|
||||
std::optional<Backend::TransactionsCursor> const&,
|
||||
boost::asio::yield_context& yield)> transactionFetcher);
|
||||
|
||||
} // namespace RPC
|
||||
#endif
|
||||
|
||||
@@ -1,212 +1,41 @@
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/Pg.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
|
||||
namespace RPC {
|
||||
|
||||
using boost::json::value_to;
|
||||
|
||||
Result
|
||||
doAccountTx(Context const& context)
|
||||
{
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
ripple::AccountID accountID;
|
||||
if (auto const status = getAccount(request, accountID); status)
|
||||
if (auto const status = getAccount(context.params, accountID); status)
|
||||
return status;
|
||||
|
||||
bool const binary = getBool(request, JS(binary), false);
|
||||
bool const forward = getBool(request, JS(forward), false);
|
||||
constexpr std::string_view outerFuncName = __func__;
|
||||
auto const maybeResponse = traverseTransactions(
|
||||
context,
|
||||
[&accountID, &outerFuncName](
|
||||
std::shared_ptr<Backend::BackendInterface const> const& backend,
|
||||
std::uint32_t const limit,
|
||||
bool const forward,
|
||||
std::optional<Backend::TransactionsCursor> const& cursorIn,
|
||||
boost::asio::yield_context& yield) {
|
||||
auto const start = std::chrono::system_clock::now();
|
||||
auto const txnsAndCursor = backend->fetchAccountTransactions(
|
||||
accountID, limit, forward, cursorIn, yield);
|
||||
BOOST_LOG_TRIVIAL(info)
|
||||
<< outerFuncName << " db fetch took "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now() - start)
|
||||
.count()
|
||||
<< " milliseconds - num blobs = " << txnsAndCursor.txns.size();
|
||||
return txnsAndCursor;
|
||||
});
|
||||
|
||||
std::optional<Backend::TransactionsCursor> cursor;
|
||||
|
||||
if (request.contains(JS(marker)))
|
||||
{
|
||||
auto const& obj = request.at(JS(marker)).as_object();
|
||||
|
||||
std::optional<std::uint32_t> transactionIndex = {};
|
||||
if (obj.contains(JS(seq)))
|
||||
{
|
||||
if (!obj.at(JS(seq)).is_int64())
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS, "transactionIndexNotInt"};
|
||||
|
||||
transactionIndex =
|
||||
boost::json::value_to<std::uint32_t>(obj.at(JS(seq)));
|
||||
}
|
||||
|
||||
std::optional<std::uint32_t> ledgerIndex = {};
|
||||
if (obj.contains(JS(ledger)))
|
||||
{
|
||||
if (!obj.at(JS(ledger)).is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "ledgerIndexNotInt"};
|
||||
|
||||
ledgerIndex =
|
||||
boost::json::value_to<std::uint32_t>(obj.at(JS(ledger)));
|
||||
}
|
||||
|
||||
if (!transactionIndex || !ledgerIndex)
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingLedgerOrSeq"};
|
||||
|
||||
cursor = {*ledgerIndex, *transactionIndex};
|
||||
}
|
||||
|
||||
auto minIndex = context.range.minSequence;
|
||||
if (request.contains(JS(ledger_index_min)))
|
||||
{
|
||||
auto& min = request.at(JS(ledger_index_min));
|
||||
|
||||
if (!min.is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMinNotNumber"};
|
||||
|
||||
if (min.as_int64() != -1)
|
||||
{
|
||||
if (context.range.maxSequence < min.as_int64() ||
|
||||
context.range.minSequence > min.as_int64())
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS, "ledgerSeqMinOutOfRange"};
|
||||
else
|
||||
minIndex = value_to<std::uint32_t>(min);
|
||||
}
|
||||
|
||||
if (forward && !cursor)
|
||||
cursor = {minIndex, 0};
|
||||
}
|
||||
|
||||
auto maxIndex = context.range.maxSequence;
|
||||
if (request.contains(JS(ledger_index_max)))
|
||||
{
|
||||
auto& max = request.at(JS(ledger_index_max));
|
||||
|
||||
if (!max.is_int64())
|
||||
return Status{Error::rpcINVALID_PARAMS, "ledgerSeqMaxNotNumber"};
|
||||
|
||||
if (max.as_int64() != -1)
|
||||
{
|
||||
if (context.range.maxSequence < max.as_int64() ||
|
||||
context.range.minSequence > max.as_int64())
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS, "ledgerSeqMaxOutOfRange"};
|
||||
else
|
||||
maxIndex = value_to<std::uint32_t>(max);
|
||||
}
|
||||
|
||||
if (minIndex > maxIndex)
|
||||
return Status{Error::rpcINVALID_PARAMS, "invalidIndex"};
|
||||
|
||||
if (!forward && !cursor)
|
||||
cursor = {maxIndex, INT32_MAX};
|
||||
}
|
||||
|
||||
if (request.contains(JS(ledger_index)) || request.contains(JS(ledger_hash)))
|
||||
{
|
||||
if (request.contains(JS(ledger_index_max)) ||
|
||||
request.contains(JS(ledger_index_min)))
|
||||
return Status{
|
||||
Error::rpcINVALID_PARAMS, "containsLedgerSpecifierAndRange"};
|
||||
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
maxIndex = minIndex = std::get<ripple::LedgerInfo>(v).seq;
|
||||
}
|
||||
|
||||
if (!cursor)
|
||||
{
|
||||
if (forward)
|
||||
cursor = {minIndex, 0};
|
||||
else
|
||||
cursor = {maxIndex, INT32_MAX};
|
||||
}
|
||||
|
||||
std::uint32_t limit;
|
||||
if (auto const status = getLimit(context, limit); status)
|
||||
return status;
|
||||
|
||||
if (request.contains(JS(limit)))
|
||||
response[JS(limit)] = limit;
|
||||
|
||||
boost::json::array txns;
|
||||
auto start = std::chrono::system_clock::now();
|
||||
auto [blobs, retCursor] = context.backend->fetchAccountTransactions(
|
||||
accountID, limit, forward, cursor, context.yield);
|
||||
|
||||
auto end = std::chrono::system_clock::now();
|
||||
BOOST_LOG_TRIVIAL(info) << __func__ << " db fetch took "
|
||||
<< ((end - start).count() / 1000000000.0)
|
||||
<< " num blobs = " << blobs.size();
|
||||
if (auto const status = std::get_if<Status>(&maybeResponse); status)
|
||||
return *status;
|
||||
auto response = std::get<boost::json::object>(maybeResponse);
|
||||
|
||||
response[JS(account)] = ripple::to_string(accountID);
|
||||
|
||||
if (retCursor)
|
||||
{
|
||||
boost::json::object cursorJson;
|
||||
cursorJson[JS(ledger)] = retCursor->ledgerSequence;
|
||||
cursorJson[JS(seq)] = retCursor->transactionIndex;
|
||||
response[JS(marker)] = cursorJson;
|
||||
}
|
||||
|
||||
std::optional<size_t> maxReturnedIndex;
|
||||
std::optional<size_t> minReturnedIndex;
|
||||
for (auto const& txnPlusMeta : blobs)
|
||||
{
|
||||
if (txnPlusMeta.ledgerSequence < minIndex ||
|
||||
txnPlusMeta.ledgerSequence > maxIndex)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug)
|
||||
<< __func__
|
||||
<< " skipping over transactions from incomplete ledger";
|
||||
continue;
|
||||
}
|
||||
|
||||
boost::json::object obj;
|
||||
|
||||
if (!binary)
|
||||
{
|
||||
auto [txn, meta] = toExpandedJson(txnPlusMeta);
|
||||
obj[JS(meta)] = meta;
|
||||
obj[JS(tx)] = txn;
|
||||
obj[JS(tx)].as_object()[JS(ledger_index)] =
|
||||
txnPlusMeta.ledgerSequence;
|
||||
obj[JS(tx)].as_object()[JS(date)] = txnPlusMeta.date;
|
||||
}
|
||||
else
|
||||
{
|
||||
obj[JS(meta)] = ripple::strHex(txnPlusMeta.metadata);
|
||||
obj[JS(tx_blob)] = ripple::strHex(txnPlusMeta.transaction);
|
||||
obj[JS(ledger_index)] = txnPlusMeta.ledgerSequence;
|
||||
obj[JS(date)] = txnPlusMeta.date;
|
||||
}
|
||||
obj[JS(validated)] = true;
|
||||
|
||||
txns.push_back(obj);
|
||||
if (!minReturnedIndex || txnPlusMeta.ledgerSequence < *minReturnedIndex)
|
||||
minReturnedIndex = txnPlusMeta.ledgerSequence;
|
||||
if (!maxReturnedIndex || txnPlusMeta.ledgerSequence > *maxReturnedIndex)
|
||||
maxReturnedIndex = txnPlusMeta.ledgerSequence;
|
||||
}
|
||||
|
||||
assert(cursor);
|
||||
if (!forward)
|
||||
{
|
||||
response[JS(ledger_index_min)] = cursor->ledgerSequence;
|
||||
response[JS(ledger_index_max)] = maxIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
response[JS(ledger_index_max)] = cursor->ledgerSequence;
|
||||
response[JS(ledger_index_min)] = minIndex;
|
||||
}
|
||||
|
||||
response[JS(transactions)] = txns;
|
||||
|
||||
auto end2 = std::chrono::system_clock::now();
|
||||
BOOST_LOG_TRIVIAL(info) << __func__ << " serialization took "
|
||||
<< ((end2 - end).count() / 1000000000.0);
|
||||
|
||||
return response;
|
||||
} // namespace RPC
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
|
||||
43
src/rpc/handlers/NFTHistory.cpp
Normal file
43
src/rpc/handlers/NFTHistory.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include <rpc/RPCHelpers.h>
|
||||
|
||||
namespace RPC {
|
||||
|
||||
Result
|
||||
doNFTHistory(Context const& context)
|
||||
{
|
||||
auto const maybeTokenID = getNFTID(context.params);
|
||||
if (auto const status = std::get_if<Status>(&maybeTokenID); status)
|
||||
return *status;
|
||||
auto const tokenID = std::get<ripple::uint256>(maybeTokenID);
|
||||
|
||||
constexpr std::string_view outerFuncName = __func__;
|
||||
auto const maybeResponse = traverseTransactions(
|
||||
context,
|
||||
[&tokenID, &outerFuncName](
|
||||
std::shared_ptr<Backend::BackendInterface const> const& backend,
|
||||
std::uint32_t const limit,
|
||||
bool const forward,
|
||||
std::optional<Backend::TransactionsCursor> const& cursorIn,
|
||||
boost::asio::yield_context& yield)
|
||||
-> Backend::TransactionsAndCursor {
|
||||
auto const start = std::chrono::system_clock::now();
|
||||
auto const txnsAndCursor = backend->fetchNFTTransactions(
|
||||
tokenID, limit, forward, cursorIn, yield);
|
||||
BOOST_LOG_TRIVIAL(info)
|
||||
<< outerFuncName << " db fetch took "
|
||||
<< std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now() - start)
|
||||
.count()
|
||||
<< " milliseconds - num blobs = " << txnsAndCursor.txns.size();
|
||||
return txnsAndCursor;
|
||||
});
|
||||
|
||||
if (auto const status = std::get_if<Status>(&maybeResponse); status)
|
||||
return *status;
|
||||
auto response = std::get<boost::json::object>(maybeResponse);
|
||||
|
||||
response[JS(nft_id)] = ripple::to_string(tokenID);
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace RPC
|
||||
@@ -89,24 +89,15 @@ doNFTInfo(Context const& context)
|
||||
auto request = context.params;
|
||||
boost::json::object response = {};
|
||||
|
||||
if (!request.contains("nft_id"))
|
||||
return Status{Error::rpcINVALID_PARAMS, "Missing nft_id"};
|
||||
|
||||
auto const& jsonTokenID = request.at("nft_id");
|
||||
if (!jsonTokenID.is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "nft_id is not a string"};
|
||||
|
||||
ripple::uint256 tokenID;
|
||||
if (!tokenID.parseHex(jsonTokenID.as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "Malformed nft_id"};
|
||||
|
||||
// We only need to fetch the ledger header because the ledger hash is
|
||||
// supposed to be included in the response. The ledger sequence is specified
|
||||
// in the request
|
||||
auto v = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&v))
|
||||
auto const maybeTokenID = getNFTID(request);
|
||||
if (auto const status = std::get_if<Status>(&maybeTokenID); status)
|
||||
return *status;
|
||||
ripple::LedgerInfo lgrInfo = std::get<ripple::LedgerInfo>(v);
|
||||
auto const tokenID = std::get<ripple::uint256>(maybeTokenID);
|
||||
|
||||
auto const maybeLedgerInfo = ledgerInfoFromRequest(context);
|
||||
if (auto status = std::get_if<Status>(&maybeLedgerInfo); status)
|
||||
return *status;
|
||||
auto const lgrInfo = std::get<ripple::LedgerInfo>(maybeLedgerInfo);
|
||||
|
||||
std::optional<Backend::NFT> dbResponse =
|
||||
context.backend->fetchNFT(tokenID, lgrInfo.seq, context.yield);
|
||||
@@ -130,10 +121,10 @@ doNFTInfo(Context const& context)
|
||||
{
|
||||
auto const maybeURI = getURI(*dbResponse, context);
|
||||
// An error occurred
|
||||
if (Status const* status = std::get_if<Status>(&maybeURI))
|
||||
if (Status const* status = std::get_if<Status>(&maybeURI); status)
|
||||
return *status;
|
||||
// A URI was found
|
||||
if (std::string const* uri = std::get_if<std::string>(&maybeURI))
|
||||
if (std::string const* uri = std::get_if<std::string>(&maybeURI); uri)
|
||||
response["uri"] = *uri;
|
||||
// A URI was not found, explicitly set to null
|
||||
else
|
||||
|
||||
@@ -129,26 +129,10 @@ enumerateNFTOffers(
|
||||
return response;
|
||||
}
|
||||
|
||||
std::variant<ripple::uint256, Status>
|
||||
getTokenid(boost::json::object const& request)
|
||||
{
|
||||
if (!request.contains(JS(nft_id)))
|
||||
return Status{Error::rpcINVALID_PARAMS, "missingTokenid"};
|
||||
|
||||
if (!request.at(JS(nft_id)).is_string())
|
||||
return Status{Error::rpcINVALID_PARAMS, "tokenidNotString"};
|
||||
|
||||
ripple::uint256 tokenid;
|
||||
if (!tokenid.parseHex(request.at(JS(nft_id)).as_string().c_str()))
|
||||
return Status{Error::rpcINVALID_PARAMS, "malformedCursor"};
|
||||
|
||||
return tokenid;
|
||||
}
|
||||
|
||||
Result
|
||||
doNFTOffers(Context const& context, bool sells)
|
||||
{
|
||||
auto const v = getTokenid(context.params);
|
||||
auto const v = getNFTID(context.params);
|
||||
if (auto const status = std::get_if<Status>(&v))
|
||||
return *status;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user