mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-23 15:20:54 +00:00
perf: Optimize MPT freeze checks to reduce redundant state reads (#7411)
Co-authored-by: Chenna Keshava B S <21219765+ckeshava@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
ca6121c5b3
commit
1b226c8b2e
@@ -61,12 +61,10 @@ hasExpired(
|
||||
: view.parentCloseTime() > boundary;
|
||||
}
|
||||
|
||||
bool
|
||||
isVaultPseudoAccountFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptShare,
|
||||
std::uint8_t depth)
|
||||
namespace {
|
||||
|
||||
std::optional<bool>
|
||||
checkVaultPseudoAccountFrozenPreconditions(ReadView const& view, std::uint8_t depth)
|
||||
{
|
||||
if (!view.rules().enabled(featureSingleAssetVault))
|
||||
return false;
|
||||
@@ -74,26 +72,37 @@ isVaultPseudoAccountFrozen(
|
||||
if (depth >= kMaxAssetCheckDepth)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("xrpl::View::isVaultPseudoAccountFrozen : reached asset check depth");
|
||||
UNREACHABLE(
|
||||
"xrpl::View::checkVaultPseudoAccountFrozenPreconditions : reached asset check depth");
|
||||
return true;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const mptIssuance = view.read(keylet::mptokenIssuance(mptShare.getMptID()));
|
||||
if (mptIssuance == nullptr)
|
||||
return false; // zero MPToken won't block deletion of MPTokenIssuance
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto const issuer = mptIssuance->getAccountID(sfIssuer);
|
||||
bool
|
||||
isVaultPseudoAccountFrozenForIssuance(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
SLE const& issuanceSle,
|
||||
std::uint8_t depth)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
issuanceSle.getType() == ltMPTOKEN_ISSUANCE,
|
||||
"xrpl::isVaultPseudoAccountFrozenForIssuance : MPTokenIssuance SLE");
|
||||
|
||||
auto const issuer = issuanceSle.getAccountID(sfIssuer);
|
||||
|
||||
// Post-fixCleanup3_2_0: vault shares carry sfReferenceHolding pointing
|
||||
// to the vault pseudo's MPToken or RippleState for the underlying.
|
||||
// Read it to derive the underlying asset and recurse, skipping the
|
||||
// issuer-account-then-vault chain. Pre-amendment shares (no field)
|
||||
// fall back to the chain lookup below.
|
||||
if (mptIssuance->isFieldPresent(sfReferenceHolding))
|
||||
if (issuanceSle.isFieldPresent(sfReferenceHolding))
|
||||
{
|
||||
auto const sleHolding =
|
||||
view.read(keylet::unchecked(mptIssuance->getFieldH256(sfReferenceHolding)));
|
||||
view.read(keylet::unchecked(issuanceSle.getFieldH256(sfReferenceHolding)));
|
||||
if (!sleHolding)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
@@ -102,7 +111,7 @@ isVaultPseudoAccountFrozen(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
return isAnyFrozen(
|
||||
view, {issuer, account}, assetOfHolding(*mptIssuance, *sleHolding), depth + 1);
|
||||
view, {issuer, account}, assetOfHolding(issuanceSle, *sleHolding), depth + 1);
|
||||
}
|
||||
|
||||
auto const mptIssuer = view.read(keylet::account(issuer));
|
||||
@@ -128,6 +137,38 @@ isVaultPseudoAccountFrozen(
|
||||
return isAnyFrozen(view, {issuer, account}, vault->at(sfAsset), depth + 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool
|
||||
isVaultPseudoAccountFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
SLE const& issuanceSle,
|
||||
std::uint8_t depth)
|
||||
{
|
||||
if (auto const result = checkVaultPseudoAccountFrozenPreconditions(view, depth))
|
||||
return *result;
|
||||
|
||||
return isVaultPseudoAccountFrozenForIssuance(view, account, issuanceSle, depth);
|
||||
}
|
||||
|
||||
bool
|
||||
isVaultPseudoAccountFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptShare,
|
||||
std::uint8_t depth)
|
||||
{
|
||||
if (auto const result = checkVaultPseudoAccountFrozenPreconditions(view, depth))
|
||||
return *result;
|
||||
|
||||
auto const issuanceSle = view.read(keylet::mptokenIssuance(mptShare.getMptID()));
|
||||
if (issuanceSle == nullptr)
|
||||
return false; // zero MPToken won't block deletion of MPTokenIssuance
|
||||
|
||||
return isVaultPseudoAccountFrozenForIssuance(view, account, *issuanceSle, depth);
|
||||
}
|
||||
|
||||
bool
|
||||
isLPTokenFrozen(
|
||||
ReadView const& view,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
@@ -633,7 +634,7 @@ ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const
|
||||
return asset.visit(
|
||||
[&](MPTIssue const& issue) {
|
||||
if (auto const sle = view.read(keylet::mptoken(issue, ammAccountID));
|
||||
sle && !isFrozen(view, ammAccountID, issue))
|
||||
sle && !isFrozen(view, ammAccountID, *sle))
|
||||
return STAmount{issue, (*sle)[sfMPTAmount]};
|
||||
return STAmount{asset};
|
||||
},
|
||||
|
||||
@@ -42,18 +42,35 @@ bool
|
||||
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
|
||||
{
|
||||
if (auto const sle = view.read(keylet::mptokenIssuance(mptIssue.getMptID())))
|
||||
return sle->isFlag(lsfMPTLocked);
|
||||
return isGlobalFrozen(*sle);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
isGlobalFrozen(SLE const& issuanceSle)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
issuanceSle.getType() == ltMPTOKEN_ISSUANCE, "xrpl::isGlobalFrozen : MPTokenIssuance SLE");
|
||||
|
||||
return issuanceSle.isFlag(lsfMPTLocked);
|
||||
}
|
||||
|
||||
bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
|
||||
{
|
||||
if (auto const sle = view.read(keylet::mptoken(mptIssue.getMptID(), account)))
|
||||
return sle->isFlag(lsfMPTLocked);
|
||||
return isIndividualFrozen(*sle);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
isIndividualFrozen(SLE const& mptSle)
|
||||
{
|
||||
XRPL_ASSERT(mptSle.getType() == ltMPTOKEN, "xrpl::isIndividualFrozen : MPToken SLE");
|
||||
|
||||
return mptSle.isFlag(lsfMPTLocked);
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(
|
||||
ReadView const& view,
|
||||
@@ -65,6 +82,34 @@ isFrozen(
|
||||
isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, SLE const& sle, std::uint8_t depth)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
sle.getType() == ltMPTOKEN || sle.getType() == ltMPTOKEN_ISSUANCE,
|
||||
"xrpl::isFrozen : MPToken or MPTokenIssuance SLE");
|
||||
|
||||
if (sle.getType() == ltMPTOKEN)
|
||||
{
|
||||
XRPL_ASSERT(sle[sfAccount] == account, "xrpl::isFrozen : valid MPToken holder");
|
||||
|
||||
MPTID const mptID = sle[sfMPTokenIssuanceID];
|
||||
auto const issuanceSle = view.read(keylet::mptokenIssuance(mptID));
|
||||
|
||||
if ((issuanceSle && isGlobalFrozen(*issuanceSle)) || isIndividualFrozen(sle))
|
||||
return true;
|
||||
|
||||
if (issuanceSle)
|
||||
return isVaultPseudoAccountFrozen(view, account, *issuanceSle, depth);
|
||||
|
||||
return isVaultPseudoAccountFrozen(view, account, MPTIssue{mptID}, depth);
|
||||
}
|
||||
|
||||
MPTIssue const mptIssue{sle[sfSequence], sle[sfIssuer]};
|
||||
return isGlobalFrozen(sle) || isIndividualFrozen(view, account, mptIssue) ||
|
||||
isVaultPseudoAccountFrozen(view, account, sle, depth);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
@@ -72,7 +117,8 @@ isAnyFrozen(
|
||||
MPTIssue const& mptIssue,
|
||||
std::uint8_t depth)
|
||||
{
|
||||
if (isGlobalFrozen(view, mptIssue))
|
||||
auto const issuanceSle = view.read(keylet::mptokenIssuance(mptIssue.getMptID()));
|
||||
if (issuanceSle && isGlobalFrozen(*issuanceSle))
|
||||
return true;
|
||||
|
||||
for (auto const& account : accounts)
|
||||
@@ -81,9 +127,15 @@ isAnyFrozen(
|
||||
return true;
|
||||
}
|
||||
|
||||
return std::ranges::any_of(accounts, [&](auto const& account) {
|
||||
return isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
|
||||
});
|
||||
// Pass the issuance SLE when we have it to avoid re-reading it per account;
|
||||
// otherwise defer to the MPTIssue overload, which handles a missing issuance.
|
||||
auto const anyVaultFrozen = [&](auto const& shareOrIssuance) {
|
||||
return std::ranges::any_of(accounts, [&](auto const& account) {
|
||||
return isVaultPseudoAccountFrozen(view, account, shareOrIssuance, depth);
|
||||
});
|
||||
};
|
||||
|
||||
return issuanceSle ? anyVaultFrozen(*issuanceSle) : anyVaultFrozen(mptIssue);
|
||||
}
|
||||
|
||||
Rate
|
||||
|
||||
@@ -439,7 +439,7 @@ accountHolds(
|
||||
auto const sleMpt = view.read(keylet::mptoken(mptIssue.getMptID(), account));
|
||||
|
||||
if (!sleMpt ||
|
||||
(zeroIfFrozen == FreezeHandling::ZeroIfFrozen && isFrozen(view, account, mptIssue)))
|
||||
(zeroIfFrozen == FreezeHandling::ZeroIfFrozen && isFrozen(view, account, *sleMpt)))
|
||||
{
|
||||
amount.clear(mptIssue);
|
||||
}
|
||||
|
||||
@@ -900,7 +900,7 @@ ValidMPTTransfer::finalize(
|
||||
// Check once: if any involved account is frozen, the whole issuance transfer is
|
||||
// considered frozen. Only need to check for frozen if there is a transfer of funds.
|
||||
if (!invalidTransfer &&
|
||||
(isFrozen(view, account, MPTIssue{mptID}) ||
|
||||
(isFrozen(view, account, *sleIssuance) ||
|
||||
!isAuthorized(view, mptID, account, reqAuth)))
|
||||
{
|
||||
invalidTransfer = true;
|
||||
|
||||
@@ -304,11 +304,11 @@ escrowCreatePreclaimHelper<MPTIssue>(
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecLOCKED
|
||||
if (isFrozen(ctx.view, account, mptIssue))
|
||||
if (isFrozen(ctx.view, account, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
// If the issuer has frozen the destination, return tecLOCKED
|
||||
if (isFrozen(ctx.view, dest, mptIssue))
|
||||
if (isFrozen(ctx.view, dest, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
// If the mpt cannot be transferred, return tecNO_AUTH
|
||||
|
||||
@@ -186,7 +186,7 @@ escrowFinishPreclaimHelper<MPTIssue>(
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the destination, return tecLOCKED
|
||||
if (isFrozen(ctx.view, dest, mptIssue))
|
||||
if (isFrozen(ctx.view, dest, *sleIssuance))
|
||||
return tecLOCKED;
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -7421,6 +7423,57 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDanglingAMMMPTokenFreezeCheck()
|
||||
{
|
||||
testcase("Dangling AMM MPToken freeze check");
|
||||
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{testableAmendments()};
|
||||
|
||||
Env env(*this, all);
|
||||
|
||||
env.fund(XRP(1'000), gw_, alice_);
|
||||
MPTTester usd({.env = env, .issuer = gw_});
|
||||
MPTTester const btc({.env = env, .issuer = gw_});
|
||||
|
||||
AMM amm(env, gw_, usd(10'000), btc(10'000));
|
||||
for (auto i = 0; i < kMaxDeletableAmmTrustLines + 10; ++i)
|
||||
{
|
||||
Account const a{std::to_string(i)};
|
||||
env.fund(XRP(1'000), a);
|
||||
env(trust(a, STAmount{amm.lptIssue(), 10'000}));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// With too many LP-token trust lines to delete in one pass, the AMM
|
||||
// remains in an empty state with zero-balance MPToken objects.
|
||||
amm.withdrawAll(gw_);
|
||||
BEAST_EXPECT(amm.ammExists());
|
||||
BEAST_EXPECT(amm.expectBalances(usd(0), btc(0), IOUAmount{0}));
|
||||
|
||||
auto const ammToken = env.le(keylet::mptoken(usd.issuanceID(), amm.ammAccount()));
|
||||
if (!BEAST_EXPECT(ammToken))
|
||||
return;
|
||||
BEAST_EXPECT((*ammToken)[sfMPTAmount] == 0);
|
||||
|
||||
usd.destroy();
|
||||
BEAST_EXPECT(env.le(keylet::mptokenIssuance(usd.issuanceID())) == nullptr);
|
||||
BEAST_EXPECT(!isFrozen(*env.current(), amm.ammAccount(), *ammToken));
|
||||
// A Payment cannot cross this empty AMM because BookStep skips AMMs
|
||||
// with zero LPTokenBalance. Probe the same ZeroIfFrozen balance read
|
||||
// used by AMM accounting.
|
||||
auto const balance = accountHolds(
|
||||
*env.current(),
|
||||
amm.ammAccount(),
|
||||
MPTIssue{usd.issuanceID()},
|
||||
FreezeHandling::ZeroIfFrozen,
|
||||
AuthHandling::IgnoreAuth,
|
||||
env.journal);
|
||||
|
||||
BEAST_EXPECT(balance == usd(0));
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -7461,6 +7514,7 @@ private:
|
||||
testDepositIntegralOverflowMPT(all);
|
||||
testDepositIntegralOverflowMPT(all - fixCleanup3_4_0);
|
||||
testWithdrawIntegralNoOverflowMPT();
|
||||
testDanglingAMMMPTokenFreezeCheck();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user