Files
xahaud/src/ripple/app/tx/impl/DeleteAccount.cpp
Ed Hennis e4b17d1cf2 XRPFees: Fee setting and handling improvements (#4247)
* Introduces amendment `XRPFees`
* Convert fee voting and protocol messages to use XRPAmounts
* Includes Validations, Change transactions, the "Fees" ledger object,
  and subscription messages

* Improve handling of 0 drop reference fee with TxQ. For use with networks that do not want to require fees
* Note that fee escalation logic is still in place, which may cause the
  open ledger fee to rise if the network is busy. 0 drop transactions
  will still queue, and fee escalation can be effectively disabled by
  modifying the configuration on all nodes

* Change default network reserves to match Mainnet

* Name the new SFields *Drops (not *XRP)
* Reserve SField IDs for Hooks

* Clarify comments explaining the ttFEE transaction field validation
2023-02-02 16:20:35 -08:00

376 lines
12 KiB
C++

//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2019 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/app/tx/impl/DeleteAccount.h>
#include <ripple/app/tx/impl/DepositPreauth.h>
#include <ripple/app/tx/impl/SetSignerList.h>
#include <ripple/app/tx/impl/details/NFTokenUtils.h>
#include <ripple/basics/FeeUnits.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/mulDiv.h>
#include <ripple/ledger/View.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/Protocol.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/st.h>
namespace ripple {
NotTEC
DeleteAccount::preflight(PreflightContext const& ctx)
{
if (!ctx.rules.enabled(featureDeletableAccounts))
return temDISABLED;
if (ctx.tx.getFlags() & tfUniversalMask)
return temINVALID_FLAG;
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret;
if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
// An account cannot be deleted and give itself the resulting XRP.
return temDST_IS_SRC;
return preflight2(ctx);
}
XRPAmount
DeleteAccount::calculateBaseFee(ReadView const& view, STTx const& tx)
{
// The fee required for AccountDelete is one owner reserve.
return view.fees().increment;
}
namespace {
// Define a function pointer type that can be used to delete ledger node types.
using DeleterFuncPtr = TER (*)(
Application& app,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j);
// Local function definitions that provides signature compatibility.
TER
offerDelete(
Application& app,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return offerDelete(view, sleDel, j);
}
TER
removeSignersFromLedger(
Application& app,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return SetSignerList::removeFromLedger(app, view, account, j);
}
TER
removeTicketFromLedger(
Application&,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const&,
beast::Journal j)
{
return Transactor::ticketDelete(view, account, delIndex, j);
}
TER
removeDepositPreauthFromLedger(
Application& app,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return DepositPreauth::removeFromLedger(app, view, delIndex, j);
}
TER
removeNFTokenOfferFromLedger(
Application& app,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal)
{
if (!nft::deleteTokenOffer(view, sleDel))
return tefBAD_LEDGER;
return tesSUCCESS;
}
// Return nullptr if the LedgerEntryType represents an obligation that can't
// be deleted. Otherwise return the pointer to the function that can delete
// the non-obligation
DeleterFuncPtr
nonObligationDeleter(LedgerEntryType t)
{
switch (t)
{
case ltOFFER:
return offerDelete;
case ltSIGNER_LIST:
return removeSignersFromLedger;
case ltTICKET:
return removeTicketFromLedger;
case ltDEPOSIT_PREAUTH:
return removeDepositPreauthFromLedger;
case ltNFTOKEN_OFFER:
return removeNFTokenOfferFromLedger;
default:
return nullptr;
}
}
} // namespace
TER
DeleteAccount::preclaim(PreclaimContext const& ctx)
{
AccountID const account{ctx.tx[sfAccount]};
AccountID const dst{ctx.tx[sfDestination]};
auto sleDst = ctx.view.read(keylet::account(dst));
if (!sleDst)
return tecNO_DST;
if ((*sleDst)[sfFlags] & lsfRequireDestTag && !ctx.tx[~sfDestinationTag])
return tecDST_TAG_NEEDED;
// Check whether the destination account requires deposit authorization.
if (ctx.view.rules().enabled(featureDepositAuth) &&
(sleDst->getFlags() & lsfDepositAuth))
{
if (!ctx.view.exists(keylet::depositPreauth(dst, account)))
return tecNO_PERMISSION;
}
auto sleAccount = ctx.view.read(keylet::account(account));
assert(sleAccount);
if (!sleAccount)
return terNO_ACCOUNT;
if (ctx.view.rules().enabled(featureNonFungibleTokensV1))
{
// If an issuer has any issued NFTs resident in the ledger then it
// cannot be deleted.
if ((*sleAccount)[~sfMintedNFTokens] !=
(*sleAccount)[~sfBurnedNFTokens])
return tecHAS_OBLIGATIONS;
// If the account owns any NFTs it cannot be deleted.
Keylet const first = keylet::nftpage_min(account);
Keylet const last = keylet::nftpage_max(account);
auto const cp = ctx.view.read(Keylet(
ltNFTOKEN_PAGE,
ctx.view.succ(first.key, last.key.next()).value_or(last.key)));
if (cp)
return tecHAS_OBLIGATIONS;
}
// We don't allow an account to be deleted if its sequence number
// is within 256 of the current ledger. This prevents replay of old
// transactions if this account is resurrected after it is deleted.
//
// We look at the account's Sequence rather than the transaction's
// Sequence in preparation for Tickets.
constexpr std::uint32_t seqDelta{255};
if ((*sleAccount)[sfSequence] + seqDelta > ctx.view.seq())
return tecTOO_SOON;
// Verify that the account does not own any objects that would prevent
// the account from being deleted.
Keylet const ownerDirKeylet{keylet::ownerDir(account)};
if (dirIsEmpty(ctx.view, ownerDirKeylet))
return tesSUCCESS;
std::shared_ptr<SLE const> sleDirNode{};
unsigned int uDirEntry{0};
uint256 dirEntry{beast::zero};
// Account has no directory at all. This _should_ have been caught
// by the dirIsEmpty() check earlier, but it's okay to catch it here.
if (!cdirFirst(
ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
return tesSUCCESS;
std::int32_t deletableDirEntryCount{0};
do
{
// Make sure any directory node types that we find are the kind
// we can delete.
auto sleItem = ctx.view.read(keylet::child(dirEntry));
if (!sleItem)
{
// Directory node has an invalid index. Bail out.
JLOG(ctx.j.fatal())
<< "DeleteAccount: directory node in ledger " << ctx.view.seq()
<< " has index to object that is missing: "
<< to_string(dirEntry);
return tefBAD_LEDGER;
}
LedgerEntryType const nodeType{
safe_cast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
if (!nonObligationDeleter(nodeType))
return tecHAS_OBLIGATIONS;
// We found a deletable directory entry. Count it. If we find too
// many deletable directory entries then bail out.
if (++deletableDirEntryCount > maxDeletableDirEntries)
return tefTOO_BIG;
} while (cdirNext(
ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
return tesSUCCESS;
}
TER
DeleteAccount::doApply()
{
auto src = view().peek(keylet::account(account_));
assert(src);
auto dst = view().peek(keylet::account(ctx_.tx[sfDestination]));
assert(dst);
if (!src || !dst)
return tefBAD_LEDGER;
// Delete all of the entries in the account directory.
Keylet const ownerDirKeylet{keylet::ownerDir(account_)};
std::shared_ptr<SLE> sleDirNode{};
unsigned int uDirEntry{0};
uint256 dirEntry{beast::zero};
if (view().exists(ownerDirKeylet) &&
dirFirst(view(), ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry))
{
do
{
// Choose the right way to delete each directory node.
auto sleItem = view().peek(keylet::child(dirEntry));
if (!sleItem)
{
// Directory node has an invalid index. Bail out.
JLOG(j_.fatal())
<< "DeleteAccount: Directory node in ledger "
<< view().seq() << " has index to object that is missing: "
<< to_string(dirEntry);
return tefBAD_LEDGER;
}
LedgerEntryType const nodeType{safe_cast<LedgerEntryType>(
sleItem->getFieldU16(sfLedgerEntryType))};
if (auto deleter = nonObligationDeleter(nodeType))
{
TER const result{
deleter(ctx_.app, view(), account_, dirEntry, sleItem, j_)};
if (!isTesSuccess(result))
return result;
}
else
{
assert(!"Undeletable entry should be found in preclaim.");
JLOG(j_.error())
<< "DeleteAccount undeletable item not found in preclaim.";
return tecHAS_OBLIGATIONS;
}
// dirFirst() and dirNext() are like iterators with exposed
// internal state. We'll take advantage of that exposed state
// to solve a common C++ problem: iterator invalidation while
// deleting elements from a container.
//
// We have just deleted one directory entry, which means our
// "iterator state" is invalid.
//
// 1. During the process of getting an entry from the
// directory uDirEntry was incremented from 0 to 1.
//
// 2. We then deleted the entry at index 0, which means the
// entry that was at 1 has now moved to 0.
//
// 3. So we verify that uDirEntry is indeed 1. Then we jam it
// back to zero to "un-invalidate" the iterator.
assert(uDirEntry == 1);
if (uDirEntry != 1)
{
JLOG(j_.error())
<< "DeleteAccount iterator re-validation failed.";
return tefBAD_LEDGER;
}
uDirEntry = 0;
} while (dirNext(
view(), ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
}
// Transfer any XRP remaining after the fee is paid to the destination:
(*dst)[sfBalance] = (*dst)[sfBalance] + mSourceBalance;
(*src)[sfBalance] = (*src)[sfBalance] - mSourceBalance;
ctx_.deliver(mSourceBalance);
assert((*src)[sfBalance] == XRPAmount(0));
// If there's still an owner directory associated with the source account
// delete it.
if (view().exists(ownerDirKeylet) && !view().emptyDirDelete(ownerDirKeylet))
{
JLOG(j_.error()) << "DeleteAccount cannot delete root dir node of "
<< toBase58(account_);
return tecHAS_OBLIGATIONS;
}
// Re-arm the password change fee if we can and need to.
if (mSourceBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
dst->clearFlag(lsfPasswordSpent);
view().update(dst);
view().erase(src);
return tesSUCCESS;
}
} // namespace ripple