Skip to content

Commit f97d5c1

Browse files
committed
wallet, rpc: Allow importdescriptors to import multipath descriptors
Multipath descriptors will be imported as multiple separate descriptors. When there are 2 multipath items, the first descriptor will be for receiving addresses and the second for change. This mirrors importmulti.
1 parent 32dcbca commit f97d5c1

File tree

1 file changed

+73
-53
lines changed

1 file changed

+73
-53
lines changed

src/wallet/rpc/backup.cpp

Lines changed: 73 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,7 +1463,6 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
14631463

14641464
const std::string& descriptor = data["desc"].get_str();
14651465
const bool active = data.exists("active") ? data["active"].get_bool() : false;
1466-
const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
14671466
const std::string label{LabelFromValue(data["label"])};
14681467

14691468
// Parse descriptor string
@@ -1473,13 +1472,19 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
14731472
if (parsed_descs.empty()) {
14741473
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
14751474
}
1476-
auto& parsed_desc = parsed_descs.at(0);
1475+
std::optional<bool> internal;
1476+
if (data.exists("internal")) {
1477+
if (parsed_descs.size() > 1) {
1478+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot have multipath descriptor while also specifying \'internal\'");
1479+
}
1480+
internal = data["internal"].get_bool();
1481+
}
14771482

14781483
// Range check
14791484
int64_t range_start = 0, range_end = 1, next_index = 0;
1480-
if (!parsed_desc->IsRange() && data.exists("range")) {
1485+
if (!parsed_descs.at(0)->IsRange() && data.exists("range")) {
14811486
throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
1482-
} else if (parsed_desc->IsRange()) {
1487+
} else if (parsed_descs.at(0)->IsRange()) {
14831488
if (data.exists("range")) {
14841489
auto range = ParseDescriptorRange(data["range"]);
14851490
range_start = range.first;
@@ -1501,10 +1506,15 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
15011506
}
15021507

15031508
// Active descriptors must be ranged
1504-
if (active && !parsed_desc->IsRange()) {
1509+
if (active && !parsed_descs.at(0)->IsRange()) {
15051510
throw JSONRPCError(RPC_INVALID_PARAMETER, "Active descriptors must be ranged");
15061511
}
15071512

1513+
// Multipath descriptors should not have a label
1514+
if (parsed_descs.size() > 1 && data.exists("label")) {
1515+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Multipath descriptors should not have a label");
1516+
}
1517+
15081518
// Ranged descriptors should not have a label
15091519
if (data.exists("range") && data.exists("label")) {
15101520
throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptors should not have a label");
@@ -1516,7 +1526,7 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
15161526
}
15171527

15181528
// Combo descriptor check
1519-
if (active && !parsed_desc->IsSingleType()) {
1529+
if (active && !parsed_descs.at(0)->IsSingleType()) {
15201530
throw JSONRPCError(RPC_WALLET_ERROR, "Combo descriptors cannot be set to active");
15211531
}
15221532

@@ -1525,61 +1535,70 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
15251535
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
15261536
}
15271537

1528-
// Need to ExpandPrivate to check if private keys are available for all pubkeys
1529-
FlatSigningProvider expand_keys;
1530-
std::vector<CScript> scripts;
1531-
if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) {
1532-
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot expand descriptor. Probably because of hardened derivations without private keys provided");
1533-
}
1534-
parsed_desc->ExpandPrivate(0, keys, expand_keys);
1535-
1536-
// Check if all private keys are provided
1537-
bool have_all_privkeys = !expand_keys.keys.empty();
1538-
for (const auto& entry : expand_keys.origins) {
1539-
const CKeyID& key_id = entry.first;
1540-
CKey key;
1541-
if (!expand_keys.GetKey(key_id, key)) {
1542-
have_all_privkeys = false;
1543-
break;
1538+
for (size_t j = 0; j < parsed_descs.size(); ++j) {
1539+
auto parsed_desc = std::move(parsed_descs[j]);
1540+
bool desc_internal = internal.has_value() && internal.value();
1541+
if (parsed_descs.size() == 2) {
1542+
desc_internal = j == 1;
1543+
} else if (parsed_descs.size() > 2) {
1544+
CHECK_NONFATAL(!desc_internal);
1545+
}
1546+
// Need to ExpandPrivate to check if private keys are available for all pubkeys
1547+
FlatSigningProvider expand_keys;
1548+
std::vector<CScript> scripts;
1549+
if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) {
1550+
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot expand descriptor. Probably because of hardened derivations without private keys provided");
1551+
}
1552+
parsed_desc->ExpandPrivate(0, keys, expand_keys);
1553+
1554+
// Check if all private keys are provided
1555+
bool have_all_privkeys = !expand_keys.keys.empty();
1556+
for (const auto& entry : expand_keys.origins) {
1557+
const CKeyID& key_id = entry.first;
1558+
CKey key;
1559+
if (!expand_keys.GetKey(key_id, key)) {
1560+
have_all_privkeys = false;
1561+
break;
1562+
}
15441563
}
1545-
}
15461564

1547-
// If private keys are enabled, check some things.
1548-
if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
1549-
if (keys.keys.empty()) {
1550-
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled");
1551-
}
1552-
if (!have_all_privkeys) {
1553-
warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
1554-
}
1555-
}
1565+
// If private keys are enabled, check some things.
1566+
if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
1567+
if (keys.keys.empty()) {
1568+
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled");
1569+
}
1570+
if (!have_all_privkeys) {
1571+
warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
1572+
}
1573+
}
15561574

1557-
WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);
1575+
WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);
15581576

