backup.cpp
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/rpc/util.h> 25 #include <wallet/wallet.h> 26 27 #include <cstdint> 28 #include <fstream> 29 #include <tuple> 30 #include <string> 31 32 #include <univalue.h> 33 34 35 36 using interfaces::FoundBlock; 37 38 namespace wallet { 39 RPCHelpMan importprunedfunds() 40 { 41 return RPCHelpMan{ 42 "importprunedfunds", 43 "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", 44 { 45 {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"}, 46 {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"}, 47 }, 48 RPCResult{RPCResult::Type::NONE, "", ""}, 49 RPCExamples{""}, 50 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 51 { 52 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 53 if (!pwallet) return UniValue::VNULL; 54 55 CMutableTransaction tx; 56 if (!DecodeHexTx(tx, request.params[0].get_str())) { 57 throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input."); 58 } 59 60 CMerkleBlock merkleBlock; 61 SpanReader{ParseHexV(request.params[1], "proof")} >> merkleBlock; 62 63 //Search partial merkle tree in proof for our transaction and index in valid block 64 std::vector<Txid> vMatch; 65 std::vector<unsigned int> vIndex; 66 if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) { 67 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock"); 68 } 69 70 LOCK(pwallet->cs_wallet); 71 int height; 72 if (!pwallet->chain().findAncestorByHash(pwallet->GetLastBlockHash(), merkleBlock.header.GetHash(), FoundBlock().height(height))) { 73 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); 74 } 75 76 std::vector<Txid>::const_iterator it; 77 if ((it = std::find(vMatch.begin(), vMatch.end(), tx.GetHash())) == vMatch.end()) { 78 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof"); 79 } 80 81 unsigned int txnIndex = vIndex[it - vMatch.begin()]; 82 83 CTransactionRef tx_ref = MakeTransactionRef(tx); 84 if (pwallet->IsMine(*tx_ref)) { 85 pwallet->AddToWallet(std::move(tx_ref), TxStateConfirmed{merkleBlock.header.GetHash(), height, static_cast<int>(txnIndex)}); 86 return UniValue::VNULL; 87 } 88 89 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction"); 90 }, 91 }; 92 } 93 94 RPCHelpMan removeprunedfunds() 95 { 96 return RPCHelpMan{ 97 "removeprunedfunds", 98 "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", 99 { 100 {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"}, 101 }, 102 RPCResult{RPCResult::Type::NONE, "", ""}, 103 RPCExamples{ 104 HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") + 105 "\nAs a JSON-RPC call\n" 106 + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") 107 }, 108 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 109 { 110 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 111 if (!pwallet) return UniValue::VNULL; 112 113 LOCK(pwallet->cs_wallet); 114 115 Txid hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))}; 116 std::vector<Txid> vHash; 117 vHash.push_back(hash); 118 if (auto res = pwallet->RemoveTxs(vHash); !res) { 119 throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original); 120 } 121 122 return UniValue::VNULL; 123 }, 124 }; 125 } 126 127 static int64_t GetImportTimestamp(const UniValue& data, int64_t now) 128 { 129 if (data.exists("timestamp")) { 130 const UniValue& timestamp = data["timestamp"]; 131 if (timestamp.isNum()) { 132 return timestamp.getInt<int64_t>(); 133 } else if (timestamp.isStr() && timestamp.get_str() == "now") { 134 return now; 135 } 136 throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type()))); 137 } 138 throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key"); 139 } 140 141 static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) 142 { 143 UniValue warnings(UniValue::VARR); 144 UniValue result(UniValue::VOBJ); 145 146 try { 147 if (!data.exists("desc")) { 148 throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found."); 149 } 150 151 const std::string& descriptor = data["desc"].get_str(); 152 const bool active = data.exists("active") ? data["active"].get_bool() : false; 153 const std::string label{LabelFromValue(data["label"])}; 154 155 // Parse descriptor string 156 FlatSigningProvider keys; 157 std::string error; 158 auto parsed_descs = Parse(descriptor, keys, error, /* require_checksum = */ true); 159 if (parsed_descs.empty()) { 160 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); 161 } 162 std::optional<bool> internal; 163 if (data.exists("internal")) { 164 if (parsed_descs.size() > 1) { 165 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot have multipath descriptor while also specifying \'internal\'"); 166 } 167 internal = data["internal"].get_bool(); 168 } 169 170 // Range check 171 std::optional<bool> is_ranged; 172 int64_t range_start = 0, range_end = 1, next_index = 0; 173 if (!parsed_descs.at(0)->IsRange() && data.exists("range")) { 174 throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); 175 } else if (parsed_descs.at(0)->IsRange()) { 176 if (data.exists("range")) { 177 auto range = ParseDescriptorRange(data["range"]); 178 range_start = range.first; 179 range_end = range.second + 1; // Specified range end is inclusive, but we need range end as exclusive 180 } else { 181 warnings.push_back("Range not given, using default keypool range"); 182 range_start = 0; 183 range_end = wallet.m_keypool_size; 184 } 185 next_index = range_start; 186 is_ranged = true; 187 188 if (data.exists("next_index")) { 189 next_index = data["next_index"].getInt<int64_t>(); 190 // bound checks 191 if (next_index < range_start || next_index >= range_end) { 192 throw JSONRPCError(RPC_INVALID_PARAMETER, "next_index is out of range"); 193 } 194 } 195 } 196 197 // Active descriptors must be ranged 198 if (active && !parsed_descs.at(0)->IsRange()) { 199 throw JSONRPCError(RPC_INVALID_PARAMETER, "Active descriptors must be ranged"); 200 } 201 202 // Multipath descriptors should not have a label 203 if (parsed_descs.size() > 1 && data.exists("label")) { 204 throw JSONRPCError(RPC_INVALID_PARAMETER, "Multipath descriptors should not have a label"); 205 } 206 207 // Ranged descriptors should not have a label 208 if (is_ranged.has_value() && is_ranged.value() && data.exists("label")) { 209 throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptors should not have a label"); 210 } 211 212 bool desc_internal = internal.has_value() && internal.value(); 213 // Internal addresses should not have a label either 214 if (desc_internal && data.exists("label")) { 215 throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label"); 216 } 217 218 // Combo descriptor check 219 if (active && !parsed_descs.at(0)->IsSingleType()) { 220 throw JSONRPCError(RPC_WALLET_ERROR, "Combo descriptors cannot be set to active"); 221 } 222 223 // If the wallet disabled private keys, abort if private keys exist 224 if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) { 225 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); 226 } 227 228 for (size_t j = 0; j < parsed_descs.size(); ++j) { 229 auto parsed_desc = std::move(parsed_descs[j]); 230 if (parsed_descs.size() == 2) { 231 desc_internal = j == 1; 232 } else if (parsed_descs.size() > 2) { 233 CHECK_NONFATAL(!desc_internal); 234 } 235 // Need to ExpandPrivate to check if private keys are available for all pubkeys 236 FlatSigningProvider expand_keys; 237 std::vector<CScript> scripts; 238 if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) { 239 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot expand descriptor. Probably because of hardened derivations without private keys provided"); 240 } 241 parsed_desc->ExpandPrivate(0, keys, expand_keys); 242 243 for (const auto& w : parsed_desc->Warnings()) { 244 warnings.push_back(w); 245 } 246 247 // Check if all private keys are provided 248 bool have_all_privkeys = !expand_keys.keys.empty(); 249 for (const auto& entry : expand_keys.origins) { 250 const CKeyID& key_id = entry.first; 251 CKey key; 252 if (!expand_keys.GetKey(key_id, key)) { 253 have_all_privkeys = false; 254 break; 255 } 256 } 257 258 // If private keys are enabled, check some things. 259 if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { 260 if (keys.keys.empty()) { 261 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled"); 262 } 263 if (!have_all_privkeys) { 264 warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors"); 265 } 266 } 267 268 WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index); 269 270 // Add descriptor to the wallet 271 auto spk_manager_res = wallet.AddWalletDescriptor(w_desc, keys, label, desc_internal); 272 273 if (!spk_manager_res) { 274 throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s': %s", descriptor, util::ErrorString(spk_manager_res).original)); 275 } 276 277 auto& spk_manager = spk_manager_res.value().get(); 278 279 // Set descriptor as active if necessary 280 if (active) { 281 if (!w_desc.descriptor->GetOutputType()) { 282 warnings.push_back("Unknown output type, cannot set descriptor to active."); 283 } else { 284 wallet.AddActiveScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal); 285 } 286 } else { 287 if (w_desc.descriptor->GetOutputType()) { 288 wallet.DeactivateScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal); 289 } 290 } 291 } 292 293 result.pushKV("success", UniValue(true)); 294 } catch (const UniValue& e) { 295 result.pushKV("success", UniValue(false)); 296 result.pushKV("error", e); 297 } 298 PushWarnings(warnings, result); 299 return result; 300 } 301 302 RPCHelpMan importdescriptors() 303 { 304 return RPCHelpMan{ 305 "importdescriptors", 306 "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" 307 "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" 308 "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n" 309 "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n" 310 "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n", 311 { 312 {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported", 313 { 314 {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", 315 { 316 {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."}, 317 {"active", RPCArg::Type::BOOL, RPCArg::Default{false}, "Set this descriptor to be the active descriptor for the corresponding output type/externality"}, 318 {"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"}, 319 {"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"}, 320 {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n" 321 "Use the string \"now\" to substitute the current synced blockchain time.\n" 322 "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n" 323 "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n" 324 "of all descriptors being imported will be scanned as well as the mempool.", 325 RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}} 326 }, 327 {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"}, 328 {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"}, 329 }, 330 }, 331 }, 332 RPCArgOptions{.oneline_description="requests"}}, 333 }, 334 RPCResult{ 335 RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result", 336 { 337 {RPCResult::Type::OBJ, "", "", 338 { 339 {RPCResult::Type::BOOL, "success", ""}, 340 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "", 341 { 342 {RPCResult::Type::STR, "", ""}, 343 }}, 344 {RPCResult::Type::OBJ, "error", /*optional=*/true, "", 345 { 346 {RPCResult::Type::ELISION, "", "JSONRPC error"}, 347 }}, 348 }}, 349 } 350 }, 351 RPCExamples{ 352 HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, " 353 "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + 354 HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'") 355 }, 356 [&](const RPCHelpMan& self, const JSONRPCRequest& main_request) -> UniValue 357 { 358 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request); 359 if (!pwallet) return UniValue::VNULL; 360 CWallet& wallet{*pwallet}; 361 362 // Make sure the results are valid at least up to the most recent block 363 // the user could have gotten from another RPC command prior to now 364 wallet.BlockUntilSyncedToCurrentChain(); 365 366 WalletRescanReserver reserver(*pwallet); 367 if (!reserver.reserve(/*with_passphrase=*/true)) { 368 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); 369 } 370 371 // Ensure that the wallet is not locked for the remainder of this RPC, as 372 // the passphrase is used to top up the keypool. 373 LOCK(pwallet->m_relock_mutex); 374 375 const UniValue& requests = main_request.params[0]; 376 const int64_t minimum_timestamp = 1; 377 int64_t now = 0; 378 int64_t lowest_timestamp = 0; 379 bool rescan = false; 380 UniValue response(UniValue::VARR); 381 { 382 LOCK(pwallet->cs_wallet); 383 EnsureWalletIsUnlocked(*pwallet); 384 385 CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now))); 386 387 // Get all timestamps and extract the lowest timestamp 388 for (const UniValue& request : requests.getValues()) { 389 // This throws an error if "timestamp" doesn't exist 390 const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp); 391 const UniValue result = ProcessDescriptorImport(*pwallet, request, timestamp); 392 response.push_back(result); 393 394 if (lowest_timestamp > timestamp ) { 395 lowest_timestamp = timestamp; 396 } 397 398 // If we know the chain tip, and at least one request was successful then allow rescan 399 if (!rescan && result["success"].get_bool()) { 400 rescan = true; 401 } 402 } 403 pwallet->ConnectScriptPubKeyManNotifiers(); 404 pwallet->RefreshAllTXOs(); 405 } 406 407 // Rescan the blockchain using the lowest timestamp 408 if (rescan) { 409 int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver, /*update=*/true); 410 pwallet->ResubmitWalletTransactions(node::TxBroadcast::MEMPOOL_NO_BROADCAST, /*force=*/true); 411 412 if (pwallet->IsAbortingRescan()) { 413 throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); 414 } 415 416 if (scanned_time > lowest_timestamp) { 417 std::vector<UniValue> results = response.getValues(); 418 response.clear(); 419 response.setArray(); 420 421 // Compose the response 422 for (unsigned int i = 0; i < requests.size(); ++i) { 423 const UniValue& request = requests.getValues().at(i); 424 425 // If the descriptor timestamp is within the successfully scanned 426 // range, or if the import result already has an error set, let 427 // the result stand unmodified. Otherwise replace the result 428 // with an error message. 429 if (scanned_time <= GetImportTimestamp(request, now) || results.at(i).exists("error")) { 430 response.push_back(results.at(i)); 431 } else { 432 std::string error_msg{strprintf("Rescan failed for descriptor with timestamp %d. There " 433 "was an error reading a block from time %d, which is after or within %d seconds " 434 "of key creation, and could contain transactions pertaining to the desc. As a " 435 "result, transactions and coins using this desc may not appear in the wallet.", 436 GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)}; 437 if (pwallet->chain().havePruned()) { 438 error_msg += strprintf(" This error could be caused by pruning or data corruption " 439 "(see bitcoind log for details) and could be dealt with by downloading and " 440 "rescanning the relevant blocks (see -reindex option and rescanblockchain RPC)."); 441 } else if (pwallet->chain().hasAssumedValidChain()) { 442 error_msg += strprintf(" This error is likely caused by an in-progress assumeutxo " 443 "background sync. Check logs or getchainstates RPC for assumeutxo background " 444 "sync progress and try again later."); 445 } else { 446 error_msg += strprintf(" This error could potentially caused by data corruption. If " 447 "the issue persists you may want to reindex (see -reindex option)."); 448 } 449 450 UniValue result = UniValue(UniValue::VOBJ); 451 result.pushKV("success", UniValue(false)); 452 result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, error_msg)); 453 response.push_back(std::move(result)); 454 } 455 } 456 } 457 } 458 459 return response; 460 }, 461 }; 462 } 463 464 RPCHelpMan listdescriptors() 465 { 466 return RPCHelpMan{ 467 "listdescriptors", 468 "List all descriptors present in a wallet.\n", 469 { 470 {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private descriptors."} 471 }, 472 RPCResult{RPCResult::Type::OBJ, "", "", { 473 {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"}, 474 {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)", 475 { 476 {RPCResult::Type::OBJ, "", "", { 477 {RPCResult::Type::STR, "desc", "Descriptor string representation"}, 478 {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"}, 479 {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"}, 480 {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"}, 481 {RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", { 482 {RPCResult::Type::NUM, "", "Range start inclusive"}, 483 {RPCResult::Type::NUM, "", "Range end inclusive"}, 484 }}, 485 {RPCResult::Type::NUM, "next", /*optional=*/true, "Same as next_index field. Kept for compatibility reason."}, 486 {RPCResult::Type::NUM, "next_index", /*optional=*/true, "The next index to generate addresses from; defined only for ranged descriptors"}, 487 }}, 488 }} 489 }}, 490 RPCExamples{ 491 HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "") 492 + HelpExampleCli("listdescriptors", "true") + HelpExampleRpc("listdescriptors", "true") 493 }, 494 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 495 { 496 const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request); 497 if (!wallet) return UniValue::VNULL; 498 499 const bool priv = !request.params[0].isNull() && request.params[0].get_bool(); 500 if (wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && priv) { 501 throw JSONRPCError(RPC_WALLET_ERROR, "Can't get private descriptor string for watch-only wallets"); 502 } 503 if (priv) { 504 EnsureWalletIsUnlocked(*wallet); 505 } 506 507 LOCK(wallet->cs_wallet); 508 509 const auto active_spk_mans = wallet->GetActiveScriptPubKeyMans(); 510 511 struct WalletDescInfo { 512 std::string descriptor; 513 uint64_t creation_time; 514 bool active; 515 std::optional<bool> internal; 516 std::optional<std::pair<int64_t,int64_t>> range; 517 int64_t next_index; 518 }; 519 520 std::vector<WalletDescInfo> wallet_descriptors; 521 for (const auto& spk_man : wallet->GetAllScriptPubKeyMans()) { 522 const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man); 523 if (!desc_spk_man) { 524 throw JSONRPCError(RPC_WALLET_ERROR, "Unexpected ScriptPubKey manager type."); 525 } 526 LOCK(desc_spk_man->cs_desc_man); 527 const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor(); 528 std::string descriptor; 529 CHECK_NONFATAL(desc_spk_man->GetDescriptorString(descriptor, priv)); 530 const bool is_range = wallet_descriptor.descriptor->IsRange(); 531 wallet_descriptors.push_back({ 532 descriptor, 533 wallet_descriptor.creation_time, 534 active_spk_mans.contains(desc_spk_man), 535 wallet->IsInternalScriptPubKeyMan(desc_spk_man), 536 is_range ? std::optional(std::make_pair(wallet_descriptor.range_start, wallet_descriptor.range_end)) : std::nullopt, 537 wallet_descriptor.next_index 538 }); 539 } 540 541 std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) { 542 return a.descriptor < b.descriptor; 543 }); 544 545 UniValue descriptors(UniValue::VARR); 546 for (const WalletDescInfo& info : wallet_descriptors) { 547 UniValue spk(UniValue::VOBJ); 548 spk.pushKV("desc", info.descriptor); 549 spk.pushKV("timestamp", info.creation_time); 550 spk.pushKV("active", info.active); 551 if (info.internal.has_value()) { 552 spk.pushKV("internal", info.internal.value()); 553 } 554 if (info.range.has_value()) { 555 UniValue range(UniValue::VARR); 556 range.push_back(info.range->first); 557 range.push_back(info.range->second - 1); 558 spk.pushKV("range", std::move(range)); 559 spk.pushKV("next", info.next_index); 560 spk.pushKV("next_index", info.next_index); 561 } 562 descriptors.push_back(std::move(spk)); 563 } 564 565 UniValue response(UniValue::VOBJ); 566 response.pushKV("wallet_name", wallet->GetName()); 567 response.pushKV("descriptors", std::move(descriptors)); 568 569 return response; 570 }, 571 }; 572 } 573 574 RPCHelpMan backupwallet() 575 { 576 return RPCHelpMan{ 577 "backupwallet", 578 "Safely copies the current wallet file to the specified destination, which can either be a directory or a path with a filename.\n", 579 { 580 {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"}, 581 }, 582 RPCResult{RPCResult::Type::NONE, "", ""}, 583 RPCExamples{ 584 HelpExampleCli("backupwallet", "\"backup.dat\"") 585 + HelpExampleRpc("backupwallet", "\"backup.dat\"") 586 }, 587 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 588 { 589 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); 590 if (!pwallet) return UniValue::VNULL; 591 592 // Make sure the results are valid at least up to the most recent block 593 // the user could have gotten from another RPC command prior to now 594 pwallet->BlockUntilSyncedToCurrentChain(); 595 596 LOCK(pwallet->cs_wallet); 597 598 std::string strDest = request.params[0].get_str(); 599 if (!pwallet->BackupWallet(strDest)) { 600 throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!"); 601 } 602 603 return UniValue::VNULL; 604 }, 605 }; 606 } 607 608 609 RPCHelpMan restorewallet() 610 { 611 return RPCHelpMan{ 612 "restorewallet", 613 "Restores and loads a wallet from backup.\n" 614 "\nThe rescan is significantly faster if block filters are available" 615 "\n(using startup option \"-blockfilterindex=1\").\n", 616 { 617 {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"}, 618 {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."}, 619 {"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."}, 620 }, 621 RPCResult{ 622 RPCResult::Type::OBJ, "", "", 623 { 624 {RPCResult::Type::STR, "name", "The wallet name if restored successfully."}, 625 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to restoring and loading the wallet.", 626 { 627 {RPCResult::Type::STR, "", ""}, 628 }}, 629 } 630 }, 631 RPCExamples{ 632 HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"") 633 + HelpExampleRpc("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"") 634 + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}}) 635 + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}}) 636 }, 637 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 638 { 639 640 WalletContext& context = EnsureWalletContext(request.context); 641 642 auto backup_file = fs::u8path(request.params[1].get_str()); 643 644 std::string wallet_name = request.params[0].get_str(); 645 646 std::optional<bool> load_on_start = request.params[2].isNull() ? std::nullopt : std::optional<bool>(request.params[2].get_bool()); 647 648 DatabaseStatus status; 649 bilingual_str error; 650 std::vector<bilingual_str> warnings; 651 652 const std::shared_ptr<CWallet> wallet = RestoreWallet(context, backup_file, wallet_name, load_on_start, status, error, warnings); 653 654 HandleWalletError(wallet, status, error); 655 656 UniValue obj(UniValue::VOBJ); 657 obj.pushKV("name", wallet->GetName()); 658 PushWarnings(warnings, obj); 659 660 return obj; 661 662 }, 663 }; 664 } 665 } // namespace wallet