Coverage Report

Created: 2026-07-23 20: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
828
{
42
828
    return RPCMethod{
43
828
        "importprunedfunds",
44
828
        "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
828
                {
46
828
                    {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"},
47
828
                    {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"},
48
828
                },
49
828
                RPCResult{RPCResult::Type::NONE, "", ""},
50
828
                RPCExamples{""},
51
828
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
52
828
{
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
828
    };
93
828
}
94
95
RPCMethod removeprunedfunds()
96
825
{
97
825
    return RPCMethod{
98
825
        "removeprunedfunds",
99
825
        "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
825
                {
101
825
                    {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"},
102
825
                },
103
825
                RPCResult{RPCResult::Type::NONE, "", ""},
104
825
                RPCExamples{
105
825
                    HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") +
106
825
            "\nAs a JSON-RPC call\n"
107
825
            + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"")
108
825
                },
109
825
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
110
825
{
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
825
    };
126
825
}
127
128
static int64_t GetImportTimestamp(const UniValue& data, int64_t now)
129
750
{
130
750
    if (data.exists("timestamp")) {
131
750
        const UniValue& timestamp = data["timestamp"];
132
750
        if (timestamp.isNum()) {
133
216
            return timestamp.getInt<int64_t>();
134
534
        } else if (timestamp.isStr() && timestamp.get_str() == "now") {
135
534
            return now;
136
534
        }
137
0
        throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type())));
138
750
    }
139
0
    throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key");
140
750
}
141
142
static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
143
746
{
144
746
    UniValue warnings(UniValue::VARR);
145
746
    UniValue result(UniValue::VOBJ);
146
147
746
    try {
148
746
        if (!data.exists("desc")) {
149
1
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found.");
150
1
        }
151
152
745
        const std::string& descriptor = data["desc"].get_str();
153
745
        const bool active = data.exists("active") ? data["active"].get_bool() : false;
154
745
        const std::string label{LabelFromValue(data["label"])};
155
156
        // Parse descriptor string
157
745
        FlatSigningProvider keys;
158
745
        std::string error;
159
745
        auto parsed_descs = Parse(descriptor, keys, error, /* require_checksum = */ true);
160
745
        if (parsed_descs.empty()) {
161
8
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
162
8
        }
163
737
        std::optional<bool> internal;
164
737
        if (data.exists("internal")) {
165
112
            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
111
            internal = data["internal"].get_bool();
169
111
        }
170
171
        // Range check
172
736
        std::optional<bool> is_ranged;
173
736
        int64_t range_start = 0, range_end = 1, next_index = 0;
174
736
        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
735
        } else if (parsed_descs.at(0)->IsRange()) {
177
466
            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
349
            } else {
182
349
                warnings.push_back("Range not given, using default keypool range");
183
349
                range_start = 0;
184
349
                range_end = wallet.m_keypool_size;
185
349
            }
186
466
            next_index = range_start;
187
466
            is_ranged = true;
188
189
466
            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
466
        }
197
198
        // Active descriptors must be ranged
199
735
        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
734
        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
733
        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
731
        bool desc_internal = internal.has_value() && internal.value();
214
        // Internal addresses should not have a label either
215
731
        if (desc_internal && data.exists("label")) {
216
1
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
217
1
        }
218
219
        // Combo descriptor check
220
730
        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
729
        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.50k
        for (size_t j = 0; j < parsed_descs.size(); ++j) {
230
789
            auto parsed_desc = std::move(parsed_descs[j]);
231
789
            if (parsed_descs.size() == 2) {
232
126
                desc_internal = j == 1;
233
663
            } 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
789
            FlatSigningProvider expand_keys;
238
789
            std::vector<CScript> scripts;
239
789
            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
788
            parsed_desc->ExpandPrivate(0, keys, expand_keys);
243
244
788
            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
788
            bool have_all_privkeys = !expand_keys.keys.empty();
250
891
            for (const auto& entry : expand_keys.origins) {
251
891
                const CKeyID& key_id = entry.first;
252
891
                CKey key;
253
891
                if (!expand_keys.GetKey(key_id, key)) {
254
413
                    have_all_privkeys = false;
255
413
                    break;
256
413
                }
257
891
            }
258
259
            // If private keys are enabled, check some things.
260
788
            if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
261
596
               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
592
               if (!have_all_privkeys) {
265
228
                   warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
266
228
               }
267
592
            }
268
269
            // If this is an unused(KEY) descriptor, check that the wallet doesn't already have other descriptors with this key
270
784
            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
782
            WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);
290
291
            // Add descriptor to the wallet
292
782
            auto spk_manager_res = wallet.AddWalletDescriptor(w_desc, keys, label, desc_internal);
