xrpld
Loading...
Searching...
No Matches
AccountDelete.cpp
1#include <xrpl/tx/transactors/account/AccountDelete.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/base_uint.h>
5#include <xrpl/basics/safe_cast.h>
6#include <xrpl/beast/utility/Zero.h>
7#include <xrpl/beast/utility/instrumentation.h>
8#include <xrpl/core/ServiceRegistry.h>
9#include <xrpl/ledger/ApplyView.h>
10#include <xrpl/ledger/ReadView.h>
11#include <xrpl/ledger/View.h>
12#include <xrpl/ledger/helpers/CredentialHelpers.h>
13#include <xrpl/ledger/helpers/DirectoryHelpers.h>
14#include <xrpl/ledger/helpers/NFTokenHelpers.h>
15#include <xrpl/ledger/helpers/OfferHelpers.h>
16#include <xrpl/protocol/AccountID.h>
17#include <xrpl/protocol/Feature.h>
18#include <xrpl/protocol/Indexes.h>
19#include <xrpl/protocol/Keylet.h>
20#include <xrpl/protocol/LedgerFormats.h>
21#include <xrpl/protocol/Protocol.h>
22#include <xrpl/protocol/SField.h>
23#include <xrpl/protocol/STLedgerEntry.h>
24#include <xrpl/protocol/STTx.h>
25#include <xrpl/protocol/TER.h>
26#include <xrpl/protocol/XRPAmount.h>
27#include <xrpl/tx/Transactor.h>
28#include <xrpl/tx/transactors/account/SignerListSet.h>
29#include <xrpl/tx/transactors/delegate/DelegateSet.h>
30#include <xrpl/tx/transactors/did/DIDDelete.h>
31#include <xrpl/tx/transactors/oracle/OracleDelete.h>
32#include <xrpl/tx/transactors/payment/DepositPreauth.h>
33
34#include <cstdint>
35#include <utility>
36
37namespace xrpl {
38bool
40{
41 return !ctx.tx.isFieldPresent(sfCredentialIDs) || ctx.rules.enabled(featureCredentials);
42}
43
46{
47 if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
48 {
49 // An account cannot be deleted and give itself the resulting XRP.
50 return temDST_IS_SRC;
51 }
52
53 if (auto const err = credentials::checkFields(ctx.tx, ctx.rules, ctx.j); !isTesSuccess(err))
54 return err;
55
56 return tesSUCCESS;
57}
58
61{
62 // The fee required for AccountDelete is one owner reserve.
64}
65
66namespace {
67// Define a function pointer type that can be used to delete ledger node types.
68using DeleterFuncPtr = TER (*)(
69 ServiceRegistry& registry,
70 ApplyView& view,
71 AccountID const& account,
72 uint256 const& delIndex,
73 SLE::ref sleDel,
75
76// Local function definitions that provides signature compatibility.
77TER
80 ApplyView& view,
81 AccountID const& account,
82 uint256 const& delIndex,
83 SLE::ref sleDel,
85{
86 return offerDelete(view, sleDel, j);
87}
88
89TER
91 ServiceRegistry& registry,
92 ApplyView& view,
93 AccountID const& account,
94 uint256 const& delIndex,
95 SLE::ref sleDel,
97{
98 return SignerListSet::removeFromLedger(registry, view, account, j);
99}
100
101TER
102removeTicketFromLedger(
104 ApplyView& view,
105 AccountID const& account,
106 uint256 const& delIndex,
107 SLE::ref,
108 beast::Journal j)
109{
110 return Transactor::ticketDelete(view, account, delIndex, j);
111}
112
113TER
114removeDepositPreauthFromLedger(
116 ApplyView& view,
117 AccountID const&,
118 uint256 const& delIndex,
119 SLE::ref,
120 beast::Journal j)
121{
122 return DepositPreauth::removeFromLedger(view, delIndex, j);
123}
124
125TER
126removeNFTokenOfferFromLedger(
128 ApplyView& view,
129 AccountID const& account,
130 uint256 const& delIndex,
131 SLE::ref sleDel,
132 beast::Journal)
133{
134 if (!nft::deleteTokenOffer(view, sleDel))
135 return tefBAD_LEDGER; // LCOV_EXCL_LINE
136
137 return tesSUCCESS;
138}
139
140TER
141removeDIDFromLedger(
143 ApplyView& view,
144 AccountID const& account,
145 uint256 const& delIndex,
146 SLE::ref sleDel,
147 beast::Journal j)
148{
149 return DIDDelete::deleteSLE(view, sleDel, account, j);
150}
151
152TER
153removeOracleFromLedger(
155 ApplyView& view,
156 AccountID const& account,
157 uint256 const&,
158 SLE::ref sleDel,
159 beast::Journal j)
160{
161 return OracleDelete::deleteOracle(view, sleDel, account, j);
162}
163
164TER
165removeCredentialFromLedger(
167 ApplyView& view,
168 AccountID const&,
169 uint256 const&,
170 SLE::ref sleDel,
171 beast::Journal j)
172{
173 return credentials::deleteSLE(view, sleDel, j);
174}
175
176TER
177removeDelegateFromLedger(
179 ApplyView& view,
180 AccountID const&,
181 uint256 const&,
182 SLE::ref sleDel,
183 beast::Journal j)
184{
185 return DelegateSet::deleteDelegate(view, sleDel, j);
186}
187
188// Return nullptr if the LedgerEntryType represents an obligation that can't
189// be deleted. Otherwise return the pointer to the function that can delete
190// the non-obligation
191DeleterFuncPtr
192nonObligationDeleter(LedgerEntryType t)
193{
194 switch (t)
195 {
196 case ltOFFER:
197 return offerDelete;
198 case ltSIGNER_LIST:
200 case ltTICKET:
201 return removeTicketFromLedger;
202 case ltDEPOSIT_PREAUTH:
203 return removeDepositPreauthFromLedger;
204 case ltNFTOKEN_OFFER:
205 return removeNFTokenOfferFromLedger;
206 case ltDID:
207 return removeDIDFromLedger;
208 case ltORACLE:
209 return removeOracleFromLedger;
210 case ltCREDENTIAL:
211 return removeCredentialFromLedger;
212 case ltDELEGATE:
213 return removeDelegateFromLedger;
214 default:
215 return nullptr;
216 }
217}
218
219} // namespace
220
221TER
223{
224 AccountID const account{ctx.tx[sfAccount]};
225 AccountID const dst{ctx.tx[sfDestination]};
226
227 auto sleDst = ctx.view.read(keylet::account(dst));
228
229 if (!sleDst)
230 return tecNO_DST;
231
232 if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx[~sfDestinationTag])
233 return tecDST_TAG_NEEDED;
234
235 // If credentials are provided - check them anyway
236 if (auto const err = credentials::valid(ctx.tx, ctx.view, account, ctx.j); !isTesSuccess(err))
237 return err;
238
239 // if credentials then postpone auth check to doApply, to check for expired
240 // credentials
241 if (!ctx.tx.isFieldPresent(sfCredentialIDs))
242 {
243 // Check whether the destination account requires deposit authorization.
244 // This also checks if destination is a pseudo-account, since pseudo-accounts have the
245 // lsfDepositAuth flag set by default
246 if (sleDst->isFlag(lsfDepositAuth))
247 {
248 if (!ctx.view.exists(keylet::depositPreauth(dst, account)))
249 return tecNO_PERMISSION;
250 }
251 }
252
253 auto sleAccount = ctx.view.read(keylet::account(account));
254 XRPL_ASSERT(sleAccount, "xrpl::AccountDelete::preclaim : non-null account");
255 if (!sleAccount)
256 return terNO_ACCOUNT;
257
258 // If an issuer has any issued NFTs resident in the ledger then it
259 // cannot be deleted.
260 if ((*sleAccount)[~sfMintedNFTokens] != (*sleAccount)[~sfBurnedNFTokens])
261 return tecHAS_OBLIGATIONS;
262
263 // If the account owns any NFTs it cannot be deleted.
264 Keylet const first = keylet::nftokenPageMin(account);
265 Keylet const last = keylet::nftokenPageMax(account);
266
267 auto const cp = ctx.view.read(
268 Keylet(ltNFTOKEN_PAGE, ctx.view.succ(first.key, last.key.next()).value_or(last.key)));
269 if (cp)
270 return tecHAS_OBLIGATIONS;
271
272 if (sleAccount->isFieldPresent(sfSponsor))
273 {
274 if (dst != sleAccount->getAccountID(sfSponsor))
276 }
277 if (sleAccount->isFieldPresent(sfSponsoringOwnerCount) ||
278 sleAccount->isFieldPresent(sfSponsoringAccountCount))
279 return tecHAS_OBLIGATIONS;
280
281 // We don't allow an account to be deleted if its sequence number
282 // is within 256 of the current ledger. This prevents replay of old
283 // transactions if this account is resurrected after it is deleted.
284 //
285 // We look at the account's Sequence rather than the transaction's
286 // Sequence in preparation for Tickets.
287 static constexpr std::uint32_t kSeqDelta{255};
288 if ((*sleAccount)[sfSequence] + kSeqDelta > ctx.view.seq())
289 return tecTOO_SOON;
290
291 // We don't allow an account to be deleted if
292 // <FirstNFTokenSequence + MintedNFTokens> is within 256 of the
293 // current ledger. This is to prevent having duplicate NFTokenIDs after
294 // account re-creation.
295 //
296 // Without this restriction, duplicate NFTokenIDs can be reproduced when
297 // authorized minting is involved. Because when the minter mints a NFToken,
298 // the issuer's sequence does not change. So when the issuer re-creates
299 // their account and mints a NFToken, it is possible that the
300 // NFTokenSequence of this NFToken is the same as the one that the
301 // authorized minter minted in a previous ledger.
302 if ((*sleAccount)[~sfFirstNFTokenSequence].value_or(0) +
303 (*sleAccount)[~sfMintedNFTokens].value_or(0) + kSeqDelta >
304 ctx.view.seq())
305 return tecTOO_SOON;
306
307 // Verify that the account does not own any objects that would prevent
308 // the account from being deleted.
309 Keylet const ownerDirKeylet{keylet::ownerDir(account)};
310 if (dirIsEmpty(ctx.view, ownerDirKeylet))
311 return tesSUCCESS;
312
313 SLE::const_pointer sleDirNode{};
314 unsigned int uDirEntry{0};
315 uint256 dirEntry{beast::kZero};
316
317 // Account has no directory at all. This _should_ have been caught
318 // by the dirIsEmpty() check earlier, but it's okay to catch it here.
319 if (!cdirFirst(ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
320 return tesSUCCESS;
321
322 std::uint32_t deletableDirEntryCount{0};
323 do
324 {
325 // Make sure any directory node types that we find are the kind
326 // we can delete.
327 auto sleItem = ctx.view.read(keylet::child(dirEntry));
328 if (!sleItem)
329 {
330 // Directory node has an invalid index. Bail out.
331 // LCOV_EXCL_START
332 JLOG(ctx.j.fatal()) << "AccountDelete: directory node in ledger " << ctx.view.seq()
333 << " has index to object that is missing: " << to_string(dirEntry);
334 return tefBAD_LEDGER;
335 // LCOV_EXCL_STOP
336 }
337
338 LedgerEntryType const nodeType{safeCast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
339
340 if (nonObligationDeleter(nodeType) == nullptr)
341 return tecHAS_OBLIGATIONS;
342
343 // We found a deletable directory entry. Count it. If we find too
344 // many deletable directory entries then bail out.
345 if (++deletableDirEntryCount > kMaxDeletableDirEntries)
346 return tefTOO_BIG;
347
348 } while (cdirNext(ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
349
350 return tesSUCCESS;
351}
352
353TER
355{
356 auto src = view().peek(keylet::account(accountID_));
357 XRPL_ASSERT(src, "xrpl::AccountDelete::doApply : non-null source account");
358
359 auto const dstID = ctx_.tx[sfDestination];
360 auto dst = view().peek(keylet::account(dstID));
361 XRPL_ASSERT(dst, "xrpl::AccountDelete::doApply : non-null destination account");
362
363 if (!src || !dst)
364 return tefBAD_LEDGER; // LCOV_EXCL_LINE
365
366 if (ctx_.tx.isFieldPresent(sfCredentialIDs))
367 {
368 if (auto err =
369 verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dstID, dst, ctx_.journal);
370 !isTesSuccess(err))
371 return err;
372 }
373
374 Keylet const ownerDirKeylet{keylet::ownerDir(accountID_)};
375 auto const ter = cleanupOnAccountDelete(
376 view(),
377 ownerDirKeylet,
378 [&](LedgerEntryType nodeType,
379 uint256 const& dirEntry,
381 if (auto deleter = nonObligationDeleter(nodeType))
382 {
383 TER const result{deleter(ctx_.registry, view(), accountID_, dirEntry, sleItem, j_)};
384
385 return {result, SkipEntry::No};
386 }
387
388 // LCOV_EXCL_START
389 UNREACHABLE(
390 "xrpl::AccountDelete::doApply : undeletable item not found "
391 "in preclaim");
392 JLOG(j_.error()) << "AccountDelete undeletable item not "
393 "found in preclaim.";
395 // LCOV_EXCL_STOP
396 },
397 ctx_.journal);
398 if (!isTesSuccess(ter))
399 return ter;
400
401 // Transfer any XRP remaining after the fee is paid to the destination:
402 auto const remainingBalance = src->getFieldAmount(sfBalance).xrp();
403 (*dst)[sfBalance] = (*dst)[sfBalance] + remainingBalance;
404 (*src)[sfBalance] = (*src)[sfBalance] - remainingBalance;
405 ctx_.deliver(remainingBalance);
406
407 if (src->isFieldPresent(sfSponsor))
408 {
409 auto const sponsorID = src->getAccountID(sfSponsor);
410 auto sponsorSle = view().peek(keylet::account(sponsorID));
411
412 if (!sponsorSle)
413 return tefINTERNAL; // LCOV_EXCL_LINE
414
415 auto const sponsoringAccountCount = sponsorSle->getFieldU32(sfSponsoringAccountCount);
416
417 XRPL_ASSERT(
418 sponsoringAccountCount != 0,
419 "xrpl::AccountDelete::doApply : sponsoring account count is present");
420 if (sponsoringAccountCount == 0)
421 {
422 // sanity check
423 // Since sfSponsoringAccountCount is set to soeDEFAULT, the field will not be
424 // present with a value of 0.
425 return tefINTERNAL; // LCOV_EXCL_LINE
426 }
427 sponsorSle->at(sfSponsoringAccountCount) = sponsoringAccountCount - 1;
428 view().update(sponsorSle);
429
430 // Following line might look redundant, but without it, sfSponsor
431 // would end up remaining in after-ltAccountRoot during the
432 // InvariantCheck.
433 src->makeFieldAbsent(sfSponsor);
434 }
435
436 XRPL_ASSERT(
437 (*src)[sfBalance] == XRPAmount(0), "xrpl::AccountDelete::doApply : source balance is zero");
438
439 // If there's still an owner directory associated with the source account
440 // delete it.
441 if (view().exists(ownerDirKeylet) && !view().emptyDirDelete(ownerDirKeylet))
442 {
443 JLOG(j_.error()) << "AccountDelete cannot delete root dir node of " << toBase58(accountID_);
444 return tecHAS_OBLIGATIONS;
445 }
446
447 // Re-arm the password change fee if we can and need to.
448 if (remainingBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
449 dst->clearFlag(lsfPasswordSpent);
450
451 view().update(dst);
452 view().erase(src);
453
454 return tesSUCCESS;
455}
456
457void
459{
460 // No transaction-specific invariants yet (future work).
461}
462
463bool
465 STTx const&,
466 TER,
467 XRPAmount,
468 ReadView const&,
469 beast::Journal const&)
470{
471 // No transaction-specific invariants yet (future work).
472 return true;
473}
474
475} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Stream fatal() const
Definition Journal.h:368
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override
Inspect a single ledger entry modified by this transaction.
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
TER doApply() override
static NotTEC preflight(PreflightContext const &ctx)
static bool checkExtraFeatures(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:134
virtual SLE::pointer peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void erase(SLE::ref sle)=0
Remove a peeked SLE.
virtual void update(SLE::ref sle)=0
Indicate changes to a peeked SLE.
BaseUInt next() const
Definition base_uint.h:477
static TER deleteSLE(ApplyContext &ctx, Keylet sleKeylet, AccountID const owner)
Definition DIDDelete.cpp:28
static TER deleteDelegate(ApplyView &view, SLE::ref sle, beast::Journal j)
static TER removeFromLedger(ApplyView &view, uint256 const &delIndex, beast::Journal j)
static TER deleteOracle(ApplyView &view, SLE::ref sle, AccountID const &account, beast::Journal j)
A view into a ledger.
Definition ReadView.h:41
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition ReadView.h:115
virtual std::optional< key_type > succ(key_type const &key, std::optional< key_type > const &last=std::nullopt) const =0
Return the key of the next state item.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:180
std::shared_ptr< STLedgerEntry > const & ref
std::shared_ptr< STLedgerEntry > pointer
std::shared_ptr< STLedgerEntry const > const & const_ref
std::shared_ptr< STLedgerEntry const > const_pointer
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:464
Service registry for dependency injection.
static TER removeFromLedger(ServiceRegistry &registry, ApplyView &view, AccountID const &account, beast::Journal j)
static XRPAmount calculateOwnerReserveFee(ReadView const &view, STTx const &tx)
beast::Journal const j_
Definition Transactor.h:155
ApplyView & view()
Definition Transactor.h:175
AccountID const accountID_
Definition Transactor.h:157
ApplyContext & ctx_
Definition Transactor.h:153
static TER ticketDelete(ApplyView &view, AccountID const &account, uint256 const &ticketIndex, beast::Journal j)
constexpr Zero kZero
Definition Zero.h:30
TER deleteSLE(ApplyView &view, SLE::ref sleCredential, beast::Journal j)
NotTEC checkFields(STTx const &tx, Rules const &rules, beast::Journal j)
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition Indexes.cpp:344
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:373
Keylet nftokenPageMin(AccountID const &owner)
NFT page keylets.
Definition Indexes.cpp:400
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition Indexes.cpp:204
Keylet nftokenPageMax(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:408
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
bool deleteTokenOffer(ApplyView &view, SLE::ref offer)
Deletes the given token offer.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ terNO_ACCOUNT
Definition TER.h:213
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
@ tefTOO_BIG
Definition TER.h:176
@ tefBAD_LEDGER
Definition TER.h:162
@ tefINTERNAL
Definition TER.h:165
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
constexpr Dest safeCast(Src s) noexcept
Definition safe_cast.h:21
TER offerDelete(ApplyView &view, SLE::ref sle, beast::Journal j)
Delete an offer.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:607
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
bool cdirNext(ReadView const &view, uint256 const &root, SLE::const_pointer &page, unsigned int &index, uint256 &entry)
Returns the next entry in the directory, advancing the index.
@ temDST_IS_SRC
Definition TER.h:96
bool isTesSuccess(TER x) noexcept
Definition TER.h:676
TERSubset< CanCvtToTER > TER
Definition TER.h:647
LedgerEntryType
Identifiers for on-ledger objects.
@ tecNO_SPONSOR_PERMISSION
Definition TER.h:372
@ tecTOO_SOON
Definition TER.h:321
@ tecNO_PERMISSION
Definition TER.h:308
@ tecDST_TAG_NEEDED
Definition TER.h:312
@ tecHAS_OBLIGATIONS
Definition TER.h:320
@ tecNO_DST
Definition TER.h:293
bool cdirFirst(ReadView const &view, uint256 const &root, SLE::const_pointer &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
BaseUInt< 256 > uint256
Definition base_uint.h:580
static TER removeSignersFromLedger(ServiceRegistry &registry, ApplyView &view, Keylet const &accountKeylet, Keylet const &ownerDirKeylet, Keylet const &signerListKeylet, beast::Journal j)
@ tesSUCCESS
Definition TER.h:245
constexpr std::size_t kMaxDeletableDirEntries
The maximum number of owner directory entries for account to be deletable.
Definition Protocol.h:74
TER cleanupOnAccountDelete(ApplyView &view, Keylet const &ownerDirKeylet, EntryDeleter const &deleter, beast::Journal j, std::optional< std::uint16_t > maxNodesToDelete=std::nullopt)
Cleanup owner directory entries on account delete.
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, SLE::const_ref sleDst, beast::Journal j)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:83
ReadView const & view
Definition Transactor.h:86
beast::Journal const j
Definition Transactor.h:91
State information when preflighting a tx.
Definition Transactor.h:38
beast::Journal const j
Definition Transactor.h:45