export.cpp raw

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