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