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
|