mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Invariant: prevent a deleted account from leaving (most) artifacts on the ledger. (#4663)
* Add feature / amendment "InvariantsV1_1" * Adds invariant AccountRootsDeletedClean: * Checks that a deleted account doesn't leave any directly accessible artifacts behind. * Always tests, but only changes the transaction result if featureInvariantsV1_1 is enabled. * Unit tests. * Resolves #4638 * [FOLD] Review feedback from @gregtatcam: * Fix unused variable warning * Improve Invariant test const correctness * [FOLD] Review feedback from @mvadari: * Centralize the account keylet function list, and some optimization * [FOLD] Some structured binding doesn't work in clang * [FOLD] Review feedback 2 from @mvadari: * Clean up and clarify some comments. * [FOLD] Change InvariantsV1_1 to unsupported * Will allow multiple PRs to be merged over time using the same amendment. * fixup! [FOLD] Change InvariantsV1_1 to unsupported * [FOLD] Update and clarify some comments. No code changes. * Move CMake directory * Rearrange sources * Rewrite includes * Recompute loops * Fix merge issue and formatting --------- Co-authored-by: Pretty Printer <cpp@ripple.com>
This commit is contained in:
@@ -277,9 +277,9 @@ FeeVoteImpl::doVoting(
|
||||
}
|
||||
|
||||
// choose our positions
|
||||
// TODO: Use structured binding once LLVM issue
|
||||
// https://github.com/llvm/llvm-project/issues/48582
|
||||
// is fixed.
|
||||
// TODO: Use structured binding once LLVM 16 is the minimum supported
|
||||
// version. See also: https://github.com/llvm/llvm-project/issues/48582
|
||||
// https://github.com/llvm/llvm-project/commit/127bf44385424891eb04cff8e52d3f157fc2cb7c
|
||||
auto const baseFee = baseFeeVote.getVotes();
|
||||
auto const baseReserve = baseReserveVote.getVotes();
|
||||
auto const incReserve = incReserveVote.getVotes();
|
||||
|
||||
@@ -358,6 +358,91 @@ AccountRootsNotDeleted::finalize(
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
AccountRootsDeletedClean::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const&)
|
||||
{
|
||||
if (isDelete && before && before->getType() == ltACCOUNT_ROOT)
|
||||
accountsDeleted_.emplace_back(before);
|
||||
}
|
||||
|
||||
bool
|
||||
AccountRootsDeletedClean::finalize(
|
||||
STTx const& tx,
|
||||
TER const result,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
// Always check for objects in the ledger, but to prevent differing
|
||||
// transaction processing results, however unlikely, only fail if the
|
||||
// feature is enabled. Enabled, or not, though, a fatal-level message will
|
||||
// be logged
|
||||
bool const enforce = view.rules().enabled(featureInvariantsV1_1);
|
||||
|
||||
auto const objectExists = [&view, enforce, &j](auto const& keylet) {
|
||||
if (auto const sle = view.read(keylet))
|
||||
{
|
||||
// Finding the object is bad
|
||||
auto const typeName = [&sle]() {
|
||||
auto item =
|
||||
LedgerFormats::getInstance().findByType(sle->getType());
|
||||
|
||||
if (item != nullptr)
|
||||
return item->getName();
|
||||
return std::to_string(sle->getType());
|
||||
}();
|
||||
|
||||
JLOG(j.fatal())
|
||||
<< "Invariant failed: account deletion left behind a "
|
||||
<< typeName << " object";
|
||||
(void)enforce;
|
||||
assert(enforce);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
for (auto const& accountSLE : accountsDeleted_)
|
||||
{
|
||||
auto const accountID = accountSLE->getAccountID(sfAccount);
|
||||
// Simple types
|
||||
for (auto const& [keyletfunc, _, __] : directAccountKeylets)
|
||||
{
|
||||
if (objectExists(std::invoke(keyletfunc, accountID)) && enforce)
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
// NFT pages. ntfpage_min and nftpage_max were already explicitly
|
||||
// checked above as entries in directAccountKeylets. This uses
|
||||
// view.succ() to check for any NFT pages in between the two
|
||||
// endpoints.
|
||||
Keylet const first = keylet::nftpage_min(accountID);
|
||||
Keylet const last = keylet::nftpage_max(accountID);
|
||||
|
||||
std::optional<uint256> key = view.succ(first.key, last.key.next());
|
||||
|
||||
// current page
|
||||
if (key && objectExists(Keylet{ltNFTOKEN_PAGE, *key}) && enforce)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keys directly stored in the AccountRoot object
|
||||
if (auto const ammKey = accountSLE->at(~sfAMMID))
|
||||
{
|
||||
if (objectExists(keylet::amm(*ammKey)) && enforce)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
LedgerEntryTypesMatch::visitEntry(
|
||||
bool,
|
||||
|
||||
@@ -164,6 +164,36 @@ public:
|
||||
beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: a deleted account must not have any objects left
|
||||
*
|
||||
* We iterate all deleted account roots, and ensure that there are no
|
||||
* objects left that are directly accessible with that account's ID.
|
||||
*
|
||||
* There should only be one deleted account, but that's checked by
|
||||
* AccountRootsNotDeleted. This invariant will handle multiple deleted account
|
||||
* roots without a problem.
|
||||
*/
|
||||
class AccountRootsDeletedClean
|
||||
{
|
||||
std::vector<std::shared_ptr<SLE const>> accountsDeleted_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(
|
||||
bool,
|
||||
std::shared_ptr<SLE const> const&,
|
||||
std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(
|
||||
STTx const&,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const&,
|
||||
beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: An account XRP balance must be in XRP and take a value
|
||||
* between 0 and INITIAL_XRP drops, inclusive.
|
||||
@@ -423,6 +453,7 @@ public:
|
||||
using InvariantChecks = std::tuple<
|
||||
TransactionFeeCheck,
|
||||
AccountRootsNotDeleted,
|
||||
AccountRootsDeletedClean,
|
||||
LedgerEntryTypesMatch,
|
||||
XRPBalanceChecks,
|
||||
XRPNotCreated,
|
||||
|
||||
@@ -191,6 +191,7 @@ getPageForToken(
|
||||
: carr[0].getFieldH256(sfNFTokenID);
|
||||
|
||||
auto np = std::make_shared<SLE>(keylet::nftpage(base, tokenIDForNewPage));
|
||||
assert(np->key() > base.key);
|
||||
np->setFieldArray(sfNFTokens, narr);
|
||||
np->setFieldH256(sfNextPageMin, cp->key());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user