coins_view.cpp
1 // Copyright (c) 2020-present The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #include <coins.h> 6 #include <consensus/amount.h> 7 #include <consensus/tx_check.h> 8 #include <consensus/tx_verify.h> 9 #include <consensus/validation.h> 10 #include <policy/policy.h> 11 #include <primitives/transaction.h> 12 #include <script/interpreter.h> 13 #include <test/fuzz/FuzzedDataProvider.h> 14 #include <test/fuzz/fuzz.h> 15 #include <test/fuzz/util.h> 16 #include <test/util/setup_common.h> 17 #include <txdb.h> 18 #include <util/hasher.h> 19 20 #include <cassert> 21 #include <algorithm> 22 #include <cstdint> 23 #include <functional> 24 #include <limits> 25 #include <memory> 26 #include <optional> 27 #include <ranges> 28 #include <stdexcept> 29 #include <string> 30 #include <utility> 31 #include <vector> 32 33 namespace { 34 const Coin EMPTY_COIN{}; 35 36 bool operator==(const Coin& a, const Coin& b) 37 { 38 if (a.IsSpent() && b.IsSpent()) return true; 39 return a.fCoinBase == b.fCoinBase && a.nHeight == b.nHeight && a.out == b.out; 40 } 41 42 /** 43 * MutationGuardCoinsViewCache asserts that nothing mutates cacheCoins until 44 * BatchWrite is called. It keeps a snapshot of the cacheCoins state, which it 45 * uses for the assertion in BatchWrite. After the call to the superclass 46 * CCoinsViewCache::BatchWrite returns, it recomputes the snapshot at that 47 * moment. 48 */ 49 class MutationGuardCoinsViewCache final : public CCoinsViewCache 50 { 51 private: 52 struct CacheCoinSnapshot { 53 COutPoint outpoint; 54 bool dirty{false}; 55 bool fresh{false}; 56 Coin coin; 57 bool operator==(const CacheCoinSnapshot&) const = default; 58 }; 59 60 std::vector<CacheCoinSnapshot> ComputeCacheCoinsSnapshot() const 61 { 62 std::vector<CacheCoinSnapshot> snapshot; 63 snapshot.reserve(cacheCoins.size()); 64 65 for (const auto& [outpoint, entry] : cacheCoins) { 66 snapshot.emplace_back(outpoint, entry.IsDirty(), entry.IsFresh(), entry.coin); 67 } 68 69 std::ranges::sort(snapshot, std::less<>{}, &CacheCoinSnapshot::outpoint); 70 return snapshot; 71 } 72 73 mutable std::vector<CacheCoinSnapshot> m_expected_snapshot{ComputeCacheCoinsSnapshot()}; 74 75 public: 76 void BatchWrite(CoinsViewCacheCursor& cursor, const uint256& block_hash) override 77 { 78 // Nothing must modify cacheCoins other than BatchWrite. 79 assert(ComputeCacheCoinsSnapshot() == m_expected_snapshot); 80 CCoinsViewCache::BatchWrite(cursor, block_hash); 81 m_expected_snapshot = ComputeCacheCoinsSnapshot(); 82 } 83 84 using CCoinsViewCache::CCoinsViewCache; 85 }; 86 } // namespace 87 88 void initialize_coins_view() 89 { 90 static const auto testing_setup = MakeNoLogFileContext<>(); 91 } 92 93 void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& coins_view_cache, CCoinsView* backend_coins_view) 94 { 95 const bool is_db{dynamic_cast<CCoinsViewDB*>(backend_coins_view) != nullptr}; 96 bool good_data{true}; 97 auto* original_backend{backend_coins_view}; 98 99 if (is_db) coins_view_cache.SetBestBlock(uint256::ONE); 100 COutPoint random_out_point; 101 Coin random_coin; 102 CMutableTransaction random_mutable_transaction; 103 LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000) 104 { 105 CallOneOf( 106 fuzzed_data_provider, 107 [&] { 108 if (random_coin.IsSpent()) { 109 return; 110 } 111 COutPoint outpoint{random_out_point}; 112 Coin coin{random_coin}; 113 if (fuzzed_data_provider.ConsumeBool()) { 114 // We can only skip the check if no unspent coin exists for this outpoint. 115 const bool possible_overwrite{coins_view_cache.PeekCoin(outpoint) || fuzzed_data_provider.ConsumeBool()}; 116 coins_view_cache.AddCoin(outpoint, std::move(coin), possible_overwrite); 117 } else { 118 coins_view_cache.EmplaceCoinInternalDANGER(std::move(outpoint), std::move(coin)); 119 } 120 }, 121 [&] { 122 coins_view_cache.Flush(/*reallocate_cache=*/fuzzed_data_provider.ConsumeBool()); 123 }, 124 [&] { 125 coins_view_cache.Sync(); 126 }, 127 [&] { 128 uint256 best_block{ConsumeUInt256(fuzzed_data_provider)}; 129 // `CCoinsViewDB::BatchWrite()` requires a non-null best block. 130 if (is_db && best_block.IsNull()) best_block = uint256::ONE; 131 coins_view_cache.SetBestBlock(best_block); 132 }, 133 [&] { 134 (void)coins_view_cache.CreateResetGuard(); 135 // Reset() clears the best block, so reseed db-backed caches. 136 if (is_db) { 137 const uint256 best_block{ConsumeUInt256(fuzzed_data_provider)}; 138 if (best_block.IsNull()) { 139 good_data = false; 140 return; 141 } 142 coins_view_cache.SetBestBlock(best_block); 143 } 144 }, 145 [&] { 146 Coin move_to; 147 (void)coins_view_cache.SpendCoin(random_out_point, fuzzed_data_provider.ConsumeBool() ? &move_to : nullptr); 148 }, 149 [&] { 150 coins_view_cache.Uncache(random_out_point); 151 }, 152 [&] { 153 const bool use_original_backend{fuzzed_data_provider.ConsumeBool()}; 154 if (use_original_backend && backend_coins_view != original_backend) { 155 // FRESH flags valid against the empty backend may be invalid 156 // against the original backend, so reset before restoring it. 157 (void)coins_view_cache.CreateResetGuard(); 158 // Reset() clears the best block; db backends require a non-null hash. 159 if (is_db) coins_view_cache.SetBestBlock(uint256::ONE); 160 } 161 backend_coins_view = use_original_backend ? original_backend : &CoinsViewEmpty::Get(); 162 coins_view_cache.SetBackend(*backend_coins_view); 163 }, 164 [&] { 165 const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider); 166 if (!opt_out_point) { 167 good_data = false; 168 return; 169 } 170 random_out_point = *opt_out_point; 171 }, 172 [&] { 173 const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider); 174 if (!opt_coin) { 175 good_data = false; 176 return; 177 } 178 random_coin = *opt_coin; 179 }, 180 [&] { 181 const std::optional<CMutableTransaction> opt_mutable_transaction = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS); 182 if (!opt_mutable_transaction) { 183 good_data = false; 184 return; 185 } 186 random_mutable_transaction = *opt_mutable_transaction; 187 }, 188 [&] { 189 CoinsCachePair sentinel{}; 190 sentinel.second.SelfRef(sentinel); 191 size_t dirty_count{0}; 192 CCoinsMapMemoryResource resource; 193 CCoinsMap coins_map{0, SaltedOutpointHasher{/*deterministic=*/true}, CCoinsMap::key_equal{}, &resource}; 194 LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000) 195 { 196 CCoinsCacheEntry coins_cache_entry; 197 if (fuzzed_data_provider.ConsumeBool()) { 198 coins_cache_entry.coin = random_coin; 199 } else { 200 const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider); 201 if (!opt_coin) { 202 good_data = false; 203 return; 204 } 205 coins_cache_entry.coin = *opt_coin; 206 } 207 // Avoid setting FRESH for an outpoint that already exists unspent in the parent view. 208 bool fresh{!coins_view_cache.PeekCoin(random_out_point) && fuzzed_data_provider.ConsumeBool()}; 209 bool dirty{fresh || fuzzed_data_provider.ConsumeBool()}; 210 auto it{coins_map.emplace(random_out_point, std::move(coins_cache_entry)).first}; 211 if (dirty) CCoinsCacheEntry::SetDirty(*it, sentinel); 212 if (fresh) CCoinsCacheEntry::SetFresh(*it, sentinel); 213 dirty_count += dirty; 214 } 215 auto cursor{CoinsViewCacheCursor(dirty_count, sentinel, coins_map, /*will_erase=*/true)}; 216 uint256 best_block{coins_view_cache.GetBestBlock()}; 217 if (fuzzed_data_provider.ConsumeBool()) best_block = ConsumeUInt256(fuzzed_data_provider); 218 // Set best block hash to non-null to satisfy the assertion in CCoinsViewDB::BatchWrite(). 219 if (is_db && best_block.IsNull()) best_block = uint256::ONE; 220 coins_view_cache.BatchWrite(cursor, best_block); 221 }); 222 } 223 224 { 225 bool expected_code_path = false; 226 try { 227 (void)coins_view_cache.Cursor(); 228 } catch (const std::logic_error&) { 229 expected_code_path = true; 230 } 231 assert(expected_code_path); 232 (void)coins_view_cache.DynamicMemoryUsage(); 233 (void)coins_view_cache.EstimateSize(); 234 (void)coins_view_cache.GetBestBlock(); 235 (void)coins_view_cache.GetCacheSize(); 236 (void)coins_view_cache.GetHeadBlocks(); 237 (void)coins_view_cache.HaveInputs(CTransaction{random_mutable_transaction}); 238 } 239 240 { 241 if (is_db && backend_coins_view == original_backend) { 242 assert(backend_coins_view->Cursor()); 243 } 244 (void)backend_coins_view->EstimateSize(); 245 (void)backend_coins_view->GetBestBlock(); 246 (void)backend_coins_view->GetHeadBlocks(); 247 } 248 249 if (fuzzed_data_provider.ConsumeBool()) { 250 CallOneOf( 251 fuzzed_data_provider, 252 [&] { 253 const CTransaction transaction{random_mutable_transaction}; 254 bool is_spent = false; 255 for (const CTxOut& tx_out : transaction.vout) { 256 if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) { 257 is_spent = true; 258 } 259 } 260 if (is_spent) { 261 // Avoid: 262 // coins.cpp:69: void CCoinsViewCache::AddCoin(const COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' failed. 263 return; 264 } 265 const int height{int(fuzzed_data_provider.ConsumeIntegral<uint32_t>() >> 1)}; 266 const bool check_for_overwrite{transaction.IsCoinBase() || [&] { 267 for (uint32_t i{0}; i < transaction.vout.size(); ++i) { 268 if (coins_view_cache.PeekCoin(COutPoint{transaction.GetHash(), i})) return true; 269 } 270 return fuzzed_data_provider.ConsumeBool(); 271 }()}; // We can only skip the check if the current txid has no unspent outputs 272 AddCoins(coins_view_cache, transaction, height, check_for_overwrite); 273 }, 274 [&] { 275 (void)ValidateInputsStandardness(CTransaction{random_mutable_transaction}, coins_view_cache); 276 }, 277 [&] { 278 TxValidationState state; 279 CAmount tx_fee_out; 280 const CTransaction transaction{random_mutable_transaction}; 281 if (ContainsSpentInput(transaction, coins_view_cache)) { 282 // Avoid: 283 // consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed. 284 return; 285 } 286 TxValidationState dummy; 287 if (!CheckTransaction(transaction, dummy)) { 288 // It is not allowed to call CheckTxInputs if CheckTransaction failed 289 return; 290 } 291 if (Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out)) { 292 assert(MoneyRange(tx_fee_out)); 293 } 294 }, 295 [&] { 296 const CTransaction transaction{random_mutable_transaction}; 297 if (ContainsSpentInput(transaction, coins_view_cache)) { 298 // Avoid: 299 // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed. 300 return; 301 } 302 (void)GetP2SHSigOpCount(transaction, coins_view_cache); 303 }, 304 [&] { 305 const CTransaction transaction{random_mutable_transaction}; 306 if (ContainsSpentInput(transaction, coins_view_cache)) { 307 // Avoid: 308 // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed. 309 return; 310 } 311 const auto flags = script_verify_flags::from_int(fuzzed_data_provider.ConsumeIntegral<script_verify_flags::value_type>()); 312 if (!transaction.vin.empty() && (flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) { 313 // Avoid: 314 // script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness &, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed. 315 return; 316 } 317 (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags); 318 }, 319 [&] { 320 (void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache); 321 }); 322 } 323 324 { 325 const Coin& coin_using_access_coin = coins_view_cache.AccessCoin(random_out_point); 326 const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN); 327 const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point); 328 const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point); 329 if (auto coin{coins_view_cache.GetCoin(random_out_point)}) { 330 assert(*coin == coin_using_access_coin); 331 assert(exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin); 332 } else { 333 assert(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin); 334 } 335 // If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent. 336 const bool exists_using_have_coin_in_backend = backend_coins_view->HaveCoin(random_out_point); 337 if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) { 338 assert(exists_using_have_coin); 339 } 340 if (auto coin{backend_coins_view->GetCoin(random_out_point)}) { 341 assert(exists_using_have_coin_in_backend); 342 // Note we can't assert that `coin_using_get_coin == *coin` because the coin in 343 // the cache may have been modified but not yet flushed. 344 } else { 345 assert(!exists_using_have_coin_in_backend); 346 } 347 } 348 } 349 350 FUZZ_TARGET(coins_view, .init = initialize_coins_view) 351 { 352 FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; 353 CCoinsViewCache coins_view_cache{&CoinsViewEmpty::Get(), /*deterministic=*/true}; 354 TestCoinsView(fuzzed_data_provider, coins_view_cache, &CoinsViewEmpty::Get()); 355 } 356 357 FUZZ_TARGET(coins_view_db, .init = initialize_coins_view) 358 { 359 FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; 360 auto db_params = DBParams{ 361 .path = "", 362 .cache_bytes = 1_MiB, 363 .memory_only = true, 364 }; 365 CCoinsViewDB backend_coins_view{std::move(db_params), CoinsViewOptions{}}; 366 CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true}; 367 TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_coins_view); 368 } 369 370 // Creates a CoinsViewOverlay and a MutationGuardCoinsViewCache as the base. 371 // This allows us to exercise all methods on a CoinsViewOverlay, while also 372 // ensuring that nothing can mutate the underlying cache until Flush or Sync is 373 // called. 374 FUZZ_TARGET(coins_view_overlay, .init = initialize_coins_view) 375 { 376 FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; 377 MutationGuardCoinsViewCache backend_cache{&CoinsViewEmpty::Get(), /*deterministic=*/true}; 378 CoinsViewOverlay coins_view_cache{&backend_cache, /*deterministic=*/true}; 379 TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_cache); 380 }