Coverage Report

Created: 2026-09-02 14:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/tmp/bitcoin/src/wallet/rpc/backup.cpp
Line
Count
Source
1
// Copyright (c) 2009-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 <clientversion.h>
7
#include <core_io.h>
8
#include <hash.h>
9
#include <interfaces/chain.h>
10
#include <key_io.h>
11
#include <merkleblock.h>
12
#include <node/types.h>
13
#include <rpc/util.h>
14
#include <script/descriptor.h>
15
#include <script/script.h>
16
#include <script/solver.h>
17
#include <sync.h>
18
#include <uint256.h>
19
#include <util/bip32.h>
20
#include <util/check.h>
21
#include <util/fs.h>
22
#include <util/time.h>
23
#include <util/translation.h>
24
#include <wallet/export.h>
25
#include <wallet/rpc/util.h>
26
#include <wallet/wallet.h>
27
28
#include <cstdint>
29
#include <fstream>
30
#include <tuple>
31
#include <string>
32
33
#include <univalue.h>
34
35
36
37
using interfaces::FoundBlock;
38
39
namespace wallet {
40
RPCMethod importprunedfunds()
41
844
{
42
844
    return RPCMethod{
43
844
        "importprunedfunds",
44
844
        "Imports funds without rescan. Corresponding address or script must previously be included in wallet. Aimed towards pruned wallets. The end-user is responsible to import additional transactions that subsequently spend the imported outputs or rescan after the point in the blockchain the transaction is included.\n",
45
844
                {
46
844
                    {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"},
47
844
                    {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"},
48
844
                },
49
844
                RPCResult{RPCResult::Type::NONE, "", ""},
50
844
                RPCExamples{""},
51
844
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
52
844
{
53
7
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
54
7
    if (!pwallet) return UniValue::VNULL;
55
56
7
    CMutableTransaction tx;
57
7
    if (!DecodeHexTx(tx, request.params[0].get_str())) {
58
1
        throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
59
1
    }
60
61
6
    CMerkleBlock merkleBlock;
62
6
    SpanReader{ParseHexV(request.params[1], "proof")} >> merkleBlock;
63
64
    //Search partial merkle tree in proof for our transaction and index in valid block
65
6
    std::vector<Txid> vMatch;
66
6
    std::vector<unsigned int> vIndex;
67
6
    if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) {
68
1
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
69
1
    }
70
71
5
    LOCK(pwallet->cs_wallet);
72
5
    int height;
73
5
    if (!pwallet->chain().findAncestorByHash(pwallet->GetLastBlockHash(), merkleBlock.header.GetHash(), FoundBlock().height(height))) {
74
1
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
75
1
    }
76
77
4
    std::vector<Txid>::const_iterator it;
78
4
    if ((it = std::find(vMatch.begin(), vMatch.end(), tx.GetHash())) == vMatch.end()) {
79
1
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof");
80
1
    }
81
82
3
    unsigned int txnIndex = vIndex[it - vMatch.begin()];
83
84
3
    CTransactionRef tx_ref = MakeTransactionRef(tx);
85
3
    if (pwallet->IsMine(*tx_ref)) {
86
2
        pwallet->AddToWallet(std::move(tx_ref), TxStateConfirmed{merkleBlock.header.GetHash(), height, static_cast<int>(txnIndex)});
87
2
        return UniValue::VNULL;
88
2
    }
89
90
1
    throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction");
91
3
},
92
844
    };
93
844
}
94
95
RPCMethod removeprunedfunds()
96
842
{
97
842
    return RPCMethod{
98
842
        "removeprunedfunds",
99
842
        "(DEPRECATED) This feature will be removed in the next major release. Start bitcoind with the `-deprecatedrpc=removeprunedfunds` option in order to use this.\n"
100
842
        "Deletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will affect wallet balances.\n",
101
842
                {
102
842
                    {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"},
103
842
                },
104
842
                RPCResult{RPCResult::Type::NONE, "", ""},
105
842
                RPCExamples{
106
842
                    HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") +
107
842
            "\nAs a JSON-RPC call\n"
108
842
            + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"")
109
842
                },
110
842
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
111
842
{
112
5
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
113
5
    if (!pwallet) return UniValue::VNULL;
114
115
5
    if (!pwallet->chain().rpcEnableDeprecated("removeprunedfunds")) {
116
1
        throw JSONRPCError(RPC_METHOD_DEPRECATED, "DEPRECATION WARNING: This feature will be removed in the next major release. Start bitcoind with the `-deprecatedrpc=removeprunedfunds` option in order to use this.");
117
1
    }
118
119
4
    LOCK(pwallet->cs_wallet);
120
121
4
    Txid hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))};
122
4
    std::vector<Txid> vHash;
123
4
    vHash.push_back(hash);
124
4
    if (auto res = pwallet->RemoveTxs(vHash); !res) {
125
1
        throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
126
1
    }
127
128
3
    return UniValue::VNULL;
129
4
},
130
842
    };
