coins: pass UTXO stats view by reference
What changed, and why it matters
This commit is a small code-quality refactor in Bitcoin Core. It changes several UTXO-statistics helpers so they take a database view by reference instead of by pointer. That removes the possibility of passing a null pointer and makes the code's contract clearer, but it does not by itself fix any reported bug or vulnerability.
Treat as routine code-quality/maintenance. No security patch or incident response needed. Reviewers may verify that all call sites now provide a valid `CCoinsViewDB` reference and that no null pointer could have been passed before the change.
Security signals we found
API hardening: pointer-to-reference conversion removes a nullability contract
No memory-safety bug demonstrated in the diff
No consensus, P2P, wallet, or RPC semantic changes visible
No CVE, advisory, or security disclosure referenced in commit message
Evidence from the diff
The patch converts CCoinsViewDB* parameters to const CCoinsViewDB& in kernel::ComputeUTXOStats, the local GetUTXOStats helper in rpc/blockchain.cpp, and related call sites. It also replaces nullable local pointer variables with references initialized directly from CoinsDB(). The change narrows the API contract so callers cannot pass nullptr, and it removes explicit null checks/dereferences. There is no functional change to consensus logic, serialization, or network behavior.
Changed components
src/kernel/coinstats.cppsrc/kernel/coinstats.hsrc/rpc/blockchain.cppsrc/test/fuzz/utxo_snapshot.cppsrc/test/fuzz/utxo_total_supply.cppsrc/validation.cppInspect captured patch +19 / −24
diff --git a/src/kernel/coinstats.cpp b/src/kernel/coinstats.cpp
index 66fcd983..8e96cdb3 100644
--- a/src/kernel/coinstats.cpp
+++ b/src/kernel/coinstats.cpp
@@ -109,13 +109,13 @@ static void ApplyStats(CCoinsStats& stats, const std::map<uint32_t, Coin>& outpu
//! Calculate statistics about the unspent transaction output set
template <typename T>
-static std::optional<CCoinsStats> ComputeUTXOStats(T hash_obj, CCoinsViewDB* view, node::BlockManager& blockman, const std::function<void()>& interruption_point)
+static std::optional<CCoinsStats> ComputeUTXOStats(T hash_obj, const CCoinsViewDB& view, node::BlockManager& blockman, const std::function<void()>& interruption_point)
{
std::unique_ptr<CCoinsViewCursor> pcursor;
CBlockIndex* pindex;
{
LOCK(::cs_main);
- pcursor = view->Cursor();
+ pcursor = view.Cursor();
pindex = blockman.LookupBlockIndex(pcursor->GetBestBlock());
}
assert(pcursor);
@@ -149,11 +149,11 @@ static std::optional<CCoinsStats> ComputeUTXOStats(T hash_obj, CCoinsViewDB* vie
FinalizeHash(hash_obj, stats);
- stats.nDiskSize = view->EstimateSize();
+ stats.nDiskSize = view.EstimateSize();
return stats;
}
-std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsViewDB* view, node::BlockManager& blockman, const std::function<void()>& interruption_point)
+std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, const CCoinsViewDB& view, node::BlockManager& blockman, const std::function<void()>& interruption_point)
{
return [&]() -> std::optional<CCoinsStats> {
switch (hash_type) {
diff --git a/src/kernel/coinstats.h b/src/kernel/coinstats.h
index 0f0f4628..eda61b89 100644
--- a/src/kernel/coinstats.h
+++ b/src/kernel/coinstats.h
@@ -77,7 +77,7 @@ uint64_t GetBogoSize(const CScript& script_pub_key);
void ApplyCoinHash(MuHash3072& muhash, const COutPoint& outpoint, const Coin& coin);
void RemoveCoinHash(MuHash3072& muhash, const COutPoint& outpoint, const Coin& coin);
-std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsViewDB* view, node::BlockManager& blockman, const std::function<void()>& interruption_point = {});
+std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, const CCoinsViewDB& view, node::BlockManager& blockman, const std::function<void()>& interruption_point = {});
} // namespace kernel
#endif // BITCOIN_KERNEL_COINSTATS_H
diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp
index f6e7ba28..ce7adf6d 100644
--- a/src/rpc/blockchain.cpp
+++ b/src/rpc/blockchain.cpp
@@ -992,7 +992,7 @@ CoinStatsHashType ParseHashType(std::string_view hash_type_input)
*
* @param[in] index_requested Signals if the coinstatsindex should be used (when available).
*/
-static std::optional<kernel::CCoinsStats> GetUTXOStats(CCoinsViewDB* view, node::BlockManager& blockman,
+static std::optional<kernel::CCoinsStats> GetUTXOStats(const CCoinsViewDB& view, node::BlockManager& blockman,
kernel::CoinStatsHashType hash_type,
const std::function<void()>& interruption_point = {},
const CBlockIndex* pindex = nullptr,
@@ -1003,7 +1003,7 @@ static std::optional<kernel::CCoinsStats> GetUTXOStats(CCoinsViewDB* view, node:
if (pindex) {
return g_coin_stats_index->LookUpStats(*pindex);
} else {
- CBlockIndex& block_index = *CHECK_NONFATAL(WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view->GetBestBlock())));
+ CBlockIndex& block_index = *CHECK_NONFATAL(WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(view.GetBestBlock())));
return g_coin_stats_index->LookUpStats(block_index);
}
}
@@ -1012,7 +1012,7 @@ static std::optional<kernel::CCoinsStats> GetUTXOStats(CCoinsViewDB* view, node:
// pindex should either be null or equal to the view's best block. This is
// because without the coinstats index we can only get coinstats about the
// best block.
- CHECK_NONFATAL(!pindex || pindex->GetBlockHash() == view->GetBestBlock());
+ CHECK_NONFATAL(!pindex || pindex->GetBlockHash() == view.GetBestBlock());
return kernel::ComputeUTXOStats(hash_type, view, blockman, interruption_point);
}
@@ -1083,13 +1083,8 @@ static RPCMethod gettxoutsetinfo()
Chainstate& active_chainstate = chainman.ActiveChainstate();
active_chainstate.ForceFlushStateToDisk(/*wipe_cache=*/false);
- CCoinsViewDB* coins_view;
- BlockManager* blockman;
- {
- LOCK(::cs_main);
- coins_view = &active_chainstate.CoinsDB();
- blockman = &active_chainstate.m_blockman;
- }
+ const CCoinsViewDB& coins_view{WITH_LOCK(::cs_main, return active_chainstate.CoinsDB())};
+ BlockManager& blockman{active_chainstate.m_blockman};
const CBlockIndex* pindex{nullptr};
if (!request.params[1].isNull()) {
@@ -1119,7 +1114,7 @@ static RPCMethod gettxoutsetinfo()
}
}
- const std::optional<CCoinsStats> maybe_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, pindex, index_requested);
+ const std::optional<CCoinsStats> maybe_stats = GetUTXOStats(coins_view, blockman, hash_type, node.rpc_interruption_point, pindex, index_requested);
if (maybe_stats.has_value()) {
const CCoinsStats& stats = maybe_stats.value();
ret.pushKV("height", stats.nHeight);
@@ -1140,8 +1135,8 @@ static RPCMethod gettxoutsetinfo()
} else {
CCoinsStats prev_stats{};
if (stats.nHeight > 0) {
- const CBlockIndex& block_index = *CHECK_NONFATAL(WITH_LOCK(::cs_main, return blockman->LookupBlockIndex(stats.hashBlock)));
- const std::optional<CCoinsStats> maybe_prev_stats = GetUTXOStats(coins_view, *blockman, hash_type, node.rpc_interruption_point, block_index.pprev, index_requested);
+ const CBlockIndex& block_index = *CHECK_NONFATAL(WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(stats.hashBlock)));
+ const std::optional<CCoinsStats> maybe_prev_stats = GetUTXOStats(coins_view, blockman, hash_type, node.rpc_interruption_point, block_index.pprev, index_requested);
if (!maybe_prev_stats) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
}
@@ -3323,7 +3318,7 @@ UniValue CreateRolledBackUTXOSnapshot(
rollback_cache.Flush();
LogInfo("Rollback complete. Computing UTXO statistics for created txoutset dump.");
- std::optional<CCoinsStats> maybe_stats = GetUTXOStats(temp_db.get(),
+ std::optional<CCoinsStats> maybe_stats = GetUTXOStats(*temp_db,
chainstate.m_blockman,
CoinStatsHashType::HASH_SERIALIZED,
node.rpc_interruption_point);
@@ -3374,7 +3369,7 @@ PrepareUTXOSnapshot(
chainstate.ForceFlushStateToDisk(/*wipe_cache=*/false);
- maybe_stats = GetUTXOStats(&chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, interruption_point);
+ maybe_stats = GetUTXOStats(chainstate.CoinsDB(), chainstate.m_blockman, CoinStatsHashType::HASH_SERIALIZED, interruption_point);
if (!maybe_stats) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set");
}
diff --git a/src/test/fuzz/utxo_snapshot.cpp b/src/test/fuzz/utxo_snapshot.cpp
index b15ff137..815517bc 100644
--- a/src/test/fuzz/utxo_snapshot.cpp
+++ b/src/test/fuzz/utxo_snapshot.cpp
@@ -59,7 +59,7 @@ void sanity_check_snapshot()
LOCK(cs_main);
auto& cs{node.chainman->ActiveChainstate()};
cs.ForceFlushStateToDisk(/*wipe_cache=*/false);
- const auto stats{*Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::HASH_SERIALIZED, &cs.CoinsDB(), node.chainman->m_blockman))};
+ const auto stats{*Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::HASH_SERIALIZED, cs.CoinsDB(), node.chainman->m_blockman))};
const auto cp_au_data{*Assert(node.chainman->GetParams().AssumeutxoForHeight(2 * COINBASE_MATURITY))};
Assert(stats.nHeight == cp_au_data.height);
Assert(stats.nTransactions + 1 == cp_au_data.m_chain_tx_count); // +1 for the genesis tx.
diff --git a/src/test/fuzz/utxo_total_supply.cpp b/src/test/fuzz/utxo_total_supply.cpp
index a6e70372..1b2dc796 100644
--- a/src/test/fuzz/utxo_total_supply.cpp
+++ b/src/test/fuzz/utxo_total_supply.cpp
@@ -102,7 +102,7 @@ FUZZ_TARGET(utxo_total_supply)
LOCK(chainman.GetMutex());
chainman.ActiveChainstate().ForceFlushStateToDisk(wipe_cache);
utxo_stats = std::move(
- *Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::NONE, &chainman.ActiveChainstate().CoinsDB(), chainman.m_blockman, {})));
+ *Assert(kernel::ComputeUTXOStats(kernel::CoinStatsHashType::NONE, chainman.ActiveChainstate().CoinsDB(), chainman.m_blockman, {})));
// Check that miner can't print more money than they are allowed to
assert(circulation == utxo_stats.total_amount);
};
diff --git a/src/validation.cpp b/src/validation.cpp
index 87cf646b..d8e24bfd 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -5906,7 +5906,7 @@ util::Result<void> ChainstateManager::PopulateAndValidateSnapshot(
// As above, okay to immediately release cs_main here since no other context knows
// about the snapshot_chainstate.
- CCoinsViewDB* snapshot_coinsdb = WITH_LOCK(::cs_main, return &snapshot_chainstate.CoinsDB());
+ const CCoinsViewDB& snapshot_coinsdb = WITH_LOCK(::cs_main, return snapshot_chainstate.CoinsDB());
std::optional<CCoinsStats> maybe_stats;
@@ -6047,7 +6047,7 @@ SnapshotCompletionResult ChainstateManager::MaybeValidateSnapshot(Chainstate& va
try {
validated_cs_stats = ComputeUTXOStats(
CoinStatsHashType::HASH_SERIALIZED,
- &validated_coins_db,
+ validated_coins_db,
m_blockman,
[&interrupt = m_interrupt] { SnapshotUTXOHashBreakpoint(interrupt); });
} catch (StopHashingException const&) {
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.