add InvariantCheck for sponsor count

This commit is contained in:
tequ
2025-09-18 18:49:53 +09:00
parent 2afe5707ab
commit a43ae9a3ce
4 changed files with 224 additions and 0 deletions

View File

@@ -112,6 +112,11 @@ class Invariants_test : public beast::unit_test::suite
{
terActual = ac.checkInvariants(terActual, fee);
BEAST_EXPECT(terExpect == terActual);
if (terExpect != terActual)
{
printf("terActual: %s\n", transHuman(terActual).c_str());
printf("terExpect: %s\n", transHuman(terExpect).c_str());
}
BEAST_EXPECT(
sink.messages().str().starts_with("Invariant failed:") ||
sink.messages().str().starts_with(
@@ -1604,6 +1609,69 @@ class Invariants_test : public beast::unit_test::suite
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
}
void
testSponsorship()
{
using namespace test::jtx;
using namespace std::string_literals;
testcase << "Sponsorship";
{
auto const expect_message =
"SponsoredOwnerCount does not equal "
"SponsoringOwnerCount delta.";
doInvariantCheck(
{{expect_message}},
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
auto const sle = ac.view().peek(keylet::account(A1.id()));
if (!sle)
return false;
sle->setFieldU32(sfSponsoredOwnerCount, 1);
ac.view().update(sle);
return true;
});
doInvariantCheck(
{{expect_message}},
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
auto const sle = ac.view().peek(keylet::account(A1.id()));
if (!sle)
return false;
sle->setFieldU32(sfSponsoringOwnerCount, 1);
ac.view().update(sle);
return true;
});
}
{
auto const expect_message =
"Invariant failed: Net delta of SponsoringAccountCount does "
"not match net delta of sfSponsorAccount presence.";
doInvariantCheck(
{{expect_message}},
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
auto const sle = ac.view().peek(keylet::account(A1.id()));
if (!sle)
return false;
sle->setFieldU32(sfSponsoringAccountCount, 1);
ac.view().update(sle);
return true;
});
doInvariantCheck(
{{expect_message}},
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
auto const sle = ac.view().peek(keylet::account(A1.id()));
if (!sle)
return false;
sle->setAccountID(sfSponsorAccount, A2.id());
ac.view().update(sle);
return true;
});
}
}
public:
void
run() override
@@ -1623,6 +1691,7 @@ public:
testNFTokenPageInvariants();
testPermissionedDomainInvariants();
testPermissionedDEX();
testSponsorship();
}
};

View File

@@ -453,6 +453,8 @@ DeleteAccount::doApply()
sponsorSle->setFieldU32(
sfSponsoringAccountCount, sponsoringAccountCount - 1);
view().update(sponsorSle);
(*src).makeFieldAbsent(sfSponsorAccount);
}
XRPL_ASSERT(

View File

@@ -2037,4 +2037,98 @@ ValidAMM::finalize(
return true;
}
// Add new sponsorship-related invariants implementations
void
SponsorshipOwnerCountsMatch::visitEntry(
bool,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
{
auto getSponsored =
[](std::shared_ptr<SLE const> const& sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoredOwnerCount);
return 0;
};
auto getSponsoring =
[](std::shared_ptr<SLE const> const& sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoringOwnerCount);
return 0;
};
std::int64_t const beforeSponsored = getSponsored(before);
std::int64_t const afterSponsored = getSponsored(after);
std::int64_t const beforeSponsoring = getSponsoring(before);
std::int64_t const afterSponsoring = getSponsoring(after);
deltaSponsoredOwnerCount_ += (afterSponsored - beforeSponsored);
deltaSponsoringOwnerCount_ += (afterSponsoring - beforeSponsoring);
}
bool
SponsorshipOwnerCountsMatch::finalize(
STTx const&,
TER const,
XRPAmount const,
ReadView const&,
beast::Journal const& j)
{
if (deltaSponsoredOwnerCount_ != deltaSponsoringOwnerCount_)
{
JLOG(j.fatal()) << "Invariant failed: SponsoredOwnerCount does not "
"equal SponsoringOwnerCount delta.";
return false;
}
return true;
}
void
SponsorshipAccountCountMatchesField::visitEntry(
bool,
std::shared_ptr<SLE const> const& before,
std::shared_ptr<SLE const> const& after)
{
auto getSponsoringAccountCount =
[](std::shared_ptr<SLE const> const& sle) -> std::uint32_t {
if (sle && sle->getType() == ltACCOUNT_ROOT)
return sle->getFieldU32(sfSponsoringAccountCount);
return 0;
};
auto hasSponsorField = [](std::shared_ptr<SLE const> const& sle) -> bool {
return sle && sle->getType() == ltACCOUNT_ROOT &&
sle->isFieldPresent(sfSponsorAccount);
};
std::int64_t const beforeCount = getSponsoringAccountCount(before);
std::int64_t const afterCount = getSponsoringAccountCount(after);
deltaSponsoringAccountCount_ += (afterCount - beforeCount);
int const beforePresent = hasSponsorField(before) ? 1 : 0;
int const afterPresent = hasSponsorField(after) ? 1 : 0;
deltaSponsorFieldPresence_ += (afterPresent - beforePresent);
}
bool
SponsorshipAccountCountMatchesField::finalize(
STTx const&,
TER const,
XRPAmount const,
ReadView const&,
beast::Journal const& j)
{
if (deltaSponsoringAccountCount_ != deltaSponsorFieldPresence_)
{
JLOG(j.fatal())
<< "Invariant failed: Net delta of SponsoringAccountCount does not "
"match net delta of sfSponsorAccount presence.";
return false;
}
return true;
}
} // namespace ripple

View File

@@ -531,6 +531,63 @@ public:
beast::Journal const&);
};
/**
* @brief Invariant: Sponsored owner counts are balanced.
*
* The following check is made for every transaction:
* - The sum of all per-account deltas of `sfSponsoredOwnerCount` equals
* the sum of all per-account deltas of `sfSponsoringOwnerCount`.
*/
class SponsorshipOwnerCountsMatch
{
std::int64_t deltaSponsoredOwnerCount_ = 0;
std::int64_t deltaSponsoringOwnerCount_ = 0;
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: Sponsoring account relationships tracked consistently.
*
* The following check is made for every transaction:
* - The net delta of `sfSponsoringAccountCount` across all accounts equals
* the net delta of the count of ltACCOUNT_ROOT entries having
* `sfSponsorAccount` present (presence transitions only: add/remove).
*/
class SponsorshipAccountCountMatchesField
{
std::int64_t deltaSponsoringAccountCount_ = 0;
std::int64_t deltaSponsorFieldPresence_ = 0;
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: Token holder's trustline balance cannot be negative after
* Clawback.
@@ -716,6 +773,8 @@ using InvariantChecks = std::tuple<
NoXRPTrustLines,
NoDeepFreezeTrustLinesWithoutFreeze,
TransfersNotFrozen,
SponsorshipOwnerCountsMatch,
SponsorshipAccountCountMatchesField,
NoBadOffers,
NoZeroEscrow,
ValidNewAccountRoot,