Coverage Report

Created: 2026-08-05 14:35

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