rippled
Loading...
Searching...
No Matches
DeleteAccount.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2019 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <xrpld/app/misc/CredentialHelpers.h>
21#include <xrpld/app/tx/detail/DID.h>
22#include <xrpld/app/tx/detail/DelegateSet.h>
23#include <xrpld/app/tx/detail/DeleteAccount.h>
24#include <xrpld/app/tx/detail/DeleteOracle.h>
25#include <xrpld/app/tx/detail/DepositPreauth.h>
26#include <xrpld/app/tx/detail/NFTokenUtils.h>
27#include <xrpld/app/tx/detail/SetSignerList.h>
28#include <xrpld/ledger/View.h>
29
30#include <xrpl/basics/Log.h>
31#include <xrpl/basics/mulDiv.h>
32#include <xrpl/beast/utility/instrumentation.h>
33#include <xrpl/protocol/Feature.h>
34#include <xrpl/protocol/FeeUnits.h>
35#include <xrpl/protocol/Indexes.h>
36#include <xrpl/protocol/Protocol.h>
37#include <xrpl/protocol/TxFlags.h>
38
39namespace ripple {
40
43{
44 if (!ctx.rules.enabled(featureDeletableAccounts))
45 return temDISABLED;
46
47 if (ctx.tx.isFieldPresent(sfCredentialIDs) &&
48 !ctx.rules.enabled(featureCredentials))
49 return temDISABLED;
50
51 if (ctx.tx.getFlags() & tfUniversalMask)
52 return temINVALID_FLAG;
53
54 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
55 return ret;
56
57 if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
58 // An account cannot be deleted and give itself the resulting XRP.
59 return temDST_IS_SRC;
60
61 if (auto const err = credentials::checkFields(ctx.tx, ctx.j);
62 !isTesSuccess(err))
63 return err;
64
65 return preflight2(ctx);
66}
67
70{
71 // The fee required for AccountDelete is one owner reserve.
72 return view.fees().increment;
73}
74
75namespace {
76// Define a function pointer type that can be used to delete ledger node types.
77using DeleterFuncPtr = TER (*)(
78 Application& app,
79 ApplyView& view,
80 AccountID const& account,
81 uint256 const& delIndex,
82 std::shared_ptr<SLE> const& sleDel,
84
85// Local function definitions that provides signature compatibility.
86TER
88 Application& app,
89 ApplyView& view,
90 AccountID const& account,
91 uint256 const& delIndex,
92 std::shared_ptr<SLE> const& sleDel,
94{
95 return offerDelete(view, sleDel, j);
96}
97
98TER
100 Application& app,
101 ApplyView& view,
102 AccountID const& account,
103 uint256 const& delIndex,
104 std::shared_ptr<SLE> const& sleDel,
106{
107 return SetSignerList::removeFromLedger(app, view, account, j);
108}
109
110TER
111removeTicketFromLedger(
112 Application&,
113 ApplyView& view,
114 AccountID const& account,
115 uint256 const& delIndex,
118{
119 return Transactor::ticketDelete(view, account, delIndex, j);
120}
121
122TER
123removeDepositPreauthFromLedger(
124 Application&,
125 ApplyView& view,
126 AccountID const&,
127 uint256 const& delIndex,
130{
131 return DepositPreauth::removeFromLedger(view, delIndex, j);
132}
133
134TER
135removeNFTokenOfferFromLedger(
136 Application& app,
137 ApplyView& view,
138 AccountID const& account,
139 uint256 const& delIndex,
140 std::shared_ptr<SLE> const& sleDel,
142{
143 if (!nft::deleteTokenOffer(view, sleDel))
144 return tefBAD_LEDGER;
145
146 return tesSUCCESS;
147}
148
149TER
150removeDIDFromLedger(
151 Application& app,
152 ApplyView& view,
153 AccountID const& account,
154 uint256 const& delIndex,
155 std::shared_ptr<SLE> const& sleDel,
157{
158 return DIDDelete::deleteSLE(view, sleDel, account, j);
159}
160
161TER
162removeOracleFromLedger(
163 Application&,
164 ApplyView& view,
165 AccountID const& account,
166 uint256 const&,
167 std::shared_ptr<SLE> const& sleDel,
169{
170 return DeleteOracle::deleteOracle(view, sleDel, account, j);
171}
172
173TER
174removeCredentialFromLedger(
175 Application&,
176 ApplyView& view,
177 AccountID const&,
178 uint256 const&,
179 std::shared_ptr<SLE> const& sleDel,
181{
182 return credentials::deleteSLE(view, sleDel, j);
183}
184
185TER
186removeDelegateFromLedger(
187 Application& app,
188 ApplyView& view,
189 AccountID const& account,
190 uint256 const& delIndex,
191 std::shared_ptr<SLE> const& sleDel,
193{
194 return DelegateSet::deleteDelegate(view, sleDel, account, j);
195}
196
197// Return nullptr if the LedgerEntryType represents an obligation that can't
198// be deleted. Otherwise return the pointer to the function that can delete
199// the non-obligation
200DeleterFuncPtr
201nonObligationDeleter(LedgerEntryType t)
202{
203 switch (t)
204 {
205 case ltOFFER:
206 return offerDelete;
207 case ltSIGNER_LIST:
209 case ltTICKET:
210 return removeTicketFromLedger;
211 case ltDEPOSIT_PREAUTH:
212 return removeDepositPreauthFromLedger;
213 case ltNFTOKEN_OFFER:
214 return removeNFTokenOfferFromLedger;
215 case ltDID:
216 return removeDIDFromLedger;
217 case ltORACLE:
218 return removeOracleFromLedger;
219 case ltCREDENTIAL:
220 return removeCredentialFromLedger;
221 case ltDELEGATE:
222 return removeDelegateFromLedger;
223 default:
224 return nullptr;
225 }
226}
227
228} // namespace
229
230TER
232{
233 AccountID const account{ctx.tx[sfAccount]};
234 AccountID const dst{ctx.tx[sfDestination]};
235
236 auto sleDst = ctx.view.read(keylet::account(dst));
237
238 if (!sleDst)
239 return tecNO_DST;
240
241 if ((*sleDst)[sfFlags] & lsfRequireDestTag && !ctx.tx[~sfDestinationTag])
242 return tecDST_TAG_NEEDED;
243
244 // If credentials are provided - check them anyway
245 if (auto const err = credentials::valid(ctx.tx, ctx.view, account, ctx.j);
246 !isTesSuccess(err))
247 return err;
248
249 // if credentials then postpone auth check to doApply, to check for expired
250 // credentials
251 if (!ctx.tx.isFieldPresent(sfCredentialIDs))
252 {
253 // Check whether the destination account requires deposit authorization.
254 if (ctx.view.rules().enabled(featureDepositAuth) &&
255 (sleDst->getFlags() & lsfDepositAuth))
256 {
257 if (!ctx.view.exists(keylet::depositPreauth(dst, account)))
258 return tecNO_PERMISSION;
259 }
260 }
261
262 auto sleAccount = ctx.view.read(keylet::account(account));
263 XRPL_ASSERT(
264 sleAccount, "ripple::DeleteAccount::preclaim : non-null account");
265 if (!sleAccount)
266 return terNO_ACCOUNT;
267
268 if (ctx.view.rules().enabled(featureNonFungibleTokensV1))
269 {
270 // If an issuer has any issued NFTs resident in the ledger then it
271 // cannot be deleted.
272 if ((*sleAccount)[~sfMintedNFTokens] !=
273 (*sleAccount)[~sfBurnedNFTokens])
274 return tecHAS_OBLIGATIONS;
275
276 // If the account owns any NFTs it cannot be deleted.
277 Keylet const first = keylet::nftpage_min(account);
278 Keylet const last = keylet::nftpage_max(account);
279
280 auto const cp = ctx.view.read(Keylet(
281 ltNFTOKEN_PAGE,
282 ctx.view.succ(first.key, last.key.next()).value_or(last.key)));
283 if (cp)
284 return tecHAS_OBLIGATIONS;
285 }
286
287 // We don't allow an account to be deleted if its sequence number
288 // is within 256 of the current ledger. This prevents replay of old
289 // transactions if this account is resurrected after it is deleted.
290 //
291 // We look at the account's Sequence rather than the transaction's
292 // Sequence in preparation for Tickets.
293 constexpr std::uint32_t seqDelta{255};
294 if ((*sleAccount)[sfSequence] + seqDelta > ctx.view.seq())
295 return tecTOO_SOON;
296
297 // When fixNFTokenRemint is enabled, we don't allow an account to be
298 // deleted if <FirstNFTokenSequence + MintedNFTokens> is within 256 of the
299 // current ledger. This is to prevent having duplicate NFTokenIDs after
300 // account re-creation.
301 //
302 // Without this restriction, duplicate NFTokenIDs can be reproduced when
303 // authorized minting is involved. Because when the minter mints a NFToken,
304 // the issuer's sequence does not change. So when the issuer re-creates
305 // their account and mints a NFToken, it is possible that the
306 // NFTokenSequence of this NFToken is the same as the one that the
307 // authorized minter minted in a previous ledger.
308 if (ctx.view.rules().enabled(fixNFTokenRemint) &&
309 ((*sleAccount)[~sfFirstNFTokenSequence].value_or(0) +
310 (*sleAccount)[~sfMintedNFTokens].value_or(0) + seqDelta >
311 ctx.view.seq()))
312 return tecTOO_SOON;
313
314 // Verify that the account does not own any objects that would prevent
315 // the account from being deleted.
316 Keylet const ownerDirKeylet{keylet::ownerDir(account)};
317 if (dirIsEmpty(ctx.view, ownerDirKeylet))
318 return tesSUCCESS;
319
320 std::shared_ptr<SLE const> sleDirNode{};
321 unsigned int uDirEntry{0};
322 uint256 dirEntry{beast::zero};
323
324 // Account has no directory at all. This _should_ have been caught
325 // by the dirIsEmpty() check earlier, but it's okay to catch it here.
326 if (!cdirFirst(
327 ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
328 return tesSUCCESS;
329
330 std::int32_t deletableDirEntryCount{0};
331 do
332 {
333 // Make sure any directory node types that we find are the kind
334 // we can delete.
335 auto sleItem = ctx.view.read(keylet::child(dirEntry));
336 if (!sleItem)
337 {
338 // Directory node has an invalid index. Bail out.
339 JLOG(ctx.j.fatal())
340 << "DeleteAccount: directory node in ledger " << ctx.view.seq()
341 << " has index to object that is missing: "
342 << to_string(dirEntry);
343 return tefBAD_LEDGER;
344 }
345
346 LedgerEntryType const nodeType{
347 safe_cast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
348
349 if (!nonObligationDeleter(nodeType))
350 return tecHAS_OBLIGATIONS;
351
352 // We found a deletable directory entry. Count it. If we find too
353 // many deletable directory entries then bail out.
354 if (++deletableDirEntryCount > maxDeletableDirEntries)
355 return tefTOO_BIG;
356
357 } while (cdirNext(
358 ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
359
360 return tesSUCCESS;
361}
362
363TER
365{
366 auto src = view().peek(keylet::account(account_));
367 XRPL_ASSERT(
368 src, "ripple::DeleteAccount::doApply : non-null source account");
369
370 auto const dstID = ctx_.tx[sfDestination];
371 auto dst = view().peek(keylet::account(dstID));
372 XRPL_ASSERT(
373 dst, "ripple::DeleteAccount::doApply : non-null destination account");
374
375 if (!src || !dst)
376 return tefBAD_LEDGER;
377
378 if (ctx_.view().rules().enabled(featureDepositAuth) &&
379 ctx_.tx.isFieldPresent(sfCredentialIDs))
380 {
381 if (auto err = verifyDepositPreauth(
382 ctx_.tx, ctx_.view(), account_, dstID, dst, ctx_.journal);
383 !isTesSuccess(err))
384 return err;
385 }
386
387 Keylet const ownerDirKeylet{keylet::ownerDir(account_)};
388 auto const ter = cleanupOnAccountDelete(
389 view(),
390 ownerDirKeylet,
391 [&](LedgerEntryType nodeType,
392 uint256 const& dirEntry,
394 if (auto deleter = nonObligationDeleter(nodeType))
395 {
396 TER const result{
397 deleter(ctx_.app, view(), account_, dirEntry, sleItem, j_)};
398
399 return {result, SkipEntry::No};
400 }
401
402 UNREACHABLE(
403 "ripple::DeleteAccount::doApply : undeletable item not found "
404 "in preclaim");
405 JLOG(j_.error()) << "DeleteAccount undeletable item not "
406 "found in preclaim.";
408 },
409 ctx_.journal);
410 if (ter != tesSUCCESS)
411 return ter;
412
413 // Transfer any XRP remaining after the fee is paid to the destination:
414 (*dst)[sfBalance] = (*dst)[sfBalance] + mSourceBalance;
415 (*src)[sfBalance] = (*src)[sfBalance] - mSourceBalance;
417
418 XRPL_ASSERT(
419 (*src)[sfBalance] == XRPAmount(0),
420 "ripple::DeleteAccount::doApply : source balance is zero");
421
422 // If there's still an owner directory associated with the source account
423 // delete it.
424 if (view().exists(ownerDirKeylet) && !view().emptyDirDelete(ownerDirKeylet))
425 {
426 JLOG(j_.error()) << "DeleteAccount cannot delete root dir node of "
427 << toBase58(account_);
428 return tecHAS_OBLIGATIONS;
429 }
430
431 // Re-arm the password change fee if we can and need to.
432 if (mSourceBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
433 dst->clearFlag(lsfPasswordSpent);
434
435 view().update(dst);
436 view().erase(src);
437
438 return tesSUCCESS;
439}
440
441} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:60
Stream fatal() const
Definition: Journal.h:352
Stream error() const
Definition: Journal.h:346
ApplyView & view()
Definition: ApplyContext.h:78
Application & app
Definition: ApplyContext.h:71
beast::Journal const journal
Definition: ApplyContext.h:75
void deliver(STAmount const &amount)
Sets the DeliveredAmount field in the metadata.
Definition: ApplyContext.h:104
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:144
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
virtual void erase(std::shared_ptr< SLE > const &sle)=0
Remove a peeked SLE.
static TER deleteSLE(ApplyContext &ctx, Keylet sleKeylet, AccountID const owner)
Definition: DID.cpp:190
static TER deleteDelegate(ApplyView &view, std::shared_ptr< SLE > const &sle, AccountID const &account, beast::Journal j)
static NotTEC preflight(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
TER doApply() override
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
static TER deleteOracle(ApplyView &view, std::shared_ptr< SLE > const &sle, AccountID const &account, beast::Journal j)
static TER removeFromLedger(ApplyView &view, uint256 const &delIndex, beast::Journal j)
A view into a ledger.
Definition: ReadView.h:52
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
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.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
LedgerIndex seq() const
Returns the sequence number of the base ledger.
Definition: ReadView.h:119
virtual Rules const & rules() const =0
Returns the tx processing rules.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition: Rules.cpp:130
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:484
std::uint32_t getFlags() const
Definition: STObject.cpp:537
static TER removeFromLedger(Application &app, ApplyView &view, AccountID const &account, beast::Journal j)
AccountID const account_
Definition: Transactor.h:143
ApplyView & view()
Definition: Transactor.h:159
beast::Journal const j_
Definition: Transactor.h:141
static TER ticketDelete(ApplyView &view, AccountID const &account, uint256 const &ticketIndex, beast::Journal j)
Definition: Transactor.cpp:479
XRPAmount mSourceBalance
Definition: Transactor.h:145
ApplyContext & ctx_
Definition: Transactor.h:140
base_uint next() const
Definition: base_uint.h:455
NotTEC checkFields(STTx const &tx, beast::Journal j)
TER deleteSLE(ApplyView &view, std::shared_ptr< SLE > const &sleCredential, beast::Journal j)
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
Keylet child(uint256 const &key) noexcept
Any item that can be in an owner dir.
Definition: Indexes.cpp:190
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:184
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition: Indexes.cpp:403
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition: Indexes.cpp:411
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:374
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition: Indexes.cpp:342
bool deleteTokenOffer(ApplyView &view, std::shared_ptr< SLE > const &offer)
Deletes the given token offer.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:25
base_uint< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition: AccountID.h:48
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:114
std::size_t constexpr maxDeletableDirEntries
The maximum number of owner directory entries for account to be deletable.
Definition: Protocol.h:66
bool cdirFirst(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
Definition: View.cpp:147
base_uint< 256 > uint256
Definition: base_uint.h:558
static TER removeSignersFromLedger(Application &app, ApplyView &view, Keylet const &accountKeylet, Keylet const &ownerDirKeylet, Keylet const &signerListKeylet, beast::Journal j)
@ lsfRequireDestTag
@ lsfPasswordSpent
@ lsfDepositAuth
NotTEC preflight1(PreflightContext const &ctx)
Performs early sanity checks on the account and fee fields.
Definition: Transactor.cpp:91
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition: View.cpp:1471
TER verifyDepositPreauth(STTx const &tx, ApplyView &view, AccountID const &src, AccountID const &dst, std::shared_ptr< SLE > const &sleDst, beast::Journal j)
@ tefBAD_LEDGER
Definition: TER.h:170
@ tefTOO_BIG
Definition: TER.h:184
NotTEC preflight2(PreflightContext const &ctx)
Checks whether the signature appears valid.
Definition: Transactor.cpp:160
bool cdirNext(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the next entry in the directory, advancing the index.
Definition: View.cpp:158
@ tecNO_DST
Definition: TER.h:290
@ tecTOO_SOON
Definition: TER.h:318
@ tecNO_PERMISSION
Definition: TER.h:305
@ tecDST_TAG_NEEDED
Definition: TER.h:309
@ tecHAS_OBLIGATIONS
Definition: TER.h:317
@ tesSUCCESS
Definition: TER.h:244
bool isTesSuccess(TER x) noexcept
Definition: TER.h:674
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:630
LedgerEntryType
Identifiers for on-ledger objects.
Definition: LedgerFormats.h:54
TER cleanupOnAccountDelete(ApplyView &view, Keylet const &ownerDirKeylet, EntryDeleter const &deleter, beast::Journal j, std::optional< uint16_t > maxNodesToDelete)
Definition: View.cpp:2542
constexpr std::uint32_t tfUniversalMask
Definition: TxFlags.h:63
@ terNO_ACCOUNT
Definition: TER.h:217
TERSubset< CanCvtToTER > TER
Definition: TER.h:645
bool dirIsEmpty(ReadView const &view, Keylet const &k)
Returns true if the directory is empty.
Definition: View.cpp:906
TERSubset< CanCvtToNotTEC > NotTEC
Definition: TER.h:605
@ temINVALID_FLAG
Definition: TER.h:111
@ temDISABLED
Definition: TER.h:114
@ temDST_IS_SRC
Definition: TER.h:108
XRPAmount increment
Definition: protocol/Fees.h:36
A pair of SHAMap key and LedgerEntryType.
Definition: Keylet.h:39
uint256 key
Definition: Keylet.h:40
State information when determining if a tx is likely to claim a fee.
Definition: Transactor.h:79
ReadView const & view
Definition: Transactor.h:82
beast::Journal const j
Definition: Transactor.h:87
State information when preflighting a tx.
Definition: Transactor.h:34
beast::Journal const j
Definition: Transactor.h:41