In 1571cdb00517a62769f635e077581c2095289646 “refactor: Add ImportDescriptor to CWallet”
This is close to a 200 line method being added in a file that’s already more than 4500 lines long and hard enough to follow - I don’t believe we should add more lengthy code in this file anymore. An alternative can be to add this implementation in a separate file while being in the same wallet namespace.
Something like below in a src/wallet/descriptor.cpp file, this is a starting point and not a full solution. PR #34681 does a simlar refactoring and can be referred to.
0#include <wallet/wallet.h>
1
2namespace wallet {
3 ImportDescriptorResult CWallet::ImportDescriptor(const std::string& descriptor,
4 bool active,
5 const std::optional<bool>& internal,
6 const std::optional<std::string>& label,
7 int64_t timestamp,
8 const std::optional<std::pair<int64_t, int64_t>>& range,
9 const std::optional<int64_t>& next_idx)
10 {
11 AssertLockHeld(cs_wallet);
12
13 ImportDescriptorResult result;
14
15 // Parse descriptor string
16 FlatSigningProvider keys;
17 std::string error;
18 auto parsed_descs = Parse(descriptor, keys, error, /*require_checksum=*/true);
19 if (parsed_descs.empty()) {
20 result.error = error;
21 result.reason = ImportDescriptorResult::FailureReason::INVALID_DESCRIPTOR;
22 return result;
23 }
24 if (internal.has_value() && parsed_descs.size() > 1) {
25 result.error = "Cannot have multipath descriptor while also specifying 'internal'";
26 result.reason = ImportDescriptorResult::FailureReason::INVALID_DESCRIPTOR;
27 return result;
28 }
29
30 // Range check
31 std::optional<bool> is_ranged;
32 int64_t range_start = 0, range_end = 1, next_index = 0;
33 if (!parsed_descs.at(0)->IsRange() && range.has_value()) {
34 result.error = "Range should not be specified for an un-ranged descriptor";
35 result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
36 return result;
37 } else if (parsed_descs.at(0)->IsRange()) {
38 if (range.has_value()) {
39 range_start = range->first;
40 range_end = range->second + 1;
41 } else {
42 result.warnings.emplace_back("Range not given, using default keypool range");
43 result.used_default_range = true;
44 range_start = 0;
45 range_end = m_keypool_size;
46 }
47 next_index = range_start;
48 is_ranged = true;
49
50 if (next_idx.has_value()) {
51 next_index = next_idx.value();
52 if (next_index < range_start || next_index >= range_end) {
53 result.error = "next_index is out of range";
54 result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
55 return result;
56 }
57 }
58 }
59
60 // Active descriptors must be ranged
61 if (active && !parsed_descs.at(0)->IsRange()) {
62 result.error = "Active descriptors must be ranged";
63 result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
64 return result;
65 }
66
67 // Multipath descriptors should not have a label
68 if (parsed_descs.size() > 1 && label.has_value()) {
69 result.error = "Multipath descriptors should not have a label";
70 result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
71 return result;
72 }
73
74 // Ranged descrptors should not have a label
75 if (is_ranged.has_value() && is_ranged.value() && label.has_value()) {
76 result.error = "Ranged descriptors should not have a label";
77 result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
78 return result;
79 }
80
81 // Internal descriptors should not have a label
82 bool desc_internal = internal.value_or(false);
83 if (desc_internal && label.has_value()) {
84 result.error = "Internal addresses should not have a label";
85 result.reason = ImportDescriptorResult::FailureReason::INVALID_PARAMETER;
86 return result;
87 }
88
89 //Combo descriptor check
90 if (active && !parsed_descs.at(0)->IsSingleType()) {
91 result.error = "Combo descriptors cannot be set to active";
92 result.reason = ImportDescriptorResult::FailureReason::WALLET_ERROR;
93 return result;
94 }
95
96 // If the wallet disabled private keys, abort if private keys exist
97 if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) {
98 result.error = "Cannot import private keys to a wallet with private keys disabled";
99 result.reason = ImportDescriptorResult::FailureReason::WALLET_ERROR;
100 return result;
101 }
102
103 const std::string label_str = label.value_or(std::string{});
104
105 for (size_t j = 0; j < parsed_descs.size(); ++j) {
106 auto parsed_desc = std::move(parsed_descs[j]);
107 if (parsed_descs.size() == 2) {
108 desc_internal = j == 1;
109 } else if (parsed_descs.size() > 2) {
110 CHECK_NONFATAL(!desc_internal);
111 }
112 // Need to ExpandPrivate to check if private keys are available for all pubkeys
113 FlatSigningProvider expand_keys;
114 std::vector<CScript> scripts;
115 if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) {
116 result.error = "Cannot expand descriptor. Probably because of hardened derivations without private keys provided";
117 result.reason = ImportDescriptorResult::FailureReason::WALLET_ERROR;
118 return result;
119 }
120 parsed_desc->ExpandPrivate(0, keys, expand_keys);
121
122 for (const auto& w : parsed_desc->Warnings()) {
123 result.warnings.push_back(w);
124 }
125
126 // Check if all private keys are provided
127 bool have_all_privkeys = !expand_keys.keys.empty();
128 for (const auto& entry : expand_keys.origins) {
129 const CKeyID& key_id = entry.first;
130 CKey key;
131 if (!expand_keys.GetKey(key_id, key)) {
132 have_all_privkeys = false;
133 break;
134 }
135 }
136
137 // If private keys are enabled, check some things.
138 if (!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
139 if (keys.keys.empty()) {
140 result.error = "Cannot import descriptor without private keys to a wallet with private keys enabled";
141 result.reason = ImportDescriptorResult::FailureReason::WALLET_ERROR;
142 return result;
143 }
144 if (!have_all_privkeys) {
145 result.warnings.emplace_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
146 }
147 }
148
149 WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);
150
151 // Add descriptor to wallet
152 auto spk_manager_res = AddWalletDescriptor(w_desc, keys, label_str, desc_internal);
153
154 if (!spk_manager_res) {
155 result.error = strprintf("Could not add descriptor '%s': %s", descriptor, util::ErrorString(spk_manager_res).original);
156 result.reason = ImportDescriptorResult::FailureReason::WALLET_ERROR;
157 return result;
158 }
159
160 auto& spk_manager = spk_manager_res.value().get();
161
162 // Set descriptor as active if necessary
163 if (active) {
164 if (!w_desc.descriptor->GetOutputType()) {
165 result.warnings.emplace_back("Unknown output type, cannot set descriptor to active.");
166 } else {
167 AddActiveScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
168 }
169 } else {
170 if (w_desc.descriptor->GetOutputType()) {
171 DeactivateScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
172 }
173 }
174 }
175
176 result.success = true;
177 return result;
178 }
179}