Coverage Report

Created: 2025-06-10 13:21

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