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