293
294
782
            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
779
            auto& spk_manager = spk_manager_res.value().get();
299
300
            // Set descriptor as active if necessary
301
779
            if (active) {
302
377
                if (!w_desc.descriptor->GetOutputType()) {
303
1
                    warnings.push_back("Unknown output type, cannot set descriptor to active.");
304
376
                } else {
305
376
                    wallet.AddActiveScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
306
376
                }
307
402
            } else {
308
402
                if (w_desc.descriptor->GetOutputType()) {
309
229
                    wallet.DeactivateScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
310
229
                }
311
402
            }
312
779
        }
313
314
716
        result.pushKV("success", UniValue(true));
315
716
    } catch (const UniValue& e) {
316
36
        result.pushKV("success", UniValue(false));
317
36
        result.pushKV("error", e);
318
36
    }
319
746
    PushWarnings(warnings, result);
320
746
    return result;
321
746
}
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
659
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);
381
659
    if (!pwallet) return UniValue::VNULL;
382
659
    CWallet& wallet{*pwallet};
383
384
659
    WalletRescanReserver reserver(*pwallet);
385
659
    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
658
    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
658
    LOCK(pwallet->m_relock_mutex);
396
397
658
    const UniValue& requests = main_request.params[0];
398
658
    const int64_t minimum_timestamp = 1;
399
658
    int64_t now = 0;
400
658
    int64_t lowest_timestamp = 0;
401
658
    bool rescan = false;
402
658
    UniValue response(UniValue::VARR);
403
658
    {
404
658
        LOCK(pwallet->cs_wallet);
405
658
        EnsureWalletIsUnlocked(*pwallet);
406
407
658
        CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now)));
408
409
        // Get all timestamps and extract the lowest timestamp
410
746
        for (const UniValue& request : requests.getValues()) {
411
            // This throws an error if "timestamp" doesn't exist
412
746
            const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp);
413
746
            const UniValue result = ProcessDescriptorImport(*pwallet, request, timestamp);
414
746
            response.push_back(result);
415
416
746
            if (lowest_timestamp > timestamp ) {
417
525
                lowest_timestamp = timestamp;
418
525
            }
419
420
            // If we know the chain tip, and at least one request was successful then allow rescan
421
746
            if (!rescan && result["success"].get_bool()) {
422
622
                rescan = true;
423
622
            }
424
746
        }
425
658
        pwallet->ConnectScriptPubKeyManNotifiers();
426
658
        pwallet->RefreshAllTXOs();
427
658
    }
428
429
    // Rescan the blockchain using the lowest timestamp
430
658
    if (rescan) {
431
622
        int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver);
432
622
        pwallet->ResubmitWalletTransactions(node::TxBroadcast::MEMPOOL_NO_BROADCAST, /*force=*/true);
433
434
622
        if (pwallet->IsAbortingRescan()) {
435
1
            throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
436
1
        }
437
438
621
        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
621
    }
480
481
657
    return response;
482
658
},
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.20k
    std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) {
537
4.20k
        return a.descriptor < b.descriptor;
538
4.20k
    });
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
887
{
571
887
    return RPCMethod{
572
887
        "backupwallet",
573
887
        "Safely copies the current wallet file to the specified destination, which can either be a directory or a path with a filename.\n",
574
887
                {
575
887
                    {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"},
576
887
                },
577
887
                RPCResult{RPCResult::Type::NONE, "", ""},
578
887
                RPCExamples{
579
887
                    HelpExampleCli("backupwallet", "\"backup.dat\"")
580
887
            + HelpExampleRpc("backupwallet", "\"backup.dat\"")
581
887
                },
582
887
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
583
887
{
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
887
    };
601
887
}
602
603
604
RPCMethod restorewallet()
605
865
{
606
865
    return RPCMethod{
607
865
        "restorewallet",
608
865
        "Restores and loads a wallet from backup.\n"
609
865
        "\nThe rescan is significantly faster if block filters are available"
610
865
        "\n(using startup option \"-blockfilterindex=1\").\n",
611
865
        {
612
865
            {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
613
865
            {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
614
865
            {"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
865
        },
616
865
        RPCResult{
617
865
            RPCResult::Type::OBJ, "", "",
618
865
            {
619
865
                {RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
620
865
                {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to restoring and loading the wallet.",
621
865
                {
622
865
                    {RPCResult::Type::STR, "", ""},
623
865
                }},
624
865
            }
625
865
        },
626
865
        RPCExamples{
627
865
            HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
628
865
            + HelpExampleRpc("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
629
865
            + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
630
865
            + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
631
865
        },
632
865
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
633
865
{
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
865
    };
659
865
}
660
} // namespace wallet