131
842
}
132
133
static int64_t GetImportTimestamp(const UniValue& data, int64_t now)
134
770
{
135
770
    if (data.exists("timestamp")) {
136
769
        const UniValue& timestamp = data["timestamp"];
137
769
        if (timestamp.isNum()) {
138
219
            return timestamp.getInt<int64_t>();
139
550
        } else if (timestamp.isStr() && timestamp.get_str() == "now") {
140
549
            return now;
141
549
        }
142
1
        throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type())));
143
769
    }
144
1
    throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key");
145
770
}
146
147
static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
148
764
{
149
764
    UniValue warnings(UniValue::VARR);
150
764
    UniValue result(UniValue::VOBJ);
151
152
764
    try {
153
764
        if (!data.exists("desc")) {
154
2
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found.");
155
2
        }
156
157
762
        const std::string& descriptor = data["desc"].get_str();
158
762
        const bool active = data.exists("active") ? data["active"].get_bool() : false;
159
762
        const std::string label{LabelFromValue(data["label"])};
160
161
        // Parse descriptor string
162
762
        FlatSigningProvider keys;
163
762
        std::string error;
164
762
        auto parsed_descs = Parse(descriptor, keys, error, /* require_checksum = */ true);
165
762
        if (parsed_descs.empty()) {
166
9
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
167
9
        }
168
753
        std::optional<bool> internal;
169
753
        if (data.exists("internal")) {
170
116
            if (parsed_descs.size() > 1) {
171
1
                throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot have multipath descriptor while also specifying \'internal\'");
172
1
            }
173
115
            internal = data["internal"].get_bool();
174
115
        }
175
176
        // Range check
177
752
        std::optional<bool> is_ranged;
178
752
        int64_t range_start = 0, range_end = 1, next_index = 0;
179
752
        if (!parsed_descs.at(0)->IsRange() && data.exists("range")) {
180
1
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
181
751
        } else if (parsed_descs.at(0)->IsRange()) {
182
475
            if (data.exists("range")) {
183
121
                auto range = ParseDescriptorRange(data["range"]);
184
121
                range_start = range.first;
185
121
                range_end = range.second + 1; // Specified range end is inclusive, but we need range end as exclusive
186
354
            } else {
187
354
                warnings.push_back("Range not given, using default keypool range");
188
354
                range_start = 0;
189
354
                range_end = wallet.m_keypool_size;
190
354
            }
191
475
            next_index = range_start;
192
475
            is_ranged = true;
193
194
475
            if (data.exists("next_index")) {
195
71
                next_index = data["next_index"].getInt<int64_t>();
196
                // bound checks
197
71
                if (next_index < range_start || next_index >= range_end) {
198
0
                    throw JSONRPCError(RPC_INVALID_PARAMETER, "next_index is out of range");
199
0
                }
200
71
            }
201
475
        }
202
203
        // Active descriptors must be ranged
204
751
        if (active && !parsed_descs.at(0)->IsRange()) {
205
1
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Active descriptors must be ranged");
206
1
        }
207
208
        // Multipath descriptors should not have a label
209
750
        if (parsed_descs.size() > 1 && data.exists("label")) {
210
1
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Multipath descriptors should not have a label");
211
1
        }
212
213
        // Ranged descriptors should not have a label
214
749
        if (is_ranged.has_value() && is_ranged.value() && data.exists("label")) {
215
2
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptors should not have a label");
216
2
        }
217
218
747
        bool desc_internal = internal.has_value() && internal.value();
219
        // Internal addresses should not have a label either
220
747
        if (desc_internal && data.exists("label")) {
221
2
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
222
2
        }
223
224
        // Combo descriptor check
225
745
        if (active && !parsed_descs.at(0)->IsSingleType()) {
226
1
            throw JSONRPCError(RPC_WALLET_ERROR, "Combo descriptors cannot be set to active");
227
1
        }
228
229
        // If the wallet disabled private keys, abort if private keys exist
230
744
        if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) {
231
3
            throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
232
3
        }
233
234
1.53k
        for (size_t j = 0; j < parsed_descs.size(); ++j) {
235
807
            auto parsed_desc = std::move(parsed_descs[j]);
236
807
            if (parsed_descs.size() == 2) {
237
132
                desc_internal = j == 1;
238
675
            } else if (parsed_descs.size() > 2) {
239
9
                CHECK_NONFATAL(!desc_internal);
240
9
            }
241
            // Expand to check whether the descriptor can be derived at the first index.
242
807
            FlatSigningProvider expand_keys;
243
807
            std::vector<CScript> scripts;
244
807
            if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) {
245
1
                throw JSONRPCError(RPC_WALLET_ERROR, "Cannot expand descriptor. Probably because of hardened derivations without private keys provided");
246
1
            }
247
248
806
            for (const auto& w : parsed_desc->Warnings()) {
249
2
               warnings.push_back(w);
250
2
            }
251
252
            // If private keys are enabled, check some things.
253
806
            if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
254
614
                if (keys.keys.empty()) {
255
4
                    throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled");
256
4
                }
257
610
                if (!parsed_desc->HavePrivateKeys(keys)) {
258
238
                    warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
259
238
                }
260
610
            }
