Coverage Report

Created: 2026-09-14 20:36

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