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