LCOV - code coverage report
Current view: top level - src/wallet/test - walletload_tests.cpp (source / functions) Coverage Total Hit
Test: total_coverage.info Lines: 85.5 % 124 106
Test Date: 2024-11-04 05:10:19 Functions: 34.8 % 23 8
Branches: 49.0 % 522 256

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

Generated by: LCOV version 2.0-1