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/DeleteAccount.h>
23#include <xrpld/app/tx/detail/DeleteOracle.h>
24#include <xrpld/app/tx/detail/DepositPreauth.h>
25#include <xrpld/app/tx/detail/NFTokenUtils.h>
26#include <xrpld/app/tx/detail/SetSignerList.h>
27#include <xrpld/ledger/View.h>
28#include <xrpl/basics/Log.h>
29#include <xrpl/basics/mulDiv.h>
30#include <xrpl/beast/utility/instrumentation.h>
31#include <xrpl/protocol/Feature.h>
32#include <xrpl/protocol/FeeUnits.h>
33#include <xrpl/protocol/Indexes.h>
34#include <xrpl/protocol/Protocol.h>
35#include <xrpl/protocol/TxFlags.h>
36#include <xrpl/protocol/st.h>
37
38namespace ripple {
39
42{
43 if (!ctx.rules.enabled(featureDeletableAccounts))
44 return temDISABLED;
45
46 if (ctx.tx.isFieldPresent(sfCredentialIDs) &&
47 !ctx.rules.enabled(featureCredentials))
48 return temDISABLED;
49
50 if (ctx.tx.getFlags() & tfUniversalMask)
51 return temINVALID_FLAG;
52
53 if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
54 return ret;
55
56 if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
57 // An account cannot be deleted and give itself the resulting XRP.
58 return temDST_IS_SRC;
59
60 if (auto const err = credentials::checkFields(ctx); !isTesSuccess(err))
61 return err;
62
63 return preflight2(ctx);
64}
65
68{
69 // The fee required for AccountDelete is one owner reserve.
70 return view.fees().increment;
71}
72
73namespace {
74// Define a function pointer type that can be used to delete ledger node types.
75using DeleterFuncPtr = TER (*)(
76 Application& app,
77 ApplyView& view,
78 AccountID const& account,
79 uint256 const& delIndex,
80 std::shared_ptr<SLE> const& sleDel,
82
83// Local function definitions that provides signature compatibility.
84TER
86 Application& app,
87 ApplyView& view,
88 AccountID const& account,
89 uint256 const& delIndex,
90 std::shared_ptr<SLE> const& sleDel,
92{
93 return offerDelete(view, sleDel, j);
94}
95
96TER
98 Application& app,
99 ApplyView& view,
100 AccountID const& account,
101 uint256 const& delIndex,
102 std::shared_ptr<SLE> const& sleDel,
104{
105 return SetSignerList::removeFromLedger(app, view, account, j);
106}
107
108TER
109removeTicketFromLedger(
110 Application&,
111 ApplyView& view,
112 AccountID const& account,
113 uint256 const& delIndex,
116{
117 return Transactor::ticketDelete(view, account, delIndex, j);
118}
119
120TER
121removeDepositPreauthFromLedger(
122 Application&,
123 ApplyView& view,
124 AccountID const&,
125 uint256 const& delIndex,
128{
129 return DepositPreauth::removeFromLedger(view, delIndex, j);
130}
131
132TER
133removeNFTokenOfferFromLedger(
134 Application& app,
135 ApplyView& view,
136 AccountID const& account,
137 uint256 const& delIndex,
138 std::shared_ptr<SLE> const& sleDel,
140{
141 if (!nft::deleteTokenOffer(view, sleDel))
142 return tefBAD_LEDGER;
143
144 return tesSUCCESS;
145}
146
147TER
148removeDIDFromLedger(
149 Application& app,
150 ApplyView& view,
151 AccountID const& account,
152 uint256 const& delIndex,
153 std::shared_ptr<SLE> const& sleDel,
155{
156 return DIDDelete::deleteSLE(view, sleDel, account, j);
157}
158
159TER
160removeOracleFromLedger(
161 Application&,
162 ApplyView& view,
163 AccountID const& account,
164 uint256 const&,
165 std::shared_ptr<SLE> const& sleDel,
167{
168 return DeleteOracle::deleteOracle(view, sleDel, account, j);
169}
170
171TER
172removeCredentialFromLedger(
173 Application&,
174 ApplyView& view,
175 AccountID const&,
176 uint256 const&,
177 std::shared_ptr<SLE> const& sleDel,
179{
180 return credentials::deleteSLE(view, sleDel, j);
181}
182
183// Return nullptr if the LedgerEntryType represents an obligation that can't
184// be deleted. Otherwise return the pointer to the function that can delete
185// the non-obligation
186DeleterFuncPtr
187nonObligationDeleter(LedgerEntryType t)
188{
189 switch (t)
190 {
191 case ltOFFER:
192 return offerDelete;
193 case ltSIGNER_LIST:
195 case ltTICKET:
196 return removeTicketFromLedger;
197 case ltDEPOSIT_PREAUTH:
198 return removeDepositPreauthFromLedger;
199 case ltNFTOKEN_OFFER:
200 return removeNFTokenOfferFromLedger;
201 case ltDID:
202 return removeDIDFromLedger;
203 case ltORACLE:
204 return removeOracleFromLedger;
205 case ltCREDENTIAL:
206 return removeCredentialFromLedger;
207 default:
208 return nullptr;
209 }
210}
211
212} // namespace
213
214TER
216{
217 AccountID const account{ctx.tx[sfAccount]};
218 AccountID const dst{ctx.tx[sfDestination]};
219
220 auto sleDst = ctx.view.read(keylet::account(dst));
221
222 if (!sleDst)
223 return tecNO_DST;
224
225 if ((*sleDst)[sfFlags] & lsfRequireDestTag && !ctx.tx[~sfDestinationTag])
226 return tecDST_TAG_NEEDED;
227
228 // If credentials are provided - check them anyway
229 if (auto const err = credentials::valid(ctx, account); !isTesSuccess(err))
230 return err;
231
232 // if credentials then postpone auth check to doApply, to check for expired
233 // credentials
234 if (!ctx.tx.isFieldPresent(sfCredentialIDs))
235 {
236 // Check whether the destination account requires deposit authorization.
237 if (ctx.view.rules().enabled(featureDepositAuth) &&
238 (sleDst->getFlags() & lsfDepositAuth))
239 {
240 if (!ctx.view.exists(keylet::depositPreauth(dst, account)))
241 return tecNO_PERMISSION;
242 }
243 }
244
245 auto sleAccount = ctx.view.read(keylet::account(account));
246 XRPL_ASSERT(
247 sleAccount, "ripple::DeleteAccount::preclaim : non-null account");
248 if (!sleAccount)
249 return terNO_ACCOUNT;
250
251 if (ctx.view.rules().enabled(featureNonFungibleTokensV1))
252 {
253 // If an issuer has any issued NFTs resident in the ledger then it
254 // cannot be deleted.
255 if ((*sleAccount)[~sfMintedNFTokens] !=
256 (*sleAccount)[~sfBurnedNFTokens])
257 return tecHAS_OBLIGATIONS;
258
259 // If the account owns any NFTs it cannot be deleted.
260 Keylet const first = keylet::nftpage_min(account);
261 Keylet const last = keylet::nftpage_max(account);
262
263 auto const cp = ctx.view.read(Keylet(
264 ltNFTOKEN_PAGE,
265 ctx.view.succ(first.key, last.key.next()).value_or(last.key)));
266 if (cp)
267 return tecHAS_OBLIGATIONS;
268 }
269
270 // We don't allow an account to be deleted if its sequence number
271 // is within 256 of the current ledger. This prevents replay of old
272 // transactions if this account is resurrected after it is deleted.
273 //
274 // We look at the account's Sequence rather than the transaction's
275 // Sequence in preparation for Tickets.
276 constexpr std::uint32_t seqDelta{255};
277 if ((*sleAccount)[sfSequence] + seqDelta > ctx.view.seq())
278 return tecTOO_SOON;
279
280 // When fixNFTokenRemint is enabled, we don't allow an account to be
281 // deleted if <FirstNFTokenSequence + MintedNFTokens> is within 256 of the
282 // current ledger. This is to prevent having duplicate NFTokenIDs after
283 // account re-creation.
284 //
285 // Without this restriction, duplicate NFTokenIDs can be reproduced when
286 // authorized minting is involved. Because when the minter mints a NFToken,
287 // the issuer's sequence does not change. So when the issuer re-creates
288 // their account and mints a NFToken, it is possible that the
289 // NFTokenSequence of this NFToken is the same as the one that the
290 // authorized minter minted in a previous ledger.
291 if (ctx.view.rules().enabled(fixNFTokenRemint) &&
292 ((*sleAccount)[~sfFirstNFTokenSequence].value_or(0) +
293 (*sleAccount)[~sfMintedNFTokens].value_or(0) + seqDelta >
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 std::shared_ptr<SLE const> sleDirNode{};
304 unsigned int uDirEntry{0};
305 uint256 dirEntry{beast::zero};
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(
310 ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
311 return tesSUCCESS;
312
313 std::int32_t deletableDirEntryCount{0};
314 do
315 {
316 // Make sure any directory node types that we find are the kind
317 // we can delete.
318 auto sleItem = ctx.view.read(keylet::child(dirEntry));
319 if (!sleItem)
320 {
321 // Directory node has an invalid index. Bail out.
322 JLOG(ctx.j.fatal())
323 << "DeleteAccount: directory node in ledger " << ctx.view.seq()
324 << " has index to object that is missing: "
325 << to_string(dirEntry);
326 return tefBAD_LEDGER;
327 }
328
329 LedgerEntryType const nodeType{
330 safe_cast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
331
332 if (!nonObligationDeleter(nodeType))
333 return tecHAS_OBLIGATIONS;
334
335 // We found a deletable directory entry. Count it. If we find too
336 // many deletable directory entries then bail out.
337 if (++deletableDirEntryCount > maxDeletableDirEntries)
338 return tefTOO_BIG;
339
340 } while (cdirNext(
341 ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
342
343 return tesSUCCESS;
344}
345
346TER
348{
349 auto src = view().peek(keylet::account(account_));
350 XRPL_ASSERT(
351 src, "ripple::DeleteAccount::doApply : non-null source account");
352
353 auto const dstID = ctx_.tx[sfDestination];
354 auto dst = view().peek(keylet::account(dstID));
355 XRPL_ASSERT(
356 dst, "ripple::DeleteAccount::doApply : non-null destination account");
357
358 if (!src || !dst)
359 return tefBAD_LEDGER;
360
361 if (ctx_.view().rules().enabled(featureDepositAuth) &&
362 ctx_.tx.isFieldPresent(sfCredentialIDs))
363 {
364 if (auto err = verifyDepositPreauth(ctx_, account_, dstID, dst);
365 !isTesSuccess(err))
366 return err;
367 }
368
369 Keylet const ownerDirKeylet{keylet::ownerDir(account_)};
370 auto const ter = cleanupOnAccountDelete(
371 view(),
372 ownerDirKeylet,
373 [&](LedgerEntryType nodeType,
374 uint256 const& dirEntry,
376 if (auto deleter = nonObligationDeleter(nodeType))
377 {
378 TER const result{
379 deleter(ctx_.app, view(), account_, dirEntry, sleItem, j_)};
380
381 return {result, SkipEntry::No};
382 }
383
384 UNREACHABLE(
385 "ripple::DeleteAccount::doApply : undeletable item not found "
386 "in preclaim");
387 JLOG(j_.error()) << "DeleteAccount undeletable item not "
388 "found in preclaim.";
390 },
391 ctx_.journal);
392 if (ter != tesSUCCESS)
393 return ter;
394
395 // Transfer any XRP remaining after the fee is paid to the destination:
396 (*dst)[sfBalance] = (*dst)[sfBalance] + mSourceBalance;
397 (*src)[sfBalance] = (*src)[sfBalance] - mSourceBalance;
399
400 XRPL_ASSERT(
401 (*src)[sfBalance] == XRPAmount(0),
402 "ripple::DeleteAccount::doApply : source balance is zero");
403
404 // If there's still an owner directory associated with the source account
405 // delete it.
406 if (view().exists(ownerDirKeylet) && !view().emptyDirDelete(ownerDirKeylet))
407 {
408 JLOG(j_.error()) << "DeleteAccount cannot delete root dir node of "
409 << toBase58(account_);
410 return tecHAS_OBLIGATIONS;
411 }
412
413 // Re-arm the password change fee if we can and need to.
414 if (mSourceBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
415 dst->clearFlag(lsfPasswordSpent);
416
417 view().update(dst);
418 view().erase(src);
419
420 return tesSUCCESS;
421}
422
423} // namespace ripple
A generic endpoint for log messages.
Definition: Journal.h:59
Stream fatal() const
Definition: Journal.h:341
Stream error() const
Definition: Journal.h:335
ApplyView & view()
Definition: ApplyContext.h:54
Application & app
Definition: ApplyContext.h:47
beast::Journal const journal
Definition: ApplyContext.h:51
void deliver(STAmount const &amount)
Sets the DeliveredAmount field in the metadata.
Definition: ApplyContext.h:80
Writeable view to a ledger, for applying a transaction.
Definition: ApplyView.h:140
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:191
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:55
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:122
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:122
bool isFieldPresent(SField const &field) const
Definition: STObject.cpp:454
std::uint32_t getFlags() const
Definition: STObject.cpp:507
static TER removeFromLedger(Application &app, ApplyView &view, AccountID const &account, beast::Journal j)
AccountID const account_
Definition: Transactor.h:91
ApplyView & view()
Definition: Transactor.h:107
beast::Journal const j_
Definition: Transactor.h:89
static TER ticketDelete(ApplyView &view, AccountID const &account, uint256 const &ticketIndex, beast::Journal j)
Definition: Transactor.cpp:416
XRPAmount mSourceBalance
Definition: Transactor.h:93
ApplyContext & ctx_
Definition: Transactor.h:88
base_uint next() const
Definition: base_uint.h:454
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:166
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:160
Keylet nftpage_min(AccountID const &owner)
NFT page keylets.
Definition: Indexes.cpp:379
Keylet nftpage_max(AccountID const &owner)
A keylet for the owner's last possible NFT page.
Definition: Indexes.cpp:387
Keylet ownerDir(AccountID const &id) noexcept
The root page of an account's directory.
Definition: Indexes.cpp:350
Keylet depositPreauth(AccountID const &owner, AccountID const &preauthorized) noexcept
A DepositPreauth.
Definition: Indexes.cpp:318
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:106
std::size_t constexpr maxDeletableDirEntries
The maximum number of owner directory entries for account to be deletable.
Definition: Protocol.h:65
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:136
base_uint< 256 > uint256
Definition: base_uint.h:557
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:82
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:134
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:147
@ 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:629
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:53
ReadView const & view
Definition: Transactor.h:56
beast::Journal const j
Definition: Transactor.h:60
State information when preflighting a tx.
Definition: Transactor.h:32