Coverage Report

Created: 2026-10-19 15:22

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/imports.h>
26
#include <wallet/rpc/util.h>
27
#include <wallet/scan.h>
28
#include <wallet/wallet.h>
29
30
#include <cstdint>
31
#include <fstream>
32
#include <tuple>
33
#include <string>
34
35
#include <univalue.h>
36
37
38
39
using interfaces::FoundBlock;
40
41
namespace wallet {
42
RPCMethod importprunedfunds()
43
885
{
44
885
    return RPCMethod{
45
885
        "importprunedfunds",
46
885
        "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",
47
885
                {
48
885
                    {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"},
49
885
                    {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"},
50
885
                },
51
885
                RPCResult{RPCResult::Type::NONE, "", ""},
52
885
                RPCExamples{""},
53
885
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
54
885
{
55
8
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
56
8
    if (!pwallet) return UniValue::VNULL;
57
58
8
    CMutableTransaction tx;
59
8
    if (!DecodeHexTx(tx, request.params[0].get_str())) {
60
1
        throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
61
1
    }
62
63
7
    CMerkleBlock merkleBlock;
64
7
    SpanReader{ParseHexV(request.params[1], "proof")} >> merkleBlock;
65
66
    //Search partial merkle tree in proof for our transaction and index in valid block
67
7
    std::vector<Txid> vMatch;
68
7
    std::vector<unsigned int> vIndex;
69
7
    if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) {
70
1
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
71
1
    }
72
73
6
    LOCK(pwallet->cs_wallet);
74
6
    int height;
75
6
    if (!pwallet->chain().findAncestorByHash(pwallet->GetLastBlockHash(), merkleBlock.header.GetHash(), FoundBlock().height(height))) {
76
1
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
77
1
    }
78
79
5
    std::vector<Txid>::const_iterator it;
80
5
    if ((it = std::find(vMatch.begin(), vMatch.end(), tx.GetHash())) == vMatch.end()) {
81
1
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof");
82
1
    }
83
84
4
    unsigned int txnIndex = vIndex[it - vMatch.begin()];
85
86
4
    const CTransactionRef tx_ref = MakeTransactionRef(tx);
87
4
    auto tx_state = TxStateConfirmed{merkleBlock.header.GetHash(), height, static_cast<int>(txnIndex)};
88
4
    if (pwallet->AddToWalletIfInvolvingMe(tx_ref, tx_state, /*rescanning_old_block=*/false)) {
89
3
        return UniValue::VNULL;
90
3
    }
91
92
1
    throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction");
93
4
},
94
885
    };
95
885
}
96
97
RPCMethod removeprunedfunds()
98
883
{
99
883
    return RPCMethod{
100
883
        "removeprunedfunds",
101
883
        "(DEPRECATED) This feature will be removed in the next major release. Start bitcoind with the `-deprecatedrpc=removeprunedfunds` option in order to use this.\n"
102
883
        "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",
103
883
                {
104
883
                    {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"},
105
883
                },
106
883
                RPCResult{RPCResult::Type::NONE, "", ""},
107
883
                RPCExamples{
108
883
                    HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") +
109
883
            "\nAs a JSON-RPC call\n"
110
883
            + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"")
111
883
                },
112
883
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
113
883
{
114
6
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
115
6
    if (!pwallet) return UniValue::VNULL;
116
117
6
    if (!pwallet->chain().rpcEnableDeprecated("removeprunedfunds")) {
118
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.");
119
1
    }
120
121
5
    LOCK(pwallet->cs_wallet);
122
123
5
    Txid hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))};
124
5
    std::vector<Txid> vHash;
125
5
    vHash.push_back(hash);
126
5
    if (auto res = pwallet->RemoveTxs(vHash); !res) {
127
1
        throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
128
1
    }
129
130
4
    return UniValue::VNULL;
131
5
},
132
883
    };
