Coverage Report

Created: 2026-08-14 20:23

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
838
{
42
838
    return RPCMethod{
43
838
        "importprunedfunds",
44
838
        "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
838
                {
46
838
                    {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"},
47
838
                    {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"},
48
838
                },
49
838
                RPCResult{RPCResult::Type::NONE, "", ""},
50
838
                RPCExamples{""},
51
838
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
52
838
{
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
838
    };
93
838
}
94
95
RPCMethod removeprunedfunds()
96
836
{
97
836
    return RPCMethod{
98
836
        "removeprunedfunds",
99
836
        "(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
836
        "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
836
                {
102
836
                    {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"},
103
836
                },
104
836
                RPCResult{RPCResult::Type::NONE, "", ""},
105
836
                RPCExamples{
106
836
                    HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") +
107
836
            "\nAs a JSON-RPC call\n"
108
836
            + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"")
109
836
                },
110
836
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
111
836
{
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
836
    };
131
836
}
132
133
static int64_t GetImportTimestamp(const UniValue& data, int64_t now)
134
763
{
135
763
    if (data.exists("timestamp")) {
136
762
        const UniValue& timestamp = data["timestamp"];
137
762
        if (timestamp.isNum()) {
138
217
            return timestamp.getInt<int64_t>();
139
545
        } else if (timestamp.isStr() && timestamp.get_str() == "now") {
140
544
            return now;
141
544
        }
142
1
        throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type())));
143
762
    }
144
1
    throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key");
145
763
}
146
147
static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
148
757
{
149
757
    UniValue warnings(UniValue::VARR);
150
757
    UniValue result(UniValue::VOBJ);
151
152
757
    try {
153
757
        if (!data.exists("desc")) {
154
2
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found.");
155
2
        }
156
157
755
        const std::string& descriptor = data["desc"].get_str();
158
755
        const bool active = data.exists("active") ? data["active"].get_bool() : false;
159
755
        const std::string label{LabelFromValue(data["label"])};
160
161
        // Parse descriptor string
162
755
        FlatSigningProvider keys;
163
755
        std::string error;
164
755
        auto parsed_descs = Parse(descriptor, keys, error, /* require_checksum = */ true);
165
755
        if (parsed_descs.empty()) {
166
9
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
167
9
        }
168
746
        std::optional<bool> internal;
169
746
        if (data.exists("internal")) {
170
114
            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
113
            internal = data["internal"].get_bool();
174
113
        }
175
176
        // Range check
177
745
        std::optional<bool> is_ranged;
178
745
        int64_t range_start = 0, range_end = 1, next_index = 0;
179
745
        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
744
        } else if (parsed_descs.at(0)->IsRange()) {
182
470
            if (data.exists("range")) {
183
119
                auto range = ParseDescriptorRange(data["range"]);
184
119
                range_start = range.first;
185
119
                range_end = range.second + 1; // Specified range end is inclusive, but we need range end as exclusive
186
351
            } else {
187
351
                warnings.push_back("Range not given, using default keypool range");
188
351
                range_start = 0;
189
351
                range_end = wallet.m_keypool_size;
190
351
            }
191
470
            next_index = range_start;
192
470
            is_ranged = true;
193
194
470
            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
470
        }
202
203
        // Active descriptors must be ranged
204
744
        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
743
        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
742
        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
740
        bool desc_internal = internal.has_value() && internal.value();
219
        // Internal addresses should not have a label either
220
740
        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
738
        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
737
        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.52k
        for (size_t j = 0; j < parsed_descs.size(); ++j) {
235
797
            auto parsed_desc = std::move(parsed_descs[j]);
236
797
            if (parsed_descs.size() == 2) {
237
126
                desc_internal = j == 1;
238
671
            } 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
797
            FlatSigningProvider expand_keys;
243
797
            std::vector<CScript> scripts;
244
797
            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
796
            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
796
            if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
254
604
                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
600
                if (!parsed_desc->HavePrivateKeys(keys)) {
258
232
                    warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
259
232
                }
260
600
            }
261
262
            // If this is an unused(KEY) descriptor, check that the wallet doesn't already have other descriptors with this key
263
792
            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
790
            WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);
283
284
            // Add descriptor to the wallet
285
790
            auto spk_manager_res = wallet.AddWalletDescriptor(w_desc, keys, label, desc_internal);
286
287
790
            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
787
            auto& spk_manager = spk_manager_res.value().get();
292
293
            // Set descriptor as active if necessary
294
787
            if (active) {
295
379
                if (!w_desc.descriptor->GetOutputType()) {
296
1
                    warnings.push_back("Unknown output type, cannot set descriptor to active.");
297
378
                } else {
298
378
                    wallet.AddActiveScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
299
378
                }
300
408
            } else {
301
408
                if (w_desc.descriptor->GetOutputType()) {
302
235
                    wallet.DeactivateScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
303
235
                }
304
408
            }
305
787
        }
306
307
724
        result.pushKV("success", UniValue(true));
308
724
    } catch (const UniValue& e) {
309
39
        result.pushKV("success", UniValue(false));
310
39
        result.pushKV("error", e);
311
39
    }
