aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/rpc/wallet.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/rpc/wallet.cpp')
-rw-r--r--src/wallet/rpc/wallet.cpp331
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