diff options
Diffstat (limited to 'src/wallet/rpc')
-rw-r--r-- | src/wallet/rpc/addresses.cpp | 9 | ||||
-rw-r--r-- | src/wallet/rpc/backup.cpp | 205 | ||||
-rw-r--r-- | src/wallet/rpc/coins.cpp | 8 | ||||
-rw-r--r-- | src/wallet/rpc/spend.cpp | 38 | ||||
-rw-r--r-- | src/wallet/rpc/transactions.cpp | 8 | ||||
-rw-r--r-- | src/wallet/rpc/util.cpp | 2 | ||||
-rw-r--r-- | src/wallet/rpc/wallet.cpp | 8 |
7 files changed, 167 insertions, 111 deletions
diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp index 0c2ad06eea..1c2951deee 100644 --- a/src/wallet/rpc/addresses.cpp +++ b/src/wallet/rpc/addresses.cpp @@ -1,8 +1,8 @@ -// Copyright (c) 2011-2022 The Bitcoin Core developers +// Copyright (c) 2011-present 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 <config/bitcoin-config.h> // IWYU pragma: keep +#include <bitcoin-build-config.h> // IWYU pragma: keep #include <core_io.h> #include <key_io.h> @@ -501,6 +501,7 @@ public: } UniValue operator()(const WitnessV1Taproot& id) const { return UniValue(UniValue::VOBJ); } + UniValue operator()(const PayToAnchor& id) const { return UniValue(UniValue::VOBJ); } UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); } }; @@ -528,13 +529,13 @@ RPCHelpMan getaddressinfo() RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "address", "The bitcoin address validated."}, - {RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded scriptPubKey generated by the address."}, + {RPCResult::Type::STR_HEX, "scriptPubKey", "The hex-encoded output script generated by the address."}, {RPCResult::Type::BOOL, "ismine", "If the address is yours."}, {RPCResult::Type::BOOL, "iswatchonly", "If the address is watchonly."}, {RPCResult::Type::BOOL, "solvable", "If we know how to spend coins sent to this address, ignoring the possible lack of private keys."}, {RPCResult::Type::STR, "desc", /*optional=*/true, "A descriptor for spending coins sent to this address (only when solvable)."}, {RPCResult::Type::STR, "parent_desc", /*optional=*/true, "The descriptor used to derive this address if this is a descriptor wallet"}, - {RPCResult::Type::BOOL, "isscript", "If the key is a script."}, + {RPCResult::Type::BOOL, "isscript", /*optional=*/true, "If the key is a script."}, {RPCResult::Type::BOOL, "ischange", "If the address was used for change output."}, {RPCResult::Type::BOOL, "iswitness", "If the address is a witness address."}, {RPCResult::Type::NUM, "witness_version", /*optional=*/true, "The version number of the witness program."}, diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp index 8cddb8b099..4ffc6f1e0d 100644 --- a/src/wallet/rpc/backup.cpp +++ b/src/wallet/rpc/backup.cpp @@ -2,7 +2,7 @@ // 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 <bitcoin-build-config.h> // IWYU pragma: keep #include <chain.h> #include <clientversion.h> @@ -471,7 +471,7 @@ RPCHelpMan importpubkey() pwallet->ImportScriptPubKeys(strLabel, script_pub_keys, /*have_solving_data=*/true, /*apply_label=*/true, /*timestamp=*/1); - pwallet->ImportPubKeys({pubKey.GetID()}, {{pubKey.GetID(), pubKey}} , /*key_origins=*/{}, /*add_keypool=*/false, /*internal=*/false, /*timestamp=*/1); + pwallet->ImportPubKeys({{pubKey.GetID(), false}}, {{pubKey.GetID(), pubKey}} , /*key_origins=*/{}, /*add_keypool=*/false, /*timestamp=*/1); } if (fRescan) { @@ -910,12 +910,13 @@ static std::string RecurseImportData(const CScript& script, ImportData& import_d case TxoutType::NONSTANDARD: case TxoutType::WITNESS_UNKNOWN: case TxoutType::WITNESS_V1_TAPROOT: + case TxoutType::ANCHOR: return "unrecognized script"; } // no default case, so the compiler can warn about missing cases NONFATAL_UNREACHABLE(); } -static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys) +static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<std::pair<CKeyID, bool>>& ordered_pubkeys) { UniValue warnings(UniValue::VARR); @@ -981,7 +982,7 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP for (size_t i = 0; i < pubKeys.size(); ++i) { CPubKey pubkey = HexToPubKey(pubKeys[i].get_str()); pubkey_map.emplace(pubkey.GetID(), pubkey); - ordered_pubkeys.push_back(pubkey.GetID()); + ordered_pubkeys.emplace_back(pubkey.GetID(), internal); } for (size_t i = 0; i < keys.size(); ++i) { const auto& str = keys[i].get_str(); @@ -1054,28 +1055,36 @@ static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CP return warnings; } -static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<CKeyID>& ordered_pubkeys) +static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<std::pair<CKeyID, bool>>& ordered_pubkeys) { UniValue warnings(UniValue::VARR); const std::string& descriptor = data["desc"].get_str(); FlatSigningProvider keys; std::string error; - auto parsed_desc = Parse(descriptor, keys, error, /* require_checksum = */ true); - if (!parsed_desc) { + auto parsed_descs = Parse(descriptor, keys, error, /* require_checksum = */ true); + if (parsed_descs.empty()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } - if (parsed_desc->GetOutputType() == OutputType::BECH32M) { + if (parsed_descs.at(0)->GetOutputType() == OutputType::BECH32M) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m descriptors cannot be imported into legacy wallets"); } - have_solving_data = parsed_desc->IsSolvable(); + std::optional<bool> internal; + if (data.exists("internal")) { + if (parsed_descs.size() > 1) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot have multipath descriptor while also specifying \'internal\'"); + } + internal = data["internal"].get_bool(); + } + + have_solving_data = parsed_descs.at(0)->IsSolvable(); const bool watch_only = data.exists("watchonly") ? data["watchonly"].get_bool() : false; int64_t range_start = 0, range_end = 0; - if (!parsed_desc->IsRange() && data.exists("range")) { + if (!parsed_descs.at(0)->IsRange() && data.exists("range")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); - } else if (parsed_desc->IsRange()) { + } else if (parsed_descs.at(0)->IsRange()) { if (!data.exists("range")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor is ranged, please specify the range"); } @@ -1084,25 +1093,34 @@ static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); - // Expand all descriptors to get public keys and scripts, and private keys if available. - for (int i = range_start; i <= range_end; ++i) { - FlatSigningProvider out_keys; - std::vector<CScript> scripts_temp; - parsed_desc->Expand(i, keys, scripts_temp, out_keys); - std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end())); - for (const auto& key_pair : out_keys.pubkeys) { - ordered_pubkeys.push_back(key_pair.first); - } + for (size_t j = 0; j < parsed_descs.size(); ++j) { + const auto& parsed_desc = parsed_descs.at(j); + bool desc_internal = internal.has_value() && internal.value(); + if (parsed_descs.size() == 2) { + desc_internal = j == 1; + } else if (parsed_descs.size() > 2) { + CHECK_NONFATAL(!desc_internal); + } + // Expand all descriptors to get public keys and scripts, and private keys if available. + for (int i = range_start; i <= range_end; ++i) { + FlatSigningProvider out_keys; + std::vector<CScript> scripts_temp; + parsed_desc->Expand(i, keys, scripts_temp, out_keys); + std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end())); + for (const auto& key_pair : out_keys.pubkeys) { + ordered_pubkeys.emplace_back(key_pair.first, desc_internal); + } - for (const auto& x : out_keys.scripts) { - import_data.import_scripts.emplace(x.second); - } + for (const auto& x : out_keys.scripts) { + import_data.import_scripts.emplace(x.second); + } - parsed_desc->ExpandPrivate(i, keys, out_keys); + parsed_desc->ExpandPrivate(i, keys, out_keys); - std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end())); - std::copy(out_keys.keys.begin(), out_keys.keys.end(), std::inserter(privkey_map, privkey_map.end())); - import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end()); + std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end())); + std::copy(out_keys.keys.begin(), out_keys.keys.end(), std::inserter(privkey_map, privkey_map.end())); + import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end()); + } } for (size_t i = 0; i < priv_keys.size(); ++i) { @@ -1166,7 +1184,7 @@ static UniValue ProcessImport(CWallet& wallet, const UniValue& data, const int64 std::map<CKeyID, CPubKey> pubkey_map; std::map<CKeyID, CKey> privkey_map; std::set<CScript> script_pub_keys; - std::vector<CKeyID> ordered_pubkeys; + std::vector<std::pair<CKeyID, bool>> ordered_pubkeys; bool have_solving_data; if (data.exists("scriptPubKey") && data.exists("desc")) { @@ -1199,7 +1217,7 @@ static UniValue ProcessImport(CWallet& wallet, const UniValue& data, const int64 if (!wallet.ImportPrivKeys(privkey_map, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } - if (!wallet.ImportPubKeys(ordered_pubkeys, pubkey_map, import_data.key_origins, add_keypool, internal, timestamp)) { + if (!wallet.ImportPubKeys(ordered_pubkeys, pubkey_map, import_data.key_origins, add_keypool, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } if (!wallet.ImportScriptPubKeys(label, script_pub_keys, have_solving_data, !internal, timestamp)) { @@ -1446,22 +1464,28 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c const std::string& descriptor = data["desc"].get_str(); const bool active = data.exists("active") ? data["active"].get_bool() : false; - const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; const std::string label{LabelFromValue(data["label"])}; // Parse descriptor string FlatSigningProvider keys; std::string error; - auto parsed_desc = Parse(descriptor, keys, error, /* require_checksum = */ true); - if (!parsed_desc) { + auto parsed_descs = Parse(descriptor, keys, error, /* require_checksum = */ true); + if (parsed_descs.empty()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } + std::optional<bool> internal; + if (data.exists("internal")) { + if (parsed_descs.size() > 1) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot have multipath descriptor while also specifying \'internal\'"); + } + internal = data["internal"].get_bool(); + } // Range check int64_t range_start = 0, range_end = 1, next_index = 0; - if (!parsed_desc->IsRange() && data.exists("range")) { + if (!parsed_descs.at(0)->IsRange() && data.exists("range")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); - } else if (parsed_desc->IsRange()) { + } else if (parsed_descs.at(0)->IsRange()) { if (data.exists("range")) { auto range = ParseDescriptorRange(data["range"]); range_start = range.first; @@ -1483,10 +1507,15 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c } // Active descriptors must be ranged - if (active && !parsed_desc->IsRange()) { + if (active && !parsed_descs.at(0)->IsRange()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Active descriptors must be ranged"); } + // Multipath descriptors should not have a label + if (parsed_descs.size() > 1 && data.exists("label")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Multipath descriptors should not have a label"); + } + // Ranged descriptors should not have a label if (data.exists("range") && data.exists("label")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptors should not have a label"); @@ -1498,7 +1527,7 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c } // Combo descriptor check - if (active && !parsed_desc->IsSingleType()) { + if (active && !parsed_descs.at(0)->IsSingleType()) { throw JSONRPCError(RPC_WALLET_ERROR, "Combo descriptors cannot be set to active"); } @@ -1507,61 +1536,70 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled"); } - // Need to ExpandPrivate to check if private keys are available for all pubkeys - FlatSigningProvider expand_keys; - std::vector<CScript> scripts; - if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Cannot expand descriptor. Probably because of hardened derivations without private keys provided"); - } - parsed_desc->ExpandPrivate(0, keys, expand_keys); - - // Check if all private keys are provided - bool have_all_privkeys = !expand_keys.keys.empty(); - for (const auto& entry : expand_keys.origins) { - const CKeyID& key_id = entry.first; - CKey key; - if (!expand_keys.GetKey(key_id, key)) { - have_all_privkeys = false; - break; + for (size_t j = 0; j < parsed_descs.size(); ++j) { + auto parsed_desc = std::move(parsed_descs[j]); + bool desc_internal = internal.has_value() && internal.value(); + if (parsed_descs.size() == 2) { + desc_internal = j == 1; + } else if (parsed_descs.size() > 2) { + CHECK_NONFATAL(!desc_internal); + } + // Need to ExpandPrivate to check if private keys are available for all pubkeys + FlatSigningProvider expand_keys; + std::vector<CScript> scripts; + if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Cannot expand descriptor. Probably because of hardened derivations without private keys provided"); + } + parsed_desc->ExpandPrivate(0, keys, expand_keys); + + // Check if all private keys are provided + bool have_all_privkeys = !expand_keys.keys.empty(); + for (const auto& entry : expand_keys.origins) { + const CKeyID& key_id = entry.first; + CKey key; + if (!expand_keys.GetKey(key_id, key)) { + have_all_privkeys = false; + break; + } } - } - // If private keys are enabled, check some things. - if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { - if (keys.keys.empty()) { - throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled"); - } - if (!have_all_privkeys) { - warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors"); - } - } + // If private keys are enabled, check some things. + if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (keys.keys.empty()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled"); + } + if (!have_all_privkeys) { + warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors"); + } + } - WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index); + WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index); - // Check if the wallet already contains the descriptor - auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc); - if (existing_spk_manager) { - if (!existing_spk_manager->CanUpdateToWalletDescriptor(w_desc, error)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, error); + // Check if the wallet already contains the descriptor + auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc); + if (existing_spk_manager) { + if (!existing_spk_manager->CanUpdateToWalletDescriptor(w_desc, error)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, error); + } } - } - // Add descriptor to the wallet - auto spk_manager = wallet.AddWalletDescriptor(w_desc, keys, label, internal); - if (spk_manager == nullptr) { - throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor)); - } + // Add descriptor to the wallet + auto spk_manager = wallet.AddWalletDescriptor(w_desc, keys, label, desc_internal); + if (spk_manager == nullptr) { + throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor)); + } - // Set descriptor as active if necessary - if (active) { - if (!w_desc.descriptor->GetOutputType()) { - warnings.push_back("Unknown output type, cannot set descriptor to active."); + // Set descriptor as active if necessary + if (active) { + if (!w_desc.descriptor->GetOutputType()) { + warnings.push_back("Unknown output type, cannot set descriptor to active."); + } else { + wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), desc_internal); + } } else { - wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal); - } - } else { - if (w_desc.descriptor->GetOutputType()) { - wallet.DeactivateScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal); + if (w_desc.descriptor->GetOutputType()) { + wallet.DeactivateScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), desc_internal); + } } } @@ -1578,6 +1616,7 @@ RPCHelpMan importdescriptors() { return RPCHelpMan{"importdescriptors", "\nImport descriptors. This will trigger a rescan of the blockchain based on the earliest timestamp of all descriptors being imported. Requires a new wallet backup.\n" + "When importing descriptors with multipath key expressions, if the multipath specifier contains exactly two elements, the descriptor produced from the second elements will be imported as an internal descriptor.\n" "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n" "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n" "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n", diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp index 2cf94a5722..f1430a3c60 100644 --- a/src/wallet/rpc/coins.cpp +++ b/src/wallet/rpc/coins.cpp @@ -530,19 +530,19 @@ RPCHelpMan listunspent() {RPCResult::Type::NUM, "vout", "the vout value"}, {RPCResult::Type::STR, "address", /*optional=*/true, "the bitcoin address"}, {RPCResult::Type::STR, "label", /*optional=*/true, "The associated label, or \"\" for the default label"}, - {RPCResult::Type::STR, "scriptPubKey", "the script key"}, + {RPCResult::Type::STR, "scriptPubKey", "the output script"}, {RPCResult::Type::STR_AMOUNT, "amount", "the transaction output amount in " + CURRENCY_UNIT}, {RPCResult::Type::NUM, "confirmations", "The number of confirmations"}, {RPCResult::Type::NUM, "ancestorcount", /*optional=*/true, "The number of in-mempool ancestor transactions, including this one (if transaction is in the mempool)"}, {RPCResult::Type::NUM, "ancestorsize", /*optional=*/true, "The virtual transaction size of in-mempool ancestors, including this one (if transaction is in the mempool)"}, {RPCResult::Type::STR_AMOUNT, "ancestorfees", /*optional=*/true, "The total fees of in-mempool ancestors (including this one) with fee deltas used for mining priority in " + CURRENCY_ATOM + " (if transaction is in the mempool)"}, - {RPCResult::Type::STR_HEX, "redeemScript", /*optional=*/true, "The redeemScript if scriptPubKey is P2SH"}, - {RPCResult::Type::STR, "witnessScript", /*optional=*/true, "witnessScript if the scriptPubKey is P2WSH or P2SH-P2WSH"}, + {RPCResult::Type::STR_HEX, "redeemScript", /*optional=*/true, "The redeem script if the output script is P2SH"}, + {RPCResult::Type::STR, "witnessScript", /*optional=*/true, "witness script if the output script is P2WSH or P2SH-P2WSH"}, {RPCResult::Type::BOOL, "spendable", "Whether we have the private keys to spend this output"}, {RPCResult::Type::BOOL, "solvable", "Whether we know how to spend this output, ignoring the lack of keys"}, {RPCResult::Type::BOOL, "reused", /*optional=*/true, "(only present if avoid_reuse is set) Whether this output is reused/dirty (sent to an address that was previously spent from)"}, {RPCResult::Type::STR, "desc", /*optional=*/true, "(only when solvable) A descriptor for spending this output"}, - {RPCResult::Type::ARR, "parent_descs", /*optional=*/false, "List of parent descriptors for the scriptPubKey of this coin.", { + {RPCResult::Type::ARR, "parent_descs", /*optional=*/false, "List of parent descriptors for the output script of this coin.", { {RPCResult::Type::STR, "desc", "The descriptor string."}, }}, {RPCResult::Type::BOOL, "safe", "Whether this output is considered safe to spend. Unconfirmed transactions\n" diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp index ac2a4826f0..bea9b2eec1 100644 --- a/src/wallet/rpc/spend.cpp +++ b/src/wallet/rpc/spend.cpp @@ -24,7 +24,7 @@ #include <univalue.h> using common::FeeModeFromString; -using common::FeeModes; +using common::FeeModesDetail; using common::InvalidEstimateModeErrorMessage; using common::StringForFeeReason; using common::TransactionErrorString; @@ -245,7 +245,7 @@ RPCHelpMan sendtoaddress() {"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Signal that this transaction can be replaced by a transaction (BIP 125)"}, {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" - "\"" + FeeModes("\"\n\"") + "\""}, + + FeeModesDetail(std::string("economical mode is used if the transaction is replaceable;\notherwise, conservative mode is used"))}, {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{true}, "(only available if avoid_reuse wallet flag is set) Avoid spending from dirty addresses; addresses are considered\n" "dirty if they have previously been used in a transaction. If true, this also activates avoidpartialspends, grouping outputs by their addresses."}, {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."}, @@ -349,7 +349,7 @@ RPCHelpMan sendmany() {"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Signal that this transaction can be replaced by a transaction (BIP 125)"}, {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" - "\"" + FeeModes("\"\n\"") + "\""}, + + FeeModesDetail(std::string("economical mode is used if the transaction is replaceable;\notherwise, conservative mode is used"))}, {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."}, {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."}, }, @@ -463,7 +463,7 @@ static std::vector<RPCArg> FundTxDoc(bool solving_data = true) std::vector<RPCArg> args = { {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks", RPCArgOptions{.also_positional = true}}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" - "\"" + FeeModes("\"\n\"") + "\"", RPCArgOptions{.also_positional = true}}, + + FeeModesDetail(std::string("economical mode is used if the transaction is replaceable;\notherwise, conservative mode is used")), RPCArgOptions{.also_positional = true}}, { "replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Marks this transaction as BIP125-replaceable.\n" "Allows this transaction to be replaced by a transaction with higher fees" @@ -542,6 +542,7 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact {"minconf", UniValueType(UniValue::VNUM)}, {"maxconf", UniValueType(UniValue::VNUM)}, {"input_weights", UniValueType(UniValue::VARR)}, + {"max_tx_weight", UniValueType(UniValue::VNUM)}, }, true, true); @@ -660,11 +661,13 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact FlatSigningProvider desc_out; std::string error; std::vector<CScript> scripts_temp; - std::unique_ptr<Descriptor> desc = Parse(desc_str, desc_out, error, true); - if (!desc) { + auto descs = Parse(desc_str, desc_out, error, true); + if (descs.empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unable to parse descriptor '%s': %s", desc_str, error)); } - desc->Expand(0, desc_out, scripts_temp, desc_out); + for (auto& desc : descs) { + desc->Expand(0, desc_out, scripts_temp, desc_out); + } coinControl.m_external_provider.Merge(std::move(desc_out)); } } @@ -701,6 +704,10 @@ CreatedTransactionResult FundTransaction(CWallet& wallet, const CMutableTransact } } + if (options.exists("max_tx_weight")) { + coinControl.m_max_tx_weight = options["max_tx_weight"].getInt<int>(); + } + if (recipients.empty()) throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output"); @@ -786,6 +793,8 @@ RPCHelpMan fundrawtransaction() }, }, }, + {"max_tx_weight", RPCArg::Type::NUM, RPCArg::Default{MAX_STANDARD_TX_WEIGHT}, "The maximum acceptable transaction weight.\n" + "Transaction building will fail if this can not be satisfied."}, }, FundTxDoc()), RPCArgOptions{ @@ -875,7 +884,7 @@ RPCHelpMan signrawtransactionwithwallet() { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, - {"scriptPubKey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "script key"}, + {"scriptPubKey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The output script"}, {"redeemScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2SH) redeem script"}, {"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2WSH or P2SH-P2WSH) witness script"}, {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED, "(required for Segwit inputs) the amount spent"}, @@ -1011,7 +1020,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name) "still be replaceable in practice, for example if it has unconfirmed ancestors which\n" "are replaceable).\n"}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" - "\"" + FeeModes("\"\n\"") + "\""}, + + FeeModesDetail(std::string("economical mode is used if the transaction is replaceable;\notherwise, conservative mode is used"))}, {"outputs", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "The outputs specified as key-value pairs.\n" "Each key may only appear once, i.e. there can only be one 'data' output, and no address may be duplicated.\n" "At least one output of either type must be specified.\n" @@ -1198,7 +1207,7 @@ RPCHelpMan send() RPCArgOptions{.skip_type_check = true}}, {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" - "\"" + FeeModes("\"\n\"") + "\""}, + + FeeModesDetail(std::string("economical mode is used if the transaction is replaceable;\notherwise, conservative mode is used"))}, {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."}, {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", Cat<std::vector<RPCArg>>( @@ -1240,6 +1249,8 @@ RPCHelpMan send() {"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."}, }, }, + {"max_tx_weight", RPCArg::Type::NUM, RPCArg::Default{MAX_STANDARD_TX_WEIGHT}, "The maximum acceptable transaction weight.\n" + "Transaction building will fail if this can not be satisfied."}, }, FundTxDoc()), RPCArgOptions{.oneline_description="options"}}, @@ -1287,6 +1298,9 @@ RPCHelpMan send() // Automatically select coins, unless at least one is manually selected. Can // be overridden by options.add_inputs. coin_control.m_allow_other_inputs = rawTx.vin.size() == 0; + if (options.exists("max_tx_weight")) { + coin_control.m_max_tx_weight = options["max_tx_weight"].getInt<int>(); + } SetOptionsInputWeights(options["inputs"], options); // Clear tx.vout since it is not meant to be used now that we are passing outputs directly. // This sets us up for a future PR to completely remove tx from the function signature in favor of passing inputs directly @@ -1319,7 +1333,7 @@ RPCHelpMan sendall() }, {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, "The fee estimate mode, must be one of (case insensitive):\n" - "\"" + FeeModes("\"\n\"") + "\""}, + + FeeModesDetail(std::string("economical mode is used if the transaction is replaceable;\notherwise, conservative mode is used"))}, {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."}, { "options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "", @@ -1697,6 +1711,8 @@ RPCHelpMan walletcreatefundedpsbt() {"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."}, }, }, + {"max_tx_weight", RPCArg::Type::NUM, RPCArg::Default{MAX_STANDARD_TX_WEIGHT}, "The maximum acceptable transaction weight.\n" + "Transaction building will fail if this can not be satisfied."}, }, FundTxDoc()), RPCArgOptions{.oneline_description="options"}}, diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp index 5abc983701..61cf36a6c1 100644 --- a/src/wallet/rpc/transactions.cpp +++ b/src/wallet/rpc/transactions.cpp @@ -415,13 +415,13 @@ static std::vector<RPCResult> TransactionDescriptionString() {RPCResult::Type::NUM_TIME, "blocktime", /*optional=*/true, "The block time expressed in " + UNIX_EPOCH_TIME + "."}, {RPCResult::Type::STR_HEX, "txid", "The transaction id."}, {RPCResult::Type::STR_HEX, "wtxid", "The hash of serialized transaction, including witness data."}, - {RPCResult::Type::ARR, "walletconflicts", "Conflicting transaction ids.", + {RPCResult::Type::ARR, "walletconflicts", "Confirmed transactions that have been detected by the wallet to conflict with this transaction.", { {RPCResult::Type::STR_HEX, "txid", "The transaction id."}, }}, {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::ARR, "mempoolconflicts", "Transactions in the mempool that directly conflict with either this transaction or an ancestor transaction", { {RPCResult::Type::STR_HEX, "txid", "The transaction id."}, }}, @@ -431,7 +431,7 @@ static std::vector<RPCResult> TransactionDescriptionString() {RPCResult::Type::STR, "comment", /*optional=*/true, "If a comment is associated with the transaction, only present if not empty."}, {RPCResult::Type::STR, "bip125-replaceable", "(\"yes|no|unknown\") Whether this transaction signals BIP125 replaceability or has an unconfirmed ancestor signaling BIP125 replaceability.\n" "May be unknown for unconfirmed transactions not in the mempool because their unconfirmed ancestors are unknown."}, - {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the scriptPubKey of this coin.", { + {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the output script of this coin.", { {RPCResult::Type::STR, "desc", "The descriptor string."}, }}, }; @@ -729,7 +729,7 @@ RPCHelpMan gettransaction() {RPCResult::Type::STR_AMOUNT, "fee", /*optional=*/true, "The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" "'send' category of transactions."}, {RPCResult::Type::BOOL, "abandoned", "'true' if the transaction has been abandoned (inputs are respendable)."}, - {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the scriptPubKey of this coin.", { + {RPCResult::Type::ARR, "parent_descs", /*optional=*/true, "Only if 'category' is 'received'. List of parent descriptors for the output script of this coin.", { {RPCResult::Type::STR, "desc", "The descriptor string."}, }}, }}, diff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp index 67b5ae0fe2..ec3b7c1085 100644 --- a/src/wallet/rpc/util.cpp +++ b/src/wallet/rpc/util.cpp @@ -91,7 +91,7 @@ std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& reques RPC_WALLET_NOT_FOUND, "No wallet is loaded. Load a wallet using loadwallet or create a new one with createwallet. (Note: A default wallet is no longer automatically created)"); } throw JSONRPCError(RPC_WALLET_NOT_SPECIFIED, - "Wallet file not specified (must request wallet RPC through /wallet/<filename> uri-path)."); + "Multiple wallets are loaded. Please select which wallet to use by requesting the RPC through the /wallet/<walletname> URI path."); } void EnsureWalletIsUnlocked(const CWallet& wallet) diff --git a/src/wallet/rpc/wallet.cpp b/src/wallet/rpc/wallet.cpp index 8c218ad766..5140ac8c05 100644 --- a/src/wallet/rpc/wallet.cpp +++ b/src/wallet/rpc/wallet.cpp @@ -3,7 +3,7 @@ // 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 <bitcoin-build-config.h> // IWYU pragma: keep #include <core_io.h> #include <key_io.h> @@ -68,7 +68,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, "descriptors", "whether this wallet uses descriptors for output script management"}, {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"}, {RPCResult::Type::BOOL, "blank", "Whether this wallet intentionally does not contain any keys, scripts, or descriptors"}, {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."}, @@ -169,7 +169,7 @@ static RPCHelpMan listwalletdir() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { UniValue wallets(UniValue::VARR); - for (const auto& path : ListDatabases(GetWalletDir())) { + for (const auto& [path, _] : ListDatabases(GetWalletDir())) { UniValue wallet(UniValue::VOBJ); wallet.pushKV("name", path.utf8string()); wallets.push_back(std::move(wallet)); @@ -495,7 +495,7 @@ static RPCHelpMan unloadwallet() } } - UnloadWallet(std::move(wallet)); + WaitForDeleteWallet(std::move(wallet)); UniValue result(UniValue::VOBJ); PushWarnings(warnings, result); |