Bitcoin Core 31.99.0
P2P Digital Currency
export.cpp
Go to the documentation of this file.
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>
11#include <wallet/context.h>
12#include <wallet/wallet.h>
13
14#include <fstream>
15
16namespace wallet {
18{
19 AssertLockHeld(wallet.cs_wallet);
20 std::vector<WalletDescInfo> wallet_descriptors;
21 for (const auto& spk_man : wallet.GetAllScriptPubKeyMans()) {
22 const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
23 if (!desc_spk_man) {
24 return util::Unexpected{"Unexpected ScriptPubKey manager type."};
25 }
26 LOCK(desc_spk_man->cs_desc_man);
27 const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor();
28 std::string descriptor;
29 if (!Assume(desc_spk_man->GetDescriptorString(descriptor, export_private))) {
30 return util::Unexpected{"Can't get descriptor string."};
31 }
32 const bool is_range = wallet_descriptor.descriptor->IsRange();
33 wallet_descriptors.emplace_back(
34 descriptor,
35 wallet_descriptor.creation_time,
36 wallet.IsActiveScriptPubKeyMan(*desc_spk_man),
37 wallet.IsInternalScriptPubKeyMan(desc_spk_man),
38 is_range ? std::optional(std::make_pair(wallet_descriptor.range_start, wallet_descriptor.range_end)) : std::nullopt,
39 wallet_descriptor.next_index
40 );
41 }
42 return wallet_descriptors;
43}
44
45util::Result<std::string> ExportWatchOnlyWallet(const CWallet& wallet, const fs::path& destination, WalletContext& context)
46{
47 AssertLockHeld(wallet.cs_wallet);
48
49 if (destination.empty()) {
50 return util::Error{_("Error: Export destination cannot be empty")};
51 }
52 if (fs::exists(destination)) {
53 return util::Error{strprintf(_("Error: Export destination '%s' already exists"), fs::PathToString(destination))};
54 }
55 if (!std::ofstream{fs::PathToString(destination)}) {
56 return util::Error{strprintf(_("Error: Could not create file '%s'"), fs::PathToString(destination))};
57 }
58 bool success = false;
59 auto cleanup_destination = interfaces::MakeCleanupHandler([&success, &destination] {
60 if (!success) fs::remove(destination);
61 });
62
63 // Get the descriptors from this wallet
64 util::Expected<std::vector<WalletDescInfo>, std::string> exported = ExportDescriptors(wallet, /*export_private=*/false);
65 if (!exported) {
66 return util::Error{Untranslated(exported.error())};
67 }
68 if (exported->empty()) {
69 return util::Error{_("Error: Wallet has no descriptors to export")};
70 }
71
72 // Setup DatabaseOptions to create a new sqlite database
73 DatabaseOptions options;
74 options.require_existing = false;
75 options.require_create = true;
77
78 // Make the wallet with the same flags as this wallet, but without private keys
79 options.create_flags = wallet.GetWalletFlags() | WALLET_FLAG_DISABLE_PRIVATE_KEYS;
80
81 // Make the watchonly wallet
82 DatabaseStatus status;
83 std::vector<bilingual_str> warnings;
84 std::string wallet_name = wallet.GetName() + "_watchonly_temp";
85 bilingual_str error;
86 std::unique_ptr<WalletDatabase> database = MakeWalletDatabase(wallet_name, options, status, error);
87 if (!database) {
88 return util::Error{strprintf(_("Wallet file creation failed: %s"), error)};
89 }
90
91 // Always remove the temporary wallet files, even when returning early on error.
92 std::shared_ptr<CWallet> watchonly_wallet;
93 fs::path wallet_path = fs::PathFromString(database->Filename()).parent_path();
94 std::vector<fs::path> cleanup_files = database->Files();
95 auto cleanup_watchonly_wallet = interfaces::MakeCleanupHandler([&watchonly_wallet, &wallet_path, &cleanup_files] {
96 if (watchonly_wallet) watchonly_wallet.reset();
97 for (const auto& file : cleanup_files) {
98 fs::remove(file);
99 }
100 fs::remove(wallet_path);
101 });
102
103 WalletContext empty_context;
104 empty_context.args = context.args;
105 watchonly_wallet = CWallet::CreateNew(empty_context, wallet_name, std::move(database), options.create_flags, /*born_encrypted=*/false, error, warnings);
106 if (!watchonly_wallet) {
107 return util::Error{strprintf(_("Error: Failed to create new watchonly wallet. %s"), error)};
108 }
109
110 {
111 LOCK(watchonly_wallet->cs_wallet);
112
113 // Parse the descriptors and add them to the new wallet
114 for (const WalletDescInfo& desc_info : *Assert(exported)) {
115 // Parse the descriptor
116 FlatSigningProvider dummy_keys;
117 std::string dummy_err;
118 std::vector<std::unique_ptr<Descriptor>> descs = Parse(desc_info.descriptor, dummy_keys, dummy_err, /*require_checksum=*/true);
119 CHECK_NONFATAL(descs.size() == 1); // All of our descriptors should be valid, and not multipath
120 CHECK_NONFATAL(dummy_keys.keys.size() == 0); // No private keys should be present in our exported descriptors
121
122 // Get the range if there is one
123 int32_t range_start = 0;
124 int32_t range_end = 0;
125 if (desc_info.range) {
126 range_start = desc_info.range->first;
127 range_end = desc_info.range->second;
128 }
129
130 WalletDescriptor w_desc(std::move(descs.at(0)), desc_info.creation_time, range_start, range_end, desc_info.next_index);
131
132 // For descriptors that cannot self expand (i.e. needs private keys or cache), retrieve the cache
133 uint256 desc_id = w_desc.id;
134 if (!w_desc.descriptor->CanSelfExpand()) {
135 DescriptorScriptPubKeyMan* desc_spkm = dynamic_cast<DescriptorScriptPubKeyMan*>(wallet.GetScriptPubKeyMan(desc_id));
136 w_desc.cache = WITH_LOCK(desc_spkm->cs_desc_man, return desc_spkm->GetWalletDescriptor().cache);
137 }
138
139 // Add to the watchonly wallet
140 if (auto spkm_res = watchonly_wallet->AddWalletDescriptor(w_desc, dummy_keys, /*label=*/"", /*internal=*/false); !spkm_res) {
141 return util::Error{util::ErrorString(spkm_res)};
142 }
143
144 // Set active spkms as active
145 if (desc_info.active) {
146 // Determine whether this descriptor is internal
147 // This is only set for active spkms
148 bool internal = false;
149 if (desc_info.internal) {
150 internal = *desc_info.internal;
151 }
152 watchonly_wallet->AddActiveScriptPubKeyMan(desc_id, *Assert(w_desc.descriptor->GetOutputType()), internal);
153 }
154 }
155
156 // Copy locked coins that are persisted
157 for (const auto& [coin, persisted] : wallet.m_locked_coins) {
158 if (!persisted) continue;
159 watchonly_wallet->LockCoin(coin, persisted);
160 }
161
162 {
163 // Make a WalletBatch for the watchonly wallet so that everything else can be written atomically
164 WalletBatch watchonly_batch(watchonly_wallet->GetDatabase());
165 if (!watchonly_batch.TxnBegin()) {
166 return util::Error{strprintf(_("Error: database transaction cannot be executed for new watchonly wallet %s"), watchonly_wallet->GetName())};
167 }
168
169 // Copy orderPosNext
170 watchonly_batch.WriteOrderPosNext(wallet.nOrderPosNext);
171
172 // Write the best block locator to avoid rescanning on reload
173 CBlockLocator best_block_locator;
174 {
175 WalletBatch local_wallet_batch(wallet.GetDatabase());
176 if (!local_wallet_batch.ReadBestBlock(best_block_locator)) {
177 return util::Error{_("Error: Unable to read wallet's best block locator record")};
178 }
179 }
180 if (!watchonly_batch.WriteBestBlock(best_block_locator)) {
181 return util::Error{_("Error: Unable to write watchonly wallet best block locator record")};
182 }
183
184 // Copy the transactions
185 for (const auto& [txid, wtx] : wallet.mapWallet) {
186 if (!watchonly_wallet->LoadToWallet(txid, [&](CWalletTx& ins_wtx, bool new_tx) EXCLUSIVE_LOCKS_REQUIRED(watchonly_wallet->cs_wallet) {
187 if (!new_tx) return false;
188 ins_wtx.SetTx(wtx.tx);
189 ins_wtx.CopyFrom(wtx);
190 return true;
191 })) {
192 return util::Error{strprintf(_("Error: Could not add tx %s to watchonly wallet"), txid.GetHex())};
193 }
194 watchonly_batch.WriteTx(watchonly_wallet->mapWallet.at(txid));
195 }
196
197 // Copy address book
198 for (const auto& [dest, entry] : wallet.m_address_book) {
199 auto address{EncodeDestination(dest)};
200 if (entry.purpose) watchonly_batch.WritePurpose(address, PurposeToString(*entry.purpose));
201 if (entry.label) watchonly_batch.WriteName(address, *entry.label);
202 for (const auto& [id, request] : entry.receive_requests) {
203 watchonly_batch.WriteAddressReceiveRequest(dest, id, request);
204 }
205 if (entry.previously_spent) watchonly_batch.WriteAddressPreviouslySpent(dest, true);
206 }
207
208 if (!watchonly_batch.TxnCommit()) {
209 return util::Error{_("Error: cannot commit db transaction for watchonly wallet export")};
210 }
211 }
212
213 // Make a backup of this wallet at the specified destination directory
214 if (!watchonly_wallet->BackupWallet(fs::PathToString(destination))) {
215 return util::Error{_("Error: Unable to write the exported wallet")};
216 }
217 success = true;
218 }
219
220 return fs::PathToString(destination);
221}
222} // namespace wallet
#define CHECK_NONFATAL(condition)
Identity function.
Definition: check.h:112
#define Assert(val)
Identity function.
Definition: check.h:116
#define Assume(val)
Assume is the identity function.
Definition: check.h:128
256-bit opaque blob.
Definition: uint256.h:196
The util::Expected class provides a standard way for low-level functions to return either error value...
Definition: expected.h:44
The util::Unexpected class represents an unexpected value stored in util::Expected.
Definition: expected.h:21
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Definition: wallet.h:310
static std::shared_ptr< CWallet > CreateNew(WalletContext &context, const std::string &name, std::unique_ptr< WalletDatabase > database, uint64_t wallet_creation_flags, bool born_encrypted, bilingual_str &error, std::vector< bilingual_str > &warnings)
Definition: wallet.cpp:3102
A transaction with a bunch of additional info that only the owner cares about.
Definition: transaction.h:195
WalletDescriptor GetWalletDescriptor() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man)
Access to the wallet database.
Definition: walletdb.h:197
bool WriteBestBlock(const CBlockLocator &locator)
Definition: walletdb.cpp:177
bool ReadBestBlock(CBlockLocator &locator)
Definition: walletdb.cpp:183
bool TxnBegin()
Begin a new transaction.
Definition: walletdb.cpp:1260
bool WriteAddressPreviouslySpent(const CTxDestination &dest, bool previously_spent)
Definition: walletdb.cpp:1225
bool TxnCommit()
Commit current transaction.
Definition: walletdb.cpp:1265
bool WriteName(const std::string &strAddress, const std::string &strName)
Definition: walletdb.cpp:76
bool WritePurpose(const std::string &strAddress, const std::string &purpose)
Definition: walletdb.cpp:88
bool WriteOrderPosNext(int64_t nOrderPosNext)
Definition: walletdb.cpp:200
bool WriteTx(const CWalletTx &wtx)
Definition: walletdb.cpp:98
bool WriteAddressReceiveRequest(const CTxDestination &dest, const std::string &id, const std::string &receive_request)
Definition: walletdb.cpp:1231
Descriptor with some wallet metadata.
Definition: walletutil.h:64
std::shared_ptr< Descriptor > descriptor
Definition: walletutil.h:66
DescriptorCache cache
Definition: walletutil.h:72
static UniValue Parse(std::string_view raw, ParamFormat format=ParamFormat::JSON)
Parse string to UniValue or throw runtime_error if string contains invalid JSON.
Definition: client.cpp:401
static bool exists(const path &p)
Definition: fs.h:96
static std::string PathToString(const path &path)
Convert path object to a byte string.
Definition: fs.h:162
static path PathFromString(const std::string &string)
Convert byte string to path object.
Definition: fs.h:185
std::string EncodeDestination(const CTxDestination &dest)
Definition: key_io.cpp:295
std::unique_ptr< Handler > MakeCleanupHandler(std::function< void()> cleanup)
Return handler wrapping a cleanup function.
Definition: interfaces.cpp:42
bilingual_str ErrorString(const Result< T > &result)
Definition: result.h:93
std::unique_ptr< WalletDatabase > MakeWalletDatabase(const std::string &name, const DatabaseOptions &options, DatabaseStatus &status, bilingual_str &error_string)
Definition: wallet.cpp:2965
std::string PurposeToString(AddressPurpose p)
Definition: wallet.h:280
util::Result< std::string > ExportWatchOnlyWallet(const CWallet &wallet, const fs::path &destination, WalletContext &context)
Make a new watchonly wallet file containing the public descriptors from this wallet The exported watc...
Definition: export.cpp:45
util::Expected< std::vector< WalletDescInfo >, std::string > ExportDescriptors(const CWallet &wallet, bool export_private)
Export the descriptors from a wallet so that they can be imported elsewhere.
Definition: export.cpp:17
@ WALLET_FLAG_DISABLE_PRIVATE_KEYS
Definition: walletutil.h:30
DatabaseStatus
Definition: db.h:186
Describes a place in the block chain to another node such that if the other node doesn't have the sam...
Definition: block.h:117
std::map< CKeyID, CKey > keys
Bilingual messages:
Definition: translation.h:24
bool require_existing
Definition: db.h:173
std::optional< DatabaseFormat > require_format
Definition: db.h:175
uint64_t create_flags
Definition: db.h:176
WalletContext struct containing references to state shared between CWallet instances,...
Definition: context.h:36
ArgsManager * args
Definition: context.h:39
#define LOCK(cs)
Definition: sync.h:268
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
Definition: sync.h:299
for(size_t start{0};start< num_entries;start+=SEED_BATCH_SIZE)
Definition: dbwrapper.cpp:381
#define EXCLUSIVE_LOCKS_REQUIRED(...)
Definition: threadsafety.h:49
#define strprintf
Format arguments and return the string or write to given std::ostream (see tinyformat::format doc for...
Definition: tinyformat.h:1172
consteval auto _(util::TranslatedLiteral str)
Definition: translation.h:79
bilingual_str Untranslated(std::string original)
Mark a bilingual_str as untranslated.
Definition: translation.h:82
AssertLockHeld(pool.cs)