261
262
            // If this is an unused(KEY) descriptor, check that the wallet doesn't already have other descriptors with this key
263
802
            if (!parsed_desc->HasScripts()) {
264
3
                if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
265
1
                    throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import unused() to wallet without private keys enabled");
266
1
                }
267
                // Unused descriptors must contain a single key.
268
                // Earlier checks will have enforced that this key is either a private key when private keys are enabled,
269
                // or that this key is a public key when private keys are disabled.
270
                // If we can retrieve the corresponding private key from the wallet, then this key is already in the wallet
271
                // and we should not import it.
272
2
                std::set<CPubKey> pubkeys;
273
2
                std::set<CExtPubKey> extpubs;
274
2
                parsed_desc->GetPubKeys(pubkeys, extpubs);
275
2
                std::transform(extpubs.begin(), extpubs.end(), std::inserter(pubkeys, pubkeys.begin()), [](const CExtPubKey& xpub) { return xpub.pubkey; });
276
2
                CHECK_NONFATAL(pubkeys.size() == 1);
277
2
                if (wallet.GetKey(pubkeys.begin()->GetID())) {
278
1
                    throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import an unused() descriptor when its private key is already in the wallet");
279
1
                }
280
2
            }
281
282
800
            WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);
283
284
            // Add descriptor to the wallet
285
800
            auto spk_manager_res = wallet.AddWalletDescriptor(w_desc, keys, label, desc_internal);
286
287
800
            if (!spk_manager_res) {
288
3
                throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s': %s", descriptor, util::ErrorString(spk_manager_res).original));
289
3
            }
290
291
797
            auto& spk_manager = spk_manager_res.value().get();
292
293
            // Set descriptor as active if necessary