133
883
}
134
135
136
/**
137
 * Converts the timestamp from UniValue data to an int64_t.
138
 *
139
 * @returns The import timestamp in int64_t, or std::nullopt if now was provided.
140
 */
141
static std::optional<int64_t> GetImportTimestamp(const UniValue& data)
142
777
{
143
777
    if (data.exists("timestamp")) {
144
776
        const UniValue& timestamp = data["timestamp"];
145
776
        if (timestamp.isNum()) {
146
220
            const int64_t value{timestamp.getInt<int64_t>()};
147
220
            if (value < 0) {
148
1
                throw JSONRPCError(RPC_INVALID_PARAMETER, "Timestamp must not be negative");
149
1
            }
150
219
            return value;
151
556
        } else if (timestamp.isStr() && timestamp.get_str() == "now") {
152
555
            return std::nullopt; // std::nullopt means use the current best block's MTP time
153
555
        }
154
1
        throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type())));
155
776
    }
156
1
    throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key");
157
777
}
158
159
static ImportDescriptorRequest ProcessUniValueDescriptor(const UniValue& data, std::optional<int64_t> timestamp)
160
774
{
161
774
    ImportDescriptorRequest request;
162
774
    if (!data.exists("desc")) {
163
2
        throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found.");
164
2
    }
165
772
    request.descriptor = data["desc"].get_str();
166
772
    request.label = LabelFromValue(data["label"]);
167
772
    request.timestamp = timestamp;
168
772
    if (data.exists("active")) request.active = data["active"].get_bool();
169
772
    if (data.exists("internal")) request.internal = data["internal"].get_bool();
170
772
    if (data.exists("range")) request.range = ParseDescriptorRange(data["range"]);
171
772
    if (data.exists("next_index")) request.next_index = data["next_index"].getInt<int64_t>();
172
772
    return request;
173
774
}
174
175
RPCMethod importdescriptors()
176
1.56k
{
177
1.56k
    return RPCMethod{
178
1.56k
        "importdescriptors",
179
1.56k
        "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"
180
1.56k
        "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"
181
1.56k
            "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
182
1.56k
            "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
183
1.56k
            "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n",
184
1.56k
                {
185
1.56k
                    {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
186
1.56k
                        {
187
1.56k
                            {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
188
1.56k
                                {
189
1.56k
                                    {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."},
190
1.56k
                                    {"active", RPCArg::Type::BOOL, RPCArg::Default{false}, "Set this descriptor to be the active descriptor for the corresponding output type/externality"},
191
1.56k
                                    {"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"},
192
1.56k
                                    {"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"},
193
1.56k
                                    {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n"
194
1.56k
                                        "Use the string \"now\" to substitute the current synced blockchain time.\n"
195
1.56k
                                        "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
196
1.56k
                                        "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
197
1.56k
                                        "of all descriptors being imported will be scanned as well as the mempool.",
198
1.56k
                                        RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}}
199
1.56k
                                    },
200
1.56k
                                    {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
201
1.56k
                                    {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"},
202
1.56k
                                },
203
1.56k
                            },
204
1.56k
                        },
205
1.56k
                        RPCArgOptions{.oneline_description="requests"}},
206
1.56k
                },
207
1.56k
                RPCResult{
208
1.56k
                    RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
209
1.56k
                    {
210
1.56k
                        {RPCResult::Type::OBJ, "", "",
211
1.56k
                        {
212
1.56k
                            {RPCResult::Type::BOOL, "success", ""},
213
1.56k
                            {RPCResult::Type::ARR, "warnings", /*optional=*/true, "",
214
1.56k
                            {
215
1.56k
                                {RPCResult::Type::STR, "", ""},
216
1.56k
                            }},
217
1.56k
                            {RPCResult::Type::OBJ, "error", /*optional=*/true, "",
218
1.56k
                            {
219
1.56k
                                {RPCResult::Type::NUM, "code", "JSONRPC error code"},
220
1.56k
                                {RPCResult::Type::STR, "message", "JSONRPC error message"},
221
1.56k
                            }},
222
1.56k
                        }},
223
1.56k
                    }
224
1.56k
                },
225
1.56k
                RPCExamples{
226
1.56k
                    HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, "
227
1.56k
                                          "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
228
1.56k
                    HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'")
229
1.56k
                },
230
1.56k
        [](const RPCMethod& self, const JSONRPCRequest& main_request) -> UniValue
231
1.56k
{
232
683
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);
233
683
    if (!pwallet) return UniValue::VNULL;
234
683
    CWallet& wallet{*pwallet};
235
236
683
    const UniValue& univalue_requests = main_request.params[0];
237
    // One result per input, in input order.
238
683
    std::vector<UniValue> results(univalue_requests.size());
239
    // Successfully parsed requests, each with the index of the input it came from.
240
683
    struct ParsedRequest {
241
683
        size_t input_index;
242
683
        ImportDescriptorRequest request;
243
683
    };
244
683
    std::vector<ParsedRequest> requests;
245
246
    // Malformed inputs (e.g. invalid label) are returned as per-item failures.
247
1.45k
    for (size_t i = 0; i < univalue_requests.size(); ++i) {
248
        // Throws a top-level RPC error if "timestamp" is missing or invalid
249
777
        std::optional<int64_t> timestamp = GetImportTimestamp(univalue_requests[i]);
250
777
        try {
251
777
            requests.push_back({i, ProcessUniValueDescriptor(univalue_requests[i], timestamp)});
252
777
        } catch (const UniValue& e) {
253
8
            results[i] = UniValue(UniValue::VOBJ);
254
8
            results[i].pushKV("success", UniValue(false));
255
8
            results[i].pushKV("error", e);
256
8
        }
257
777
    }
258
259
    // Hand off the successfully parsed requests to the batch importer, which
260
    // handles wallet locking, rescanning and rescan-failure error composition.
261
683
    std::vector<ImportDescriptorRequest> descriptor_requests;
262
680
    descriptor_requests.reserve(requests.size());
263
766
    for (auto& parsed : requests) {
264
766
        descriptor_requests.push_back(std::move(parsed.request));
265
766
    }
266
267
680
    std::vector<ImportResult> import_results{ProcessDescriptorsImport(wallet, descriptor_requests)};
268
269
    // Wallet-wide precondition failure (e.g. already rescanning, or locked):
270
    // surface as a top-level RPC error.
271
680
    if (import_results.size() == 1 && import_results[0].has_error() && import_results[0].error->is_general_error) {
272
4
        const ImportError& import_error = import_results[0].error.value();
273
4
        RPCErrorCode rpc_error_code{HandleWalletErrorCode(import_error.wallet_error.code)};
274
4
        throw JSONRPCError(rpc_error_code, import_error.wallet_error.message.original);
275
4
    }
276
277
    // Translate each ImportResult into the per-input UniValue result. Inputs
278
    // that failed to parse already hold an error in results[] and are not
279
    // part of `requests`, so use input_index to map each import_results[k]
280
    // back to its slot.
281
676
    CHECK_NONFATAL(import_results.size() == requests.size());
282
1.43k
    for (size_t k = 0; k < requests.size(); ++k) {
283
763
        UniValue& result = results[requests[k].input_index];
284
763
        const ImportResult& import_result = import_results[k];
285
763
        result = UniValue(UniValue::VOBJ);
286
763
        UniValue warnings(UniValue::VARR);
287
763
        if (import_result.has_error()) {
288
35
            const WalletError& error = import_result.error.value().wallet_error;
289
35
            auto write_error = [&result, &error](int code) {
290
35
                result.pushKV("success", false);
291
35
                result.pushKV("error", JSONRPCError(code, error.message.original));
292
35
            };
293
35
            if (error.code == WalletErrorCode::UnlockNeeded) NONFATAL_UNREACHABLE();
294
35
            write_error(HandleWalletErrorCode(error.code));
295
728
        } else {
296
728
            result.pushKV("success", true);
297
728
        }
298
763
        for (const auto& w : import_result.warnings) {
299
600
            warnings.push_back(w);
300
600
        }
301
763
        PushWarnings(warnings, result);
302
763
    }
303
304
676
    UniValue response(UniValue::VARR);
305
771
    for (UniValue& result : results) {
306
771
        response.push_back(std::move(result));
307
771
    }
308
676
    return response;
309
676
},
310
1.56k
    };
311
1.56k
}
312
313
RPCMethod listdescriptors()
314
1.14k
{
315
1.14k
    return RPCMethod{
316
1.14k
        "listdescriptors",
317
1.14k
        "List all descriptors present in a wallet.\n",
318
1.14k
        {
319
1.14k
            {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private descriptors."}
320
1.14k
        },
321
1.14k
        RPCResult{RPCResult::Type::OBJ, "", "", {
322
1.14k
            {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
323
1.14k
            {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)",
324
1.14k
            {
325
1.14k
                {RPCResult::Type::OBJ, "", "", {
326
1.14k
                    {RPCResult::Type::STR, "desc", "Descriptor string representation"},
327
1.14k
                    {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},
328
1.14k
                    {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
329
1.14k
                    {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"},
330
1.14k
                    {RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", {
331
1.14k
                        {RPCResult::Type::NUM, "", "Range start inclusive"},
332
1.14k
                        {RPCResult::Type::NUM, "", "Range end inclusive"},
333
1.14k
                    }},
334
1.14k
                    {RPCResult::Type::NUM, "next", /*optional=*/true, "Same as next_index field. Kept for compatibility reason."},
335
1.14k
                    {RPCResult::Type::NUM, "next_index", /*optional=*/true, "The next index to generate addresses from; defined only for ranged descriptors"},
336
1.14k
                }},
337
1.14k
            }}
338
1.14k
        }},
339
1.14k
        RPCExamples{
340
1.14k
            HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "")
341
1.14k
            + HelpExampleCli("listdescriptors", "true") + HelpExampleRpc("listdescriptors", "true")
342
1.14k
        },
343
1.14k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
344
1.14k
{
345
269
    const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
346
269
    if (!wallet) return UniValue::VNULL;
347
348
269
    const bool priv = !request.params[0].isNull() && request.params[0].get_bool();
349
269
    if (wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && priv) {
350
1
        throw JSONRPCError(RPC_WALLET_ERROR, "Can't get private descriptor string for watch-only wallets");
351
1
    }
352
268
    if (priv) {
353
88
        EnsureWalletIsUnlocked(*wallet);
354
88
    }
355
356
268
    LOCK(wallet->cs_wallet);
357
268
    util::Expected<std::vector<WalletDescInfo>, std::string> exported = ExportDescriptors(*wallet, priv);
358
268
    if (!exported) {
359
0
        throw JSONRPCError(RPC_WALLET_ERROR, exported.error());
360
0
    }
361
268
    std::vector<WalletDescInfo> wallet_descriptors = *exported;
362
363
5.82k
    std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) {
364
5.82k
        return a.descriptor < b.descriptor;
365
5.82k
    });
366
367
268
    UniValue descriptors(UniValue::VARR);
368
2.09k
    for (const WalletDescInfo& info : wallet_descriptors) {
369
2.09k
        UniValue spk(UniValue::VOBJ);
370
2.09k
        spk.pushKV("desc", info.descriptor);
371
2.09k
        spk.pushKV("timestamp", info.creation_time);
372
2.09k
        spk.pushKV("active", info.active);
373
2.09k
        if (info.internal.has_value()) {
374
1.90k
            spk.pushKV("internal", info.internal.value());
375
1.90k
        }
376
2.09k
        if (info.range.has_value()) {
377
1.95k
            UniValue range(UniValue::VARR);
378
1.95k
            range.push_back(info.range->first);
379
1.95k
            range.push_back(info.range->second - 1);
380
1.95k
            spk.pushKV("range", std::move(range));
381
1.95k
            spk.pushKV("next", info.next_index);
382
1.95k
            spk.pushKV("next_index", info.next_index);
383
1.95k
        }
384
2.09k
        descriptors.push_back(std::move(spk));
385
2.09k
    }
386
387
268
    UniValue response(UniValue::VOBJ);
388
268
    response.pushKV("wallet_name", wallet->GetName());
389
268
    response.pushKV("descriptors", std::move(descriptors));
390
391
268
    return response;
392
268
},
393
1.14k
    };
