Coverage Report

Created: 2026-05-30 09:47

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
824
{
41
824
    return RPCMethod{
42
824
        "importprunedfunds",
43
824
        "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
824
                {
45
824
                    {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"},
46
824
                    {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"},
47
824
                },
48
824
                RPCResult{RPCResult::Type::NONE, "", ""},
49
824
                RPCExamples{""},
50
824
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
51
824
{
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
824
    };
92
824
}
93
94
RPCMethod removeprunedfunds()
95
825
{
96
825
    return RPCMethod{
97
825
        "removeprunedfunds",
98
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",
99
825
                {
100
825
                    {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"},
101
825
                },
102
825
                RPCResult{RPCResult::Type::NONE, "", ""},
103
825
                RPCExamples{
104
825
                    HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") +
105
825
            "\nAs a JSON-RPC call\n"
106
825
            + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"")
107
825
                },
108
825
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
109
825
{
110
8
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
111
8
    if (!pwallet) return UniValue::VNULL;
112
113
8
    LOCK(pwallet->cs_wallet);
114
115
8
    Txid hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))};
116
8
    std::vector<Txid> vHash;
117
8
    vHash.push_back(hash);
118
8
    if (auto res = pwallet->RemoveTxs(vHash); !res) {
119
1
        throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
120
1
    }
121
122
7
    return UniValue::VNULL;
123
8
},
124
825
    };
125
825
}
126
127
static int64_t GetImportTimestamp(const UniValue& data, int64_t now)
128
737
{
129
737
    if (data.exists("timestamp")) {
130
737
        const UniValue& timestamp = data["timestamp"];
131
737
        if (timestamp.isNum()) {
132
212
            return timestamp.getInt<int64_t>();
133
525
        } else if (timestamp.isStr() && timestamp.get_str() == "now") {
134
525
            return now;
135
525
        }
136
0
        throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type())));
137
737
    }
138
0
    throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key");
139
737
}
140
141
static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
142
735
{
143
735
    UniValue warnings(UniValue::VARR);
144
735
    UniValue result(UniValue::VOBJ);
145
146
735
    try {
147
735
        if (!data.exists("desc")) {
148
1
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found.");
149
1
        }
150
151
734
        const std::string& descriptor = data["desc"].get_str();
152
734
        const bool active = data.exists("active") ? data["active"].get_bool() : false;
153
734
        const std::string label{LabelFromValue(data["label"])};
154
155
        // Parse descriptor string
156
734
        FlatSigningProvider keys;
157
734
        std::string error;
158
734
        auto parsed_descs = Parse(descriptor, keys, error, /* require_checksum = */ true);
159
734
        if (parsed_descs.empty()) {
160
8
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
161
8
        }
162
726
        std::optional<bool> internal;
163
726
        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
725
        std::optional<bool> is_ranged;
172
725
        int64_t range_start = 0, range_end = 1, next_index = 0;
173
725
        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
724
        } else if (parsed_descs.at(0)->IsRange()) {
176
460
            if (data.exists("range")) {
177
115
                auto range = ParseDescriptorRange(data["range"]);
178
115
                range_start = range.first;
179
115
                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
460
            next_index = range_start;
186
460
            is_ranged = true;
187
188
460
            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
460
        }
196
197
        // Active descriptors must be ranged
198
724
        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
723
        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
722
        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
720
        bool desc_internal = internal.has_value() && internal.value();
213
        // Internal addresses should not have a label either
214
720
        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
719
        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
718
        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.48k
        for (size_t j = 0; j < parsed_descs.size(); ++j) {
229
778
            auto parsed_desc = std::move(parsed_descs[j]);
230
778
            if (parsed_descs.size() == 2) {
231
126
                desc_internal = j == 1;
232
652
            } 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
778
            FlatSigningProvider expand_keys;
237
778
            std::vector<CScript> scripts;
238
778
            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
777
            parsed_desc->ExpandPrivate(0, keys, expand_keys);
242
243
777
            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
777
            bool have_all_privkeys = !expand_keys.keys.empty();
249
861
            for (const auto& entry : expand_keys.origins) {
250
861
                const CKeyID& key_id = entry.first;
251
861
                CKey key;
252
861
                if (!expand_keys.GetKey(key_id, key)) {
253
409
                    have_all_privkeys = false;
254
409
                    break;
255
409
                }
256
861
            }
257
258
            // If private keys are enabled, check some things.
259
777
            if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
260
586
               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
582
               if (!have_all_privkeys) {
264
225
                   warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
265
225
               }
266
582
            }
267
268
            // If this is an unused(KEY) descriptor, check that the wallet doesn't already have other descriptors with this key
269
773
            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
771
            WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);
289
290
            // Add descriptor to the wallet
291
771
            auto spk_manager_res = wallet.AddWalletDescriptor(w_desc, keys, label, desc_internal);
292
293
771
            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
768
            auto& spk_manager = spk_manager_res.value().get();
298
299
            // Set descriptor as active if necessary
300
768
            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
395
            } else {
307
395
                if (w_desc.descriptor->GetOutputType()) {
308
224
                    wallet.DeactivateScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
309
224
                }
310
395
            }
