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()`.
This commit is contained in:
Lőrinc
2026-01-14 23:43:24 +01:00
parent 0ca4295f2e
commit 4b32181dbb

View File

@@ -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<Coin> 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<Coin> 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;