294
797
            if (active) {
295
386
                if (!w_desc.descriptor->GetOutputType()) {
296
1
                    warnings.push_back("Unknown output type, cannot set descriptor to active.");
297
385
                } else {
298
385
                    wallet.AddActiveScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
299
385
                }
300
411
            } else {
301
411
                if (w_desc.descriptor->GetOutputType()) {
302
236
                    wallet.DeactivateScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
303
236
                }
304
411
            }
305
797
        }
306
307
731
        result.pushKV("success", UniValue(true));
308
731
    } catch (const UniValue& e) {
309
39
        result.pushKV("success", UniValue(false));
310
39
        result.pushKV("error", e);
311
39
    }
312
764
    PushWarnings(warnings, result);
313
764
    return result;
314
764
}
315
316
RPCMethod importdescriptors()
317
1.51k
{
318
1.51k
    return RPCMethod{
319
1.51k
        "importdescriptors",
320
1.51k
        "Import descriptors. This will trigger a rescan of the blockchain based on the earliest timestamp of all descriptors being imported. Requires a new wallet backup.\n"
321
1.51k
        "When importing descriptors with multipath key expressions, if the multipath specifier contains exactly two elements, the descriptor produced from the second element will be imported as an internal descriptor.\n"
322
1.51k
            "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
323
1.51k
            "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
324
1.51k
            "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n",
325
1.51k
                {
326
1.51k
                    {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
327
1.51k
                        {
328
1.51k
                            {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
329
1.51k
                                {
330
1.51k
                                    {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."},
331
1.51k
                                    {"active", RPCArg::Type::BOOL, RPCArg::Default{false}, "Set this descriptor to be the active descriptor for the corresponding output type/externality"},
332
1.51k
                                    {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in the form [begin,end]) to import"},
333
1.51k
                                    {"next_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "If a ranged descriptor is set to active, this specifies the next index to generate addresses from"},
334
1.51k
                                    {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n"
335
1.51k
                                        "Use the string \"now\" to substitute the current synced blockchain time.\n"
336
1.51k
                                        "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
337
1.51k
                                        "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
338
1.51k
                                        "of all descriptors being imported will be scanned as well as the mempool.",
339
1.51k
                                        RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}}
340
1.51k
                                    },
341
1.51k
                                    {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
342
1.51k
                                    {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"},
343
1.51k
                                },
344
1.51k
                            },
345
1.51k
                        },
346
1.51k
                        RPCArgOptions{.oneline_description="requests"}},
347
1.51k
                },
348
1.51k
                RPCResult{
349
1.51k
                    RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
350
1.51k
                    {
351
1.51k
                        {RPCResult::Type::OBJ, "", "",
352
1.51k
                        {
353
1.51k
                            {RPCResult::Type::BOOL, "success", ""},
354
1.51k
                            {RPCResult::Type::ARR, "warnings", /*optional=*/true, "",
355
1.51k
                            {
356
1.51k
                                {RPCResult::Type::STR, "", ""},
357
1.51k
                            }},
358
1.51k
                            {RPCResult::Type::OBJ, "error", /*optional=*/true, "",
359
1.51k
                            {
360
1.51k
                                {RPCResult::Type::NUM, "code", "JSONRPC error code"},
361
1.51k
                                {RPCResult::Type::STR, "message", "JSONRPC error message"},
362
1.51k
                            }},
363
1.51k
                        }},
364
1.51k
                    }
365
1.51k
                },
366
1.51k
                RPCExamples{
367
1.51k
                    HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, "
368
1.51k
                                          "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
369
1.51k
                    HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'")
370
1.51k
                },
371
1.51k
        [](const RPCMethod& self, const JSONRPCRequest& main_request) -> UniValue
372
1.51k
{
373
674
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);
374
674
    if (!pwallet) return UniValue::VNULL;
375
674
    CWallet& wallet{*pwallet};
376
377
674
    WalletRescanReserver reserver(*pwallet);
378
674
    if (!reserver.reserve(/*with_passphrase=*/true)) {
379
1
        throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
380
1
    }
381
382
    // Make sure the results are valid at least up to the most recent block
383
    // the user could have gotten from another RPC command prior to now
384
673
    wallet.BlockUntilSyncedToCurrentChain();
385
386
    // Ensure that the wallet is not locked for the remainder of this RPC, as
387
    // the passphrase is used to top up the keypool.
388
673
    LOCK(pwallet->m_relock_mutex);
389
390
673
    const UniValue& requests = main_request.params[0];
391
673
    const int64_t minimum_timestamp = 1;
392
673
    int64_t now = 0;
393
673
    int64_t lowest_timestamp = 0;
394
673
    bool rescan = false;
395
673
    UniValue response(UniValue::VARR);
396
673
    {
397
673
        LOCK(pwallet->cs_wallet);
398
673
        EnsureWalletIsUnlocked(*pwallet);
399
400
673
        CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now)));
401
402
        // Get all timestamps and extract the lowest timestamp
403
766
        for (const UniValue& request : requests.getValues()) {
404
            // This throws an error if "timestamp" doesn't exist
405
766
            const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp);
406
766
            const UniValue result = ProcessDescriptorImport(*pwallet, request, timestamp);
407
766
            response.push_back(result);
408
409
766
            if (lowest_timestamp > timestamp ) {
410
528
                lowest_timestamp = timestamp;
411
528
            }
412
413
            // If we know the chain tip, and at least one request was successful then allow rescan
414
766
            if (!rescan && result["success"].get_bool()) {
415
633
                rescan = true;
416
633
            }
417
766
        }
418
673
        pwallet->ConnectScriptPubKeyManNotifiers();
419
673
        pwallet->RefreshAllTXOs();
420
673
    }
