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 : 878 : RPCMethod importprunedfunds()
43 : : {
44 : 878 : return RPCMethod{
45 : 878 : "importprunedfunds",
46 [ + - ]: 1756 : "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 [ + - + - ]: 1756 : {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"},
49 [ + - + - ]: 1756 : {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"},
50 : : },
51 [ + - + - : 1756 : RPCResult{RPCResult::Type::NONE, "", ""},
+ - + - ]
52 [ + - + - ]: 2634 : RPCExamples{""},
53 : 878 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
54 : : {
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 [ + - + - ]: 2 : throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
61 : : }
62 : :
63 [ + - ]: 6 : CMerkleBlock merkleBlock;
64 [ + - + - : 18 : 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 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
71 : : }
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 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
77 : : }
78 : :
79 : 4 : std::vector<Txid>::const_iterator it;
80 [ + - + + ]: 4 : 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 [ + - ]: 3 : unsigned int txnIndex = vIndex[it - vMatch.begin()];
85 : :
86 [ + - ]: 3 : CTransactionRef tx_ref = MakeTransactionRef(tx);
87 [ + - + + ]: 3 : if (pwallet->IsMine(*tx_ref)) {
88 [ + - + - ]: 4 : pwallet->AddToWallet(std::move(tx_ref), TxStateConfirmed{merkleBlock.header.GetHash(), height, static_cast<int>(txnIndex)});
89 [ - + ]: 2 : return UniValue::VNULL;
90 : : }
91 : :
92 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction");
93 [ + - ]: 25 : },
94 [ + - + - : 5268 : };
+ + - - ]
95 [ + - + - : 3512 : }
- - ]
96 : :
97 : 876 : RPCMethod removeprunedfunds()
98 : : {
99 : 876 : return RPCMethod{
100 : 876 : "removeprunedfunds",
101 [ + - ]: 1752 : "(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 : : "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 : : {
104 [ + - + - ]: 1752 : {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"},
105 : : },
106 [ + - + - : 1752 : RPCResult{RPCResult::Type::NONE, "", ""},
+ - + - ]
107 : 876 : RPCExamples{
108 [ + - + - : 1752 : HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") +
+ - ]
109 : 876 : "\nAs a JSON-RPC call\n"
110 [ + - + - : 3504 : + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"")
+ - + - ]
111 [ + - ]: 876 : },
112 : 876 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
113 : : {
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 [ + - + - ]: 2 : 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 : : }
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 [ + - + - ]: 2 : throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
128 : 1 : }
129 : :
130 : 3 : return UniValue::VNULL;
131 [ + - ]: 11 : },
132 [ + - + - : 4380 : };
+ + - - ]
133 [ + - ]: 1752 : }
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 : 780 : static std::optional<int64_t> GetImportTimestamp(const UniValue& data)
142 : : {
143 [ + + ]: 1560 : if (data.exists("timestamp")) {
144 [ + - ]: 779 : const UniValue& timestamp = data["timestamp"];
145 [ + + ]: 779 : if (timestamp.isNum()) {
146 : 225 : const int64_t value{timestamp.getInt<int64_t>()};
147 [ + + ]: 225 : if (value < 0) {
148 [ + - + - ]: 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Timestamp must not be negative");
149 : : }
150 : 224 : 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 : : }
154 [ + - + - : 2 : throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type())));
+ - ]
155 : : }
156 [ + - + - ]: 2 : throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key");
157 : : }
158 : :
159 : 777 : static ImportDescriptorRequest ProcessUniValueDescriptor(const UniValue& data, std::optional<int64_t> timestamp)
160 : : {
161 [ + - ]: 777 : ImportDescriptorRequest request;
162 [ + - + + ]: 1554 : if (!data.exists("desc")) {
163 [ + - + - ]: 4 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found.");
164 : : }
165 [ + - + - : 1550 : request.descriptor = data["desc"].get_str();
+ - + - ]
166 [ + - + - : 776 : request.label = LabelFromValue(data["label"]);
+ + ]
167 : 774 : request.timestamp = timestamp;
168 [ + - + + : 1548 : if (data.exists("active")) request.active = data["active"].get_bool();
+ - + - +
- ]
169 [ + - + + : 1548 : if (data.exists("internal")) request.internal = data["internal"].get_bool();
+ - + - +
- ]
170 [ + - + + : 1676 : if (data.exists("range")) request.range = ParseDescriptorRange(data["range"]);
+ - + - +
+ - + ]
171 [ + - + + : 1538 : if (data.exists("next_index")) request.next_index = data["next_index"].getInt<int64_t>();
+ - + - +
- ]
172 : 769 : return request;
173 : 8 : }
174 : :
175 : 1557 : RPCMethod importdescriptors()
176 : : {
177 : 1557 : return RPCMethod{
178 : 1557 : "importdescriptors",
179 [ + - ]: 3114 : "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 : : "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 : : "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
182 : : "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
183 : : "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n",
184 : : {
185 [ + - + - ]: 3114 : {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
186 : : {
187 [ + - + - ]: 3114 : {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
188 : : {
189 [ + - + - ]: 3114 : {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."},
190 [ + - + - : 4671 : {"active", RPCArg::Type::BOOL, RPCArg::Default{false}, "Set this descriptor to be the active descriptor for the corresponding output type/externality"},
+ - ]
191 [ + - + - ]: 3114 : {"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 [ + - + - ]: 3114 : {"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 [ + - + - ]: 3114 : {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n"
194 : : "Use the string \"now\" to substitute the current synced blockchain time.\n"
195 : : "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
196 : : "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
197 : 1557 : "of all descriptors being imported will be scanned as well as the mempool.",
198 [ + - ]: 3114 : RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}}
199 : : },
200 [ + - + - : 4671 : {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
+ - ]
201 [ + - + - : 4671 : {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"},
+ - ]
202 : : },
203 : : },
204 : : },
205 [ + - ]: 1557 : RPCArgOptions{.oneline_description="requests"}},
206 : : },
207 [ + - ]: 3114 : RPCResult{
208 [ + - + - ]: 3114 : RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
209 : : {
210 [ + - + - ]: 3114 : {RPCResult::Type::OBJ, "", "",
211 : : {
212 [ + - + - ]: 3114 : {RPCResult::Type::BOOL, "success", ""},
213 [ + - + - ]: 3114 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "",
214 : : {
215 [ + - + - ]: 3114 : {RPCResult::Type::STR, "", ""},
216 : : }},
217 [ + - + - ]: 3114 : {RPCResult::Type::OBJ, "error", /*optional=*/true, "",
218 : : {
219 [ + - + - ]: 3114 : {RPCResult::Type::NUM, "code", "JSONRPC error code"},
220 [ + - + - ]: 3114 : {RPCResult::Type::STR, "message", "JSONRPC error message"},
221 : : }},
222 : : }},
223 : : }
224 [ + - + - : 29583 : },
+ - + - +
- + + + +
+ + + + -
- - - - -
- - ]
225 : 1557 : RPCExamples{
226 [ + - + - : 3114 : HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, "
+ - ]
227 : 1557 : "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
228 [ + - + - : 4671 : HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'")
+ - + - ]
229 [ + - ]: 1557 : },
230 : 1557 : [](const RPCMethod& self, const JSONRPCRequest& main_request) -> UniValue
231 : : {
232 : 686 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);
233 [ - + ]: 686 : if (!pwallet) return UniValue::VNULL;
234 [ + - ]: 686 : CWallet& wallet{*pwallet};
235 : :
236 [ + - ]: 686 : const UniValue& univalue_requests = main_request.params[0];
237 : : // One result per input, in input order.
238 [ - + + - ]: 686 : std::vector<UniValue> results(univalue_requests.size());
239 : : // Successfully parsed requests, each with the index of the input it came from.
240 : 2433 : struct ParsedRequest {
241 : : size_t input_index;
242 : : ImportDescriptorRequest request;
243 : : };
244 : 686 : std::vector<ParsedRequest> requests;
245 : :
246 : : // Malformed inputs (e.g. invalid label) are returned as per-item failures.
247 [ - + + + ]: 1463 : for (size_t i = 0; i < univalue_requests.size(); ++i) {
248 : : // Throws a top-level RPC error if "timestamp" is missing or invalid
249 [ + - + + ]: 780 : std::optional<int64_t> timestamp = GetImportTimestamp(univalue_requests[i]);
250 : 777 : try {
251 : 1538 : requests.push_back({i, ProcessUniValueDescriptor(univalue_requests[i], timestamp)});
252 [ - + ]: 8 : } catch (const UniValue& e) {
253 : 8 : results[i] = UniValue(UniValue::VOBJ);
254 [ + - + - : 16 : results[i].pushKV("success", UniValue(false));
+ - ]
255 [ + - + - : 16 : results[i].pushKV("error", e);
+ - ]
256 : 8 : }
257 : : }
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 [ - + + - ]: 683 : descriptor_requests.reserve(requests.size());
263 [ + + ]: 1452 : for (auto& parsed : requests) {
264 [ + - ]: 769 : descriptor_requests.push_back(std::move(parsed.request));
265 : : }
266 : :
267 [ + - ]: 683 : 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 [ - + + + : 683 : 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 : : }
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 [ - + + - ]: 679 : CHECK_NONFATAL(import_results.size() == requests.size());
282 [ - + + + ]: 1445 : for (size_t k = 0; k < requests.size(); ++k) {
283 : 766 : UniValue& result = results[requests[k].input_index];
284 : 766 : const ImportResult& import_result = import_results[k];
285 : 766 : result = UniValue(UniValue::VOBJ);
286 : 766 : UniValue warnings(UniValue::VARR);
287 [ + + ]: 766 : if (import_result.has_error()) {
288 [ - + ]: 33 : const WalletError& error = import_result.error.value().wallet_error;
289 : 66 : auto write_error = [&result, &error](int code) {
290 [ + - + - ]: 66 : result.pushKV("success", false);
291 [ + - + - ]: 66 : 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 : : } else {
296 [ + - + - : 1466 : result.pushKV("success", true);
+ - ]
297 : : }
298 [ + + ]: 1365 : for (const auto& w : import_result.warnings) {
299 [ + - + - ]: 599 : warnings.push_back(w);
300 : : }
301 [ + - ]: 766 : PushWarnings(warnings, result);
302 : 766 : }
303 : :
304 : 679 : UniValue response(UniValue::VARR);
305 [ + + ]: 1453 : for (UniValue& result : results) {
306 [ + - ]: 774 : response.push_back(std::move(result));
307 : : }
308 : 679 : return response;
309 [ + - + + ]: 2157 : },
310 [ + - + - : 26469 : };
+ - + - +
+ + + + +
- - - - -
- ]
311 [ + - + - : 46710 : }
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - + - +
- + - - -
- - - - ]
312 : :
313 : 1097 : RPCMethod listdescriptors()
314 : : {
315 : 1097 : return RPCMethod{
316 : 1097 : "listdescriptors",
317 [ + - ]: 2194 : "List all descriptors present in a wallet.\n",
318 : : {
319 [ + - + - : 3291 : {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private descriptors."}
+ - ]
320 : : },
321 [ + - + - : 4388 : RPCResult{RPCResult::Type::OBJ, "", "", {
+ - ]
322 [ + - + - ]: 2194 : {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
323 [ + - + - ]: 2194 : {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)",
324 : : {
325 [ + - + - ]: 2194 : {RPCResult::Type::OBJ, "", "", {
326 [ + - + - ]: 2194 : {RPCResult::Type::STR, "desc", "Descriptor string representation"},
327 [ + - + - ]: 2194 : {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},
328 [ + - + - ]: 2194 : {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
329 [ + - + - ]: 2194 : {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 [ + - + - ]: 2194 : {RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", {
331 [ + - + - ]: 2194 : {RPCResult::Type::NUM, "", "Range start inclusive"},
332 [ + - + - ]: 2194 : {RPCResult::Type::NUM, "", "Range end inclusive"},
333 : : }},
334 [ + - + - ]: 2194 : {RPCResult::Type::NUM, "next", /*optional=*/true, "Same as next_index field. Kept for compatibility reason."},
335 [ + - + - ]: 2194 : {RPCResult::Type::NUM, "next_index", /*optional=*/true, "The next index to generate addresses from; defined only for ranged descriptors"},
336 : : }},
337 : : }}
338 [ + - + - : 31813 : }},
+ - + - +
- + + + +
+ + + + -
- - - - -
- - ]
339 : 1097 : RPCExamples{
340 [ + - + - : 2194 : HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "")
+ - + - +
- + - +
- ]
341 [ + - + - : 4388 : + HelpExampleCli("listdescriptors", "true") + HelpExampleRpc("listdescriptors", "true")
+ - + - +
- + - + -
+ - ]
342 [ + - ]: 1097 : },
343 : 1097 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
344 : : {
345 [ - + ]: 226 : const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
346 [ - + ]: 225 : if (!wallet) return UniValue::VNULL;
347 : :
348 [ + - + + : 225 : const bool priv = !request.params[0].isNull() && request.params[0].get_bool();
+ - + - +
+ ]
349 [ + - + + : 225 : if (wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && priv) {
+ + ]
350 [ + - + - ]: 2 : throw JSONRPCError(RPC_WALLET_ERROR, "Can't get private descriptor string for watch-only wallets");
351 : : }
352 [ + + ]: 224 : if (priv) {
353 [ + + ]: 88 : EnsureWalletIsUnlocked(*wallet);
354 : : }
355 : :
356 [ + - ]: 223 : LOCK(wallet->cs_wallet);
357 [ + - ]: 223 : util::Expected<std::vector<WalletDescInfo>, std::string> exported = ExportDescriptors(*wallet, priv);
358 [ - + ]: 223 : if (!exported) {
359 [ # # ]: 0 : throw JSONRPCError(RPC_WALLET_ERROR, exported.error());
360 : : }
361 [ + - ]: 223 : std::vector<WalletDescInfo> wallet_descriptors = *exported;
362 : :
363 : 223 : std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) {
364 [ - - - - : 4848 : return a.descriptor < b.descriptor;
+ + + + +
+ + + + +
+ + - + -
+ - - +
+ ]
365 : : });
366 : :
367 : 223 : UniValue descriptors(UniValue::VARR);
368 [ + + ]: 1939 : for (const WalletDescInfo& info : wallet_descriptors) {
369 : 1716 : UniValue spk(UniValue::VOBJ);
370 [ + - + - : 3432 : spk.pushKV("desc", info.descriptor);
+ - ]
371 [ + - + - : 3432 : spk.pushKV("timestamp", info.creation_time);
+ - ]
372 [ + - + - : 3432 : spk.pushKV("active", info.active);
+ - ]
373 [ + + ]: 1716 : if (info.internal.has_value()) {
374 [ + - + - : 3144 : spk.pushKV("internal", info.internal.value());
+ - ]
375 : : }
376 [ + + ]: 1716 : if (info.range.has_value()) {
377 : 1621 : UniValue range(UniValue::VARR);
378 [ + - + - ]: 1621 : range.push_back(info.range->first);
379 [ + - + - ]: 1621 : range.push_back(info.range->second - 1);
380 [ + - + - ]: 3242 : spk.pushKV("range", std::move(range));
381 [ + - + - : 3242 : spk.pushKV("next", info.next_index);
+ - ]
382 [ + - + - : 3242 : spk.pushKV("next_index", info.next_index);
+ - ]
383 : 1621 : }
384 [ + - ]: 1716 : descriptors.push_back(std::move(spk));
385 : 1716 : }
386 : :
387 : 223 : UniValue response(UniValue::VOBJ);
388 [ + - + - : 446 : response.pushKV("wallet_name", wallet->GetName());
+ - ]
389 [ + - + - ]: 446 : response.pushKV("descriptors", std::move(descriptors));
390 : :
391 : 223 : return response;
392 [ + - ]: 669 : },
393 [ + - + - : 5485 : };
+ + - - ]
394 [ + - + - : 28522 : }
+ - + - +
- + - + -
+ - + - +
- + - + -
+ - - - -
- - - ]
395 : :
396 : 940 : RPCMethod backupwallet()
397 : : {
398 : 940 : return RPCMethod{
399 : 940 : "backupwallet",
400 [ + - ]: 1880 : "Safely copies the current wallet file to the specified destination, which can either be a directory or a path with a filename.\n",
401 : : {
402 [ + - + - ]: 1880 : {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"},
403 : : },
404 [ + - + - : 1880 : RPCResult{RPCResult::Type::NONE, "", ""},
+ - + - ]
405 : 940 : RPCExamples{
406 [ + - + - : 1880 : HelpExampleCli("backupwallet", "\"backup.dat\"")
+ - ]
407 [ + - + - : 3760 : + HelpExampleRpc("backupwallet", "\"backup.dat\"")
+ - + - ]
408 [ + - ]: 940 : },
409 : 940 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
410 : : {
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 [ + - + - ]: 8 : throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!");
423 : : }
424 : :
425 : 65 : return UniValue::VNULL;
426 [ + - ]: 203 : },
427 [ + - + - : 4700 : };
+ + - - ]
428 [ + - ]: 1880 : }
429 : :
430 : :
431 : 918 : RPCMethod restorewallet()
432 : : {
433 : 918 : return RPCMethod{
434 : 918 : "restorewallet",
435 [ + - ]: 1836 : "Restores and loads a wallet from backup.\n"
436 : : "\nThe rescan is significantly faster if block filters are available"
437 : : "\n(using startup option \"-blockfilterindex=1\").\n",
438 : : {
439 [ + - + - ]: 1836 : {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
440 [ + - + - ]: 1836 : {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
441 [ + - + - ]: 1836 : {"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 : : },
443 [ + - ]: 1836 : RPCResult{
444 [ + - + - ]: 1836 : RPCResult::Type::OBJ, "", "",
445 : : {
446 [ + - + - ]: 1836 : {RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
447 [ + - + - ]: 1836 : {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to restoring and loading the wallet.",
448 : : {
449 [ + - + - ]: 1836 : {RPCResult::Type::STR, "", ""},
450 : : }},
451 : : }
452 [ + - + - : 8262 : },
+ - + + +
+ - - -
- ]
453 : 918 : RPCExamples{
454 [ + - + - : 1836 : HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
+ - ]
455 [ + - + - : 3672 : + HelpExampleRpc("restorewallet", R"("testwallet", "home\\backups\\backup-file.bak")")
+ - + - ]
456 [ + - + - : 6426 : + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak"}, {"load_on_startup", true}})
+ - + - +
+ - - ]
457 [ + - + - : 6426 : + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak"}, {"load_on_startup", true}})
+ - + - +
+ - - ]
458 [ + - ]: 918 : },
459 : 918 : [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
460 : : {
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 : 29 : UniValue obj(UniValue::VOBJ);
479 [ + - + - : 58 : obj.pushKV("name", wallet->GetName());
+ - ]
480 [ + - ]: 29 : PushWarnings(warnings, obj);
481 : :
482 [ + - ]: 29 : return obj;
483 : :
484 : 141 : },
485 [ + - + - : 6426 : };
+ + - - ]
486 [ + - + - : 12852 : }
+ - + - +
- + - + -
+ - + - +
- + - + -
- - - - -
- - - ]
487 : : } // namespace wallet
|