carrot_impl: input selection overhaul
* Split subsetting into `form_preferred_input_candidate_subsets` fuction * Use `get_input_counts_in_preferred_order` directly from `make_single_transfer_input_selector` * Input selection policies are passed number of inputs to select for, instead of dynamic * Filter dust before calling policies * Throw more descriptive errors when not enough money (could still be improved for not enough input case) * Replace multiprecision::int128_t with multiprecision::uint128_t for simplicity and cryptonote::print_money * More documentation * Remove "select two, prefer oldest" policy for now
This commit is contained in:
+377
-370
@@ -31,6 +31,7 @@
|
||||
|
||||
//local headers
|
||||
#include "carrot_core/config.h"
|
||||
#include "carrot_core/exceptions.h"
|
||||
#include "common/container_helpers.h"
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h"
|
||||
#include "misc_log_ex.h"
|
||||
@@ -62,30 +63,6 @@ static std::ostream &operator<<(std::ostream &os, const CarrotPreSelectedInput &
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
static int compare_input_candidate_same_ki(const CarrotPreSelectedInput &lhs, const CarrotPreSelectedInput &rhs)
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(lhs.core.key_image == rhs.core.key_image,
|
||||
"compare_input_candidate_same_ki: this function is not meant to compare inputs of different key images");
|
||||
|
||||
// first prefer the higher amount
|
||||
if (lhs.core.amount < rhs.core.amount)
|
||||
return -1;
|
||||
else if (lhs.core.amount > rhs.core.amount)
|
||||
return 1;
|
||||
|
||||
// then prefer older
|
||||
if (lhs.block_index < rhs.block_index)
|
||||
return 1;
|
||||
else if (lhs.block_index > rhs.block_index)
|
||||
return -1;
|
||||
|
||||
// It should be computationally intractable for lhs.is_external != rhs.is_external, but I haven't
|
||||
// looked into it too deeply. I guess you would want to prefer whichever one !is_external.
|
||||
|
||||
return 0;
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
static std::set<size_t> set_union(const std::set<size_t> &a, const std::set<size_t> &b)
|
||||
{
|
||||
std::set<size_t> c = a;
|
||||
@@ -98,10 +75,10 @@ static void stable_sort_indices_by_amount(const epee::span<const CarrotPreSelect
|
||||
std::vector<size_t> &indices_inout)
|
||||
{
|
||||
std::stable_sort(indices_inout.begin(), indices_inout.end(),
|
||||
[input_candidates](const size_t a, const size_t b) -> bool
|
||||
[input_candidates](const std::size_t a, const std::size_t b) -> bool
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(a < input_candidates.size() && b < input_candidates.size(),
|
||||
"input candidate index out of range");
|
||||
CARROT_CHECK_AND_THROW(a < input_candidates.size() && b < input_candidates.size(),
|
||||
std::out_of_range, "input candidate index out of range");
|
||||
return input_candidates[a].core.amount < input_candidates[b].core.amount;
|
||||
});
|
||||
}
|
||||
@@ -111,304 +88,436 @@ static void stable_sort_indices_by_block_index(const epee::span<const CarrotPreS
|
||||
std::vector<size_t> &indices_inout)
|
||||
{
|
||||
std::stable_sort(indices_inout.begin(), indices_inout.end(),
|
||||
[input_candidates](const size_t a, const size_t b) -> bool
|
||||
[input_candidates](const std::size_t a, const std::size_t b) -> bool
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(a < input_candidates.size() && b < input_candidates.size(),
|
||||
"input candidate index out of range");
|
||||
CARROT_CHECK_AND_THROW(a < input_candidates.size() && b < input_candidates.size(),
|
||||
std::out_of_range, "input candidate index out of range");
|
||||
return input_candidates[a].block_index < input_candidates[b].block_index;
|
||||
});
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
static std::pair<size_t, boost::multiprecision::int128_t> input_count_for_max_usable_money(
|
||||
static std::pair<std::size_t, boost::multiprecision::uint128_t> input_count_for_max_usable_money(
|
||||
const epee::span<const CarrotPreSelectedInput> input_candidates,
|
||||
const std::set<size_t> &selectable_inputs,
|
||||
const std::map<size_t, rct::xmr_amount> &fee_by_input_count)
|
||||
const std::set<std::size_t> &selectable_inputs,
|
||||
std::size_t max_num_input_count,
|
||||
const std::map<std::size_t, rct::xmr_amount> &fee_by_input_count)
|
||||
{
|
||||
// Returns (N, X) where the X is the sum of the amounts of the greatest N <= CARROT_MAX_TX_INPUTS
|
||||
// Returns (N, X) where the X is the sum of the amounts of the greatest N <= max_num_input_count
|
||||
// inputs from selectable_inputs, maximizing X - F(N). F(N) is the fee for this transaction,
|
||||
// given input count N. This should correctly handle "almost-dust": inputs which are less than
|
||||
// the fee, but greater than or equal to the difference of the fee compared to excluding that
|
||||
// input. If this function returns N == 0, then there aren't enough usable funds, i.e. no N
|
||||
// exists such that X - F(N) > 0.
|
||||
|
||||
size_t num_ins = 0;
|
||||
boost::multiprecision::int128_t cumulative_input_sum = 0;
|
||||
rct::xmr_amount last_fee = 0;
|
||||
if (fee_by_input_count.empty() || selectable_inputs.empty())
|
||||
return {0, 0};
|
||||
|
||||
std::vector<size_t> selectable_inputs_vec(selectable_inputs.cbegin(), selectable_inputs.cend());
|
||||
stable_sort_indices_by_amount(input_candidates, selectable_inputs_vec);
|
||||
max_num_input_count = std::min(max_num_input_count, selectable_inputs.size());
|
||||
CARROT_CHECK_AND_THROW(max_num_input_count <= fee_by_input_count.crbegin()->first,
|
||||
too_few_inputs, "fee by input count does not contain info for provided max input count");
|
||||
|
||||
// for selectable indices in descending amount...
|
||||
for (auto it = selectable_inputs_vec.crbegin(); it != selectable_inputs_vec.crend(); ++it)
|
||||
// maintain list of top amounts of selectable_inputs
|
||||
std::multiset<rct::xmr_amount> top_amounts;
|
||||
for (const std::size_t selectable_input : selectable_inputs)
|
||||
{
|
||||
if (num_ins == CARROT_MAX_TX_INPUTS)
|
||||
break;
|
||||
CARROT_CHECK_AND_THROW(selectable_input < input_candidates.size(),
|
||||
std::out_of_range, "selectable input out of range");
|
||||
const rct::xmr_amount amount = input_candidates[selectable_input].core.amount;
|
||||
top_amounts.insert(amount);
|
||||
|
||||
if (top_amounts.size() > max_num_input_count)
|
||||
top_amounts.erase(top_amounts.cbegin());
|
||||
}
|
||||
|
||||
// add up all the top amounts from the greatest to least until one fails to pay for its own marginal fee
|
||||
std::size_t num_ins = 0;
|
||||
rct::xmr_amount last_fee = 0;
|
||||
boost::multiprecision::uint128_t cumulative_input_sum = 0;
|
||||
for (auto amount_it = top_amounts.crbegin(); amount_it != top_amounts.crend(); ++amount_it)
|
||||
{
|
||||
const rct::xmr_amount amount = *amount_it;
|
||||
const rct::xmr_amount current_fee = fee_by_input_count.at(num_ins + 1);
|
||||
CARROT_CHECK_AND_THROW(current_fee > last_fee,
|
||||
carrot_logic_error, "provided fee by input count is not monotonically increasing");
|
||||
const rct::xmr_amount marginal_fee_diff = current_fee - last_fee;
|
||||
if (amount <= marginal_fee_diff)
|
||||
break;
|
||||
++num_ins;
|
||||
|
||||
const rct::xmr_amount amount = input_candidates[*it].core.amount;
|
||||
|
||||
if (amount < fee_by_input_count.at(num_ins) - last_fee)
|
||||
{
|
||||
// then this input doesn't pay for itself, rollback previous state and break
|
||||
// since all next inputs will have same amount or less
|
||||
--num_ins;
|
||||
break;
|
||||
}
|
||||
|
||||
last_fee = current_fee;
|
||||
cumulative_input_sum += amount;
|
||||
last_fee = fee_by_input_count.at(num_ins);
|
||||
}
|
||||
|
||||
return {num_ins, cumulative_input_sum};
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
int compare_input_candidate_same_ki(const CarrotPreSelectedInput &lhs, const CarrotPreSelectedInput &rhs)
|
||||
{
|
||||
CARROT_CHECK_AND_THROW(lhs.core.key_image == rhs.core.key_image,
|
||||
component_out_of_order, "this function is not meant to compare inputs of different key images");
|
||||
|
||||
// First prefer the higher amount,
|
||||
if (lhs.core.amount < rhs.core.amount)
|
||||
return -1;
|
||||
else if (lhs.core.amount > rhs.core.amount)
|
||||
return 1;
|
||||
|
||||
// Then prefer older,
|
||||
if (lhs.block_index < rhs.block_index)
|
||||
return 1;
|
||||
else if (lhs.block_index > rhs.block_index)
|
||||
return -1;
|
||||
|
||||
// Then prefer Carrot over pre-Carrot. It should be computationally intractable for
|
||||
// lhs.is_pre_carrot != rhs.is_pre_carrot, when they both successfully scan, but I haven't
|
||||
// looked into it too deeply.
|
||||
if (lhs.is_pre_carrot && !rhs.is_pre_carrot)
|
||||
return -1;
|
||||
else if (!lhs.is_pre_carrot && rhs.is_pre_carrot)
|
||||
return 1;
|
||||
|
||||
// Then prefer internal over external. Same tractability note as with is_pre_carrot.
|
||||
if (lhs.is_external && !rhs.is_external)
|
||||
return -1;
|
||||
else if (!lhs.is_external && rhs.is_external)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
std::vector<std::set<std::size_t>> form_preferred_input_candidate_subsets(
|
||||
const epee::span<const CarrotPreSelectedInput> input_candidates,
|
||||
const std::uint32_t flags,
|
||||
const bool is_normal_transfer)
|
||||
{
|
||||
using namespace InputSelectionFlags;
|
||||
|
||||
// Sanity check flags
|
||||
const bool confused_qfs = (flags & ALLOW_PRE_CARROT_INPUTS_IN_NORMAL_TRANSFERS) &&
|
||||
!(flags & ALLOW_EXTERNAL_INPUTS_IN_NORMAL_TRANSFERS);
|
||||
CARROT_CHECK_AND_THROW(!confused_qfs, std::invalid_argument,
|
||||
"It does not make sense to allow pre-carrot inputs in normal transfers, but not external carrot inputs.");
|
||||
|
||||
// 1. Compile map of best input candidates by key image to mitigate the "burning bug" for legacy enotes
|
||||
std::unordered_map<crypto::key_image, std::size_t> best_input_by_key_image;
|
||||
for (size_t i = 0; i < input_candidates.size(); ++i)
|
||||
{
|
||||
const CarrotPreSelectedInput &input_candidate = input_candidates[i];
|
||||
auto it = best_input_by_key_image.find(input_candidate.core.key_image);
|
||||
if (it == best_input_by_key_image.end())
|
||||
{
|
||||
best_input_by_key_image[input_candidate.core.key_image] = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
const CarrotPreSelectedInput &other_input_candidate = input_candidates[it->second];
|
||||
if (compare_input_candidate_same_ki(other_input_candidate, input_candidate) < 0)
|
||||
it->second = i;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Collect set of non-burned inputs
|
||||
std::set<std::size_t> all_non_burned_inputs;
|
||||
for (const auto &best_input : best_input_by_key_image)
|
||||
all_non_burned_inputs.insert(best_input.second);
|
||||
|
||||
// 3. Partition into:
|
||||
// a) Pre-carrot (no quantum forward secrecy)
|
||||
// b) External carrot (quantum forward secret if public address not known)
|
||||
// c) Internal carrot (always quantum forward secret unless secret keys known)
|
||||
std::set<std::size_t> pre_carrot_inputs;
|
||||
std::set<std::size_t> external_carrot_inputs;
|
||||
std::set<std::size_t> internal_inputs;
|
||||
for (std::size_t candidate_idx : all_non_burned_inputs)
|
||||
{
|
||||
if (input_candidates[candidate_idx].is_pre_carrot)
|
||||
pre_carrot_inputs.insert(candidate_idx);
|
||||
else if (input_candidates[candidate_idx].is_external)
|
||||
external_carrot_inputs.insert(candidate_idx);
|
||||
else
|
||||
internal_inputs.insert(candidate_idx);
|
||||
}
|
||||
|
||||
// 4. Calculate misc features
|
||||
const bool must_use_internal = !(flags & ALLOW_EXTERNAL_INPUTS_IN_NORMAL_TRANSFERS) && is_normal_transfer;
|
||||
const bool allow_mixed_externality = (flags & ALLOW_MIXED_INTERNAL_EXTERNAL) && !must_use_internal;
|
||||
const bool must_use_carrot = !(flags & ALLOW_PRE_CARROT_INPUTS_IN_NORMAL_TRANSFERS) && is_normal_transfer;
|
||||
const bool allow_mixed_carrotness = (flags & ALLOW_MIXED_CARROT_PRE_CARROT) && !must_use_carrot;
|
||||
|
||||
// 5. We should prefer to spend non-forward-secret enotes in transactions where all the outputs
|
||||
// are going back to ourself. Otherwise, if we spend these enotes while transferring money to
|
||||
// another entity, an external observer who A) has a quantum computer, and B) knows one of their
|
||||
// public addresses, will be able to trace the money transfer. Such an observer will always be
|
||||
// able to tell which view-incoming keys / accounts these non-forward-secrets enotes belong to,
|
||||
// their amounts, and where they're spent. So since they already know that information, churning
|
||||
// back to oneself doesn't actually reveal that much more additional information.
|
||||
const bool prefer_non_fs = !is_normal_transfer;
|
||||
CARROT_CHECK_AND_THROW(!must_use_internal || !prefer_non_fs,
|
||||
carrot_logic_error, "bug: must_use_internal AND prefer_non_fs are true");
|
||||
|
||||
// There is no "prefer pre-carrot" variable since in the case that we prefer spending
|
||||
// non-forward-secret, we always prefer first spending pre-carrot over carrot, if it is allowed
|
||||
|
||||
// 6. Define input_candidate_subsets and how to add to it
|
||||
std::vector<std::set<std::size_t>> input_candidate_subsets;
|
||||
input_candidate_subsets.reserve(8);
|
||||
const auto push_subset = [&input_candidate_subsets](const std::set<std::size_t> &subset)
|
||||
{
|
||||
if (subset.empty()) return;
|
||||
const auto subset_it = std::find(input_candidate_subsets.cbegin(), input_candidate_subsets.cend(), subset);
|
||||
if (subset_it != input_candidate_subsets.cend()) return; // subset already present (could be more efficient)
|
||||
input_candidate_subsets.push_back(subset);
|
||||
};
|
||||
|
||||
// 7. Try dispatching for non-forward-secret input subsets, if preferred in this context
|
||||
if (prefer_non_fs)
|
||||
{
|
||||
// try getting rid of pre-carrot enotes first, if allowed
|
||||
if (!must_use_carrot)
|
||||
push_subset(pre_carrot_inputs);
|
||||
|
||||
// ... then external carrot
|
||||
push_subset(external_carrot_inputs);
|
||||
}
|
||||
|
||||
// 8. Try dispatching for internal
|
||||
push_subset(internal_inputs);
|
||||
|
||||
// 9. Try dispatching for non-FS *after* internal, if allowed and not already tried
|
||||
if (!must_use_internal || !prefer_non_fs)
|
||||
{
|
||||
// Spending non-FS inputs in a normal transfer transaction is not ideal, but at least
|
||||
// when partition it like this, we aren't "dirtying" the carrot with the pre-carrot, and
|
||||
// the internal with the external
|
||||
if (!must_use_carrot)
|
||||
push_subset(pre_carrot_inputs);
|
||||
push_subset(external_carrot_inputs);
|
||||
}
|
||||
|
||||
// 10. Try dispatching for all non-FS (mixed pre-carrot & carrot external), if allowed
|
||||
if (allow_mixed_carrotness)
|
||||
{
|
||||
// We're mixing carrot/pre-carrot spends here, but avoiding "dirtying" the internal
|
||||
push_subset(set_union(pre_carrot_inputs, external_carrot_inputs));
|
||||
}
|
||||
|
||||
// 11. Try dispatching for all carrot, if allowed
|
||||
if (allow_mixed_externality)
|
||||
{
|
||||
// We're mixing internal & external carrot spends here, but avoiding "dirtying" the
|
||||
// carrot spends with pre-carrot spends. This will be quantum forward secret iff the
|
||||
// adversary doesn't know one of your public addresses
|
||||
push_subset(set_union(external_carrot_inputs, internal_inputs));
|
||||
}
|
||||
|
||||
//! @TODO: MRL discussion about whether step 11 or step 12 should go first. In other words,
|
||||
// do we prefer to avoid dirtying internal, and protect against quantum adversaries
|
||||
// who know your public addresses? Or do we prefer to avoid dirtying w/ pre-carrot,
|
||||
// and protect against quantum adversaries with no special knowledge of your public
|
||||
// addresses, but whose attacks are only relevant when spending pre-FCMP++ enotes?
|
||||
|
||||
// 12. Try dispatching for everything, if allowed
|
||||
if (allow_mixed_carrotness && allow_mixed_externality)
|
||||
push_subset(all_non_burned_inputs);
|
||||
|
||||
// Notice that we don't combine just the pre_carrot_inputs and internal_inputs by themselves
|
||||
|
||||
return input_candidate_subsets;
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
std::vector<std::size_t> get_input_counts_in_preferred_order()
|
||||
{
|
||||
// 1 or 2 randomly, then
|
||||
// other ascending powers of 2, then
|
||||
// other ascending positive numbers
|
||||
|
||||
//! @TODO: MRL discussion about 2 vs 1 default input count when 1 input can pay. If we default
|
||||
// to 1, then that may reveal more information about the amount, and reveals that one can't pay
|
||||
// with 1 output when using 2. Vice versa, if we default to 2, then that means that one only
|
||||
// owns 1 output when using 1. It may be the most advantageous to randomly switch between
|
||||
// preferring 1 vs 2. See: https://lavalle.pl/planning/node437.html. Con to this approach: if we
|
||||
// default to 1 over 2 always then there's scenarios where we net save tx fees and proving time.
|
||||
|
||||
static_assert(CARROT_MAX_TX_INPUTS == FCMP_PLUS_PLUS_MAX_INPUTS, "inconsistent input count max limit");
|
||||
static_assert(CARROT_MIN_TX_INPUTS == 1 && CARROT_MAX_TX_INPUTS == 8,
|
||||
"refactor this function for different input count limits");
|
||||
|
||||
const bool random_bit = 0 == (crypto::rand<uint8_t>() & 0x01);
|
||||
if (random_bit)
|
||||
return {2, 1, 4, 8, 3, 5, 6, 7};
|
||||
else
|
||||
return {1, 2, 4, 8, 3, 5, 6, 7};
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
select_inputs_func_t make_single_transfer_input_selector(
|
||||
const epee::span<const CarrotPreSelectedInput> input_candidates,
|
||||
const epee::span<const input_selection_policy_t> policies,
|
||||
const std::uint32_t flags,
|
||||
std::set<size_t> *selected_input_indices_out)
|
||||
{
|
||||
using namespace InputSelectionFlags;
|
||||
|
||||
CHECK_AND_ASSERT_THROW_MES(!policies.empty(),
|
||||
"make_single_transfer_input_selector: no input selection policies provided");
|
||||
|
||||
// Sanity check flags
|
||||
const bool confused_qfs = (flags & ALLOW_PRE_CARROT_INPUTS_IN_NORMAL_TRANSFERS) &&
|
||||
!(flags & ALLOW_EXTERNAL_INPUTS_IN_NORMAL_TRANSFERS);
|
||||
CHECK_AND_ASSERT_THROW_MES(!confused_qfs,
|
||||
"make single transfer input selector: It does not make sense to allow pre-carrot inputs in normal transfers, "
|
||||
"but not external carrot inputs.");
|
||||
|
||||
// input selector :)
|
||||
return [=](const boost::multiprecision::int128_t &nominal_output_sum,
|
||||
// input selector :D
|
||||
return [=](const boost::multiprecision::uint128_t &nominal_output_sum,
|
||||
const std::map<std::size_t, rct::xmr_amount> &fee_by_input_count,
|
||||
const std::size_t num_normal_payment_proposals,
|
||||
const std::size_t num_selfsend_payment_proposals,
|
||||
std::vector<CarrotSelectedInput> &selected_inputs_out)
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(!fee_by_input_count.empty(),
|
||||
"make_single_transfer_input_selector: no provided allowed input count");
|
||||
using namespace InputSelectionFlags;
|
||||
|
||||
// 1. Sanity checks valid arguments
|
||||
const std::size_t n_candidates = input_candidates.size();
|
||||
CARROT_CHECK_AND_THROW(!fee_by_input_count.empty(), missing_components, "no provided allowed input count");
|
||||
CARROT_CHECK_AND_THROW(!policies.empty(), missing_components, "no input selection policies provided");
|
||||
CARROT_CHECK_AND_THROW(n_candidates, not_enough_money, "no input candidates provided");
|
||||
|
||||
// 2. Log
|
||||
MDEBUG("Running single transfer input selector with " << input_candidates.size() << " candidates and "
|
||||
<< policies.size() << " policies, for " << num_normal_payment_proposals << " normal payment proposals, "
|
||||
<< num_selfsend_payment_proposals << " self-send payment proposals, "
|
||||
<< cryptonote::print_money(boost::numeric_cast<boost::multiprecision::uint128_t>(nominal_output_sum))
|
||||
<< cryptonote::print_money(nominal_output_sum)
|
||||
<< " output sum, and fee range " << cryptonote::print_money(fee_by_input_count.cbegin()->second)
|
||||
<< "-" << cryptonote::print_money(fee_by_input_count.crbegin()->second));
|
||||
|
||||
// 1. Compile map of best input candidates by key image to mitigate the "burning bug" for legacy enotes
|
||||
std::unordered_map<crypto::key_image, size_t> best_input_by_key_image;
|
||||
for (size_t i = 0; i < input_candidates.size(); ++i)
|
||||
{
|
||||
const CarrotPreSelectedInput &input_candidate = input_candidates[i];
|
||||
auto it = best_input_by_key_image.find(input_candidate.core.key_image);
|
||||
if (it == best_input_by_key_image.end())
|
||||
{
|
||||
best_input_by_key_image[input_candidate.core.key_image] = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
const CarrotPreSelectedInput &other_input_candidate = input_candidates[it->second];
|
||||
if (compare_input_candidate_same_ki(other_input_candidate, input_candidate) < 0)
|
||||
it->second = i;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Collect set of non-burned inputs
|
||||
std::set<size_t> all_non_burned_inputs;
|
||||
for (const auto &best_input : best_input_by_key_image)
|
||||
all_non_burned_inputs.insert(best_input.second);
|
||||
|
||||
// 3. Partition into:
|
||||
// a) Pre-carrot (no quantum forward secrecy)
|
||||
// b) External carrot (quantum forward secret if public address not known)
|
||||
// c) Internal carrot (always quantum forward secret unless secret keys known)
|
||||
std::set<size_t> pre_carrot_inputs;
|
||||
std::set<size_t> external_carrot_inputs;
|
||||
std::set<size_t> internal_inputs;
|
||||
for (size_t candidate_idx : all_non_burned_inputs)
|
||||
{
|
||||
if (input_candidates[candidate_idx].is_pre_carrot)
|
||||
pre_carrot_inputs.insert(candidate_idx);
|
||||
else if (input_candidates[candidate_idx].is_external)
|
||||
external_carrot_inputs.insert(candidate_idx);
|
||||
else
|
||||
internal_inputs.insert(candidate_idx);
|
||||
}
|
||||
|
||||
// 4. Calculate minimum required input money sum for a given input count
|
||||
// 3. Calculate minimum required input money sum for a given input count
|
||||
const bool subtract_fee = flags & IS_KNOWN_FEE_SUBTRACTABLE;
|
||||
std::map<size_t, boost::multiprecision::int128_t> required_money_by_input_count;
|
||||
std::map<std::size_t, boost::multiprecision::uint128_t> required_money_by_input_count;
|
||||
for (const auto &fee_and_input_count : fee_by_input_count)
|
||||
{
|
||||
required_money_by_input_count[fee_and_input_count.first] =
|
||||
nominal_output_sum + (subtract_fee ? 0 : fee_and_input_count.second);
|
||||
}
|
||||
const boost::multiprecision::uint128_t absolute_minimum_required_money
|
||||
= required_money_by_input_count.cbegin()->second;
|
||||
|
||||
// 5. Calculate misc features
|
||||
const bool must_use_internal = !(flags & ALLOW_EXTERNAL_INPUTS_IN_NORMAL_TRANSFERS) &&
|
||||
(num_normal_payment_proposals != 0);
|
||||
const bool allow_mixed_externality = (flags & ALLOW_MIXED_INTERNAL_EXTERNAL) &&
|
||||
!must_use_internal;
|
||||
const bool must_use_carrot = !(flags & ALLOW_PRE_CARROT_INPUTS_IN_NORMAL_TRANSFERS) &&
|
||||
(num_normal_payment_proposals != 0);
|
||||
const bool allow_mixed_carrotness = (flags & ALLOW_MIXED_CARROT_PRE_CARROT) &&
|
||||
!must_use_carrot;
|
||||
// 4. Quick check of total money and single tx input count limited total money
|
||||
boost::multiprecision::uint128_t total_candidate_money = 0;
|
||||
for (const CarrotPreSelectedInput &input_candidate : input_candidates)
|
||||
total_candidate_money += input_candidate.core.amount;
|
||||
CARROT_CHECK_AND_THROW(total_candidate_money >= absolute_minimum_required_money,
|
||||
not_enough_money,
|
||||
"Not enough money in all inputs (" << cryptonote::print_money(total_candidate_money)
|
||||
<< ") to fund minimum output sum (" << cryptonote::print_money(absolute_minimum_required_money) << ')');
|
||||
|
||||
// We should prefer to spend non-forward-secret enotes in transactions where all the outputs are going back to
|
||||
// ourself. Otherwise, if we spend these enotes while transferring money to another entity, an external observer
|
||||
// who A) has a quantum computer, and B) knows one of their public addresses, will be able to trace the money
|
||||
// transfer. Such an observer will always be able to tell which view-incoming keys / accounts these
|
||||
// non-forward-secrets enotes belong to, their amounts, and where they're spent. So since they already know that
|
||||
// information, churning back to oneself doesn't actually reveal that much more additional information.
|
||||
const bool prefer_non_fs = num_normal_payment_proposals == 0;
|
||||
CHECK_AND_ASSERT_THROW_MES(!must_use_internal || !prefer_non_fs,
|
||||
"make_single_transfer_input_selector: bug: must_use_internal AND prefer_non_fs are true");
|
||||
std::set<std::size_t> all_idxs; for (std::size_t i = 0; i < input_candidates.size(); ++i) all_idxs.insert(i);
|
||||
const std::pair<std::size_t, boost::multiprecision::uint128_t> max_usable_money =
|
||||
input_count_for_max_usable_money(input_candidates, all_idxs, FCMP_PLUS_PLUS_MAX_INPUTS, fee_by_input_count);
|
||||
CARROT_CHECK_AND_THROW(max_usable_money.second >= absolute_minimum_required_money,
|
||||
not_enough_usable_money,
|
||||
"Not enough usable money in top " << max_usable_money.first << " inputs ("
|
||||
<< cryptonote::print_money(max_usable_money.second) << ") to fund minimum output sum ("
|
||||
<< cryptonote::print_money(absolute_minimum_required_money) << ')');
|
||||
|
||||
// There is no "prefer pre-carrot" variable since in the case that we prefer spending non-forward-secret, we
|
||||
// always prefer first spending pre-carrot over carrot, if it is allowed
|
||||
// 5. Get preferred input candidate subsets
|
||||
//! @TODO: dummy check num_normal_payment_proposals
|
||||
const std::vector<std::set<std::size_t>> input_candidate_subsets = form_preferred_input_candidate_subsets(
|
||||
input_candidates,
|
||||
flags,
|
||||
num_normal_payment_proposals);
|
||||
|
||||
// 6. Short-hand functor for dispatching input selection on a subset of inputs
|
||||
// Note: Result goes into `selected_inputs_indices`. If already populated, then this functor does nothing
|
||||
// 6. Get preferred transaction input counts
|
||||
const std::vector<std::size_t> input_counts = get_input_counts_in_preferred_order();
|
||||
|
||||
// 7. For each input candidate subset...
|
||||
std::set<size_t> selected_inputs_indices;
|
||||
const auto try_dispatch_input_selection =
|
||||
[&](const std::set<size_t> &selectable_indices)
|
||||
for (const std::set<std::size_t> &input_candidate_subset : input_candidate_subsets)
|
||||
{
|
||||
// Return early if already selected inputs or no available selectable
|
||||
const bool already_selected = !selected_inputs_indices.empty();
|
||||
if (already_selected || selectable_indices.empty())
|
||||
return;
|
||||
if (selected_inputs_indices.size()) break;
|
||||
|
||||
// Return early if not enough money in this selectable set...
|
||||
// Skip if not enough money in this selectable set for max number of tx inputs...
|
||||
const auto max_usable_money = input_count_for_max_usable_money(input_candidates,
|
||||
selectable_indices,
|
||||
fee_by_input_count);
|
||||
const bool enough_money = max_usable_money.first > 0
|
||||
&& max_usable_money.second >= required_money_by_input_count.at(max_usable_money.first);
|
||||
if (!enough_money)
|
||||
return;
|
||||
input_candidate_subset, FCMP_PLUS_PLUS_MAX_INPUTS, fee_by_input_count);
|
||||
if (!max_usable_money.first)
|
||||
continue;
|
||||
else if (max_usable_money.second < required_money_by_input_count.at(max_usable_money.first))
|
||||
continue;
|
||||
|
||||
const boost::multiprecision::uint128_t max_usable_money_u128 =
|
||||
boost::numeric_cast<boost::multiprecision::uint128_t>(max_usable_money.second);
|
||||
MDEBUG("Trying to dispatch input selection on " << selectable_indices.size() <<
|
||||
"-input subset with max usable money: " << cryptonote::print_money(max_usable_money_u128) << " XMR");
|
||||
for (const size_t selectable_index : selectable_indices)
|
||||
// Debug log input candidate subset
|
||||
MDEBUG("Trying to dispatch input selection on " << input_candidate_subset.size() <<
|
||||
"-input subset with tx max usable money: " << cryptonote::print_money(max_usable_money.second));
|
||||
for (const std::size_t selectable_index : input_candidate_subset)
|
||||
{
|
||||
const CarrotPreSelectedInput &input_candidate = input_candidates[selectable_index];
|
||||
MDEBUG(" " << input_candidate);
|
||||
}
|
||||
|
||||
// For each passed policy and while not already selected inputs, dispatch policy...
|
||||
for (size_t policy_idx = 0; policy_idx < policies.size() && selected_inputs_indices.empty(); ++policy_idx)
|
||||
policies[policy_idx](input_candidates,
|
||||
selectable_indices,
|
||||
required_money_by_input_count,
|
||||
selected_inputs_indices);
|
||||
// For each transaction input count...
|
||||
for (const std::size_t n_inputs : input_counts)
|
||||
{
|
||||
if (selected_inputs_indices.size()) break;
|
||||
|
||||
// Check that returned selected indices were actually selectable
|
||||
for (const size_t selected_inputs_index : selected_inputs_indices)
|
||||
CHECK_AND_ASSERT_THROW_MES(selectable_indices.count(selected_inputs_index),
|
||||
"make_single_transfer_input_selector: bug in policy: returned unselectable index");
|
||||
const boost::multiprecision::uint128_t &required_money = required_money_by_input_count.at(n_inputs);
|
||||
|
||||
// Skip if not enough money in this selectable set for exact number of inputs...
|
||||
const auto max_usable_money = input_count_for_max_usable_money(input_candidates,
|
||||
input_candidate_subset, n_inputs, fee_by_input_count);
|
||||
if (max_usable_money.first != n_inputs)
|
||||
continue;
|
||||
else if (max_usable_money.second < required_money)
|
||||
continue;
|
||||
|
||||
// After this point, we expect one of the policies to succeed, otherwise all input selection fails
|
||||
|
||||
// at least one call to an input selection subroutine has enough usable money to work with
|
||||
MDEBUG("Trying input selection with " << n_inputs << " tx inputs");
|
||||
|
||||
// Filter all dust out of subset unless ALLOW_DUST flag is provided
|
||||
std::set<std::size_t> candidate_subset_filtered = input_candidate_subset;
|
||||
if (!(flags * ALLOW_DUST))
|
||||
{
|
||||
const rct::xmr_amount dust_threshold = fee_by_input_count.at(n_inputs)
|
||||
- (n_inputs > CARROT_MIN_TX_INPUTS ? fee_by_input_count.at(n_inputs - 1) : 0);
|
||||
for (auto it = candidate_subset_filtered.cbegin(); it != candidate_subset_filtered.cend();)
|
||||
{
|
||||
if (*it >= input_candidates.size() || input_candidates[*it].core.amount <= dust_threshold)
|
||||
it = candidate_subset_filtered.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// For each input selection policy...
|
||||
for (const input_selection_policy_t &policy : policies)
|
||||
{
|
||||
if (selected_inputs_indices.size()) break;
|
||||
|
||||
policy(input_candidates,
|
||||
candidate_subset_filtered,
|
||||
n_inputs,
|
||||
required_money,
|
||||
selected_inputs_indices);
|
||||
}
|
||||
|
||||
// Check nominal success
|
||||
CARROT_CHECK_AND_THROW(selected_inputs_indices.size(),
|
||||
carrot_runtime_error, "provided input selection policies failed with enough usable money");
|
||||
CARROT_CHECK_AND_THROW(selected_inputs_indices.size() == n_inputs,
|
||||
carrot_logic_error, "bug in policy: selected wrong number of inputs");
|
||||
|
||||
// Check selected indices were actually selectable
|
||||
for (const std::size_t selected_inputs_index : selected_inputs_indices)
|
||||
CARROT_CHECK_AND_THROW(candidate_subset_filtered.count(selected_inputs_index),
|
||||
carrot_logic_error, "bug in policy: returned unselectable index");
|
||||
}
|
||||
};
|
||||
|
||||
// 8. Try dispatching for non-forward-secret input subsets, if preferred in this context
|
||||
if (prefer_non_fs)
|
||||
{
|
||||
// try getting rid of pre-carrot enotes first, if allowed
|
||||
if (!must_use_carrot)
|
||||
try_dispatch_input_selection(pre_carrot_inputs);
|
||||
// 8. Sanity check indices
|
||||
CARROT_CHECK_AND_THROW(!selected_inputs_indices.empty(),
|
||||
not_enough_usable_money,
|
||||
"No single allowed subset of candidates had enough money to fund payment proposals and fees for inputs");
|
||||
CARROT_CHECK_AND_THROW(*selected_inputs_indices.crbegin() < input_candidates.size(),
|
||||
carrot_logic_error, "bug: selected inputs index out of range");
|
||||
|
||||
// ... then external carrot
|
||||
try_dispatch_input_selection(external_carrot_inputs);
|
||||
}
|
||||
|
||||
// 9. Try dispatching for internal
|
||||
try_dispatch_input_selection(internal_inputs);
|
||||
|
||||
// 10. Try dispatching for non-FS *after* internal, if allowed and not already tried
|
||||
if (!must_use_internal || !prefer_non_fs)
|
||||
{
|
||||
// Spending non-FS inputs in a normal transfer transaction is not ideal, but at least
|
||||
// when partition it like this, we aren't "dirtying" the carrot with the pre-carrot, and
|
||||
// the internal with the external
|
||||
if (!must_use_carrot)
|
||||
try_dispatch_input_selection(pre_carrot_inputs);
|
||||
try_dispatch_input_selection(external_carrot_inputs);
|
||||
}
|
||||
|
||||
// 11. Try dispatching for all non-FS (mixed pre-carrot & carrot external), if allowed
|
||||
if (allow_mixed_carrotness)
|
||||
{
|
||||
// We're mixing carrot/pre-carrot spends here, but avoiding "dirtying" the internal
|
||||
try_dispatch_input_selection(set_union(pre_carrot_inputs, external_carrot_inputs));
|
||||
}
|
||||
|
||||
// 12. Try dispatching for all carrot, if allowed
|
||||
if (allow_mixed_externality)
|
||||
{
|
||||
// We're mixing internal & external carrot spends here, but avoiding "dirtying" the
|
||||
// carrot spends with pre-carrot spends. This will be quantum forward secret iff the
|
||||
// adversary doesn't know one of your public addresses
|
||||
try_dispatch_input_selection(set_union(external_carrot_inputs, internal_inputs));
|
||||
}
|
||||
|
||||
//! @TODO: MRL discussion about whether step 11 or step 12 should go first. In other words,
|
||||
// do we prefer to avoid dirtying internal, and protect against quantum adversaries
|
||||
// who know your public addresses? Or do we prefer to avoid dirtying w/ pre-carrot,
|
||||
// and protect against quantum adversaries with no special knowledge of your public
|
||||
// addresses, but whose attacks are only relevant when spending pre-FCMP++ enotes?
|
||||
|
||||
// 13. Try dispatching for everything, if allowed
|
||||
if (allow_mixed_carrotness && allow_mixed_externality)
|
||||
try_dispatch_input_selection(all_non_burned_inputs);
|
||||
|
||||
// Notice that we don't combine just the pre_carrot_inputs and internal_inputs by themselves
|
||||
|
||||
// 14. Sanity check indices
|
||||
CHECK_AND_ASSERT_THROW_MES(!selected_inputs_indices.empty(),
|
||||
"make_single_transfer_input_selector: input selection failed");
|
||||
CHECK_AND_ASSERT_THROW_MES(*selected_inputs_indices.crbegin() < input_candidates.size(),
|
||||
"make_single_transfer_input_selector: bug: selected inputs index out of range");
|
||||
|
||||
// 15. Do a greedy search for inputs whose amount doesn't pay for itself and drop them, logging debug messages
|
||||
// Note: this also happens to be optimal if the fee difference between each input count is constant
|
||||
bool should_search_for_dust = !(flags & ALLOW_DUST);
|
||||
while (should_search_for_dust && selected_inputs_indices.size() > CARROT_MIN_TX_INPUTS)
|
||||
{
|
||||
should_search_for_dust = false; // only loop again if we remove an input below
|
||||
const boost::multiprecision::int128_t fee_diff =
|
||||
required_money_by_input_count.at(selected_inputs_indices.size()) -
|
||||
required_money_by_input_count.at(selected_inputs_indices.size() - 1);
|
||||
CHECK_AND_ASSERT_THROW_MES(fee_diff >= 0,
|
||||
"make_single_transfer_input_selector: bug: fee is expected to be higher with fewer inputs");
|
||||
for (auto it = selected_inputs_indices.begin(); it != selected_inputs_indices.end(); ++it)
|
||||
{
|
||||
const CarrotPreSelectedInput &input_candidate = input_candidates[*it];
|
||||
if (input_candidate.core.amount < fee_diff)
|
||||
{
|
||||
MDEBUG("make_single_transfer_input_selector: dropping dusty input "
|
||||
<< input_candidate.core.key_image << " with amount " << input_candidate.core.amount
|
||||
<< ", which is less than the difference in fee of this transaction with it: " << fee_diff);
|
||||
selected_inputs_indices.erase(it);
|
||||
should_search_for_dust = true;
|
||||
break; // break out of inner `for` loop so we can recalculate `fee_diff`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 16. Check the sum of input amounts is great enough
|
||||
const size_t num_selected = selected_inputs_indices.size();
|
||||
const boost::multiprecision::int128_t required_money = required_money_by_input_count.at(num_selected);
|
||||
boost::multiprecision::int128_t input_amount_sum = 0;
|
||||
for (const size_t idx : selected_inputs_indices)
|
||||
// 9. Check the sum of input amounts is great enough
|
||||
const std::size_t num_selected = selected_inputs_indices.size();
|
||||
const boost::multiprecision::uint128_t required_money = required_money_by_input_count.at(num_selected);
|
||||
boost::multiprecision::uint128_t input_amount_sum = 0;
|
||||
for (const std::size_t idx : selected_inputs_indices)
|
||||
input_amount_sum += input_candidates[idx].core.amount;
|
||||
CHECK_AND_ASSERT_THROW_MES(input_amount_sum >= required_money,
|
||||
"make_single_transfer_input_selector: bug: input selection returned successful without enough funds");
|
||||
CARROT_CHECK_AND_THROW(input_amount_sum >= required_money,
|
||||
carrot_logic_error, "bug: input selection returned successful without enough funds");
|
||||
|
||||
// 17. Collect selected inputs
|
||||
// 10. Collect selected inputs
|
||||
selected_inputs_out.clear();
|
||||
selected_inputs_out.reserve(num_selected);
|
||||
for (size_t selected_input_index : selected_inputs_indices)
|
||||
@@ -422,100 +531,19 @@ select_inputs_func_t make_single_transfer_input_selector(
|
||||
namespace ispolicy
|
||||
{
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
std::vector<std::size_t> get_input_counts_in_preferred_order()
|
||||
{
|
||||
// 1 or 2 randomly, then
|
||||
// other ascending non-zero powers of 2, then
|
||||
// other ascending non-zero numbers
|
||||
|
||||
//! @TODO: MRL discussion about 2 vs 1 default input count when 1 input can pay. If we default to 1, then that may
|
||||
// reveal more information about the amount, and reveals that one can't pay with 1 output when using 2. Vice versa,
|
||||
// if we default to 2, then that means that one only owns 1 output when using 1. It may be the most advantageous to
|
||||
// randomly switch between preferring 1 vs 2. See: https://lavalle.pl/planning/node437.html
|
||||
|
||||
static_assert(CARROT_MAX_TX_INPUTS == FCMP_PLUS_PLUS_MAX_INPUTS, "inconsistent input count max limit");
|
||||
static_assert(CARROT_MIN_TX_INPUTS == 1 && CARROT_MAX_TX_INPUTS == 8,
|
||||
"refactor this function for different input count limits");
|
||||
|
||||
const bool random_bit = 0 == (crypto::rand<uint8_t>() & 0x01);
|
||||
if (random_bit)
|
||||
return {2, 1, 4, 8, 3, 5, 6, 7};
|
||||
else
|
||||
return {1, 2, 4, 8, 3, 5, 6, 7};
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
void select_two_inputs_prefer_oldest(const epee::span<const CarrotPreSelectedInput> input_candidates,
|
||||
const std::set<size_t> &selectable_inputs,
|
||||
const std::map<size_t, boost::multiprecision::int128_t> &required_money_by_input_count,
|
||||
std::set<size_t> &selected_inputs_indices_out)
|
||||
{
|
||||
// calculate required money and fee diff from one to two inputs
|
||||
const boost::multiprecision::int128_t required_money = required_money_by_input_count.at(2);
|
||||
const rct::xmr_amount fee_diff = boost::numeric_cast<rct::xmr_amount>(required_money -
|
||||
required_money_by_input_count.at(1));
|
||||
|
||||
// copy selectable_inputs, excluding dust, then sort by ascending block index
|
||||
std::vector<size_t> selectable_inputs_by_bi;
|
||||
selectable_inputs_by_bi.reserve(selectable_inputs.size());
|
||||
for (size_t idx : selectable_inputs)
|
||||
if (input_candidates[idx].core.amount > fee_diff)
|
||||
selectable_inputs_by_bi.push_back(idx);
|
||||
stable_sort_indices_by_block_index(input_candidates, selectable_inputs_by_bi);
|
||||
|
||||
// then copy again and *stable* sort by amount
|
||||
std::vector<size_t> selectable_inputs_by_amount_bi = selectable_inputs_by_bi;
|
||||
stable_sort_indices_by_amount(input_candidates, selectable_inputs_by_amount_bi);
|
||||
|
||||
// for each input in ascending block index order...
|
||||
for (size_t low_bi_input : selectable_inputs_by_bi)
|
||||
{
|
||||
// calculate how much we need in a corresponding input to this one
|
||||
const rct::xmr_amount old_amount = input_candidates[low_bi_input].core.amount;
|
||||
const boost::multiprecision::int128_t required_money_in_other_128 = (required_money > old_amount)
|
||||
? (required_money - old_amount) : 0;
|
||||
if (required_money_in_other_128 >= std::numeric_limits<rct::xmr_amount>::max())
|
||||
continue;
|
||||
const rct::xmr_amount required_money_in_other =
|
||||
boost::numeric_cast<rct::xmr_amount>(required_money_in_other_128);
|
||||
|
||||
// do a binary search for an input with at least that amount
|
||||
auto other_it = std::lower_bound(selectable_inputs_by_amount_bi.cbegin(),
|
||||
selectable_inputs_by_amount_bi.cend(),
|
||||
required_money_in_other,
|
||||
[input_candidates](size_t selectable_index, rct::xmr_amount required_money_in_other) -> bool
|
||||
{ return input_candidates[selectable_index].core.amount < required_money_in_other; });
|
||||
|
||||
// check that the iterator is in bounds and the complementary input isn't equal to the first
|
||||
if (other_it == selectable_inputs_by_amount_bi.cend())
|
||||
continue;
|
||||
else if (*other_it == low_bi_input)
|
||||
++other_it; // can't choose same input twice
|
||||
|
||||
if (other_it == selectable_inputs_by_amount_bi.cend())
|
||||
continue;
|
||||
|
||||
// we found a match !
|
||||
selected_inputs_indices_out = {low_bi_input, *other_it};
|
||||
return;
|
||||
}
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
void select_greedy_aging_fixed_count(const std::size_t fixed_n_inputs,
|
||||
const epee::span<const CarrotPreSelectedInput> input_candidates,
|
||||
void select_greedy_aging(const epee::span<const CarrotPreSelectedInput> input_candidates,
|
||||
const std::set<std::size_t> &selectable_inputs,
|
||||
const std::map<size_t, boost::multiprecision::int128_t> &required_money_by_input_count,
|
||||
const std::size_t n_inputs,
|
||||
const boost::multiprecision::uint128_t &required_money,
|
||||
std::set<std::size_t> &selected_inputs_indices_out)
|
||||
{
|
||||
MTRACE(__func__ << ": fixed_n_inputs=" << fixed_n_inputs << ", selectable_inputs.size()="
|
||||
<< selectable_inputs.size());
|
||||
MTRACE(__func__ << ": n_inputs=" << n_inputs << ", selectable_inputs.size()=" << selectable_inputs.size());
|
||||
|
||||
selected_inputs_indices_out.clear();
|
||||
|
||||
CHECK_AND_ASSERT_MES(fixed_n_inputs,, "select_greedy_aging: fixed_n_inputs must be non-zero");
|
||||
CHECK_AND_ASSERT_MES(fixed_n_inputs <= selectable_inputs.size(),,
|
||||
"select_greedy_aging: not enough inputs: " << selectable_inputs.size() << '/' << fixed_n_inputs);
|
||||
CHECK_AND_ASSERT_MES(required_money_by_input_count.count(fixed_n_inputs),,
|
||||
"select_greedy_aging: input count " << fixed_n_inputs << "not allowed");
|
||||
CHECK_AND_ASSERT_MES(n_inputs,, "select_greedy_aging: n_inputs must be non-zero");
|
||||
CHECK_AND_ASSERT_MES(n_inputs <= selectable_inputs.size(),,
|
||||
"select_greedy_aging: not enough inputs: " << selectable_inputs.size() << '/' << n_inputs);
|
||||
|
||||
// Sort selectable inputs by amount
|
||||
std::vector<std::size_t> selectable_inputs_by_amount(selectable_inputs.cbegin(), selectable_inputs.cend());
|
||||
@@ -524,7 +552,7 @@ void select_greedy_aging_fixed_count(const std::size_t fixed_n_inputs,
|
||||
// Select highest amount inputs and collect ordered multi-map of block indices of current selected inputs
|
||||
boost::multiprecision::uint128_t input_amount_sum = 0;
|
||||
std::multimap<std::uint64_t, std::size_t> selected_indices_by_block_index;
|
||||
for (size_t i = 0; i < fixed_n_inputs; ++i)
|
||||
for (size_t i = 0; i < n_inputs; ++i)
|
||||
{
|
||||
const std::size_t selectable_idx = selectable_inputs_by_amount.at(selectable_inputs_by_amount.size() - i - 1);
|
||||
const CarrotPreSelectedInput &input = input_candidates[selectable_idx];
|
||||
@@ -534,11 +562,9 @@ void select_greedy_aging_fixed_count(const std::size_t fixed_n_inputs,
|
||||
}
|
||||
|
||||
// Check enough money
|
||||
const boost::multiprecision::uint128_t required_money =
|
||||
boost::numeric_cast<boost::multiprecision::uint128_t>(required_money_by_input_count.at(fixed_n_inputs));
|
||||
if (input_amount_sum < required_money)
|
||||
{
|
||||
MDEBUG("not enough money in " << fixed_n_inputs << " inputs: " << cryptonote::print_money(input_amount_sum));
|
||||
MDEBUG("not enough money in " << n_inputs << " inputs: " << cryptonote::print_money(input_amount_sum));
|
||||
selected_inputs_indices_out.clear();
|
||||
return;
|
||||
}
|
||||
@@ -548,7 +574,7 @@ void select_greedy_aging_fixed_count(const std::size_t fixed_n_inputs,
|
||||
for (auto bi_it = selected_indices_by_block_index.rbegin(); bi_it != selected_indices_by_block_index.rend();)
|
||||
{
|
||||
std::uint64_t min_block_index = bi_it->first;
|
||||
size_t input_of_min_block_index_input = bi_it->second;
|
||||
std::size_t input_of_min_block_index_input = bi_it->second;
|
||||
const boost::multiprecision::uint128_t surplus = input_amount_sum - required_money;
|
||||
const rct::xmr_amount currently_selected_amount = input_candidates[bi_it->second].core.amount;
|
||||
const rct::xmr_amount lowest_replacement_amount = (currently_selected_amount > surplus)
|
||||
@@ -576,8 +602,8 @@ void select_greedy_aging_fixed_count(const std::size_t fixed_n_inputs,
|
||||
selected_indices_by_block_index.emplace(min_block_index, input_of_min_block_index_input);
|
||||
input_amount_sum -= currently_selected_amount;
|
||||
input_amount_sum += input_candidates[input_of_min_block_index_input].core.amount;
|
||||
CHECK_AND_ASSERT_THROW_MES(input_amount_sum >= required_money,
|
||||
"select_greedy_aging: BUG: replaced an input with one of too low amount");
|
||||
CARROT_CHECK_AND_THROW(input_amount_sum >= required_money,
|
||||
carrot_logic_error, "BUG: replaced an input with one of too low amount");
|
||||
}
|
||||
else // no replacement, go to next input
|
||||
{
|
||||
@@ -586,24 +612,5 @@ void select_greedy_aging_fixed_count(const std::size_t fixed_n_inputs,
|
||||
}
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
void select_greedy_aging(const epee::span<const CarrotPreSelectedInput> input_candidates,
|
||||
const std::set<std::size_t> &selectable_inputs,
|
||||
const std::map<size_t, boost::multiprecision::int128_t> &required_money_by_input_count,
|
||||
std::set<std::size_t> &selected_inputs_indices_out)
|
||||
{
|
||||
selected_inputs_indices_out.clear();
|
||||
|
||||
for (const std::size_t n_inputs : get_input_counts_in_preferred_order())
|
||||
{
|
||||
select_greedy_aging_fixed_count(n_inputs,
|
||||
input_candidates,
|
||||
selectable_inputs,
|
||||
required_money_by_input_count,
|
||||
selected_inputs_indices_out);
|
||||
if (!selected_inputs_indices_out.empty())
|
||||
return;
|
||||
}
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
} //namespace ispolicy
|
||||
} //namespace carrot
|
||||
|
||||
Reference in New Issue
Block a user