wallet.cpp
1 // Copyright (c) 2010 Satoshi Nakamoto 2 // Copyright (c) 2009-present The Bitcoin Core developers 3 // Distributed under the MIT software license, see the accompanying 4 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 6 #include <bitcoin-build-config.h> // IWYU pragma: keep 7 8 #include <core_io.h> 9 #include <key_io.h> 10 #include <rpc/server.h> 11 #include <rpc/util.h> 12 #include <univalue.h> 13 #include <util/translation.h> 14 #include <wallet/context.h> 15 #include <wallet/receive.h> 16 #include <wallet/rpc/util.h> 17 #include <wallet/rpc/wallet.h> 18 #include <wallet/wallet.h> 19 #include <wallet/walletutil.h> 20 21 #include <optional> 22 #include <string_view> 23 24 25 namespace wallet { 26 27 static const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS{ 28 {WALLET_FLAG_AVOID_REUSE, 29 "You need to rescan the blockchain in order to correctly mark used " 30 "destinations in the past. Until this is done, some destinations may " 31 "be considered unused, even if the opposite is the case."}, 32 }; 33 34 static RPCHelpMan getwalletinfo() 35 { 36 return RPCHelpMan{"getwalletinfo", 37 "Returns an object containing various wallet state info.\n", 38 {}, 39 RPCResult{ 40 RPCResult::Type::OBJ, "", "", 41 { 42 { 43 {RPCResult::Type::STR, "walletname", "the wallet name"}, 44 {RPCResult::Type::NUM, "walletversion", "(DEPRECATED) only related to unsupported legacy wallet, returns the latest version 169900 for backwards compatibility"}, 45 {RPCResult::Type::STR, "format", "the database format (only sqlite)"}, 46 {RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"}, 47 {RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"}, 48 {RPCResult::Type::NUM, "keypoolsize_hd_internal", /*optional=*/true, "how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)"}, 49 {RPCResult::Type::NUM_TIME, "unlocked_until", /*optional=*/true, "the " + UNIX_EPOCH_TIME + " until which the wallet is unlocked for transfers, or 0 if the wallet is locked (only present for passphrase-encrypted wallets)"}, 50 {RPCResult::Type::STR_AMOUNT, "paytxfee", "the transaction fee configuration, set in " + CURRENCY_UNIT + "/kvB"}, 51 {RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"}, 52 {RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"}, 53 {RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress", 54 { 55 {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"}, 56 {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"}, 57 }, /*skip_type_check=*/true}, 58 {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for output script management"}, 59 {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"}, 60 {RPCResult::Type::BOOL, "blank", "Whether this wallet intentionally does not contain any keys, scripts, or descriptors"}, 61 {RPCResult::Type::NUM_TIME, "birthtime", /*optional=*/true, "The start time for blocks scanning. It could be modified by (re)importing any descriptor with an earlier timestamp."}, 62 {RPCResult::Type::ARR, "flags", "The flags currently set on the wallet", 63 { 64 {RPCResult::Type::STR, "flag", "The name of the flag"}, 65 }}, 66 RESULT_LAST_PROCESSED_BLOCK, 67 }}, 68 }, 69 RPCExamples{ 70 HelpExampleCli("getwalletinfo", "") 71 + HelpExampleRpc("getwalletinfo", "") 72 }, 73 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 74 { 75 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); 76 if (!pwallet) return UniValue::VNULL; 77 78 // Make sure the results are valid at least up to the most recent block 79 // the user could have gotten from another RPC command prior to now 80 pwallet->BlockUntilSyncedToCurrentChain(); 81 82 LOCK(pwallet->cs_wallet); 83 84 UniValue obj(UniValue::VOBJ); 85 86 const int latest_legacy_wallet_minversion{169900}; 87 88 size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); 89 obj.pushKV("walletname", pwallet->GetName()); 90 obj.pushKV("walletversion", latest_legacy_wallet_minversion); 91 obj.pushKV("format", pwallet->GetDatabase().Format()); 92 obj.pushKV("txcount", (int)pwallet->mapWallet.size()); 93 obj.pushKV("keypoolsize", (int64_t)kpExternalSize); 94 obj.pushKV("keypoolsize_hd_internal", pwallet->GetKeyPoolSize() - kpExternalSize); 95 96 if (pwallet->IsCrypted()) { 97 obj.pushKV("unlocked_until", pwallet->nRelockTime); 98 } 99 obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK())); 100 obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); 101 obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)); 102 if (pwallet->IsScanning()) { 103 UniValue scanning(UniValue::VOBJ); 104 scanning.pushKV("duration", Ticks<std::chrono::seconds>(pwallet->ScanningDuration())); 105 scanning.pushKV("progress", pwallet->ScanningProgress()); 106 obj.pushKV("scanning", std::move(scanning)); 107 } else { 108 obj.pushKV("scanning", false); 109 } 110 obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); 111 obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)); 112 obj.pushKV("blank", pwallet->IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)); 113 if (int64_t birthtime = pwallet->GetBirthTime(); birthtime != UNKNOWN_TIME) { 114 obj.pushKV("birthtime", birthtime); 115 } 116 117 // Push known flags 118 UniValue flags(UniValue::VARR); 119 uint64_t wallet_flags = pwallet->GetWalletFlags(); 120 for (uint64_t i = 0; i < 64; ++i) { 121 uint64_t flag = uint64_t{1} << i; 122 if (flag & wallet_flags) { 123 if (flag & KNOWN_WALLET_FLAGS) { 124 flags.push_back(WALLET_FLAG_TO_STRING.at(WalletFlags{flag})); 125 } else { 126 flags.push_back(strprintf("unknown_flag_%u", i)); 127 } 128 } 129 } 130 obj.pushKV("flags", flags); 131 132 AppendLastProcessedBlock(obj, *pwallet); 133 return obj; 134 }, 135 }; 136 } 137 138 static RPCHelpMan listwalletdir() 139 { 140 return RPCHelpMan{"listwalletdir", 141 "Returns a list of wallets in the wallet directory.\n", 142 {}, 143 RPCResult{ 144 RPCResult::Type::OBJ, "", "", 145 { 146 {RPCResult::Type::ARR, "wallets", "", 147 { 148 {RPCResult::Type::OBJ, "", "", 149 { 150 {RPCResult::Type::STR, "name", "The wallet name"}, 151 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.", 152 { 153 {RPCResult::Type::STR, "", ""}, 154 }}, 155 }}, 156 }}, 157 } 158 }, 159 RPCExamples{ 160 HelpExampleCli("listwalletdir", "") 161 + HelpExampleRpc("listwalletdir", "") 162 }, 163 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 164 { 165 UniValue wallets(UniValue::VARR); 166 for (const auto& [path, db_type] : ListDatabases(GetWalletDir())) { 167 UniValue wallet(UniValue::VOBJ); 168 wallet.pushKV("name", path.utf8string()); 169 UniValue warnings(UniValue::VARR); 170 if (db_type == "bdb") { 171 warnings.push_back("This wallet is a legacy wallet and will need to be migrated with migratewallet before it can be loaded"); 172 } 173 wallet.pushKV("warnings", warnings); 174 wallets.push_back(std::move(wallet)); 175 } 176 177 UniValue result(UniValue::VOBJ); 178 result.pushKV("wallets", std::move(wallets)); 179 return result; 180 }, 181 }; 182 } 183 184 static RPCHelpMan listwallets() 185 { 186 return RPCHelpMan{"listwallets", 187 "Returns a list of currently loaded wallets.\n" 188 "For full information on the wallet, use \"getwalletinfo\"\n", 189 {}, 190 RPCResult{ 191 RPCResult::Type::ARR, "", "", 192 { 193 {RPCResult::Type::STR, "walletname", "the wallet name"}, 194 } 195 }, 196 RPCExamples{ 197 HelpExampleCli("listwallets", "") 198 + HelpExampleRpc("listwallets", "") 199 }, 200 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 201 { 202 UniValue obj(UniValue::VARR); 203 204 WalletContext& context = EnsureWalletContext(request.context); 205 for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) { 206 LOCK(wallet->cs_wallet); 207 obj.push_back(wallet->GetName()); 208 } 209 210 return obj; 211 }, 212 }; 213 } 214 215 static RPCHelpMan loadwallet() 216 { 217 return RPCHelpMan{ 218 "loadwallet", 219 "Loads a wallet from a wallet file or directory." 220 "\nNote that all wallet command-line options used when starting bitcoind will be" 221 "\napplied to the new wallet.\n", 222 { 223 {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The path to the directory of the wallet to be loaded, either absolute or relative to the \"wallets\" directory. The \"wallets\" directory is set by the -walletdir option and defaults to the \"wallets\" folder within the data directory."}, 224 {"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."}, 225 }, 226 RPCResult{ 227 RPCResult::Type::OBJ, "", "", 228 { 229 {RPCResult::Type::STR, "name", "The wallet name if loaded successfully."}, 230 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to loading the wallet.", 231 { 232 {RPCResult::Type::STR, "", ""}, 233 }}, 234 } 235 }, 236 RPCExamples{ 237 "\nLoad wallet from the wallet dir:\n" 238 + HelpExampleCli("loadwallet", "\"walletname\"") 239 + HelpExampleRpc("loadwallet", "\"walletname\"") 240 + "\nLoad wallet using absolute path (Unix):\n" 241 + HelpExampleCli("loadwallet", "\"/path/to/walletname/\"") 242 + HelpExampleRpc("loadwallet", "\"/path/to/walletname/\"") 243 + "\nLoad wallet using absolute path (Windows):\n" 244 + HelpExampleCli("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"") 245 + HelpExampleRpc("loadwallet", "\"DriveLetter:\\path\\to\\walletname\\\"") 246 }, 247 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 248 { 249 WalletContext& context = EnsureWalletContext(request.context); 250 const std::string name(request.params[0].get_str()); 251 252 DatabaseOptions options; 253 DatabaseStatus status; 254 ReadDatabaseArgs(*context.args, options); 255 options.require_existing = true; 256 bilingual_str error; 257 std::vector<bilingual_str> warnings; 258 std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool()); 259 260 { 261 LOCK(context.wallets_mutex); 262 if (std::any_of(context.wallets.begin(), context.wallets.end(), [&name](const auto& wallet) { return wallet->GetName() == name; })) { 263 throw JSONRPCError(RPC_WALLET_ALREADY_LOADED, "Wallet \"" + name + "\" is already loaded."); 264 } 265 } 266 267 std::shared_ptr<CWallet> const wallet = LoadWallet(context, name, load_on_start, options, status, error, warnings); 268 269 HandleWalletError(wallet, status, error); 270 271 UniValue obj(UniValue::VOBJ); 272 obj.pushKV("name", wallet->GetName()); 273 PushWarnings(warnings, obj); 274 275 return obj; 276 }, 277 }; 278 } 279 280 static RPCHelpMan setwalletflag() 281 { 282 std::string flags; 283 for (auto& it : STRING_TO_WALLET_FLAG) 284 if (it.second & MUTABLE_WALLET_FLAGS) 285 flags += (flags == "" ? "" : ", ") + it.first; 286 287 return RPCHelpMan{ 288 "setwalletflag", 289 "Change the state of the given wallet flag for a wallet.\n", 290 { 291 {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags}, 292 {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."}, 293 }, 294 RPCResult{ 295 RPCResult::Type::OBJ, "", "", 296 { 297 {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"}, 298 {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"}, 299 {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"}, 300 } 301 }, 302 RPCExamples{ 303 HelpExampleCli("setwalletflag", "avoid_reuse") 304 + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"") 305 }, 306 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 307 { 308 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 309 if (!pwallet) return UniValue::VNULL; 310 311 std::string flag_str = request.params[0].get_str(); 312 bool value = request.params[1].isNull() || request.params[1].get_bool(); 313 314 if (!STRING_TO_WALLET_FLAG.count(flag_str)) { 315 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unknown wallet flag: %s", flag_str)); 316 } 317 318 auto flag = STRING_TO_WALLET_FLAG.at(flag_str); 319 320 if (!(flag & MUTABLE_WALLET_FLAGS)) { 321 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is immutable: %s", flag_str)); 322 } 323 324 UniValue res(UniValue::VOBJ); 325 326 if (pwallet->IsWalletFlagSet(flag) == value) { 327 throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is already set to %s: %s", value ? "true" : "false", flag_str)); 328 } 329 330 res.pushKV("flag_name", flag_str); 331 res.pushKV("flag_state", value); 332 333 if (value) { 334 pwallet->SetWalletFlag(flag); 335 } else { 336 pwallet->UnsetWalletFlag(flag); 337 } 338 339 if (flag && value && WALLET_FLAG_CAVEATS.count(flag)) { 340 res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag)); 341 } 342 343 return res; 344 }, 345 }; 346 } 347 348 static RPCHelpMan createwallet() 349 { 350 return RPCHelpMan{ 351 "createwallet", 352 "Creates and loads a new wallet.\n", 353 { 354 {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name for the new wallet. If this is a path, the wallet will be created at the path location."}, 355 {"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false}, "Disable the possibility of private keys (only watchonlys are possible in this mode)."}, 356 {"blank", RPCArg::Type::BOOL, RPCArg::Default{false}, "Create a blank wallet. A blank wallet has no keys."}, 357 {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."}, 358 {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{false}, "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."}, 359 {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "If set, must be \"true\""}, 360 {"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."}, 361 {"external_signer", RPCArg::Type::BOOL, RPCArg::Default{false}, "Use an external signer such as a hardware wallet. Requires -signer to be configured. Wallet creation will fail if keys cannot be fetched. Requires disable_private_keys and descriptors set to true."}, 362 }, 363 RPCResult{ 364 RPCResult::Type::OBJ, "", "", 365 { 366 {RPCResult::Type::STR, "name", "The wallet name if created successfully. If the wallet was created using a full path, the wallet_name will be the full path."}, 367 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to creating and loading the wallet.", 368 { 369 {RPCResult::Type::STR, "", ""}, 370 }}, 371 } 372 }, 373 RPCExamples{ 374 HelpExampleCli("createwallet", "\"testwallet\"") 375 + HelpExampleRpc("createwallet", "\"testwallet\"") 376 + HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"load_on_startup", true}}) 377 + HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"load_on_startup", true}}) 378 }, 379 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 380 { 381 WalletContext& context = EnsureWalletContext(request.context); 382 uint64_t flags = 0; 383 if (!request.params[1].isNull() && request.params[1].get_bool()) { 384 flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; 385 } 386 387 if (!request.params[2].isNull() && request.params[2].get_bool()) { 388 flags |= WALLET_FLAG_BLANK_WALLET; 389 } 390 SecureString passphrase; 391 passphrase.reserve(100); 392 std::vector<bilingual_str> warnings; 393 if (!request.params[3].isNull()) { 394 passphrase = std::string_view{request.params[3].get_str()}; 395 if (passphrase.empty()) { 396 // Empty string means unencrypted 397 warnings.emplace_back(Untranslated("Empty string given as passphrase, wallet will not be encrypted.")); 398 } 399 } 400 401 if (!request.params[4].isNull() && request.params[4].get_bool()) { 402 flags |= WALLET_FLAG_AVOID_REUSE; 403 } 404 flags |= WALLET_FLAG_DESCRIPTORS; 405 if (!self.Arg<bool>("descriptors")) { 406 throw JSONRPCError(RPC_WALLET_ERROR, "descriptors argument must be set to \"true\"; it is no longer possible to create a legacy wallet."); 407 } 408 if (!request.params[7].isNull() && request.params[7].get_bool()) { 409 #ifdef ENABLE_EXTERNAL_SIGNER 410 flags |= WALLET_FLAG_EXTERNAL_SIGNER; 411 #else 412 throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without external signing support (required for external signing)"); 413 #endif 414 } 415 416 DatabaseOptions options; 417 DatabaseStatus status; 418 ReadDatabaseArgs(*context.args, options); 419 options.require_create = true; 420 options.create_flags = flags; 421 options.create_passphrase = passphrase; 422 bilingual_str error; 423 std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool()); 424 const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings); 425 if (!wallet) { 426 RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT ? RPC_WALLET_ENCRYPTION_FAILED : RPC_WALLET_ERROR; 427 throw JSONRPCError(code, error.original); 428 } 429 430 UniValue obj(UniValue::VOBJ); 431 obj.pushKV("name", wallet->GetName()); 432 PushWarnings(warnings, obj); 433 434 return obj; 435 }, 436 }; 437 } 438 439 static RPCHelpMan unloadwallet() 440 { 441 return RPCHelpMan{"unloadwallet", 442 "Unloads the wallet referenced by the request endpoint or the wallet_name argument.\n" 443 "If both are specified, they must be identical.", 444 { 445 {"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to unload. If provided both here and in the RPC endpoint, the two must be identical."}, 446 {"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."}, 447 }, 448 RPCResult{RPCResult::Type::OBJ, "", "", { 449 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to unloading the wallet.", 450 { 451 {RPCResult::Type::STR, "", ""}, 452 }}, 453 }}, 454 RPCExamples{ 455 HelpExampleCli("unloadwallet", "wallet_name") 456 + HelpExampleRpc("unloadwallet", "wallet_name") 457 }, 458 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 459 { 460 const std::string wallet_name{EnsureUniqueWalletName(request, self.MaybeArg<std::string_view>("wallet_name"))}; 461 462 WalletContext& context = EnsureWalletContext(request.context); 463 std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name); 464 if (!wallet) { 465 throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); 466 } 467 468 std::vector<bilingual_str> warnings; 469 { 470 WalletRescanReserver reserver(*wallet); 471 if (!reserver.reserve()) { 472 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); 473 } 474 475 // Release the "main" shared pointer and prevent further notifications. 476 // Note that any attempt to load the same wallet would fail until the wallet 477 // is destroyed (see CheckUniqueFileid). 478 std::optional<bool> load_on_start{self.MaybeArg<bool>("load_on_startup")}; 479 if (!RemoveWallet(context, wallet, load_on_start, warnings)) { 480 throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded"); 481 } 482 } 483 484 WaitForDeleteWallet(std::move(wallet)); 485 486 UniValue result(UniValue::VOBJ); 487 PushWarnings(warnings, result); 488 489 return result; 490 }, 491 }; 492 } 493 494 RPCHelpMan simulaterawtransaction() 495 { 496 return RPCHelpMan{ 497 "simulaterawtransaction", 498 "Calculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n", 499 { 500 {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of hex strings of raw transactions.\n", 501 { 502 {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""}, 503 }, 504 }, 505 {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", 506 { 507 {"include_watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "(DEPRECATED) No longer used"}, 508 }, 509 }, 510 }, 511 RPCResult{ 512 RPCResult::Type::OBJ, "", "", 513 { 514 {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."}, 515 } 516 }, 517 RPCExamples{ 518 HelpExampleCli("simulaterawtransaction", "[\"myhex\"]") 519 + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]") 520 }, 521 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 522 { 523 const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request); 524 if (!rpc_wallet) return UniValue::VNULL; 525 const CWallet& wallet = *rpc_wallet; 526 527 LOCK(wallet.cs_wallet); 528 529 const auto& txs = request.params[0].get_array(); 530 CAmount changes{0}; 531 std::map<COutPoint, CAmount> new_utxos; // UTXO:s that were made available in transaction array 532 std::set<COutPoint> spent; 533 534 for (size_t i = 0; i < txs.size(); ++i) { 535 CMutableTransaction mtx; 536 if (!DecodeHexTx(mtx, txs[i].get_str(), /* try_no_witness */ true, /* try_witness */ true)) { 537 throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Transaction hex string decoding failure."); 538 } 539 540 // Fetch previous transactions (inputs) 541 std::map<COutPoint, Coin> coins; 542 for (const CTxIn& txin : mtx.vin) { 543 coins[txin.prevout]; // Create empty map entry keyed by prevout. 544 } 545 wallet.chain().findCoins(coins); 546 547 // Fetch debit; we are *spending* these; if the transaction is signed and 548 // broadcast, we will lose everything in these 549 for (const auto& txin : mtx.vin) { 550 const auto& outpoint = txin.prevout; 551 if (spent.count(outpoint)) { 552 throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction(s) are spending the same output more than once"); 553 } 554 if (new_utxos.count(outpoint)) { 555 changes -= new_utxos.at(outpoint); 556 new_utxos.erase(outpoint); 557 } else { 558 if (coins.at(outpoint).IsSpent()) { 559 throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more transaction inputs are missing or have been spent already"); 560 } 561 changes -= wallet.GetDebit(txin); 562 } 563 spent.insert(outpoint); 564 } 565 566 // Iterate over outputs; we are *receiving* these, if the wallet considers 567 // them "mine"; if the transaction is signed and broadcast, we will receive 568 // everything in these 569 // Also populate new_utxos in case these are spent in later transactions 570 571 const auto& hash = mtx.GetHash(); 572 for (size_t i = 0; i < mtx.vout.size(); ++i) { 573 const auto& txout = mtx.vout[i]; 574 bool is_mine = wallet.IsMine(txout); 575 changes += new_utxos[COutPoint(hash, i)] = is_mine ? txout.nValue : 0; 576 } 577 } 578 579 UniValue result(UniValue::VOBJ); 580 result.pushKV("balance_change", ValueFromAmount(changes)); 581 582 return result; 583 } 584 }; 585 } 586 587 static RPCHelpMan migratewallet() 588 { 589 return RPCHelpMan{ 590 "migratewallet", 591 "Migrate the wallet to a descriptor wallet.\n" 592 "A new wallet backup will need to be made.\n" 593 "\nThe migration process will create a backup of the wallet before migrating. This backup\n" 594 "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n" 595 "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet." 596 "\nEncrypted wallets must have the passphrase provided as an argument to this call.\n" 597 "\nThis RPC may take a long time to complete. Increasing the RPC client timeout is recommended.", 598 { 599 {"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to migrate. If provided both here and in the RPC endpoint, the two must be identical."}, 600 {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"}, 601 }, 602 RPCResult{ 603 RPCResult::Type::OBJ, "", "", 604 { 605 {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"}, 606 {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"}, 607 {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"}, 608 {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"}, 609 } 610 }, 611 RPCExamples{ 612 HelpExampleCli("migratewallet", "") 613 + HelpExampleRpc("migratewallet", "") 614 }, 615 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 616 { 617 const std::string wallet_name{EnsureUniqueWalletName(request, self.MaybeArg<std::string_view>("wallet_name"))}; 618 619 SecureString wallet_pass; 620 wallet_pass.reserve(100); 621 if (!request.params[1].isNull()) { 622 wallet_pass = std::string_view{request.params[1].get_str()}; 623 } 624 625 WalletContext& context = EnsureWalletContext(request.context); 626 util::Result<MigrationResult> res = MigrateLegacyToDescriptor(wallet_name, wallet_pass, context); 627 if (!res) { 628 throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original); 629 } 630 631 UniValue r{UniValue::VOBJ}; 632 r.pushKV("wallet_name", res->wallet_name); 633 if (res->watchonly_wallet) { 634 r.pushKV("watchonly_name", res->watchonly_wallet->GetName()); 635 } 636 if (res->solvables_wallet) { 637 r.pushKV("solvables_name", res->solvables_wallet->GetName()); 638 } 639 r.pushKV("backup_path", res->backup_path.utf8string()); 640 641 return r; 642 }, 643 }; 644 } 645 646 RPCHelpMan gethdkeys() 647 { 648 return RPCHelpMan{ 649 "gethdkeys", 650 "List all BIP 32 HD keys in the wallet and which descriptors use them.\n", 651 { 652 {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", { 653 {"active_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show the keys for only active descriptors"}, 654 {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private keys"} 655 }}, 656 }, 657 RPCResult{RPCResult::Type::ARR, "", "", { 658 { 659 {RPCResult::Type::OBJ, "", "", { 660 {RPCResult::Type::STR, "xpub", "The extended public key"}, 661 {RPCResult::Type::BOOL, "has_private", "Whether the wallet has the private key for this xpub"}, 662 {RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"}, 663 {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects that use this HD key", 664 { 665 {RPCResult::Type::OBJ, "", "", { 666 {RPCResult::Type::STR, "desc", "Descriptor string representation"}, 667 {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"}, 668 }}, 669 }}, 670 }}, 671 } 672 }}, 673 RPCExamples{ 674 HelpExampleCli("gethdkeys", "") + HelpExampleRpc("gethdkeys", "") 675 + HelpExampleCliNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}}) + HelpExampleRpcNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}}) 676 }, 677 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 678 { 679 const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request); 680 if (!wallet) return UniValue::VNULL; 681 682 LOCK(wallet->cs_wallet); 683 684 UniValue options{request.params[0].isNull() ? UniValue::VOBJ : request.params[0]}; 685 const bool active_only{options.exists("active_only") ? options["active_only"].get_bool() : false}; 686 const bool priv{options.exists("private") ? options["private"].get_bool() : false}; 687 if (priv) { 688 EnsureWalletIsUnlocked(*wallet); 689 } 690 691 692 std::set<ScriptPubKeyMan*> spkms; 693 if (active_only) { 694 spkms = wallet->GetActiveScriptPubKeyMans(); 695 } else { 696 spkms = wallet->GetAllScriptPubKeyMans(); 697 } 698 699 std::map<CExtPubKey, std::set<std::tuple<std::string, bool, bool>>> wallet_xpubs; 700 std::map<CExtPubKey, CExtKey> wallet_xprvs; 701 for (auto* spkm : spkms) { 702 auto* desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(spkm)}; 703 CHECK_NONFATAL(desc_spkm); 704 LOCK(desc_spkm->cs_desc_man); 705 WalletDescriptor w_desc = desc_spkm->GetWalletDescriptor(); 706 707 // Retrieve the pubkeys from the descriptor 708 std::set<CPubKey> desc_pubkeys; 709 std::set<CExtPubKey> desc_xpubs; 710 w_desc.descriptor->GetPubKeys(desc_pubkeys, desc_xpubs); 711 for (const CExtPubKey& xpub : desc_xpubs) { 712 std::string desc_str; 713 bool ok = desc_spkm->GetDescriptorString(desc_str, false); 714 CHECK_NONFATAL(ok); 715 wallet_xpubs[xpub].emplace(desc_str, wallet->IsActiveScriptPubKeyMan(*spkm), desc_spkm->HasPrivKey(xpub.pubkey.GetID())); 716 if (std::optional<CKey> key = priv ? desc_spkm->GetKey(xpub.pubkey.GetID()) : std::nullopt) { 717 wallet_xprvs[xpub] = CExtKey(xpub, *key); 718 } 719 } 720 } 721 722 UniValue response(UniValue::VARR); 723 for (const auto& [xpub, descs] : wallet_xpubs) { 724 bool has_xprv = false; 725 UniValue descriptors(UniValue::VARR); 726 for (const auto& [desc, active, has_priv] : descs) { 727 UniValue d(UniValue::VOBJ); 728 d.pushKV("desc", desc); 729 d.pushKV("active", active); 730 has_xprv |= has_priv; 731 732 descriptors.push_back(std::move(d)); 733 } 734 UniValue xpub_info(UniValue::VOBJ); 735 xpub_info.pushKV("xpub", EncodeExtPubKey(xpub)); 736 xpub_info.pushKV("has_private", has_xprv); 737 if (priv) { 738 xpub_info.pushKV("xprv", EncodeExtKey(wallet_xprvs.at(xpub))); 739 } 740 xpub_info.pushKV("descriptors", std::move(descriptors)); 741 742 response.push_back(std::move(xpub_info)); 743 } 744 745 return response; 746 }, 747 }; 748 } 749 750 static RPCHelpMan createwalletdescriptor() 751 { 752 return RPCHelpMan{"createwalletdescriptor", 753 "Creates the wallet's descriptor for the given address type. " 754 "The address type must be one that the wallet does not already have a descriptor for." 755 + HELP_REQUIRING_PASSPHRASE, 756 { 757 {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are " + FormatAllOutputTypes() + "."}, 758 {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", { 759 {"internal", RPCArg::Type::BOOL, RPCArg::DefaultHint{"Both external and internal will be generated unless this parameter is specified"}, "Whether to only make one descriptor that is internal (if parameter is true) or external (if parameter is false)"}, 760 {"hdkey", RPCArg::Type::STR, RPCArg::DefaultHint{"The HD key used by all other active descriptors"}, "The HD key that the wallet knows the private key of, listed using 'gethdkeys', to use for this descriptor's key"}, 761 }}, 762 }, 763 RPCResult{ 764 RPCResult::Type::OBJ, "", "", 765 { 766 {RPCResult::Type::ARR, "descs", "The public descriptors that were added to the wallet", 767 {{RPCResult::Type::STR, "", ""}} 768 } 769 }, 770 }, 771 RPCExamples{ 772 HelpExampleCli("createwalletdescriptor", "bech32m") 773 + HelpExampleRpc("createwalletdescriptor", "bech32m") 774 }, 775 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue 776 { 777 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); 778 if (!pwallet) return UniValue::VNULL; 779 780 std::optional<OutputType> output_type = ParseOutputType(request.params[0].get_str()); 781 if (!output_type) { 782 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str())); 783 } 784 785 UniValue options{request.params[1].isNull() ? UniValue::VOBJ : request.params[1]}; 786 UniValue internal_only{options["internal"]}; 787 UniValue hdkey{options["hdkey"]}; 788 789 std::vector<bool> internals; 790 if (internal_only.isNull()) { 791 internals.push_back(false); 792 internals.push_back(true); 793 } else { 794 internals.push_back(internal_only.get_bool()); 795 } 796 797 LOCK(pwallet->cs_wallet); 798 EnsureWalletIsUnlocked(*pwallet); 799 800 CExtPubKey xpub; 801 if (hdkey.isNull()) { 802 std::set<CExtPubKey> active_xpubs = pwallet->GetActiveHDPubKeys(); 803 if (active_xpubs.size() != 1) { 804 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to determine which HD key to use from active descriptors. Please specify with 'hdkey'"); 805 } 806 xpub = *active_xpubs.begin(); 807 } else { 808 xpub = DecodeExtPubKey(hdkey.get_str()); 809 if (!xpub.pubkey.IsValid()) { 810 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to parse HD key. Please provide a valid xpub"); 811 } 812 } 813 814 std::optional<CKey> key = pwallet->GetKey(xpub.pubkey.GetID()); 815 if (!key) { 816 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Private key for %s is not known", EncodeExtPubKey(xpub))); 817 } 818 CExtKey active_hdkey(xpub, *key); 819 820 std::vector<std::reference_wrapper<DescriptorScriptPubKeyMan>> spkms; 821 WalletBatch batch{pwallet->GetDatabase()}; 822 for (bool internal : internals) { 823 WalletDescriptor w_desc = GenerateWalletDescriptor(xpub, *output_type, internal); 824 uint256 w_id = DescriptorID(*w_desc.descriptor); 825 if (!pwallet->GetScriptPubKeyMan(w_id)) { 826 spkms.emplace_back(pwallet->SetupDescriptorScriptPubKeyMan(batch, active_hdkey, *output_type, internal)); 827 } 828 } 829 if (spkms.empty()) { 830 throw JSONRPCError(RPC_WALLET_ERROR, "Descriptor already exists"); 831 } 832 833 // Fetch each descspkm from the wallet in order to get the descriptor strings 834 UniValue descs{UniValue::VARR}; 835 for (const auto& spkm : spkms) { 836 std::string desc_str; 837 bool ok = spkm.get().GetDescriptorString(desc_str, false); 838 CHECK_NONFATAL(ok); 839 descs.push_back(desc_str); 840 } 841 UniValue out{UniValue::VOBJ}; 842 out.pushKV("descs", std::move(descs)); 843 return out; 844 } 845 }; 846 } 847 848 // addresses 849 RPCHelpMan getaddressinfo(); 850 RPCHelpMan getnewaddress(); 851 RPCHelpMan getrawchangeaddress(); 852 RPCHelpMan setlabel(); 853 RPCHelpMan listaddressgroupings(); 854 RPCHelpMan keypoolrefill(); 855 RPCHelpMan getaddressesbylabel(); 856 RPCHelpMan listlabels(); 857 #ifdef ENABLE_EXTERNAL_SIGNER 858 RPCHelpMan walletdisplayaddress(); 859 #endif // ENABLE_EXTERNAL_SIGNER 860 861 // backup 862 RPCHelpMan importprunedfunds(); 863 RPCHelpMan removeprunedfunds(); 864 RPCHelpMan importdescriptors(); 865 RPCHelpMan listdescriptors(); 866 RPCHelpMan backupwallet(); 867 RPCHelpMan restorewallet(); 868 869 // coins 870 RPCHelpMan getreceivedbyaddress(); 871 RPCHelpMan getreceivedbylabel(); 872 RPCHelpMan getbalance(); 873 RPCHelpMan lockunspent(); 874 RPCHelpMan listlockunspent(); 875 RPCHelpMan getbalances(); 876 RPCHelpMan listunspent(); 877 878 // encryption 879 RPCHelpMan walletpassphrase(); 880 RPCHelpMan walletpassphrasechange(); 881 RPCHelpMan walletlock(); 882 RPCHelpMan encryptwallet(); 883 884 // spend 885 RPCHelpMan sendtoaddress(); 886 RPCHelpMan sendmany(); 887 RPCHelpMan settxfee(); 888 RPCHelpMan fundrawtransaction(); 889 RPCHelpMan bumpfee(); 890 RPCHelpMan psbtbumpfee(); 891 RPCHelpMan send(); 892 RPCHelpMan sendall(); 893 RPCHelpMan walletprocesspsbt(); 894 RPCHelpMan walletcreatefundedpsbt(); 895 RPCHelpMan signrawtransactionwithwallet(); 896 897 // signmessage 898 RPCHelpMan signmessage(); 899 900 // transactions 901 RPCHelpMan listreceivedbyaddress(); 902 RPCHelpMan listreceivedbylabel(); 903 RPCHelpMan listtransactions(); 904 RPCHelpMan listsinceblock(); 905 RPCHelpMan gettransaction(); 906 RPCHelpMan abandontransaction(); 907 RPCHelpMan rescanblockchain(); 908 RPCHelpMan abortrescan(); 909 910 std::span<const CRPCCommand> GetWalletRPCCommands() 911 { 912 static const CRPCCommand commands[]{ 913 {"rawtransactions", &fundrawtransaction}, 914 {"wallet", &abandontransaction}, 915 {"wallet", &abortrescan}, 916 {"wallet", &backupwallet}, 917 {"wallet", &bumpfee}, 918 {"wallet", &psbtbumpfee}, 919 {"wallet", &createwallet}, 920 {"wallet", &createwalletdescriptor}, 921 {"wallet", &restorewallet}, 922 {"wallet", &encryptwallet}, 923 {"wallet", &getaddressesbylabel}, 924 {"wallet", &getaddressinfo}, 925 {"wallet", &getbalance}, 926 {"wallet", &gethdkeys}, 927 {"wallet", &getnewaddress}, 928 {"wallet", &getrawchangeaddress}, 929 {"wallet", &getreceivedbyaddress}, 930 {"wallet", &getreceivedbylabel}, 931 {"wallet", &gettransaction}, 932 {"wallet", &getbalances}, 933 {"wallet", &getwalletinfo}, 934 {"wallet", &importdescriptors}, 935 {"wallet", &importprunedfunds}, 936 {"wallet", &keypoolrefill}, 937 {"wallet", &listaddressgroupings}, 938 {"wallet", &listdescriptors}, 939 {"wallet", &listlabels}, 940 {"wallet", &listlockunspent}, 941 {"wallet", &listreceivedbyaddress}, 942 {"wallet", &listreceivedbylabel}, 943 {"wallet", &listsinceblock}, 944 {"wallet", &listtransactions}, 945 {"wallet", &listunspent}, 946 {"wallet", &listwalletdir}, 947 {"wallet", &listwallets}, 948 {"wallet", &loadwallet}, 949 {"wallet", &lockunspent}, 950 {"wallet", &migratewallet}, 951 {"wallet", &removeprunedfunds}, 952 {"wallet", &rescanblockchain}, 953 {"wallet", &send}, 954 {"wallet", &sendmany}, 955 {"wallet", &sendtoaddress}, 956 {"wallet", &setlabel}, 957 {"wallet", &settxfee}, 958 {"wallet", &setwalletflag}, 959 {"wallet", &signmessage}, 960 {"wallet", &signrawtransactionwithwallet}, 961 {"wallet", &simulaterawtransaction}, 962 {"wallet", &sendall}, 963 {"wallet", &unloadwallet}, 964 {"wallet", &walletcreatefundedpsbt}, 965 #ifdef ENABLE_EXTERNAL_SIGNER 966 {"wallet", &walletdisplayaddress}, 967 #endif // ENABLE_EXTERNAL_SIGNER 968 {"wallet", &walletlock}, 969 {"wallet", &walletpassphrase}, 970 {"wallet", &walletpassphrasechange}, 971 {"wallet", &walletprocesspsbt}, 972 }; 973 return commands; 974 } 975 } // namespace wallet