311
768
        }
312
313
705
        result.pushKV("success", UniValue(true));
314
705
    } catch (const UniValue& e) {
315
36
        result.pushKV("success", UniValue(false));
316
36
        result.pushKV("error", e);
317
36
    }
318
735
    PushWarnings(warnings, result);
319
735
    return result;
320
735
}
321
322
RPCMethod importdescriptors()
323
1.46k
{
324
1.46k
    return RPCMethod{
325
1.46k
        "importdescriptors",
326
1.46k
        "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.46k
        "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.46k
            "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
329
1.46k
            "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
330
1.46k
            "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n",
331
1.46k
                {
332
1.46k
                    {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
333
1.46k
                        {
334
1.46k
                            {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
335
1.46k
                                {
336
1.46k
                                    {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."},
337
1.46k
                                    {"active", RPCArg::Type::BOOL, RPCArg::Default{false}, "Set this descriptor to be the active descriptor for the corresponding output type/externality"},
338
1.46k
                                    {"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.46k
                                    {"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.46k
                                    {"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.46k
                                        "Use the string \"now\" to substitute the current synced blockchain time.\n"
342
1.46k
                                        "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
343
1.46k
                                        "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
344
1.46k
                                        "of all descriptors being imported will be scanned as well as the mempool.",
345
1.46k
                                        RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}}
346
1.46k
                                    },
347
1.46k
                                    {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
348
1.46k
                                    {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"},
349
1.46k
                                },
350
1.46k
                            },
351
1.46k
                        },
352
1.46k
                        RPCArgOptions{.oneline_description="requests"}},
353
1.46k
                },
354
1.46k
                RPCResult{
355
1.46k
                    RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
356
1.46k
                    {
357
1.46k
                        {RPCResult::Type::OBJ, "", "",
358
1.46k
                        {
359
1.46k
                            {RPCResult::Type::BOOL, "success", ""},
360
1.46k
                            {RPCResult::Type::ARR, "warnings", /*optional=*/true, "",
361
1.46k
                            {
362
1.46k
                                {RPCResult::Type::STR, "", ""},
363
1.46k
                            }},
364
1.46k
                            {RPCResult::Type::OBJ, "error", /*optional=*/true, "",
365
1.46k
                            {
366
1.46k
                                {RPCResult::Type::NUM, "code", "JSONRPC error code"},
367
1.46k
                                {RPCResult::Type::STR, "message", "JSONRPC error message"},
368
1.46k
                            }},
369
1.46k
                        }},
370
1.46k
                    }
371
1.46k
                },
372
1.46k
                RPCExamples{
373
1.46k
                    HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, "
374
1.46k
                                          "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
375
1.46k
                    HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'")
376
1.46k
                },
377
1.46k
        [](const RPCMethod& self, const JSONRPCRequest& main_request) -> UniValue
378
1.46k
{
379
650
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);
380
650
    if (!pwallet) return UniValue::VNULL;
381
650
    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
650
    wallet.BlockUntilSyncedToCurrentChain();
386
387
650
    WalletRescanReserver reserver(*pwallet);
388
650
    if (!reserver.reserve(/*with_passphrase=*/true)) {
389
0
        throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
390
0
    }
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
650
    LOCK(pwallet->m_relock_mutex);
395
396
650
    const UniValue& requests = main_request.params[0];
397
650
    const int64_t minimum_timestamp = 1;
398
650
    int64_t now = 0;
399
650
    int64_t lowest_timestamp = 0;
400
650
    bool rescan = false;
401
650
    UniValue response(UniValue::VARR);
402
650
    {
403
650
        LOCK(pwallet->cs_wallet);
404
650
        EnsureWalletIsUnlocked(*pwallet);
405
406
650
        CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now)));
407
408
        // Get all timestamps and extract the lowest timestamp
409
735
        for (const UniValue& request : requests.getValues()) {
410
            // This throws an error if "timestamp" doesn't exist
411
735
            const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp);
412
735
            const UniValue result = ProcessDescriptorImport(*pwallet, request, timestamp);
413
735
            response.push_back(result);
414
415
735
            if (lowest_timestamp > timestamp ) {
416
515
                lowest_timestamp = timestamp;
417
515
            }
418
419
            // If we know the chain tip, and at least one request was successful then allow rescan
420
735
            if (!rescan && result["success"].get_bool()) {
421
615
                rescan = true;
422
615
            }
423
735
        }
424
650
        pwallet->ConnectScriptPubKeyManNotifiers();
425
650
        pwallet->RefreshAllTXOs();
426
650
    }
427
428
    // Rescan the blockchain using the lowest timestamp
429
650
    if (rescan) {
430
615
        int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver);
431
615
        pwallet->ResubmitWalletTransactions(node::TxBroadcast::MEMPOOL_NO_BROADCAST, /*force=*/true);
432
433
615
        if (pwallet->IsAbortingRescan()) {
434
0
            throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
435
0
        }
436
437
615
        if (scanned_time > lowest_timestamp) {
438
1
            std::vector<UniValue> results = response.getValues();
439
1
            response.clear();
440
1
            response.setArray();
441
442
            // Compose the response
443
2
            for (unsigned int i = 0; i < requests.size(); ++i) {
444
1
                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
1
                if (scanned_time <= GetImportTimestamp(request, now) || results.at(i).exists("error")) {
451
0
                    response.push_back(results.at(i));
452
1
                } else {
453
1
                    std::string error_msg{strprintf("Rescan failed for descriptor with timestamp %d. There "
454
1
                            "was an error reading a block from time %d, which is after or within %d seconds "
455
1
                            "of key creation, and could contain transactions pertaining to the desc. As a "
456
1
                            "result, transactions and coins using this desc may not appear in the wallet.",
457
1
                            GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)};
458
1
                    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
1
                    } else if (pwallet->chain().hasAssumedValidChain()) {
463
1
                        error_msg += strprintf(" This error is likely caused by an in-progress assumeutxo "
464
1
                                "background sync. Check logs or getchainstates RPC for assumeutxo background "
465
1
                                "sync progress and try again later.");
466
1
                    } 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
1
                    UniValue result = UniValue(UniValue::VOBJ);
472
1
                    result.pushKV("success", UniValue(false));
473
1
                    result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, error_msg));
474
1
                    response.push_back(std::move(result));
475
1
                }