421
422
    // Rescan the blockchain using the lowest timestamp
423
673
    if (rescan) {
424
633
        int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver);
425
633
        pwallet->ResubmitWalletTransactions(node::TxBroadcast::MEMPOOL_NO_BROADCAST, /*force=*/true);
426
427
633
        if (pwallet->IsAbortingRescan()) {
428
1
            throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
429
1
        }
430
431
632
        if (scanned_time > lowest_timestamp) {
432
2
            std::vector<UniValue> results = response.getValues();
433
2
            response.clear();
434
2
            response.setArray();
435
436
            // Compose the response
437
4
            for (unsigned int i = 0; i < requests.size(); ++i) {
438
2
                const UniValue& request = requests.getValues().at(i);
439
440
                // If the descriptor timestamp is within the successfully scanned
441
                // range, or if the import result already has an error set, let
442
                // the result stand unmodified. Otherwise replace the result
443
                // with an error message.
444
2
                if (scanned_time <= GetImportTimestamp(request, now) || results.at(i).exists("error")) {
445
0
                    response.push_back(results.at(i));
446
2
                } else {
447
2
                    std::string error_msg{strprintf("Rescan failed for descriptor with timestamp %d. There "
448
2
                            "was an error reading a block from time %d, which is after or within %d seconds "
449
2
                            "of key creation, and could contain transactions pertaining to the desc. As a "
450
2
                            "result, transactions and coins using this desc may not appear in the wallet.",
451
2
                            GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)};
452
2
                    if (pwallet->chain().havePruned()) {
453
0
                        error_msg += strprintf(" This error could be caused by pruning or data corruption "
454
0
                                "(see bitcoind log for details) and could be dealt with by downloading and "
455
0
                                "rescanning the relevant blocks (see -reindex option and rescanblockchain RPC).");
456
2
                    } else if (pwallet->chain().hasAssumedValidChain()) {
457
2
                        error_msg += strprintf(" This error is likely caused by an in-progress assumeutxo "
458
2
                                "background sync. Check logs or getchainstates RPC for assumeutxo background "
459
2
                                "sync progress and try again later.");
460
2
                    } else {
461
0
                        error_msg += strprintf(" This error could potentially caused by data corruption. If "
462
0
                                "the issue persists you may want to reindex (see -reindex option).");
463
0
                    }
464
465
2
                    UniValue result = UniValue(UniValue::VOBJ);
466
2
                    result.pushKV("success", UniValue(false));
467
2
                    result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, error_msg));
468
2
                    response.push_back(std::move(result));
469
2
                }
