Coverage Report

Created: 2026-04-29 19:21

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