aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/rpc
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/rpc')
-rw-r--r--src/wallet/rpc/addresses.cpp10
-rw-r--r--src/wallet/rpc/backup.cpp13
-rw-r--r--src/wallet/rpc/coins.cpp7
-rw-r--r--src/wallet/rpc/transactions.cpp8
-rw-r--r--src/wallet/rpc/util.cpp5
-rw-r--r--src/wallet/rpc/wallet.cpp219
6 files changed, 242 insertions, 20 deletions
diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp
index e9b93afc30..65587f0b18 100644
--- a/src/wallet/rpc/addresses.cpp
+++ b/src/wallet/rpc/addresses.cpp
@@ -2,6 +2,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <config/bitcoin-config.h> // IWYU pragma: keep
+
#include <core_io.h>
#include <key_io.h>
#include <rpc/util.h>
@@ -391,6 +393,7 @@ class DescribeWalletAddressVisitor
public:
const SigningProvider * const provider;
+ // NOLINTNEXTLINE(misc-no-recursion)
void ProcessSubScript(const CScript& subscript, UniValue& obj) const
{
// Always present: script type and redeemscript
@@ -441,6 +444,7 @@ public:
return obj;
}
+ // NOLINTNEXTLINE(misc-no-recursion)
UniValue operator()(const ScriptHash& scripthash) const
{
UniValue obj(UniValue::VOBJ);
@@ -461,6 +465,7 @@ public:
return obj;
}
+ // NOLINTNEXTLINE(misc-no-recursion)
UniValue operator()(const WitnessV0ScriptHash& id) const
{
UniValue obj(UniValue::VOBJ);
@@ -782,9 +787,8 @@ RPCHelpMan walletdisplayaddress()
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address");
}
- if (!pwallet->DisplayAddress(dest)) {
- throw JSONRPCError(RPC_MISC_ERROR, "Failed to display address");
- }
+ util::Result<void> res = pwallet->DisplayAddress(dest);
+ if (!res) throw JSONRPCError(RPC_MISC_ERROR, util::ErrorString(res).original);
UniValue result(UniValue::VOBJ);
result.pushKV("address", request.params[0].get_str());
diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp
index d17a220603..8d3eea59ee 100644
--- a/src/wallet/rpc/backup.cpp
+++ b/src/wallet/rpc/backup.cpp
@@ -2,6 +2,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <config/bitcoin-config.h> // IWYU pragma: keep
+
#include <chain.h>
#include <clientversion.h>
#include <core_io.h>
@@ -394,14 +396,8 @@ RPCHelpMan removeprunedfunds()
uint256 hash(ParseHashV(request.params[0], "txid"));
std::vector<uint256> vHash;
vHash.push_back(hash);
- std::vector<uint256> vHashOut;
-
- if (pwallet->ZapSelectTx(vHash, vHashOut) != DBErrors::LOAD_OK) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Could not properly delete the transaction.");
- }
-
- if(vHashOut.empty()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction does not exist in wallet.");
+ if (auto res = pwallet->RemoveTxs(vHash); !res) {
+ throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
}
return UniValue::VNULL;
@@ -851,6 +847,7 @@ enum class ScriptContext
// Analyse the provided scriptPubKey, determining which keys and which redeem scripts from the ImportData struct are needed to spend it, and mark them as used.
// Returns an error string, or the empty string for success.
+// NOLINTNEXTLINE(misc-no-recursion)
static std::string RecurseImportData(const CScript& script, ImportData& import_data, const ScriptContext script_ctx)
{
// Use Solver to obtain script type and parsed pubkeys or hashes:
diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp
index 0cb0891141..b6c7396f4b 100644
--- a/src/wallet/rpc/coins.cpp
+++ b/src/wallet/rpc/coins.cpp
@@ -194,15 +194,12 @@ RPCHelpMan getbalance()
LOCK(pwallet->cs_wallet);
- const auto dummy_value{self.MaybeArg<std::string>(0)};
+ const auto dummy_value{self.MaybeArg<std::string>("dummy")};
if (dummy_value && *dummy_value != "*") {
throw JSONRPCError(RPC_METHOD_DEPRECATED, "dummy first argument must be excluded or set to \"*\".");
}
- int min_depth = 0;
- if (!request.params[1].isNull()) {
- min_depth = request.params[1].getInt<int>();
- }
+ const auto min_depth{self.Arg<int>("minconf")};
bool include_watchonly = ParseIncludeWatchonly(request.params[2], *pwallet);
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index e6c021d426..05b340995d 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -40,6 +40,10 @@ static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue
for (const uint256& conflict : wallet.GetTxConflicts(wtx))
conflicts.push_back(conflict.GetHex());
entry.pushKV("walletconflicts", conflicts);
+ UniValue mempool_conflicts(UniValue::VARR);
+ for (const Txid& mempool_conflict : wtx.mempool_conflicts)
+ mempool_conflicts.push_back(mempool_conflict.GetHex());
+ entry.pushKV("mempoolconflicts", mempool_conflicts);
entry.pushKV("time", wtx.GetTxTime());
entry.pushKV("timereceived", int64_t{wtx.nTimeReceived});
@@ -417,6 +421,10 @@ static std::vector<RPCResult> TransactionDescriptionString()
}},
{RPCResult::Type::STR_HEX, "replaced_by_txid", /*optional=*/true, "Only if 'category' is 'send'. The txid if this tx was replaced."},
{RPCResult::Type::STR_HEX, "replaces_txid", /*optional=*/true, "Only if 'category' is 'send'. The txid if this tx replaces another."},
+ {RPCResult::Type::ARR, "mempoolconflicts", "Transactions that directly conflict with either this transaction or an ancestor transaction",
+ {
+ {RPCResult::Type::STR_HEX, "txid", "The transaction id."},
+ }},
{RPCResult::Type::STR, "to", /*optional=*/true, "If a comment to is associated with the transaction."},
{RPCResult::Type::NUM_TIME, "time", "The transaction time expressed in " + UNIX_EPOCH_TIME + "."},
{RPCResult::Type::NUM_TIME, "timereceived", "The time received expressed in " + UNIX_EPOCH_TIME + "."},
diff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp
index 06ec7db1bc..1252843e9d 100644
--- a/src/wallet/rpc/util.cpp
+++ b/src/wallet/rpc/util.cpp
@@ -11,6 +11,7 @@
#include <wallet/context.h>
#include <wallet/wallet.h>
+#include <string_view>
#include <univalue.h>
#include <boost/date_time/posix_time/posix_time.hpp>
@@ -61,9 +62,9 @@ bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& wal
bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request, std::string& wallet_name)
{
- if (URL_DECODE && request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) {
+ if (request.URI.starts_with(WALLET_ENDPOINT_BASE)) {
// wallet endpoint was used
- wallet_name = URL_DECODE(request.URI.substr(WALLET_ENDPOINT_BASE.size()));
+ wallet_name = UrlDecode(std::string_view{request.URI}.substr(WALLET_ENDPOINT_BASE.size()));
return true;
}
return false;
diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp
index 391153a2a1..f1cb595271 100644
--- a/src/wallet/rpc/wallet.cpp
+++ b/src/wallet/rpc/wallet.cpp
@@ -3,6 +3,8 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <config/bitcoin-config.h> // IWYU pragma: keep
+
#include <core_io.h>
#include <key_io.h>
#include <rpc/server.h>
@@ -748,13 +750,13 @@ RPCHelpMan simulaterawtransaction()
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."
- "\nEncrypted wallets must have the passphrase provided as an argument to this call.",
+ "\nEncrypted wallets must have the passphrase provided as an argument to this call.\n"
+ "\nThis RPC may take a long time to complete. Increasing the RPC client timeout is recommended.",
{
{"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."},
{"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
@@ -813,6 +815,217 @@ static RPCHelpMan migratewallet()
};
}
+RPCHelpMan gethdkeys()
+{
+ return RPCHelpMan{
+ "gethdkeys",
+ "\nList all BIP 32 HD keys in the wallet and which descriptors use them.\n",
+ {
+ {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
+ {"active_only", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show the keys for only active descriptors"},
+ {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private keys"}
+ }},
+ },
+ RPCResult{RPCResult::Type::ARR, "", "", {
+ {
+ {RPCResult::Type::OBJ, "", "", {
+ {RPCResult::Type::STR, "xpub", "The extended public key"},
+ {RPCResult::Type::BOOL, "has_private", "Whether the wallet has the private key for this xpub"},
+ {RPCResult::Type::STR, "xprv", /*optional=*/true, "The extended private key if \"private\" is true"},
+ {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects that use this HD key",
+ {
+ {RPCResult::Type::OBJ, "", "", {
+ {RPCResult::Type::STR, "desc", "Descriptor string representation"},
+ {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
+ }},
+ }},
+ }},
+ }
+ }},
+ RPCExamples{
+ HelpExampleCli("gethdkeys", "") + HelpExampleRpc("gethdkeys", "")
+ + HelpExampleCliNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}}) + HelpExampleRpcNamed("gethdkeys", {{"active_only", "true"}, {"private", "true"}})
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
+ if (!wallet) return UniValue::VNULL;
+
+ if (!wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "gethdkeys is not available for non-descriptor wallets");
+ }
+
+ LOCK(wallet->cs_wallet);
+
+ UniValue options{request.params[0].isNull() ? UniValue::VOBJ : request.params[0]};
+ const bool active_only{options.exists("active_only") ? options["active_only"].get_bool() : false};
+ const bool priv{options.exists("private") ? options["private"].get_bool() : false};
+ if (priv) {
+ EnsureWalletIsUnlocked(*wallet);
+ }
+
+
+ std::set<ScriptPubKeyMan*> spkms;
+ if (active_only) {
+ spkms = wallet->GetActiveScriptPubKeyMans();
+ } else {
+ spkms = wallet->GetAllScriptPubKeyMans();
+ }
+
+ std::map<CExtPubKey, std::set<std::tuple<std::string, bool, bool>>> wallet_xpubs;
+ std::map<CExtPubKey, CExtKey> wallet_xprvs;
+ for (auto* spkm : spkms) {
+ auto* desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(spkm)};
+ CHECK_NONFATAL(desc_spkm);
+ LOCK(desc_spkm->cs_desc_man);
+ WalletDescriptor w_desc = desc_spkm->GetWalletDescriptor();
+
+ // Retrieve the pubkeys from the descriptor
+ std::set<CPubKey> desc_pubkeys;
+ std::set<CExtPubKey> desc_xpubs;
+ w_desc.descriptor->GetPubKeys(desc_pubkeys, desc_xpubs);
+ for (const CExtPubKey& xpub : desc_xpubs) {
+ std::string desc_str;
+ bool ok = desc_spkm->GetDescriptorString(desc_str, false);
+ CHECK_NONFATAL(ok);
+ wallet_xpubs[xpub].emplace(desc_str, wallet->IsActiveScriptPubKeyMan(*spkm), desc_spkm->HasPrivKey(xpub.pubkey.GetID()));
+ if (std::optional<CKey> key = priv ? desc_spkm->GetKey(xpub.pubkey.GetID()) : std::nullopt) {
+ wallet_xprvs[xpub] = CExtKey(xpub, *key);
+ }
+ }
+ }
+
+ UniValue response(UniValue::VARR);
+ for (const auto& [xpub, descs] : wallet_xpubs) {
+ bool has_xprv = false;
+ UniValue descriptors(UniValue::VARR);
+ for (const auto& [desc, active, has_priv] : descs) {
+ UniValue d(UniValue::VOBJ);
+ d.pushKV("desc", desc);
+ d.pushKV("active", active);
+ has_xprv |= has_priv;
+
+ descriptors.push_back(std::move(d));
+ }
+ UniValue xpub_info(UniValue::VOBJ);
+ xpub_info.pushKV("xpub", EncodeExtPubKey(xpub));
+ xpub_info.pushKV("has_private", has_xprv);
+ if (priv) {
+ xpub_info.pushKV("xprv", EncodeExtKey(wallet_xprvs.at(xpub)));
+ }
+ xpub_info.pushKV("descriptors", std::move(descriptors));
+
+ response.push_back(std::move(xpub_info));
+ }
+
+ return response;
+ },
+ };
+}
+
+static RPCHelpMan createwalletdescriptor()
+{
+ return RPCHelpMan{"createwalletdescriptor",
+ "Creates the wallet's descriptor for the given address type. "
+ "The address type must be one that the wallet does not already have a descriptor for."
+ + HELP_REQUIRING_PASSPHRASE,
+ {
+ {"type", RPCArg::Type::STR, RPCArg::Optional::NO, "The address type the descriptor will produce. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
+ {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", {
+ {"internal", RPCArg::Type::BOOL, RPCArg::DefaultHint{"Both external and internal will be generated unless this parameter is specified"}, "Whether to only make one descriptor that is internal (if parameter is true) or external (if parameter is false)"},
+ {"hdkey", RPCArg::Type::STR, RPCArg::DefaultHint{"The HD key used by all other active descriptors"}, "The HD key that the wallet knows the private key of, listed using 'gethdkeys', to use for this descriptor's key"},
+ }},
+ },
+ RPCResult{
+ RPCResult::Type::OBJ, "", "",
+ {
+ {RPCResult::Type::ARR, "descs", "The public descriptors that were added to the wallet",
+ {{RPCResult::Type::STR, "", ""}}
+ }
+ },
+ },
+ RPCExamples{
+ HelpExampleCli("createwalletdescriptor", "bech32m")
+ + HelpExampleRpc("createwalletdescriptor", "bech32m")
+ },
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
+ {
+ std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
+ if (!pwallet) return UniValue::VNULL;
+
+ // Make sure wallet is a descriptor wallet
+ if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "createwalletdescriptor is not available for non-descriptor wallets");
+ }
+
+ std::optional<OutputType> output_type = ParseOutputType(request.params[0].get_str());
+ if (!output_type) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
+ }
+
+ UniValue options{request.params[1].isNull() ? UniValue::VOBJ : request.params[1]};
+ UniValue internal_only{options["internal"]};
+ UniValue hdkey{options["hdkey"]};
+
+ std::vector<bool> internals;
+ if (internal_only.isNull()) {
+ internals.push_back(false);
+ internals.push_back(true);
+ } else {
+ internals.push_back(internal_only.get_bool());
+ }
+
+ LOCK(pwallet->cs_wallet);
+ EnsureWalletIsUnlocked(*pwallet);
+
+ CExtPubKey xpub;
+ if (hdkey.isNull()) {
+ std::set<CExtPubKey> active_xpubs = pwallet->GetActiveHDPubKeys();
+ if (active_xpubs.size() != 1) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to determine which HD key to use from active descriptors. Please specify with 'hdkey'");
+ }
+ xpub = *active_xpubs.begin();
+ } else {
+ xpub = DecodeExtPubKey(hdkey.get_str());
+ if (!xpub.pubkey.IsValid()) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Unable to parse HD key. Please provide a valid xpub");
+ }
+ }
+
+ std::optional<CKey> key = pwallet->GetKey(xpub.pubkey.GetID());
+ if (!key) {
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Private key for %s is not known", EncodeExtPubKey(xpub)));
+ }
+ CExtKey active_hdkey(xpub, *key);
+
+ std::vector<std::reference_wrapper<DescriptorScriptPubKeyMan>> spkms;
+ WalletBatch batch{pwallet->GetDatabase()};
+ for (bool internal : internals) {
+ WalletDescriptor w_desc = GenerateWalletDescriptor(xpub, *output_type, internal);
+ uint256 w_id = DescriptorID(*w_desc.descriptor);
+ if (!pwallet->GetScriptPubKeyMan(w_id)) {
+ spkms.emplace_back(pwallet->SetupDescriptorScriptPubKeyMan(batch, active_hdkey, *output_type, internal));
+ }
+ }
+ if (spkms.empty()) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Descriptor already exists");
+ }
+
+ // Fetch each descspkm from the wallet in order to get the descriptor strings
+ UniValue descs{UniValue::VARR};
+ for (const auto& spkm : spkms) {
+ std::string desc_str;
+ bool ok = spkm.get().GetDescriptorString(desc_str, false);
+ CHECK_NONFATAL(ok);
+ descs.push_back(desc_str);
+ }
+ UniValue out{UniValue::VOBJ};
+ out.pushKV("descs", std::move(descs));
+ return out;
+ }
+ };
+}
+
// addresses
RPCHelpMan getaddressinfo();
RPCHelpMan getnewaddress();
@@ -896,6 +1109,7 @@ Span<const CRPCCommand> GetWalletRPCCommands()
{"wallet", &bumpfee},
{"wallet", &psbtbumpfee},
{"wallet", &createwallet},
+ {"wallet", &createwalletdescriptor},
{"wallet", &restorewallet},
{"wallet", &dumpprivkey},
{"wallet", &dumpwallet},
@@ -903,6 +1117,7 @@ Span<const CRPCCommand> GetWalletRPCCommands()
{"wallet", &getaddressesbylabel},
{"wallet", &getaddressinfo},
{"wallet", &getbalance},
+ {"wallet", &gethdkeys},
{"wallet", &getnewaddress},
{"wallet", &getrawchangeaddress},
{"wallet", &getreceivedbyaddress},