Coverage Report

Created: 2026-09-21 19:49

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
852
{
44
852
    return RPCMethod{
45
852
        "importprunedfunds",
46
852
        "Imports funds without rescan. Corresponding address or script must previously be included in wallet. Aimed towards pruned wallets. The end-user is responsible to import additional transactions that subsequently spend the imported outputs or rescan after the point in the blockchain the transaction is included.\n",
47
852
                {
48
852
                    {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"},
49
852
                    {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"},
50
852
                },
51
852
                RPCResult{RPCResult::Type::NONE, "", ""},
52
852
                RPCExamples{""},
53
852
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
54
852
{
55
7
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
56
7
    if (!pwallet) return UniValue::VNULL;
57
58
7
    CMutableTransaction tx;
59
7
    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
6
    CMerkleBlock merkleBlock;
64
6
    SpanReader{ParseHexV(request.params[1], "proof")} >> merkleBlock;
65
66
    //Search partial merkle tree in proof for our transaction and index in valid block
67
6
    std::vector<Txid> vMatch;
68
6
    std::vector<unsigned int> vIndex;
69
6
    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
5
    LOCK(pwallet->cs_wallet);
74
5
    int height;
75
5
    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
4
    std::vector<Txid>::const_iterator it;
80
4
    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
3
    unsigned int txnIndex = vIndex[it - vMatch.begin()];
85
86
3
    CTransactionRef tx_ref = MakeTransactionRef(tx);
87
3
    if (pwallet->IsMine(*tx_ref)) {
88
2
        pwallet->AddToWallet(std::move(tx_ref), TxStateConfirmed{merkleBlock.header.GetHash(), height, static_cast<int>(txnIndex)});
89
2
        return UniValue::VNULL;
90
2
    }
91
92
1
    throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction");
93
3
},
94
852
    };
95
852
}
96
97
RPCMethod removeprunedfunds()
98
850
{
99
850
    return RPCMethod{
100
850
        "removeprunedfunds",
101
850
        "(DEPRECATED) This feature will be removed in the next major release. Start bitcoind with the `-deprecatedrpc=removeprunedfunds` option in order to use this.\n"
102
850
        "Deletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will affect wallet balances.\n",
103
850
                {
104
850
                    {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"},
105
850
                },
106
850
                RPCResult{RPCResult::Type::NONE, "", ""},
107
850
                RPCExamples{
108
850
                    HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") +
109
850
            "\nAs a JSON-RPC call\n"
110
850
            + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"")
111
850
                },
112
850
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
113
850
{
114
5
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
115
5
    if (!pwallet) return UniValue::VNULL;
116
117
5
    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
4
    LOCK(pwallet->cs_wallet);
122
123
4
    Txid hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))};
124
4
    std::vector<Txid> vHash;
125
4
    vHash.push_back(hash);
126
4
    if (auto res = pwallet->RemoveTxs(vHash); !res) {
127
1
        throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
128
1
    }
129
130
3
    return UniValue::VNULL;
131
4
},
132
850
    };
