Merge bitcoin/bitcoin#34165: coins: don't mutate main cache when connecting block

cae6d895f8 fuzz: add target for CoinsViewOverlay (Andrew Toth)
86eda88c8e fuzz: move backend mutating block to end of coins_view (Andrew Toth)
89824fb27b fuzz: pass coins_view_cache to TestCoinsView in coins_view (Andrew Toth)
73e99a5966 coins: don't mutate main cache when connecting block (Andrew Toth)
67c0d1798e coins: introduce CoinsViewOverlay (Andrew Toth)
69b01af0eb coins: add PeekCoin() (Andrew Toth)

Pull request description:

  This is a slightly modified version of the first few commits of #31132, which can be merged as an independent change. It has a small benefit on its own, but will help in moving the parent PR forward.

  When accessing coins via the `CCoinsViewCache`, methods like `GetCoin` can call `FetchCoin` which actually mutate `cacheCoins` internally to cache entries when they are pulled from the backing db. This is generally a performance improvement for single threaded access patterns, but it precludes us from accessing entries in a `CCoinsViewCache` from multiple threads without a lock.

  Another aspect is that when we use the resettable `CCoinsViewCache` view backed by the main cache for use in `ConnectBlock()`, we will insert entries into the main cache even if the block is determined to be invalid. This is not the biggest concern, since an invalid block requires proof-of-work. But, an attacker could craft multiple invalid blocks to fill the main cache. This would make us `Flush` the cache more often than necessary. Obviously this would be very expensive to do on mainnet.

  Introduce `CoinsViewOverlay`, a `CCoinsViewCache` subclass that reads coins without mutating the underlying cache via `FetchCoin()`.

  Add `PeekCoin()` to look up a Coin through a stack of `CCoinsViewCache` layers without populating parent caches. This prevents the main cache from caching inputs pulled from disk for a block that has not yet been fully validated. Once `Flush()` is called on the view, these inputs will be added as spent to `coinsCache` in the main cache via `BatchWrite()`.

  This is the foundation for async input fetching, where worker threads must not mutate shared state.

ACKs for top commit:
  l0rinc:
    ACK cae6d895f8
  sipa:
    reACK cae6d895f8
  sedited:
    Re-ACK cae6d895f8
  willcl-ark:
    ACK cae6d895f8
  vasild:
    Cursory ACK cae6d895f8
  ryanofsky:
    Code review ACK cae6d895f8. PR is basically back to the form I had acked the first time, implementing `PeekCoin()` by calling `GetCoin()`. This is not ideal because `PeekCoin()` is not supposed to modify caches and `GetCoin()` does that, but it at least avoids problems of the subsequent approach tried where `GetCoin()` calls `PeekCoin` and would result in bugs when subclasses implement `GetCoin` forgetting to override `PeekCoin`. Hopefully #34124 can clean all of this by making relevant methods pure virtual.

Tree-SHA512: a81a98e60ca9e47454933ad879840cc226cb3b841bc36a4b746c34b350e07c546cdb5ddc55ec1ff66cf65d1ec503d22201d3dc12d4e82a8f4d386ccc52ba6441
This commit is contained in:
Ryan Ofsky
2026-02-19 21:39:14 -05:00
10 changed files with 386 additions and 38 deletions

View File

@@ -1171,4 +1171,25 @@ BOOST_AUTO_TEST_CASE(ccoins_reset_guard)
BOOST_CHECK_EQUAL(cache.GetDirtyCount(), 0U);
}
BOOST_AUTO_TEST_CASE(ccoins_peekcoin)
{
CCoinsViewTest base{m_rng};
// Populate the base view with a coin.
const COutPoint outpoint{Txid::FromUint256(m_rng.rand256()), m_rng.rand32()};
const Coin coin{CTxOut{m_rng.randrange(10), CScript{}}, 1, false};
{
CCoinsViewCache cache{&base};
cache.AddCoin(outpoint, Coin{coin}, /*possible_overwrite=*/false);
cache.Flush();
}
// Verify PeekCoin can read through the cache stack without mutating the intermediate cache.
CCoinsViewCacheTest main_cache{&base};
const auto fetched{main_cache.PeekCoin(outpoint)};
BOOST_CHECK(fetched.has_value());
BOOST_CHECK(*fetched == coin);
BOOST_CHECK(!main_cache.HaveCoinInCache(outpoint));
}
BOOST_AUTO_TEST_SUITE_END()