mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +00:00
@@ -17,13 +17,15 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <backend/BackendInterface.h>
|
||||
#include <backend/DBHelpers.h>
|
||||
#include <log/Logger.h>
|
||||
#include <rpc/Errors.h>
|
||||
#include <rpc/RPCHelpers.h>
|
||||
#include <util/Profiler.h>
|
||||
|
||||
#include <ripple/basics/StringUtilities.h>
|
||||
#include <ripple/protocol/nftPageMask.h>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
@@ -405,6 +407,61 @@ getStartHint(ripple::SLE const& sle, ripple::AccountID const& accountID)
|
||||
return sle.getFieldU64(ripple::sfOwnerNode);
|
||||
}
|
||||
|
||||
// traverse account's nfts
|
||||
// return Status if error occurs
|
||||
// return [nextpage, count of nft already found] if success
|
||||
std::variant<Status, AccountCursor>
|
||||
traverseNFTObjects(
|
||||
BackendInterface const& backend,
|
||||
std::uint32_t sequence,
|
||||
ripple::AccountID const& accountID,
|
||||
ripple::uint256 nextPage,
|
||||
std::uint32_t limit,
|
||||
boost::asio::yield_context& yield,
|
||||
std::function<void(ripple::SLE&&)> atOwnedNode)
|
||||
{
|
||||
auto const firstNFTPage = ripple::keylet::nftpage_min(accountID);
|
||||
auto const lastNFTPage = ripple::keylet::nftpage_max(accountID);
|
||||
|
||||
// check if nextPage is valid
|
||||
if (nextPage != beast::zero and firstNFTPage.key != (nextPage & ~ripple::nft::pageMask))
|
||||
return Status{RippledError::rpcINVALID_PARAMS, "Invalid marker."};
|
||||
|
||||
// no marker, start from the last page
|
||||
ripple::uint256 currentPage = nextPage == beast::zero ? lastNFTPage.key : nextPage;
|
||||
|
||||
// read the current page
|
||||
auto page = backend.fetchLedgerObject(currentPage, sequence, yield);
|
||||
|
||||
if (!page)
|
||||
{
|
||||
if (nextPage == beast::zero) // no nft objects in lastNFTPage
|
||||
return AccountCursor{beast::zero, 0};
|
||||
else // marker is in the right range, but still invalid
|
||||
return Status{RippledError::rpcINVALID_PARAMS, "Invalid marker."};
|
||||
}
|
||||
|
||||
// the object exists and the key is in right range, must be nft page
|
||||
ripple::SLE pageSLE{ripple::SLE{ripple::SerialIter{page->data(), page->size()}, currentPage}};
|
||||
|
||||
auto count = 0;
|
||||
// traverse the nft page linked list until the start of the list or reach the limit
|
||||
while (true)
|
||||
{
|
||||
auto const nftPreviousPage = pageSLE.getFieldH256(ripple::sfPreviousPageMin);
|
||||
atOwnedNode(std::move(pageSLE));
|
||||
count++;
|
||||
|
||||
if (count == limit or nftPreviousPage == beast::zero)
|
||||
return AccountCursor{nftPreviousPage, count};
|
||||
|
||||
page = backend.fetchLedgerObject(nftPreviousPage, sequence, yield);
|
||||
pageSLE = ripple::SLE{ripple::SerialIter{page->data(), page->size()}, nftPreviousPage};
|
||||
}
|
||||
|
||||
return AccountCursor{beast::zero, 0};
|
||||
}
|
||||
|
||||
std::variant<Status, AccountCursor>
|
||||
traverseOwnedNodes(
|
||||
BackendInterface const& backend,
|
||||
@@ -413,53 +470,50 @@ traverseOwnedNodes(
|
||||
std::uint32_t limit,
|
||||
std::optional<std::string> jsonCursor,
|
||||
boost::asio::yield_context& yield,
|
||||
std::function<void(ripple::SLE&&)> atOwnedNode)
|
||||
std::function<void(ripple::SLE&&)> atOwnedNode,
|
||||
bool nftIncluded)
|
||||
{
|
||||
if (!backend.fetchLedgerObject(ripple::keylet::account(accountID).key, sequence, yield))
|
||||
return Status{RippledError::rpcACT_NOT_FOUND};
|
||||
|
||||
auto const maybeCursor = parseAccountCursor(jsonCursor);
|
||||
if (!maybeCursor)
|
||||
return Status(ripple::rpcINVALID_PARAMS, "Malformed cursor");
|
||||
|
||||
if (!maybeCursor)
|
||||
return Status{RippledError::rpcINVALID_PARAMS, "Malformed cursor."};
|
||||
|
||||
// the format is checked in RPC framework level
|
||||
auto [hexCursor, startHint] = *maybeCursor;
|
||||
|
||||
return traverseOwnedNodes(
|
||||
backend,
|
||||
ripple::keylet::ownerDir(accountID),
|
||||
hexCursor,
|
||||
startHint,
|
||||
sequence,
|
||||
limit,
|
||||
jsonCursor,
|
||||
yield,
|
||||
atOwnedNode);
|
||||
}
|
||||
auto const isNftMarkerNonZero = startHint == std::numeric_limits<uint32_t>::max() and hexCursor != beast::zero;
|
||||
auto const isNftMarkerZero = startHint == std::numeric_limits<uint32_t>::max() and hexCursor == beast::zero;
|
||||
// if we need to traverse nft objects and this is the first request -> traverse nft objects
|
||||
// if we need to traverse nft objects and the marker is still in nft page -> traverse nft objects
|
||||
// if we need to traverse nft objects and the marker is still in nft page but next page is zero -> owned nodes
|
||||
// if we need to traverse nft objects and the marker is not in nft page -> traverse owned nodes
|
||||
if (nftIncluded and (!jsonCursor or isNftMarkerNonZero))
|
||||
{
|
||||
auto const cursorMaybe = traverseNFTObjects(backend, sequence, accountID, hexCursor, limit, yield, atOwnedNode);
|
||||
|
||||
std::variant<Status, AccountCursor>
|
||||
ngTraverseOwnedNodes(
|
||||
BackendInterface const& backend,
|
||||
ripple::AccountID const& accountID,
|
||||
std::uint32_t sequence,
|
||||
std::uint32_t limit,
|
||||
std::optional<std::string> jsonCursor,
|
||||
boost::asio::yield_context& yield,
|
||||
std::function<void(ripple::SLE&&)> atOwnedNode)
|
||||
{
|
||||
auto const maybeCursor = parseAccountCursor(jsonCursor);
|
||||
// the format is checked in RPC framework level
|
||||
auto const [hexCursor, startHint] = *maybeCursor;
|
||||
if (auto const status = std::get_if<Status>(&cursorMaybe))
|
||||
return *status;
|
||||
|
||||
auto const [nextNFTPage, nftsCount] = std::get<AccountCursor>(cursorMaybe);
|
||||
|
||||
// if limit reach , we return the next page and max as marker
|
||||
if (nftsCount >= limit)
|
||||
return AccountCursor{nextNFTPage, std::numeric_limits<uint32_t>::max()};
|
||||
|
||||
// adjust limit ,continue traversing owned nodes
|
||||
limit -= nftsCount;
|
||||
hexCursor = beast::zero;
|
||||
startHint = 0;
|
||||
}
|
||||
else if (nftIncluded and isNftMarkerZero)
|
||||
{
|
||||
// the last request happen to fetch all the nft, adjust marker to continue traversing owned nodes
|
||||
hexCursor = beast::zero;
|
||||
startHint = 0;
|
||||
}
|
||||
|
||||
return traverseOwnedNodes(
|
||||
backend,
|
||||
ripple::keylet::ownerDir(accountID),
|
||||
hexCursor,
|
||||
startHint,
|
||||
sequence,
|
||||
limit,
|
||||
jsonCursor,
|
||||
yield,
|
||||
atOwnedNode);
|
||||
backend, ripple::keylet::ownerDir(accountID), hexCursor, startHint, sequence, limit, yield, atOwnedNode);
|
||||
}
|
||||
|
||||
std::variant<Status, AccountCursor>
|
||||
@@ -470,7 +524,6 @@ traverseOwnedNodes(
|
||||
std::uint32_t const startHint,
|
||||
std::uint32_t sequence,
|
||||
std::uint32_t limit,
|
||||
std::optional<std::string> jsonCursor,
|
||||
boost::asio::yield_context& yield,
|
||||
std::function<void(ripple::SLE&&)> atOwnedNode)
|
||||
{
|
||||
@@ -496,7 +549,7 @@ traverseOwnedNodes(
|
||||
auto hintDir = backend.fetchLedgerObject(hintIndex.key, sequence, yield);
|
||||
|
||||
if (!hintDir)
|
||||
return Status(ripple::rpcINVALID_PARAMS, "Invalid marker");
|
||||
return Status(ripple::rpcINVALID_PARAMS, "Invalid marker.");
|
||||
|
||||
ripple::SerialIter it{hintDir->data(), hintDir->size()};
|
||||
ripple::SLE sle{it, hintIndex.key};
|
||||
@@ -505,7 +558,7 @@ traverseOwnedNodes(
|
||||
std::find(std::begin(indexes), std::end(indexes), hexMarker) == std::end(indexes))
|
||||
{
|
||||
// the index specified by marker is not in the page specified by marker
|
||||
return Status(ripple::rpcINVALID_PARAMS, "Invalid marker");
|
||||
return Status(ripple::rpcINVALID_PARAMS, "Invalid marker.");
|
||||
}
|
||||
|
||||
currentIndex = hintIndex;
|
||||
@@ -515,7 +568,7 @@ traverseOwnedNodes(
|
||||
auto const ownerDir = backend.fetchLedgerObject(currentIndex.key, sequence, yield);
|
||||
|
||||
if (!ownerDir)
|
||||
return Status(ripple::rpcINVALID_PARAMS, "Owner directory not found");
|
||||
return Status(ripple::rpcINVALID_PARAMS, "Owner directory not found.");
|
||||
|
||||
ripple::SerialIter it{ownerDir->data(), ownerDir->size()};
|
||||
ripple::SLE sle{it, currentIndex.key};
|
||||
|
||||
@@ -101,16 +101,6 @@ getLedgerInfoFromHashOrSeq(
|
||||
std::optional<uint32_t> ledgerIndex,
|
||||
uint32_t maxSeq);
|
||||
|
||||
std::variant<Status, AccountCursor>
|
||||
traverseOwnedNodes(
|
||||
BackendInterface const& backend,
|
||||
ripple::AccountID const& accountID,
|
||||
std::uint32_t sequence,
|
||||
std::uint32_t limit,
|
||||
std::optional<std::string> jsonCursor,
|
||||
boost::asio::yield_context& yield,
|
||||
std::function<void(ripple::SLE&&)> atOwnedNode);
|
||||
|
||||
std::variant<Status, AccountCursor>
|
||||
traverseOwnedNodes(
|
||||
BackendInterface const& backend,
|
||||
@@ -119,21 +109,21 @@ traverseOwnedNodes(
|
||||
std::uint32_t const startHint,
|
||||
std::uint32_t sequence,
|
||||
std::uint32_t limit,
|
||||
std::optional<std::string> jsonCursor,
|
||||
boost::asio::yield_context& yield,
|
||||
std::function<void(ripple::SLE&&)> atOwnedNode);
|
||||
|
||||
// Remove the account check from traverseOwnedNodes
|
||||
// Account check has been done by framework,remove it from internal function
|
||||
std::variant<Status, AccountCursor>
|
||||
ngTraverseOwnedNodes(
|
||||
traverseOwnedNodes(
|
||||
BackendInterface const& backend,
|
||||
ripple::AccountID const& accountID,
|
||||
std::uint32_t sequence,
|
||||
std::uint32_t limit,
|
||||
std::optional<std::string> jsonCursor,
|
||||
boost::asio::yield_context& yield,
|
||||
std::function<void(ripple::SLE&&)> atOwnedNode);
|
||||
std::function<void(ripple::SLE&&)> atOwnedNode,
|
||||
bool nftIncluded = false);
|
||||
|
||||
std::shared_ptr<ripple::SLE const>
|
||||
read(
|
||||
|
||||
@@ -160,7 +160,7 @@ CustomValidator AccountMarkerValidator =
|
||||
if (!parseAccountCursor(value.as_string().c_str()))
|
||||
{
|
||||
// align with the current error message
|
||||
return Error{Status{RippledError::rpcINVALID_PARAMS, "Malformed cursor"}};
|
||||
return Error{Status{RippledError::rpcINVALID_PARAMS, "Malformed cursor."}};
|
||||
}
|
||||
|
||||
return MaybeError{};
|
||||
|
||||
@@ -87,7 +87,7 @@ AccountChannelsHandler::process(AccountChannelsHandler::Input input, Context con
|
||||
return true;
|
||||
};
|
||||
|
||||
auto const next = ngTraverseOwnedNodes(
|
||||
auto const next = traverseOwnedNodes(
|
||||
*sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
|
||||
|
||||
if (auto status = std::get_if<Status>(&next))
|
||||
|
||||
@@ -63,7 +63,7 @@ AccountCurrenciesHandler::process(AccountCurrenciesHandler::Input input, Context
|
||||
};
|
||||
|
||||
// traverse all owned nodes, limit->max, marker->empty
|
||||
ngTraverseOwnedNodes(
|
||||
traverseOwnedNodes(
|
||||
*sharedPtrBackend_,
|
||||
*accountID,
|
||||
lgrInfo.seq,
|
||||
|
||||
@@ -129,7 +129,7 @@ AccountLinesHandler::process(AccountLinesHandler::Input input, Context const& ct
|
||||
}
|
||||
};
|
||||
|
||||
auto const next = ngTraverseOwnedNodes(
|
||||
auto const next = traverseOwnedNodes(
|
||||
*sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
|
||||
|
||||
if (auto status = std::get_if<Status>(&next))
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
namespace RPC {
|
||||
|
||||
// document does not mention nft_page, we still support it tho
|
||||
std::unordered_map<std::string, ripple::LedgerEntryType> const AccountObjectsHandler::TYPESMAP{
|
||||
{"state", ripple::ltRIPPLE_STATE},
|
||||
{"ticket", ripple::ltTICKET},
|
||||
@@ -93,8 +92,8 @@ AccountObjectsHandler::process(AccountObjectsHandler::Input input, Context const
|
||||
return true;
|
||||
};
|
||||
|
||||
auto const next = ngTraverseOwnedNodes(
|
||||
*sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
|
||||
auto const next = traverseOwnedNodes(
|
||||
*sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse, true);
|
||||
|
||||
if (auto status = std::get_if<Status>(&next))
|
||||
return Error{*status};
|
||||
|
||||
@@ -70,7 +70,7 @@ AccountOffersHandler::process(AccountOffersHandler::Input input, Context const&
|
||||
return true;
|
||||
};
|
||||
|
||||
auto const next = ngTraverseOwnedNodes(
|
||||
auto const next = traverseOwnedNodes(
|
||||
*sharedPtrBackend_, *accountID, lgrInfo.seq, input.limit, input.marker, ctx.yield, addToResponse);
|
||||
|
||||
if (auto const status = std::get_if<Status>(&next))
|
||||
|
||||
@@ -109,7 +109,7 @@ GatewayBalancesHandler::process(GatewayBalancesHandler::Input input, Context con
|
||||
};
|
||||
|
||||
// traverse all owned nodes, limit->max, marker->empty
|
||||
auto const ret = ngTraverseOwnedNodes(
|
||||
auto const ret = traverseOwnedNodes(
|
||||
*sharedPtrBackend_,
|
||||
*accountID,
|
||||
lgrInfo.seq,
|
||||
|
||||
@@ -111,15 +111,7 @@ NFTOffersHandlerBase::iterateOfferDirectory(
|
||||
}
|
||||
|
||||
auto result = traverseOwnedNodes(
|
||||
*sharedPtrBackend_,
|
||||
directory,
|
||||
cursor,
|
||||
startHint,
|
||||
lgrInfo.seq,
|
||||
reserve,
|
||||
{},
|
||||
yield,
|
||||
[&offers](ripple::SLE&& offer) {
|
||||
*sharedPtrBackend_, directory, cursor, startHint, lgrInfo.seq, reserve, yield, [&offers](ripple::SLE&& offer) {
|
||||
if (offer.getType() == ripple::ltNFTOKEN_OFFER)
|
||||
{
|
||||
offers.push_back(std::move(offer));
|
||||
|
||||
@@ -85,7 +85,7 @@ NoRippleCheckHandler::process(NoRippleCheckHandler::Input input, Context const&
|
||||
|
||||
auto limit = input.limit;
|
||||
|
||||
ngTraverseOwnedNodes(
|
||||
traverseOwnedNodes(
|
||||
*sharedPtrBackend_,
|
||||
*accountID,
|
||||
lgrInfo.seq,
|
||||
|
||||
Reference in New Issue
Block a user