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