Compare commits

...
Author SHA1 Message Date
Mayukha Vadari 57da6709a0 Document insertToken parameters 2026-10-05 15:45:21 +01:00
Mayukha Vadari d6e8b9d273 Include Keylet.h in the SLEBase test
(cherry picked from commit 3cca9d3266)
2026-10-05 15:45:09 +01:00
Mayukha Vadari 3571bfe25f test: Drop extern template declarations from SLEBase test
An explicit instantiation declaration stops clang from emitting the
inline constructors of the header-only entries at -O0, so the debug
builds fail to link xrpl_tests (undefined AccountRootEntry constructors).
Naming each class in a static_assert already instantiates its definition,
which is all the exhaustiveness check needs.

(cherry picked from commit 611415e1f9)
2026-10-05 15:45:09 +01:00
Mayukha Vadari 13d8989c35 refactor: Move insertToken's per-page step onto NFTokenPageEntry
nft::insertToken() first finds or creates the page for the token (and
may split a full page), then adds the token to that page's NFTokens
array, sorts it, and updates the page. The second part only needs the
page, so it becomes NFTokenPageEntry::insertToken(STObject&&). The
free function keeps the page lookup and calls the member.

The member body uses nft::compareTokens(), so it goes into a new
NFTokenPageEntry.cpp with explicit instantiation. The header now
includes STArray.h because the explicit instantiation also
instantiates the inline findToken(), which iterates an STArray.

No behavior change.
2026-10-05 15:45:09 +01:00
Mayukha Vadari ff3e8fe2d3 test: Declare, not define, entry instantiations in SLEBase test
The exhaustive per-entry-type check in the SLEBase gtest used explicit
instantiation definitions. Once an entry class gets out-of-line member
bodies in its own .cpp (with the explicit instantiation definition
there), the test would instantiate the class a second time, and MSVC
rejects it with C4661 because those member bodies are not visible in
the test translation unit. Explicit instantiation declarations still
instantiate the class definition, which is all the check needs.

(cherry picked from commit 8b07abc3cd)
2026-10-05 15:45:09 +01:00
Mayukha Vadari 4a0a10747d refactor: Use NFTokenPageEntry::findToken in findTokenAndPage
nft::findTokenAndPage() had its own copy of the per-page scan that
NFTokenPageEntry::findToken() now provides. Call the member instead.
The page lookup stays in the free function.

No behavior change.
2026-10-05 15:45:05 +01:00
Mayukha Vadari 48d9881906 Document findToken parameters and return value 2026-10-05 15:44:56 +01:00
Mayukha Vadari 96a0c607b1 refactor: Move findToken's per-page search onto NFTokenPageEntry
nft::findToken() first locates the candidate page by key and then
scans that page's NFTokens array for the ID. The scan only needs the
page, so it becomes NFTokenPageEntry::findToken(id) const. The free
function keeps the page lookup and calls the member.

No behavior change.
2026-10-05 13:41:11 +01:00
Mayukha Vadari 72eaa4c0c6 refactor: Use NFTokenPageEntry everywhere
Replace raw NFTokenPage SLE pointers with NFTokenPageEntryR and
NFTokenPageEntryW in the NFToken helpers and in AccountDelete. The
page-taking helpers (locatePage, getPageForToken, mergePages,
removeToken, TokenAndPage::page) now take or return the wrapper, so
the compiler enforces read-only versus writable access to pages.

Pure refactor: the order of reads, writes, updates and erases is
unchanged.
2026-10-05 13:33:23 +01:00
6 changed files with 175 additions and 96 deletions

No files matched your search