470
2
            }
471
2
        }
472
632
    }
473
474
672
    return response;
475
673
},
476
1.51k
    };
477
1.51k
}
478
479
RPCMethod listdescriptors()
480
1.04k
{
481
1.04k
    return RPCMethod{
482
1.04k
        "listdescriptors",
483
1.04k
        "List all descriptors present in a wallet.\n",
484
1.04k
        {
485
1.04k
            {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private descriptors."}
486
1.04k
        },
487
1.04k
        RPCResult{RPCResult::Type::OBJ, "", "", {
488
1.04k
            {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
489
1.04k
            {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)",
490
1.04k
            {
491
1.04k
                {RPCResult::Type::OBJ, "", "", {
492
1.04k
                    {RPCResult::Type::STR, "desc", "Descriptor string representation"},
493
1.04k
                    {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},
494
1.04k
                    {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
495
1.04k
                    {RPCResult::Type::BOOL, "internal", /*optional=*/true, "True if this descriptor is used to generate change addresses. False if this descriptor is used to generate receiving addresses; defined only for active descriptors"},
496
1.04k
                    {RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", {
497
1.04k
                        {RPCResult::Type::NUM, "", "Range start inclusive"},
498
1.04k
                        {RPCResult::Type::NUM, "", "Range end inclusive"},
499
1.04k
                    }},
500
1.04k
                    {RPCResult::Type::NUM, "next", /*optional=*/true, "Same as next_index field. Kept for compatibility reason."},
501
1.04k
                    {RPCResult::Type::NUM, "next_index", /*optional=*/true, "The next index to generate addresses from; defined only for ranged descriptors"},
502
1.04k
                }},
503
1.04k
            }}
504
1.04k
        }},
505
1.04k
        RPCExamples{
506
1.04k
            HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "")
507
1.04k
            + HelpExampleCli("listdescriptors", "true") + HelpExampleRpc("listdescriptors", "true")
508
1.04k
        },
509
1.04k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
510
1.04k
{
511
212
    const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
512
212
    if (!wallet) return UniValue::VNULL;
513
514
212
    const bool priv = !request.params[0].isNull() && request.params[0].get_bool();
515
212
    if (wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && priv) {
516
1
        throw JSONRPCError(RPC_WALLET_ERROR, "Can't get private descriptor string for watch-only wallets");
517
1
    }
518
211
    if (priv) {
519
88
        EnsureWalletIsUnlocked(*wallet);
520
88
    }
521
522
211
    LOCK(wallet->cs_wallet);
523
211
    util::Expected<std::vector<WalletDescInfo>, std::string> exported = ExportDescriptors(*wallet, priv);
524
211
    if (!exported) {
525
0
        throw JSONRPCError(RPC_WALLET_ERROR, exported.error());
526
0
    }
527
211
    std::vector<WalletDescInfo> wallet_descriptors = *exported;
528
529
4.62k
    std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) {
530
4.62k
        return a.descriptor < b.descriptor;
531
4.62k
    });
532
533
211
    UniValue descriptors(UniValue::VARR);
534
1.62k
    for (const WalletDescInfo& info : wallet_descriptors) {
535
1.62k
        UniValue spk(UniValue::VOBJ);
536
1.62k
        spk.pushKV("desc", info.descriptor);
537
1.62k
        spk.pushKV("timestamp", info.creation_time);
538
1.62k
        spk.pushKV("active", info.active);
539
1.62k
        if (info.internal.has_value()) {
540
1.49k
            spk.pushKV("internal", info.internal.value());
541
1.49k
        }
542
1.62k
        if (info.range.has_value()) {
543
1.53k
            UniValue range(UniValue::VARR);
544
1.53k
            range.push_back(info.range->first);
545
1.53k
            range.push_back(info.range->second - 1);
546
1.53k
            spk.pushKV("range", std::move(range));
547
1.53k
            spk.pushKV("next", info.next_index);
548
1.53k
            spk.pushKV("next_index", info.next_index);
549
1.53k
        }
550
1.62k
        descriptors.push_back(std::move(spk));
551
1.62k
    }
552
553
211
    UniValue response(UniValue::VOBJ);
554
211
    response.pushKV("wallet_name", wallet->GetName());
555
211
    response.pushKV("descriptors", std::move(descriptors));
556
557
211
    return response;
558
211
},
559
1.04k
    };
