Thanks for the suggestion! Adopted the pcursor->GetBestBlock() approach - it does make the intent clearer since pindex is explicitly tied to the cursor’s snapshot.
One thing to note: cs_main is still held during Cursor() because internally CCoinsViewDB::Cursor() calls GetBestBlock() and NewIterator() as two separate operations.
So if I understand correctly, without the lock, a concurrent FlushStateToDisk could slip between them, causing the cursor’s hashBlock to be from a different state than its iterator data - the same kind of mismatch this PR is fixing. I changed the diff to:
0diff --git a/src/kernel/coinstats.cpp b/src/kernel/coinstats.cpp
1index e1d3fff5ea..79da8ea4d4 100644
2--- a/src/kernel/coinstats.cpp
3+++ b/src/kernel/coinstats.cpp
4@@ -152,8 +152,8 @@ std::optional<CCoinsStats> ComputeUTXOStats(CoinStatsHashType hash_type, CCoinsV
5 CBlockIndex* pindex;
6 {
7 LOCK(::cs_main);
8- pindex = blockman.LookupBlockIndex(view->GetBestBlock());
9 pcursor = view->Cursor();
10+ pindex = blockman.LookupBlockIndex(pcursor->GetBestBlock());
11 }
12 CCoinsStats stats{Assert(pindex)->nHeight, pindex->GetBlockHash()};