Skip to content

Commit e003a47

Browse files
committed
carrot_impl review: input selection
1 parent 0a72375 commit e003a47

2 files changed

Lines changed: 36 additions & 21 deletions

File tree

src/carrot_impl/input_selection.cpp

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,13 @@ std::vector<std::set<std::size_t>> form_preferred_input_candidate_subsets(
209209

210210
// 5. We should prefer to spend non-forward-secret enotes in transactions where all the outputs
211211
// are going back to ourself. Otherwise, if we spend these enotes while transferring money to
212-
// another entity, an external observer who A) has a quantum computer, and B) knows one of their
212+
// another entity, an external observer who A) has a quantum computer, and B) knows one of our
213213
// public addresses, will be able to trace the money transfer. Such an observer will always be
214214
// able to tell which view-incoming keys / accounts these non-forward-secrets enotes belong to,
215215
// their amounts, and where they're spent. So since they already know that information, churning
216-
// back to oneself doesn't actually reveal that much more additional information.
216+
// back to oneself doesn't actually reveal that much more additional information. Plus it allows
217+
// us to automatically convert non-forward-secret enotes to forward-secret enotes through
218+
// churning.
217219
const bool prefer_non_fs = !is_normal_transfer;
218220
CARROT_CHECK_AND_THROW(!must_use_internal || !prefer_non_fs,
219221
carrot_logic_error, "bug: must_use_internal AND prefer_non_fs are true");
@@ -250,7 +252,7 @@ std::vector<std::set<std::size_t>> form_preferred_input_candidate_subsets(
250252
if (!must_use_internal || !prefer_non_fs)
251253
{
252254
// Spending non-FS inputs in a normal transfer transaction is not ideal, but at least
253-
// when partition it like this, we aren't "dirtying" the carrot with the pre-carrot, and
255+
// when partitioned like this, we aren't "dirtying" the carrot with the pre-carrot, and
254256
// the internal with the external
255257
if (!must_use_carrot)
256258
push_subset(pre_carrot_inputs);
@@ -343,6 +345,8 @@ select_inputs_func_t make_single_transfer_input_selector(
343345
const std::size_t max_n_inputs,
344346
std::set<size_t> *selected_input_indices_out)
345347
{
348+
const std::size_t max_n_inputs = max_n_inputs ? max_n_inputs : FCMP_PLUS_PLUS_MAX_INPUTS;
349+
346350
// input selector :D
347351
return [=](const boost::multiprecision::uint128_t &nominal_output_sum,
348352
const std::map<std::size_t, xmr_amount> &fee_by_input_count,
@@ -354,7 +358,7 @@ select_inputs_func_t make_single_transfer_input_selector(
354358

355359
// 1. Sanity checks valid arguments
356360
const std::size_t n_candidates = input_candidates.size();
357-
CARROT_CHECK_AND_THROW(!fee_by_input_count.empty(), missing_components, "no provided allowed input count");
361+
CARROT_CHECK_AND_THROW(!fee_by_input_count.empty(), missing_components, "no provided fee by input count");
358362
CARROT_CHECK_AND_THROW(!policies.empty(), missing_components, "no input selection policies provided");
359363
CARROT_CHECK_AND_THROW(n_candidates, not_enough_money, "no input candidates provided");
360364

@@ -388,7 +392,7 @@ select_inputs_func_t make_single_transfer_input_selector(
388392

389393
std::set<std::size_t> all_idxs; for (std::size_t i = 0; i < input_candidates.size(); ++i) all_idxs.insert(i);
390394
const std::pair<std::size_t, boost::multiprecision::uint128_t> max_usable_money =
391-
input_count_for_max_usable_money(input_candidates, all_idxs, FCMP_PLUS_PLUS_MAX_INPUTS, fee_by_input_count);
395+
input_count_for_max_usable_money(input_candidates, all_idxs, max_n_inputs, fee_by_input_count);
392396
CARROT_CHECK_AND_THROW(max_usable_money.second >= absolute_minimum_required_money,
393397
not_enough_usable_money,
394398
"Not enough usable money in top " << max_usable_money.first << " inputs ("
@@ -413,7 +417,7 @@ select_inputs_func_t make_single_transfer_input_selector(
413417

414418
// Skip if not enough money in this selectable set for max number of tx inputs...
415419
const auto max_usable_money = input_count_for_max_usable_money(input_candidates,
416-
input_candidate_subset, FCMP_PLUS_PLUS_MAX_INPUTS, fee_by_input_count);
420+
input_candidate_subset, max_n_inputs, fee_by_input_count);
417421
if (!max_usable_money.first)
418422
continue;
419423
else if (max_usable_money.second < required_money_by_input_count.at(max_usable_money.first))
@@ -442,6 +446,16 @@ select_inputs_func_t make_single_transfer_input_selector(
442446
continue;
443447
else if (max_usable_money.second < required_money)
444448
continue;
449+
else if (subtract_fee && fee_by_input_count[max_usable_money->first] >= nominal_output_sum)
450+
{
451+
// Note: we do not check for combinations less than max_usable_money where the fee is lower than
452+
// the output sum
453+
MDEBUG("Skipping input selection... fee exceeds output sum with 'subtract fee' option set. Fee "
454+
<< cryptonote::print_money(fee_by_input_count[max_usable_money->first])
455+
<< " for " << max_usable_money->first
456+
<< " inputs is >= output sum " << cryptonote::print_money(nominal_output_sum));
457+
continue;
458+
}
445459

446460
// After this point, we expect one of the policies to succeed, otherwise all input selection fails
447461

@@ -492,8 +506,9 @@ select_inputs_func_t make_single_transfer_input_selector(
492506
CARROT_CHECK_AND_THROW(!selected_inputs_indices.empty(),
493507
not_enough_usable_money,
494508
"No single allowed subset of candidates had enough money to fund payment proposals and fees for inputs");
495-
CARROT_CHECK_AND_THROW(*selected_inputs_indices.crbegin() < input_candidates.size(),
496-
carrot_logic_error, "bug: selected inputs index out of range");
509+
for (const std::size_t selected_inputs_index : selected_inputs_indices)
510+
CARROT_CHECK_AND_THROW(selected_inputs_index < input_candidates.size(),
511+
carrot_logic_error, "bug: selected inputs index out of range");
497512

498513
// 9. Check the sum of input amounts is great enough
499514
const std::size_t num_selected = selected_inputs_indices.size();
@@ -566,17 +581,16 @@ void select_greedy_aging(const epee::span<const InputCandidate> input_candidates
566581
const xmr_amount currently_selected_amount = input_candidates[bi_it->second].core.amount;
567582
const xmr_amount lowest_replacement_amount = (currently_selected_amount > surplus)
568583
? boost::numeric_cast<xmr_amount>(currently_selected_amount - surplus) : 0;
569-
const auto lower_amount_it = std::lower_bound(selectable_inputs_by_amount.cbegin(),
570-
selectable_inputs_by_amount.cend(), lowest_replacement_amount,
571-
[&input_candidates](const std::size_t selectable_idx, const xmr_amount lowest_replacement_amount)
572-
{
573-
CARROT_CHECK_AND_THROW(selectable_idx < input_candidates.size(),
574-
std::out_of_range, "input candidate index out of range");
575-
return input_candidates[selectable_idx].core.amount < lowest_replacement_amount;
576-
});
577-
for (auto amount_it = lower_amount_it; amount_it != selectable_inputs_by_amount.cend(); ++amount_it)
584+
for (size_t i = 0; i < selectable_inputs_by_amount.size(); ++i)
578585
{
579-
const std::size_t potential_replacement_idx = *amount_it;
586+
// Ignore candidates with amounts too low for replacement
587+
const std::size_t potential_replacement_idx = selectable_inputs_by_amount[i];
588+
CARROT_CHECK_AND_THROW(potential_replacement_idx < input_candidates.size(),
589+
std::out_of_range, "input candidate index out of range");
590+
if (input_candidates[potential_replacement_idx].core.amount < lowest_replacement_amount)
591+
continue;
592+
593+
// Replace if candidate's block index is lower
580594
if (selected_inputs_indices_out.count(potential_replacement_idx))
581595
continue;
582596
const InputCandidate &potential_replacement_input = input_candidates[potential_replacement_idx];

src/carrot_impl/input_selection.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ std::pair<std::size_t, boost::multiprecision::uint128_t> get_input_count_for_max
110110
* The better candidate is determined by criteria in descending order of importance as follows:
111111
* 1. Amount (higher is better, duh)
112112
* 2. Age (older is better for protection against double spend attacks)
113-
* 3. Is pre-Carrot enote? (`false` is better for spending QFS)
114-
* 4. Is external enote? (`false` is better for spending QFS)
113+
* 3. Is pre-Carrot enote? (`false` is better for quantum forward-secrecy)
114+
* 4. Is external enote? (`false` is better for quantum forward-secrecy)
115115
*/
116116
int compare_input_candidate_same_ota(const InputCandidate &lhs, const InputCandidate &rhs);
117117
/**
@@ -206,7 +206,8 @@ select_inputs_func_t make_single_transfer_input_selector(
206206
namespace ispolicy
207207
{
208208
/**
209-
* brief: select_greedy_aging - an ISP which generally attempts to select old outputs, but isn't necessarily optimal
209+
* brief: select_greedy_aging - an input selection policy which generally attempts to select old outputs, but
210+
* isn't necessarily optimal
210211
*/
211212
void select_greedy_aging(const epee::span<const InputCandidate>,
212213
const std::set<std::size_t>&,

0 commit comments

Comments
 (0)