312
757
    PushWarnings(warnings, result);
313
757
    return result;
314
757
}
315
316
RPCMethod importdescriptors()
317
1.49k
{
318
1.49k
    return RPCMethod{
319
1.49k
        "importdescriptors",
320
1.49k
        "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.49k
        "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.49k
            "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
323
1.49k
            "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
324
1.49k
            "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n",
325
1.49k
                {
326
1.49k
                    {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
327
1.49k
                        {
328
1.49k
                            {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
329
1.49k
                                {
330
1.49k
                                    {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."},
331
1.49k
                                    {"active", RPCArg::Type::BOOL, RPCArg::Default{false}, "Set this descriptor to be the active descriptor for the corresponding output type/externality"},
332
1.49k
                                    {"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.49k
                                    {"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.49k
                                    {"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.49k
                                        "Use the string \"now\" to substitute the current synced blockchain time.\n"
336
1.49k
                                        "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
337
1.49k
                                        "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
338
1.49k
                                        "of all descriptors being imported will be scanned as well as the mempool.",
339
1.49k
                                        RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}}
340
1.49k
                                    },
341
1.49k
                                    {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
342
1.49k
                                    {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"},
343
1.49k
                                },
344
1.49k
                            },
345
1.49k
                        },
346
1.49k
                        RPCArgOptions{.oneline_description="requests"}},
347
1.49k
                },
348
1.49k
                RPCResult{
349
1.49k
                    RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
350
1.49k
                    {
351
1.49k
                        {RPCResult::Type::OBJ, "", "",
352
1.49k
                        {
353
1.49k
                            {RPCResult::Type::BOOL, "success", ""},
354
1.49k
                            {RPCResult::Type::ARR, "warnings", /*optional=*/true, "",
355
1.49k
                            {
356
1.49k
                                {RPCResult::Type::STR, "", ""},
357
1.49k
                            }},
358
1.49k
                            {RPCResult::Type::OBJ, "error", /*optional=*/true, "",
359
1.49k
                            {
360
1.49k
                                {RPCResult::Type::NUM, "code", "JSONRPC error code"},
361
1.49k
                                {RPCResult::Type::STR, "message", "JSONRPC error message"},
362
1.49k
                            }},
363
1.49k
                        }},
364
1.49k
                    }
365
1.49k
                },
366
1.49k
                RPCExamples{
367
1.49k
                    HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, "
368
1.49k
                                          "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
369
1.49k
                    HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'")
370
1.49k
                },
371
1.49k
        [](const RPCMethod& self, const JSONRPCRequest& main_request) -> UniValue
372
1.49k
{
373
667
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);
374
667
    if (!pwallet) return UniValue::VNULL;
375
667
    CWallet& wallet{*pwallet};
376
377
667
    WalletRescanReserver reserver(*pwallet);
378
667
    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
666
    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
666
    LOCK(pwallet->m_relock_mutex);
389
390
666
    const UniValue& requests = main_request.params[0];
391
666
    const int64_t minimum_timestamp = 1;
392
666
    int64_t now = 0;
393
666
    int64_t lowest_timestamp = 0;
394
666
    bool rescan = false;
395
666
    UniValue response(UniValue::VARR);
396
666
    {
397
666
        LOCK(pwallet->cs_wallet);
398
666
        EnsureWalletIsUnlocked(*pwallet);
399
400
666
        CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now)));
401
402
        // Get all timestamps and extract the lowest timestamp
403
759
        for (const UniValue& request : requests.getValues()) {
404
            // This throws an error if "timestamp" doesn't exist
405
759
            const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp);
406
759
            const UniValue result = ProcessDescriptorImport(*pwallet, request, timestamp);
407
759
            response.push_back(result);
408
409
759
            if (lowest_timestamp > timestamp ) {
410
514
                lowest_timestamp = timestamp;
411
514
            }
412
413
            // If we know the chain tip, and at least one request was successful then allow rescan
414
759
            if (!rescan && result["success"].get_bool()) {
415
626
                rescan = true;
416
626
            }
417
759
        }
418
666
        pwallet->ConnectScriptPubKeyManNotifiers();
419
666
        pwallet->RefreshAllTXOs();
420
666
    }
421
422
    // Rescan the blockchain using the lowest timestamp
423
666
    if (rescan) {
424
626
        int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver);
425
626
        pwallet->ResubmitWalletTransactions(node::TxBroadcast::MEMPOOL_NO_BROADCAST, /*force=*/true);
426
427
626
        if (pwallet->IsAbortingRescan()) {
428
1
            throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
429
1
        }
430
431
625
        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
625
    }
473
474
665
    return response;
475
666
},
476
1.49k
    };
