LCOV - code coverage report
Current view: top level - src/wallet - export.cpp (source / functions) Coverage Total Hit
Test: test_bitcoin_coverage.info Lines: 0.0 % 113 0
Test Date: 2026-07-11 06:48:01 Functions: 0.0 % 5 0
Branches: 0.0 % 242 0

             Branch data     Line data    Source code
       1                 :             : // Copyright (c) 2026-present 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/export.h>
       6                 :             : 
       7                 :             : #include <key_io.h>
       8                 :             : #include <util/fs.h>
       9                 :             : #include <util/expected.h>
      10                 :             : #include <wallet/scriptpubkeyman.h>
      11                 :             : #include <wallet/context.h>
      12                 :             : #include <wallet/sqlite.h>
      13                 :             : #include <wallet/wallet.h>
      14                 :             : 
      15                 :             : #include <fstream>
      16                 :             : 
      17                 :             : namespace wallet {
      18                 :           0 : util::Expected<std::vector<WalletDescInfo>, std::string> ExportDescriptors(const CWallet& wallet, bool export_private)
      19                 :             : {
      20                 :           0 :     AssertLockHeld(wallet.cs_wallet);
      21                 :           0 :     std::vector<WalletDescInfo> wallet_descriptors;
      22   [ #  #  #  # ]:           0 :     for (const auto& spk_man : wallet.GetAllScriptPubKeyMans()) {
      23         [ #  # ]:           0 :         const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
      24         [ #  # ]:           0 :         if (!desc_spk_man) {
      25         [ #  # ]:           0 :             return util::Unexpected{"Unexpected ScriptPubKey manager type."};
      26                 :             :         }
      27         [ #  # ]:           0 :         LOCK(desc_spk_man->cs_desc_man);
      28         [ #  # ]:           0 :         const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor();
      29         [ #  # ]:           0 :         std::string descriptor;
      30   [ #  #  #  # ]:           0 :         if (!Assume(desc_spk_man->GetDescriptorString(descriptor, export_private))) {
      31         [ #  # ]:           0 :             return util::Unexpected{"Can't get descriptor string."};
      32                 :             :         }
      33         [ #  # ]:           0 :         const bool is_range = wallet_descriptor.descriptor->IsRange();
      34                 :           0 :         wallet_descriptors.emplace_back(
      35                 :             :             descriptor,
      36                 :             :             wallet_descriptor.creation_time,
      37   [ #  #  #  # ]:           0 :             wallet.IsActiveScriptPubKeyMan(*desc_spk_man),
      38         [ #  # ]:           0 :             wallet.IsInternalScriptPubKeyMan(desc_spk_man),
      39         [ #  # ]:           0 :             is_range ? std::optional(std::make_pair(wallet_descriptor.range_start, wallet_descriptor.range_end)) : std::nullopt,
      40                 :             :             wallet_descriptor.next_index
      41                 :             :         );
      42   [ #  #  #  # ]:           0 :     }
      43                 :           0 :     return wallet_descriptors;
      44                 :           0 : }
      45                 :             : 
      46                 :           0 : util::Result<std::string> ExportWatchOnlyWallet(const CWallet& wallet, const fs::path& destination, WalletContext& context)
      47                 :             : {
      48                 :           0 :     AssertLockHeld(wallet.cs_wallet);
      49                 :             : 
      50         [ #  # ]:           0 :     if (destination.empty()) {
      51                 :           0 :         return util::Error{_("Error: Export destination cannot be empty")};
      52                 :             :     }
      53         [ #  # ]:           0 :     if (fs::exists(destination)) {
      54   [ #  #  #  # ]:           0 :         return util::Error{strprintf(_("Error: Export destination '%s' already exists"), fs::PathToString(destination))};
      55                 :             :     }
      56   [ #  #  #  #  :           0 :     if (!std::ofstream{fs::PathToString(destination)}) {
                   #  # ]
      57   [ #  #  #  # ]:           0 :         return util::Error{strprintf(_("Error: Could not create file '%s'"), fs::PathToString(destination))};
      58                 :             :     }
      59                 :           0 :     bool success = false;
      60         [ #  # ]:           0 :     auto cleanup_destination = interfaces::MakeCleanupHandler([&success, &destination] {
      61         [ #  # ]:           0 :         if (!success) fs::remove(destination);
      62         [ #  # ]:           0 :     });
      63                 :             : 
      64                 :             :     // Get the descriptors from this wallet
      65         [ #  # ]:           0 :     util::Expected<std::vector<WalletDescInfo>, std::string> exported = ExportDescriptors(wallet, /*export_private=*/false);
      66         [ #  # ]:           0 :     if (!exported) {
      67   [ #  #  #  # ]:           0 :         return util::Error{Untranslated(exported.error())};
      68                 :             :     }
      69         [ #  # ]:           0 :     if (exported->empty()) {
      70         [ #  # ]:           0 :         return util::Error{_("Error: Wallet has no descriptors to export")};
      71                 :             :     }
      72                 :             : 
      73                 :             :     // Make the wallet with the same flags as this wallet, but without private keys
      74         [ #  # ]:           0 :     const uint64_t create_flags = wallet.GetWalletFlags() | WALLET_FLAG_DISABLE_PRIVATE_KEYS;
      75                 :             : 
      76                 :             :     // Create the temporary watchonly wallet in memory to avoid leaving files on disk
      77                 :           0 :     std::vector<bilingual_str> warnings;
      78         [ #  # ]:           0 :     bilingual_str error;
      79         [ #  # ]:           0 :     WalletContext empty_context;
      80                 :           0 :     empty_context.args = context.args;
      81   [ #  #  #  #  :           0 :     std::shared_ptr<CWallet> watchonly_wallet = CWallet::CreateNew(empty_context, /*name=*/wallet.GetName() + "_watchonly_temp", MakeInMemoryWalletDatabase(), create_flags, /*born_encrypted=*/false, error, warnings);
                   #  # ]
      82         [ #  # ]:           0 :     if (!watchonly_wallet) {
      83         [ #  # ]:           0 :         return util::Error{strprintf(_("Error: Failed to create new watchonly wallet. %s"), error)};
      84                 :             :     }
      85                 :             : 
      86                 :           0 :     {
      87         [ #  # ]:           0 :         LOCK(watchonly_wallet->cs_wallet);
      88                 :             : 
      89                 :             :         // Parse the descriptors and add them to the new wallet
      90   [ #  #  #  # ]:           0 :         for (const WalletDescInfo& desc_info : *Assert(exported)) {
      91                 :             :             // Parse the descriptor
      92                 :           0 :             FlatSigningProvider dummy_keys;
      93         [ #  # ]:           0 :             std::string dummy_err;
      94   [ #  #  #  # ]:           0 :             std::vector<std::unique_ptr<Descriptor>> descs = Parse(desc_info.descriptor, dummy_keys, dummy_err, /*require_checksum=*/true);
      95   [ #  #  #  # ]:           0 :             CHECK_NONFATAL(descs.size() == 1); // All of our descriptors should be valid, and not multipath
      96         [ #  # ]:           0 :             CHECK_NONFATAL(dummy_keys.keys.size() == 0); // No private keys should be present in our exported descriptors
      97                 :             : 
      98                 :             :             // Get the range if there is one
      99                 :           0 :             int32_t range_start = 0;
     100                 :           0 :             int32_t range_end = 0;
     101         [ #  # ]:           0 :             if (desc_info.range) {
     102                 :           0 :                 range_start = desc_info.range->first;
     103                 :           0 :                 range_end = desc_info.range->second;
     104                 :             :             }
     105                 :             : 
     106   [ #  #  #  #  :           0 :             WalletDescriptor w_desc(std::move(descs.at(0)), desc_info.creation_time, range_start, range_end, desc_info.next_index);
                   #  # ]
     107                 :             : 
     108                 :             :             // For descriptors that cannot self expand (i.e. needs private keys or cache), retrieve the cache
     109                 :           0 :             uint256 desc_id = w_desc.id;
     110   [ #  #  #  # ]:           0 :             if (!w_desc.descriptor->CanSelfExpand()) {
     111   [ #  #  #  # ]:           0 :                 DescriptorScriptPubKeyMan* desc_spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(wallet.GetScriptPubKeyMan(desc_id));
     112   [ #  #  #  #  :           0 :                 w_desc.cache = WITH_LOCK(desc_spkm->cs_desc_man, return desc_spkm->GetWalletDescriptor().cache);
                   #  # ]
     113                 :             :             }
     114                 :             : 
     115                 :             :             // Add to the watchonly wallet
     116   [ #  #  #  #  :           0 :             if (auto spkm_res = watchonly_wallet->AddWalletDescriptor(w_desc, dummy_keys, /*label=*/"", /*internal=*/false); !spkm_res) {
                   #  # ]
     117         [ #  # ]:           0 :                 return util::Error{util::ErrorString(spkm_res)};
     118                 :           0 :             }
     119                 :             : 
     120                 :             :             // Set active spkms as active
     121         [ #  # ]:           0 :             if (desc_info.active) {
     122                 :             :                 // Determine whether this descriptor is internal
     123                 :             :                 // This is only set for active spkms
     124                 :           0 :                 bool internal = false;
     125         [ #  # ]:           0 :                 if (desc_info.internal) {
     126                 :           0 :                     internal = *desc_info.internal;
     127                 :             :                 }
     128   [ #  #  #  # ]:           0 :                 watchonly_wallet->AddActiveScriptPubKeyMan(desc_id, *Assert(w_desc.descriptor->GetOutputType()), internal);
     129                 :             :             }
     130                 :           0 :         }
     131                 :             : 
     132                 :             :         // Copy locked coins that are persisted
     133   [ #  #  #  # ]:           0 :         for (const auto& [coin, persisted] : wallet.m_locked_coins) {
     134         [ #  # ]:           0 :             if (!persisted) continue;
     135         [ #  # ]:           0 :             watchonly_wallet->LockCoin(coin, persisted);
     136                 :             :         }
     137                 :             : 
     138                 :           0 :         {
     139                 :             :             // Make a WalletBatch for the watchonly wallet so that everything else can be written atomically
     140         [ #  # ]:           0 :             WalletBatch watchonly_batch(watchonly_wallet->GetDatabase());
     141   [ #  #  #  # ]:           0 :             if (!watchonly_batch.TxnBegin()) {
     142         [ #  # ]:           0 :                 return util::Error{strprintf(_("Error: database transaction cannot be executed for new watchonly wallet %s"), watchonly_wallet->GetName())};
     143                 :             :             }
     144                 :             : 
     145                 :             :             // Copy orderPosNext
     146         [ #  # ]:           0 :             watchonly_batch.WriteOrderPosNext(wallet.nOrderPosNext);
     147                 :             : 
     148                 :             :             // Write the best block locator to avoid rescanning on reload
     149                 :           0 :             CBlockLocator best_block_locator;
     150                 :           0 :             {
     151         [ #  # ]:           0 :                 WalletBatch local_wallet_batch(wallet.GetDatabase());
     152   [ #  #  #  # ]:           0 :                 if (!local_wallet_batch.ReadBestBlock(best_block_locator)) {
     153         [ #  # ]:           0 :                     return util::Error{_("Error: Unable to read wallet's best block locator record")};
     154                 :             :                 }
     155                 :           0 :             }
     156   [ #  #  #  # ]:           0 :             if (!watchonly_batch.WriteBestBlock(best_block_locator)) {
     157         [ #  # ]:           0 :                 return util::Error{_("Error: Unable to write watchonly wallet best block locator record")};
     158                 :             :             }
     159                 :             : 
     160                 :             :             // Copy the transactions
     161   [ #  #  #  # ]:           0 :             for (const auto& [txid, wtx] : wallet.mapWallet) {
     162   [ #  #  #  # ]:           0 :                 if (!watchonly_wallet->LoadToWallet(txid, [&](CWalletTx& ins_wtx, bool new_tx) EXCLUSIVE_LOCKS_REQUIRED(watchonly_wallet->cs_wallet) {
     163         [ #  # ]:           0 :                     if (!new_tx) return false;
     164   [ #  #  #  # ]:           0 :                     ins_wtx.SetTx(wtx.tx);
     165                 :           0 :                     ins_wtx.CopyFrom(wtx);
     166                 :           0 :                     return true;
     167                 :             :                 })) {
     168   [ #  #  #  # ]:           0 :                     return util::Error{strprintf(_("Error: Could not add tx %s to watchonly wallet"), txid.GetHex())};
     169                 :             :                 }
     170   [ #  #  #  # ]:           0 :                 watchonly_batch.WriteTx(watchonly_wallet->mapWallet.at(txid));
     171                 :             :             }
     172                 :             : 
     173                 :             :             // Copy address book
     174   [ #  #  #  # ]:           0 :             for (const auto& [dest, entry] : wallet.m_address_book) {
     175         [ #  # ]:           0 :                 auto address{EncodeDestination(dest)};
     176   [ #  #  #  #  :           0 :                 if (entry.purpose) watchonly_batch.WritePurpose(address, PurposeToString(*entry.purpose));
                   #  # ]
     177   [ #  #  #  # ]:           0 :                 if (entry.label) watchonly_batch.WriteName(address, *entry.label);
     178   [ #  #  #  # ]:           0 :                 for (const auto& [id, request] : entry.receive_requests) {
     179         [ #  # ]:           0 :                     watchonly_batch.WriteAddressReceiveRequest(dest, id, request);
     180                 :             :                 }
     181   [ #  #  #  # ]:           0 :                 if (entry.previously_spent) watchonly_batch.WriteAddressPreviouslySpent(dest, true);
     182                 :           0 :             }
     183                 :             : 
     184   [ #  #  #  # ]:           0 :             if (!watchonly_batch.TxnCommit()) {
     185         [ #  # ]:           0 :                 return util::Error{_("Error: cannot commit db transaction for watchonly wallet export")};
     186                 :             :             }
     187                 :           0 :         }
     188                 :             : 
     189                 :             :         // Make a backup of this wallet at the specified destination directory
     190   [ #  #  #  #  :           0 :         if (!watchonly_wallet->BackupWallet(fs::PathToString(destination))) {
                   #  # ]
     191         [ #  # ]:           0 :             return util::Error{_("Error: Unable to write the exported wallet")};
     192                 :             :         }
     193         [ #  # ]:           0 :         success = true;
     194                 :           0 :     }
     195                 :             : 
     196         [ #  # ]:           0 :     return fs::PathToString(destination);
     197                 :           0 : }
     198                 :             : } // namespace wallet
        

Generated by: LCOV version 2.0-1