LCOV - code coverage report
Current view: top level - src/wallet/test/fuzz - notifications.cpp (source / functions) Hit Total Coverage
Test: fuzz_coverage.info Lines: 16 113 14.2 %
Date: 2023-09-26 12:08:55 Functions: 10 18 55.6 %

          Line data    Source code
       1             : // Copyright (c) 2021-2022 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 <test/fuzz/FuzzedDataProvider.h>
       6             : #include <test/fuzz/fuzz.h>
       7             : #include <test/fuzz/util.h>
       8             : #include <test/util/setup_common.h>
       9             : #include <util/translation.h>
      10             : #include <wallet/context.h>
      11             : #include <wallet/receive.h>
      12             : #include <wallet/wallet.h>
      13             : #include <wallet/walletdb.h>
      14             : #include <wallet/walletutil.h>
      15             : 
      16             : #include <cassert>
      17         173 : #include <cstdint>
      18         173 : #include <string>
      19             : #include <vector>
      20             : 
      21             : namespace wallet {
      22             : namespace {
      23             : const TestingSetup* g_setup;
      24             : 
      25           0 : void initialize_setup()
      26             : {
      27         173 :     static const auto testing_setup = MakeNoLogFileContext<const TestingSetup>();
      28           0 :     g_setup = testing_setup.get();
      29           0 : }
      30             : 
      31             : /**
      32             :  * Wraps a descriptor wallet for fuzzing. The constructor writes the sqlite db
      33             :  * to disk, the destructor deletes it.
      34             :  */
      35             : struct FuzzedWallet {
      36             :     ArgsManager args;
      37             :     WalletContext context;
      38             :     std::shared_ptr<CWallet> wallet;
      39           0 :     FuzzedWallet(const std::string& name)
      40             :     {
      41           0 :         context.args = &args;
      42           0 :         context.chain = g_setup->m_node.chain.get();
      43             : 
      44           0 :         DatabaseOptions options;
      45           0 :         options.require_create = true;
      46           0 :         options.create_flags = WALLET_FLAG_DESCRIPTORS;
      47           0 :         const std::optional<bool> load_on_start;
      48           0 :         gArgs.ForceSetArg("-keypool", "0"); // Avoid timeout in TopUp()
      49             : 
      50             :         DatabaseStatus status;
      51           0 :         bilingual_str error;
      52           0 :         std::vector<bilingual_str> warnings;
      53           0 :         wallet = CreateWallet(context, name, load_on_start, options, status, error, warnings);
      54           0 :         assert(wallet);
      55           0 :         assert(error.empty());
      56           0 :         assert(warnings.empty());
      57           0 :         assert(wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
      58           0 :     }
      59           0 :     ~FuzzedWallet()
      60             :     {
      61           0 :         const auto name{wallet->GetName()};
      62           0 :         std::vector<bilingual_str> warnings;
      63           0 :         std::optional<bool> load_on_start;
      64           0 :         assert(RemoveWallet(context, wallet, load_on_start, warnings));
      65           0 :         assert(warnings.empty());
      66           0 :         UnloadWallet(std::move(wallet));
      67           0 :         fs::remove_all(GetWalletDir() / fs::PathFromString(name));
      68           0 :     }
      69           0 :     CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider)
      70             :     {
      71           0 :         auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)};
      72           0 :         util::Result<CTxDestination> op_dest{util::Error{}};
      73           0 :         if (fuzzed_data_provider.ConsumeBool()) {
      74         173 :             op_dest = wallet->GetNewDestination(type, "");
      75           0 :         } else {
      76           0 :             op_dest = wallet->GetNewChangeDestination(type);
      77             :         }
      78           0 :         return GetScriptForDestination(*Assert(op_dest));
      79           0 :     }
      80             : };
      81             : 
      82         346 : FUZZ_TARGET(wallet_notifications, .init = initialize_setup)
      83             : {
      84           0 :     FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
      85             :     // The total amount, to be distributed to the wallets a and b in txs
      86             :     // without fee. Thus, the balance of the wallets should always equal the
      87             :     // total amount.
      88           0 :     const auto total_amount{ConsumeMoney(fuzzed_data_provider)};
      89           0 :     FuzzedWallet a{"fuzzed_wallet_a"};
      90           0 :     FuzzedWallet b{"fuzzed_wallet_b"};
      91         173 : 
      92             :     // Keep track of all coins in this test.
      93             :     // Each tuple in the chain represents the coins and the block created with
      94             :     // those coins. Once the block is mined, the next tuple will have an empty
      95             :     // block and the freshly mined coins.
      96             :     using Coins = std::set<std::tuple<CAmount, COutPoint>>;
      97           0 :     std::vector<std::tuple<Coins, CBlock>> chain;
      98             :     {
      99         173 :         // Add the initial entry
     100           0 :         chain.emplace_back();
     101           0 :         auto& [coins, block]{chain.back()};
     102           0 :         coins.emplace(total_amount, COutPoint{uint256::ONE, 1});
     103             :     }
     104           0 :     LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 200)
     105             :     {
     106           0 :         CallOneOf(
     107             :             fuzzed_data_provider,
     108           0 :             [&] {
     109           0 :                 auto& [coins_orig, block]{chain.back()};
     110             :                 // Copy the coins for this block and consume all of them
     111           0 :                 Coins coins = coins_orig;
     112           0 :                 while (!coins.empty()) {
     113             :                     // Create a new tx
     114           0 :                     CMutableTransaction tx{};
     115             :                     // Add some coins as inputs to it
     116           0 :                     auto num_inputs{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, coins.size())};
     117           0 :                     CAmount in{0};
     118           0 :                     while (num_inputs-- > 0) {
     119           0 :                         const auto& [coin_amt, coin_outpoint]{*coins.begin()};
     120           0 :                         in += coin_amt;
     121           0 :                         tx.vin.emplace_back(coin_outpoint);
     122           0 :                         coins.erase(coins.begin());
     123             :                     }
     124             :                     // Create some outputs spending all inputs, without fee
     125           0 :                     LIMITED_WHILE(in > 0 && fuzzed_data_provider.ConsumeBool(), 100)
     126             :                     {
     127           0 :                         const auto out_value{ConsumeMoney(fuzzed_data_provider, in)};
     128           0 :                         in -= out_value;
     129           0 :                         auto& wallet{fuzzed_data_provider.ConsumeBool() ? a : b};
     130           0 :                         tx.vout.emplace_back(out_value, wallet.GetScriptPubKey(fuzzed_data_provider));
     131           0 :                     }
     132             :                     // Spend the remaining input value, if any
     133           0 :                     auto& wallet{fuzzed_data_provider.ConsumeBool() ? a : b};
     134           0 :                     tx.vout.emplace_back(in, wallet.GetScriptPubKey(fuzzed_data_provider));
     135             :                     // Add tx to block
     136           0 :                     block.vtx.emplace_back(MakeTransactionRef(tx));
     137           0 :                 }
     138             :                 // Mine block
     139           0 :                 const uint256& hash = block.GetHash();
     140           0 :                 interfaces::BlockInfo info{hash};
     141           0 :                 info.prev_hash = &block.hashPrevBlock;
     142           0 :                 info.height = chain.size();
     143           0 :                 info.data = &block;
     144             :                 // Ensure that no blocks are skipped by the wallet by setting the chain's accumulated
     145             :                 // time to the maximum value. This ensures that the wallet's birth time is always
     146             :                 // earlier than this maximum time.
     147           0 :                 info.chain_time_max = std::numeric_limits<unsigned int>::max();
     148           0 :                 a.wallet->blockConnected(info);
     149           0 :                 b.wallet->blockConnected(info);
     150             :                 // Store the coins for the next block
     151           0 :                 Coins coins_new;
     152           0 :                 for (const auto& tx : block.vtx) {
     153           0 :                     uint32_t i{0};
     154           0 :                     for (const auto& out : tx->vout) {
     155           0 :                         coins_new.emplace(out.nValue, COutPoint{tx->GetHash(), i++});
     156             :                     }
     157             :                 }
     158           0 :                 chain.emplace_back(coins_new, CBlock{});
     159           0 :             },
     160           0 :             [&] {
     161           0 :                 if (chain.size() <= 1) return; // The first entry can't be removed
     162           0 :                 auto& [coins, block]{chain.back()};
     163         173 :                 if (block.vtx.empty()) return; // Can only disconnect if the block was submitted first
     164         173 :                 // Disconnect block
     165         173 :                 const uint256& hash = block.GetHash();
     166         173 :                 interfaces::BlockInfo info{hash};
     167         173 :                 info.prev_hash = &block.hashPrevBlock;
     168         173 :                 info.height = chain.size() - 1;
     169         173 :                 info.data = &block;
     170         173 :                 a.wallet->blockDisconnected(info);
     171           0 :                 b.wallet->blockDisconnected(info);
     172           0 :                 chain.pop_back();
     173           0 :             });
     174           0 :         auto& [coins, first_block]{chain.front()};
     175           0 :         if (!first_block.vtx.empty()) {
     176             :             // Only check balance when at least one block was submitted
     177           0 :             const auto bal_a{GetBalance(*a.wallet).m_mine_trusted};
     178           0 :             const auto bal_b{GetBalance(*b.wallet).m_mine_trusted};
     179           0 :             assert(total_amount == bal_a + bal_b);
     180           0 :         }
     181           0 :     }
     182           0 : }
     183             : } // namespace
     184             : } // namespace wallet

Generated by: LCOV version 1.14