Files
rippled/src/libxrpl/tx/transactors/account/AccountDelete.cpp

441 lines
13 KiB
C++

#include <xrpl/tx/transactors/account/AccountDelete.h>
#include <xrpl/basics/Log.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/safe_cast.h>
#include <xrpl/beast/utility/Zero.h>
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/core/ServiceRegistry.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/ledger/View.h>
#include <xrpl/ledger/helpers/CredentialHelpers.h>
#include <xrpl/ledger/helpers/DirectoryHelpers.h>
#include <xrpl/ledger/helpers/NFTokenHelpers.h>
#include <xrpl/ledger/helpers/OfferHelpers.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Keylet.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STLedgerEntry.h>
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/tx/Transactor.h>
#include <xrpl/tx/transactors/account/SignerListSet.h>
#include <xrpl/tx/transactors/delegate/DelegateSet.h>
#include <xrpl/tx/transactors/did/DIDDelete.h>
#include <xrpl/tx/transactors/oracle/OracleDelete.h>
#include <xrpl/tx/transactors/payment/DepositPreauth.h>
#include <cstdint>
#include <memory>
#include <utility>
namespace xrpl {
bool
AccountDelete::checkExtraFeatures(PreflightContext const& ctx)
{
return !ctx.tx.isFieldPresent(sfCredentialIDs) || ctx.rules.enabled(featureCredentials);
}
NotTEC
AccountDelete::preflight(PreflightContext const& ctx)
{
if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
{
// An account cannot be deleted and give itself the resulting XRP.
return temDST_IS_SRC;
}
if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err))
return err;
return tesSUCCESS;
}
XRPAmount
AccountDelete::calculateBaseFee(ReadView const& view, STTx const& tx)
{
// The fee required for AccountDelete is one owner reserve.
return calculateOwnerReserveFee(view, tx);
}
namespace {
// Define a function pointer type that can be used to delete ledger node types.
using DeleterFuncPtr = TER (*)(
ServiceRegistry& registry,
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(
ServiceRegistry&,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return offerDelete(view, sleDel, j);
}
TER
removeSignersFromLedger(
ServiceRegistry& registry,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return SignerListSet::removeFromLedger(registry, view, account, j);
}
TER
removeTicketFromLedger(
ServiceRegistry&,
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(
ServiceRegistry&,
ApplyView& view,
AccountID const&,
uint256 const& delIndex,
std::shared_ptr<SLE> const&,
beast::Journal j)
{
return DepositPreauth::removeFromLedger(view, delIndex, j);
}
TER
removeNFTokenOfferFromLedger(
ServiceRegistry&,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal)
{
if (!nft::deleteTokenOffer(view, sleDel))
return tefBAD_LEDGER; // LCOV_EXCL_LINE
return tesSUCCESS;
}
TER
removeDIDFromLedger(
ServiceRegistry&,
ApplyView& view,
AccountID const& account,
uint256 const& delIndex,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return DIDDelete::deleteSLE(view, sleDel, account, j);
}
TER
removeOracleFromLedger(
ServiceRegistry&,
ApplyView& view,
AccountID const& account,
uint256 const&,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return OracleDelete::deleteOracle(view, sleDel, account, j);
}
TER
removeCredentialFromLedger(
ServiceRegistry&,
ApplyView& view,
AccountID const&,
uint256 const&,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return credentials::deleteSLE(view, sleDel, j);
}
TER
removeDelegateFromLedger(
ServiceRegistry&,
ApplyView& view,
AccountID const&,
uint256 const&,
std::shared_ptr<SLE> const& sleDel,
beast::Journal j)
{
return DelegateSet::deleteDelegate(view, sleDel, j);
}
// 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;
case ltDID:
return removeDIDFromLedger;
case ltORACLE:
return removeOracleFromLedger;
case ltCREDENTIAL:
return removeCredentialFromLedger;
case ltDELEGATE:
return removeDelegateFromLedger;
default:
return nullptr;
}
}
} // namespace
TER
AccountDelete::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) != 0u) && !ctx.tx[~sfDestinationTag])
return tecDST_TAG_NEEDED;
// If credentials are provided - check them anyway
if (auto const err = credentials::valid(ctx.tx, ctx.view, account, ctx.j); !isTesSuccess(err))
return err;
// if credentials then postpone auth check to doApply, to check for expired
// credentials
if (!ctx.tx.isFieldPresent(sfCredentialIDs))
{
// Check whether the destination account requires deposit authorization.
if ((sleDst->getFlags() & lsfDepositAuth) != 0u)
{
if (!ctx.view.exists(keylet::depositPreauth(dst, account)))
return tecNO_PERMISSION;
}
}
auto sleAccount = ctx.view.read(keylet::account(account));
XRPL_ASSERT(sleAccount, "xrpl::AccountDelete::preclaim : non-null account");
if (!sleAccount)
return terNO_ACCOUNT;
// 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::nftpageMin(account);
Keylet const last = keylet::nftpageMax(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 kSEQ_DELTA{255};
if ((*sleAccount)[sfSequence] + kSEQ_DELTA > ctx.view.seq())
return tecTOO_SOON;
// We don't allow an account to be deleted if
// <FirstNFTokenSequence + MintedNFTokens> is within 256 of the
// current ledger. This is to prevent having duplicate NFTokenIDs after
// account re-creation.
//
// Without this restriction, duplicate NFTokenIDs can be reproduced when
// authorized minting is involved. Because when the minter mints a NFToken,
// the issuer's sequence does not change. So when the issuer re-creates
// their account and mints a NFToken, it is possible that the
// NFTokenSequence of this NFToken is the same as the one that the
// authorized minter minted in a previous ledger.
if ((*sleAccount)[~sfFirstNFTokenSequence].value_or(0) +
(*sleAccount)[~sfMintedNFTokens].value_or(0) + kSEQ_DELTA >
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::kZERO};
// 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::uint32_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.
// LCOV_EXCL_START
JLOG(ctx.j.fatal()) << "AccountDelete: directory node in ledger " << ctx.view.seq()
<< " has index to object that is missing: " << to_string(dirEntry);
return tefBAD_LEDGER;
// LCOV_EXCL_STOP
}
LedgerEntryType const nodeType{safeCast<LedgerEntryType>((*sleItem)[sfLedgerEntryType])};
if (nonObligationDeleter(nodeType) == nullptr)
return tecHAS_OBLIGATIONS;
// We found a deletable directory entry. Count it. If we find too
// many deletable directory entries then bail out.
if (++deletableDirEntryCount > kMAX_DELETABLE_DIR_ENTRIES)
return tefTOO_BIG;
} while (cdirNext(ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry));
return tesSUCCESS;
}
TER
AccountDelete::doApply()
{
auto src = view().peek(keylet::account(account_));
XRPL_ASSERT(src, "xrpl::AccountDelete::doApply : non-null source account");
auto const dstID = ctx_.tx[sfDestination];
auto dst = view().peek(keylet::account(dstID));
XRPL_ASSERT(dst, "xrpl::AccountDelete::doApply : non-null destination account");
if (!src || !dst)
return tefBAD_LEDGER; // LCOV_EXCL_LINE
if (ctx_.tx.isFieldPresent(sfCredentialIDs))
{
if (auto err =
verifyDepositPreauth(ctx_.tx, ctx_.view(), account_, dstID, dst, ctx_.journal);
!isTesSuccess(err))
return err;
}
Keylet const ownerDirKeylet{keylet::ownerDir(account_)};
auto const ter = cleanupOnAccountDelete(
view(),
ownerDirKeylet,
[&](LedgerEntryType nodeType,
uint256 const& dirEntry,
std::shared_ptr<SLE>& sleItem) -> std::pair<TER, SkipEntry> {
if (auto deleter = nonObligationDeleter(nodeType))
{
TER const result{deleter(ctx_.registry, view(), account_, dirEntry, sleItem, j_)};
return {result, SkipEntry::No};
}
// LCOV_EXCL_START
UNREACHABLE(
"xrpl::AccountDelete::doApply : undeletable item not found "
"in preclaim");
JLOG(j_.error()) << "AccountDelete undeletable item not "
"found in preclaim.";
return {tecHAS_OBLIGATIONS, SkipEntry::No};
// LCOV_EXCL_STOP
},
ctx_.journal);
if (!isTesSuccess(ter))
return ter;
// Transfer any XRP remaining after the fee is paid to the destination:
auto const remainingBalance = src->getFieldAmount(sfBalance).xrp();
(*dst)[sfBalance] = (*dst)[sfBalance] + remainingBalance;
(*src)[sfBalance] = (*src)[sfBalance] - remainingBalance;
ctx_.deliver(remainingBalance);
XRPL_ASSERT(
(*src)[sfBalance] == XRPAmount(0), "xrpl::AccountDelete::doApply : source balance is zero");
// If there's still an owner directory associated with the source account
// delete it.
if (view().exists(ownerDirKeylet) && !view().emptyDirDelete(ownerDirKeylet))
{
JLOG(j_.error()) << "AccountDelete cannot delete root dir node of " << toBase58(account_);
return tecHAS_OBLIGATIONS;
}
// Re-arm the password change fee if we can and need to.
if (remainingBalance > XRPAmount(0) && dst->isFlag(lsfPasswordSpent))
dst->clearFlag(lsfPasswordSpent);
view().update(dst);
view().erase(src);
return tesSUCCESS;
}
void
AccountDelete::visitInvariantEntry(
bool,
std::shared_ptr<SLE const> const&,
std::shared_ptr<SLE const> const&)
{
// No transaction-specific invariants yet (future work).
}
bool
AccountDelete::finalizeInvariants(
STTx const&,
TER,
XRPAmount,
ReadView const&,
beast::Journal const&)
{
// No transaction-specific invariants yet (future work).
return true;
}
} // namespace xrpl