carrot_impl: make multiple sweep tx proposals at once

This commit is contained in:
jeffro256
2025-04-30 14:37:47 -05:00
committed by akildemir
parent c4dbc4ea7b
commit b982d4c5a6
3 changed files with 424 additions and 75 deletions

View File

@@ -50,17 +50,11 @@ namespace wallet
{
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
static bool is_transfer_unlocked_for_next_fcmp_pp_block(const wallet2::transfer_details &td,
const uint64_t top_block_index)
template <typename T>
static constexpr T div_ceil(T dividend, T divisor)
{
const uint64_t next_block_index = top_block_index + 1;
// @TODO: handle FCMP++ conversion of UNIX unlock time to block index number
if (td.m_block_height + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE > next_block_index)
return false;
return true;
static_assert(std::is_unsigned_v<T>, "T not unsigned int");
return (dividend + divisor - 1) / divisor;
}
//-------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------
@@ -70,12 +64,18 @@ static bool is_transfer_usable_for_input_selection(const wallet2::transfer_detai
const rct::xmr_amount ignore_above,
const rct::xmr_amount ignore_below,
const uint64_t top_block_index)
{
{
/**
* This additional check appears to be for fcmp++.
const uint64_t last_locked_block_index = cryptonote::get_last_locked_block_index(
td.m_tx.unlock_time, td.m_block_height);
*/
return !td.m_spent
&& td.m_key_image_known
&& !td.m_key_image_partial
&& !td.m_frozen
&& is_transfer_unlocked_for_next_fcmp_pp_block(td, top_block_index)
// && last_locked_block_index <= top_block_index
&& td.m_subaddr_index.major == from_account
&& (from_subaddresses.empty() || from_subaddresses.count(td.m_subaddr_index.minor) == 1)
&& td.amount() >= ignore_below
@@ -254,9 +254,6 @@ std::vector<carrot::CarrotTransactionProposalV1> make_carrot_transaction_proposa
{
wallet2::transfer_container unused_transfers(transfers);
//! @TODO: handle HW devices
carrot::view_incoming_key_ram_borrowed_device k_view_incoming_dev(acc_keys.m_view_secret_key);
std::vector<carrot::CarrotTransactionProposalV1> tx_proposals;
tx_proposals.reserve(dsts.size() / (FCMP_PLUS_PLUS_MAX_OUTPUTS - 1) + 1);
@@ -307,8 +304,6 @@ std::vector<carrot::CarrotTransactionProposalV1> make_carrot_transaction_proposa
fee_per_weight,
extra,
std::move(select_inputs),
/*s_view_balance_dev=*/nullptr, //! @TODO: handle carrot
&k_view_incoming_dev,
acc_keys.m_account_address.m_spend_public_key,
subtractable_normal_payment_proposals,
subtractable_selfsend_payment_proposals,
@@ -369,7 +364,7 @@ std::vector<carrot::CarrotTransactionProposalV1> make_carrot_transaction_proposa
w.get_account().get_keys());
}
//-------------------------------------------------------------------------------------------------------------------
carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_sweep(
std::vector<carrot::CarrotTransactionProposalV1> make_carrot_transaction_proposals_wallet2_sweep(
const wallet2::transfer_container &transfers,
const std::unordered_map<crypto::public_key, cryptonote::subaddress_index> &subaddress_map,
const std::vector<crypto::key_image> &input_key_images,
@@ -381,10 +376,11 @@ carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_swe
const std::uint64_t top_block_index,
const cryptonote::account_keys &acc_keys)
{
CHECK_AND_ASSERT_THROW_MES(!input_key_images.empty(),
"make carrot transaction proposal wallet2 sweep: no key images provided");
CHECK_AND_ASSERT_THROW_MES(n_dests <= carrot::CARROT_MAX_TX_INPUTS,
"make carrot transaction proposal wallet2 sweep: too many destinations");
const size_t n_inputs = input_key_images.size();
CHECK_AND_ASSERT_THROW_MES(n_inputs,
__func__ << ": no key images provided");
CHECK_AND_ASSERT_THROW_MES(n_dests,
__func__ << ": n_dests is zero");
// Check that the key image is available and isn't spent, and collect amounts
std::vector<rct::xmr_amount> input_amounts;
@@ -394,7 +390,7 @@ carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_swe
{
const auto ki_it = best_transfers_by_ki.find(ki);
CHECK_AND_ASSERT_THROW_MES(ki_it != best_transfers_by_ki.cend(),
"make carrot transaction proposal wallet2 sweep: unknown key image");
__func__ << ": unknown key image");
const wallet2::transfer_details &td = transfers.at(ki_it->second);
CHECK_AND_ASSERT_THROW_MES(is_transfer_usable_for_input_selection(td,
td.m_subaddr_index.major,
@@ -402,49 +398,90 @@ carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_swe
/*ignore_above=*/MONEY_SUPPLY,
/*ignore_below=*/0,
top_block_index),
"make carrot transaction proposal wallet2 sweep: transfer not usable as an input");
__func__ << ": transfer not usable as an input");
input_amounts.push_back(td.amount());
}
// build n_dests payment proposals
std::vector<carrot::CarrotPaymentProposalV1> normal_payment_proposals;
std::vector<carrot::CarrotPaymentProposalVerifiableSelfSendV1> selfsend_payment_proposals;
for (size_t i = 0; i < n_dests; ++i)
// get 1 payment proposal corresponding to (address, is_subaddres)
std::vector<carrot::CarrotPaymentProposalV1> normal_payment_proposal;
std::vector<carrot::CarrotPaymentProposalVerifiableSelfSendV1> selfsend_payment_proposal;
const bool is_selfsend_dest = build_payment_proposals(normal_payment_proposal,
selfsend_payment_proposal,
cryptonote::tx_destination_entry(/*amount=*/0, address, is_subaddress),
subaddress_map);
CHECK_AND_ASSERT_THROW_MES((is_selfsend_dest && selfsend_payment_proposal.size() == 1)
|| (!is_selfsend_dest && normal_payment_proposal.size() == 1),
__func__ << ": BUG in build_payment_proposals: incorrect count for payment proposal lists");
// in/out/tx count calculations
const size_t max_dsts_per_tx = FCMP_PLUS_PLUS_MAX_OUTPUTS - (size_t(!is_selfsend_dest));
const size_t min_n_dests = div_ceil<size_t>(n_inputs, FCMP_PLUS_PLUS_MAX_INPUTS);
const size_t max_n_dests = n_inputs * max_dsts_per_tx;
CHECK_AND_ASSERT_THROW_MES(n_dests >= min_n_dests,
__func__ << ": not enough destinations (" << n_dests << ") for number of inputs to be spent ("
<< n_inputs << ")");
CHECK_AND_ASSERT_THROW_MES(n_dests <= max_n_dests,
__func__ << ": too many destinations (" << n_dests << ") for number of inputs to be spent ("
<< n_inputs << ")");
const size_t n_txs = std::max(div_ceil(n_dests, max_dsts_per_tx), min_n_dests);
CHECK_AND_ASSERT_THROW_MES(n_txs, __func__ << ": BUG: calculated target num of txs to be 0");
struct sweep_tx_outlay_t
{
build_payment_proposals(normal_payment_proposals,
selfsend_payment_proposals,
cryptonote::tx_destination_entry(/*amount=*/0, address, is_subaddress),
subaddress_map);
std::vector<carrot::CarrotSelectedInput> selected_inputs;
size_t n_tx_dests;
};
// build list of sweep_tx_outlay_t's
std::vector<sweep_tx_outlay_t> tx_outlays(n_txs);
size_t input_idx = 0;
for (size_t tx_idx = 0; tx_idx < tx_outlays.size(); ++tx_idx)
{
sweep_tx_outlay_t &tx_outlay = tx_outlays[tx_idx];
const size_t n_remaining_inputs = n_inputs - input_idx;
const size_t n_tx_inputs = std::min(div_ceil(n_inputs, n_txs), n_remaining_inputs);
const size_t n_tx_dests = n_dests / n_txs + ((tx_idx < (n_dests % n_txs)) ? 1 : 0);
const size_t max_input_idx = input_idx + n_tx_inputs;
tx_outlay.selected_inputs.reserve(n_tx_inputs);
for (; input_idx < max_input_idx; ++input_idx)
tx_outlay.selected_inputs.push_back({input_amounts.at(input_idx), input_key_images.at(input_idx)});
tx_outlay.n_tx_dests = n_tx_dests;
}
// Collect CarrotSelectedInput
std::vector<carrot::CarrotSelectedInput> selected_inputs(input_key_images.size());
for (size_t i = 0; i < input_key_images.size(); ++i)
//! @TODO: sanity check tx_outlays
// convert sweep outlays into transaction proposals
std::vector<carrot::CarrotTransactionProposalV1> tx_proposals;
tx_proposals.reserve(tx_outlays.size());
for (sweep_tx_outlay_t &sweep_outlay : tx_outlays)
{
selected_inputs[i] = carrot::CarrotSelectedInput{
.amount = input_amounts.at(i),
.key_image = input_key_images.at(i)
};
std::vector<carrot::CarrotPaymentProposalV1> tx_normal_payment_proposals;
std::vector<carrot::CarrotPaymentProposalVerifiableSelfSendV1> tx_selfsend_payment_proposals;
if (is_selfsend_dest)
tx_selfsend_payment_proposals.resize(sweep_outlay.n_tx_dests, selfsend_payment_proposal.at(0));
else
tx_normal_payment_proposals.resize(sweep_outlay.n_tx_dests, normal_payment_proposal.at(0));
carrot::CarrotTransactionProposalV1 tx_proposal;
carrot::make_carrot_transaction_proposal_v1_sweep(tx_normal_payment_proposals,
tx_selfsend_payment_proposals,
fee_per_weight,
extra,
std::move(sweep_outlay.selected_inputs),
acc_keys.m_account_address.m_spend_public_key,
tx_proposal);
tx_proposals.push_back(std::move(tx_proposal));
}
//! @TODO: handle HW devices
carrot::view_incoming_key_ram_borrowed_device k_view_incoming_dev(acc_keys.m_view_secret_key);
carrot::CarrotTransactionProposalV1 tx_proposal;
carrot::make_carrot_transaction_proposal_v1_sweep(normal_payment_proposals,
selfsend_payment_proposals,
fee_per_weight,
extra,
std::move(selected_inputs),
/*s_view_balance_dev=*/nullptr, //! @TODO: handle carrot
&k_view_incoming_dev,
acc_keys.m_account_address.m_spend_public_key,
tx_proposal);
return tx_proposal;
return tx_proposals;
}
//-------------------------------------------------------------------------------------------------------------------
carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_sweep(
std::vector<carrot::CarrotTransactionProposalV1> make_carrot_transaction_proposals_wallet2_sweep(
wallet2 &w,
const std::vector<crypto::key_image> &input_key_images,
const cryptonote::account_public_address &address,
@@ -460,10 +497,10 @@ carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_swe
const std::uint64_t current_chain_height = w.get_blockchain_current_height();
CHECK_AND_ASSERT_THROW_MES(current_chain_height > 0,
"make_carrot_transaction_proposal_wallet2_transfer: chain height is 0, there is no top block");
"make_carrot_transaction_proposals_wallet2_sweep: chain height is 0, there is no top block");
const std::uint64_t top_block_index = current_chain_height - 1;
return make_carrot_transaction_proposal_wallet2_sweep(
return make_carrot_transaction_proposals_wallet2_sweep(
transfers,
w.get_subaddress_map_ref(),
input_key_images,