476
1
            }
477
1
        }
478
615
    }
479
480
650
    return response;
481
650
},
482
1.46k
    };
483
1.46k
}
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
4.01k
    std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) {
563
4.01k
        return a.descriptor < b.descriptor;
564
4.01k
    });
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
883
{
597
883
    return RPCMethod{
598
883
        "backupwallet",
599
883
        "Safely copies the current wallet file to the specified destination, which can either be a directory or a path with a filename.\n",
600
883
                {
601
883
                    {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"},
602
883
                },
603
883
                RPCResult{RPCResult::Type::NONE, "", ""},
604
883
                RPCExamples{
605
883
                    HelpExampleCli("backupwallet", "\"backup.dat\"")
606
883
            + HelpExampleRpc("backupwallet", "\"backup.dat\"")
607
883
                },
608
883
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
609
883
{
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
883
    };
627
883
}
628
629
630
RPCMethod restorewallet()
631
854
{
632
854
    return RPCMethod{
633
854
        "restorewallet",
634
854
        "Restores and loads a wallet from backup.\n"
635
854
        "\nThe rescan is significantly faster if block filters are available"
636
854
        "\n(using startup option \"-blockfilterindex=1\").\n",
637
854
        {
638
854
            {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
639
854
            {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
640
854
            {"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
854
        },
642
854
        RPCResult{
643
854
            RPCResult::Type::OBJ, "", "",
644
854
            {
645
854
                {RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
646
854
                {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to restoring and loading the wallet.",
647
854
                {
648
854
                    {RPCResult::Type::STR, "", ""},
649
854
                }},
650
854
            }
651
854
        },
652
854
        RPCExamples{
653
854
            HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
654
854
            + HelpExampleRpc("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
655
854
            + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
656
854
            + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
657
854
        },
658
854
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
659
854
{
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
854
    };
685
854
}
686
} // namespace wallet