diff --git a/src/test/app/Regression_test.cpp b/src/test/app/Regression_test.cpp index f54a88ace0..ddaece8894 100644 --- a/src/test/app/Regression_test.cpp +++ b/src/test/app/Regression_test.cpp @@ -16,11 +16,15 @@ //============================================================================== #include +#include #include +#include #include +#include #include #include #include +#include #include namespace ripple { @@ -247,6 +251,80 @@ struct Regression_test : public beast::unit_test::suite jrReader.parse(jvRequest, buffers) && jvRequest.isObject()); } + void + testInvalidTxObjectIDType() + { + testcase("Invalid Transaction Object ID Type"); + // Crasher bug introduced in 2.0.1. Fixed in 2.3.0. + + using namespace jtx; + Env env(*this); + + Account alice("alice"); + Account bob("bob"); + env.fund(XRP(10'000), alice, bob); + env.close(); + + { + auto const alice_index = keylet::account(alice).key; + if (BEAST_EXPECT(alice_index.isNonZero())) + { + env(check::cash( + alice, alice_index, check::DeliverMin(XRP(100))), + ter(tecNO_ENTRY)); + } + } + + { + auto const bob_index = keylet::account(bob).key; + + auto const digest = [&]() -> std::optional { + auto const& state = + env.app().getLedgerMaster().getClosedLedger()->stateMap(); + SHAMapHash digest; + if (!state.peekItem(bob_index, digest)) + return std::nullopt; + return digest.as_uint256(); + }(); + + auto const mapCounts = [&](CountedObjects::List const& list) { + std::map result; + for (auto const& e : list) + { + result[e.first] = e.second; + } + + return result; + }; + + if (BEAST_EXPECT(bob_index.isNonZero()) && + BEAST_EXPECT(digest.has_value())) + { + auto& cache = env.app().cachedSLEs(); + cache.del(*digest, false); + auto const beforeCounts = + mapCounts(CountedObjects::getInstance().getCounts(0)); + + env(check::cash(alice, bob_index, check::DeliverMin(XRP(100))), + ter(tecNO_ENTRY)); + + auto const afterCounts = + mapCounts(CountedObjects::getInstance().getCounts(0)); + + using namespace std::string_literals; + BEAST_EXPECT( + beforeCounts.at("CachedView::hit"s) == + afterCounts.at("CachedView::hit"s)); + BEAST_EXPECT( + beforeCounts.at("CachedView::hitExpired"s) + 1 == + afterCounts.at("CachedView::hitExpired"s)); + BEAST_EXPECT( + beforeCounts.at("CachedView::miss"s) == + afterCounts.at("CachedView::miss"s)); + } + } + } + void run() override { @@ -256,6 +334,7 @@ struct Regression_test : public beast::unit_test::suite testFeeEscalationAutofill(); testFeeEscalationExtremeConfig(); testJsonInvalid(); + testInvalidTxObjectIDType(); } }; diff --git a/src/xrpld/ledger/detail/CachedView.cpp b/src/xrpld/ledger/detail/CachedView.cpp index 645a2c79c1..0463e5de05 100644 --- a/src/xrpld/ledger/detail/CachedView.cpp +++ b/src/xrpld/ledger/detail/CachedView.cpp @@ -57,6 +57,10 @@ CachedViewImpl::read(Keylet const& k) const baseRead = true; return base_.read(k); }); + // If the sle is null, then a failure must have occurred in base_.read() + XRPL_ASSERT( + sle || baseRead, + "ripple::CachedView::read : null SLE result from base"); if (cacheHit && baseRead) hitsexpired.increment(); else if (cacheHit)