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, bool is_db) 94 { 95 bool good_data{true}; 96 97 if (is_db) coins_view_cache.SetBestBlock(uint256::ONE); 98 COutPoint random_out_point; 99 Coin random_coin; 100 CMutableTransaction random_mutable_transaction; 101 LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000) 102 { 103 CallOneOf( 104 fuzzed_data_provider, 105 [&] { 106 if (random_coin.IsSpent()) { 107 return; 108 } 109 COutPoint outpoint{random_out_point}; 110 Coin coin{random_coin}; 111 if (fuzzed_data_provider.ConsumeBool()) { 112 // We can only skip the check if no unspent coin exists for this outpoint. 113 const bool possible_overwrite{coins_view_cache.PeekCoin(outpoint) || fuzzed_data_provider.ConsumeBool()}; 114 coins_view_cache.AddCoin(outpoint, std::move(coin), possible_overwrite); 115 } else { 116 coins_view_cache.EmplaceCoinInternalDANGER(std::move(outpoint), std::move(coin)); 117 } 118 }, 119 [&] { 120 coins_view_cache.Flush(/*reallocate_cache=*/fuzzed_data_provider.ConsumeBool()); 121 }, 122 [&] { 123 coins_view_cache.Sync(); 124 }, 125 [&] { 126 uint256 best_block{ConsumeUInt256(fuzzed_data_provider)}; 127 // Set best block hash to non-null to satisfy the assertion in CCoinsViewDB::BatchWrite(). 128 if (is_db && best_block.IsNull()) best_block = uint256::ONE; 129 coins_view_cache.SetBestBlock(best_block); 130 }, 131 [&] { 132 { 133 const auto reset_guard{coins_view_cache.CreateResetGuard()}; 134 } 135 // Set best block hash to non-null to satisfy the assertion in CCoinsViewDB::BatchWrite(). 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 if (fuzzed_data_provider.ConsumeBool()) { 154 backend_coins_view = CCoinsView{}; 155 } 156 coins_view_cache.SetBackend(backend_coins_view); 157 }, 158 [&] { 159 const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider); 160 if (!opt_out_point) { 161 good_data = false; 162 return; 163 } 164 random_out_point = *opt_out_point; 165 }, 166 [&] { 167 const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider); 168 if (!opt_coin) { 169 good_data = false; 170 return; 171 } 172 random_coin = *opt_coin; 173 }, 174 [&] { 175 const std::optional<CMutableTransaction> opt_mutable_transaction = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS); 176 if (!opt_mutable_transaction) { 177 good_data = false; 178 return; 179 } 180 random_mutable_transaction = *opt_mutable_transaction; 181 }, 182 [&] { 183 CoinsCachePair sentinel{}; 184 sentinel.second.SelfRef(sentinel); 185 size_t dirty_count{0}; 186 CCoinsMapMemoryResource resource; 187 CCoinsMap coins_map{0, SaltedOutpointHasher{/*deterministic=*/true}, CCoinsMap::key_equal{}, &resource}; 188 LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000) 189 { 190 CCoinsCacheEntry coins_cache_entry; 191 if (fuzzed_data_provider.ConsumeBool()) { 192 coins_cache_entry.coin = random_coin; 193 } else { 194 const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider); 195 if (!opt_coin) { 196 good_data = false; 197 return; 198 } 199 coins_cache_entry.coin = *opt_coin; 200 } 201 // Avoid setting FRESH for an outpoint that already exists unspent in the parent view. 202 bool fresh{!coins_view_cache.PeekCoin(random_out_point) && fuzzed_data_provider.ConsumeBool()}; 203 bool dirty{fresh || fuzzed_data_provider.ConsumeBool()}; 204 auto it{coins_map.emplace(random_out_point, std::move(coins_cache_entry)).first}; 205 if (dirty) CCoinsCacheEntry::SetDirty(*it, sentinel); 206 if (fresh) CCoinsCacheEntry::SetFresh(*it, sentinel); 207 dirty_count += dirty; 208 } 209 auto cursor{CoinsViewCacheCursor(dirty_count, sentinel, coins_map, /*will_erase=*/true)}; 210 uint256 best_block{coins_view_cache.GetBestBlock()}; 211 if (fuzzed_data_provider.ConsumeBool()) best_block = ConsumeUInt256(fuzzed_data_provider); 212 // Set best block hash to non-null to satisfy the assertion in CCoinsViewDB::BatchWrite(). 213 if (is_db && best_block.IsNull()) best_block = uint256::ONE; 214 coins_view_cache.BatchWrite(cursor, best_block); 215 }); 216 } 217 218 { 219 bool expected_code_path = false; 220 try { 221 (void)coins_view_cache.Cursor(); 222 } catch (const std::logic_error&) { 223 expected_code_path = true; 224 } 225 assert(expected_code_path); 226 (void)coins_view_cache.DynamicMemoryUsage(); 227 (void)coins_view_cache.EstimateSize(); 228 (void)coins_view_cache.GetBestBlock(); 229 (void)coins_view_cache.GetCacheSize(); 230 (void)coins_view_cache.GetHeadBlocks(); 231 (void)coins_view_cache.HaveInputs(CTransaction{random_mutable_transaction}); 232 } 233 234 { 235 if (is_db) { 236 std::unique_ptr<CCoinsViewCursor> coins_view_cursor = backend_coins_view.Cursor(); 237 assert(!!coins_view_cursor); 238 } 239 (void)backend_coins_view.EstimateSize(); 240 (void)backend_coins_view.GetBestBlock(); 241 (void)backend_coins_view.GetHeadBlocks(); 242 } 243 244 if (fuzzed_data_provider.ConsumeBool()) { 245 CallOneOf( 246 fuzzed_data_provider, 247 [&] { 248 const CTransaction transaction{random_mutable_transaction}; 249 bool is_spent = false; 250 for (const CTxOut& tx_out : transaction.vout) { 251 if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) { 252 is_spent = true; 253 } 254 } 255 if (is_spent) { 256 // Avoid: 257 // coins.cpp:69: void CCoinsViewCache::AddCoin(const COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' failed. 258 return; 259 } 260 const int height{int(fuzzed_data_provider.ConsumeIntegral<uint32_t>() >> 1)}; 261 const bool check_for_overwrite{transaction.IsCoinBase() || [&] { 262 for (uint32_t i{0}; i < transaction.vout.size(); ++i) { 263 if (coins_view_cache.PeekCoin(COutPoint{transaction.GetHash(), i})) return true; 264 } 265 return fuzzed_data_provider.ConsumeBool(); 266 }()}; // We can only skip the check if the current txid has no unspent outputs 267 AddCoins(coins_view_cache, transaction, height, check_for_overwrite); 268 }, 269 [&] { 270 (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache); 271 }, 272 [&] { 273 TxValidationState state; 274 CAmount tx_fee_out; 275 const CTransaction transaction{random_mutable_transaction}; 276 if (ContainsSpentInput(transaction, coins_view_cache)) { 277 // Avoid: 278 // consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed. 279 return; 280 } 281 TxValidationState dummy; 282 if (!CheckTransaction(transaction, dummy)) { 283 // It is not allowed to call CheckTxInputs if CheckTransaction failed 284 return; 285 } 286 if (Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out)) { 287 assert(MoneyRange(tx_fee_out)); 288 } 289 }, 290 [&] { 291 const CTransaction transaction{random_mutable_transaction}; 292 if (ContainsSpentInput(transaction, coins_view_cache)) { 293 // Avoid: 294 // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed. 295 return; 296 } 297 (void)GetP2SHSigOpCount(transaction, coins_view_cache); 298 }, 299 [&] { 300 const CTransaction transaction{random_mutable_transaction}; 301 if (ContainsSpentInput(transaction, coins_view_cache)) { 302 // Avoid: 303 // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed. 304 return; 305 } 306 const auto flags = script_verify_flags::from_int(fuzzed_data_provider.ConsumeIntegral<script_verify_flags::value_type>()); 307 if (!transaction.vin.empty() && (flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) { 308 // Avoid: 309 // script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness &, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed. 310 return; 311 } 312 (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags); 313 }, 314 [&] { 315 (void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache); 316 }); 317 } 318 319 { 320 const Coin& coin_using_access_coin = coins_view_cache.AccessCoin(random_out_point); 321 const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN); 322 const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point); 323 const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point); 324 if (auto coin{coins_view_cache.GetCoin(random_out_point)}) { 325 assert(*coin == coin_using_access_coin); 326 assert(exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin); 327 } else { 328 assert(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin); 329 } 330 // If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent. 331 const bool exists_using_have_coin_in_backend = backend_coins_view.HaveCoin(random_out_point); 332 if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) { 333 assert(exists_using_have_coin); 334 } 335 if (auto coin{backend_coins_view.GetCoin(random_out_point)}) { 336 assert(exists_using_have_coin_in_backend); 337 // Note we can't assert that `coin_using_get_coin == *coin` because the coin in 338 // the cache may have been modified but not yet flushed. 339 } else { 340 assert(!exists_using_have_coin_in_backend); 341 } 342 } 343 } 344 345 FUZZ_TARGET(coins_view, .init = initialize_coins_view) 346 { 347 FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; 348 CCoinsView backend_coins_view; 349 CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true}; 350 TestCoinsView(fuzzed_data_provider, coins_view_cache, backend_coins_view, /*is_db=*/false); 351 } 352 353 FUZZ_TARGET(coins_view_db, .init = initialize_coins_view) 354 { 355 FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; 356 auto db_params = DBParams{ 357 .path = "", 358 .cache_bytes = 1_MiB, 359 .memory_only = true, 360 }; 361 CCoinsViewDB backend_coins_view{std::move(db_params), CoinsViewOptions{}}; 362 CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true}; 363 TestCoinsView(fuzzed_data_provider, coins_view_cache, backend_coins_view, /*is_db=*/true); 364 } 365 366 // Creates a CoinsViewOverlay and a MutationGuardCoinsViewCache as the base. 367 // This allows us to exercise all methods on a CoinsViewOverlay, while also 368 // ensuring that nothing can mutate the underlying cache until Flush or Sync is 369 // called. 370 FUZZ_TARGET(coins_view_overlay, .init = initialize_coins_view) 371 { 372 FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; 373 CCoinsView backend_base_coins_view; 374 MutationGuardCoinsViewCache backend_cache{&backend_base_coins_view, /*deterministic=*/true}; 375 CoinsViewOverlay coins_view_cache{&backend_cache, /*deterministic=*/true}; 376 TestCoinsView(fuzzed_data_provider, coins_view_cache, backend_cache, /*is_db=*/false); 377 }