Coverage Report

Created: 2026-07-14 18:13

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