backup.cpp
1 // Copyright (c) 2009-2022 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 #if defined(HAVE_CONFIG_H) 6 #include <config/bitcoin-config.h> 7 #endif 8 9 #include <chain.h> 10 #include <clientversion.h> 11 #include <core_io.h> 12 #include <hash.h> 13 #include <interfaces/chain.h> 14 #include <key_io.h> 15 #include <merkleblock.h> 16 #include <rpc/util.h> 17 #include <script/descriptor.h> 18 #include <script/script.h> 19 #include <script/solver.h> 20 #include <sync.h> 21 #include <uint256.h> 22 #include <util/bip32.h> 23 #include <util/fs.h> 24 #include <util/time.h> 25 #include <util/translation.h> 26 #include <wallet/rpc/util.h> 27 #include <wallet/wallet.h> 28 29 #include <cstdint> 30 #include <fstream> 31 #include <tuple> 32 #include <string> 33 34 #include <univalue.h> 35 36 37 38 using interfaces::FoundBlock; 39 40 namespace wallet { 41 std::string static EncodeDumpString(const std::string &str) { 42 std::stringstream ret; 43 for (const unsigned char c : str) { 44 if (c <= 32 || c >= 128 || c == '%') { 45 ret << '%' << HexStr({&c, 1}); 46 } else { 47 ret << c; 48 } 49 } 50 return ret.str(); 51 } 52 53 static std::string DecodeDumpString(const std::string &str) { 54 std::stringstream ret; 55 for (unsigned int pos = 0; pos < str.length(); pos++) { 56 unsigned char c = str[pos]; 57 if (c == '%' && pos+2 < str.length()) { 58 c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) | 59 ((str[pos+2]>>6)*9+((str[pos+2]-'0')&15)); 60 pos += 2; 61 } 62 ret << c; 63 } 64 return ret.str(); 65 } 66 67 static bool GetWalletAddressesForKey(const LegacyScriptPubKeyMan* spk_man, const CWallet& wallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) 68 { 69 bool fLabelFound = false; 70 CKey key; 71 spk_man->GetKey(keyid, key); 72 for (const auto& dest : GetAllDestinationsForKey(key.GetPubKey())) { 73 const auto* address_book_entry = wallet.FindAddressBookEntry(dest); 74 if (address_book_entry) { 75 if (!strAddr.empty()) { 76 strAddr += ","; 77 } 78 strAddr += EncodeDestination(dest); 79 strLabel = EncodeDumpString(address_book_entry->GetLabel()); 80 fLabelFound = true; 81 } 82 } 83 if (!fLabelFound) { 84 strAddr = EncodeDestination(GetDestinationForKey(key.GetPubKey(), wallet.m_default_address_type)); 85 } 86 return fLabelFound; 87 } 88 89 static const int64_t TIMESTAMP_MIN = 0; 90 91 static void RescanWallet(CWallet& wallet, const WalletRescanReserver& reserver, int64_t time_begin = TIMESTAMP_MIN, bool update = true) 92 { 93 int64_t scanned_time = wallet.RescanFromTime(time_begin, reserver, update); 94 if (wallet.IsAbortingRescan()) { 95 throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); 96 } else if (scanned_time > time_begin) { 97 throw JSONRPCError(RPC_WALLET_ERROR, "Rescan was unable to fully rescan the blockchain. Some transactions may be missing."); 98 } 99 } 100 101 static void EnsureBlockDataFromTime(const CWallet& wallet, int64_t timestamp) 102 { 103 auto& chain{wallet.chain()}; 104 if (!chain.havePruned()) { 105 return; 106 } 107 108 int height{0}; 109 const bool found{chain.findFirstBlockWithTimeAndHeight(timestamp - TIMESTAMP_WINDOW, 0, FoundBlock().height(height))}; 110 111 uint256 tip_hash{WITH_LOCK(wallet.cs_wallet, return wallet.GetLastBlockHash())}; 112 if (found && !chain.hasBlocks(tip_hash, height)) { 113 throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Pruned blocks from height %d required to import keys. Use RPC call getblockchaininfo to determine your pruned height.", height)); 114 } 115 } 116 117 RPCHelpMan importprivkey() 118 { 119 return RPCHelpMan{"importprivkey", 120 "\nAdds a private key (as returned by dumpprivkey) to your wallet. Requires a new wallet backup.\n" 121 "Hint: use importmulti to import more than one private key.\n" 122 "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" 123 "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" 124 "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n" 125 "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n" 126 "Note: Use \"getwalletinfo\" to query the scanning progress.\n" 127 "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" with \"combo(X)\" for descriptor wallets.\n", 128 { 129 {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key (see dumpprivkey)"}, 130 {"label", RPCArg::Type::STR, RPCArg::DefaultHint{"current label if address exists, otherwise \"\""}, "An optional label"}, 131 {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."}, 132 }, 133 RPCResult{RPCResult::Type::NONE, "", ""}, 134 RPCExamples{ 135 "\nDump a private key\n" 136 + HelpExampleCli("dumpprivkey", "\"myaddress\"") + 137 "\nImport the private key with rescan\n" 138 + HelpExampleCli("importprivkey", "\"mykey\"") + 139 "\nImport using a label and without rescan\n" 140 + HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") + 141 "\nImport using default blank label and without rescan\n" 142 + HelpExampleCli("importprivkey", "\"mykey\" \"\" false") + 143 "\nAs a JSON-RPC call\n" 144 + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false") 145 }, 146 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 147 { 148 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 149 if (!pwallet) return UniValue::VNULL; 150 151 if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { 152 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); 153 } 154 155 EnsureLegacyScriptPubKeyMan(*pwallet, true); 156 157 WalletRescanReserver reserver(*pwallet); 158 bool fRescan = true; 159 { 160 LOCK(pwallet->cs_wallet); 161 162 EnsureWalletIsUnlocked(*pwallet); 163 164 std::string strSecret = request.params[0].get_str(); 165 const std::string strLabel{LabelFromValue(request.params[1])}; 166 167 // Whether to perform rescan after import 168 if (!request.params[2].isNull()) 169 fRescan = request.params[2].get_bool(); 170 171 if (fRescan && pwallet->chain().havePruned()) { 172 // Exit early and print an error. 173 // If a block is pruned after this check, we will import the key(s), 174 // but fail the rescan with a generic error. 175 throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned"); 176 } 177 178 if (fRescan && !reserver.reserve()) { 179 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); 180 } 181 182 CKey key = DecodeSecret(strSecret); 183 if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); 184 185 CPubKey pubkey = key.GetPubKey(); 186 CHECK_NONFATAL(key.VerifyPubKey(pubkey)); 187 CKeyID vchAddress = pubkey.GetID(); 188 { 189 pwallet->MarkDirty(); 190 191 // We don't know which corresponding address will be used; 192 // label all new addresses, and label existing addresses if a 193 // label was passed. 194 for (const auto& dest : GetAllDestinationsForKey(pubkey)) { 195 if (!request.params[1].isNull() || !pwallet->FindAddressBookEntry(dest)) { 196 pwallet->SetAddressBook(dest, strLabel, AddressPurpose::RECEIVE); 197 } 198 } 199 200 // Use timestamp of 1 to scan the whole chain 201 if (!pwallet->ImportPrivKeys({{vchAddress, key}}, 1)) { 202 throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); 203 } 204 205 // Add the wpkh script for this key if possible 206 if (pubkey.IsCompressed()) { 207 pwallet->ImportScripts({GetScriptForDestination(WitnessV0KeyHash(vchAddress))}, /*timestamp=*/0); 208 } 209 } 210 } 211 if (fRescan) { 212 RescanWallet(*pwallet, reserver); 213 } 214 215 return UniValue::VNULL; 216 }, 217 }; 218 } 219 220 RPCHelpMan importaddress() 221 { 222 return RPCHelpMan{"importaddress", 223 "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" 224 "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" 225 "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" 226 "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n" 227 "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n" 228 "If you have the full public key, you should call importpubkey instead of this.\n" 229 "Hint: use importmulti to import more than one address.\n" 230 "\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n" 231 "as change, and not show up in many RPCs.\n" 232 "Note: Use \"getwalletinfo\" to query the scanning progress.\n" 233 "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" for descriptor wallets.\n", 234 { 235 {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Bitcoin address (or hex-encoded script)"}, 236 {"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"}, 237 {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."}, 238 {"p2sh", RPCArg::Type::BOOL, RPCArg::Default{false}, "Add the P2SH version of the script as well"}, 239 }, 240 RPCResult{RPCResult::Type::NONE, "", ""}, 241 RPCExamples{ 242 "\nImport an address with rescan\n" 243 + HelpExampleCli("importaddress", "\"myaddress\"") + 244 "\nImport using a label without rescan\n" 245 + HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") + 246 "\nAs a JSON-RPC call\n" 247 + HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false") 248 }, 249 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 250 { 251 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 252 if (!pwallet) return UniValue::VNULL; 253 254 EnsureLegacyScriptPubKeyMan(*pwallet, true); 255 256 const std::string strLabel{LabelFromValue(request.params[1])}; 257 258 // Whether to perform rescan after import 259 bool fRescan = true; 260 if (!request.params[2].isNull()) 261 fRescan = request.params[2].get_bool(); 262 263 if (fRescan && pwallet->chain().havePruned()) { 264 // Exit early and print an error. 265 // If a block is pruned after this check, we will import the key(s), 266 // but fail the rescan with a generic error. 267 throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned"); 268 } 269 270 WalletRescanReserver reserver(*pwallet); 271 if (fRescan && !reserver.reserve()) { 272 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); 273 } 274 275 // Whether to import a p2sh version, too 276 bool fP2SH = false; 277 if (!request.params[3].isNull()) 278 fP2SH = request.params[3].get_bool(); 279 280 { 281 LOCK(pwallet->cs_wallet); 282 283 CTxDestination dest = DecodeDestination(request.params[0].get_str()); 284 if (IsValidDestination(dest)) { 285 if (fP2SH) { 286 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead"); 287 } 288 if (OutputTypeFromDestination(dest) == OutputType::BECH32M) { 289 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m addresses cannot be imported into legacy wallets"); 290 } 291 292 pwallet->MarkDirty(); 293 294 pwallet->ImportScriptPubKeys(strLabel, {GetScriptForDestination(dest)}, /*have_solving_data=*/false, /*apply_label=*/true, /*timestamp=*/1); 295 } else if (IsHex(request.params[0].get_str())) { 296 std::vector<unsigned char> data(ParseHex(request.params[0].get_str())); 297 CScript redeem_script(data.begin(), data.end()); 298 299 std::set<CScript> scripts = {redeem_script}; 300 pwallet->ImportScripts(scripts, /*timestamp=*/0); 301 302 if (fP2SH) { 303 scripts.insert(GetScriptForDestination(ScriptHash(redeem_script))); 304 } 305 306 pwallet->ImportScriptPubKeys(strLabel, scripts, /*have_solving_data=*/false, /*apply_label=*/true, /*timestamp=*/1); 307 } else { 308 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); 309 } 310 } 311 if (fRescan) 312 { 313 RescanWallet(*pwallet, reserver); 314 pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); 315 } 316 317 return UniValue::VNULL; 318 }, 319 }; 320 } 321 322 RPCHelpMan importprunedfunds() 323 { 324 return RPCHelpMan{"importprunedfunds", 325 "\nImports 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", 326 { 327 {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"}, 328 {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"}, 329 }, 330 RPCResult{RPCResult::Type::NONE, "", ""}, 331 RPCExamples{""}, 332 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 333 { 334 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 335 if (!pwallet) return UniValue::VNULL; 336 337 CMutableTransaction tx; 338 if (!DecodeHexTx(tx, request.params[0].get_str())) { 339 throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input."); 340 } 341 uint256 hashTx = tx.GetHash(); 342 343 DataStream ssMB{ParseHexV(request.params[1], "proof")}; 344 CMerkleBlock merkleBlock; 345 ssMB >> merkleBlock; 346 347 //Search partial merkle tree in proof for our transaction and index in valid block 348 std::vector<uint256> vMatch; 349 std::vector<unsigned int> vIndex; 350 if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) { 351 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock"); 352 } 353 354 LOCK(pwallet->cs_wallet); 355 int height; 356 if (!pwallet->chain().findAncestorByHash(pwallet->GetLastBlockHash(), merkleBlock.header.GetHash(), FoundBlock().height(height))) { 357 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); 358 } 359 360 std::vector<uint256>::const_iterator it; 361 if ((it = std::find(vMatch.begin(), vMatch.end(), hashTx)) == vMatch.end()) { 362 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof"); 363 } 364 365 unsigned int txnIndex = vIndex[it - vMatch.begin()]; 366 367 CTransactionRef tx_ref = MakeTransactionRef(tx); 368 if (pwallet->IsMine(*tx_ref)) { 369 pwallet->AddToWallet(std::move(tx_ref), TxStateConfirmed{merkleBlock.header.GetHash(), height, static_cast<int>(txnIndex)}); 370 return UniValue::VNULL; 371 } 372 373 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction"); 374 }, 375 }; 376 } 377 378 RPCHelpMan removeprunedfunds() 379 { 380 return RPCHelpMan{"removeprunedfunds", 381 "\nDeletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will affect wallet balances.\n", 382 { 383 {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"}, 384 }, 385 RPCResult{RPCResult::Type::NONE, "", ""}, 386 RPCExamples{ 387 HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") + 388 "\nAs a JSON-RPC call\n" 389 + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") 390 }, 391 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 392 { 393 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 394 if (!pwallet) return UniValue::VNULL; 395 396 LOCK(pwallet->cs_wallet); 397 398 uint256 hash(ParseHashV(request.params[0], "txid")); 399 std::vector<uint256> vHash; 400 vHash.push_back(hash); 401 if (auto res = pwallet->RemoveTxs(vHash); !res) { 402 throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original); 403 } 404 405 return UniValue::VNULL; 406 }, 407 }; 408 } 409 410 RPCHelpMan importpubkey() 411 { 412 return RPCHelpMan{"importpubkey", 413 "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n" 414 "Hint: use importmulti to import more than one public key.\n" 415 "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" 416 "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n" 417 "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n" 418 "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n" 419 "Note: Use \"getwalletinfo\" to query the scanning progress.\n" 420 "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" with \"combo(X)\" for descriptor wallets.\n", 421 { 422 {"pubkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The hex-encoded public key"}, 423 {"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"}, 424 {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."}, 425 }, 426 RPCResult{RPCResult::Type::NONE, "", ""}, 427 RPCExamples{ 428 "\nImport a public key with rescan\n" 429 + HelpExampleCli("importpubkey", "\"mypubkey\"") + 430 "\nImport using a label without rescan\n" 431 + HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") + 432 "\nAs a JSON-RPC call\n" 433 + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false") 434 }, 435 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 436 { 437 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 438 if (!pwallet) return UniValue::VNULL; 439 440 EnsureLegacyScriptPubKeyMan(*pwallet, true); 441 442 const std::string strLabel{LabelFromValue(request.params[1])}; 443 444 // Whether to perform rescan after import 445 bool fRescan = true; 446 if (!request.params[2].isNull()) 447 fRescan = request.params[2].get_bool(); 448 449 if (fRescan && pwallet->chain().havePruned()) { 450 // Exit early and print an error. 451 // If a block is pruned after this check, we will import the key(s), 452 // but fail the rescan with a generic error. 453 throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned"); 454 } 455 456 WalletRescanReserver reserver(*pwallet); 457 if (fRescan && !reserver.reserve()) { 458 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); 459 } 460 461 if (!IsHex(request.params[0].get_str())) 462 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); 463 std::vector<unsigned char> data(ParseHex(request.params[0].get_str())); 464 CPubKey pubKey(data); 465 if (!pubKey.IsFullyValid()) 466 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); 467 468 { 469 LOCK(pwallet->cs_wallet); 470 471 std::set<CScript> script_pub_keys; 472 for (const auto& dest : GetAllDestinationsForKey(pubKey)) { 473 script_pub_keys.insert(GetScriptForDestination(dest)); 474 } 475 476 pwallet->MarkDirty(); 477 478 pwallet->ImportScriptPubKeys(strLabel, script_pub_keys, /*have_solving_data=*/true, /*apply_label=*/true, /*timestamp=*/1); 479 480 pwallet->ImportPubKeys({pubKey.GetID()}, {{pubKey.GetID(), pubKey}} , /*key_origins=*/{}, /*add_keypool=*/false, /*internal=*/false, /*timestamp=*/1); 481 } 482 if (fRescan) 483 { 484 RescanWallet(*pwallet, reserver); 485 pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); 486 } 487 488 return UniValue::VNULL; 489 }, 490 }; 491 } 492 493 494 RPCHelpMan importwallet() 495 { 496 return RPCHelpMan{"importwallet", 497 "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n" 498 "Note: Blockchain and Mempool will be rescanned after a successful import. Use \"getwalletinfo\" to query the scanning progress.\n" 499 "Note: This command is only compatible with legacy wallets.\n", 500 { 501 {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet file"}, 502 }, 503 RPCResult{RPCResult::Type::NONE, "", ""}, 504 RPCExamples{ 505 "\nDump the wallet\n" 506 + HelpExampleCli("dumpwallet", "\"test\"") + 507 "\nImport the wallet\n" 508 + HelpExampleCli("importwallet", "\"test\"") + 509 "\nImport using the json rpc call\n" 510 + HelpExampleRpc("importwallet", "\"test\"") 511 }, 512 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 513 { 514 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 515 if (!pwallet) return UniValue::VNULL; 516 517 EnsureLegacyScriptPubKeyMan(*pwallet, true); 518 519 WalletRescanReserver reserver(*pwallet); 520 if (!reserver.reserve()) { 521 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); 522 } 523 524 int64_t nTimeBegin = 0; 525 bool fGood = true; 526 { 527 LOCK(pwallet->cs_wallet); 528 529 EnsureWalletIsUnlocked(*pwallet); 530 531 std::ifstream file; 532 file.open(fs::u8path(request.params[0].get_str()), std::ios::in | std::ios::ate); 533 if (!file.is_open()) { 534 throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); 535 } 536 CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nTimeBegin))); 537 538 int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg()); 539 file.seekg(0, file.beg); 540 541 // Use uiInterface.ShowProgress instead of pwallet.ShowProgress because pwallet.ShowProgress has a cancel button tied to AbortRescan which 542 // we don't want for this progress bar showing the import progress. uiInterface.ShowProgress does not have a cancel button. 543 pwallet->chain().showProgress(strprintf("%s " + _("Importing…").translated, pwallet->GetDisplayName()), 0, false); // show progress dialog in GUI 544 std::vector<std::tuple<CKey, int64_t, bool, std::string>> keys; 545 std::vector<std::pair<CScript, int64_t>> scripts; 546 while (file.good()) { 547 pwallet->chain().showProgress("", std::max(1, std::min(50, (int)(((double)file.tellg() / (double)nFilesize) * 100))), false); 548 std::string line; 549 std::getline(file, line); 550 if (line.empty() || line[0] == '#') 551 continue; 552 553 std::vector<std::string> vstr = SplitString(line, ' '); 554 if (vstr.size() < 2) 555 continue; 556 CKey key = DecodeSecret(vstr[0]); 557 if (key.IsValid()) { 558 int64_t nTime = ParseISO8601DateTime(vstr[1]); 559 std::string strLabel; 560 bool fLabel = true; 561 for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) { 562 if (vstr[nStr].front() == '#') 563 break; 564 if (vstr[nStr] == "change=1") 565 fLabel = false; 566 if (vstr[nStr] == "reserve=1") 567 fLabel = false; 568 if (vstr[nStr].substr(0,6) == "label=") { 569 strLabel = DecodeDumpString(vstr[nStr].substr(6)); 570 fLabel = true; 571 } 572 } 573 nTimeBegin = std::min(nTimeBegin, nTime); 574 keys.emplace_back(key, nTime, fLabel, strLabel); 575 } else if(IsHex(vstr[0])) { 576 std::vector<unsigned char> vData(ParseHex(vstr[0])); 577 CScript script = CScript(vData.begin(), vData.end()); 578 int64_t birth_time = ParseISO8601DateTime(vstr[1]); 579 if (birth_time > 0) nTimeBegin = std::min(nTimeBegin, birth_time); 580 scripts.emplace_back(script, birth_time); 581 } 582 } 583 file.close(); 584 EnsureBlockDataFromTime(*pwallet, nTimeBegin); 585 // We now know whether we are importing private keys, so we can error if private keys are disabled 586 if (keys.size() > 0 && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { 587 pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI 588 throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled when private keys are disabled"); 589 } 590 double total = (double)(keys.size() + scripts.size()); 591 double progress = 0; 592 for (const auto& key_tuple : keys) { 593 pwallet->chain().showProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false); 594 const CKey& key = std::get<0>(key_tuple); 595 int64_t time = std::get<1>(key_tuple); 596 bool has_label = std::get<2>(key_tuple); 597 std::string label = std::get<3>(key_tuple); 598 599 CPubKey pubkey = key.GetPubKey(); 600 CHECK_NONFATAL(key.VerifyPubKey(pubkey)); 601 CKeyID keyid = pubkey.GetID(); 602 603 pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(PKHash(keyid))); 604 605 if (!pwallet->ImportPrivKeys({{keyid, key}}, time)) { 606 pwallet->WalletLogPrintf("Error importing key for %s\n", EncodeDestination(PKHash(keyid))); 607 fGood = false; 608 continue; 609 } 610 611 if (has_label) 612 pwallet->SetAddressBook(PKHash(keyid), label, AddressPurpose::RECEIVE); 613 progress++; 614 } 615 for (const auto& script_pair : scripts) { 616 pwallet->chain().showProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false); 617 const CScript& script = script_pair.first; 618 int64_t time = script_pair.second; 619 620 if (!pwallet->ImportScripts({script}, time)) { 621 pwallet->WalletLogPrintf("Error importing script %s\n", HexStr(script)); 622 fGood = false; 623 continue; 624 } 625 626 progress++; 627 } 628 pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI 629 } 630 pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI 631 RescanWallet(*pwallet, reserver, nTimeBegin, /*update=*/false); 632 pwallet->MarkDirty(); 633 634 if (!fGood) 635 throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys/scripts to wallet"); 636 637 return UniValue::VNULL; 638 }, 639 }; 640 } 641 642 RPCHelpMan dumpprivkey() 643 { 644 return RPCHelpMan{"dumpprivkey", 645 "\nReveals the private key corresponding to 'address'.\n" 646 "Then the importprivkey can be used with this output\n" 647 "Note: This command is only compatible with legacy wallets.\n", 648 { 649 {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for the private key"}, 650 }, 651 RPCResult{ 652 RPCResult::Type::STR, "key", "The private key" 653 }, 654 RPCExamples{ 655 HelpExampleCli("dumpprivkey", "\"myaddress\"") 656 + HelpExampleCli("importprivkey", "\"mykey\"") 657 + HelpExampleRpc("dumpprivkey", "\"myaddress\"") 658 }, 659 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 660 { 661 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); 662 if (!pwallet) return UniValue::VNULL; 663 664 const LegacyScriptPubKeyMan& spk_man = EnsureConstLegacyScriptPubKeyMan(*pwallet); 665 666 LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); 667 668 EnsureWalletIsUnlocked(*pwallet); 669 670 std::string strAddress = request.params[0].get_str(); 671 CTxDestination dest = DecodeDestination(strAddress); 672 if (!IsValidDestination(dest)) { 673 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); 674 } 675 auto keyid = GetKeyForDestination(spk_man, dest); 676 if (keyid.IsNull()) { 677 throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); 678 } 679 CKey vchSecret; 680 if (!spk_man.GetKey(keyid, vchSecret)) { 681 throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); 682 } 683 return EncodeSecret(vchSecret); 684 }, 685 }; 686 } 687 688 689 RPCHelpMan dumpwallet() 690 { 691 return RPCHelpMan{"dumpwallet", 692 "\nDumps all wallet keys in a human-readable format to a server-side file. This does not allow overwriting existing files.\n" 693 "Imported scripts are included in the dumpfile, but corresponding BIP173 addresses, etc. may not be added automatically by importwallet.\n" 694 "Note that if your wallet contains keys which are not derived from your HD seed (e.g. imported keys), these are not covered by\n" 695 "only backing up the seed itself, and must be backed up too (e.g. ensure you back up the whole dumpfile).\n" 696 "Note: This command is only compatible with legacy wallets.\n", 697 { 698 {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The filename with path (absolute path recommended)"}, 699 }, 700 RPCResult{ 701 RPCResult::Type::OBJ, "", "", 702 { 703 {RPCResult::Type::STR, "filename", "The filename with full absolute path"}, 704 } 705 }, 706 RPCExamples{ 707 HelpExampleCli("dumpwallet", "\"test\"") 708 + HelpExampleRpc("dumpwallet", "\"test\"") 709 }, 710 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 711 { 712 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); 713 if (!pwallet) return UniValue::VNULL; 714 715 const CWallet& wallet = *pwallet; 716 const LegacyScriptPubKeyMan& spk_man = EnsureConstLegacyScriptPubKeyMan(wallet); 717 718 // Make sure the results are valid at least up to the most recent block 719 // the user could have gotten from another RPC command prior to now 720 wallet.BlockUntilSyncedToCurrentChain(); 721 722 LOCK(wallet.cs_wallet); 723 724 EnsureWalletIsUnlocked(wallet); 725 726 fs::path filepath = fs::u8path(request.params[0].get_str()); 727 filepath = fs::absolute(filepath); 728 729 /* Prevent arbitrary files from being overwritten. There have been reports 730 * that users have overwritten wallet files this way: 731 * https://github.com/bitcoin/bitcoin/issues/9934 732 * It may also avoid other security issues. 733 */ 734 if (fs::exists(filepath)) { 735 throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.utf8string() + " already exists. If you are sure this is what you want, move it out of the way first"); 736 } 737 738 std::ofstream file; 739 file.open(filepath); 740 if (!file.is_open()) 741 throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); 742 743 std::map<CKeyID, int64_t> mapKeyBirth; 744 wallet.GetKeyBirthTimes(mapKeyBirth); 745 746 int64_t block_time = 0; 747 CHECK_NONFATAL(wallet.chain().findBlock(wallet.GetLastBlockHash(), FoundBlock().time(block_time))); 748 749 // Note: To avoid a lock order issue, access to cs_main must be locked before cs_KeyStore. 750 // So we do the two things in this function that lock cs_main first: GetKeyBirthTimes, and findBlock. 751 LOCK(spk_man.cs_KeyStore); 752 753 const std::map<CKeyID, int64_t>& mapKeyPool = spk_man.GetAllReserveKeys(); 754 std::set<CScriptID> scripts = spk_man.GetCScripts(); 755 756 // sort time/key pairs 757 std::vector<std::pair<int64_t, CKeyID> > vKeyBirth; 758 vKeyBirth.reserve(mapKeyBirth.size()); 759 for (const auto& entry : mapKeyBirth) { 760 vKeyBirth.emplace_back(entry.second, entry.first); 761 } 762 mapKeyBirth.clear(); 763 std::sort(vKeyBirth.begin(), vKeyBirth.end()); 764 765 // produce output 766 file << strprintf("# Wallet dump created by %s %s\n", PACKAGE_NAME, FormatFullVersion()); 767 file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime())); 768 file << strprintf("# * Best block at time of backup was %i (%s),\n", wallet.GetLastBlockHeight(), wallet.GetLastBlockHash().ToString()); 769 file << strprintf("# mined on %s\n", FormatISO8601DateTime(block_time)); 770 file << "\n"; 771 772 // add the base58check encoded extended master if the wallet uses HD 773 CKeyID seed_id = spk_man.GetHDChain().seed_id; 774 if (!seed_id.IsNull()) 775 { 776 CKey seed; 777 if (spk_man.GetKey(seed_id, seed)) { 778 CExtKey masterKey; 779 masterKey.SetSeed(seed); 780 781 file << "# extended private masterkey: " << EncodeExtKey(masterKey) << "\n\n"; 782 } 783 } 784 for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { 785 const CKeyID &keyid = it->second; 786 std::string strTime = FormatISO8601DateTime(it->first); 787 std::string strAddr; 788 std::string strLabel; 789 CKey key; 790 if (spk_man.GetKey(keyid, key)) { 791 CKeyMetadata metadata; 792 const auto it{spk_man.mapKeyMetadata.find(keyid)}; 793 if (it != spk_man.mapKeyMetadata.end()) metadata = it->second; 794 file << strprintf("%s %s ", EncodeSecret(key), strTime); 795 if (GetWalletAddressesForKey(&spk_man, wallet, keyid, strAddr, strLabel)) { 796 file << strprintf("label=%s", strLabel); 797 } else if (keyid == seed_id) { 798 file << "hdseed=1"; 799 } else if (mapKeyPool.count(keyid)) { 800 file << "reserve=1"; 801 } else if (metadata.hdKeypath == "s") { 802 file << "inactivehdseed=1"; 803 } else { 804 file << "change=1"; 805 } 806 file << strprintf(" # addr=%s%s\n", strAddr, (metadata.has_key_origin ? " hdkeypath="+WriteHDKeypath(metadata.key_origin.path, /*apostrophe=*/true) : "")); 807 } 808 } 809 file << "\n"; 810 for (const CScriptID &scriptid : scripts) { 811 CScript script; 812 std::string create_time = "0"; 813 std::string address = EncodeDestination(ScriptHash(scriptid)); 814 // get birth times for scripts with metadata 815 auto it = spk_man.m_script_metadata.find(scriptid); 816 if (it != spk_man.m_script_metadata.end()) { 817 create_time = FormatISO8601DateTime(it->second.nCreateTime); 818 } 819 if(spk_man.GetCScript(scriptid, script)) { 820 file << strprintf("%s %s script=1", HexStr(script), create_time); 821 file << strprintf(" # addr=%s\n", address); 822 } 823 } 824 file << "\n"; 825 file << "# End of dump\n"; 826 file.close(); 827 828 UniValue reply(UniValue::VOBJ); 829 reply.pushKV("filename", filepath.utf8string()); 830 831 return reply; 832 }, 833 }; 834 } 835 836 struct ImportData 837 { 838 // Input data 839 std::unique_ptr<CScript> redeemscript; //!< Provided redeemScript; will be moved to `import_scripts` if relevant. 840 std::unique_ptr<CScript> witnessscript; //!< Provided witnessScript; will be moved to `import_scripts` if relevant. 841 842 // Output data 843 std::set<CScript> import_scripts; 844 std::map<CKeyID, bool> used_keys; //!< Import these private keys if available (the value indicates whether if the key is required for solvability) 845 std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> key_origins; 846 }; 847 848 enum class ScriptContext 849 { 850 TOP, //!< Top-level scriptPubKey 851 P2SH, //!< P2SH redeemScript 852 WITNESS_V0, //!< P2WSH witnessScript 853 }; 854 855 // Analyse the provided scriptPubKey, determining which keys and which redeem scripts from the ImportData struct are needed to spend it, and mark them as used. 856 // Returns an error string, or the empty string for success. 857 static std::string RecurseImportData(const CScript& script, ImportData& import_data, const ScriptContext script_ctx) 858 { 859 // Use Solver to obtain script type and parsed pubkeys or hashes: 860 std::vector<std::vector<unsigned char>> solverdata; 861 TxoutType script_type = Solver(script, solverdata); 862 863 switch (script_type) { 864 case TxoutType::PUBKEY: { 865 CPubKey pubkey(solverdata[0]); 866 import_data.used_keys.emplace(pubkey.GetID(), false); 867 return ""; 868 } 869 case TxoutType::PUBKEYHASH: { 870 CKeyID id = CKeyID(uint160(solverdata[0])); 871 import_data.used_keys[id] = true; 872 return ""; 873 } 874 case TxoutType::SCRIPTHASH: { 875 if (script_ctx == ScriptContext::P2SH) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside another P2SH"); 876 if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside a P2WSH"); 877 CHECK_NONFATAL(script_ctx == ScriptContext::TOP); 878 CScriptID id = CScriptID(uint160(solverdata[0])); 879 auto subscript = std::move(import_data.redeemscript); // Remove redeemscript from import_data to check for superfluous script later. 880 if (!subscript) return "missing redeemscript"; 881 if (CScriptID(*subscript) != id) return "redeemScript does not match the scriptPubKey"; 882 import_data.import_scripts.emplace(*subscript); 883 return RecurseImportData(*subscript, import_data, ScriptContext::P2SH); 884 } 885 case TxoutType::MULTISIG: { 886 for (size_t i = 1; i + 1< solverdata.size(); ++i) { 887 CPubKey pubkey(solverdata[i]); 888 import_data.used_keys.emplace(pubkey.GetID(), false); 889 } 890 return ""; 891 } 892 case TxoutType::WITNESS_V0_SCRIPTHASH: { 893 if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2WSH inside another P2WSH"); 894 CScriptID id{RIPEMD160(solverdata[0])}; 895 auto subscript = std::move(import_data.witnessscript); // Remove redeemscript from import_data to check for superfluous script later. 896 if (!subscript) return "missing witnessscript"; 897 if (CScriptID(*subscript) != id) return "witnessScript does not match the scriptPubKey or redeemScript"; 898 if (script_ctx == ScriptContext::TOP) { 899 import_data.import_scripts.emplace(script); // Special rule for IsMine: native P2WSH requires the TOP script imported (see script/ismine.cpp) 900 } 901 import_data.import_scripts.emplace(*subscript); 902 return RecurseImportData(*subscript, import_data, ScriptContext::WITNESS_V0); 903 } 904 case TxoutType::WITNESS_V0_KEYHASH: { 905 if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2WPKH inside P2WSH"); 906 CKeyID id = CKeyID(uint160(solverdata[0])); 907 import_data.used_keys[id] = true; 908 if (script_ctx == ScriptContext::TOP) { 909 import_data.import_scripts.emplace(script); // Special rule for IsMine: native P2WPKH requires the TOP script imported (see script/ismine.cpp) 910 } 911 return ""; 912 } 913 case TxoutType::NULL_DATA: 914 return "unspendable script"; 915 case TxoutType::NONSTANDARD: 916 case TxoutType::WITNESS_UNKNOWN: 917 case TxoutType::WITNESS_V1_TAPROOT: 918 return "unrecognized script"; 919 } // no default case, so the compiler can warn about missing cases 920 NONFATAL_UNREACHABLE(); 921 } 922 923 static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys) 924 { 925 UniValue warnings(UniValue::VARR); 926 927 // First ensure scriptPubKey has either a script or JSON with "address" string 928 const UniValue& scriptPubKey = data["scriptPubKey"]; 929 bool isScript = scriptPubKey.getType() == UniValue::VSTR; 930 if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address"))) { 931 throw JSONRPCError(RPC_INVALID_PARAMETER, "scriptPubKey must be string with script or JSON with address string"); 932 } 933 const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); 934 935 // Optional fields. 936 const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : ""; 937 const std::string& witness_script_hex = data.exists("witnessscript") ? data["witnessscript"].get_str() : ""; 938 const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue(); 939 const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); 940 const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; 941 const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false; 942 943 if (data.exists("range")) { 944 throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for a non-descriptor import"); 945 } 946 947 // Generate the script and destination for the scriptPubKey provided 948 CScript script; 949 if (!isScript) { 950 CTxDestination dest = DecodeDestination(output); 951 if (!IsValidDestination(dest)) { 952 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\""); 953 } 954 if (OutputTypeFromDestination(dest) == OutputType::BECH32M) { 955 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m addresses cannot be imported into legacy wallets"); 956 } 957 script = GetScriptForDestination(dest); 958 } else { 959 if (!IsHex(output)) { 960 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey \"" + output + "\""); 961 } 962 std::vector<unsigned char> vData(ParseHex(output)); 963 script = CScript(vData.begin(), vData.end()); 964 CTxDestination dest; 965 if (!ExtractDestination(script, dest) && !internal) { 966 throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set to true for nonstandard scriptPubKey imports."); 967 } 968 } 969 script_pub_keys.emplace(script); 970 971 // Parse all arguments 972 if (strRedeemScript.size()) { 973 if (!IsHex(strRedeemScript)) { 974 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script \"" + strRedeemScript + "\": must be hex string"); 975 } 976 auto parsed_redeemscript = ParseHex(strRedeemScript); 977 import_data.redeemscript = std::make_unique<CScript>(parsed_redeemscript.begin(), parsed_redeemscript.end()); 978 } 979 if (witness_script_hex.size()) { 980 if (!IsHex(witness_script_hex)) { 981 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script \"" + witness_script_hex + "\": must be hex string"); 982 } 983 auto parsed_witnessscript = ParseHex(witness_script_hex); 984 import_data.witnessscript = std::make_unique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end()); 985 } 986 for (size_t i = 0; i < pubKeys.size(); ++i) { 987 const auto& str = pubKeys[i].get_str(); 988 if (!IsHex(str)) { 989 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" must be a hex string"); 990 } 991 auto parsed_pubkey = ParseHex(str); 992 CPubKey pubkey(parsed_pubkey); 993 if (!pubkey.IsFullyValid()) { 994 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key"); 995 } 996 pubkey_map.emplace(pubkey.GetID(), pubkey); 997 ordered_pubkeys.push_back(pubkey.GetID()); 998 } 999 for (size_t i = 0; i < keys.size(); ++i) { 1000 const auto& str = keys[i].get_str(); 1001 CKey key = DecodeSecret(str); 1002 if (!key.IsValid()) { 1003 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); 1004 } 1005 CPubKey pubkey = key.GetPubKey(); 1006 CKeyID id = pubkey.GetID(); 1007 if (pubkey_map.count(id)) { 1008 pubkey_map.erase(id); 1009 } 1010 privkey_map.emplace(id, key); 1011 } 1012 1013 1014 // Verify and process input data 1015 have_solving_data = import_data.redeemscript || import_data.witnessscript || pubkey_map.size() || privkey_map.size(); 1016 if (have_solving_data) { 1017 // Match up data in import_data with the scriptPubKey in script. 1018 auto error = RecurseImportData(script, import_data, ScriptContext::TOP); 1019 1020 // Verify whether the watchonly option corresponds to the availability of private keys. 1021 bool spendable = std::all_of(import_data.used_keys.begin(), import_data.used_keys.end(), [&](const std::pair<CKeyID, bool>& used_key){ return privkey_map.count(used_key.first) > 0; }); 1022 if (!watchOnly && !spendable) { 1023 warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."); 1024 } 1025 if (watchOnly && spendable) { 1026 warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag."); 1027 } 1028 1029 // Check that all required keys for solvability are provided. 1030 if (error.empty()) { 1031 for (const auto& require_key : import_data.used_keys) { 1032 if (!require_key.second) continue; // Not a required key 1033 if (pubkey_map.count(require_key.first) == 0 && privkey_map.count(require_key.first) == 0) { 1034 error = "some required keys are missing"; 1035 } 1036 } 1037 } 1038 1039 if (!error.empty()) { 1040 warnings.push_back("Importing as non-solvable: " + error + ". If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript."); 1041 import_data = ImportData(); 1042 pubkey_map.clear(); 1043 privkey_map.clear(); 1044 have_solving_data = false; 1045 } else { 1046 // RecurseImportData() removes any relevant redeemscript/witnessscript from import_data, so we can use that to discover if a superfluous one was provided. 1047 if (import_data.redeemscript) warnings.push_back("Ignoring redeemscript as this is not a P2SH script."); 1048 if (import_data.witnessscript) warnings.push_back("Ignoring witnessscript as this is not a (P2SH-)P2WSH script."); 1049 for (auto it = privkey_map.begin(); it != privkey_map.end(); ) { 1050 auto oldit = it++; 1051 if (import_data.used_keys.count(oldit->first) == 0) { 1052 warnings.push_back("Ignoring irrelevant private key."); 1053 privkey_map.erase(oldit); 1054 } 1055 } 1056 for (auto it = pubkey_map.begin(); it != pubkey_map.end(); ) { 1057 auto oldit = it++; 1058 auto key_data_it = import_data.used_keys.find(oldit->first); 1059 if (key_data_it == import_data.used_keys.end() || !key_data_it->second) { 1060 warnings.push_back("Ignoring public key \"" + HexStr(oldit->first) + "\" as it doesn't appear inside P2PKH or P2WPKH."); 1061 pubkey_map.erase(oldit); 1062 } 1063 } 1064 } 1065 } 1066 1067 return warnings; 1068 } 1069 1070 static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys) 1071 { 1072 UniValue warnings(UniValue::VARR); 1073 1074 const std::string& descriptor = data["desc"].get_str(); 1075 FlatSigningProvider keys; 1076 std::string error; 1077 auto parsed_desc = Parse(descriptor, keys, error, /* require_checksum = */ true); 1078 if (!parsed_desc) { 1079 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); 1080 } 1081 if (parsed_desc->GetOutputType() == OutputType::BECH32M) { 1082 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m descriptors cannot be imported into legacy wallets"); 1083 } 1084 1085 have_solving_data = parsed_desc->IsSolvable(); 1086 const bool watch_only = data.exists("watchonly") ? data["watchonly"].get_bool() : false; 1087 1088 int64_t range_start = 0, range_end = 0; 1089 if (!parsed_desc->IsRange() && data.exists("range")) { 1090 throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); 1091 } else if (parsed_desc->IsRange()) { 1092 if (!data.exists("range")) { 1093 throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor is ranged, please specify the range"); 1094 } 1095 std::tie(range_start, range_end) = ParseDescriptorRange(data["range"]); 1096 } 1097 1098 const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); 1099 1100 // Expand all descriptors to get public keys and scripts, and private keys if available. 1101 for (int i = range_start; i <= range_end; ++i) { 1102 FlatSigningProvider out_keys; 1103 std::vector<CScript> scripts_temp; 1104 parsed_desc->Expand(i, keys, scripts_temp, out_keys); 1105 std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end())); 1106 for (const auto& key_pair : out_keys.pubkeys) { 1107 ordered_pubkeys.push_back(key_pair.first); 1108 } 1109 1110 for (const auto& x : out_keys.scripts) { 1111 import_data.import_scripts.emplace(x.second); 1112 } 1113 1114 parsed_desc->ExpandPrivate(i, keys, out_keys); 1115 1116 std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end())); 1117 std::copy(out_keys.keys.begin(), out_keys.keys.end(), std::inserter(privkey_map, privkey_map.end())); 1118 import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end()); 1119 } 1120 1121 for (size_t i = 0; i < priv_keys.size(); ++i) { 1122 const auto& str = priv_keys[i].get_str(); 1123 CKey key = DecodeSecret(str); 1124 if (!key.IsValid()) { 1125 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); 1126 } 1127 CPubKey pubkey = key.GetPubKey(); 1128 CKeyID id = pubkey.GetID(); 1129 1130 // Check if this private key corresponds to a public key from the descriptor 1131 if (!pubkey_map.count(id)) { 1132 warnings.push_back("Ignoring irrelevant private key."); 1133 } else { 1134 privkey_map.emplace(id, key); 1135 } 1136 } 1137 1138 // Check if all the public keys have corresponding private keys in the import for spendability. 1139 // This does not take into account threshold multisigs which could be spendable without all keys. 1140 // Thus, threshold multisigs without all keys will be considered not spendable here, even if they are, 1141 // perhaps triggering a false warning message. This is consistent with the current wallet IsMine check. 1142 bool spendable = std::all_of(pubkey_map.begin(), pubkey_map.end(), 1143 [&](const std::pair<CKeyID, CPubKey>& used_key) { 1144 return privkey_map.count(used_key.first) > 0; 1145 }) && std::all_of(import_data.key_origins.begin(), import_data.key_origins.end(), 1146 [&](const std::pair<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& entry) { 1147 return privkey_map.count(entry.first) > 0; 1148 }); 1149 if (!watch_only && !spendable) { 1150 warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."); 1151 } 1152 if (watch_only && spendable) { 1153 warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag."); 1154 } 1155 1156 return warnings; 1157 } 1158 1159 static UniValue ProcessImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) 1160 { 1161 UniValue warnings(UniValue::VARR); 1162 UniValue result(UniValue::VOBJ); 1163 1164 try { 1165 const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; 1166 // Internal addresses should not have a label 1167 if (internal && data.exists("label")) { 1168 throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label"); 1169 } 1170 const std::string label{LabelFromValue(data["label"])}; 1171 const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false; 1172 1173 // Add to keypool only works with privkeys disabled 1174 if (add_keypool && !wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { 1175 throw JSONRPCError(RPC_INVALID_PARAMETER, "Keys can only be imported to the keypool when private keys are disabled"); 1176 } 1177 1178 ImportData import_data; 1179 std::map<CKeyID, CPubKey> pubkey_map; 1180 std::map<CKeyID, CKey> privkey_map; 1181 std::set<CScript> script_pub_keys; 1182 std::vector<CKeyID> ordered_pubkeys; 1183 bool have_solving_data; 1184 1185 if (data.exists("scriptPubKey") && data.exists("desc")) { 1186 throw JSONRPCError(RPC_INVALID_PARAMETER, "Both a descriptor and a scriptPubKey should not be provided."); 1187 } else if (data.exists("scriptPubKey")) { 1188 warnings = ProcessImportLegacy(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys); 1189 } else if (data.exists("desc")) { 1190 warnings = ProcessImportDescriptor(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys); 1191 } else { 1192 throw JSONRPCError(RPC_INVALID_PARAMETER, "Either a descriptor or scriptPubKey must be provided."); 1193 } 1194 1195 // If private keys are disabled, abort if private keys are being imported 1196 if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !privkey_map.empty()) { 1197 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); 1198 } 1199 1200 // Check whether we have any work to do 1201 for (const CScript& script : script_pub_keys) { 1202 if (wallet.IsMine(script) & ISMINE_SPENDABLE) { 1203 throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script (\"" + HexStr(script) + "\")"); 1204 } 1205 } 1206 1207 // All good, time to import 1208 wallet.MarkDirty(); 1209 if (!wallet.ImportScripts(import_data.import_scripts, timestamp)) { 1210 throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet"); 1211 } 1212 if (!wallet.ImportPrivKeys(privkey_map, timestamp)) { 1213 throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); 1214 } 1215 if (!wallet.ImportPubKeys(ordered_pubkeys, pubkey_map, import_data.key_origins, add_keypool, internal, timestamp)) { 1216 throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); 1217 } 1218 if (!wallet.ImportScriptPubKeys(label, script_pub_keys, have_solving_data, !internal, timestamp)) { 1219 throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); 1220 } 1221 1222 result.pushKV("success", UniValue(true)); 1223 } catch (const UniValue& e) { 1224 result.pushKV("success", UniValue(false)); 1225 result.pushKV("error", e); 1226 } catch (...) { 1227 result.pushKV("success", UniValue(false)); 1228 1229 result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields")); 1230 } 1231 PushWarnings(warnings, result); 1232 return result; 1233 } 1234 1235 static int64_t GetImportTimestamp(const UniValue& data, int64_t now) 1236 { 1237 if (data.exists("timestamp")) { 1238 const UniValue& timestamp = data["timestamp"]; 1239 if (timestamp.isNum()) { 1240 return timestamp.getInt<int64_t>(); 1241 } else if (timestamp.isStr() && timestamp.get_str() == "now") { 1242 return now; 1243 } 1244 throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type()))); 1245 } 1246 throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key"); 1247 } 1248 1249 RPCHelpMan importmulti() 1250 { 1251 return RPCHelpMan{"importmulti", 1252 "\nImport addresses/scripts (with private or public keys, redeem script (P2SH)), optionally rescanning the blockchain from the earliest creation time of the imported scripts. Requires a new wallet backup.\n" 1253 "If an address/script is imported without all of the private keys required to spend from that address, it will be watchonly. The 'watchonly' option must be set to true in this case or a warning will be returned.\n" 1254 "Conversely, if all the private keys are provided and the address/script is spendable, the watchonly option must be set to false, or a warning will be returned.\n" 1255 "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n" 1256 "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n" 1257 "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n" 1258 "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n" 1259 "Note: Use \"getwalletinfo\" to query the scanning progress.\n" 1260 "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" for descriptor wallets.\n", 1261 { 1262 {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported", 1263 { 1264 {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", 1265 { 1266 {"desc", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Descriptor to import. If using descriptor, do not also provide address/scriptPubKey, scripts, or pubkeys"}, 1267 {"scriptPubKey", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of scriptPubKey (string for script, json for address). Should not be provided if using a descriptor", 1268 RPCArgOptions{.type_str={"\"<script>\" | { \"address\":\"<address>\" }", "string / json"}} 1269 }, 1270 {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Creation time of the key expressed in " + UNIX_EPOCH_TIME + ",\n" 1271 "or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n" 1272 "key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n" 1273 "\"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n" 1274 "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n" 1275 "creation time of all keys being imported by the importmulti call will be scanned.", 1276 RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}} 1277 }, 1278 {"redeemscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH or P2SH-P2WSH address/scriptPubKey"}, 1279 {"witnessscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey"}, 1280 {"pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Array of strings giving pubkeys to import. They must occur in P2PKH or P2WPKH scripts. They are not required when the private key is also provided (see the \"keys\" argument).", 1281 { 1282 {"pubKey", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""}, 1283 } 1284 }, 1285 {"keys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Array of strings giving private keys to import. The corresponding public keys must occur in the output or redeemscript.", 1286 { 1287 {"key", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""}, 1288 } 1289 }, 1290 {"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"}, 1291 {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Stating whether matching outputs should be treated as not incoming payments (also known as change)"}, 1292 {"watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "Stating whether matching outputs should be considered watchonly."}, 1293 {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false"}, 1294 {"keypool", RPCArg::Type::BOOL, RPCArg::Default{false}, "Stating whether imported public keys should be added to the keypool for when users request new addresses. Only allowed when wallet private keys are disabled"}, 1295 }, 1296 }, 1297 }, 1298 RPCArgOptions{.oneline_description="requests"}}, 1299 {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", 1300 { 1301 {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions after all imports."}, 1302 }, 1303 RPCArgOptions{.oneline_description="options"}}, 1304 }, 1305 RPCResult{ 1306 RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result", 1307 { 1308 {RPCResult::Type::OBJ, "", "", 1309 { 1310 {RPCResult::Type::BOOL, "success", ""}, 1311 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "", 1312 { 1313 {RPCResult::Type::STR, "", ""}, 1314 }}, 1315 {RPCResult::Type::OBJ, "error", /*optional=*/true, "", 1316 { 1317 {RPCResult::Type::ELISION, "", "JSONRPC error"}, 1318 }}, 1319 }}, 1320 } 1321 }, 1322 RPCExamples{ 1323 HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, " 1324 "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + 1325 HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'") 1326 }, 1327 [&](const RPCHelpMan& self, const JSONRPCRequest& mainRequest) -> UniValue 1328 { 1329 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(mainRequest); 1330 if (!pwallet) return UniValue::VNULL; 1331 CWallet& wallet{*pwallet}; 1332 1333 // Make sure the results are valid at least up to the most recent block 1334 // the user could have gotten from another RPC command prior to now 1335 wallet.BlockUntilSyncedToCurrentChain(); 1336 1337 EnsureLegacyScriptPubKeyMan(*pwallet, true); 1338 1339 const UniValue& requests = mainRequest.params[0]; 1340 1341 //Default options 1342 bool fRescan = true; 1343 1344 if (!mainRequest.params[1].isNull()) { 1345 const UniValue& options = mainRequest.params[1]; 1346 1347 if (options.exists("rescan")) { 1348 fRescan = options["rescan"].get_bool(); 1349 } 1350 } 1351 1352 WalletRescanReserver reserver(*pwallet); 1353 if (fRescan && !reserver.reserve()) { 1354 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); 1355 } 1356 1357 int64_t now = 0; 1358 bool fRunScan = false; 1359 int64_t nLowestTimestamp = 0; 1360 UniValue response(UniValue::VARR); 1361 { 1362 LOCK(pwallet->cs_wallet); 1363 1364 // Check all requests are watchonly 1365 bool is_watchonly{true}; 1366 for (size_t i = 0; i < requests.size(); ++i) { 1367 const UniValue& request = requests[i]; 1368 if (!request.exists("watchonly") || !request["watchonly"].get_bool()) { 1369 is_watchonly = false; 1370 break; 1371 } 1372 } 1373 // Wallet does not need to be unlocked if all requests are watchonly 1374 if (!is_watchonly) EnsureWalletIsUnlocked(wallet); 1375 1376 // Verify all timestamps are present before importing any keys. 1377 CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nLowestTimestamp).mtpTime(now))); 1378 for (const UniValue& data : requests.getValues()) { 1379 GetImportTimestamp(data, now); 1380 } 1381 1382 const int64_t minimumTimestamp = 1; 1383 1384 for (const UniValue& data : requests.getValues()) { 1385 const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp); 1386 const UniValue result = ProcessImport(*pwallet, data, timestamp); 1387 response.push_back(result); 1388 1389 if (!fRescan) { 1390 continue; 1391 } 1392 1393 // If at least one request was successful then allow rescan. 1394 if (result["success"].get_bool()) { 1395 fRunScan = true; 1396 } 1397 1398 // Get the lowest timestamp. 1399 if (timestamp < nLowestTimestamp) { 1400 nLowestTimestamp = timestamp; 1401 } 1402 } 1403 } 1404 if (fRescan && fRunScan && requests.size()) { 1405 int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, /*update=*/true); 1406 pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); 1407 1408 if (pwallet->IsAbortingRescan()) { 1409 throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); 1410 } 1411 if (scannedTime > nLowestTimestamp) { 1412 std::vector<UniValue> results = response.getValues(); 1413 response.clear(); 1414 response.setArray(); 1415 size_t i = 0; 1416 for (const UniValue& request : requests.getValues()) { 1417 // If key creation date is within the successfully scanned 1418 // range, or if the import result already has an error set, let 1419 // the result stand unmodified. Otherwise replace the result 1420 // with an error message. 1421 if (scannedTime <= GetImportTimestamp(request, now) || results.at(i).exists("error")) { 1422 response.push_back(results.at(i)); 1423 } else { 1424 UniValue result = UniValue(UniValue::VOBJ); 1425 result.pushKV("success", UniValue(false)); 1426 result.pushKV( 1427 "error", 1428 JSONRPCError( 1429 RPC_MISC_ERROR, 1430 strprintf("Rescan failed for key with creation timestamp %d. There was an error reading a " 1431 "block from time %d, which is after or within %d seconds of key creation, and " 1432 "could contain transactions pertaining to the key. As a result, transactions " 1433 "and coins using this key may not appear in the wallet. This error could be " 1434 "caused by pruning or data corruption (see bitcoind log for details) and could " 1435 "be dealt with by downloading and rescanning the relevant blocks (see -reindex " 1436 "option and rescanblockchain RPC).", 1437 GetImportTimestamp(request, now), scannedTime - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW))); 1438 response.push_back(std::move(result)); 1439 } 1440 ++i; 1441 } 1442 } 1443 } 1444 1445 return response; 1446 }, 1447 }; 1448 } 1449 1450 static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) 1451 { 1452 UniValue warnings(UniValue::VARR); 1453 UniValue result(UniValue::VOBJ); 1454 1455 try { 1456 if (!data.exists("desc")) { 1457 throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found."); 1458 } 1459 1460 const std::string& descriptor = data["desc"].get_str(); 1461 const bool active = data.exists("active") ? data["active"].get_bool() : false; 1462 const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; 1463 const std::string label{LabelFromValue(data["label"])}; 1464 1465 // Parse descriptor string 1466 FlatSigningProvider keys; 1467 std::string error; 1468 auto parsed_desc = Parse(descriptor, keys, error, /* require_checksum = */ true); 1469 if (!parsed_desc) { 1470 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); 1471 } 1472 1473 // Range check 1474 int64_t range_start = 0, range_end = 1, next_index = 0; 1475 if (!parsed_desc->IsRange() && data.exists("range")) { 1476 throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); 1477 } else if (parsed_desc->IsRange()) { 1478 if (data.exists("range")) { 1479 auto range = ParseDescriptorRange(data["range"]); 1480 range_start = range.first; 1481 range_end = range.second + 1; // Specified range end is inclusive, but we need range end as exclusive 1482 } else { 1483 warnings.push_back("Range not given, using default keypool range"); 1484 range_start = 0; 1485 range_end = wallet.m_keypool_size; 1486 } 1487 next_index = range_start; 1488 1489 if (data.exists("next_index")) { 1490 next_index = data["next_index"].getInt<int64_t>(); 1491 // bound checks 1492 if (next_index < range_start || next_index >= range_end) { 1493 throw JSONRPCError(RPC_INVALID_PARAMETER, "next_index is out of range"); 1494 } 1495 } 1496 } 1497 1498 // Active descriptors must be ranged 1499 if (active && !parsed_desc->IsRange()) { 1500 throw JSONRPCError(RPC_INVALID_PARAMETER, "Active descriptors must be ranged"); 1501 } 1502 1503 // Ranged descriptors should not have a label 1504 if (data.exists("range") && data.exists("label")) { 1505 throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptors should not have a label"); 1506 } 1507 1508 // Internal addresses should not have a label either 1509 if (internal && data.exists("label")) { 1510 throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label"); 1511 } 1512 1513 // Combo descriptor check 1514 if (active && !parsed_desc->IsSingleType()) { 1515 throw JSONRPCError(RPC_WALLET_ERROR, "Combo descriptors cannot be set to active"); 1516 } 1517 1518 // If the wallet disabled private keys, abort if private keys exist 1519 if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) { 1520 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); 1521 } 1522 1523 // Need to ExpandPrivate to check if private keys are available for all pubkeys 1524 FlatSigningProvider expand_keys; 1525 std::vector<CScript> scripts; 1526 if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) { 1527 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot expand descriptor. Probably because of hardened derivations without private keys provided"); 1528 } 1529 parsed_desc->ExpandPrivate(0, keys, expand_keys); 1530 1531 // Check if all private keys are provided 1532 bool have_all_privkeys = !expand_keys.keys.empty(); 1533 for (const auto& entry : expand_keys.origins) { 1534 const CKeyID& key_id = entry.first; 1535 CKey key; 1536 if (!expand_keys.GetKey(key_id, key)) { 1537 have_all_privkeys = false; 1538 break; 1539 } 1540 } 1541 1542 // If private keys are enabled, check some things. 1543 if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { 1544 if (keys.keys.empty()) { 1545 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled"); 1546 } 1547 if (!have_all_privkeys) { 1548 warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors"); 1549 } 1550 } 1551 1552 WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index); 1553 1554 // Check if the wallet already contains the descriptor 1555 auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc); 1556 if (existing_spk_manager) { 1557 if (!existing_spk_manager->CanUpdateToWalletDescriptor(w_desc, error)) { 1558 throw JSONRPCError(RPC_INVALID_PARAMETER, error); 1559 } 1560 } 1561 1562 // Add descriptor to the wallet 1563 auto spk_manager = wallet.AddWalletDescriptor(w_desc, keys, label, internal); 1564 if (spk_manager == nullptr) { 1565 throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor)); 1566 } 1567 1568 // Set descriptor as active if necessary 1569 if (active) { 1570 if (!w_desc.descriptor->GetOutputType()) { 1571 warnings.push_back("Unknown output type, cannot set descriptor to active."); 1572 } else { 1573 wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal); 1574 } 1575 } else { 1576 if (w_desc.descriptor->GetOutputType()) { 1577 wallet.DeactivateScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal); 1578 } 1579 } 1580 1581 result.pushKV("success", UniValue(true)); 1582 } catch (const UniValue& e) { 1583 result.pushKV("success", UniValue(false)); 1584 result.pushKV("error", e); 1585 } 1586 PushWarnings(warnings, result); 1587 return result; 1588 } 1589 1590 RPCHelpMan importdescriptors() 1591 { 1592 return RPCHelpMan{"importdescriptors", 1593 "\nImport 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" 1594 "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n" 1595 "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n" 1596 "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n", 1597 { 1598 {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported", 1599 { 1600 {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", 1601 { 1602 {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."}, 1603 {"active", RPCArg::Type::BOOL, RPCArg::Default{false}, "Set this descriptor to be the active descriptor for the corresponding output type/externality"}, 1604 {"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"}, 1605 {"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"}, 1606 {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n" 1607 "Use the string \"now\" to substitute the current synced blockchain time.\n" 1608 "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n" 1609 "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n" 1610 "of all descriptors being imported will be scanned as well as the mempool.", 1611 RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}} 1612 }, 1613 {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"}, 1614 {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"}, 1615 }, 1616 }, 1617 }, 1618 RPCArgOptions{.oneline_description="requests"}}, 1619 }, 1620 RPCResult{ 1621 RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result", 1622 { 1623 {RPCResult::Type::OBJ, "", "", 1624 { 1625 {RPCResult::Type::BOOL, "success", ""}, 1626 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "", 1627 { 1628 {RPCResult::Type::STR, "", ""}, 1629 }}, 1630 {RPCResult::Type::OBJ, "error", /*optional=*/true, "", 1631 { 1632 {RPCResult::Type::ELISION, "", "JSONRPC error"}, 1633 }}, 1634 }}, 1635 } 1636 }, 1637 RPCExamples{ 1638 HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, " 1639 "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + 1640 HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'") 1641 }, 1642 [&](const RPCHelpMan& self, const JSONRPCRequest& main_request) -> UniValue 1643 { 1644 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request); 1645 if (!pwallet) return UniValue::VNULL; 1646 CWallet& wallet{*pwallet}; 1647 1648 // Make sure the results are valid at least up to the most recent block 1649 // the user could have gotten from another RPC command prior to now 1650 wallet.BlockUntilSyncedToCurrentChain(); 1651 1652 // Make sure wallet is a descriptor wallet 1653 if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { 1654 throw JSONRPCError(RPC_WALLET_ERROR, "importdescriptors is not available for non-descriptor wallets"); 1655 } 1656 1657 WalletRescanReserver reserver(*pwallet); 1658 if (!reserver.reserve(/*with_passphrase=*/true)) { 1659 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); 1660 } 1661 1662 // Ensure that the wallet is not locked for the remainder of this RPC, as 1663 // the passphrase is used to top up the keypool. 1664 LOCK(pwallet->m_relock_mutex); 1665 1666 const UniValue& requests = main_request.params[0]; 1667 const int64_t minimum_timestamp = 1; 1668 int64_t now = 0; 1669 int64_t lowest_timestamp = 0; 1670 bool rescan = false; 1671 UniValue response(UniValue::VARR); 1672 { 1673 LOCK(pwallet->cs_wallet); 1674 EnsureWalletIsUnlocked(*pwallet); 1675 1676 CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now))); 1677 1678 // Get all timestamps and extract the lowest timestamp 1679 for (const UniValue& request : requests.getValues()) { 1680 // This throws an error if "timestamp" doesn't exist 1681 const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp); 1682 const UniValue result = ProcessDescriptorImport(*pwallet, request, timestamp); 1683 response.push_back(result); 1684 1685 if (lowest_timestamp > timestamp ) { 1686 lowest_timestamp = timestamp; 1687 } 1688 1689 // If we know the chain tip, and at least one request was successful then allow rescan 1690 if (!rescan && result["success"].get_bool()) { 1691 rescan = true; 1692 } 1693 } 1694 pwallet->ConnectScriptPubKeyManNotifiers(); 1695 } 1696 1697 // Rescan the blockchain using the lowest timestamp 1698 if (rescan) { 1699 int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver, /*update=*/true); 1700 pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true); 1701 1702 if (pwallet->IsAbortingRescan()) { 1703 throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); 1704 } 1705 1706 if (scanned_time > lowest_timestamp) { 1707 std::vector<UniValue> results = response.getValues(); 1708 response.clear(); 1709 response.setArray(); 1710 1711 // Compose the response 1712 for (unsigned int i = 0; i < requests.size(); ++i) { 1713 const UniValue& request = requests.getValues().at(i); 1714 1715 // If the descriptor timestamp is within the successfully scanned 1716 // range, or if the import result already has an error set, let 1717 // the result stand unmodified. Otherwise replace the result 1718 // with an error message. 1719 if (scanned_time <= GetImportTimestamp(request, now) || results.at(i).exists("error")) { 1720 response.push_back(results.at(i)); 1721 } else { 1722 UniValue result = UniValue(UniValue::VOBJ); 1723 result.pushKV("success", UniValue(false)); 1724 result.pushKV( 1725 "error", 1726 JSONRPCError( 1727 RPC_MISC_ERROR, 1728 strprintf("Rescan failed for descriptor with timestamp %d. There was an error reading a " 1729 "block from time %d, which is after or within %d seconds of key creation, and " 1730 "could contain transactions pertaining to the desc. As a result, transactions " 1731 "and coins using this desc may not appear in the wallet. This error could be " 1732 "caused by pruning or data corruption (see bitcoind log for details) and could " 1733 "be dealt with by downloading and rescanning the relevant blocks (see -reindex " 1734 "option and rescanblockchain RPC).", 1735 GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW))); 1736 response.push_back(std::move(result)); 1737 } 1738 } 1739 } 1740 } 1741 1742 return response; 1743 }, 1744 }; 1745 } 1746 1747 RPCHelpMan listdescriptors() 1748 { 1749 return RPCHelpMan{ 1750 "listdescriptors", 1751 "\nList descriptors imported into a descriptor-enabled wallet.\n", 1752 { 1753 {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private descriptors."} 1754 }, 1755 RPCResult{RPCResult::Type::OBJ, "", "", { 1756 {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"}, 1757 {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)", 1758 { 1759 {RPCResult::Type::OBJ, "", "", { 1760 {RPCResult::Type::STR, "desc", "Descriptor string representation"}, 1761 {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"}, 1762 {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"}, 1763 {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"}, 1764 {RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", { 1765 {RPCResult::Type::NUM, "", "Range start inclusive"}, 1766 {RPCResult::Type::NUM, "", "Range end inclusive"}, 1767 }}, 1768 {RPCResult::Type::NUM, "next", /*optional=*/true, "Same as next_index field. Kept for compatibility reason."}, 1769 {RPCResult::Type::NUM, "next_index", /*optional=*/true, "The next index to generate addresses from; defined only for ranged descriptors"}, 1770 }}, 1771 }} 1772 }}, 1773 RPCExamples{ 1774 HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "") 1775 + HelpExampleCli("listdescriptors", "true") + HelpExampleRpc("listdescriptors", "true") 1776 }, 1777 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 1778 { 1779 const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request); 1780 if (!wallet) return UniValue::VNULL; 1781 1782 if (!wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { 1783 throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets"); 1784 } 1785 1786 const bool priv = !request.params[0].isNull() && request.params[0].get_bool(); 1787 if (priv) { 1788 EnsureWalletIsUnlocked(*wallet); 1789 } 1790 1791 LOCK(wallet->cs_wallet); 1792 1793 const auto active_spk_mans = wallet->GetActiveScriptPubKeyMans(); 1794 1795 struct WalletDescInfo { 1796 std::string descriptor; 1797 uint64_t creation_time; 1798 bool active; 1799 std::optional<bool> internal; 1800 std::optional<std::pair<int64_t,int64_t>> range; 1801 int64_t next_index; 1802 }; 1803 1804 std::vector<WalletDescInfo> wallet_descriptors; 1805 for (const auto& spk_man : wallet->GetAllScriptPubKeyMans()) { 1806 const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man); 1807 if (!desc_spk_man) { 1808 throw JSONRPCError(RPC_WALLET_ERROR, "Unexpected ScriptPubKey manager type."); 1809 } 1810 LOCK(desc_spk_man->cs_desc_man); 1811 const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor(); 1812 std::string descriptor; 1813 if (!desc_spk_man->GetDescriptorString(descriptor, priv)) { 1814 throw JSONRPCError(RPC_WALLET_ERROR, "Can't get descriptor string."); 1815 } 1816 const bool is_range = wallet_descriptor.descriptor->IsRange(); 1817 wallet_descriptors.push_back({ 1818 descriptor, 1819 wallet_descriptor.creation_time, 1820 active_spk_mans.count(desc_spk_man) != 0, 1821 wallet->IsInternalScriptPubKeyMan(desc_spk_man), 1822 is_range ? std::optional(std::make_pair(wallet_descriptor.range_start, wallet_descriptor.range_end)) : std::nullopt, 1823 wallet_descriptor.next_index 1824 }); 1825 } 1826 1827 std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) { 1828 return a.descriptor < b.descriptor; 1829 }); 1830 1831 UniValue descriptors(UniValue::VARR); 1832 for (const WalletDescInfo& info : wallet_descriptors) { 1833 UniValue spk(UniValue::VOBJ); 1834 spk.pushKV("desc", info.descriptor); 1835 spk.pushKV("timestamp", info.creation_time); 1836 spk.pushKV("active", info.active); 1837 if (info.internal.has_value()) { 1838 spk.pushKV("internal", info.internal.value()); 1839 } 1840 if (info.range.has_value()) { 1841 UniValue range(UniValue::VARR); 1842 range.push_back(info.range->first); 1843 range.push_back(info.range->second - 1); 1844 spk.pushKV("range", range); 1845 spk.pushKV("next", info.next_index); 1846 spk.pushKV("next_index", info.next_index); 1847 } 1848 descriptors.push_back(spk); 1849 } 1850 1851 UniValue response(UniValue::VOBJ); 1852 response.pushKV("wallet_name", wallet->GetName()); 1853 response.pushKV("descriptors", descriptors); 1854 1855 return response; 1856 }, 1857 }; 1858 } 1859 1860 RPCHelpMan backupwallet() 1861 { 1862 return RPCHelpMan{"backupwallet", 1863 "\nSafely copies the current wallet file to the specified destination, which can either be a directory or a path with a filename.\n", 1864 { 1865 {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"}, 1866 }, 1867 RPCResult{RPCResult::Type::NONE, "", ""}, 1868 RPCExamples{ 1869 HelpExampleCli("backupwallet", "\"backup.dat\"") 1870 + HelpExampleRpc("backupwallet", "\"backup.dat\"") 1871 }, 1872 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 1873 { 1874 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); 1875 if (!pwallet) return UniValue::VNULL; 1876 1877 // Make sure the results are valid at least up to the most recent block 1878 // the user could have gotten from another RPC command prior to now 1879 pwallet->BlockUntilSyncedToCurrentChain(); 1880 1881 LOCK(pwallet->cs_wallet); 1882 1883 std::string strDest = request.params[0].get_str(); 1884 if (!pwallet->BackupWallet(strDest)) { 1885 throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!"); 1886 } 1887 1888 return UniValue::VNULL; 1889 }, 1890 }; 1891 } 1892 1893 1894 RPCHelpMan restorewallet() 1895 { 1896 return RPCHelpMan{ 1897 "restorewallet", 1898 "\nRestores and loads a wallet from backup.\n" 1899 "\nThe rescan is significantly faster if a descriptor wallet is restored" 1900 "\nand block filters are available (using startup option \"-blockfilterindex=1\").\n", 1901 { 1902 {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"}, 1903 {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."}, 1904 {"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."}, 1905 }, 1906 RPCResult{ 1907 RPCResult::Type::OBJ, "", "", 1908 { 1909 {RPCResult::Type::STR, "name", "The wallet name if restored successfully."}, 1910 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to restoring and loading the wallet.", 1911 { 1912 {RPCResult::Type::STR, "", ""}, 1913 }}, 1914 } 1915 }, 1916 RPCExamples{ 1917 HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"") 1918 + HelpExampleRpc("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"") 1919 + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}}) 1920 + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}}) 1921 }, 1922 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 1923 { 1924 1925 WalletContext& context = EnsureWalletContext(request.context); 1926 1927 auto backup_file = fs::u8path(request.params[1].get_str()); 1928 1929 std::string wallet_name = request.params[0].get_str(); 1930 1931 std::optional<bool> load_on_start = request.params[2].isNull() ? std::nullopt : std::optional<bool>(request.params[2].get_bool()); 1932 1933 DatabaseStatus status; 1934 bilingual_str error; 1935 std::vector<bilingual_str> warnings; 1936 1937 const std::shared_ptr<CWallet> wallet = RestoreWallet(context, backup_file, wallet_name, load_on_start, status, error, warnings); 1938 1939 HandleWalletError(wallet, status, error); 1940 1941 UniValue obj(UniValue::VOBJ); 1942 obj.pushKV("name", wallet->GetName()); 1943 PushWarnings(warnings, obj); 1944 1945 return obj; 1946 1947 }, 1948 }; 1949 } 1950 } // namespace wallet