1559-
// Check if the wallet already contains the descriptor
1560-
auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc);
1561-
if (existing_spk_manager) {
1562-
if (!existing_spk_manager->CanUpdateToWalletDescriptor(w_desc, error)) {
1563-
throw JSONRPCError(RPC_INVALID_PARAMETER, error);
1577+
// Check if the wallet already contains the descriptor
1578+
auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc);
1579+
if (existing_spk_manager) {
1580+
if (!existing_spk_manager->CanUpdateToWalletDescriptor(w_desc, error)) {
1581+
throw JSONRPCError(RPC_INVALID_PARAMETER, error);
1582+
}
15641583
}
1565-
}
15661584

1567-
// Add descriptor to the wallet
1568-
auto spk_manager = wallet.AddWalletDescriptor(w_desc, keys, label, internal);
1569-
if (spk_manager == nullptr) {
1570-
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor));
1571-
}
1585+
// Add descriptor to the wallet
1586+
auto spk_manager = wallet.AddWalletDescriptor(w_desc, keys, label, desc_internal);
1587+
if (spk_manager == nullptr) {
1588+
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor));
1589+
}
15721590

1573-
// Set descriptor as active if necessary
1574-
if (active) {
1575-
if (!w_desc.descriptor->GetOutputType()) {
1576-
warnings.push_back("Unknown output type, cannot set descriptor to active.");
1591+
// Set descriptor as active if necessary
1592+
if (active) {
1593+
if (!w_desc.descriptor->GetOutputType()) {
1594+
warnings.push_back("Unknown output type, cannot set descriptor to active.");
1595+
} else {
1596+
wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
1597+
}
15771598
} else {
1578-
wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal);
1579-
}
1580-
} else {
1581-
if (w_desc.descriptor->GetOutputType()) {
1582-
wallet.DeactivateScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal);
1599+
if (w_desc.descriptor->GetOutputType()) {
1600+
wallet.DeactivateScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
1601+
}
15831602
}
15841603
}
15851604

@@ -1596,6 +1615,7 @@ RPCHelpMan importdescriptors()
15961615
{
15971616
return RPCHelpMan{"importdescriptors",
15981617
"\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"
1618+
"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"
15991619
"\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
16001620
"may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
16011621
"The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n",

0 commit comments

Comments
 (0)