Coverage Report

Created: 2026-10-07 13:27

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
881
{
44
881
    return RPCMethod{
45
881
        "importprunedfunds",
46
881
        "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
881
                {
48
881
                    {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"},
49
881
                    {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"},
50
881
                },
51
881
                RPCResult{RPCResult::Type::NONE, "", ""},
52
881
                RPCExamples{""},
53
881
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
54
881
{
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
881
    };
95
881
}
96
97
RPCMethod removeprunedfunds()
98
879
{
99
879
    return RPCMethod{
100
879
        "removeprunedfunds",
101
879
        "(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
879
        "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
879
                {
104
879
                    {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"},
105
879
                },
106
879
                RPCResult{RPCResult::Type::NONE, "", ""},
107
879
                RPCExamples{
108
879
                    HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") +
109
879
            "\nAs a JSON-RPC call\n"
110
879
            + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"")
111
879
                },
112
879
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
113
879
{
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
879
    };
133
879
}
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
775
{
143
775
    if (data.exists("timestamp")) {
144
774
        const UniValue& timestamp = data["timestamp"];
145
774
        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
554
        } else if (timestamp.isStr() && timestamp.get_str() == "now") {
152
553
            return std::nullopt; // std::nullopt means use the current best block's MTP time
153
553
        }
154
1
        throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type())));
155
774
    }
156
1
    throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key");
157
775
}
158
159
static ImportDescriptorRequest ProcessUniValueDescriptor(const UniValue& data, std::optional<int64_t> timestamp)
160
772
{
161
772
    ImportDescriptorRequest request;
162
772
    if (!data.exists("desc")) {
163
2
        throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found.");
164
2
    }
165
770
    request.descriptor = data["desc"].get_str();
166
770
    request.label = LabelFromValue(data["label"]);
167
770
    request.timestamp = timestamp;
168
770
    if (data.exists("active")) request.active = data["active"].get_bool();
169
770
    if (data.exists("internal")) request.internal = data["internal"].get_bool();
170
770
    if (data.exists("range")) request.range = ParseDescriptorRange(data["range"]);
171
770
    if (data.exists("next_index")) request.next_index = data["next_index"].getInt<int64_t>();
172
770
    return request;
173
772
}
174
175
RPCMethod importdescriptors()
176
1.55k
{
177
1.55k
    return RPCMethod{
178
1.55k
        "importdescriptors",
179
1.55k
        "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.55k
        "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.55k
            "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
182
1.55k
            "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
183
1.55k
            "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n",
184
1.55k
                {
185
1.55k
                    {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
186
1.55k
                        {
187
1.55k
                            {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
188
1.55k
                                {
189
1.55k
                                    {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."},
190
1.55k
                                    {"active", RPCArg::Type::BOOL, RPCArg::Default{false}, "Set this descriptor to be the active descriptor for the corresponding output type/externality"},
191
1.55k
                                    {"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.55k
                                    {"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.55k
                                    {"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.55k
                                        "Use the string \"now\" to substitute the current synced blockchain time.\n"
195
1.55k
                                        "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
196
1.55k
                                        "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
197
1.55k
                                        "of all descriptors being imported will be scanned as well as the mempool.",
198
1.55k
                                        RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}}
199
1.55k
                                    },
200
1.55k
                                    {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
201
1.55k
                                    {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"},
202
1.55k
                                },
203
1.55k
                            },
204
1.55k
                        },
205
1.55k
                        RPCArgOptions{.oneline_description="requests"}},
206
1.55k
                },
207
1.55k
                RPCResult{
208
1.55k
                    RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
209
1.55k
                    {
210
1.55k
                        {RPCResult::Type::OBJ, "", "",
211
1.55k
                        {
212
1.55k
                            {RPCResult::Type::BOOL, "success", ""},
213
1.55k
                            {RPCResult::Type::ARR, "warnings", /*optional=*/true, "",
214
1.55k
                            {
215
1.55k
                                {RPCResult::Type::STR, "", ""},
216
1.55k
                            }},
217
1.55k
                            {RPCResult::Type::OBJ, "error", /*optional=*/true, "",
218
1.55k
                            {
219
1.55k
                                {RPCResult::Type::NUM, "code", "JSONRPC error code"},
220
1.55k
                                {RPCResult::Type::STR, "message", "JSONRPC error message"},
221
1.55k
                            }},
222
1.55k
                        }},
223
1.55k
                    }
224
1.55k
                },
225
1.55k
                RPCExamples{
226
1.55k
                    HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, "
227
1.55k
                                          "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
228
1.55k
                    HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'")
229
1.55k
                },
230
1.55k
        [](const RPCMethod& self, const JSONRPCRequest& main_request) -> UniValue
231
1.55k
{
232
681
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);
233
681
    if (!pwallet) return UniValue::VNULL;
234
681
    CWallet& wallet{*pwallet};
235
236
681
    const UniValue& univalue_requests = main_request.params[0];
237
    // One result per input, in input order.
238
681
    std::vector<UniValue> results(univalue_requests.size());
239
    // Successfully parsed requests, each with the index of the input it came from.
240
681
    struct ParsedRequest {
241
681
        size_t input_index;
242
681
        ImportDescriptorRequest request;
243
681
    };
244
681
    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
775
        std::optional<int64_t> timestamp = GetImportTimestamp(univalue_requests[i]);
250
775
        try {
251
775
            requests.push_back({i, ProcessUniValueDescriptor(univalue_requests[i], timestamp)});
252
775
        } 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
775
    }
258
259
    // Hand off the successfully parsed requests to the batch importer, which
260
    // handles wallet locking, rescanning and rescan-failure error composition.
261
681
    std::vector<ImportDescriptorRequest> descriptor_requests;
262
678
    descriptor_requests.reserve(requests.size());
263
764
    for (auto& parsed : requests) {
264
764
        descriptor_requests.push_back(std::move(parsed.request));
265
764
    }
266
267
678
    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
678
    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
674
    CHECK_NONFATAL(import_results.size() == requests.size());
282
1.43k
    for (size_t k = 0; k < requests.size(); ++k) {
283
761
        UniValue& result = results[requests[k].input_index];
284
761
        const ImportResult& import_result = import_results[k];
285
761
        result = UniValue(UniValue::VOBJ);
286
761
        UniValue warnings(UniValue::VARR);
287
761
        if (import_result.has_error()) {
288
33
            const WalletError& error = import_result.error.value().wallet_error;
289
33
            auto write_error = [&result, &error](int code) {
290
33
                result.pushKV("success", false);
291
33
                result.pushKV("error", JSONRPCError(code, error.message.original));
292
33
            };
293
33
            if (error.code == WalletErrorCode::UnlockNeeded) NONFATAL_UNREACHABLE();
294
33
            write_error(HandleWalletErrorCode(error.code));
295
728
        } else {
296
728
            result.pushKV("success", true);
297
728
        }
298
761
        for (const auto& w : import_result.warnings) {
299
599
            warnings.push_back(w);
300
599
        }
301
761
        PushWarnings(warnings, result);
302
761
    }
303
304
674
    UniValue response(UniValue::VARR);
305
769
    for (UniValue& result : results) {
306
769
        response.push_back(std::move(result));
307
769
    }
308
674
    return response;
309
674
},
310
1.55k
    };
311
1.55k
}
312
313
RPCMethod listdescriptors()
314
1.13k
{
315
1.13k
    return RPCMethod{
316
1.13k
        "listdescriptors",
317
1.13k
        "List all descriptors present in a wallet.\n",
318
1.13k
        {
319
1.13k
            {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private descriptors."}
320
1.13k
        },
321
1.13k
        RPCResult{RPCResult::Type::OBJ, "", "", {
322
1.13k
            {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
323
1.13k
            {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)",
324
1.13k
            {
325
1.13k
                {RPCResult::Type::OBJ, "", "", {
326
1.13k
                    {RPCResult::Type::STR, "desc", "Descriptor string representation"},
327
1.13k
                    {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},
328
1.13k
                    {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
329
1.13k
                    {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.13k
                    {RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", {
331
1.13k
                        {RPCResult::Type::NUM, "", "Range start inclusive"},
332
1.13k
                        {RPCResult::Type::NUM, "", "Range end inclusive"},
333
1.13k
                    }},
334
1.13k
                    {RPCResult::Type::NUM, "next", /*optional=*/true, "Same as next_index field. Kept for compatibility reason."},
335
1.13k
                    {RPCResult::Type::NUM, "next_index", /*optional=*/true, "The next index to generate addresses from; defined only for ranged descriptors"},
336
1.13k
                }},
337
1.13k
            }}
338
1.13k
        }},
339
1.13k
        RPCExamples{
340
1.13k
            HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "")
341
1.13k
            + HelpExampleCli("listdescriptors", "true") + HelpExampleRpc("listdescriptors", "true")
342
1.13k
        },
343
1.13k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
344
1.13k
{
345
266
    const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
346
266
    if (!wallet) return UniValue::VNULL;
347
348
266
    const bool priv = !request.params[0].isNull() && request.params[0].get_bool();
349
266
    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
265
    if (priv) {
353
88
        EnsureWalletIsUnlocked(*wallet);
354
88
    }
355
356
265
    LOCK(wallet->cs_wallet);
357
265
    util::Expected<std::vector<WalletDescInfo>, std::string> exported = ExportDescriptors(*wallet, priv);
358
265
    if (!exported) {
359
0
        throw JSONRPCError(RPC_WALLET_ERROR, exported.error());
360
0
    }
361
265
    std::vector<WalletDescInfo> wallet_descriptors = *exported;
362
363
5.78k
    std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) {
364
5.78k
        return a.descriptor < b.descriptor;
365
5.78k
    });
366
367
265
    UniValue descriptors(UniValue::VARR);
368
2.07k
    for (const WalletDescInfo& info : wallet_descriptors) {
369
2.07k
        UniValue spk(UniValue::VOBJ);
370
2.07k
        spk.pushKV("desc", info.descriptor);
371
2.07k
        spk.pushKV("timestamp", info.creation_time);
372
2.07k
        spk.pushKV("active", info.active);
373
2.07k
        if (info.internal.has_value()) {
374
1.89k
            spk.pushKV("internal", info.internal.value());
375
1.89k
        }
376
2.07k
        if (info.range.has_value()) {
377
1.94k
            UniValue range(UniValue::VARR);
378
1.94k
            range.push_back(info.range->first);
379
1.94k
            range.push_back(info.range->second - 1);
380
1.94k
            spk.pushKV("range", std::move(range));
381
1.94k
            spk.pushKV("next", info.next_index);
382
1.94k
            spk.pushKV("next_index", info.next_index);
383
1.94k
        }
384
2.07k
        descriptors.push_back(std::move(spk));
385
2.07k
    }
386
387
265
    UniValue response(UniValue::VOBJ);
388
265
    response.pushKV("wallet_name", wallet->GetName());
389
265
    response.pushKV("descriptors", std::move(descriptors));
390
391
265
    return response;
392
265
},
393
1.13k
    };
394
1.13k
}
395
396
RPCMethod backupwallet()
397
942
{
398
942
    return RPCMethod{
399
942
        "backupwallet",
400
942
        "Safely copies the current wallet file to the specified destination, which can either be a directory or a path with a filename.\n",
401
942
                {
402
942
                    {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"},
403
942
                },
404
942
                RPCResult{RPCResult::Type::NONE, "", ""},
405
942
                RPCExamples{
406
942
                    HelpExampleCli("backupwallet", "\"backup.dat\"")
407
942
            + HelpExampleRpc("backupwallet", "\"backup.dat\"")
408
942
                },
409
942
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
410
942
{
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
942
    };
428
942
}
429
430
431
RPCMethod restorewallet()
432
920
{
433
920
    return RPCMethod{
434
920
        "restorewallet",
435
920
        "Restores and loads a wallet from backup.\n"
436
920
        "\nThe rescan is significantly faster if block filters are available"
437
920
        "\n(using startup option \"-blockfilterindex=1\").\n",
438
920
        {
439
920
            {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
440
920
            {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
441
920
            {"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
920
        },
443
920
        RPCResult{
444
920
            RPCResult::Type::OBJ, "", "",
445
920
            {
446
920
                {RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
447
920
                {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to restoring and loading the wallet.",
448
920
                {
449
920
                    {RPCResult::Type::STR, "", ""},
450
920
                }},
451
920
            }
452
920
        },
453
920
        RPCExamples{
454
920
            HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
455
920
            + HelpExampleRpc("restorewallet", R"("testwallet", "home\\backups\\backup-file.bak")")
456
920
            + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak"}, {"load_on_startup", true}})
457
920
            + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak"}, {"load_on_startup", true}})
458
920
        },
459
920
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
460
920
{
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
920
    };
486
920
}
487
} // namespace wallet