From 29f9a4d679f91a67a491f391fefe1f1591c87853 Mon Sep 17 00:00:00 2001 From: jeffro256 Date: Mon, 28 Apr 2025 12:32:54 -0500 Subject: [PATCH] carrot_impl: make multiple tx transfer proposals at once --- src/wallet/tx_builder.cpp | 186 +++++++++---------------- src/wallet/tx_builder.h | 30 +--- tests/unit_tests/wallet_tx_builder.cpp | 6 +- 3 files changed, 77 insertions(+), 145 deletions(-) diff --git a/src/wallet/tx_builder.cpp b/src/wallet/tx_builder.cpp index 924027bbd..8dadd72f1 100644 --- a/src/wallet/tx_builder.cpp +++ b/src/wallet/tx_builder.cpp @@ -35,6 +35,7 @@ #include "carrot_impl/carrot_tx_builder_utils.h" #include "carrot_impl/input_selection.h" #include "cryptonote_basic/cryptonote_format_utils.h" +#include "common/container_helpers.h" //third party headers @@ -237,61 +238,70 @@ carrot::select_inputs_func_t make_wallet2_single_transfer_input_selector( }; } //------------------------------------------------------------------------------------------------------------------- -carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_transfer_subtractable( +std::vector make_carrot_transaction_proposals_wallet2_transfer( const wallet2::transfer_container &transfers, const std::unordered_map &subaddress_map, - const std::vector &dsts, + std::vector dsts, const rct::xmr_amount fee_per_weight, const std::vector &extra, const std::uint32_t subaddr_account, const std::set &subaddr_indices, const rct::xmr_amount ignore_above, const rct::xmr_amount ignore_below, - const wallet2::unique_index_container& subtract_fee_from_outputs, + wallet2::unique_index_container subtract_fee_from_outputs, const std::uint64_t top_block_index, const cryptonote::account_keys &acc_keys) { - // build payment proposals and subtractable info - std::vector normal_payment_proposals; - std::vector selfsend_payment_proposals; - std::set subtractable_normal_payment_proposals; - std::set subtractable_selfsend_payment_proposals; - for (size_t i = 0; i < dsts.size(); ++i) - { - const cryptonote::tx_destination_entry &dst = dsts.at(i); - const bool is_selfsend = build_payment_proposals(normal_payment_proposals, - selfsend_payment_proposals, - dst, - subaddress_map); - if (subtract_fee_from_outputs.count(i)) - { - if (is_selfsend) - subtractable_selfsend_payment_proposals.insert(selfsend_payment_proposals.size() - 1); - else - subtractable_normal_payment_proposals.insert(normal_payment_proposals.size() - 1); - } - } - - // make input selector - std::set selected_transfer_indices; - carrot::select_inputs_func_t select_inputs = make_wallet2_single_transfer_input_selector( - transfers, - subaddr_account, - subaddr_indices, - ignore_above, - ignore_below, - top_block_index, - /*allow_carrot_external_inputs_in_normal_transfers=*/true, - /*allow_pre_carrot_inputs_in_normal_transfers=*/true, - selected_transfer_indices); + 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); - carrot::CarrotTransactionProposalV1 tx_proposal; - if (subtract_fee_from_outputs.size()) + std::vector tx_proposals; + tx_proposals.reserve(dsts.size() / (FCMP_PLUS_PLUS_MAX_OUTPUTS - 1) + 1); + + while (!dsts.empty()) { - carrot::make_carrot_transaction_proposal_v1_transfer_subtractable( + const std::size_t num_dsts_to_complete = std::min(dsts.size(), FCMP_PLUS_PLUS_MAX_OUTPUTS - 1); + + // build payment proposals and subtractable info from last `num_dsts_to_complete` dsts + std::vector normal_payment_proposals; + std::vector selfsend_payment_proposals; + std::set subtractable_normal_payment_proposals; + std::set subtractable_selfsend_payment_proposals; + for (size_t i = 0; i < num_dsts_to_complete && !dsts.empty(); ++i) + { + const cryptonote::tx_destination_entry &dst = dsts.back(); + const bool is_selfsend = build_payment_proposals(normal_payment_proposals, + selfsend_payment_proposals, + dst, + subaddress_map); + if (subtract_fee_from_outputs.count(dsts.size() - 1)) + { + if (is_selfsend) + subtractable_selfsend_payment_proposals.insert(selfsend_payment_proposals.size() - 1); + else + subtractable_normal_payment_proposals.insert(normal_payment_proposals.size() - 1); + } + dsts.pop_back(); + } + + // make input selector + std::set selected_transfer_indices; + carrot::select_inputs_func_t select_inputs = make_wallet2_single_transfer_input_selector( + unused_transfers, + subaddr_account, + subaddr_indices, + ignore_above, + ignore_below, + top_block_index, + /*allow_carrot_external_inputs_in_normal_transfers=*/true, + /*allow_pre_carrot_inputs_in_normal_transfers=*/true, + selected_transfer_indices); + + // make proposal + carrot::CarrotTransactionProposalV1 tx_proposal; + carrot::make_carrot_transaction_proposal_v1_transfer( normal_payment_proposals, selfsend_payment_proposals, fee_per_weight, @@ -303,25 +313,23 @@ carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_tra subtractable_normal_payment_proposals, subtractable_selfsend_payment_proposals, tx_proposal); - } - else // non-subtractable - { - carrot::make_carrot_transaction_proposal_v1_transfer( - normal_payment_proposals, - selfsend_payment_proposals, - 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, - tx_proposal); + + // update `unused_transfers` for next proposal by removing selected transfers + tools::for_all_in_vector_erase_no_preserve_order_if(unused_transfers, + [&tx_proposal](const wallet2::transfer_details &td) -> bool { + const auto &used_kis = tx_proposal.key_images_sorted; + const auto ki_it = std::find(used_kis.cbegin(), used_kis.cend(), td.m_key_image); + return ki_it != used_kis.cend(); + } + ); + + tx_proposals.push_back(std::move(tx_proposal)); } - return tx_proposal; + return tx_proposals; } //------------------------------------------------------------------------------------------------------------------- -carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_transfer_subtractable( +std::vector make_carrot_transaction_proposals_wallet2_transfer( wallet2 &w, const std::vector &dsts, const std::uint32_t priority, @@ -335,14 +343,18 @@ carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_tra wallet2::transfer_container transfers; w.get_transfers(transfers); + const bool use_per_byte_fee = w.use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0); + CHECK_AND_ASSERT_THROW_MES(use_per_byte_fee, + "make_carrot_transaction_proposals_wallet2_transfer: not using per-byte base fee"); + const rct::xmr_amount fee_per_weight = w.get_base_fee(priority); 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_subtractable: chain height is 0, there is no top block"); + "make_carrot_transaction_proposals_wallet2_transfer: 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_transfer_subtractable( + return make_carrot_transaction_proposals_wallet2_transfer( transfers, w.get_subaddress_map_ref(), dsts, @@ -357,68 +369,6 @@ carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_tra w.get_account().get_keys()); } //------------------------------------------------------------------------------------------------------------------- -carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_transfer( - const wallet2::transfer_container &transfers, - const std::unordered_map &subaddress_map, - const std::vector &dsts, - const rct::xmr_amount fee_per_weight, - const std::vector &extra, - const std::uint32_t subaddr_account, - const std::set &subaddr_indices, - const rct::xmr_amount ignore_above, - const rct::xmr_amount ignore_below, - const std::uint64_t top_block_index, - const cryptonote::account_keys &acc_keys) -{ - return make_carrot_transaction_proposal_wallet2_transfer_subtractable( - transfers, - subaddress_map, - dsts, - fee_per_weight, - extra, - subaddr_account, - subaddr_indices, - ignore_above, - ignore_below, - /*subtract_fee_from_outputs=*/{}, - top_block_index, - acc_keys); -} -//------------------------------------------------------------------------------------------------------------------- -carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_transfer( - wallet2 &w, - const std::vector &dsts, - const std::uint32_t priority, - const std::vector &extra, - const std::uint32_t subaddr_account, - const std::set &subaddr_indices, - const rct::xmr_amount ignore_above, - const rct::xmr_amount ignore_below) -{ - wallet2::transfer_container transfers; - w.get_transfers(transfers); - - const rct::xmr_amount fee_per_weight = w.get_base_fee(priority); - - 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"); - const std::uint64_t top_block_index = current_chain_height - 1; - - return make_carrot_transaction_proposal_wallet2_transfer( - transfers, - w.get_subaddress_map_ref(), - dsts, - fee_per_weight, - extra, - subaddr_account, - subaddr_indices, - ignore_above, - ignore_below, - top_block_index, - w.get_account().get_keys()); -} -//------------------------------------------------------------------------------------------------------------------- carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_sweep( const wallet2::transfer_container &transfers, const std::unordered_map &subaddress_map, diff --git a/src/wallet/tx_builder.h b/src/wallet/tx_builder.h index abc0d5c42..98c74352c 100644 --- a/src/wallet/tx_builder.h +++ b/src/wallet/tx_builder.h @@ -56,20 +56,20 @@ carrot::select_inputs_func_t make_wallet2_single_transfer_input_selector( const bool allow_pre_carrot_inputs_in_normal_transfers, std::set &selected_transfer_indices_out); -carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_transfer_subtractable( +std::vector make_carrot_transaction_proposals_wallet2_transfer( const wallet2::transfer_container &transfers, const std::unordered_map &subaddress_map, - const std::vector &dsts, + std::vector dsts, const rct::xmr_amount fee_per_weight, const std::vector &extra, const uint32_t subaddr_account, const std::set &subaddr_indices, const rct::xmr_amount ignore_above, const rct::xmr_amount ignore_below, - const wallet2::unique_index_container& subtract_fee_from_outputs, + wallet2::unique_index_container subtract_fee_from_outputs, const std::uint64_t top_block_index, const cryptonote::account_keys &acc_keys); -carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_transfer_subtractable( +std::vector make_carrot_transaction_proposals_wallet2_transfer( wallet2 &w, const std::vector &dsts, const std::uint32_t priority, @@ -80,28 +80,6 @@ carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_tra const rct::xmr_amount ignore_below, const wallet2::unique_index_container &subtract_fee_from_outputs); -carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_transfer( - const wallet2::transfer_container &transfers, - const std::unordered_map &subaddress_map, - const std::vector &dsts, - const rct::xmr_amount fee_per_weight, - const std::vector &extra, - const uint32_t subaddr_account, - const std::set &subaddr_indices, - const rct::xmr_amount ignore_above, - const rct::xmr_amount ignore_below, - const std::uint64_t top_block_index, - const cryptonote::account_keys &acc_keys); -carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_transfer( - wallet2 &w, - const std::vector &dsts, - const std::uint32_t priority, - const std::vector &extra, - const std::uint32_t subaddr_account, - const std::set &subaddr_indices, - const rct::xmr_amount ignore_above, - const rct::xmr_amount ignore_below); - carrot::CarrotTransactionProposalV1 make_carrot_transaction_proposal_wallet2_sweep( const wallet2::transfer_container &transfers, const std::unordered_map &subaddress_map, diff --git a/tests/unit_tests/wallet_tx_builder.cpp b/tests/unit_tests/wallet_tx_builder.cpp index d8c40a93b..e946ae8ea 100644 --- a/tests/unit_tests/wallet_tx_builder.cpp +++ b/tests/unit_tests/wallet_tx_builder.cpp @@ -158,7 +158,7 @@ TEST(wallet_tx_builder, make_carrot_transaction_proposal_wallet2_transfer_1) const uint64_t top_block_index = std::max(transfers.front().m_block_height, transfers.back().m_block_height) + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE; - const carrot::CarrotTransactionProposalV1 tx_proposal = tools::wallet::make_carrot_transaction_proposal_wallet2_transfer( + const std::vector tx_proposals = tools::wallet::make_carrot_transaction_proposals_wallet2_transfer( transfers, /*subaddress_map=*/{}, dsts, @@ -168,9 +168,13 @@ TEST(wallet_tx_builder, make_carrot_transaction_proposal_wallet2_transfer_1) /*subaddr_indices=*/{}, /*ignore_above=*/MONEY_SUPPLY, /*ignore_below=*/0, + {}, top_block_index, alice); + ASSERT_EQ(1, tx_proposals.size()); + const carrot::CarrotTransactionProposalV1 tx_proposal = tx_proposals.at(0); + std::vector expected_key_images{ transfers.front().m_key_image, transfers.back().m_key_image};