LCOV - code coverage report
Current view: top level - src/wallet/test - walletload_tests.cpp (source / functions) Coverage Total Hit
Test: test_bitcoin_coverage.info Lines: 84.8 % 125 106
Test Date: 2025-04-25 04:35:45 Functions: 41.7 % 24 10
Branches: 49.0 % 526 258

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2022 The Bitcoin Core developers
       2                 :             : // Distributed under the MIT software license, see the accompanying
       3                 :             : // file COPYING or https://www.opensource.org/licenses/mit-license.php.
       4                 :             : 
       5                 :             : #include <wallet/test/util.h>
       6                 :             : #include <wallet/wallet.h>
       7                 :             : #include <test/util/logging.h>
       8                 :             : #include <test/util/setup_common.h>
       9                 :             : 
      10                 :             : #include <boost/test/unit_test.hpp>
      11                 :             : 
      12                 :             : namespace wallet {
      13                 :             : 
      14                 :             : BOOST_AUTO_TEST_SUITE(walletload_tests)
      15                 :             : 
      16                 :             : class DummyDescriptor final : public Descriptor {
      17                 :             : private:
      18                 :             :     std::string desc;
      19                 :             : public:
      20         [ +  - ]:           2 :     explicit DummyDescriptor(const std::string& descriptor) : desc(descriptor) {};
      21                 :           2 :     ~DummyDescriptor() = default;
      22                 :             : 
      23                 :           4 :     std::string ToString(bool compat_format) const override { return desc; }
      24                 :           0 :     std::optional<OutputType> GetOutputType() const override { return OutputType::UNKNOWN; }
      25                 :             : 
      26                 :           0 :     bool IsRange() const override { return false; }
      27                 :           0 :     bool IsSolvable() const override { return false; }
      28                 :           0 :     bool IsSingleType() const override { return true; }
      29                 :           0 :     bool IsSingleKey() const override { return true; }
      30                 :           0 :     bool ToPrivateString(const SigningProvider& provider, std::string& out) const override { return false; }
      31                 :           0 :     bool ToNormalizedString(const SigningProvider& provider, std::string& out, const DescriptorCache* cache = nullptr) const override { return false; }
      32                 :           0 :     bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out, DescriptorCache* write_cache = nullptr) const override { return false; };
      33                 :           0 :     bool ExpandFromCache(int pos, const DescriptorCache& read_cache, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override { return false; }
      34                 :           0 :     void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const override {}
      35                 :           0 :     std::optional<int64_t> ScriptSize() const override { return {}; }
      36                 :           0 :     std::optional<int64_t> MaxSatisfactionWeight(bool) const override { return {}; }
      37                 :           0 :     std::optional<int64_t> MaxSatisfactionElems() const override { return {}; }
      38                 :           0 :     void GetPubKeys(std::set<CPubKey>& pubkeys, std::set<CExtPubKey>& ext_pubs) const override {}
      39                 :             : };
      40                 :             : 
      41   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(wallet_load_descriptors, TestingSetup)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
      42                 :             : {
      43         [ +  - ]:           1 :     std::unique_ptr<WalletDatabase> database = CreateMockableWalletDatabase();
      44                 :           1 :     {
      45                 :             :         // Write unknown active descriptor
      46         [ +  - ]:           1 :         WalletBatch batch(*database, false);
      47         [ +  - ]:           1 :         std::string unknown_desc = "trx(tpubD6NzVbkrYhZ4Y4S7m6Y5s9GD8FqEMBy56AGphZXuagajudVZEnYyBahZMgHNCTJc2at82YX6s8JiL1Lohu5A3v1Ur76qguNH4QVQ7qYrBQx/86'/1'/0'/0/*)#8pn8tzdt";
      48   [ +  -  +  -  :           2 :         WalletDescriptor wallet_descriptor(std::make_shared<DummyDescriptor>(unknown_desc), 0, 0, 0, 0);
                   -  + ]
      49   [ +  -  +  -  :           2 :         BOOST_CHECK(batch.WriteDescriptor(uint256(), wallet_descriptor));
             +  -  +  - ]
      50   [ +  -  +  -  :           2 :         BOOST_CHECK(batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(OutputType::UNKNOWN), uint256(), false));
                   +  - ]
      51                 :           1 :     }
      52                 :             : 
      53                 :           1 :     {
      54                 :             :         // Now try to load the wallet and verify the error.
      55   [ +  -  +  -  :           2 :         const std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(database)));
          +  -  +  -  -  
                      - ]
      56   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::UNKNOWN_DESCRIPTOR);
             +  -  +  - ]
      57                 :           0 :     }
      58                 :             : 
      59                 :             :     // Test 2
      60                 :             :     // Now write a valid descriptor with an invalid ID.
      61                 :             :     // As the software produces another ID for the descriptor, the loading process must be aborted.
      62         [ +  - ]:           2 :     database = CreateMockableWalletDatabase();
      63                 :             : 
      64                 :             :     // Verify the error
      65                 :           1 :     bool found = false;
      66         [ +  - ]:           3 :     DebugLogHelper logHelper("The descriptor ID calculated by the wallet differs from the one in DB", [&](const std::string* s) {
      67                 :           2 :         found = true;
      68                 :           2 :         return false;
      69   [ +  -  +  - ]:           2 :     });
      70                 :             : 
      71                 :           1 :     {
      72                 :             :         // Write valid descriptor with invalid ID
      73         [ +  - ]:           1 :         WalletBatch batch(*database, false);
      74         [ +  - ]:           1 :         std::string desc = "wpkh([d34db33f/84h/0h/0h]xpub6DJ2dNUysrn5Vt36jH2KLBT2i1auw1tTSSomg8PhqNiUtx8QX2SvC9nrHu81fT41fvDUnhMjEzQgXnQjKEu3oaqMSzhSrHMxyyoEAmUHQbY/0/*)#cjjspncu";
      75   [ +  -  +  -  :           2 :         WalletDescriptor wallet_descriptor(std::make_shared<DummyDescriptor>(desc), 0, 0, 0, 0);
                   -  + ]
      76   [ +  -  +  -  :           2 :         BOOST_CHECK(batch.WriteDescriptor(uint256::ONE, wallet_descriptor));
                   +  - ]
      77                 :           1 :     }
      78                 :             : 
      79                 :           1 :     {
      80                 :             :         // Now try to load the wallet and verify the error.
      81   [ +  -  +  -  :           2 :         const std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", std::move(database)));
          +  -  +  -  -  
                      - ]
      82   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::CORRUPT);
                   +  - ]
      83   [ +  -  +  -  :           2 :         BOOST_CHECK(found); // The error must be logged
             +  -  +  - ]
      84                 :           0 :     }
      85                 :           1 : }
      86                 :             : 
      87                 :           6 : bool HasAnyRecordOfType(WalletDatabase& db, const std::string& key)
      88                 :             : {
      89                 :           6 :     std::unique_ptr<DatabaseBatch> batch = db.MakeBatch(false);
      90   [ +  -  +  -  :          12 :     BOOST_CHECK(batch);
                   +  - ]
      91         [ +  - ]:           6 :     std::unique_ptr<DatabaseCursor> cursor = batch->GetNewCursor();
      92   [ +  -  +  - ]:          12 :     BOOST_CHECK(cursor);
      93                 :          24 :     while (true) {
      94                 :          15 :         DataStream ssKey{};
      95                 :          15 :         DataStream ssValue{};
      96         [ +  - ]:          15 :         DatabaseCursor::Status status = cursor->Next(ssKey, ssValue);
      97         [ -  + ]:          15 :         assert(status != DatabaseCursor::Status::FAIL);
      98         [ +  + ]:          15 :         if (status == DatabaseCursor::Status::DONE) break;
      99         [ +  - ]:          12 :         std::string type;
     100         [ +  - ]:          12 :         ssKey >> type;
     101         [ +  + ]:          12 :         if (type == key) return true;
     102                 :          15 :     }
     103                 :           3 :     return false;
     104                 :           6 : }
     105                 :             : 
     106                 :             : template<typename... Args>
     107                 :           2 : SerializeData MakeSerializeData(const Args&... args)
     108                 :             : {
     109                 :           2 :     DataStream s{};
     110         [ +  - ]:           2 :     SerializeMany(s, args...);
     111         [ +  - ]:           2 :     return {s.begin(), s.end()};
     112                 :           2 : }
     113                 :             : 
     114                 :             : 
     115   [ +  -  +  -  :           7 : BOOST_FIXTURE_TEST_CASE(wallet_load_ckey, TestingSetup)
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
          -  +  -  +  -  
          +  -  +  -  +  
                      - ]
     116                 :             : {
     117                 :           1 :     SerializeData ckey_record_key;
     118                 :           1 :     SerializeData ckey_record_value;
     119         [ +  - ]:           1 :     MockableData records;
     120                 :             : 
     121                 :           1 :     {
     122                 :             :         // Context setup.
     123                 :             :         // Create and encrypt legacy wallet
     124   [ +  -  +  -  :           2 :         std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase()));
          +  -  +  -  +  
                -  -  - ]
     125         [ +  - ]:           1 :         LOCK(wallet->cs_wallet);
     126         [ +  - ]:           1 :         auto legacy_spkm = wallet->GetOrCreateLegacyScriptPubKeyMan();
     127   [ +  -  +  -  :           2 :         BOOST_CHECK(legacy_spkm->SetupGeneration(true));
             +  -  +  - ]
     128                 :             : 
     129                 :             :         // Retrieve a key
     130   [ +  -  +  -  :           2 :         CTxDestination dest = *Assert(legacy_spkm->GetNewDestination(OutputType::LEGACY));
                   +  - ]
     131         [ +  - ]:           1 :         CKeyID key_id = GetKeyForDestination(*legacy_spkm, dest);
     132                 :           1 :         CKey first_key;
     133   [ +  -  +  -  :           2 :         BOOST_CHECK(legacy_spkm->GetKey(key_id, first_key));
             +  -  +  - ]
     134                 :             : 
     135                 :             :         // Encrypt the wallet
     136   [ +  -  +  -  :           2 :         BOOST_CHECK(wallet->EncryptWallet("encrypt"));
          +  -  +  -  +  
                      - ]
     137         [ +  - ]:           1 :         wallet->Flush();
     138                 :             : 
     139                 :             :         // Store a copy of all the records
     140   [ +  -  +  - ]:           1 :         records = GetMockableDatabase(*wallet).m_records;
     141                 :             : 
     142                 :             :         // Get the record for the retrieved key
     143   [ +  -  +  - ]:           2 :         ckey_record_key = MakeSerializeData(DBKeys::CRYPTED_KEY, first_key.GetPubKey());
     144   [ +  -  +  - ]:           1 :         ckey_record_value = records.at(ckey_record_key);
     145   [ +  -  +  - ]:           2 :     }
     146                 :             : 
     147                 :           1 :     {
     148                 :             :         // First test case:
     149                 :             :         // Erase all the crypted keys from db and unlock the wallet.
     150                 :             :         // The wallet will only re-write the crypted keys to db if any checksum is missing at load time.
     151                 :             :         // So, if any 'ckey' record re-appears on db, then the checksums were not properly calculated, and we are re-writing
     152                 :             :         // the records every time that 'CWallet::Unlock' gets called, which is not good.
     153                 :             : 
     154                 :             :         // Load the wallet and check that is encrypted
     155   [ +  -  +  -  :           2 :         std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
          +  -  +  -  +  
             -  +  -  -  
                      - ]
     156   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::LOAD_OK);
                   +  - ]
     157   [ +  -  +  -  :           2 :         BOOST_CHECK(wallet->IsCrypted());
             +  -  +  - ]
     158   [ +  -  +  -  :           2 :         BOOST_CHECK(HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY));
             +  -  +  - ]
     159                 :             : 
     160                 :             :         // Now delete all records and check that the 'Unlock' function doesn't re-write them
     161   [ +  -  +  -  :           2 :         BOOST_CHECK(wallet->GetLegacyScriptPubKeyMan()->DeleteRecords());
          +  -  +  -  +  
                      - ]
     162   [ +  -  +  -  :           2 :         BOOST_CHECK(!HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY));
             +  -  +  - ]
     163   [ +  -  +  -  :           2 :         BOOST_CHECK(wallet->Unlock("encrypt"));
          +  -  +  -  +  
                      - ]
     164   [ +  -  +  -  :           2 :         BOOST_CHECK(!HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY));
             +  -  +  - ]
     165                 :           0 :     }
     166                 :             : 
     167                 :           1 :     {
     168                 :             :         // Second test case:
     169                 :             :         // Verify that loading up a 'ckey' with no checksum triggers a complete re-write of the crypted keys.
     170                 :             : 
     171                 :             :         // Cut off the 32 byte checksum from a ckey record
     172   [ +  -  +  - ]:           1 :         records[ckey_record_key].resize(ckey_record_value.size() - 32);
     173                 :             : 
     174                 :             :         // Load the wallet and check that is encrypted
     175   [ +  -  +  -  :           2 :         std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
          +  -  +  -  +  
             -  +  -  -  
                      - ]
     176   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::LOAD_OK);
                   +  - ]
     177   [ +  -  +  -  :           2 :         BOOST_CHECK(wallet->IsCrypted());
             +  -  +  - ]
     178   [ +  -  +  -  :           2 :         BOOST_CHECK(HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY));
             +  -  +  - ]
     179                 :             : 
     180                 :             :         // Now delete all ckey records and check that the 'Unlock' function re-writes them
     181                 :             :         // (this is because the wallet, at load time, found a ckey record with no checksum)
     182   [ +  -  +  -  :           2 :         BOOST_CHECK(wallet->GetLegacyScriptPubKeyMan()->DeleteRecords());
          +  -  +  -  +  
                      - ]
     183   [ +  -  +  -  :           2 :         BOOST_CHECK(!HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY));
             +  -  +  - ]
     184   [ +  -  +  -  :           2 :         BOOST_CHECK(wallet->Unlock("encrypt"));
          +  -  +  -  +  
                      - ]
     185   [ +  -  +  -  :           2 :         BOOST_CHECK(HasAnyRecordOfType(wallet->GetDatabase(), DBKeys::CRYPTED_KEY));
             +  -  +  - ]
     186                 :           0 :     }
     187                 :             : 
     188                 :           1 :     {
     189                 :             :         // Third test case:
     190                 :             :         // Verify that loading up a 'ckey' with an invalid checksum throws an error.
     191                 :             : 
     192                 :             :         // Cut off the 32 byte checksum from a ckey record
     193   [ +  -  +  - ]:           1 :         records[ckey_record_key].resize(ckey_record_value.size() - 32);
     194                 :             :         // Fill in the checksum space with 0s
     195   [ +  -  +  - ]:           1 :         records[ckey_record_key].resize(ckey_record_value.size());
     196                 :             : 
     197   [ +  -  +  -  :           2 :         std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
          +  -  +  -  +  
             -  +  -  -  
                      - ]
     198   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::CORRUPT);
             +  -  +  - ]
     199                 :           0 :     }
     200                 :             : 
     201                 :           1 :     {
     202                 :             :         // Fourth test case:
     203                 :             :         // Verify that loading up a 'ckey' with an invalid pubkey throws an error
     204         [ +  - ]:           1 :         CPubKey invalid_key;
     205   [ +  -  +  -  :           2 :         BOOST_CHECK(!invalid_key.IsValid());
                   +  - ]
     206         [ +  - ]:           1 :         SerializeData key = MakeSerializeData(DBKeys::CRYPTED_KEY, invalid_key);
     207   [ +  -  +  - ]:           1 :         records[key] = ckey_record_value;
     208                 :             : 
     209   [ +  -  +  -  :           2 :         std::shared_ptr<CWallet> wallet(new CWallet(m_node.chain.get(), "", CreateMockableWalletDatabase(records)));
          +  -  +  -  +  
             -  +  -  -  
                      - ]
     210   [ +  -  +  -  :           1 :         BOOST_CHECK_EQUAL(wallet->LoadWallet(), DBErrors::CORRUPT);
             +  -  +  - ]
     211                 :           1 :     }
     212                 :           1 : }
     213                 :             : 
     214                 :             : BOOST_AUTO_TEST_SUITE_END()
     215                 :             : } // namespace wallet
        

Generated by: LCOV version 2.0-1