560
1.04k
}
561
562
RPCMethod backupwallet()
563
903
{
564
903
    return RPCMethod{
565
903
        "backupwallet",
566
903
        "Safely copies the current wallet file to the specified destination, which can either be a directory or a path with a filename.\n",
567
903
                {
568
903
                    {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"},
569
903
                },
570
903
                RPCResult{RPCResult::Type::NONE, "", ""},
571
903
                RPCExamples{
572
903
                    HelpExampleCli("backupwallet", "\"backup.dat\"")
573
903
            + HelpExampleRpc("backupwallet", "\"backup.dat\"")
574
903
                },
575
903
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
576
903
{
577
66
    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
578
66
    if (!pwallet) return UniValue::VNULL;
579
580
    // Make sure the results are valid at least up to the most recent block
581
    // the user could have gotten from another RPC command prior to now
582
66
    pwallet->BlockUntilSyncedToCurrentChain();
583
584
66
    LOCK(pwallet->cs_wallet);
585
586
66
    std::string strDest = request.params[0].get_str();
587
66
    if (!pwallet->BackupWallet(strDest)) {
588
4
        throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!");
589
4
    }
590
591
62
    return UniValue::VNULL;
592
66
},
593
903
    };
594
903
}
595
596
597
RPCMethod restorewallet()
598
881
{
599
881
    return RPCMethod{
600
881
        "restorewallet",
601
881
        "Restores and loads a wallet from backup.\n"
602
881
        "\nThe rescan is significantly faster if block filters are available"
603
881
        "\n(using startup option \"-blockfilterindex=1\").\n",
604
881
        {
605
881
            {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
606
881
            {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
607
881
            {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
608
881
        },
609
881
        RPCResult{
610
881
            RPCResult::Type::OBJ, "", "",
611
881
            {
612
881
                {RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
613
881
                {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to restoring and loading the wallet.",
614
881
                {
615
881
                    {RPCResult::Type::STR, "", ""},
616
881
                }},
617
881
            }
618
881
        },
619
881
        RPCExamples{
620
881
            HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
621
881
            + HelpExampleRpc("restorewallet", R"("testwallet", "home\\backups\\backup-file.bak")")
622
881
            + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak"}, {"load_on_startup", true}})
623
881
            + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak"}, {"load_on_startup", true}})
624
881
        },
625
881
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
626
881
{
627
628
44
    WalletContext& context = EnsureWalletContext(request.context);
629
630
44
    auto backup_file = fs::u8path(request.params[1].get_str());
631
632
44
    std::string wallet_name = request.params[0].get_str();
633
634
44
    std::optional<bool> load_on_start = request.params[2].isNull() ? std::nullopt : std::optional<bool>(request.params[2].get_bool());
635
636
44
    DatabaseStatus status;
637
44
    bilingual_str error;
638
44
    std::vector<bilingual_str> warnings;
639
640
44
    const std::shared_ptr<CWallet> wallet = RestoreWallet(context, backup_file, wallet_name, load_on_start, status, error, warnings);
641
642
44
    HandleWalletError(wallet, status, error);
643
644
44
    UniValue obj(UniValue::VOBJ);
645
44
    obj.pushKV("name", wallet->GetName());
646
44
    PushWarnings(warnings, obj);
647
648
44
    return obj;
649
650
44
},
651
881
    };
652
881
}
653
} // namespace wallet