carrot_impl: more documentation and rename CarrotPreSelectedInput

This commit is contained in:
jeffro256
2025-05-11 00:21:18 -05:00
committed by akildemir
parent 02e8fea2fe
commit f1e48c9094
9 changed files with 126 additions and 53 deletions
+13 -13
View File
@@ -55,7 +55,7 @@ static std::ostream &operator<<(std::ostream &os, const CarrotSelectedInput &inp
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static std::ostream &operator<<(std::ostream &os, const CarrotPreSelectedInput &input)
static std::ostream &operator<<(std::ostream &os, const InputCandidate &input)
{
os << "{ core = " << input.core << ", block_index = " << input.block_index
<< ", carrot = " << !input.is_pre_carrot << ", external = " << input.is_external << " }";
@@ -71,7 +71,7 @@ static std::set<size_t> set_union(const std::set<size_t> &a, const std::set<size
};
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static void stable_sort_indices_by_amount(const epee::span<const CarrotPreSelectedInput> input_candidates,
static void stable_sort_indices_by_amount(const epee::span<const InputCandidate> input_candidates,
std::vector<size_t> &indices_inout)
{
std::stable_sort(indices_inout.begin(), indices_inout.end(),
@@ -85,7 +85,7 @@ static void stable_sort_indices_by_amount(const epee::span<const CarrotPreSelect
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static std::pair<std::size_t, boost::multiprecision::uint128_t> input_count_for_max_usable_money(
const epee::span<const CarrotPreSelectedInput> input_candidates,
const epee::span<const InputCandidate> input_candidates,
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)
@@ -139,7 +139,7 @@ static std::pair<std::size_t, boost::multiprecision::uint128_t> input_count_for_
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
int compare_input_candidate_same_ki(const CarrotPreSelectedInput &lhs, const CarrotPreSelectedInput &rhs)
int compare_input_candidate_same_ki(const InputCandidate &lhs, const InputCandidate &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");
@@ -174,7 +174,7 @@ int compare_input_candidate_same_ki(const CarrotPreSelectedInput &lhs, const Car
}
//-------------------------------------------------------------------------------------------------------------------
std::vector<std::set<std::size_t>> form_preferred_input_candidate_subsets(
const epee::span<const CarrotPreSelectedInput> input_candidates,
const epee::span<const InputCandidate> input_candidates,
const std::uint32_t flags,
const bool is_normal_transfer)
{
@@ -190,7 +190,7 @@ std::vector<std::set<std::size_t>> form_preferred_input_candidate_subsets(
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];
const InputCandidate &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())
{
@@ -198,7 +198,7 @@ std::vector<std::set<std::size_t>> form_preferred_input_candidate_subsets(
}
else
{
const CarrotPreSelectedInput &other_input_candidate = input_candidates[it->second];
const InputCandidate &other_input_candidate = input_candidates[it->second];
if (compare_input_candidate_same_ki(other_input_candidate, input_candidate) < 0)
it->second = i;
}
@@ -338,7 +338,7 @@ std::vector<std::size_t> get_input_counts_in_preferred_order()
}
//-------------------------------------------------------------------------------------------------------------------
select_inputs_func_t make_single_transfer_input_selector(
const epee::span<const CarrotPreSelectedInput> input_candidates,
const epee::span<const InputCandidate> input_candidates,
const epee::span<const input_selection_policy_t> policies,
const std::uint32_t flags,
std::set<size_t> *selected_input_indices_out)
@@ -379,7 +379,7 @@ select_inputs_func_t make_single_transfer_input_selector(
// 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)
for (const InputCandidate &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,
@@ -424,7 +424,7 @@ select_inputs_func_t make_single_transfer_input_selector(
"-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];
const InputCandidate &input_candidate = input_candidates[selectable_index];
MDEBUG(" " << input_candidate);
}
@@ -518,7 +518,7 @@ select_inputs_func_t make_single_transfer_input_selector(
namespace ispolicy
{
//-------------------------------------------------------------------------------------------------------------------
void select_greedy_aging(const epee::span<const CarrotPreSelectedInput> input_candidates,
void select_greedy_aging(const epee::span<const InputCandidate> input_candidates,
const std::set<std::size_t> &selectable_inputs,
const std::size_t n_inputs,
const boost::multiprecision::uint128_t &required_money,
@@ -542,7 +542,7 @@ void select_greedy_aging(const epee::span<const CarrotPreSelectedInput> input_ca
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];
const InputCandidate &input = input_candidates[selectable_idx];
input_amount_sum += input.core.amount;
selected_inputs_indices_out.insert(selectable_idx);
selected_indices_by_block_index.emplace(input.block_index, selectable_idx);
@@ -573,7 +573,7 @@ void select_greedy_aging(const epee::span<const CarrotPreSelectedInput> input_ca
const std::size_t potential_replacement_idx = *amount_it;
if (selected_inputs_indices_out.count(potential_replacement_idx))
continue;
const CarrotPreSelectedInput &potential_replacement_input = input_candidates[potential_replacement_idx];
const InputCandidate &potential_replacement_input = input_candidates[potential_replacement_idx];
if (potential_replacement_input.block_index < min_block_index)
{
min_block_index = potential_replacement_input.block_index;
+43 -12
View File
@@ -40,7 +40,7 @@
namespace carrot
{
struct CarrotPreSelectedInput
struct InputCandidate
{
CarrotSelectedInput core;
@@ -62,12 +62,22 @@ namespace InputSelectionFlags
static constexpr std::uint32_t ALLOW_DUST = 1 << 5;
}
/**
* brief: input_selection_policy_t - a functor which implements N-input selection on a subset of candidates
* param: input_candidates -
* param: selectable_input_indices - subset of indices in `input_candidates` allowed to select from
* param: n_inputs - exact number of selected inputs should return
* param: required_money - sum of amounts of selected inputs should be greater than or equal to this value
* outparam: selected_inputs_indices_out - `n_inputs` subset of `selectable_input_indices`, of selected input indices
*
* To signify selection failure, `selected_inputs_indices_out` should be empty after end of call.
*/
using input_selection_policy_t = std::function<void(
epee::span<const CarrotPreSelectedInput>, // input candidates
const std::set<std::size_t>&, // selectable subset indices
std::size_t, // input count
const boost::multiprecision::uint128_t&, // required money for input count
std::set<std::size_t>& // selected indices
epee::span<const InputCandidate> input_candidates,
const std::set<std::size_t> &selectable_input_indices,
std::size_t n_inputs,
const boost::multiprecision::uint128_t &required_money,
std::set<std::size_t> &selected_inputs_indices_out
)>;
/**
@@ -83,11 +93,11 @@ using input_selection_policy_t = std::function<void(
* 3. Is pre-Carrot enote? (`false` is better for spending QFS)
* 4. Is external enote? (`false` is better for spending QFS)
*/
int compare_input_candidate_same_ki(const CarrotPreSelectedInput &lhs, const CarrotPreSelectedInput &rhs);
int compare_input_candidate_same_ki(const InputCandidate &lhs, const InputCandidate &rhs);
/**
* brief: form_preferred_input_candidate_subsets - make subsets of input candidates to try selection in preferred order
* param: input_candidates - slice to user-provided input candidates
* param: flags - values in InputSelectionFlags namespace OR'ed together
* param: flags - see InputSelectionFlags namespace
* param: is_normal_transfer - true iff num normal non-dummy payments in tx to perform selection for is >= 1
* return: ordered list of subsets (represented by 0-based indices) of input_candidates to try selection on
*
@@ -134,7 +144,7 @@ int compare_input_candidate_same_ki(const CarrotPreSelectedInput &lhs, const Car
* internal due to the lack of the view-balance secret s_vb in legacy key hierarchies.
*/
std::vector<std::set<std::size_t>> form_preferred_input_candidate_subsets(
const epee::span<const CarrotPreSelectedInput> input_candidates,
const epee::span<const InputCandidate> input_candidates,
const std::uint32_t flags,
const bool is_normal_transfer);
/**
@@ -145,16 +155,37 @@ std::vector<std::set<std::size_t>> form_preferred_input_candidate_subsets(
* function is to determine the order in which we should try to select a certain number of inputs.
*/
std::vector<std::size_t> get_input_counts_in_preferred_order();
/**
* brief: make_single_transfer_input_selector - a customizable input selector for single (i.e. not batched) transfers
* param: input_candidates -
* param: policies - slice of ISPs to attempt selection on, in order of user's preference
* param: flags - see InputSelectionFlags namespace
* outparam: selected_input_indices_out - selected indices into `input_candidates` (optional)
* return: input selector functor
*
* The returned input selector considers provided input candidates, and creates subsets of the
* candidates as according to `form_preferred_input_candidate_subsets`. Then, in the order of input
* counts as according to `get_input_counts_in_preferred_order`, finds the first pair (subset, input
* count) that contains enough "usable" money: a sum of money great enough to pay the nominal output
* sum, plus any required fees as according to the input count. Once that is found, ISPs are
* dispatched in provided user order until one succeeds. If none succeed for that pair, then the
* whole of input selection fails, we do not move onto the next (subset, input count) pair.
*
* SAFETY: The lifetime of objects referenced by `input_candidates` and `selected_input_indices_out`
* (if not null) must be valid at least as long as as the last call to the returned functor.
*/
select_inputs_func_t make_single_transfer_input_selector(
const epee::span<const CarrotPreSelectedInput> input_candidates,
const epee::span<const InputCandidate> input_candidates,
const epee::span<const input_selection_policy_t> policies,
const std::uint32_t flags,
std::set<size_t> *selected_input_indices_out);
namespace ispolicy
{
void select_greedy_aging(const epee::span<const CarrotPreSelectedInput>,
/**
* brief: select_greedy_aging - an ISP which generally attempts to select old outputs, but isn't necessarily optimal
*/
void select_greedy_aging(const epee::span<const InputCandidate>,
const std::set<std::size_t>&,
std::size_t,
const boost::multiprecision::uint128_t&,
+8
View File
@@ -39,9 +39,14 @@
namespace carrot
{
/**
* brief: subaddress_index -
*/
struct subaddress_index
{
// j_major
std::uint32_t major;
// j_minor
std::uint32_t minor;
bool is_subaddress() const
@@ -58,6 +63,9 @@ static inline bool operator!=(const subaddress_index a, const subaddress_index b
return !(a == b);
}
/**
* brief: AddressDeriveType - used in hybrid key hierarchies to specify how to derive subaddresses for the same index
*/
enum class AddressDeriveType
{
Auto,
+23 -2
View File
@@ -40,18 +40,39 @@
namespace carrot
{
/**
* brief: get_output_enote_proposals_from_proposal_v1 - get_output_enote_proposals for transaction proposals
* param: tx_proposal - transaction proposal
* param: s_view_balance_dev - device for s_vb (optional)
* param: k_view_dev - device for k_v (optional)
* outparam: output_enote_proposals_out - resultant output enote set
* putparam: encrypted_payment_id_out - resultant pid_enc
* outparam: payment_proposal_order_out - order of payment proposals in resultant output enote set
*/
void get_output_enote_proposals_from_proposal_v1(const CarrotTransactionProposalV1 &tx_proposal,
const view_balance_secret_device *s_view_balance_dev,
const view_incoming_key_device *k_view_dev,
std::vector<RCTOutputEnoteProposal> &output_enote_proposals_out,
encrypted_payment_id_t &encrypted_payment_id_out,
std::vector<std::pair<bool, std::size_t>> *payment_proposal_order_out = nullptr);
/**
* brief: make_signable_tx_hash_from_proposal_v1 - make signable transaction hash from tx proposal and keys
* param: tx_proposal - transaction proposal
* param: s_view_balance_dev - device for s_vb (optional)
* param: k_view_dev - device for k_v (optional)
* outparam: signable_tx_hash_out - signable transaction hash
*/
void make_signable_tx_hash_from_proposal_v1(const CarrotTransactionProposalV1 &tx_proposal,
const view_balance_secret_device *s_view_balance_dev,
const view_incoming_key_device *k_view_dev,
crypto::hash &signable_tx_hash_out);
/**
* brief: make_pruned_transaction_from_proposal_v1 - make pruned Carrot/FCMP++ transaction from tx proposal and keys
* param: tx_proposal - transaction proposal
* param: s_view_balance_dev - device for s_vb (optional)
* param: k_view_dev - device for k_v (optional)
* outparam: pruned_tx_out - pruned Carrot/FCMP++ transaction represented by transaction proposal
*/
void make_pruned_transaction_from_proposal_v1(const CarrotTransactionProposalV1 &tx_proposal,
const view_balance_secret_device *s_view_balance_dev,
const view_incoming_key_device *k_view_dev,
+17 -1
View File
@@ -41,6 +41,12 @@
namespace carrot
{
/**
* brief: CarrotPaymentProposalVerifiableSelfSendV1 - A selfsend payment proposal, verified to an owned address
*
* The `subaddr_index` field is intended to be be used to derive
* `proposal.destination_address_spend_pubkey`, without the need for a subaddress lookahead table.
*/
struct CarrotPaymentProposalVerifiableSelfSendV1
{
CarrotPaymentProposalSelfSendV1 proposal;
@@ -48,7 +54,17 @@ struct CarrotPaymentProposalVerifiableSelfSendV1
};
/**
* brief: CarrotTransactionProposalV1
* brief: CarrotTransactionProposalV1 - A specification on how to construct a Carrot transaction, minus key material
*
* The fields in `CarrotTransactionProposalV1` are chosen as what is the absolute minimum amount of
* information required to verifiably, in a human-meaningful way, reconstruct the "signable
* transaction hash". The signable transaction hash, (usually written `signable_tx_hash` in code),
* is the actual 32-byte message that the FCMP++ Spend-Authorization & Linkability (SA/L) proofs
* sign and verify against. In situations where the signing device may be separate from the device
* formulating the transaction (e.g. cold signing, hardware devices, multisig, etc), this struct
* can be passed amongst signers so that the signers can actually verifying what they are signing.
*
* For exact details on what goes into the signable transaction hash, see `rct::get_pre_mlsag_hash`.
*/
struct CarrotTransactionProposalV1
{
-5
View File
@@ -70,13 +70,8 @@ static void append_additional_payment_proposal_if_necessary(
bool have_payment_type_selfsend = false;
for (const CarrotPaymentProposalVerifiableSelfSendV1 &selfsend_payment_proposal : selfsend_payment_proposals_inout)
{
if (selfsend_payment_proposal.proposal.enote_type == CarrotEnoteType::PAYMENT)
{
have_payment_type_selfsend = true;
break;
}
}
const auto additional_output_proposal = get_additional_output_proposal(normal_payment_proposals_inout.size(),
selfsend_payment_proposals_inout.size(),
+2
View File
@@ -26,6 +26,8 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//! @file Utilities for creating Carrot transaction proposals
#pragma once
//local headers
+3 -3
View File
@@ -211,8 +211,8 @@ carrot::select_inputs_func_t make_wallet2_single_transfer_input_selector(
const bool allow_pre_carrot_inputs_in_normal_transfers,
std::set<size_t> &selected_transfer_indices_out)
{
// Collect transfer_container into a `std::vector<carrot::CarrotPreSelectedInput>` for usable inputs
std::vector<carrot::CarrotPreSelectedInput> input_candidates;
// Collect transfer_container into a `std::vector<carrot::InputCandidate>` for usable inputs
std::vector<carrot::InputCandidate> input_candidates;
std::vector<size_t> input_candidates_transfer_indices;
input_candidates.reserve(transfers.size());
input_candidates_transfer_indices.reserve(transfers.size());
@@ -226,7 +226,7 @@ carrot::select_inputs_func_t make_wallet2_single_transfer_input_selector(
ignore_below,
top_block_index))
{
input_candidates.push_back(carrot::CarrotPreSelectedInput{
input_candidates.push_back(carrot::InputCandidate{
.core = carrot::CarrotSelectedInput{
.amount = td.amount(),
.key_image = td.m_key_image