Branch data Line data Source code
1 : : // Copyright (c) 2022 The Bitcoin Core developers
2 : : // Distributed under the MIT software license, see the accompanying
3 : : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 : :
5 : : #include <policy/feerate.h>
6 : : #include <policy/policy.h>
7 : : #include <primitives/transaction.h>
8 : : #include <test/fuzz/FuzzedDataProvider.h>
9 : : #include <test/fuzz/fuzz.h>
10 : : #include <test/fuzz/util.h>
11 : : #include <test/util/setup_common.h>
12 : : #include <wallet/coinselection.h>
13 : :
14 : : #include <numeric>
15 : : #include <vector>
16 : :
17 : : namespace wallet {
18 : :
19 : 451284 : static void AddCoin(const CAmount& value, int n_input, int n_input_bytes, int locktime, std::vector<COutput>& coins, CFeeRate fee_rate)
20 : : {
21 : 451284 : CMutableTransaction tx;
22 [ + - ]: 451284 : tx.vout.resize(n_input + 1);
23 [ + - ]: 451284 : tx.vout[n_input].nValue = value;
24 : 451284 : tx.nLockTime = locktime; // all transactions get different hashes
25 [ + - + - : 451284 : coins.emplace_back(COutPoint(tx.GetHash(), n_input), tx.vout.at(n_input), /*depth=*/0, n_input_bytes, /*spendable=*/true, /*solvable=*/true, /*safe=*/true, /*time=*/0, /*from_me=*/true, fee_rate);
+ - ]
26 : 451284 : }
27 : :
28 : : // Randomly distribute coins to instances of OutputGroup
29 : 1536 : static void GroupCoins(FuzzedDataProvider& fuzzed_data_provider, const std::vector<COutput>& coins, const CoinSelectionParams& coin_params, bool positive_only, std::vector<OutputGroup>& output_groups)
30 : : {
31 : 1536 : auto output_group = OutputGroup(coin_params);
32 : 1536 : bool valid_outputgroup{false};
33 [ + + ]: 719784 : for (auto& coin : coins) {
34 [ + + + + ]: 718248 : if (!positive_only || (positive_only && coin.GetEffectiveValue() > 0)) {
35 [ + - + - ]: 1331654 : output_group.Insert(std::make_shared<COutput>(coin), /*ancestors=*/0, /*descendants=*/0);
36 : : }
37 : : // If positive_only was specified, nothing was inserted, leading to an empty output group
38 : : // that would be invalid for the BnB algorithm
39 [ + + + - : 718248 : valid_outputgroup = !positive_only || output_group.GetSelectionAmount() > 0;
+ + ]
40 [ + + ]: 666677 : if (valid_outputgroup && fuzzed_data_provider.ConsumeBool()) {
41 [ + - ]: 357886 : output_groups.push_back(output_group);
42 : 357886 : output_group = OutputGroup(coin_params);
43 : 357886 : valid_outputgroup = false;
44 : : }
45 : : }
46 [ + + + - ]: 1536 : if (valid_outputgroup) output_groups.push_back(output_group);
47 : 1536 : }
48 : :
49 : 1569 : static CAmount CreateCoins(FuzzedDataProvider& fuzzed_data_provider, std::vector<COutput>& utxo_pool, CoinSelectionParams& coin_params, int& next_locktime)
50 : : {
51 : 1569 : CAmount total_balance{0};
52 [ + + + + ]: 355923 : LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000)
53 : : {
54 : 354657 : const int n_input{fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 10)};
55 : 354657 : const int n_input_bytes{fuzzed_data_provider.ConsumeIntegralInRange<int>(41, 10000)};
56 : 354657 : const CAmount amount{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY)};
57 [ + + ]: 354657 : if (total_balance + amount >= MAX_MONEY) {
58 : : break;
59 : : }
60 : 354354 : AddCoin(amount, n_input, n_input_bytes, ++next_locktime, utxo_pool, coin_params.m_effective_feerate);
61 : 354354 : total_balance += amount;
62 : : }
63 : :
64 : 1569 : return total_balance;
65 : : }
66 : :
67 : 118 : static SelectionResult ManualSelection(std::vector<COutput>& utxos, const CAmount& total_amount, const bool& subtract_fee_outputs)
68 : : {
69 : 118 : SelectionResult result(total_amount, SelectionAlgorithm::MANUAL);
70 : 118 : std::set<std::shared_ptr<COutput>> utxo_pool;
71 [ + + ]: 10925 : for (const auto& utxo : utxos) {
72 [ + - + - ]: 21614 : utxo_pool.insert(std::make_shared<COutput>(utxo));
73 : : }
74 [ + - ]: 118 : result.AddInputs(utxo_pool, subtract_fee_outputs);
75 : 118 : return result;
76 : 118 : }
77 : :
78 : : // Returns true if the result contains an error and the message is not empty
79 : 592 : static bool HasErrorMsg(const util::Result<SelectionResult>& res) { return !util::ErrorString(res).empty(); }
80 : :
81 [ + - ]: 902 : FUZZ_TARGET(coin_grinder)
82 : : {
83 : 490 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
84 : 490 : std::vector<COutput> utxo_pool;
85 : :
86 : 490 : const CAmount target{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY)};
87 : :
88 : 490 : FastRandomContext fast_random_context{ConsumeUInt256(fuzzed_data_provider)};
89 : 490 : CoinSelectionParams coin_params{fast_random_context};
90 : 490 : coin_params.m_subtract_fee_outputs = fuzzed_data_provider.ConsumeBool();
91 : 490 : coin_params.m_long_term_feerate = CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
92 : 490 : coin_params.m_effective_feerate = CFeeRate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
93 : 490 : coin_params.change_output_size = fuzzed_data_provider.ConsumeIntegralInRange<int>(10, 1000);
94 : 490 : coin_params.change_spend_size = fuzzed_data_provider.ConsumeIntegralInRange<int>(10, 1000);
95 [ + - + - ]: 490 : coin_params.m_cost_of_change= coin_params.m_effective_feerate.GetFee(coin_params.change_output_size) + coin_params.m_long_term_feerate.GetFee(coin_params.change_spend_size);
96 [ + - ]: 490 : coin_params.m_change_fee = coin_params.m_effective_feerate.GetFee(coin_params.change_output_size);
97 : : // For other results to be comparable to SRD, we must align the change_target with SRD’s hardcoded behavior
98 : 490 : coin_params.m_min_change_target = CHANGE_LOWER + coin_params.m_change_fee;
99 : :
100 : : // Create some coins
101 : 490 : CAmount total_balance{0};
102 : 490 : CAmount max_spendable{0};
103 : 490 : int next_locktime{0};
104 [ + + + - ]: 95698 : LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), 10000)
105 : : {
106 : 95408 : const int n_input{fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 10)};
107 : 95408 : const int n_input_bytes{fuzzed_data_provider.ConsumeIntegralInRange<int>(41, 10000)};
108 : 95408 : const CAmount amount{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY)};
109 [ + + ]: 95408 : if (total_balance + amount >= MAX_MONEY) {
110 : : break;
111 : : }
112 [ + - ]: 95208 : AddCoin(amount, n_input, n_input_bytes, ++next_locktime, utxo_pool, coin_params.m_effective_feerate);
113 : 95208 : total_balance += amount;
114 [ + - ]: 95208 : CAmount eff_value = amount - coin_params.m_effective_feerate.GetFee(n_input_bytes);
115 : 95208 : max_spendable += eff_value;
116 : : }
117 : :
118 : 490 : std::vector<OutputGroup> group_pos;
119 [ + - ]: 490 : GroupCoins(fuzzed_data_provider, utxo_pool, coin_params, /*positive_only=*/true, group_pos);
120 : :
121 : : // Run coinselection algorithms
122 [ + - ]: 490 : auto result_cg = CoinGrinder(group_pos, target, coin_params.m_min_change_target, MAX_STANDARD_TX_WEIGHT);
123 [ + + + - : 490 : if (target + coin_params.m_min_change_target > max_spendable || HasErrorMsg(result_cg)) return; // We only need to compare algorithms if CoinGrinder has a solution
+ + ]
124 [ - + ]: 447 : assert(result_cg);
125 [ + - + + ]: 447 : if (!result_cg->GetAlgoCompleted()) return; // Bail out if CoinGrinder solution is not optimal
126 : :
127 [ + - ]: 444 : auto result_srd = SelectCoinsSRD(group_pos, target, coin_params.m_change_fee, fast_random_context, MAX_STANDARD_TX_WEIGHT);
128 [ + + + - : 860 : if (result_srd && result_srd->GetChange(CHANGE_LOWER, coin_params.m_change_fee) > 0) { // exclude any srd solutions that don’t have change, err on excluding
+ - ]
129 [ - + ]: 416 : assert(result_srd->GetWeight() >= result_cg->GetWeight());
130 : : }
131 : :
132 [ + - ]: 444 : auto result_knapsack = KnapsackSolver(group_pos, target, coin_params.m_min_change_target, fast_random_context, MAX_STANDARD_TX_WEIGHT);
133 [ + + + - : 881 : if (result_knapsack && result_knapsack->GetChange(CHANGE_LOWER, coin_params.m_change_fee) > 0) { // exclude any knapsack solutions that don’t have change, err on excluding
+ + ]
134 [ - + ]: 387 : assert(result_knapsack->GetWeight() >= result_cg->GetWeight());
135 : : }
136 : 490 : }
137 : :
138 [ + - ]: 601 : FUZZ_TARGET(coin_grinder_is_optimal)
139 : : {
140 : 189 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
141 : :
142 : 189 : FastRandomContext fast_random_context{ConsumeUInt256(fuzzed_data_provider)};
143 : 189 : CoinSelectionParams coin_params{fast_random_context};
144 : 189 : coin_params.m_subtract_fee_outputs = false;
145 : : // Set effective feerate up to MAX_MONEY sats per 1'000'000 vB (2'100'000'000 sat/vB = 21'000 BTC/kvB).
146 [ + - ]: 189 : coin_params.m_effective_feerate = CFeeRate{ConsumeMoney(fuzzed_data_provider, MAX_MONEY), 1'000'000};
147 : 189 : coin_params.m_min_change_target = ConsumeMoney(fuzzed_data_provider);
148 : :
149 : : // Create some coins
150 : 189 : CAmount max_spendable{0};
151 : 189 : int next_locktime{0};
152 : 189 : static constexpr unsigned max_output_groups{16};
153 : 189 : std::vector<OutputGroup> group_pos;
154 [ + + + + ]: 1911 : LIMITED_WHILE(fuzzed_data_provider.ConsumeBool(), max_output_groups)
155 : : {
156 : : // With maximum m_effective_feerate and n_input_bytes = 1'000'000, input_fee <= MAX_MONEY.
157 : 1722 : const int n_input_bytes{fuzzed_data_provider.ConsumeIntegralInRange<int>(1, 1'000'000)};
158 : : // Only make UTXOs with positive effective value
159 [ + - ]: 1722 : const CAmount input_fee = coin_params.m_effective_feerate.GetFee(n_input_bytes);
160 : : // Ensure that each UTXO has at least an effective value of 1 sat
161 : 1722 : const CAmount eff_value{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY + group_pos.size() - max_spendable - max_output_groups)};
162 : 1722 : const CAmount amount{eff_value + input_fee};
163 : 1722 : std::vector<COutput> temp_utxo_pool;
164 : :
165 [ + - ]: 1722 : AddCoin(amount, /*n_input=*/0, n_input_bytes, ++next_locktime, temp_utxo_pool, coin_params.m_effective_feerate);
166 : 1722 : max_spendable += eff_value;
167 : :
168 [ + - ]: 1722 : auto output_group = OutputGroup(coin_params);
169 [ + - + - : 1722 : output_group.Insert(std::make_shared<COutput>(temp_utxo_pool.at(0)), /*ancestors=*/0, /*descendants=*/0);
+ - ]
170 [ + - ]: 1722 : group_pos.push_back(output_group);
171 : 1722 : }
172 [ - + ]: 189 : size_t num_groups = group_pos.size();
173 [ - + ]: 189 : assert(num_groups <= max_output_groups);
174 : :
175 : : // Only choose targets below max_spendable
176 [ + + ]: 221 : const CAmount target{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, std::max(CAmount{1}, max_spendable - coin_params.m_min_change_target))};
177 : :
178 : : // Brute force optimal solution
179 : 189 : CAmount best_amount{MAX_MONEY};
180 : 189 : int best_weight{std::numeric_limits<int>::max()};
181 [ + + ]: 4155423 : for (uint32_t pattern = 1; (pattern >> num_groups) == 0; ++pattern) {
182 : : CAmount subset_amount{0};
183 : : int subset_weight{0};
184 [ + + ]: 69960204 : for (unsigned i = 0; i < num_groups; ++i) {
185 [ + + ]: 65804970 : if ((pattern >> i) & 1) {
186 [ + - ]: 32903346 : subset_amount += group_pos[i].GetSelectionAmount();
187 : 32903346 : subset_weight += group_pos[i].m_weight;
188 : : }
189 : : }
190 [ + + + + : 4155234 : if ((subset_amount >= target + coin_params.m_min_change_target) && (subset_weight < best_weight || (subset_weight == best_weight && subset_amount < best_amount))) {
+ + ]
191 : 1230 : best_weight = subset_weight;
192 : 1230 : best_amount = subset_amount;
193 : : }
194 : : }
195 : :
196 [ + + ]: 189 : if (best_weight < std::numeric_limits<int>::max()) {
197 : : // Sufficient funds and acceptable weight: CoinGrinder should find at least one solution
198 : 163 : int high_max_selection_weight = fuzzed_data_provider.ConsumeIntegralInRange<int>(best_weight, std::numeric_limits<int>::max());
199 : :
200 [ + - ]: 163 : auto result_cg = CoinGrinder(group_pos, target, coin_params.m_min_change_target, high_max_selection_weight);
201 [ - + ]: 163 : assert(result_cg);
202 [ - + ]: 163 : assert(result_cg->GetWeight() <= high_max_selection_weight);
203 [ + - - + ]: 163 : assert(result_cg->GetSelectedEffectiveValue() >= target + coin_params.m_min_change_target);
204 [ + - + - : 163 : assert(best_weight < result_cg->GetWeight() || (best_weight == result_cg->GetWeight() && best_amount <= result_cg->GetSelectedEffectiveValue()));
+ - - + ]
205 [ + - + - ]: 163 : if (result_cg->GetAlgoCompleted()) {
206 : : // If CoinGrinder exhausted the search space, it must return the optimal solution
207 [ - + ]: 163 : assert(best_weight == result_cg->GetWeight());
208 [ + - - + ]: 163 : assert(best_amount == result_cg->GetSelectedEffectiveValue());
209 : : }
210 : 163 : }
211 : :
212 : : // CoinGrinder cannot ever find a better solution than the brute-forced best, or there is none in the first place
213 : 189 : int low_max_selection_weight = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, best_weight - 1);
214 [ + - ]: 189 : auto result_cg = CoinGrinder(group_pos, target, coin_params.m_min_change_target, low_max_selection_weight);
215 : : // Max_weight should have been exceeded, or there were insufficient funds
216 [ - + ]: 189 : assert(!result_cg);
217 : 189 : }
218 : :
219 [ + - ]: 935 : FUZZ_TARGET(coinselection)
220 : : {
221 : 523 : FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
222 : 523 : std::vector<COutput> utxo_pool;
223 : :
224 : 523 : const CFeeRate long_term_fee_rate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
225 : 523 : const CFeeRate effective_fee_rate{ConsumeMoney(fuzzed_data_provider, /*max=*/COIN)};
226 : : // Discard feerate must be at least dust relay feerate
227 : 523 : const CFeeRate discard_fee_rate{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(DUST_RELAY_TX_FEE, COIN)};
228 : 523 : const CAmount target{fuzzed_data_provider.ConsumeIntegralInRange<CAmount>(1, MAX_MONEY)};
229 : 523 : const bool subtract_fee_outputs{fuzzed_data_provider.ConsumeBool()};
230 : :
231 : 523 : FastRandomContext fast_random_context{ConsumeUInt256(fuzzed_data_provider)};
232 : 523 : CoinSelectionParams coin_params{fast_random_context};
233 : 523 : coin_params.m_subtract_fee_outputs = subtract_fee_outputs;
234 : 523 : coin_params.m_long_term_feerate = long_term_fee_rate;
235 : 523 : coin_params.m_effective_feerate = effective_fee_rate;
236 : 523 : coin_params.change_output_size = fuzzed_data_provider.ConsumeIntegralInRange(1, MAX_SCRIPT_SIZE);
237 [ + - ]: 523 : coin_params.m_change_fee = effective_fee_rate.GetFee(coin_params.change_output_size);
238 : 523 : coin_params.m_discard_feerate = discard_fee_rate;
239 : 523 : coin_params.change_spend_size = fuzzed_data_provider.ConsumeIntegralInRange<int>(41, 1000);
240 [ + - ]: 523 : const auto change_spend_fee{coin_params.m_discard_feerate.GetFee(coin_params.change_spend_size)};
241 : 523 : coin_params.m_cost_of_change = coin_params.m_change_fee + change_spend_fee;
242 [ + - ]: 523 : CScript change_out_script = CScript() << std::vector<unsigned char>(coin_params.change_output_size, OP_TRUE);
243 [ + - + - ]: 523 : const auto dust{GetDustThreshold(CTxOut{/*nValueIn=*/0, change_out_script}, coin_params.m_discard_feerate)};
244 [ + + ]: 523 : coin_params.min_viable_change = std::max(change_spend_fee + 1, dust);
245 : :
246 : 523 : int next_locktime{0};
247 [ + - ]: 523 : CAmount total_balance{CreateCoins(fuzzed_data_provider, utxo_pool, coin_params, next_locktime)};
248 : :
249 : 523 : std::vector<OutputGroup> group_pos;
250 [ + - ]: 523 : GroupCoins(fuzzed_data_provider, utxo_pool, coin_params, /*positive_only=*/true, group_pos);
251 : 523 : std::vector<OutputGroup> group_all;
252 [ + - ]: 523 : GroupCoins(fuzzed_data_provider, utxo_pool, coin_params, /*positive_only=*/false, group_all);
253 : :
254 [ + + ]: 134320 : for (const OutputGroup& group : group_all) {
255 [ + - ]: 133797 : const CoinEligibilityFilter filter{fuzzed_data_provider.ConsumeIntegral<int>(), fuzzed_data_provider.ConsumeIntegral<int>(), fuzzed_data_provider.ConsumeIntegral<uint64_t>()};
256 [ + - ]: 133797 : (void)group.EligibleForSpending(filter);
257 : : }
258 : :
259 : 523 : int max_selection_weight = fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max());
260 : :
261 : : // Run coinselection algorithms
262 [ + + + - : 1204 : auto result_bnb = coin_params.m_subtract_fee_outputs ? util::Error{Untranslated("BnB disabled when SFFO is enabled")} :
+ - + + -
- ]
263 [ + - ]: 523 : SelectCoinsBnB(group_pos, target, coin_params.m_cost_of_change, max_selection_weight);
264 [ + + ]: 523 : if (result_bnb) {
265 [ + - - + ]: 116 : assert(result_bnb->GetChange(coin_params.min_viable_change, coin_params.m_change_fee) == 0);
266 [ + - - + ]: 116 : assert(result_bnb->GetSelectedValue() >= target);
267 [ - + ]: 116 : assert(result_bnb->GetWeight() <= max_selection_weight);
268 [ + - ]: 116 : (void)result_bnb->GetShuffledInputVector();
269 [ + - ]: 116 : (void)result_bnb->GetInputSet();
270 : : }
271 : :
272 [ + - ]: 523 : auto result_srd = SelectCoinsSRD(group_pos, target, coin_params.m_change_fee, fast_random_context, max_selection_weight);
273 [ + + ]: 523 : if (result_srd) {
274 [ + - - + ]: 243 : assert(result_srd->GetSelectedValue() >= target);
275 [ + - - + ]: 243 : assert(result_srd->GetChange(CHANGE_LOWER, coin_params.m_change_fee) > 0); // Demonstrate that SRD creates change of at least CHANGE_LOWER
276 [ - + ]: 243 : assert(result_srd->GetWeight() <= max_selection_weight);
277 [ + - ]: 243 : result_srd->RecalculateWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);
278 [ + - ]: 243 : (void)result_srd->GetShuffledInputVector();
279 [ + - ]: 243 : (void)result_srd->GetInputSet();
280 : : }
281 : :
282 [ + - ]: 523 : CAmount change_target{GenerateChangeTarget(target, coin_params.m_change_fee, fast_random_context)};
283 [ + - ]: 523 : auto result_knapsack = KnapsackSolver(group_all, target, change_target, fast_random_context, max_selection_weight);
284 [ + + ]: 523 : if (result_knapsack) {
285 [ + - - + ]: 261 : assert(result_knapsack->GetSelectedValue() >= target);
286 [ - + ]: 261 : assert(result_knapsack->GetWeight() <= max_selection_weight);
287 [ + - ]: 261 : result_knapsack->RecalculateWaste(coin_params.min_viable_change, coin_params.m_cost_of_change, coin_params.m_change_fee);
288 [ + - ]: 261 : (void)result_knapsack->GetShuffledInputVector();
289 [ + - ]: 261 : (void)result_knapsack->GetInputSet();
290 : : }
291 : :
292 : : // If the total balance is sufficient for the target and we are not using
293 : : // effective values, Knapsack should always find a solution (unless the selection exceeded the max tx weight).
294 [ + + + + : 523 : if (total_balance >= target && subtract_fee_outputs && !HasErrorMsg(result_knapsack)) {
+ - + + ]
295 [ - + ]: 98 : assert(result_knapsack);
296 : : }
297 : :
298 : 523 : std::vector<COutput> utxos;
299 : 523 : std::vector<util::Result<SelectionResult>> results;
300 [ + - ]: 523 : results.emplace_back(std::move(result_srd));
301 [ + - ]: 523 : results.emplace_back(std::move(result_knapsack));
302 [ + - ]: 523 : results.emplace_back(std::move(result_bnb));
303 [ + - ]: 523 : CAmount new_total_balance{CreateCoins(fuzzed_data_provider, utxos, coin_params, next_locktime)};
304 [ + + ]: 523 : if (new_total_balance > 0) {
305 : 172 : std::set<std::shared_ptr<COutput>> new_utxo_pool;
306 [ + + ]: 32199 : for (const auto& utxo : utxos) {
307 [ + - + - ]: 64054 : new_utxo_pool.insert(std::make_shared<COutput>(utxo));
308 : : }
309 [ + + ]: 688 : for (auto& result : results) {
310 [ + + ]: 516 : if (!result) continue;
311 [ + - ]: 373 : const auto weight{result->GetWeight()};
312 [ + - ]: 373 : result->AddInputs(new_utxo_pool, subtract_fee_outputs);
313 [ - + ]: 373 : assert(result->GetWeight() > weight);
314 : : }
315 : 172 : }
316 : :
317 : 523 : std::vector<COutput> manual_inputs;
318 [ + - ]: 523 : CAmount manual_balance{CreateCoins(fuzzed_data_provider, manual_inputs, coin_params, next_locktime)};
319 [ + + ]: 523 : if (manual_balance == 0) return;
320 [ + - ]: 118 : auto manual_selection{ManualSelection(manual_inputs, manual_balance, coin_params.m_subtract_fee_outputs)};
321 [ + + ]: 472 : for (auto& result : results) {
322 [ + + ]: 354 : if (!result) continue;
323 [ + - ]: 243 : const CAmount old_target{result->GetTarget()};
324 [ + - + - ]: 243 : const std::set<std::shared_ptr<COutput>> input_set{result->GetInputSet()};
325 [ + - ]: 243 : const int old_weight{result->GetWeight()};
326 [ + - ]: 243 : result->Merge(manual_selection);
327 [ + - - + ]: 243 : assert(result->GetInputSet().size() == input_set.size() + manual_inputs.size());
328 [ - + ]: 243 : assert(result->GetTarget() == old_target + manual_selection.GetTarget());
329 [ - + ]: 243 : assert(result->GetWeight() == old_weight + manual_selection.GetWeight());
330 : 243 : }
331 : 523 : }
332 : :
333 : : } // namespace wallet
|