133
850
}
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
774
{
143
774
    if (data.exists("timestamp")) {
144
773
        const UniValue& timestamp = data["timestamp"];
145
773
        if (timestamp.isNum()) {
146
219
            const int64_t value{timestamp.getInt<int64_t>()};
147
219
            if (value < 0) {
148
1
                throw JSONRPCError(RPC_INVALID_PARAMETER, "Timestamp must not be negative");
149
1
            }
150
218
            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
773
    }
156
1
    throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key");
157
774
}
158
159
static ImportDescriptorRequest ProcessUniValueDescriptor(const UniValue& data, std::optional<int64_t> timestamp)
160
771
{
161
771
    ImportDescriptorRequest request;
162
771
    if (!data.exists("desc")) {
163
2
        throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found.");
164
2
    }
165
769
    request.descriptor = data["desc"].get_str();
166
769
    request.label = LabelFromValue(data["label"]);
167
769
    request.timestamp = timestamp;
168
769
    if (data.exists("active")) request.active = data["active"].get_bool();
169
769
    if (data.exists("internal")) request.internal = data["internal"].get_bool();
170
769
    if (data.exists("range")) request.range = ParseDescriptorRange(data["range"]);
171
769
    if (data.exists("next_index")) request.next_index = data["next_index"].getInt<int64_t>();
172
769
    return request;
173
771
}
174
175
RPCMethod importdescriptors()
176
1.52k
{
177
1.52k
    return RPCMethod{
178
1.52k
        "importdescriptors",
179
1.52k
        "Import descriptors. This will trigger a rescan of the blockchain based on the earliest timestamp of all descriptors being imported. Requires a new wallet backup.\n"
180
1.52k
        "When importing descriptors with multipath key expressions, if the multipath specifier contains exactly two elements, the descriptor produced from the second element will be imported as an internal descriptor.\n"
181
1.52k
            "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
182
1.52k
            "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
183
1.52k
            "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n",
184
1.52k
                {
185
1.52k
                    {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
186
1.52k
                        {
187
1.52k
                            {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
188
1.52k
                                {
189
1.52k
                                    {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."},
190
1.52k
                                    {"active", RPCArg::Type::BOOL, RPCArg::Default{false}, "Set this descriptor to be the active descriptor for the corresponding output type/externality"},
191
1.52k
                                    {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in the form [begin,end]) to import"},
192
1.52k
                                    {"next_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "If a ranged descriptor is set to active, this specifies the next index to generate addresses from"},
193
1.52k
                                    {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n"
194
1.52k
                                        "Use the string \"now\" to substitute the current synced blockchain time.\n"
195
1.52k
                                        "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
196
1.52k
                                        "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
197
1.52k
                                        "of all descriptors being imported will be scanned as well as the mempool.",
198
1.52k
                                        RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}}
199
1.52k
                                    },
200
1.52k
                                    {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
201
1.52k
                                    {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"},
202
1.52k
                                },
203
1.52k
                            },
204
1.52k
                        },
205
1.52k
                        RPCArgOptions{.oneline_description="requests"}},
206
1.52k
                },
207
1.52k
                RPCResult{
208
1.52k
                    RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
209
1.52k
                    {
210
1.52k
                        {RPCResult::Type::OBJ, "", "",
211
1.52k
                        {
212
1.52k
                            {RPCResult::Type::BOOL, "success", ""},
213
1.52k
                            {RPCResult::Type::ARR, "warnings", /*optional=*/true, "",
214
1.52k
                            {
215
1.52k
                                {RPCResult::Type::STR, "", ""},
216
1.52k
                            }},
217
1.52k
                            {RPCResult::Type::OBJ, "error", /*optional=*/true, "",
218
1.52k
                            {
219
1.52k
                                {RPCResult::Type::NUM, "code", "JSONRPC error code"},
220
1.52k
                                {RPCResult::Type::STR, "message", "JSONRPC error message"},
221
1.52k
                            }},
222
1.52k
                        }},
223
1.52k
                    }
224
1.52k
                },
225
1.52k
                RPCExamples{
226
1.52k
                    HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, "
227
1.52k
                                          "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
228
1.52k
                    HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'")
229
1.52k
                },
230
1.52k
        [](const RPCMethod& self, const JSONRPCRequest& main_request) -> UniValue
231
1.52k
{
232
680
    std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);
233
680
    if (!pwallet) return UniValue::VNULL;
234
680
    CWallet& wallet{*pwallet};
235
236
680
    const UniValue& univalue_requests = main_request.params[0];
237
    // One result per input, in input order.
238
680
    std::vector<UniValue> results(univalue_requests.size());
239
    // Successfully parsed requests, each with the index of the input it came from.
240
680
    struct ParsedRequest {
241
680
        size_t input_index;
242
680
        ImportDescriptorRequest request;
243
680
    };
244
680
    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
774
        std::optional<int64_t> timestamp = GetImportTimestamp(univalue_requests[i]);
250
774
        try {
251
774
            requests.push_back({i, ProcessUniValueDescriptor(univalue_requests[i], timestamp)});
252
774
        } 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
774
    }
258
259
    // Hand off the successfully parsed requests to the batch importer, which
260
    // handles wallet locking, rescanning and rescan-failure error composition.
261
680
    std::vector<ImportDescriptorRequest> descriptor_requests;
262
677
    descriptor_requests.reserve(requests.size());
263
763
    for (auto& parsed : requests) {
264
763
        descriptor_requests.push_back(std::move(parsed.request));
265
763
    }
266
267
677
    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
677
    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
673
    CHECK_NONFATAL(import_results.size() == requests.size());
282
1.43k
    for (size_t k = 0; k < requests.size(); ++k) {
283
760
        UniValue& result = results[requests[k].input_index];
284
760
        const ImportResult& import_result = import_results[k];
285
760
        result = UniValue(UniValue::VOBJ);
286
760
        UniValue warnings(UniValue::VARR);
287
760
        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
727
        } else {
296
727
            result.pushKV("success", true);
297
727
        }
298
760
        for (const auto& w : import_result.warnings) {
299
599
            warnings.push_back(w);
300
599
        }
301
760
        PushWarnings(warnings, result);
302
760
    }
303
304
673
    UniValue response(UniValue::VARR);
305
768
    for (UniValue& result : results) {
306
768
        response.push_back(std::move(result));
307
768
    }
308
673
    return response;
309
673
},
310
1.52k
    };
311
1.52k
}
312
313
RPCMethod listdescriptors()
314
1.07k
{
315
1.07k
    return RPCMethod{
316
1.07k
        "listdescriptors",
317
1.07k
        "List all descriptors present in a wallet.\n",
318
1.07k
        {
319
1.07k
            {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private descriptors."}
320
1.07k
        },
321
1.07k
        RPCResult{RPCResult::Type::OBJ, "", "", {
322
1.07k
            {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
323
1.07k
            {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)",
324
1.07k
            {
325
1.07k
                {RPCResult::Type::OBJ, "", "", {
326
1.07k
                    {RPCResult::Type::STR, "desc", "Descriptor string representation"},
327
1.07k
                    {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},
328
1.07k
                    {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
329
1.07k
                    {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.07k
                    {RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", {
331
1.07k
                        {RPCResult::Type::NUM, "", "Range start inclusive"},
332
1.07k
                        {RPCResult::Type::NUM, "", "Range end inclusive"},
333
1.07k
                    }},
334
1.07k
                    {RPCResult::Type::NUM, "next", /*optional=*/true, "Same as next_index field. Kept for compatibility reason."},
335
1.07k
                    {RPCResult::Type::NUM, "next_index", /*optional=*/true, "The next index to generate addresses from; defined only for ranged descriptors"},
336
1.07k
                }},
337
1.07k
            }}
338
1.07k
        }},
339
1.07k
        RPCExamples{
340
1.07k
            HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "")
341
1.07k
            + HelpExampleCli("listdescriptors", "true") + HelpExampleRpc("listdescriptors", "true")
342
1.07k
        },
343
1.07k
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
344
1.07k
{
345
226
    const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
346
226
    if (!wallet) return UniValue::VNULL;
347
348
226
    const bool priv = !request.params[0].isNull() && request.params[0].get_bool();
349
226
    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
225
    if (priv) {
353
88
        EnsureWalletIsUnlocked(*wallet);
354
88
    }
355
356
225
    LOCK(wallet->cs_wallet);
357
225
    util::Expected<std::vector<WalletDescInfo>, std::string> exported = ExportDescriptors(*wallet, priv);
358
225
    if (!exported) {
359
0
        throw JSONRPCError(RPC_WALLET_ERROR, exported.error());
360
0
    }
361
225
    std::vector<WalletDescInfo> wallet_descriptors = *exported;
362
363
4.88k
    std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) {
364
4.88k
        return a.descriptor < b.descriptor;
365
4.88k
    });
366
367
225
    UniValue descriptors(UniValue::VARR);
368
1.71k
    for (const WalletDescInfo& info : wallet_descriptors) {
369
1.71k
        UniValue spk(UniValue::VOBJ);
370
1.71k
        spk.pushKV("desc", info.descriptor);
371
1.71k
        spk.pushKV("timestamp", info.creation_time);
372
1.71k
        spk.pushKV("active", info.active);
373
1.71k
        if (info.internal.has_value()) {
374
1.57k
            spk.pushKV("internal", info.internal.value());
375
1.57k
        }
376
1.71k
        if (info.range.has_value()) {
377
1.62k
            UniValue range(UniValue::VARR);
378
1.62k
            range.push_back(info.range->first);
379
1.62k
            range.push_back(info.range->second - 1);
380
1.62k
            spk.pushKV("range", std::move(range));
381
1.62k
            spk.pushKV("next", info.next_index);
382
1.62k
            spk.pushKV("next_index", info.next_index);
383
1.62k
        }
384
1.71k
        descriptors.push_back(std::move(spk));
385
1.71k
    }
386
387
225
    UniValue response(UniValue::VOBJ);
388
225
    response.pushKV("wallet_name", wallet->GetName());
389
225
    response.pushKV("descriptors", std::move(descriptors));
390
391
225
    return response;
392
225
},
393
1.07k
    };
