Bitcoin Core 32.99.0
P2P Digital Currency
imports.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 http://www.opensource.org/licenses/mit-license.php.
4
5#include <chain.h>
6#include <wallet/imports.h>
7#include <wallet/scan.h>
8
9namespace wallet {
10
12{
13 AssertLockHeld(wallet.cs_wallet);
14
15 std::vector<std::string> warnings;
16
17 // Parse descriptor string
19 std::string error;
20 auto parsed_descs = Parse(request.descriptor, keys, error, /*require_checksum=*/true);
21 if (parsed_descs.empty()) {
22 return ImportResult(WalletErrorCode::InvalidDescriptor, error, warnings);
23 }
24
25 if (request.internal.has_value() && parsed_descs.size() > 1) {
26 return ImportResult(
28 "Cannot have multipath descriptor while also specifying 'internal'",
29 warnings
30 );
31 }
32
33 // Range check
34 bool is_ranged{false};
35 int64_t range_start = 0, range_end = 1, next_index = 0;
36 if (!parsed_descs.at(0)->IsRange() && request.range.has_value()) {
37 return ImportResult(
39 "Range should not be specified for an un-ranged descriptor",
40 warnings
41 );
42 } else if (parsed_descs.at(0)->IsRange()) {
43 if (request.range.has_value()) {
44 int64_t low = request.range->first;
45 int64_t high = request.range->second;
46 if (auto res = CheckDescriptorRangeBounds(low, high); !res) {
48 res.error());
49 }
50 range_start = low;
51 range_end = high + 1; // Specified range end is inclusive, but we need range end as exclusive
52 } else {
53 warnings.emplace_back("Range not given, using default keypool range");
54 range_start = 0;
55 range_end = wallet.m_keypool_size;
56 }
57 next_index = request.next_index.value_or(range_start);
58 is_ranged = true;
59
60 if (next_index < range_start || next_index >= range_end) {
61 return ImportResult(
63 "next_index is out of range",
64 warnings
65 );
66 }
67 }
68
69 // Active descriptors must be ranged
70 if (request.active && !parsed_descs.at(0)->IsRange()) {
71 return ImportResult(
73 "Active descriptors must be ranged",
74 warnings
75 );
76 }
77
78 // Multipath descriptors should not have a label
79 if (parsed_descs.size() > 1 && !request.label.empty()) {
80 return ImportResult(
82 "Multipath descriptors should not have a label",
83 warnings
84 );
85 }
86
87 // Ranged descriptors should not have a label
88 if (is_ranged && !request.label.empty()) {
89 return ImportResult(
91 "Ranged descriptors should not have a label",
92 warnings
93 );
94 }
95
96 bool desc_internal = request.internal.value_or(false);
97 // Internal addresses should not have a label either
98 if (desc_internal && !request.label.empty()) {
99 return ImportResult(
101 "Internal addresses should not have a label",
102 warnings
103 );
104 }
105
106 // Combo descriptor check
107 if (request.active && !parsed_descs.at(0)->IsSingleType()) {
108 return ImportResult(
110 "Combo descriptors cannot be set to active",
111 warnings
112 );
113 }
114
115 // If the wallet disabled private keys, abort if private keys exist
116 if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) {
117 return ImportResult(
119 "Cannot import private keys to a wallet with private keys disabled",
120 warnings
121 );
122 }
123
124 for (size_t j = 0; j < parsed_descs.size(); ++j) {
125 auto parsed_desc = std::move(parsed_descs[j]);
126 if (parsed_descs.size() == 2) {
127 desc_internal = j == 1;
128 } else if (parsed_descs.size() > 2) {
129 CHECK_NONFATAL(!desc_internal);
130 }
131 // ExpandPrivate to whether the descriptor can be derived at the first index.
132 FlatSigningProvider expand_keys;
133 std::vector<CScript> scripts;
134 if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) {
135 return ImportResult(
137 "Cannot expand descriptor. Probably because of hardened derivations without private keys provided",
138 warnings
139 );
140 }
141
142 for (const auto& w : parsed_desc->Warnings()) {
143 warnings.push_back(w);
144 }
145
146 // If private keys are enabled, check some things.
147 if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
148 if (keys.keys.empty()) {
149 return ImportResult(
151 "Cannot import descriptor without private keys to a wallet with private keys enabled",
152 warnings
153 );
154 }
155 if (!parsed_desc->HavePrivateKeys(keys)) {
156 warnings.emplace_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
157 }
158 }
159 // If this is an unused(KEY) descriptor, check that the wallet doesn't already have other descriptors with this key
160 if (!parsed_desc->HasScripts()) {
161 if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
162 return ImportResult(
164 "Cannot import unused() to wallet without private keys enabled",
165 warnings
166 );
167 }
168 // Unused descriptors must contain a single key.
169 // Earlier checks will have enforced that this key is either a private key when private keys are enabled,
170 // or that this key is a public key when private keys are disabled.
171 // If we can retrieve the corresponding private key from the wallet, then this key is already in the wallet
172 // and we should not import it.
173 std::set<CPubKey> pubkeys;
174 std::set<CExtPubKey> extpubs;
175 parsed_desc->GetPubKeys(pubkeys, extpubs);
176 std::transform(extpubs.begin(), extpubs.end(), std::inserter(pubkeys, pubkeys.begin()), [](const CExtPubKey& xpub) { return xpub.pubkey; });
177 CHECK_NONFATAL(pubkeys.size() == 1);
178 if (wallet.GetKey(pubkeys.begin()->GetID())) {
179 return ImportResult(
181 "Cannot import an unused() descriptor when its private key is already in the wallet",
182 warnings
183 );
184 }
185 }
186
187 Assume(request.timestamp.has_value());
188 WalletDescriptor w_desc(std::move(parsed_desc), request.timestamp.value(), range_start, range_end, next_index);
189
190 // Add descriptor to the wallet
191 auto spk_manager_res = wallet.AddWalletDescriptor(w_desc, keys, request.label, desc_internal);
192
193 if (!spk_manager_res) {
194 return ImportResult(
196 strprintf("Could not add descriptor '%s': %s", request.descriptor, util::ErrorString(spk_manager_res).original),
197 warnings
198 );
199 }
200
201 auto& spk_manager = spk_manager_res.value().get();
202
203 // Set descriptor as active if necessary
204 if (request.active) {
205 if (!w_desc.descriptor->GetOutputType()) {
206 warnings.emplace_back("Unknown output type, cannot set descriptor to active.");
207 } else {
208 wallet.AddActiveScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
209 }
210 } else {
211 if (w_desc.descriptor->GetOutputType()) {
212 wallet.DeactivateScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
213 }
214 }
215 }
216
217 ImportResult result;
218 result.warnings = warnings;
219 return result;
220}
221
222std::vector<ImportResult> ProcessDescriptorsImport(CWallet& wallet,
223 std::vector<ImportDescriptorRequest>& requests)
224{
225 std::vector<ImportResult> response;
226
228 if (!reserver.reserve(/*with_passphrase=*/true)) {
229 return {ImportResult{
231 "Wallet is currently rescanning. Abort existing rescan or wait.",
232 /*warnings=*/{},
233 /*general_error=*/true
234 }};
235 }
236
237 // Make sure the results are valid at least up to the most recent block
238 // the user could have gotten from another RPC command prior to now
239 wallet.BlockUntilSyncedToCurrentChain();
240
241 // Ensure that the wallet is not locked for the remainder of this call,
242 // as the passphrase is used to top up the keypool.
243 LOCK(wallet.m_relock_mutex);
244 int64_t now = 0;
245 int64_t lowest_timestamp = 0;
246 bool rescan = false;
247 {
248 LOCK(wallet.cs_wallet);
249 if (wallet.IsLocked()) {
250 return {ImportResult{
252 "Error: Please enter the wallet passphrase with walletpassphrase first.",
253 /*warnings=*/{},
254 /*general_error=*/true
255 }};
256 }
257
258 CHECK_NONFATAL(wallet.chain().findBlock(wallet.GetLastBlockHash(), interfaces::FoundBlock().time(lowest_timestamp).mtpTime(now)));
259
260 for (ImportDescriptorRequest& request : requests) {
261 request.timestamp = request.timestamp.value_or(now);
262 const ImportResult& import_result = ImportDescriptor(wallet, request);
263
264 if (lowest_timestamp > request.timestamp.value()) {
265 lowest_timestamp = request.timestamp.value();
266 }
267 if (!import_result.has_error()) {
268 // At least one request succeeded, so we need to rescan
269 rescan = true;
270 }
271 response.push_back(import_result);
272 }
273 wallet.ConnectScriptPubKeyManNotifiers();
274 wallet.RefreshAllTXOs();
275 }
276
277 if (rescan) {
278 const int64_t scanned_time = wallet.Scanner().ScanFromTime(lowest_timestamp, reserver);
279 wallet.ResubmitWalletTransactions(node::TxBroadcast::MEMPOOL_NO_BROADCAST, /*force=*/true);
280
281 if (wallet.Scanner().IsAborting()) {
282 return {ImportResult{
284 "Rescan aborted by user.",
285 /*warnings=*/{},
286 /*general_error=*/true
287 }};
288 }
289
290 if (scanned_time > lowest_timestamp) {
291 // Compose the response
292 for (size_t i = 0; i < requests.size(); ++i) {
293 ImportResult& result = response.at(i);
294
295 // If the descriptor timestamp is within the successfully scanned
296 // range, or if the import result already has an error set, let
297 // the result stand unmodified. Otherwise replace the result
298 // with an error message.
299 const int64_t timestamp{requests.at(i).timestamp.value()};
300 if (scanned_time > timestamp && !result.has_error()) {
301 std::string error_msg = strprintf("Rescan failed for descriptor with timestamp %d. There "
302 "was an error reading a block from time %d, which is after or within %d seconds "
303 "of key creation, and could contain transactions pertaining to the desc. As a "
304 "result, transactions and coins using this desc may not appear in the wallet.",
305 timestamp, scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW);
306 if (wallet.chain().havePruned()) {
307 error_msg += strprintf(" This error could be caused by pruning or data corruption "
308 "(see bitcoind log for details) and could be dealt with by downloading and "
309 "rescanning the relevant blocks (see -reindex option and rescanblockchain RPC).");
310 } else if (wallet.chain().hasAssumedValidChain()) {
311 error_msg += strprintf(" This error is likely caused by an in-progress assumeutxo "
312 "background sync. Check logs or getchainstates RPC for assumeutxo background "
313 "sync progress and try again later.");
314 } else {
315 error_msg += strprintf(" This error could potentially caused by data corruption. If "
316 "the issue persists you may want to reindex (see -reindex option).");
317 }
318 result.error = ImportError{
320 Untranslated(error_msg),
321 /*is_wallet_error=*/false
322 };
323 }
324 }
325 }
326 }
327 return response;
328}
329
330} // namespace wallet
constexpr int64_t TIMESTAMP_WINDOW
Timestamp window used as a grace period by code that compares external timestamps (such as timestamps...
Definition: chain.h:37
#define CHECK_NONFATAL(condition)
Identity function.
Definition: check.h:112
#define Assume(val)
Assume is the identity function.
Definition: check.h:128
Helper for findBlock to selectively return pieces of block data.
Definition: chain.h:52
FoundBlock & time(int64_t &time)
Definition: chain.h:56
FoundBlock & mtpTime(int64_t &mtp_time)
Definition: chain.h:58
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Definition: wallet.h:313
Descriptor with some wallet metadata.
Definition: walletutil.h:64
std::shared_ptr< Descriptor > descriptor
Definition: walletutil.h:70
RAII object to check and reserve a wallet rescan.
Definition: scan.h:37
bool reserve(bool with_passphrase=false)
Definition: scan.cpp:40
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:408
@ MEMPOOL_NO_BROADCAST
Add the transaction to the mempool, but don't broadcast to anybody.
bilingual_str ErrorString(const Result< T > &result)
Definition: result.h:93
std::vector< ImportResult > ProcessDescriptorsImport(CWallet &wallet, std::vector< ImportDescriptorRequest > &requests)
Definition: imports.cpp:222
@ UnlockNeeded
The wallet is locked and the operation requires access to private keys.
@ InvalidDescriptor
TODO Add correct descriptions to each error.
@ GenericError
Generic wallet error.
ImportResult ImportDescriptor(CWallet &wallet, const ImportDescriptorRequest &request) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
Definition: imports.cpp:11
@ WALLET_FLAG_DISABLE_PRIVATE_KEYS
Definition: walletutil.h:30
util::Expected< void, std::string > CheckDescriptorRangeBounds(int64_t low, int64_t high)
Validate the numeric bounds of a descriptor key-expression range [low, high] (high inclusive).
Definition: descriptor.cpp:52
std::string original
Definition: translation.h:25
Information about a descriptor to be imported.
Definition: imports.h:49
std::optional< ImportError > error
Definition: imports.h:35
bool has_error() const
Definition: imports.h:37
std::vector< std::string > warnings
Definition: imports.h:34
#define LOCK(cs)
Definition: sync.h:268
std::vector< uint16_t > keys
Definition: dbwrapper.cpp:376
#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
bilingual_str Untranslated(std::string original)
Mark a bilingual_str as untranslated.
Definition: translation.h:82
AssertLockHeld(pool.cs)