aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/rpc/backup.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/rpc/backup.cpp')
-rw-r--r--src/wallet/rpc/backup.cpp205
1 files changed, 122 insertions, 83 deletions
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",