Compare commits

...

30 Commits

Author SHA1 Message Date
Ed Hennis
6270c9c850 Merge remote-tracking branch 'XRPLF/develop' into ximinez/emptydirectoryinvariant
* XRPLF/develop: (30 commits)
  chore: Pin Python packages for codegen using uv (7329)
  style: Use shfmt instead of bashate (7326)
  fix: Fix edge-case where vault-depositor may get stuck (7139)
  fix: Fix `VaultInvariant` and `VaultDeposit` precision bugs at IOU scale boundaries (7272)
  ci: Add clang to nix images (7308)
  fix: Include management-fee delta in doOverpayment assertion (7039)
  fix: Fix clang-tidy pre-commit hook to locate compile_commands.json from repo root (7325)
  fix: Use consistent scale for `debtTotal` (7093)
  fix: Skip deleted book directories and non-root modifications in `ValidBookDirectory` invariant (7312)
  fix: Address review feedback on FD/handle guarding (5823 follow-up) (7310)
  fix: Fix non-canonical MPT amount (7117)
  release: Bump version to 3.2.0-b7 (7316)
  fix: Check if the MPT first loss cover can be sent to the broker before deleting the broker (7125)
  fix: Fix RPM prerelease ordering and start xrpld on DEB install (7313)
  ci: Re-enable full nproc for Linux (7315)
  fix: Add assorted MPT/DEX fixes (7040)
  refactor: Remove dead `fetchBatch` code (7309)
  release: Bump version to 3.2.0-b6 (7311)
  chore: Revert graceful peer disconnection and follow-up fix (7296)
  fix: Fix IOU precision issues in LoanBrokerCover transactions (7274)
  ...
2026-05-26 16:55:33 -04:00
Ed Hennis
90ea49b9f9 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-05-19 16:54:04 -04:00
Ed Hennis
ba35a9fb20 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-05-19 10:15:45 -04:00
Ed Hennis
0ef677f007 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-05-19 09:50:43 -04:00
Ed Hennis
a8f75ce155 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-05-14 20:39:33 -04:00
Ed Hennis
1cb6fd15b1 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-05-14 10:48:49 -04:00
Ed Hennis
ef59b14fb7 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-05-13 12:04:09 -04:00
Ed Hennis
4d2dc5fa54 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-05-12 20:11:38 -04:00
Ed Hennis
5b81a40bf1 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-05-07 18:10:30 -04:00
Ed Hennis
713dcaacb0 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-05-07 14:19:38 -04:00
Ed Hennis
b6b367e290 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-05-07 13:28:59 -04:00
Ed Hennis
50501c89d1 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-05-06 22:34:50 -04:00
Ed Hennis
dacc664f90 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-05-06 14:18:29 -04:00
Ed Hennis
410322362d Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-05-05 19:08:36 -04:00
Ed Hennis
588aa5c420 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-05-01 14:01:09 -04:00
Ed Hennis
f4a37fb3a4 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-04-28 16:31:44 -04:00
Ed Hennis
5faacf6006 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-04-25 14:46:10 -04:00
Ed Hennis
cabee3faac Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-04-23 15:56:30 -04:00
Ed Hennis
7ed2258782 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-04-22 23:52:49 -04:00
Ed Hennis
f97b4d01fb Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-04-22 14:49:30 -04:00
Ed Hennis
e657df5fe1 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-04-22 13:11:01 -04:00
Ed Hennis
ca190b5aaa Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-04-21 19:35:04 -04:00
Ed Hennis
503014f03f Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-04-20 17:50:03 -04:00
Ed Hennis
29f5829680 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-04-20 15:45:21 -04:00
Ed Hennis
2b1f7f9d55 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-04-20 11:39:26 -04:00
Ed Hennis
c776515cee Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-04-17 18:21:03 -04:00
Ed Hennis
5d9b00dba4 Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-04-16 13:44:53 -04:00
Ed Hennis
a81d37465e Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-04-15 19:09:37 -04:00
Ed Hennis
e341af4aee Merge branch 'develop' into ximinez/emptydirectoryinvariant 2026-04-15 14:29:12 -04:00
Ed Hennis
8122ed62b6 Experiment: Add invariant to enforce directory node population
- Experiment: Always delete the root
2026-04-13 19:58:50 -04:00
5 changed files with 90 additions and 1 deletions

View File

@@ -20,6 +20,10 @@ removeTokenOffersWithLimit(
Keylet const& directory,
std::size_t maxDeletableOffers);
/** Returns tesSUCCESS if NFToken has few enough offers that it can be burned */
TER
notTooManyOffers(ReadView const& view, uint256 const& nftokenID);
/** Finds the specified token in the owner's token directory. */
std::optional<STObject>
findToken(ReadView const& view, AccountID const& owner, uint256 const& nftokenID);

View File

@@ -373,6 +373,22 @@ public:
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/**
* @brief Invariants: An account's directory should never be empty
*
*/
class NoEmptyDirectory
{
bool bad_ = false;
public:
void
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
bool
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
};
/** Verify that MPT/XRP STAmounts are canonical in any ledger entries left after the
* transaction applies.
*/
@@ -418,7 +434,8 @@ using InvariantChecks = std::tuple<
ValidVault,
ValidMPTPayment,
ValidAmounts,
ValidMPTTransfer>;
ValidMPTTransfer,
NoEmptyDirectory>;
/**
* @brief get a tuple of all invariant checks

View File

@@ -255,6 +255,7 @@ ApplyView::emptyDirDelete(Keylet const& directory)
bool
ApplyView::dirRemove(Keylet const& directory, std::uint64_t page, uint256 const& key, bool keepRoot)
{
keepRoot = false;
auto node = peek(keylet::page(directory, page));
if (!node)

View File

@@ -622,6 +622,33 @@ removeTokenOffersWithLimit(ApplyView& view, Keylet const& directory, std::size_t
return deletedOffersCount;
}
TER
notTooManyOffers(ReadView const& view, uint256 const& nftokenID)
{
std::size_t totalOffers = 0;
{
Dir const buys(view, keylet::nftBuys(nftokenID));
for (auto iter = buys.begin(); iter != buys.end(); iter.next_page())
{
totalOffers += iter.page_size();
if (totalOffers > kMaxDeletableTokenOfferEntries)
return tefTOO_BIG;
}
}
{
Dir const sells(view, keylet::nftSells(nftokenID));
for (auto iter = sells.begin(); iter != sells.end(); iter.next_page())
{
totalOffers += iter.page_size();
if (totalOffers > kMaxDeletableTokenOfferEntries)
return tefTOO_BIG;
}
}
return tesSUCCESS;
}
bool
deleteTokenOffer(ApplyView& view, std::shared_ptr<SLE> const& offer)
{

View File

@@ -1080,6 +1080,46 @@ NoModifiedUnmodifiableFields::finalize(
return true;
}
//------------------------------------------------------------------------------
void
NoEmptyDirectory::visitEntry(
bool isDelete,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
{
if (isDelete)
return;
if (before && before->getType() != ltDIR_NODE)
return;
if (after && after->getType() != ltDIR_NODE)
return;
if (!after->isFieldPresent(sfOwner))
// Not an account dir
return;
bad_ = after->at(sfIndexes).empty();
}
bool
NoEmptyDirectory::finalize(
STTx const& tx,
TER const result,
XRPAmount const,
ReadView const& view,
beast::Journal const& j)
{
if (bad_)
{
JLOG(j.fatal()) << "Invariant failed: empty owner directory.";
return false;
}
return true;
}
//------------------------------------------------------------------------------
void
ValidAmounts::visitEntry(
bool isDelete,