From 4b32181dbbe32f9cd9f8c2063bd31b27746f794d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Wed, 14 Jan 2026 23:43:24 +0100 Subject: [PATCH] test: add `HaveInputs` call-path unit tests Add unit tests covering `CCoinsViewCache::HaveInputs()`. The tests document that `HaveInputs()` consults the cache first and that a cache miss pulls from the backing view via `GetCoin()`. --- src/test/coins_tests.cpp | 87 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 8c0756d8528..ad182559a33 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -1050,6 +1050,93 @@ BOOST_FIXTURE_TEST_CASE(ccoins_flush_behavior, FlushTest) } } +BOOST_AUTO_TEST_CASE(ccoins_haveinputs_cache_miss_uses_base_getcoin) +{ + const COutPoint prevout{Txid::FromUint256(m_rng.rand256()), 0}; + + CCoinsViewDB db{{.path = "test", .cache_bytes = 1_MiB, .memory_only = true}, {}}; + { + CCoinsViewCache write_cache{&db}; + write_cache.SetBestBlock(m_rng.rand256()); + write_cache.AddCoin(prevout, Coin{CTxOut{1, CScript{}}, 1, false}, /*possible_overwrite=*/false); + write_cache.Flush(); + } + + class CCoinsViewSpy final : public CCoinsViewBacked + { + public: + const COutPoint expected; + mutable size_t havecoin_calls{0}, getcoin_calls{0}; + + explicit CCoinsViewSpy(CCoinsView* view, const COutPoint& out) : CCoinsViewBacked(view), expected{out} {} + + std::optional GetCoin(const COutPoint& out) const override + { + ++getcoin_calls; + BOOST_CHECK(out == expected); + return CCoinsViewBacked::GetCoin(out); + } + + bool HaveCoin(const COutPoint& out) const override + { + ++havecoin_calls; + BOOST_CHECK(out == expected); + return CCoinsViewBacked::HaveCoin(out); + } + }; + + CCoinsViewSpy base{&db, prevout}; + CCoinsViewCache cache{&base}; + + CMutableTransaction mtx; + mtx.vin.emplace_back(prevout); + const CTransaction tx{mtx}; + BOOST_CHECK(!tx.IsCoinBase()); + + BOOST_CHECK(cache.HaveInputs(tx)); + BOOST_CHECK_EQUAL(base.getcoin_calls, 1); + BOOST_CHECK_EQUAL(base.havecoin_calls, 0); +} + +BOOST_AUTO_TEST_CASE(ccoins_cache_hit_does_not_call_base) +{ + class CCoinsViewNoCall final : public CCoinsView + { + public: + std::optional GetCoin(const COutPoint&) const override + { + BOOST_FAIL("Base GetCoin should not be called when input is cached"); + return std::nullopt; + } + + bool HaveCoin(const COutPoint&) const override + { + BOOST_FAIL("Base HaveCoin should not be called when input is cached"); + return false; + } + }; + + const COutPoint prevout{Txid::FromUint256(m_rng.rand256()), 0}; + CCoinsViewNoCall base; + CCoinsViewCache cache{&base}; + + cache.AddCoin(prevout, Coin{CTxOut{1, CScript{}}, 1, false}, /*possible_overwrite=*/false); + BOOST_CHECK(cache.HaveCoinInCache(prevout)); + + BOOST_CHECK(!cache.AccessCoin(prevout).IsSpent()); + BOOST_CHECK(cache.GetCoin(prevout)); + BOOST_CHECK(cache.HaveCoin(prevout)); + + CMutableTransaction mtx; + mtx.vin.emplace_back(prevout); + const CTransaction tx{mtx}; + BOOST_CHECK(!tx.IsCoinBase()); + BOOST_CHECK(cache.HaveInputs(tx)); + + BOOST_CHECK(cache.SpendCoin(prevout)); + BOOST_CHECK(!cache.HaveCoinInCache(prevout)); +} + BOOST_AUTO_TEST_CASE(coins_resource_is_used) { CCoinsMapMemoryResource resource;