394
1.14k
}
395
396
RPCMethod backupwallet()
397
946
{
398
946
    return RPCMethod{
399
946
        "backupwallet",
400
946
        "Safely copies the current wallet file to the specified destination, which can either be a directory or a path with a filename.\n",
401
946
                {
402
946
                    {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"},
403
946
                },
404
946
                RPCResult{RPCResult::Type::NONE, "", ""},
405
946
                RPCExamples{
406
946
                    HelpExampleCli("backupwallet", "\"backup.dat\"")
407
946
            + HelpExampleRpc("backupwallet", "\"backup.dat\"")
408
946
                },
409
946
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
410
946
{
411
69
    const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
412
69
    if (!pwallet) return UniValue::VNULL;
413
414
    // Make sure the results are valid at least up to the most recent block
415
    // the user could have gotten from another RPC command prior to now
416
69
    pwallet->BlockUntilSyncedToCurrentChain();
417
418
69
    LOCK(pwallet->cs_wallet);
419
420
69
    std::string strDest = request.params[0].get_str();
421
69
    if (!pwallet->BackupWallet(strDest)) {
422
4
        throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!");
423
4
    }
424
425
65
    return UniValue::VNULL;
426
69
},
427
946
    };
428
946
}
429
430
431
RPCMethod restorewallet()
432
924
{
433
924
    return RPCMethod{
434
924
        "restorewallet",
435
924
        "Restores and loads a wallet from backup.\n"
436
924
        "\nThe rescan is significantly faster if block filters are available"
437
924
        "\n(using startup option \"-blockfilterindex=1\").\n",
438
924
        {
439
924
            {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
440
924
            {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
441
924
            {"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."},
442
924
        },
443
924
        RPCResult{
444
924
            RPCResult::Type::OBJ, "", "",
445
924
            {
446
924
                {RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
447
924
                {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to restoring and loading the wallet.",
448
924
                {
449
924
                    {RPCResult::Type::STR, "", ""},
450
924
                }},
451
924
            }
452
924
        },
453
924
        RPCExamples{
454
924
            HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
455
924
            + HelpExampleRpc("restorewallet", R"("testwallet", "home\\backups\\backup-file.bak")")
456
924
            + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak"}, {"load_on_startup", true}})
457
924
            + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak"}, {"load_on_startup", true}})
458
924
        },
459
924
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
460
924
{
461
462
47
    WalletContext& context = EnsureWalletContext(request.context);
463
464
47
    auto backup_file = fs::u8path(request.params[1].get_str());
465
466
47
    std::string wallet_name = request.params[0].get_str();
467
468
47
    std::optional<bool> load_on_start = request.params[2].isNull() ? std::nullopt : std::optional<bool>(request.params[2].get_bool());
469
470
47
    DatabaseStatus status;
471
47
    bilingual_str error;
472
47
    std::vector<bilingual_str> warnings;
473
474
47
    const std::shared_ptr<CWallet> wallet = RestoreWallet(context, backup_file, wallet_name, load_on_start, status, error, warnings);
475
476
47
    HandleWalletError(wallet, status, error);
477
478
47
    UniValue obj(UniValue::VOBJ);
479
47
    obj.pushKV("name", wallet->GetName());
480
47
    PushWarnings(warnings, obj);
481
482
47
    return obj;
483
484
47
},
485
924
    };
486
924
}
487
} // namespace wallet