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