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