diff --git a/include/xrpl/tx/invariants/InvariantCheck.h b/include/xrpl/tx/invariants/InvariantCheck.h index 6438d7e5bf..4523605f83 100644 --- a/include/xrpl/tx/invariants/InvariantCheck.h +++ b/include/xrpl/tx/invariants/InvariantCheck.h @@ -399,7 +399,7 @@ public: finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&) const; private: - SLE::const_pointer sle_; + std::vector sles_; }; // additional invariant checks can be declared above and then added to this // tuple diff --git a/src/libxrpl/tx/invariants/InvariantCheck.cpp b/src/libxrpl/tx/invariants/InvariantCheck.cpp index 94d5927603..1eb09ab373 100644 --- a/src/libxrpl/tx/invariants/InvariantCheck.cpp +++ b/src/libxrpl/tx/invariants/InvariantCheck.cpp @@ -1078,14 +1078,20 @@ ObjectHasPseudoAccount::visitEntry(bool isDelete, SLE::const_ref before, SLE::co // After should never be null when isDelete = false, if it is, something went horribly wrong if (!after) - return; + { + XRPL_ASSERT( + after, + "xrpl::ObjectHasPseudoAccount::visitEntry : modified ledger entry missing after state"); + return; // LCOV_EXCL_LINE + } switch (after->getType()) { case ltAMM: case ltVAULT: case ltLOAN_BROKER: - sle_ = after; + sles_.push_back(after); + break; default: return; } @@ -1102,28 +1108,33 @@ ObjectHasPseudoAccount::finalize( if (!view.rules().enabled(fixCleanup3_3_0)) return true; - if (!sle_) + if (sles_.empty()) return true; - // For current ledger entry types, pseudo-account is identified by `sfAccount` field. - if (!sle_->isFieldPresent(sfAccount)) + bool failed = false; + for (auto const& sle : sles_) { - JLOG(j.fatal()) << "Invariant failed: ledger entry " << sle_->getSType() - << " is missing pseudo-account field"; - return false; + // For current ledger entry types, pseudo-account is identified by `sfAccount` field. + if (!sle->isFieldPresent(sfAccount)) + { + JLOG(j.fatal()) << "Invariant failed: ledger entry " << sle->getSType() + << " is missing pseudo-account field"; + failed = true; + continue; + } + + bool const exists = view.read(keylet::account(sle->getAccountID(sfAccount))) != nullptr; + + // The pseudo-account must exist on the ledger + if (!exists) + { + JLOG(j.fatal()) << "Invariant failed: ledger entry " << sle->getSType() + << " pseudo-account does not exist"; + failed = true; + } } - bool const exists = view.read(keylet::account(sle_->getAccountID(sfAccount))) != nullptr; - - // The pseudo-account must exist on the ledger - if (!exists) - { - JLOG(j.fatal()) << "Invariant failed: ledger entry " << sle_->getSType() - << " pseudo-account does not exist"; - return false; - } - - return true; + return !failed; } } // namespace xrpl diff --git a/src/test/app/Invariants_test.cpp b/src/test/app/Invariants_test.cpp index 9891bbf054..95a4d79a9d 100644 --- a/src/test/app/Invariants_test.cpp +++ b/src/test/app/Invariants_test.cpp @@ -4895,6 +4895,23 @@ class Invariants_test : public beast::unit_test::Suite // An account that is never funded, so it won't exist in any view. AccountID const ghostId = Account{"Ghost"}.id(); + doInvariantCheck( + Env{*this, amendments}, + {{"is missing pseudo-account field"}}, + [](Account const& a1, Account const&, ApplyContext& ac) { + auto const brokerKeylet = keylet::loanbroker(a1.id(), ac.view().seq()); + auto sle = std::make_shared(brokerKeylet); + sle->makeFieldAbsent(sfAccount); + auto const page = ac.view().dirInsert( + keylet::ownerDir(a1.id()), sle->key(), describeOwnerDir(a1.id())); + if (!page) + return false; + sle->setFieldU64(sfOwnerNode, *page); + sle->setFieldU32(sfOwnerCount, 1); + ac.view().insert(sle); + return true; + }); + // Vault: pseudo-account is referenced but does not exist on the ledger. doInvariantCheck( Env{*this, amendments},