394
1.07k
}
395
396
RPCMethod backupwallet()
397
914
{
398
914
    return RPCMethod{
399
914
        "backupwallet",
400
914
        "Safely copies the current wallet file to the specified destination, which can either be a directory or a path with a filename.\n",
401
914
                {
402
914
                    {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"},
403
914
                },
404
914
                RPCResult{RPCResult::Type::NONE, "", ""},
405
914
                RPCExamples{
406
914
                    HelpExampleCli("backupwallet", "\"backup.dat\"")
407
914
            + HelpExampleRpc("backupwallet", "\"backup.dat\"")
408
914
                },
409
914
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
410
914
{
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
914
    };
428
914
}
429
430
431
RPCMethod restorewallet()
432
892
{
433
892
    return RPCMethod{
434
892
        "restorewallet",
435
892
        "Restores and loads a wallet from backup.\n"
436
892
        "\nThe rescan is significantly faster if block filters are available"
437
892
        "\n(using startup option \"-blockfilterindex=1\").\n",
438
892
        {
439
892
            {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
440
892
            {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
441
892
            {"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
892
        },
443
892
        RPCResult{
444
892
            RPCResult::Type::OBJ, "", "",
445
892
            {
446
892
                {RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
447
892
                {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to restoring and loading the wallet.",
448
892
                {
449
892
                    {RPCResult::Type::STR, "", ""},
450
892
                }},
451
892
            }
452
892
        },
453
892
        RPCExamples{
454
892
            HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
455
892
            + HelpExampleRpc("restorewallet", R"("testwallet", "home\\backups\\backup-file.bak")")
456
892
            + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak"}, {"load_on_startup", true}})
457
892
            + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak"}, {"load_on_startup", true}})
458
892
        },
459
892
        [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
460
892
{
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
892
    };
486
892
}
487
} // namespace wallet