mirror of
https://github.com/monero-project/monero.git
synced 2025-02-22 17:50:20 +02:00
wallet2 input selection
This commit is contained in:
parent
41286bf392
commit
309cf26341
@ -240,7 +240,7 @@ select_inputs_func_t make_single_transfer_input_selector(
|
||||
}
|
||||
|
||||
// 5. Calculate misc features
|
||||
const bool must_use_internal = (flags & InputSelectionFlags::ALLOW_EXTERNAL_INPUTS_IN_NORMAL_TRANSFERS) &&
|
||||
const bool must_use_internal = !(flags & InputSelectionFlags::ALLOW_EXTERNAL_INPUTS_IN_NORMAL_TRANSFERS) &&
|
||||
(num_normal_payment_proposals != 0);
|
||||
const bool prefer_external = num_normal_payment_proposals == 0;
|
||||
CHECK_AND_ASSERT_THROW_MES(!must_use_internal || !prefer_external,
|
||||
@ -291,20 +291,27 @@ select_inputs_func_t make_single_transfer_input_selector(
|
||||
for (size_t policy_idx = 0; policy_idx < policies.size() && selected_inputs_indices.empty(); ++policy_idx)
|
||||
try_dispatch_input_selection_policy(internal_sorted_inputs, policies[policy_idx]);
|
||||
|
||||
// 9. Try dispatching input selection policies in order for mixed (if allowed)
|
||||
// 9. Try dispatching input selection policies in order for external after internal (if not already tried)
|
||||
if (!must_use_internal || !prefer_external)
|
||||
{
|
||||
for (size_t policy_idx = 0; policy_idx < policies.size() && selected_inputs_indices.empty(); ++policy_idx)
|
||||
try_dispatch_input_selection_policy(external_sorted_inputs, policies[policy_idx]);
|
||||
}
|
||||
|
||||
// 10. Try dispatching input selection policies in order for mixed (if allowed)
|
||||
if (allow_mixed)
|
||||
{
|
||||
for (size_t policy_idx = 0; policy_idx < policies.size() && selected_inputs_indices.empty(); ++policy_idx)
|
||||
try_dispatch_input_selection_policy(sorted_inputs, policies[policy_idx]);
|
||||
}
|
||||
|
||||
// 10. Sanity check indices
|
||||
// 11. 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");
|
||||
|
||||
// 11. Do a greedy search for inputs whose amount doesn't pay for itself and drop them, logging debug messages
|
||||
// 12. 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 & InputSelectionFlags::ALLOW_DUST);
|
||||
while (should_search_for_dust && selected_inputs_indices.size() > CARROT_MIN_TX_INPUTS)
|
||||
@ -330,7 +337,7 @@ select_inputs_func_t make_single_transfer_input_selector(
|
||||
}
|
||||
}
|
||||
|
||||
// 12. Check the sum of input amounts is great enough
|
||||
// 13. 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;
|
||||
@ -339,7 +346,7 @@ select_inputs_func_t make_single_transfer_input_selector(
|
||||
CHECK_AND_ASSERT_THROW_MES(input_amount_sum >= required_money,
|
||||
"make_single_transfer_input_selector: bug: input selection returned successful without enough funds");
|
||||
|
||||
// 13. Collect selected inputs
|
||||
// 14. Collect selected inputs
|
||||
selected_inputs_out.clear();
|
||||
selected_inputs_out.reserve(num_selected);
|
||||
for (size_t selected_input_index : selected_inputs_indices)
|
||||
|
@ -37,6 +37,7 @@ set(wallet_sources
|
||||
node_rpc_proxy.cpp
|
||||
message_store.cpp
|
||||
message_transporter.cpp
|
||||
tx_builder.cpp
|
||||
)
|
||||
|
||||
monero_find_all_headers(wallet_private_headers "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
@ -50,6 +51,7 @@ target_link_libraries(wallet
|
||||
PUBLIC
|
||||
rpc_base
|
||||
multisig
|
||||
carrot_impl
|
||||
common
|
||||
cryptonote_core
|
||||
mnemonics
|
||||
|
162
src/wallet/tx_builder.cpp
Normal file
162
src/wallet/tx_builder.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
// Copyright (c) 2025, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// 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.
|
||||
|
||||
//paired header
|
||||
#include "tx_builder.h"
|
||||
|
||||
//local headers
|
||||
#include "carrot_impl/carrot_tx_builder_inputs.h"
|
||||
#include "cryptonote_basic/cryptonote_format_utils.h"
|
||||
|
||||
//third party headers
|
||||
|
||||
//standard headers
|
||||
|
||||
#undef MONERO_DEFAULT_LOG_CATEGORY
|
||||
#define MONERO_DEFAULT_LOG_CATEGORY "wallet"
|
||||
|
||||
namespace tools
|
||||
{
|
||||
namespace wallet
|
||||
{
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
static bool is_transfer_unlocked_for_next_fcmp_pp_block(const wallet2::transfer_details &td,
|
||||
const uint64_t top_block_index)
|
||||
{
|
||||
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 bool is_transfer_usable_for_input_selection(const wallet2::transfer_details &td,
|
||||
const std::uint32_t from_account,
|
||||
const std::set<std::uint32_t> from_subaddresses,
|
||||
const rct::xmr_amount ignore_above,
|
||||
const rct::xmr_amount ignore_below,
|
||||
const uint64_t top_block_index)
|
||||
{
|
||||
return !td.m_spent
|
||||
&& td.m_key_image_known
|
||||
&& !td.m_frozen
|
||||
&& is_transfer_unlocked_for_next_fcmp_pp_block(td, 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
|
||||
&& td.amount() <= ignore_above
|
||||
;
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
carrot::select_inputs_func_t make_wallet2_single_transfer_input_selector(
|
||||
const wallet2::transfer_container &transfers,
|
||||
const std::uint32_t from_account,
|
||||
const std::set<std::uint32_t> &from_subaddresses,
|
||||
const rct::xmr_amount ignore_above,
|
||||
const rct::xmr_amount ignore_below,
|
||||
const std::uint64_t top_block_index,
|
||||
const bool allow_carrot_external_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;
|
||||
std::vector<size_t> input_candidates_transfer_indices;
|
||||
input_candidates.reserve(transfers.size());
|
||||
input_candidates_transfer_indices.reserve(transfers.size());
|
||||
for (size_t i = 0; i < transfers.size(); ++i)
|
||||
{
|
||||
const wallet2::transfer_details& td = transfers.at(i);
|
||||
if (is_transfer_usable_for_input_selection(td,
|
||||
from_account,
|
||||
from_subaddresses,
|
||||
ignore_above,
|
||||
ignore_below,
|
||||
top_block_index))
|
||||
{
|
||||
input_candidates.push_back(carrot::CarrotPreSelectedInput{
|
||||
.core = carrot::CarrotSelectedInput{
|
||||
.amount = td.amount(),
|
||||
.key_image = td.m_key_image
|
||||
},
|
||||
.is_external = true, // @TODO: derive this info from field in transfer_details
|
||||
.block_index = td.m_block_height
|
||||
});
|
||||
input_candidates_transfer_indices.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Create wrapper around `make_single_transfer_input_selector`
|
||||
return [input_candidates = std::move(input_candidates),
|
||||
input_candidates_transfer_indices = std::move(input_candidates_transfer_indices),
|
||||
allow_carrot_external_inputs_in_normal_transfers,
|
||||
&selected_transfer_indices_out
|
||||
](
|
||||
const boost::multiprecision::int128_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<carrot::CarrotSelectedInput> &selected_inputs_outs
|
||||
){
|
||||
const std::vector<carrot::InputSelectionPolicy> policies{
|
||||
carrot::InputSelectionPolicy::TwoInputsPreferOldest
|
||||
}; // @TODO
|
||||
|
||||
// TODO: not all carrot is internal
|
||||
const std::uint32_t flags = allow_carrot_external_inputs_in_normal_transfers
|
||||
? carrot::InputSelectionFlags::ALLOW_EXTERNAL_INPUTS_IN_NORMAL_TRANSFERS : 0;
|
||||
|
||||
// Make inner input selection functor
|
||||
std::set<size_t> selected_input_indices;
|
||||
const carrot::select_inputs_func_t inner = carrot::make_single_transfer_input_selector(
|
||||
epee::to_span(input_candidates),
|
||||
epee::to_span(policies),
|
||||
flags,
|
||||
&selected_input_indices);
|
||||
|
||||
// Call input selection
|
||||
inner(nominal_output_sum,
|
||||
fee_by_input_count,
|
||||
num_normal_payment_proposals,
|
||||
num_selfsend_payment_proposals,
|
||||
selected_inputs_outs);
|
||||
|
||||
// Collect converted selected_input_indices -> selected_transfer_indices_out
|
||||
selected_transfer_indices_out.clear();
|
||||
for (const size_t input_index : selected_input_indices)
|
||||
selected_transfer_indices_out.insert(input_candidates_transfer_indices.at(input_index));
|
||||
};
|
||||
}
|
||||
//-------------------------------------------------------------------------------------------------------------------
|
||||
} //namespace wallet
|
||||
} //namespace tools
|
55
src/wallet/tx_builder.h
Normal file
55
src/wallet/tx_builder.h
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2025, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// 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.
|
||||
|
||||
#pragma once
|
||||
|
||||
//local headers
|
||||
#include "carrot_impl/carrot_tx_builder_inputs.h"
|
||||
#include "wallet2.h"
|
||||
|
||||
//third party headers
|
||||
|
||||
//standard headers
|
||||
|
||||
//forward declarations
|
||||
|
||||
namespace tools
|
||||
{
|
||||
namespace wallet
|
||||
{
|
||||
carrot::select_inputs_func_t make_wallet2_single_transfer_input_selector(
|
||||
const wallet2::transfer_container &transfers,
|
||||
const std::uint32_t from_account,
|
||||
const std::set<std::uint32_t> &from_subaddresses,
|
||||
const rct::xmr_amount ignore_above,
|
||||
const rct::xmr_amount ignore_below,
|
||||
const std::uint64_t top_block_index,
|
||||
const bool allow_carrot_external_inputs_in_normal_transfers,
|
||||
std::set<size_t> &selected_transfer_indices_out);
|
||||
} //namespace wallet
|
||||
} //namespace tools
|
@ -102,6 +102,7 @@ set(unit_tests_sources
|
||||
vercmp.cpp
|
||||
ringdb.cpp
|
||||
wallet_storage.cpp
|
||||
wallet_tx_builder.cpp
|
||||
wipeable_string.cpp
|
||||
is_hdd.cpp
|
||||
aligned.cpp
|
||||
|
136
tests/unit_tests/wallet_tx_builder.cpp
Normal file
136
tests/unit_tests/wallet_tx_builder.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
// Copyright (c) 2025, The Monero Project
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are
|
||||
// permitted provided that the following conditions are met:
|
||||
//
|
||||
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
// of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without specific
|
||||
// prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
// 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.
|
||||
|
||||
#include "unit_tests_utils.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "carrot_core/config.h"
|
||||
#include "common/container_helpers.h"
|
||||
#include "ringct/rctOps.h"
|
||||
#include "wallet/tx_builder.h"
|
||||
|
||||
static tools::wallet2::transfer_details gen_transfer_details()
|
||||
{
|
||||
return tools::wallet2::transfer_details{
|
||||
.m_block_height = crypto::rand_idx<uint64_t>(CRYPTONOTE_MAX_BLOCK_NUMBER),
|
||||
.m_tx = {},
|
||||
.m_txid = crypto::rand<crypto::hash>(),
|
||||
.m_internal_output_index = crypto::rand_idx<uint64_t>(carrot::CARROT_MAX_TX_OUTPUTS),
|
||||
.m_global_output_index = crypto::rand_idx<uint64_t>(CRYPTONOTE_MAX_BLOCK_NUMBER * 1000ull),
|
||||
.m_spent = false,
|
||||
.m_frozen = false,
|
||||
.m_spent_height = 0,
|
||||
.m_key_image = rct::rct2pk(rct::pkGen()),
|
||||
.m_mask = rct::skGen(),
|
||||
.m_amount = crypto::rand_range<rct::xmr_amount>(0, COIN), // [0, 1] XMR i.e. [0, 1e12] pXMR
|
||||
.m_rct = true,
|
||||
.m_key_image_known = true,
|
||||
.m_key_image_request = false,
|
||||
.m_pk_index = 1,
|
||||
.m_subaddr_index = {},
|
||||
.m_key_image_partial = false,
|
||||
.m_multisig_k = {},
|
||||
.m_multisig_info = {},
|
||||
.m_uses = {},
|
||||
};
|
||||
}
|
||||
|
||||
static bool compare_transfer_to_selected_input(const tools::wallet2::transfer_details &td,
|
||||
const carrot::CarrotSelectedInput &input)
|
||||
{
|
||||
return td.m_amount == input.amount && td.m_key_image == input.key_image;
|
||||
}
|
||||
|
||||
TEST(wallet_tx_builder, input_selection_basic)
|
||||
{
|
||||
std::map<std::size_t, rct::xmr_amount> fee_by_input_count;
|
||||
for (size_t i = carrot::CARROT_MIN_TX_INPUTS; i <= carrot::CARROT_MAX_TX_INPUTS; ++i)
|
||||
fee_by_input_count[i] = 30680000 * i - i*i;
|
||||
|
||||
const boost::multiprecision::int128_t nominal_output_sum = 4444444444444; // 4.444... XMR
|
||||
|
||||
// add 10 random transfers
|
||||
tools::wallet2::transfer_container transfers;
|
||||
for (size_t i = 0; i < 10; ++i)
|
||||
{
|
||||
tools::wallet2::transfer_details &td = tools::add_element(transfers);
|
||||
td = gen_transfer_details();
|
||||
td.m_block_height = transfers.size(); // small ascending block heights
|
||||
}
|
||||
|
||||
// modify one so that it funds the transfer all by itself
|
||||
const size_t rand_idx = crypto::rand_idx(transfers.size());
|
||||
transfers[rand_idx].m_amount = boost::numeric_cast<rct::xmr_amount>(nominal_output_sum +
|
||||
fee_by_input_count.crbegin()->second +
|
||||
crypto::rand_range<rct::xmr_amount>(0, COIN));
|
||||
|
||||
// set such that all transfers are unlocked
|
||||
const std::uint64_t top_block_index = transfers.size() + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE;
|
||||
|
||||
// make input selector
|
||||
std::set<size_t> selected_transfer_indices;
|
||||
const carrot::select_inputs_func_t input_selector = tools::wallet::make_wallet2_single_transfer_input_selector(
|
||||
transfers,
|
||||
/*from_account=*/0,
|
||||
/*from_subaddresses=*/{},
|
||||
/*ignore_above=*/std::numeric_limits<rct::xmr_amount>::max(),
|
||||
/*ignore_below=*/0,
|
||||
top_block_index,
|
||||
/*allow_carrot_external_inputs_in_normal_transfers=*/true,
|
||||
selected_transfer_indices
|
||||
);
|
||||
|
||||
// select inputs
|
||||
std::vector<carrot::CarrotSelectedInput> selected_inputs;
|
||||
input_selector(nominal_output_sum,
|
||||
fee_by_input_count,
|
||||
1, // number of normal payment proposals
|
||||
1, // number of self-send payment proposals
|
||||
selected_inputs);
|
||||
|
||||
ASSERT_EQ(2, selected_inputs.size()); // assert two inputs selected
|
||||
ASSERT_EQ(2, selected_transfer_indices.size());
|
||||
ASSERT_LT(*selected_transfer_indices.crbegin(), transfers.size());
|
||||
ASSERT_NE(selected_inputs.front().key_image, selected_inputs.back().key_image);
|
||||
|
||||
// Assert content of selected inputs matches the content in `transfers`
|
||||
std::set<size_t> matched_transfer_indices;
|
||||
for (const carrot::CarrotSelectedInput &selected_input : selected_inputs)
|
||||
{
|
||||
for (const size_t selected_transfer_index : selected_transfer_indices)
|
||||
{
|
||||
if (compare_transfer_to_selected_input(transfers.at(selected_transfer_index), selected_input))
|
||||
{
|
||||
const auto insert_res = matched_transfer_indices.insert(selected_transfer_index);
|
||||
if (insert_res.second)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(selected_transfer_indices.size(), matched_transfer_indices.size());
|
||||
}
|
Loading…
Reference in New Issue
Block a user