@@ -8,6 +8,11 @@
#include <xrpl/protocol/Indexes.h> #include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h> #include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h> #include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h> // IWYU pragma: keep
#include <xrpl/protocol/STObject.h>
#include <optional>
namespace xrpl { namespace xrpl {
@@ -29,6 +34,37 @@ public:
: Base(keylet::nftokenPage(page, token), view, j) : Base(keylet::nftokenPage(page, token), view, j)
{ {
} }
/**
* Returns the token with ID @p id if this page holds it.
*
* @param id the ID of the token to look for.
* @return the token, or std::nullopt if this page does not hold it.
*/
[[nodiscard]] std::optional<STObject>
findToken(UInt256 const& id) const
{
for (auto const& t : (*this)->getFieldArray(sfNFTokens))
{
if (t[sfNFTokenID] == id)
return t;
}
return std::nullopt;
}
/**
* Adds @p nft to this page's NFTokens array, keeps the array sorted, and
* updates the page.
*
* The caller must make sure that the token belongs on this page and that
* the page has space for it.
*
* @param nft the token to add.
*/
void
insertToken(STObject&& nft)
requires Base::kIsWritable;
}; };
using NFTokenPageEntryR = NFTokenPageEntry<ReadView>; using NFTokenPageEntryR = NFTokenPageEntry<ReadView>;
+9 -3
View File
@@ -5,6 +5,7 @@
#include <xrpl/beast/utility/Journal.h> #include <xrpl/beast/utility/Journal.h>
#include <xrpl/ledger/ApplyView.h> #include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h> #include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/NFTokenPageEntry.h>
#include <xrpl/protocol/AccountID.h> #include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Issue.h> #include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/Keylet.h> #include <xrpl/protocol/Keylet.h>
@@ -46,9 +47,10 @@ findToken(ReadView const& view, AccountID const& owner, UInt256 const& nftokenID
struct TokenAndPage struct TokenAndPage
{ {
STObject token; STObject token;
SLE::pointer page; NFTokenPageEntryW page;
TokenAndPage(STObject token, SLE::pointer page) : token(std::move(token)), page(std::move(page)) TokenAndPage(STObject token, NFTokenPageEntryW page)
: token(std::move(token)), page(std::move(page))
{ {
} }
}; };
@@ -68,7 +70,11 @@ TER
removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID); removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID);
TER TER
removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID, SLE::Ref page); removeToken(
ApplyView& view,
AccountID const& owner,
UInt256 const& nftokenID,
NFTokenPageEntryW& page);
/** /**
* Deletes the given token offer. * Deletes the given token offer.
@@ -0,0 +1,35 @@
#include <xrpl/ledger/entries/NFTokenPageEntry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STObject.h>
#include <utility>
namespace xrpl {
template <typename ViewT>
void
NFTokenPageEntry<ViewT>::insertToken(STObject&& nft)
requires Base::kIsWritable
{
{
auto arr = (*this)->getFieldArray(sfNFTokens);
arr.pushBack(std::move(nft));
arr.sort([](STObject const& o1, STObject const& o2) {
return nft::compareTokens(o1.getFieldH256(sfNFTokenID), o2.getFieldH256(sfNFTokenID));
});
(*this)->setFieldArray(sfNFTokens, arr);
}
this->update();
}
template class NFTokenPageEntry<ReadView>;
template class NFTokenPageEntry<ApplyView>;
} // namespace xrpl
+77 -88
View File
@@ -7,6 +7,7 @@
#include <xrpl/beast/utility/instrumentation.h> #include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h> #include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h> #include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/entries/NFTokenPageEntry.h>
#include <xrpl/ledger/helpers/AccountRootHelpers.h> #include <xrpl/ledger/helpers/AccountRootHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h> #include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/RippleStateHelpers.h> #include <xrpl/ledger/helpers/RippleStateHelpers.h>
@@ -43,7 +44,7 @@
namespace xrpl::nft { namespace xrpl::nft {
static SLE::const_pointer static NFTokenPageEntryR
locatePage(ReadView const& view, AccountID const& owner, UInt256 const& id) locatePage(ReadView const& view, AccountID const& owner, UInt256 const& id)
{ {
auto const first = keylet::nftokenPage(keylet::nftokenPageMin(owner), id); auto const first = keylet::nftokenPage(keylet::nftokenPageMin(owner), id);
@@ -52,11 +53,11 @@ locatePage(ReadView const& view, AccountID const& owner, UInt256 const& id)
// This NFT can only be found in the first page with a key that's strictly // This NFT can only be found in the first page with a key that's strictly
// greater than `first`, so look for that, up until the maximum possible // greater than `first`, so look for that, up until the maximum possible
// page. // page.
return view.read( return NFTokenPageEntryR(
Keylet(ltNFTOKEN_PAGE, view.succ(first.key, last.key.next()).value_or(last.key))); Keylet(ltNFTOKEN_PAGE, view.succ(first.key, last.key.next()).value_or(last.key)), view);
} }
static SLE::pointer static NFTokenPageEntryW
locatePage(ApplyView& view, AccountID const& owner, UInt256 const& id) locatePage(ApplyView& view, AccountID const& owner, UInt256 const& id)
{ {
auto const first = keylet::nftokenPage(keylet::nftokenPageMin(owner), id); auto const first = keylet::nftokenPage(keylet::nftokenPageMin(owner), id);
@@ -65,11 +66,11 @@ locatePage(ApplyView& view, AccountID const& owner, UInt256 const& id)
// This NFT can only be found in the first page with a key that's strictly // This NFT can only be found in the first page with a key that's strictly
// greater than `first`, so look for that, up until the maximum possible // greater than `first`, so look for that, up until the maximum possible
// page. // page.
return view.peek( return NFTokenPageEntryW(
Keylet(ltNFTOKEN_PAGE, view.succ(first.key, last.key.next()).value_or(last.key))); Keylet(ltNFTOKEN_PAGE, view.succ(first.key, last.key.next()).value_or(last.key)), view);
} }
static SLE::pointer static std::optional<NFTokenPageEntryW>
getPageForToken( getPageForToken(
ApplyView& view, ApplyView& view,
AccountID const& owner, AccountID const& owner,
@@ -83,18 +84,19 @@ getPageForToken(
// This NFT can only be found in the first page with a key that's strictly // This NFT can only be found in the first page with a key that's strictly
// greater than `first`, so look for that, up until the maximum possible // greater than `first`, so look for that, up until the maximum possible
// page. // page.
auto cp = NFTokenPageEntryW cp(
view.peek(Keylet(ltNFTOKEN_PAGE, view.succ(first.key, last.key.next()).value_or(last.key))); Keylet(ltNFTOKEN_PAGE, view.succ(first.key, last.key.next()).value_or(last.key)), view);
// A suitable page doesn't exist; we'll have to create one. // A suitable page doesn't exist; we'll have to create one.
if (!cp) if (!cp)
{ {
STArray const arr; STArray const arr;
cp = std::make_shared<SLE>(last); NFTokenPageEntryW newPage(last, view);
cp->setFieldArray(sfNFTokens, arr); newPage.newSLE();
view.insert(cp); newPage->setFieldArray(sfNFTokens, arr);
newPage.insert();
createCallback(view, owner); createCallback(view, owner);
return cp; return newPage;
} }
STArray narr = cp->getFieldArray(sfNFTokens); STArray narr = cp->getFieldArray(sfNFTokens);
@@ -139,7 +141,7 @@ getPageForToken(
// There should be no circumstance when splitIter == end(), but if it // There should be no circumstance when splitIter == end(), but if it
// were to happen we should bail out because something is confused. // were to happen we should bail out because something is confused.
if (splitIter == narr.end()) if (splitIter == narr.end())
return nullptr; return std::nullopt;
// If splitIter == begin(), then the entire page is filled with // If splitIter == begin(), then the entire page is filled with
// equivalent tokens. This requires special handling. // equivalent tokens. This requires special handling.
@@ -150,7 +152,7 @@ getPageForToken(
{ {
// If the passed in id belongs exactly on this (full) page // If the passed in id belongs exactly on this (full) page
// this account simply cannot store the NFT. // this account simply cannot store the NFT.
return nullptr; return std::nullopt;
} }
if (relation > 0) if (relation > 0)
@@ -184,7 +186,8 @@ getPageForToken(
? narr[kDirMaxTokensPerPage - 1].getFieldH256(sfNFTokenID).next() ? narr[kDirMaxTokensPerPage - 1].getFieldH256(sfNFTokenID).next()
: carr[0].getFieldH256(sfNFTokenID); : carr[0].getFieldH256(sfNFTokenID);
auto np = std::make_shared<SLE>(keylet::nftokenPage(base, tokenIDForNewPage)); NFTokenPageEntryW np(base, tokenIDForNewPage, view);
np.newSLE();
XRPL_ASSERT(np->key() > base.key, "xrpl::nft::getPageForToken : valid NFT page index"); XRPL_ASSERT(np->key() > base.key, "xrpl::nft::getPageForToken : valid NFT page index");
np->setFieldArray(sfNFTokens, narr); np->setFieldArray(sfNFTokens, narr);
np->setFieldH256(sfNextPageMin, cp->key()); np->setFieldH256(sfNextPageMin, cp->key());
@@ -193,22 +196,22 @@ getPageForToken(
{ {
np->setFieldH256(sfPreviousPageMin, *ppm); np->setFieldH256(sfPreviousPageMin, *ppm);
if (auto p3 = view.peek(Keylet(ltNFTOKEN_PAGE, *ppm))) if (NFTokenPageEntryW p3(Keylet(ltNFTOKEN_PAGE, *ppm), view); p3)
{ {
p3->setFieldH256(sfNextPageMin, np->key()); p3->setFieldH256(sfNextPageMin, np->key());
view.update(p3); p3.update();
} }
} }
view.insert(np); np.insert();
cp->setFieldArray(sfNFTokens, carr); cp->setFieldArray(sfNFTokens, carr);
cp->setFieldH256(sfPreviousPageMin, np->key()); cp->setFieldH256(sfPreviousPageMin, np->key());
view.update(cp); cp.update();
createCallback(view, owner); createCallback(view, owner);
return (first.key < np->key()) ? np : cp; return (first.key < np->key()) ? std::move(np) : std::move(cp);
} }
bool bool
@@ -232,7 +235,7 @@ changeTokenURI(
UInt256 const& nftokenID, UInt256 const& nftokenID,
std::optional<xrpl::Slice> const& uri) std::optional<xrpl::Slice> const& uri)
{ {
SLE::pointer const page = locatePage(view, owner, nftokenID); NFTokenPageEntryW page = locatePage(view, owner, nftokenID);
// If the page couldn't be found, the given NFT isn't owned by this account // If the page couldn't be found, the given NFT isn't owned by this account
if (!page) if (!page)
@@ -256,7 +259,7 @@ changeTokenURI(
nftIter->makeFieldAbsent(sfURI); nftIter->makeFieldAbsent(sfURI);
} }
view.update(page); page.update();
return tesSUCCESS; return tesSUCCESS;
} }
@@ -271,7 +274,7 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft)
// First, we need to locate the page the NFT belongs to, creating it // First, we need to locate the page the NFT belongs to, creating it
// if necessary. This operation may fail if it is impossible to insert // if necessary. This operation may fail if it is impossible to insert
// the NFT. // the NFT.
SLE::pointer const page = std::optional<NFTokenPageEntryW> page =
getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) { getPageForToken(view, owner, nft[sfNFTokenID], [](ApplyView& view, AccountID const& owner) {
increaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()}); increaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()});
}); });
@@ -279,24 +282,13 @@ insertToken(ApplyView& view, AccountID owner, STObject&& nft)
if (!page) if (!page)
return tecNO_SUITABLE_NFTOKEN_PAGE; return tecNO_SUITABLE_NFTOKEN_PAGE;
{ page->insertToken(std::move(nft));
auto arr = page->getFieldArray(sfNFTokens);
arr.pushBack(std::move(nft));
arr.sort([](STObject const& o1, STObject const& o2) {
return compareTokens(o1.getFieldH256(sfNFTokenID), o2.getFieldH256(sfNFTokenID));
});
page->setFieldArray(sfNFTokens, arr);
}
view.update(page);
return tesSUCCESS; return tesSUCCESS;
} }
static bool static bool
mergePages(ApplyView& view, SLE::Ref p1, SLE::Ref p2) mergePages(ApplyView& view, NFTokenPageEntryW& p1, NFTokenPageEntryW& p2)
{ {
if (p1->key() >= p2->key()) if (p1->key() >= p2->key())
Throw<std::runtime_error>("mergePages: pages passed in out of order!"); Throw<std::runtime_error>("mergePages: pages passed in out of order!");
@@ -334,19 +326,19 @@ mergePages(ApplyView& view, SLE::Ref p1, SLE::Ref p2)
if (auto const ppm = (*p1)[~sfPreviousPageMin]) if (auto const ppm = (*p1)[~sfPreviousPageMin])
{ {
auto p0 = view.peek(Keylet(ltNFTOKEN_PAGE, *ppm)); NFTokenPageEntryW p0(Keylet(ltNFTOKEN_PAGE, *ppm), view);
if (!p0) if (!p0)
Throw<std::runtime_error>("mergePages: p0 can't be located!"); Throw<std::runtime_error>("mergePages: p0 can't be located!");
p0->setFieldH256(sfNextPageMin, p2->key()); p0->setFieldH256(sfNextPageMin, p2->key());
view.update(p0); p0.update();
p2->setFieldH256(sfPreviousPageMin, *ppm); p2->setFieldH256(sfPreviousPageMin, *ppm);
} }
view.update(p2); p2.update();
view.erase(p1); p1.erase();
return true; return true;
} }
@@ -357,7 +349,7 @@ mergePages(ApplyView& view, SLE::Ref p1, SLE::Ref p2)
TER TER
removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID) removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID)
{ {
SLE::pointer const page = locatePage(view, owner, nftokenID); NFTokenPageEntryW page = locatePage(view, owner, nftokenID);
// If the page couldn't be found, the given NFT isn't owned by this account // If the page couldn't be found, the given NFT isn't owned by this account
if (!page) if (!page)
@@ -370,7 +362,11 @@ removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID)
* Remove the token from the owner's token directory. * Remove the token from the owner's token directory.
*/ */
TER TER
removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID, SLE::Ref curr) removeToken(
ApplyView& view,
AccountID const& owner,
UInt256 const& nftokenID,
NFTokenPageEntryW& curr)
{ {
// We found a page, but the given NFT may not be in it. // We found a page, but the given NFT may not be in it.
auto arr = curr->getFieldArray(sfNFTokens); auto arr = curr->getFieldArray(sfNFTokens);
@@ -386,14 +382,14 @@ removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID, S
} }
// Page management: // Page management:
auto const loadPage = [&view](SLE::Ref page1, SF_UINT256 const& field) { auto const loadPage = [&view](NFTokenPageEntryW const& page1, SF_UINT256 const& field) {
SLE::pointer page2; std::optional<NFTokenPageEntryW> page2;
if (auto const id = (*page1)[~field]) if (auto const id = (*page1)[~field])
{ {
page2 = view.peek(Keylet(ltNFTOKEN_PAGE, *id)); page2.emplace(Keylet(ltNFTOKEN_PAGE, *id), view);
if (!page2) if (!*page2)
{ {
Throw<std::runtime_error>(std::format( Throw<std::runtime_error>(std::format(
"page {} has a broken {} field pointing to {}", "page {} has a broken {} field pointing to {}",
@@ -406,8 +402,8 @@ removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID, S
return page2; return page2;
}; };
auto const prev = loadPage(curr, sfPreviousPageMin); auto prev = loadPage(curr, sfPreviousPageMin);
auto const next = loadPage(curr, sfNextPageMin); auto next = loadPage(curr, sfNextPageMin);
if (!arr.empty()) if (!arr.empty())
{ {
@@ -415,14 +411,14 @@ removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID, S
// pages. Note that this consolidation attempt may actually merge three // pages. Note that this consolidation attempt may actually merge three
// pages into one! // pages into one!
curr->setFieldArray(sfNFTokens, arr); curr->setFieldArray(sfNFTokens, arr);
view.update(curr); curr.update();
std::uint32_t cnt = 0; std::uint32_t cnt = 0;
if (prev && mergePages(view, prev, curr)) if (prev && mergePages(view, *prev, curr))
++cnt; ++cnt;
if (next && mergePages(view, curr, next)) if (next && mergePages(view, curr, *next))
++cnt; ++cnt;
if (cnt != 0) if (cnt != 0)
@@ -446,16 +442,17 @@ removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID, S
((curr->key() & nft::kPageMask) == kPageMask)) ((curr->key() & nft::kPageMask) == kPageMask))
{ {
// Copy all relevant information from prev to curr. // Copy all relevant information from prev to curr.
curr->peekFieldArray(sfNFTokens) = prev->peekFieldArray(sfNFTokens); curr->peekFieldArray(sfNFTokens) = (*prev)->peekFieldArray(sfNFTokens);
if (auto const prevLink = prev->at(~sfPreviousPageMin)) if (auto const prevLink = (*prev)->at(~sfPreviousPageMin))
{ {
curr->at(sfPreviousPageMin) = *prevLink; curr->at(sfPreviousPageMin) = *prevLink;
// Also fix up the NextPageMin link in the new Previous. // Also fix up the NextPageMin link in the new Previous.
auto const newPrev = loadPage(curr, sfPreviousPageMin); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) link set above
auto newPrev = loadPage(curr, sfPreviousPageMin).value();
newPrev->at(sfNextPageMin) = curr->key(); newPrev->at(sfNextPageMin) = curr->key();
view.update(newPrev); newPrev.update();
} }
else else
{ {
@@ -464,8 +461,8 @@ removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID, S
decreaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()}); decreaseOwnerCount(view, owner, {}, 1, beast::Journal{beast::Journal::getNullSink()});
view.update(curr); curr.update();
view.erase(prev); prev->erase();
return tesSUCCESS; return tesSUCCESS;
} }
@@ -473,14 +470,14 @@ removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID, S
// and then remove it. // and then remove it.
if (next) if (next)
{ {
prev->setFieldH256(sfNextPageMin, next->key()); (*prev)->setFieldH256(sfNextPageMin, (*next)->key());
} }
else else
{ {
prev->makeFieldAbsent(sfNextPageMin); (*prev)->makeFieldAbsent(sfNextPageMin);
} }
view.update(prev); prev->update();
} }
if (next) if (next)
@@ -488,17 +485,17 @@ removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID, S
// Make our next page point to our previous page: // Make our next page point to our previous page:
if (prev) if (prev)
{ {
next->setFieldH256(sfPreviousPageMin, prev->key()); (*next)->setFieldH256(sfPreviousPageMin, (*prev)->key());
} }
else else
{ {
next->makeFieldAbsent(sfPreviousPageMin); (*next)->makeFieldAbsent(sfPreviousPageMin);
} }
view.update(next); next->update();
} }
view.erase(curr); curr.erase();
uint32_t cnt = 1; uint32_t cnt = 1;
@@ -510,12 +507,13 @@ removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID, S
// //
// But, in case that analysis is wrong, it's good to leave this code here // But, in case that analysis is wrong, it's good to leave this code here
// just in case. // just in case.
if (prev && next && if (prev && next)
mergePages( {
view, NFTokenPageEntryW p1(Keylet(ltNFTOKEN_PAGE, (*prev)->key()), view);
view.peek(Keylet(ltNFTOKEN_PAGE, prev->key())), NFTokenPageEntryW p2(Keylet(ltNFTOKEN_PAGE, (*next)->key()), view);
view.peek(Keylet(ltNFTOKEN_PAGE, next->key())))) if (mergePages(view, p1, p2))
cnt++; cnt++;
}
decreaseOwnerCount(view, owner, {}, cnt, beast::Journal{beast::Journal::getNullSink()}); decreaseOwnerCount(view, owner, {}, cnt, beast::Journal{beast::Journal::getNullSink()});
@@ -525,39 +523,30 @@ removeToken(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID, S
std::optional<STObject> std::optional<STObject>
findToken(ReadView const& view, AccountID const& owner, UInt256 const& nftokenID) findToken(ReadView const& view, AccountID const& owner, UInt256 const& nftokenID)
{ {
SLE::const_pointer const page = locatePage(view, owner, nftokenID); NFTokenPageEntryR const page = locatePage(view, owner, nftokenID);
// If the page couldn't be found, the given NFT isn't owned by this account // If the page couldn't be found, the given NFT isn't owned by this account
if (!page) if (!page)
return std::nullopt; return std::nullopt;
// We found a candidate page, but the given NFT may not be in it. // We found a candidate page, but the given NFT may not be in it.
for (auto const& t : page->getFieldArray(sfNFTokens)) return page.findToken(nftokenID);
{
if (t[sfNFTokenID] == nftokenID)
return t;
}
return std::nullopt;
} }
std::optional<TokenAndPage> std::optional<TokenAndPage>
findTokenAndPage(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID) findTokenAndPage(ApplyView& view, AccountID const& owner, UInt256 const& nftokenID)
{ {
SLE::pointer page = locatePage(view, owner, nftokenID); NFTokenPageEntryW page = locatePage(view, owner, nftokenID);
// If the page couldn't be found, the given NFT isn't owned by this account // If the page couldn't be found, the given NFT isn't owned by this account
if (!page) if (!page)
return std::nullopt; return std::nullopt;
// We found a candidate page, but the given NFT may not be in it. // We found a candidate page, but the given NFT may not be in it.
for (auto const& t : page->getFieldArray(sfNFTokens)) if (auto token = page.findToken(nftokenID))
{ {
if (t[sfNFTokenID] == nftokenID) // This std::optional constructor is explicit, so it is spelled out.
{ return std::optional<TokenAndPage>(std::in_place, std::move(*token), std::move(page));
// This std::optional constructor is explicit, so it is spelled out.
return std::optional<TokenAndPage>(std::in_place, t, std::move(page));
}
} }
return std::nullopt; return std::nullopt;
} }
@@ -733,7 +722,7 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner)
nextPage->at(sfPreviousPageMin) = *prevLink; nextPage->at(sfPreviousPageMin) = *prevLink;
// Also fix up the NextPageMin link in the new Previous. // Also fix up the NextPageMin link in the new Previous.
auto const newPrev = view.peek(Keylet(ltNFTOKEN_PAGE, *prevLink)); NFTokenPageEntryW newPrev(Keylet(ltNFTOKEN_PAGE, *prevLink), view);
if (!newPrev) if (!newPrev)
{ {
// LCOV_EXCL_START // LCOV_EXCL_START
@@ -743,7 +732,7 @@ repairNFTokenDirectoryLinks(ApplyView& view, AccountID const& owner)
// LCOV_EXCL_STOP // LCOV_EXCL_STOP
} }
newPrev->at(sfNextPageMin) = nextPage->key(); newPrev->at(sfNextPageMin) = nextPage->key();
view.update(newPrev); newPrev.update();
} }
view.erase(page); view.erase(page);
view.insert(nextPage); view.insert(nextPage);
@@ -9,6 +9,7 @@
#include <xrpl/ledger/ApplyView.h> #include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h> #include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h> #include <xrpl/ledger/View.h>
#include <xrpl/ledger/entries/NFTokenPageEntry.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h> #include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h> #include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/NFTokenHelpers.h> #include <xrpl/ledger/helpers/NFTokenHelpers.h>
@@ -264,8 +265,9 @@ AccountDelete::preclaim(PreclaimContext const& ctx)
Keylet const first = keylet::nftokenPageMin(account); Keylet const first = keylet::nftokenPageMin(account);
Keylet const last = keylet::nftokenPageMax(account); Keylet const last = keylet::nftokenPageMax(account);
auto const cp = ctx.view.read( NFTokenPageEntryR const cp(
Keylet(ltNFTOKEN_PAGE, ctx.view.succ(first.key, last.key.next()).value_or(last.key))); Keylet(ltNFTOKEN_PAGE, ctx.view.succ(first.key, last.key.next()).value_or(last.key)),
ctx.view);
if (cp) if (cp)
return tecHAS_OBLIGATIONS; return tecHAS_OBLIGATIONS;
+14 -3
View File
@@ -38,6 +38,7 @@
#include <xrpl/ledger/entries/XChainOwnedClaimIDEntry.h> // IWYU pragma: keep #include <xrpl/ledger/entries/XChainOwnedClaimIDEntry.h> // IWYU pragma: keep
#include <xrpl/ledger/entries/XChainOwnedCreateAccountClaimIDEntry.h> // IWYU pragma: keep #include <xrpl/ledger/entries/XChainOwnedCreateAccountClaimIDEntry.h> // IWYU pragma: keep
#include <xrpl/protocol/Indexes.h> #include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h> #include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h> #include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h> #include <xrpl/protocol/STLedgerEntry.h>
@@ -76,12 +77,22 @@ template class SLEBase<ApplyView>;
#pragma push_macro("LEDGER_ENTRY") #pragma push_macro("LEDGER_ENTRY")
#undef LEDGER_ENTRY #undef LEDGER_ENTRY
// No explicit instantiation here, neither a definition nor a declaration.
// A definition would instantiate an entry a second time once its member
// bodies live in its own .cpp (MSVC rejects that with C4661 under /WX), and a
// declaration (`extern template`) stops clang from emitting the inline
// constructors of the header-only entries at -O0, which leaves them undefined
// at link time in the debug builds. Naming the class in a static_assert is
// enough to instantiate its definition, which is all this exhaustiveness
// check needs; the per-type suites exercise the members.
#define LEDGER_ENTRY(tag, value, name, ...) \ #define LEDGER_ENTRY(tag, value, name, ...) \
template class name##Entry<ReadView>; \
template class name##Entry<ApplyView>; \
static_assert( \ static_assert( \
name##Entry<ReadView>::kEntryType == tag && name##Entry<ApplyView>::kEntryType == tag, \ name##Entry<ReadView>::kEntryType == tag && name##Entry<ApplyView>::kEntryType == tag, \
#name "Entry must be bound to " #tag); #name "Entry must be bound to " #tag); \
static_assert( \
std::is_constructible_v<name##Entry<ReadView>, Keylet const&, ReadView const&> && \
std::is_constructible_v<name##Entry<ApplyView>, Keylet const&, ApplyView&>, \
#name "Entry must be constructible from a Keylet and a view");
#include <xrpl/protocol/detail/ledger_entries.macro> #include <xrpl/protocol/detail/ledger_entries.macro>