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 {
38
39bool
41{
42 return !ctx.tx.isFieldPresent(sfCredentialIDs) || ctx.rules.enabled(featureCredentials);
43}
44
47{
48 if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
49 {
50 // An account cannot be deleted and give itself the resulting XRP.
51 return temDST_IS_SRC;
52 }
53
54 if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err))
55 return err;
56
57 return tesSUCCESS;
58}
59
62{
63 // The fee required for AccountDelete is one owner reserve.
65}
66
67namespace {
68// Define a function pointer type that can be used to delete ledger node types.
69using DeleterFuncPtr = TER (*)(
70 ServiceRegistry& registry,
71 ApplyView& view,
72 AccountID const& account,
73 uint256 const& delIndex,
74 SLE::ref sleDel,
76
77// Local function definitions that provides signature compatibility.
78TER
81 ApplyView& view,
82 AccountID const& account,
83 uint256 const& delIndex,
84 SLE::ref sleDel,
86{
87 return offerDelete(view, sleDel, j);
88}
89
90TER
92 ServiceRegistry& registry,
93 ApplyView& view,
94 AccountID const& account,
95 uint256 const& delIndex,
96 SLE::ref sleDel,
98{
99 return SignerListSet::removeFromLedger(registry, view, account, j);
100}
101
102TER
103removeTicketFromLedger(
105 ApplyView& view,
106 AccountID const& account,
107 uint256 const& delIndex,
108 SLE::ref,
109 beast::Journal j)
110{
111 return Transactor::ticketDelete(view, account, delIndex, j);
112}
113
114TER
115removeDepositPreauthFromLedger(
117 ApplyView& view,
118 AccountID const&,
119 uint256 const& delIndex,
120 SLE::ref,
121 beast::Journal j)
122{
123 return DepositPreauth::removeFromLedger(view, delIndex, j);
124}
125
126TER
127removeNFTokenOfferFromLedger(
129 ApplyView& view,
130 AccountID const& account,
131 uint256 const& delIndex,
132 SLE::ref sleDel,
133 beast::Journal)
134{
135 if (!nft::deleteTokenOffer(view, sleDel))
136 return tefBAD_LEDGER; // LCOV_EXCL_LINE
137
138 return tesSUCCESS;
139}
140
141TER
142removeDIDFromLedger(
144 ApplyView& view,
145 AccountID const& account,
146 uint256 const& delIndex,
147 SLE::ref sleDel,
148 beast::Journal j)
149{
150 return DIDDelete::deleteSLE(view, sleDel, account, j);
151}
152
153TER
154removeOracleFromLedger(
156 ApplyView& view,
157 AccountID const& account,
158 uint256 const&,
159 SLE::ref sleDel,
160 beast::Journal j)
161{
162 return OracleDelete::deleteOracle(view, sleDel, account, j);
163}
164
165TER
166removeCredentialFromLedger(
168 ApplyView& view,
169 AccountID const&,
170 uint256 const&,
171 SLE::ref sleDel,
172 beast::Journal j)
173{
174 return credentials::deleteSLE(view, sleDel, j);
175}
176
177TER
178removeDelegateFromLedger(
180 ApplyView& view,
181 AccountID const&,
182 uint256 const&,
183 SLE::ref sleDel,
184 beast::Journal j)
185{
186 return DelegateSet::deleteDelegate(view, sleDel, j);
187}
188
189// Return nullptr if the LedgerEntryType represents an obligation that can't
190// be deleted. Otherwise return the pointer to the function that can delete
191// the non-obligation
192DeleterFuncPtr
193nonObligationDeleter(LedgerEntryType t)
194{
195 switch (t)
196 {
197 case ltOFFER:
198 return offerDelete;
199 case ltSIGNER_LIST:
201 case ltTICKET:
202 return removeTicketFromLedger;
203 case ltDEPOSIT_PREAUTH:
204 return removeDepositPreauthFromLedger;
205 case ltNFTOKEN_OFFER:
206 return removeNFTokenOfferFromLedger;
207 case ltDID:
208 return removeDIDFromLedger;
209 case ltORACLE:
210 return removeOracleFromLedger;
211 case ltCREDENTIAL:
212 return removeCredentialFromLedger;
213 case ltDELEGATE:
214 return removeDelegateFromLedger;
215 default:
216 return nullptr;
217 }
218}
219
220} // namespace
221
222TER
224{
225 AccountID const account{ctx.tx[sfAccount]};
226 AccountID const dst{ctx.tx[sfDestination]};
227
228 auto sleDst = ctx.view.read(keylet::account(dst));
229
230 if (!sleDst)
231 return tecNO_DST;
232
233 if (sleDst->isFlag(lsfRequireDestTag) && !ctx.tx[~sfDestinationTag])
234 return tecDST_TAG_NEEDED;
235
236 // If credentials are provided - check them anyway
237 if (auto const err = credentials::valid(ctx.tx, ctx.view, account, ctx.j); !isTesSuccess(err))
238 return err;
239
240 // if credentials then postpone auth check to doApply, to check for expired
241 // credentials
242 if (!ctx.tx.isFieldPresent(sfCredentialIDs))
243 {
244 // Check whether the destination account requires deposit authorization.
245 if (sleDst->isFlag(lsfDepositAuth))
246 {
247 if (!ctx.view.exists(keylet::depositPreauth(dst, account)))
248 return tecNO_PERMISSION;
249 }
250 }
251
252 auto sleAccount = ctx.view.read(keylet::account(account));
253 XRPL_ASSERT(sleAccount, "xrpl::AccountDelete::preclaim : non-null account");
254 if (!sleAccount)
255 return terNO_ACCOUNT;
256
257 // If an issuer has any issued NFTs resident in the ledger then it
258 // cannot be deleted.
259 if ((*sleAccount)[~sfMintedNFTokens] != (*sleAccount)[~sfBurnedNFTokens])
260 return tecHAS_OBLIGATIONS;
261
262 // If the account owns any NFTs it cannot be deleted.
263 Keylet const first = keylet::nftokenPageMin(account);
264 Keylet const last = keylet::nftokenPageMax(account);
265
266 auto const cp = ctx.view.read(
267 Keylet(ltNFTOKEN_PAGE, ctx.view.succ(first.key, last.key.next()).value_or(last.key)));
268 if (cp)
269 return tecHAS_OBLIGATIONS;
270
271 // We don't allow an account to be deleted if its sequence number
272 // is within 256 of the current ledger. This prevents replay of old
273 // transactions if this account is resurrected after it is deleted.
274 //
275 // We look at the account's Sequence rather than the transaction's
276 // Sequence in preparation for Tickets.
277 static constexpr std::uint32_t kSeqDelta{255};
278 if ((*sleAccount)[sfSequence] + kSeqDelta > ctx.view.seq())
279 return tecTOO_SOON;
280
281 // We don't allow an account to be deleted if
282 // <FirstNFTokenSequence + MintedNFTokens> is within 256 of the
283 // current ledger. This is to prevent having duplicate NFTokenIDs after
284 // account re-creation.
285 //
286 // Without this restriction, duplicate NFTokenIDs can be reproduced when
287 // authorized minting is involved. Because when the minter mints a NFToken,
288 // the issuer's sequence does not change. So when the issuer re-creates
289 // their account and mints a NFToken, it is possible that the
290 // NFTokenSequence of this NFToken is the same as the one that the
291 // authorized minter minted in a previous ledger.
292 if ((*sleAccount)[~sfFirstNFTokenSequence].value_or(0) +
293 (*sleAccount)[~sfMintedNFTokens].value_or(0) + kSeqDelta >
294 ctx.view.seq())
295 return tecTOO_SOON;
296
297 // Verify that the account does not own any objects that would prevent
298 // the account from being deleted.
299 Keylet const ownerDirKeylet{keylet::ownerDir(account)};
300 if (dirIsEmpty(ctx.view, ownerDirKeylet))
301 return tesSUCCESS;
302
303 SLE::const_pointer sleDirNode{};
304 unsigned int uDirEntry{0};
305 uint256 dirEntry{beast::kZero};
306
307 // Account has no directory at all. This _should_ have been caught
308 // by the dirIsEmpty() check earlier, but it's okay to catch it here.
309 if (!cdirFirst(ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
310 return tesSUCCESS;
311
312 std::uint32_t deletableDirEntryCount{0};
313 do
314 {
315 // Make sure any directory node types that we find are the kind
316 // we can delete.
317 auto sleItem = ctx.view.read(keylet::child(dirEntry));
318 if (!sleItem)
319 {
320 // Directory node has an invalid index. Bail out.
321 // LCOV_EXCL_START
322 JLOG(ctx.j.fatal()) << "AccountDelete: directory node in ledger " << ctx.view.seq()
323 << " has index to object that is missing: " << to_string(dirEntry);
324 return tefBAD_LEDGER;
325 // LCOV_EXCL_STOP
326 }
327
328 LedgerEntryType const nodeType{safeCast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
329
330 if (nonObligationDeleter(nodeType) == nullptr)
331 return tecHAS_OBLIGATIONS;
332
333 // We found a deletable directory entry. Count it. If we find too
334 // many deletable directory entries then bail out.
335 if (++deletableDirEntryCount > kMaxDeletableDirEntries)
336 return tefTOO_BIG;
337
338 } while (cdirNext(ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
339
340 return tesSUCCESS;
341}
342
343TER
345{
346 auto src = view().peek(keylet::account(accountID_));
347 XRPL_ASSERT(src, "xrpl::AccountDelete::doApply : non-null source account");
348
349 auto const dstID = ctx_.tx[sfDestination];
350 auto dst = view().peek(keylet::account(dstID));
351 XRPL_ASSERT(dst, "xrpl::AccountDelete::doApply : non-null destination account");
352
353 if (!src || !dst)
354 return tefBAD_LEDGER; // LCOV_EXCL_LINE
355
356 if (ctx_.tx.isFieldPresent(sfCredentialIDs))
357 {
358 if (auto err =
359 verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, dstID, dst, ctx_.journal);
360 !isTesSuccess(err))
361 return err;
362 }
363
364 Keylet const ownerDirKeylet{keylet::ownerDir(accountID_)};
365 auto const ter = cleanupOnAccountDelete(
366 view(),
367 ownerDirKeylet,
368 [&](LedgerEntryType nodeType,
369 uint256 const& dirEntry,
371 if (auto deleter = nonObligationDeleter(nodeType))
372 {
373 TER const result{deleter(ctx_.registry, view(), accountID_, dirEntry, sleItem, j_)};
374
375 return {result, SkipEntry::No};
376 }
377
378 // LCOV_EXCL_START
379 UNREACHABLE(
380 "xrpl::AccountDelete::doApply : undeletable item not found "
381 "in preclaim");
382 JLOG(j_.error()) << "AccountDelete undeletable item not "
383 "found in preclaim.";
385 // LCOV_EXCL_STOP
386 },
387 ctx_.journal);
388 if (!isTesSuccess(ter))
389 return ter;
390
391 // Transfer any XRP remaining after the fee is paid to the destination:
392 auto const remainingBalance = src->getFieldAmount(sfBalance).xrp();
393 (*dst)[sfBalance] = (*dst)[sfBalance] + remainingBalance;
394 (*src)[sfBalance] = (*src)[sfBalance] - remainingBalance;
395 ctx_.deliver(remainingBalance);
396
397 XRPL_ASSERT(
398 (*src)[sfBalance] == XRPAmount(0), "xrpl::AccountDelete::doApply : source balance is zero");
399
400 // If there's still an owner directory associated with the source account
401 // delete it.
402 if (view().exists(ownerDirKeylet) && !view().emptyDirDelete(ownerDirKeylet))
403 {
404 JLOG(j_.error()) << "AccountDelete cannot delete root dir node of " << toBase58(accountID_);
405 return tecHAS_OBLIGATIONS;
406 }
407
408 // Re-arm the password change fee if we can and need to.
409 if (remainingBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
410 dst->clearFlag(lsfPasswordSpent);
411
412 view().update(dst);
413 view().erase(src);
414
415 return tesSUCCESS;
416}
417
418void
420{
421 // No transaction-specific invariants yet (future work).
422}
423
424bool
426 STTx const&,
427 TER,
428 XRPAmount,
429 ReadView const&,
430 beast::Journal const&)
431{
432 // No transaction-specific invariants yet (future work).
433 return true;
434}
435
436} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream fatal() const
Definition Journal.h:321
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:118
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:460
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:31
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:97
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:171
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:454
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:118
ApplyView & view()
Definition Transactor.h:136
AccountID const accountID_
Definition Transactor.h:120
ApplyContext & ctx_
Definition Transactor.h:116
static TER ticketDelete(ApplyView &view, AccountID const &account, uint256 const &ticketIndex, beast::Journal j)
TER deleteSLE(ApplyView &view, SLE::ref sleCredential, beast::Journal j)
NotTEC checkFields(STTx const &tx, 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:328
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition Indexes.cpp:357
Keylet nftokenPageMin(AccountID const &owner)
NFT page keylets.
Definition Indexes.cpp:384
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition Indexes.cpp:192
Keylet nftokenPageMax(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition Indexes.cpp:392
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
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:209
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
@ tefTOO_BIG
Definition TER.h:174
@ tefBAD_LEDGER
Definition TER.h:160
constexpr std::enable_if_t< std::is_integral_v< Dest > &&std::is_integral_v< Src >, Dest > safeCast(Src s) noexcept
Definition safe_cast.h:21
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:93
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:633
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:594
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
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:94
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
LedgerEntryType
Identifiers for on-ledger objects.
@ tecTOO_SOON
Definition TER.h:316
@ tecNO_PERMISSION
Definition TER.h:303
@ tecDST_TAG_NEEDED
Definition TER.h:307
@ tecHAS_OBLIGATIONS
Definition TER.h:315
@ tecNO_DST
Definition TER.h:288
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:562
static TER removeSignersFromLedger(ServiceRegistry &registry, ApplyView &view, Keylet const &accountKeylet, Keylet const &ownerDirKeylet, Keylet const &signerListKeylet, beast::Journal j)
@ tesSUCCESS
Definition TER.h:240
constexpr std::size_t kMaxDeletableDirEntries
The maximum number of owner directory entries for account to be deletable.
Definition Protocol.h:53
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:19
uint256 key
Definition Keylet.h:20
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
beast::Journal const j
Definition Transactor.h:69
State information when preflighting a tx.
Definition Transactor.h:18
beast::Journal const j
Definition Transactor.h:25