coins: add explicit CoinsViewEmpty noop backend

Introduce `CoinsViewEmpty` as an explicit no-op `CCoinsView` implementation, and define its singleton accessor out of line in `coins.cpp` to avoid `-Wunique-object-duplication` in shared-library builds.`
Use it at call sites that intentionally want a no-op backend instead of constructing anonymous placeholder views.

`CCoinsViewTest` and `CoinsViewBottom` now inherit defaults from `CoinsViewEmpty` (e.g. the unused `EstimateSize()`, which now returns 0).

Co-authored-by: Ryan Ofsky <ryan@ofsky.org>
This commit is contained in:
Lőrinc
2026-02-15 15:16:27 +01:00
parent 90c635c01c
commit b637566c8d
15 changed files with 54 additions and 63 deletions

View File

@@ -38,14 +38,14 @@ bool operator==(const Coin &a, const Coin &b) {
a.out == b.out;
}
class CCoinsViewTest : public CCoinsView
class CCoinsViewTest : public CoinsViewEmpty
{
FastRandomContext& m_rng;
uint256 hashBestBlock_;
std::map<COutPoint, Coin> map_;
public:
CCoinsViewTest(FastRandomContext& rng) : m_rng{rng} {}
explicit CCoinsViewTest(FastRandomContext& rng) : m_rng{rng} {}
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override
{
@@ -677,8 +677,7 @@ public:
}
}
CCoinsView root;
CCoinsViewCacheTest base{&root};
CCoinsViewCacheTest base{&CoinsViewEmpty::Get()};
CCoinsViewCacheTest cache{&base};
};
@@ -1087,8 +1086,7 @@ BOOST_AUTO_TEST_CASE(coins_resource_is_used)
BOOST_AUTO_TEST_CASE(ccoins_addcoin_exception_keeps_usage_balanced)
{
CCoinsView root;
CCoinsViewCacheTest cache{&root};
CCoinsViewCacheTest cache{&CoinsViewEmpty::Get()};
const COutPoint outpoint{Txid::FromUint256(m_rng.rand256()), m_rng.rand32()};
@@ -1105,8 +1103,7 @@ BOOST_AUTO_TEST_CASE(ccoins_addcoin_exception_keeps_usage_balanced)
BOOST_AUTO_TEST_CASE(ccoins_emplace_duplicate_keeps_usage_balanced)
{
CCoinsView root;
CCoinsViewCacheTest cache{&root};
CCoinsViewCacheTest cache{&CoinsViewEmpty::Get()};
const COutPoint outpoint{Txid::FromUint256(m_rng.rand256()), m_rng.rand32()};

View File

@@ -90,11 +90,11 @@ void initialize_coins_view()
static const auto testing_setup = MakeNoLogFileContext<>();
}
void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& coins_view_cache, CCoinsView* backend_coins_view, bool is_db)
void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& coins_view_cache, CCoinsView* backend_coins_view)
{
const bool is_db{dynamic_cast<CCoinsViewDB*>(backend_coins_view) != nullptr};
bool good_data{true};
auto* original_backend{backend_coins_view};
CCoinsView coins_view_empty{};
if (is_db) coins_view_cache.SetBestBlock(uint256::ONE);
COutPoint random_out_point;
@@ -158,7 +158,7 @@ void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& co
// Reset() clears the best block; db backends require a non-null hash.
if (is_db) coins_view_cache.SetBestBlock(uint256::ONE);
}
backend_coins_view = use_original_backend ? original_backend : &coins_view_empty;
backend_coins_view = use_original_backend ? original_backend : &CoinsViewEmpty::Get();
coins_view_cache.SetBackend(*backend_coins_view);
},
[&] {
@@ -350,9 +350,8 @@ void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& co
FUZZ_TARGET(coins_view, .init = initialize_coins_view)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
CCoinsView backend_coins_view;
CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true};
TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_coins_view, /*is_db=*/false);
CCoinsViewCache coins_view_cache{&CoinsViewEmpty::Get(), /*deterministic=*/true};
TestCoinsView(fuzzed_data_provider, coins_view_cache, &CoinsViewEmpty::Get());
}
FUZZ_TARGET(coins_view_db, .init = initialize_coins_view)
@@ -365,7 +364,7 @@ FUZZ_TARGET(coins_view_db, .init = initialize_coins_view)
};
CCoinsViewDB backend_coins_view{std::move(db_params), CoinsViewOptions{}};
CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true};
TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_coins_view, /*is_db=*/true);
TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_coins_view);
}
// Creates a CoinsViewOverlay and a MutationGuardCoinsViewCache as the base.
@@ -375,8 +374,7 @@ FUZZ_TARGET(coins_view_db, .init = initialize_coins_view)
FUZZ_TARGET(coins_view_overlay, .init = initialize_coins_view)
{
FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
CCoinsView backend_base_coins_view;
MutationGuardCoinsViewCache backend_cache{&backend_base_coins_view, /*deterministic=*/true};
MutationGuardCoinsViewCache backend_cache{&CoinsViewEmpty::Get(), /*deterministic=*/true};
CoinsViewOverlay coins_view_cache{&backend_cache, /*deterministic=*/true};
TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_cache, /*is_db=*/false);
TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_cache);
}

View File

@@ -139,7 +139,7 @@ struct CacheLevel
*
* The initial state consists of the empty UTXO set.
*/
class CoinsViewBottom final : public CCoinsView
class CoinsViewBottom final : public CoinsViewEmpty
{
std::map<COutPoint, Coin> m_data;
@@ -153,11 +153,6 @@ public:
return std::nullopt;
}
uint256 GetBestBlock() const final { return {}; }
std::vector<uint256> GetHeadBlocks() const final { return {}; }
std::unique_ptr<CCoinsViewCursor> Cursor() const final { return {}; }
size_t EstimateSize() const final { return m_data.size(); }
void BatchWrite(CoinsViewCacheCursor& cursor, const uint256&) final
{
for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) {

View File

@@ -86,8 +86,7 @@ FUZZ_TARGET(transaction, .init = initialize_transaction)
(void)RecursiveDynamicUsage(tx);
(void)SignalsOptInRBF(tx);
CCoinsView coins_view;
const CCoinsViewCache coins_view_cache(&coins_view);
const CCoinsViewCache coins_view_cache{&CoinsViewEmpty::Get()};
(void)ValidateInputsStandardness(tx, coins_view_cache);
(void)IsWitnessStandard(tx, coins_view_cache);

View File

@@ -277,8 +277,7 @@ BOOST_AUTO_TEST_CASE(switchover)
BOOST_AUTO_TEST_CASE(ValidateInputsStandardness)
{
CCoinsView coinsDummy;
CCoinsViewCache coins(&coinsDummy);
CCoinsViewCache coins{&CoinsViewEmpty::Get()};
FillableSigningProvider keystore;
CKey key[6];
for (int i = 0; i < 6; i++)

View File

@@ -116,8 +116,7 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
CMutableTransaction spendingTx;
// Create utxo set
CCoinsView coinsDummy;
CCoinsViewCache coins(&coinsDummy);
CCoinsViewCache coins{&CoinsViewEmpty::Get()};
// Create key
CKey key = GenerateRandomKey();
CPubKey pubkey = key.GetPubKey();

View File

@@ -392,8 +392,7 @@ BOOST_AUTO_TEST_CASE(basic_transaction_tests)
BOOST_AUTO_TEST_CASE(test_Get)
{
FillableSigningProvider keystore;
CCoinsView coinsDummy;
CCoinsViewCache coins(&coinsDummy);
CCoinsViewCache coins{&CoinsViewEmpty::Get()};
std::vector<CMutableTransaction> dummyTransactions =
SetupDummyInputs(keystore, coins, {11*CENT, 50*CENT, 21*CENT, 22*CENT});
@@ -749,8 +748,7 @@ BOOST_AUTO_TEST_CASE(test_witness)
BOOST_AUTO_TEST_CASE(test_IsStandard)
{
FillableSigningProvider keystore;
CCoinsView coinsDummy;
CCoinsViewCache coins(&coinsDummy);
CCoinsViewCache coins{&CoinsViewEmpty::Get()};
std::vector<CMutableTransaction> dummyTransactions =
SetupDummyInputs(keystore, coins, {11*CENT, 50*CENT, 21*CENT, 22*CENT});
@@ -1022,8 +1020,7 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
{
CCoinsView coins_dummy;
CCoinsViewCache coins(&coins_dummy);
CCoinsViewCache coins{&CoinsViewEmpty::Get()};
CKey key;
key.MakeNewKey(true);
@@ -1132,8 +1129,7 @@ BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
BOOST_AUTO_TEST_CASE(checktxinputs_invalid_transactions_test)
{
auto check_invalid{[](CAmount input_value, CAmount output_value, bool coinbase, int spend_height, TxValidationResult expected_result, std::string_view expected_reason) {
CCoinsView coins_dummy;
CCoinsViewCache inputs(&coins_dummy);
CCoinsViewCache inputs{&CoinsViewEmpty::Get()};
const COutPoint prevout{Txid::FromUint256(uint256::ONE), 0};
inputs.AddCoin(prevout, Coin{{input_value, CScript() << OP_TRUE}, /*nHeightIn=*/1, coinbase}, /*possible_overwrite=*/false);
@@ -1181,8 +1177,7 @@ BOOST_AUTO_TEST_CASE(getvalueout_out_of_range_throws)
/** Sanity check the return value of SpendsNonAnchorWitnessProg for various output types. */
BOOST_AUTO_TEST_CASE(spends_witness_prog)
{
CCoinsView coins_dummy;
CCoinsViewCache coins(&coins_dummy);
CCoinsViewCache coins{&CoinsViewEmpty::Get()};
CKey key;
key.MakeNewKey(true);
const CPubKey pubkey{key.GetPubKey()};

View File

@@ -465,8 +465,7 @@ std::pair<CMutableTransaction, CAmount> TestChain100Setup::CreateValidTransactio
keystore.AddKey(input_signing_key);
}
// - Populate a CoinsViewCache with the unspent output
CCoinsView coins_view;
CCoinsViewCache coins_cache(&coins_view);
CCoinsViewCache coins_cache{&CoinsViewEmpty::Get()};
for (const auto& input_transaction : input_transactions) {
AddCoins(coins_cache, *input_transaction.get(), input_height);
}