477
1.49k
}
478
479
RPCMethod listdescriptors()
480
1.03k
{
481
1.03k
    return RPCMethod{
482
1.03k
        "listdescriptors",
483
1.03k
        "List all descriptors present in a wallet.\n",
484
1.03k
        {
485
1.03k
            {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private descriptors."}
486
1.03k
        },
487
1.03k
        RPCResult{RPCResult::Type::OBJ, "", "", {
488
1.03k
            {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
489
1.03k
            {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)",
490
1.03k
            {
491
1.03k
                {RPCResult::Type::OBJ, "", "", {
492
1.03k
                    {RPCResult::Type::STR, "desc", "Descriptor string representation"},
493
1.03k
                    {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},
494
1.03k
                    {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
495
1.03k
                    {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.03k
                    {RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", {
497
1.03k
                        {RPCResult::Type::NUM, "", "Range start inclusive"},
498
1.03k
                        {RPCResult::Type::NUM, "", "Range end inclusive"},
499
1.03k
                    }},
500
1.03k
                    {RPCResult::Type::NUM, "next", /*optional=*/true, "Same as next_index field. Kept for compatibility reason."},
501
1.03k
                    {RPCResult::Type::NUM, "next_index", /*optional=*/true, "The next index to generate addresses from; defined only for ranged descriptors"},
502
1.03k
                }},
503
1.03k
            }}
504
1.03k
        }},
505
1.03k
        RPCExamples{
506
1.03k
            HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "")
507
1.03k
            + HelpExampleCli("listdescriptors", "true") + HelpExampleRpc("listdescriptors", "true")
508
1.03k
        },
509
1.03k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
510
1.03k
{
511
206
    const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
512
206
    if (!wallet) return UniValue::VNULL;
513
514
206
    const bool priv = !request.params[0].isNull() && request.params[0].get_bool();
515
206
    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
205
    if (priv) {
519
84
        EnsureWalletIsUnlocked(*wallet);
520
84
    }
521
522
205
    LOCK(wallet->cs_wallet);
523
205
    util::Expected<std::vector<WalletDescInfo>, std::string> exported = ExportDescriptors(*wallet, priv);
524
205
    if (!exported) {
525
0
        throw JSONRPCError(RPC_WALLET_ERROR, exported.error());
526
0
    }
527
205
    std::vector<WalletDescInfo> wallet_descriptors = *exported;
528
529
4.77k
    std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) {
530
4.77k
        return a.descriptor < b.descriptor;
531
4.77k
    });
532
533
205
    UniValue descriptors(UniValue::VARR);
534
1.57k
    for (const WalletDescInfo& info : wallet_descriptors) {
535
1.57k
        UniValue spk(UniValue::VOBJ);
536
1.57k
        spk.pushKV("desc", info.descriptor);
537
1.57k
        spk.pushKV("timestamp", info.creation_time);
538
1.57k
        spk.pushKV("active", info.active);
539
1.57k
        if (info.internal.has_value()) {
540
1.44k
            spk.pushKV("internal", info.internal.value());
541
1.44k
        }
542
1.57k
        if (info.range.has_value()) {
543
1.48k
            UniValue range(UniValue::VARR);
544
1.48k
            range.push_back(info.range->first);
545
1.48k
            range.push_back(info.range->second - 1);
546
1.48k
            spk.pushKV("range", std::move(range));
547
1.48k
            spk.pushKV("next", info.next_index);
548
1.48k
            spk.pushKV("next_index", info.next_index);
549
1.48k
        }
550
1.57k
        descriptors.push_back(std::move(spk));
551
1.57k
    }
552
553
205
    UniValue response(UniValue::VOBJ);
554
205
    response.pushKV("wallet_name", wallet->GetName());
555
205
    response.pushKV("descriptors", std::move(descriptors));
556
557
205
    return response;
558
205
},
559
1.03k
    };
560
1.03k
}
561
562
RPCMethod backupwallet()
563
897
{
564
897
    return RPCMethod{
565
897
        "backupwallet",
566
897
        "Safely copies the current wallet file to the specified destination, which can either be a directory or a path with a filename.\n",
567
897
                {
568
897
                    {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"},
569
897
                },
570
897
                RPCResult{RPCResult::Type::NONE, "", ""},
571
897
                RPCExamples{
572
897
                    HelpExampleCli("backupwallet", "\"backup.dat\"")
573
897
            + HelpExampleRpc("backupwallet", "\"backup.dat\"")
574
897
                },
575
897
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
576
897
{
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
897
    };
594
897
}
595
596
597
RPCMethod restorewallet()
598
875
{
599
875
    return RPCMethod{
600
875
        "restorewallet",
601
875
        "Restores and loads a wallet from backup.\n"
602
875
        "\nThe rescan is significantly faster if block filters are available"
603
875
        "\n(using startup option \"-blockfilterindex=1\").\n",
604
875
        {
605
875
            {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
606
875
            {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
607
875
            {"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
875
        },
609
875
        RPCResult{
610
875
            RPCResult::Type::OBJ, "", "",
611
875
            {
612
875
                {RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
613
875
                {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to restoring and loading the wallet.",
614
875
                {
615
875
                    {RPCResult::Type::STR, "", ""},
616
875
                }},
617
875
            }
618
875
        },
619
875
        RPCExamples{
620
875
            HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
621
875
            + HelpExampleRpc("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
622
875
            + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
623
875
            + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
624
875
        },
625
875
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
626
875
{
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
875
    };
652
875
}
653
} // namespace wallet