fix: Reject credentials on pseudo-accounts and unpin Vault/LoanBroker (FN-36)

CredentialCreate accepted any existing Subject, including Vault, LoanBroker, and
AMM pseudo-accounts. A pseudo-account can't sign, so it can never accept or
delete a credential issued to it; the unaccepted credential stays pinned in the
pseudo-account's owner directory and blocks deletion of the owning object,
locking it and its owner reserve indefinitely.

Behind fixCleanup3_4_0:
- CredentialCreate::preclaim rejects a pseudo-account Subject with
  tecPSEUDO_ACCOUNT.
- VaultDelete and LoanBrokerDelete remove any credentials pinned to the
  pseudo-account before deleting it.
- isOnlyLiquidityProvider ignores credential entries and deleteAMMAccount removes
  them, so an AMM pinned before the amendment can still be withdrawn and deleted.

This clears objects pinned before the amendment and prevents new pins after it.
This commit is contained in:
Timur Ialymov
2026-07-29 13:00:38 +01:00
parent ecdd457f35
commit fa6a96e6d8
12 changed files with 367 additions and 1 deletions

View File

@@ -13,6 +13,7 @@
#include <xrpl/protocol/STVector256.h>
#include <xrpl/protocol/TER.h>
#include <cstdint>
#include <memory>
#include <set>
#include <utility>
@@ -32,6 +33,29 @@ checkExpired(SLE const& sleCredential, NetClock::time_point const& closed);
[[nodiscard]] TER
deleteSLE(ApplyView& view, SLE::ref sleCredential, beast::Journal j);
/**
* @brief Remove credentials pinned to a pseudo-account's owner directory.
*
* Cleans up credentials that were linked to a pseudo-account (Vault, LoanBroker,
* AMM) before fixCleanup3_4_0, which such an account can neither accept nor
* delete. Only credentials are removed, at most @p maxNodesToDelete of them; on
* reaching that bound the result is `tecINCOMPLETE` and the caller must
* propagate it so a later transaction resumes.
*
* @param view Mutable ledger view.
* @param pseudoAcct The pseudo-account whose directory is cleaned.
* @param maxNodesToDelete Upper bound on directory entries processed in one call.
* @param j Journal for diagnostics.
* @return tesSUCCESS once no credentials remain, tecINCOMPLETE if the bound was
* reached, or a deletion error.
*/
[[nodiscard]] TER
deletePseudoAccountCredentials(
ApplyView& view,
AccountID const& pseudoAcct,
std::uint16_t maxNodesToDelete,
beast::Journal j);
// Amendment and parameters checks for sfCredentialIDs field
NotTEC
checkFields(STTx const& tx, beast::Journal j);

View File

@@ -365,6 +365,12 @@ using TxID = uint256;
*/
constexpr std::uint16_t kMaxDeletableAmmTrustLines = 512;
/**
* The maximum number of credentials to delete from a pseudo-account's owner
* directory in a single transaction.
*/
constexpr std::uint16_t kMaxDeletablePseudoAccountCredentials = 512;
/**
* The maximum length of a URI inside an Oracle
*/