diff options
author | Samuel Dobson <dobsonsa68@gmail.com> | 2021-12-01 16:43:31 +1300 |
---|---|---|
committer | Samuel Dobson <dobsonsa68@gmail.com> | 2021-12-08 11:45:21 +1300 |
commit | e116b9747d083bf269f1e1c67295b57d700d9dbd (patch) | |
tree | 1d6e33a6f0e87df62c6d43ad73f51097b86d3825 /src/wallet/rpc/wallet.cpp | |
parent | 8e30875fde99f5c03785fd5e1af929b194b3ffcf (diff) |
MOVEONLY: Move rpcwallet to rpc/wallet
Diffstat (limited to 'src/wallet/rpc/wallet.cpp')
-rw-r--r-- | src/wallet/rpc/wallet.cpp | 736 |
1 files changed, 736 insertions, 0 deletions
diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp new file mode 100644 index 0000000000..b0b586c41c --- /dev/null +++ b/src/wallet/rpc/wallet.cpp @@ -0,0 +1,736 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <consensus/amount.h> +#include <core_io.h> +#include <interfaces/chain.h> +#include <key_io.h> +#include <node/context.h> +#include <outputtype.h> +#include <policy/feerate.h> +#include <policy/fees.h> +#include <policy/rbf.h> +#include <rpc/server.h> +#include <rpc/util.h> +#include <script/descriptor.h> +#include <script/sign.h> +#include <util/string.h> +#include <util/system.h> +#include <util/translation.h> +#include <wallet/coincontrol.h> +#include <wallet/load.h> +#include <wallet/receive.h> +#include <wallet/rpc/wallet.h> +#include <wallet/rpc/util.h> +#include <wallet/wallet.h> +#include <wallet/walletdb.h> +#include <wallet/walletutil.h> + +#include <optional> +#include <stdint.h> + +#include <univalue.h> + +#include <map> + + +/** Checks if a CKey is in the given CWallet compressed or otherwise*/ +bool HaveKey(const SigningProvider& wallet, const CKey& key) +{ + CKey key2; + key2.Set(key.begin(), key.end(), !key.IsCompressed()); + return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID()); +} + +static RPCHelpMan getwalletinfo() +{ + return RPCHelpMan{"getwalletinfo", + "Returns an object containing various wallet state info.\n", + {}, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + { + {RPCResult::Type::STR, "walletname", "the wallet name"}, + {RPCResult::Type::NUM, "walletversion", "the wallet version"}, + {RPCResult::Type::STR, "format", "the database format (bdb or sqlite)"}, + {RPCResult::Type::STR_AMOUNT, "balance", "DEPRECATED. Identical to getbalances().mine.trusted"}, + {RPCResult::Type::STR_AMOUNT, "unconfirmed_balance", "DEPRECATED. Identical to getbalances().mine.untrusted_pending"}, + {RPCResult::Type::STR_AMOUNT, "immature_balance", "DEPRECATED. Identical to getbalances().mine.immature"}, + {RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"}, + {RPCResult::Type::NUM_TIME, "keypoololdest", /* optional */ true, "the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool. Legacy wallets only."}, + {RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"}, + {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)"}, + {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)"}, + {RPCResult::Type::STR_AMOUNT, "paytxfee", "the transaction fee configuration, set in " + CURRENCY_UNIT + "/kvB"}, + {RPCResult::Type::STR_HEX, "hdseedid", /* optional */ true, "the Hash160 of the HD seed (only present when HD is enabled)"}, + {RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"}, + {RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"}, + {RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress", + { + {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"}, + {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"}, + }}, + {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"}, + }}, + }, + RPCExamples{ + HelpExampleCli("getwalletinfo", "") + + HelpExampleRpc("getwalletinfo", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; + + // Make sure the results are valid at least up to the most recent block + // the user could have gotten from another RPC command prior to now + pwallet->BlockUntilSyncedToCurrentChain(); + + LOCK(pwallet->cs_wallet); + + UniValue obj(UniValue::VOBJ); + + size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); + const auto bal = GetBalance(*pwallet); + obj.pushKV("walletname", pwallet->GetName()); + obj.pushKV("walletversion", pwallet->GetVersion()); + obj.pushKV("format", pwallet->GetDatabase().Format()); + obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted)); + obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending)); + obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature)); + obj.pushKV("txcount", (int)pwallet->mapWallet.size()); + const auto kp_oldest = pwallet->GetOldestKeyPoolTime(); + if (kp_oldest.has_value()) { + obj.pushKV("keypoololdest", kp_oldest.value()); + } + obj.pushKV("keypoolsize", (int64_t)kpExternalSize); + + LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan(); + if (spk_man) { + CKeyID seed_id = spk_man->GetHDChain().seed_id; + if (!seed_id.IsNull()) { + obj.pushKV("hdseedid", seed_id.GetHex()); + } + } + + if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) { + obj.pushKV("keypoolsize_hd_internal", (int64_t)(pwallet->GetKeyPoolSize() - kpExternalSize)); + } + if (pwallet->IsCrypted()) { + obj.pushKV("unlocked_until", pwallet->nRelockTime); + } + obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK())); + obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); + obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)); + if (pwallet->IsScanning()) { + UniValue scanning(UniValue::VOBJ); + scanning.pushKV("duration", pwallet->ScanningDuration() / 1000); + scanning.pushKV("progress", pwallet->ScanningProgress()); + obj.pushKV("scanning", scanning); + } else { + obj.pushKV("scanning", false); + } + obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); + return obj; +}, + }; +} + +static RPCHelpMan listwalletdir() +{ + return RPCHelpMan{"listwalletdir", + "Returns a list of wallets in the wallet directory.\n", + {}, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::ARR, "wallets", "", + { + {RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "name", "The wallet name"}, + }}, + }}, + } + }, + RPCExamples{ + HelpExampleCli("listwalletdir", "") + + HelpExampleRpc("listwalletdir", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + UniValue wallets(UniValue::VARR); + for (const auto& path : ListDatabases(GetWalletDir())) { + UniValue wallet(UniValue::VOBJ); + wallet.pushKV("name", path.u8string()); + wallets.push_back(wallet); + } + + UniValue result(UniValue::VOBJ); + result.pushKV("wallets", wallets); + return result; +}, + }; +} + +static RPCHelpMan listwallets() +{ + return RPCHelpMan{"listwallets", + "Returns a list of currently loaded wallets.\n" + "For full information on the wallet, use \"getwalletinfo\"\n", + {}, + RPCResult{ + RPCResult::Type::ARR, "", "", + { + {RPCResult::Type::STR, "walletname", "the wallet name"}, + } + }, + RPCExamples{ + HelpExampleCli("listwallets", "") + + HelpExampleRpc("listwallets", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + UniValue obj(UniValue::VARR); + + WalletContext& context = EnsureWalletContext(request.context); + for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) { + LOCK(wallet->cs_wallet); + obj.push_back(wallet->GetName()); + } + + return obj; +}, + }; +} + +static RPCHelpMan loadwallet() +{ + return RPCHelpMan{"loadwallet", + "\nLoads a wallet from a wallet file or directory." + "\nNote that all wallet command-line options used when starting bitcoind will be" + "\napplied to the new wallet.\n", + { + {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."}, + {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "name", "The wallet name if loaded successfully."}, + {RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."}, + } + }, + RPCExamples{ + HelpExampleCli("loadwallet", "\"test.dat\"") + + HelpExampleRpc("loadwallet", "\"test.dat\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + WalletContext& context = EnsureWalletContext(request.context); + const std::string name(request.params[0].get_str()); + + auto [wallet, warnings] = LoadWalletHelper(context, request.params[1], name); + + UniValue obj(UniValue::VOBJ); + obj.pushKV("name", wallet->GetName()); + obj.pushKV("warning", Join(warnings, Untranslated("\n")).original); + + return obj; +}, + }; +} + +static RPCHelpMan setwalletflag() +{ + std::string flags = ""; + for (auto& it : WALLET_FLAG_MAP) + if (it.second & MUTABLE_WALLET_FLAGS) + flags += (flags == "" ? "" : ", ") + it.first; + + return RPCHelpMan{"setwalletflag", + "\nChange the state of the given wallet flag for a wallet.\n", + { + {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags}, + {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"}, + {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"}, + {RPCResult::Type::STR, "warnings", "Any warnings associated with the change"}, + } + }, + RPCExamples{ + HelpExampleCli("setwalletflag", "avoid_reuse") + + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; + + std::string flag_str = request.params[0].get_str(); + bool value = request.params[1].isNull() || request.params[1].get_bool(); + + if (!WALLET_FLAG_MAP.count(flag_str)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unknown wallet flag: %s", flag_str)); + } + + auto flag = WALLET_FLAG_MAP.at(flag_str); + + if (!(flag & MUTABLE_WALLET_FLAGS)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is immutable: %s", flag_str)); + } + + UniValue res(UniValue::VOBJ); + + if (pwallet->IsWalletFlagSet(flag) == value) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is already set to %s: %s", value ? "true" : "false", flag_str)); + } + + res.pushKV("flag_name", flag_str); + res.pushKV("flag_state", value); + + if (value) { + pwallet->SetWalletFlag(flag); + } else { + pwallet->UnsetWalletFlag(flag); + } + + if (flag && value && WALLET_FLAG_CAVEATS.count(flag)) { + res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag)); + } + + return res; +}, + }; +} + +static RPCHelpMan createwallet() +{ + return RPCHelpMan{ + "createwallet", + "\nCreates and loads a new wallet.\n", + { + {"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."}, + {"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false}, "Disable the possibility of private keys (only watchonlys are possible in this mode)."}, + {"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."}, + {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Encrypt the wallet with this passphrase."}, + {"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."}, + {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation"}, + {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."}, + {"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."}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {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."}, + {RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."}, + } + }, + RPCExamples{ + HelpExampleCli("createwallet", "\"testwallet\"") + + HelpExampleRpc("createwallet", "\"testwallet\"") + + HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}}) + + HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}}) + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + WalletContext& context = EnsureWalletContext(request.context); + uint64_t flags = 0; + if (!request.params[1].isNull() && request.params[1].get_bool()) { + flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; + } + + if (!request.params[2].isNull() && request.params[2].get_bool()) { + flags |= WALLET_FLAG_BLANK_WALLET; + } + SecureString passphrase; + passphrase.reserve(100); + std::vector<bilingual_str> warnings; + if (!request.params[3].isNull()) { + passphrase = request.params[3].get_str().c_str(); + if (passphrase.empty()) { + // Empty string means unencrypted + warnings.emplace_back(Untranslated("Empty string given as passphrase, wallet will not be encrypted.")); + } + } + + if (!request.params[4].isNull() && request.params[4].get_bool()) { + flags |= WALLET_FLAG_AVOID_REUSE; + } + if (request.params[5].isNull() || request.params[5].get_bool()) { +#ifndef USE_SQLITE + throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without sqlite support (required for descriptor wallets)"); +#endif + flags |= WALLET_FLAG_DESCRIPTORS; + } + if (!request.params[7].isNull() && request.params[7].get_bool()) { +#ifdef ENABLE_EXTERNAL_SIGNER + flags |= WALLET_FLAG_EXTERNAL_SIGNER; +#else + throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without external signing support (required for external signing)"); +#endif + } + +#ifndef USE_BDB + if (!(flags & WALLET_FLAG_DESCRIPTORS)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without bdb support (required for legacy wallets)"); + } +#endif + + DatabaseOptions options; + DatabaseStatus status; + options.require_create = true; + options.create_flags = flags; + options.create_passphrase = passphrase; + bilingual_str error; + std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool()); + const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings); + if (!wallet) { + RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT ? RPC_WALLET_ENCRYPTION_FAILED : RPC_WALLET_ERROR; + throw JSONRPCError(code, error.original); + } + + UniValue obj(UniValue::VOBJ); + obj.pushKV("name", wallet->GetName()); + obj.pushKV("warning", Join(warnings, Untranslated("\n")).original); + + return obj; +}, + }; +} + +static RPCHelpMan unloadwallet() +{ + return RPCHelpMan{"unloadwallet", + "Unloads the wallet referenced by the request endpoint otherwise unloads the wallet specified in the argument.\n" + "Specifying the wallet name on a wallet endpoint is invalid.", + { + {"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."}, + {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."}, + }, + RPCResult{RPCResult::Type::OBJ, "", "", { + {RPCResult::Type::STR, "warning", "Warning message if wallet was not unloaded cleanly."}, + }}, + RPCExamples{ + HelpExampleCli("unloadwallet", "wallet_name") + + HelpExampleRpc("unloadwallet", "wallet_name") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + std::string wallet_name; + if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) { + if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "RPC endpoint wallet and wallet_name parameter specify different wallets"); + } + } else { + wallet_name = request.params[0].get_str(); + } + + WalletContext& context = EnsureWalletContext(request.context); + std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name); + if (!wallet) { + throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); + } + + // Release the "main" shared pointer and prevent further notifications. + // Note that any attempt to load the same wallet would fail until the wallet + // is destroyed (see CheckUniqueFileid). + std::vector<bilingual_str> warnings; + std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool()); + if (!RemoveWallet(context, wallet, load_on_start, warnings)) { + throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded"); + } + + UnloadWallet(std::move(wallet)); + + UniValue result(UniValue::VOBJ); + result.pushKV("warning", Join(warnings, Untranslated("\n")).original); + return result; +}, + }; +} + +static RPCHelpMan sethdseed() +{ + return RPCHelpMan{"sethdseed", + "\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" + "HD will have a new HD seed set so that new keys added to the keypool will be derived from this new seed.\n" + "\nNote that you will need to MAKE A NEW BACKUP of your wallet after setting the HD wallet seed." + + HELP_REQUIRING_PASSPHRASE, + { + {"newkeypool", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to flush old unused addresses, including change addresses, from the keypool and regenerate it.\n" + "If true, the next address from getnewaddress and change address from getrawchangeaddress will be from this new seed.\n" + "If false, addresses (including change addresses if the wallet already had HD Chain Split enabled) from the existing\n" + "keypool will be used until it has been depleted."}, + {"seed", RPCArg::Type::STR, RPCArg::DefaultHint{"random seed"}, "The WIF private key to use as the new HD seed.\n" + "The seed value can be retrieved using the dumpwallet command. It is the private key marked hdseed=1"}, + }, + RPCResult{RPCResult::Type::NONE, "", ""}, + RPCExamples{ + HelpExampleCli("sethdseed", "") + + HelpExampleCli("sethdseed", "false") + + HelpExampleCli("sethdseed", "true \"wifkey\"") + + HelpExampleRpc("sethdseed", "true, \"wifkey\"") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; + + LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true); + + if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed to a wallet with private keys disabled"); + } + + LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); + + // Do not do anything to non-HD wallets + if (!pwallet->CanSupportFeature(FEATURE_HD)) { + 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"); + } + + EnsureWalletIsUnlocked(*pwallet); + + bool flush_key_pool = true; + if (!request.params[0].isNull()) { + flush_key_pool = request.params[0].get_bool(); + } + + CPubKey master_pub_key; + if (request.params[1].isNull()) { + master_pub_key = spk_man.GenerateNewSeed(); + } else { + CKey key = DecodeSecret(request.params[1].get_str()); + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); + } + + if (HaveKey(spk_man, key)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key (either as an HD seed or as a loose private key)"); + } + + master_pub_key = spk_man.DeriveNewSeed(key); + } + + spk_man.SetHDSeed(master_pub_key); + if (flush_key_pool) spk_man.NewKeyPool(); + + return NullUniValue; +}, + }; +} + +static RPCHelpMan upgradewallet() +{ + return RPCHelpMan{"upgradewallet", + "\nUpgrade the wallet. Upgrades to the latest version if no version number is specified.\n" + "New keys may be generated and a new wallet backup will need to be made.", + { + {"version", RPCArg::Type::NUM, RPCArg::Default{FEATURE_LATEST}, "The version number to upgrade to. Default is the latest wallet version."} + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"}, + {RPCResult::Type::NUM, "previous_version", "Version of wallet before this operation"}, + {RPCResult::Type::NUM, "current_version", "Version of wallet after this operation"}, + {RPCResult::Type::STR, "result", /* optional */ true, "Description of result, if no error"}, + {RPCResult::Type::STR, "error", /* optional */ true, "Error message (if there is one)"} + }, + }, + RPCExamples{ + HelpExampleCli("upgradewallet", "169900") + + HelpExampleRpc("upgradewallet", "169900") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); + if (!pwallet) return NullUniValue; + + RPCTypeCheck(request.params, {UniValue::VNUM}, true); + + EnsureWalletIsUnlocked(*pwallet); + + int version = 0; + if (!request.params[0].isNull()) { + version = request.params[0].get_int(); + } + bilingual_str error; + const int previous_version{pwallet->GetVersion()}; + const bool wallet_upgraded{pwallet->UpgradeWallet(version, error)}; + const int current_version{pwallet->GetVersion()}; + std::string result; + + if (wallet_upgraded) { + if (previous_version == current_version) { + result = "Already at latest version. Wallet version unchanged."; + } else { + result = strprintf("Wallet upgraded successfully from version %i to version %i.", previous_version, current_version); + } + } + + UniValue obj(UniValue::VOBJ); + obj.pushKV("wallet_name", pwallet->GetName()); + obj.pushKV("previous_version", previous_version); + obj.pushKV("current_version", current_version); + if (!result.empty()) { + obj.pushKV("result", result); + } else { + CHECK_NONFATAL(!error.empty()); + obj.pushKV("error", error.original); + } + return obj; +}, + }; +} + +RPCHelpMan abortrescan(); +RPCHelpMan dumpprivkey(); +RPCHelpMan importprivkey(); +RPCHelpMan importaddress(); +RPCHelpMan importpubkey(); +RPCHelpMan dumpwallet(); +RPCHelpMan importwallet(); +RPCHelpMan importprunedfunds(); +RPCHelpMan removeprunedfunds(); +RPCHelpMan importmulti(); +RPCHelpMan importdescriptors(); +RPCHelpMan listdescriptors(); +RPCHelpMan signmessage(); +RPCHelpMan backupwallet(); +RPCHelpMan restorewallet(); + +// addresses +RPCHelpMan getnewaddress(); +RPCHelpMan getrawchangeaddress(); +RPCHelpMan setlabel(); +RPCHelpMan listaddressgroupings(); +RPCHelpMan addmultisigaddress(); +RPCHelpMan keypoolrefill(); +RPCHelpMan newkeypool(); +RPCHelpMan getaddressesbylabel(); +RPCHelpMan listlabels(); +#ifdef ENABLE_EXTERNAL_SIGNER +RPCHelpMan walletdisplayaddress(); +#endif // ENABLE_EXTERNAL_SIGNER + +// coins +RPCHelpMan getreceivedbyaddress(); +RPCHelpMan getreceivedbylabel(); +RPCHelpMan getbalance(); +RPCHelpMan getunconfirmedbalance(); +RPCHelpMan lockunspent(); +RPCHelpMan listlockunspent(); +RPCHelpMan getbalances(); +RPCHelpMan listunspent(); + +// encryption +RPCHelpMan walletpassphrase(); +RPCHelpMan walletpassphrasechange(); +RPCHelpMan walletlock(); +RPCHelpMan encryptwallet(); + +// spend +RPCHelpMan sendtoaddress(); +RPCHelpMan sendmany(); +RPCHelpMan settxfee(); +RPCHelpMan fundrawtransaction(); +RPCHelpMan bumpfee(); +RPCHelpMan psbtbumpfee(); +RPCHelpMan send(); +RPCHelpMan walletprocesspsbt(); +RPCHelpMan walletcreatefundedpsbt(); + +// transactions +RPCHelpMan listreceivedbyaddress(); +RPCHelpMan listreceivedbylabel(); +RPCHelpMan listtransactions(); +RPCHelpMan listsinceblock(); +RPCHelpMan gettransaction(); +RPCHelpMan abandontransaction(); +RPCHelpMan rescanblockchain(); + +Span<const CRPCCommand> GetWalletRPCCommands() +{ +// clang-format off +static const CRPCCommand commands[] = +{ // category actor (function) + // ------------------ ------------------------ + { "rawtransactions", &fundrawtransaction, }, + { "wallet", &abandontransaction, }, + { "wallet", &abortrescan, }, + { "wallet", &addmultisigaddress, }, + { "wallet", &backupwallet, }, + { "wallet", &bumpfee, }, + { "wallet", &psbtbumpfee, }, + { "wallet", &createwallet, }, + { "wallet", &restorewallet, }, + { "wallet", &dumpprivkey, }, + { "wallet", &dumpwallet, }, + { "wallet", &encryptwallet, }, + { "wallet", &getaddressesbylabel, }, + { "wallet", &getaddressinfo, }, + { "wallet", &getbalance, }, + { "wallet", &getnewaddress, }, + { "wallet", &getrawchangeaddress, }, + { "wallet", &getreceivedbyaddress, }, + { "wallet", &getreceivedbylabel, }, + { "wallet", &gettransaction, }, + { "wallet", &getunconfirmedbalance, }, + { "wallet", &getbalances, }, + { "wallet", &getwalletinfo, }, + { "wallet", &importaddress, }, + { "wallet", &importdescriptors, }, + { "wallet", &importmulti, }, + { "wallet", &importprivkey, }, + { "wallet", &importprunedfunds, }, + { "wallet", &importpubkey, }, + { "wallet", &importwallet, }, + { "wallet", &keypoolrefill, }, + { "wallet", &listaddressgroupings, }, + { "wallet", &listdescriptors, }, + { "wallet", &listlabels, }, + { "wallet", &listlockunspent, }, + { "wallet", &listreceivedbyaddress, }, + { "wallet", &listreceivedbylabel, }, + { "wallet", &listsinceblock, }, + { "wallet", &listtransactions, }, + { "wallet", &listunspent, }, + { "wallet", &listwalletdir, }, + { "wallet", &listwallets, }, + { "wallet", &loadwallet, }, + { "wallet", &lockunspent, }, + { "wallet", &newkeypool, }, + { "wallet", &removeprunedfunds, }, + { "wallet", &rescanblockchain, }, + { "wallet", &send, }, + { "wallet", &sendmany, }, + { "wallet", &sendtoaddress, }, + { "wallet", &sethdseed, }, + { "wallet", &setlabel, }, + { "wallet", &settxfee, }, + { "wallet", &setwalletflag, }, + { "wallet", &signmessage, }, + { "wallet", &signrawtransactionwithwallet, }, + { "wallet", &unloadwallet, }, + { "wallet", &upgradewallet, }, + { "wallet", &walletcreatefundedpsbt, }, +#ifdef ENABLE_EXTERNAL_SIGNER + { "wallet", &walletdisplayaddress, }, +#endif // ENABLE_EXTERNAL_SIGNER + { "wallet", &walletlock, }, + { "wallet", &walletpassphrase, }, + { "wallet", &walletpassphrasechange, }, + { "wallet", &walletprocesspsbt, }, +}; +// clang-format on + return commands; +} |