diff options
Diffstat (limited to 'src/wallet/rpc/wallet.cpp')
-rw-r--r-- | src/wallet/rpc/wallet.cpp | 331 |
1 files changed, 250 insertions, 81 deletions
diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index 883a3c102b..675c4a759d 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -8,6 +8,7 @@ #include <rpc/server.h> #include <rpc/util.h> #include <util/translation.h> +#include <wallet/context.h> #include <wallet/receive.h> #include <wallet/rpc/wallet.h> #include <wallet/rpc/util.h> @@ -55,7 +56,7 @@ static RPCHelpMan getwalletinfo() { {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"}, {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"}, - }}, + }, /*skip_type_check=*/true}, {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"}, {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"}, }}, @@ -67,7 +68,7 @@ static RPCHelpMan getwalletinfo() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; // 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 @@ -220,6 +221,7 @@ static RPCHelpMan loadwallet() DatabaseOptions options; DatabaseStatus status; + ReadDatabaseArgs(*context.args, options); options.require_existing = true; bilingual_str error; std::vector<bilingual_str> warnings; @@ -239,7 +241,7 @@ static RPCHelpMan loadwallet() static RPCHelpMan setwalletflag() { - std::string flags = ""; + std::string flags; for (auto& it : WALLET_FLAG_MAP) if (it.second & MUTABLE_WALLET_FLAGS) flags += (flags == "" ? "" : ", ") + it.first; @@ -255,7 +257,7 @@ static RPCHelpMan setwalletflag() { {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"}, + {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"}, } }, RPCExamples{ @@ -265,7 +267,7 @@ static RPCHelpMan setwalletflag() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; std::string flag_str = request.params[0].get_str(); bool value = request.params[1].isNull() || request.params[1].get_bool(); @@ -315,7 +317,9 @@ static RPCHelpMan createwallet() {"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"}, + {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation." + " Setting to \"false\" will create a legacy wallet; however, the legacy wallet type is being deprecated and" + " support for creating and opening legacy wallets will be removed in the future."}, {"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."}, }, @@ -379,6 +383,7 @@ static RPCHelpMan createwallet() DatabaseOptions options; DatabaseStatus status; + ReadDatabaseArgs(*context.args, options); options.require_create = true; options.create_flags = flags; options.create_passphrase = passphrase; @@ -475,7 +480,7 @@ static RPCHelpMan sethdseed() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true); @@ -516,7 +521,7 @@ static RPCHelpMan sethdseed() spk_man.SetHDSeed(master_pub_key); if (flush_key_pool) spk_man.NewKeyPool(); - return NullUniValue; + return UniValue::VNULL; }, }; } @@ -527,7 +532,7 @@ static 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."} + {"version", RPCArg::Type::NUM, RPCArg::Default{int{FEATURE_LATEST}}, "The version number to upgrade to. Default is the latest wallet version."} }, RPCResult{ RPCResult::Type::OBJ, "", "", @@ -546,7 +551,7 @@ static RPCHelpMan upgradewallet() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); - if (!pwallet) return NullUniValue; + if (!pwallet) return UniValue::VNULL; RPCTypeCheck(request.params, {UniValue::VNUM}, true); @@ -554,7 +559,7 @@ static RPCHelpMan upgradewallet() int version = 0; if (!request.params[0].isNull()) { - version = request.params[0].get_int(); + version = request.params[0].getInt<int>(); } bilingual_str error; const int previous_version{pwallet->GetVersion()}; @@ -585,6 +590,170 @@ static RPCHelpMan upgradewallet() }; } +RPCHelpMan simulaterawtransaction() +{ + return RPCHelpMan{"simulaterawtransaction", + "\nCalculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n", + { + {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "An array of hex strings of raw transactions.\n", + { + {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""}, + }, + }, + {"options", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED_NAMED_ARG, "Options", + { + {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Whether to include watch-only addresses (see RPC importaddress)"}, + }, + }, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."}, + } + }, + RPCExamples{ + HelpExampleCli("simulaterawtransaction", "[\"myhex\"]") + + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request); + if (!rpc_wallet) return UniValue::VNULL; + const CWallet& wallet = *rpc_wallet; + + RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VOBJ}, true); + + LOCK(wallet.cs_wallet); + + UniValue include_watchonly(UniValue::VNULL); + if (request.params[1].isObject()) { + UniValue options = request.params[1]; + RPCTypeCheckObj(options, + { + {"include_watchonly", UniValueType(UniValue::VBOOL)}, + }, + true, true); + + include_watchonly = options["include_watchonly"]; + } + + isminefilter filter = ISMINE_SPENDABLE; + if (ParseIncludeWatchonly(include_watchonly, wallet)) { + filter |= ISMINE_WATCH_ONLY; + } + + const auto& txs = request.params[0].get_array(); + CAmount changes{0}; + std::map<COutPoint, CAmount> new_utxos; // UTXO:s that were made available in transaction array + std::set<COutPoint> spent; + + for (size_t i = 0; i < txs.size(); ++i) { + CMutableTransaction mtx; + if (!DecodeHexTx(mtx, txs[i].get_str(), /* try_no_witness */ true, /* try_witness */ true)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Transaction hex string decoding failure."); + } + + // Fetch previous transactions (inputs) + std::map<COutPoint, Coin> coins; + for (const CTxIn& txin : mtx.vin) { + coins[txin.prevout]; // Create empty map entry keyed by prevout. + } + wallet.chain().findCoins(coins); + + // Fetch debit; we are *spending* these; if the transaction is signed and + // broadcast, we will lose everything in these + for (const auto& txin : mtx.vin) { + const auto& outpoint = txin.prevout; + if (spent.count(outpoint)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction(s) are spending the same output more than once"); + } + if (new_utxos.count(outpoint)) { + changes -= new_utxos.at(outpoint); + new_utxos.erase(outpoint); + } else { + if (coins.at(outpoint).IsSpent()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more transaction inputs are missing or have been spent already"); + } + changes -= wallet.GetDebit(txin, filter); + } + spent.insert(outpoint); + } + + // Iterate over outputs; we are *receiving* these, if the wallet considers + // them "mine"; if the transaction is signed and broadcast, we will receive + // everything in these + // Also populate new_utxos in case these are spent in later transactions + + const auto& hash = mtx.GetHash(); + for (size_t i = 0; i < mtx.vout.size(); ++i) { + const auto& txout = mtx.vout[i]; + bool is_mine = 0 < (wallet.IsMine(txout) & filter); + changes += new_utxos[COutPoint(hash, i)] = is_mine ? txout.nValue : 0; + } + } + + UniValue result(UniValue::VOBJ); + result.pushKV("balance_change", ValueFromAmount(changes)); + + return result; +} + }; +} + +static RPCHelpMan migratewallet() +{ + return RPCHelpMan{"migratewallet", + "EXPERIMENTAL warning: This call may not work as expected and may be changed in future releases\n" + "\nMigrate the wallet to a descriptor wallet.\n" + "A new wallet backup will need to be made.\n" + "\nThe migration process will create a backup of the wallet before migrating. This backup\n" + "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n" + "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet." + + HELP_REQUIRING_PASSPHRASE, + {}, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"}, + {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"}, + {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"}, + {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"}, + } + }, + RPCExamples{ + HelpExampleCli("migratewallet", "") + + HelpExampleRpc("migratewallet", "") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue + { + std::shared_ptr<CWallet> wallet = GetWalletForJSONRPCRequest(request); + if (!wallet) return NullUniValue; + + EnsureWalletIsUnlocked(*wallet); + + WalletContext& context = EnsureWalletContext(request.context); + + util::Result<MigrationResult> res = MigrateLegacyToDescriptor(std::move(wallet), context); + if (!res) { + throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original); + } + + UniValue r{UniValue::VOBJ}; + r.pushKV("wallet_name", res->wallet_name); + if (res->watchonly_wallet) { + r.pushKV("watchonly_name", res->watchonly_wallet->GetName()); + } + if (res->solvables_wallet) { + r.pushKV("solvables_name", res->solvables_wallet->GetName()); + } + r.pushKV("backup_path", res->backup_path.u8string()); + + return r; + }, + }; +} + // addresses RPCHelpMan getaddressinfo(); RPCHelpMan getnewaddress(); @@ -639,6 +808,7 @@ RPCHelpMan fundrawtransaction(); RPCHelpMan bumpfee(); RPCHelpMan psbtbumpfee(); RPCHelpMan send(); +RPCHelpMan sendall(); RPCHelpMan walletprocesspsbt(); RPCHelpMan walletcreatefundedpsbt(); RPCHelpMan signrawtransactionwithwallet(); @@ -658,78 +828,77 @@ RPCHelpMan abortrescan(); 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, }, + static const CRPCCommand commands[]{ + {"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", &migratewallet}, + {"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", &simulaterawtransaction}, + {"wallet", &sendall}, + {"wallet", &unloadwallet}, + {"wallet", &upgradewallet}, + {"wallet", &walletcreatefundedpsbt}, #ifdef ENABLE_EXTERNAL_SIGNER - { "wallet", &walletdisplayaddress, }, + {"wallet", &walletdisplayaddress}, #endif // ENABLE_EXTERNAL_SIGNER - { "wallet", &walletlock, }, - { "wallet", &walletpassphrase, }, - { "wallet", &walletpassphrasechange, }, - { "wallet", &walletprocesspsbt, }, -}; -// clang-format on + {"wallet", &walletlock}, + {"wallet", &walletpassphrase}, + {"wallet", &walletpassphrasechange}, + {"wallet", &walletprocesspsbt}, + }; return commands; } } // namespace wallet |