Coverage Report

Created: 2026-06-16 16:41

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