diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 9af5c551b5..f67c65df81 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -101,7 +101,8 @@ class Invariants_test : public beast::unit_test::suite if (messages.find(m) == std::string::npos) { // uncomment if you want to log the invariant failure - // std::cerr << " --> " << m << std::endl; + // std::cerr << " expected --> " << m << std::endl; + // std::cerr << " actual --> " << messages << std::endl; fail(); } } @@ -3516,6 +3517,26 @@ class Invariants_test : public beast::unit_test::suite }); } + { + auto const expect_message = "OwnerCount must be greater than or equal to SponsoredOwnerCount."; + + 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(sfOwnerCount, 0); + sle->setFieldU32(sfSponsoredOwnerCount, 1); + ac.view().update(sle); + + auto const sle2 = ac.view().peek(keylet::account(A2.id())); + if (!sle2) + return false; + sle2->setFieldU32(sfSponsoringOwnerCount, 1); + ac.view().update(sle2); + return true; + }); + } + { auto const expect_message = "Invariant failed: Net delta of SponsoringAccountCount does " diff --git a/src/xrpld/app/tx/detail/InvariantCheck.cpp b/src/xrpld/app/tx/detail/InvariantCheck.cpp index 86b8c5ede6..c3966fa517 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.cpp +++ b/src/xrpld/app/tx/detail/InvariantCheck.cpp @@ -3287,6 +3287,17 @@ SponsorshipOwnerCountsMatch::visitEntry( return 0; }; + auto getOwnerCount = [](std::shared_ptr const& sle) -> std::uint32_t { + if (sle && sle->getType() == ltACCOUNT_ROOT) + return sle->getFieldU32(sfOwnerCount); + return 0; + }; + auto getSponsoredOwnerCount = [](std::shared_ptr const& sle) -> std::uint32_t { + if (sle && sle->getType() == ltACCOUNT_ROOT) + return sle->getFieldU32(sfSponsoredOwnerCount); + return 0; + }; + std::int64_t const beforeSponsored = getSponsored(before); std::int64_t const afterSponsored = getSponsored(after); std::int64_t const beforeSponsoring = getSponsoring(before); @@ -3294,6 +3305,9 @@ SponsorshipOwnerCountsMatch::visitEntry( deltaSponsoredOwnerCount_ += (afterSponsored - beforeSponsored); deltaSponsoringOwnerCount_ += (afterSponsoring - beforeSponsoring); + + if (getOwnerCount(after) < getSponsoredOwnerCount(after)) + invalidOwnerCountLessThanSponsoredOwnerCount_ += 1; } bool @@ -3306,6 +3320,12 @@ SponsorshipOwnerCountsMatch::finalize(STTx const&, TER const, XRPAmount const, R return false; } + if (invalidOwnerCountLessThanSponsoredOwnerCount_ > 0) + { + JLOG(j.fatal()) << "Invariant failed: OwnerCount must be greater than or equal to SponsoredOwnerCount."; + return false; + } + return true; } diff --git a/src/xrpld/app/tx/detail/InvariantCheck.h b/src/xrpld/app/tx/detail/InvariantCheck.h index b4ccd6a5f4..45d18581fd 100644 --- a/src/xrpld/app/tx/detail/InvariantCheck.h +++ b/src/xrpld/app/tx/detail/InvariantCheck.h @@ -401,11 +401,13 @@ public: * 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`. + * - Account OwnerCount must be greater than or equal to SponsoredOwnerCount. */ class SponsorshipOwnerCountsMatch { std::int64_t deltaSponsoredOwnerCount_ = 0; std::int64_t deltaSponsoringOwnerCount_ = 0; + std::uint64_t invalidOwnerCountLessThanSponsoredOwnerCount_ = 0; public: void