1430 lines
60 KiB
C++
1430 lines
60 KiB
C++
// Copyright (c) 2024, 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 "gtest/gtest.h"
|
|
|
|
#include <boost/multiprecision/cpp_int.hpp>
|
|
|
|
#include "carrot_core/output_set_finalization.h"
|
|
#include "carrot_core/payment_proposal.h"
|
|
#include "carrot_impl/carrot_tx_builder_utils.h"
|
|
#include "carrot_impl/carrot_tx_format_utils.h"
|
|
#include "carrot_impl/input_selection.h"
|
|
#include "carrot_mock_helpers.h"
|
|
#include "common/container_helpers.h"
|
|
#include "crypto/generators.h"
|
|
#include "cryptonote_basic/account.h"
|
|
#include "cryptonote_basic/subaddress_index.h"
|
|
#include "cryptonote_basic/cryptonote_format_utils.h"
|
|
#include "cryptonote_core/blockchain.h"
|
|
#include "ringct/bulletproofs_plus.h"
|
|
#include "ringct/rctOps.h"
|
|
#include "ringct/rctSigs.h"
|
|
|
|
using namespace carrot;
|
|
|
|
namespace
|
|
{
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
static void unittest_scan_enote_set_multi_account(const std::vector<CarrotEnoteV1> &enotes,
|
|
const encrypted_payment_id_t encrypted_payment_id,
|
|
const epee::span<const mock::mock_carrot_and_legacy_keys * const> accounts,
|
|
std::vector<std::vector<mock::mock_scan_result_t>> &res)
|
|
{
|
|
res.clear();
|
|
res.reserve(accounts.size());
|
|
|
|
for (const auto *account : accounts)
|
|
mock_scan_enote_set(enotes, encrypted_payment_id, *account, tools::add_element(res));
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
struct unittest_transaction_preproposal
|
|
{
|
|
using per_payment_proposal = std::pair<CarrotPaymentProposalV1, /*is subtractble?*/bool>;
|
|
using per_ss_payment_proposal = std::pair<CarrotPaymentProposalVerifiableSelfSendV1, /*is subtractble?*/bool>;
|
|
using per_account = std::pair<mock::mock_carrot_and_legacy_keys, std::vector<per_payment_proposal>>;
|
|
using per_input = std::pair<crypto::key_image, rct::xmr_amount>;
|
|
|
|
std::vector<per_account> per_account_payments;
|
|
std::vector<per_ss_payment_proposal> explicit_selfsend_proposals;
|
|
size_t self_sender_index{0};
|
|
rct::xmr_amount fee_per_weight;
|
|
std::vector<std::uint8_t> extra_extra;
|
|
|
|
void get_flattened_payment_proposals(std::vector<CarrotPaymentProposalV1> &normal_payment_proposals_out,
|
|
std::vector<CarrotPaymentProposalVerifiableSelfSendV1> &selfsend_payment_proposals_out,
|
|
std::set<size_t> &subtractable_normal_payment_proposals,
|
|
std::set<size_t> &subtractable_selfsend_payment_proposals) const
|
|
{
|
|
size_t norm_idx = 0;
|
|
for (const per_account &pa : per_account_payments)
|
|
{
|
|
for (const per_payment_proposal &ppp : pa.second)
|
|
{
|
|
normal_payment_proposals_out.push_back(ppp.first);
|
|
if (ppp.second)
|
|
subtractable_normal_payment_proposals.insert(norm_idx);
|
|
|
|
norm_idx++;
|
|
}
|
|
}
|
|
|
|
for (size_t ss_idx = 0; ss_idx < explicit_selfsend_proposals.size(); ++ss_idx)
|
|
{
|
|
const per_ss_payment_proposal &pspp = explicit_selfsend_proposals.at(ss_idx);
|
|
selfsend_payment_proposals_out.push_back(pspp.first);
|
|
if (pspp.second)
|
|
subtractable_selfsend_payment_proposals.insert(ss_idx);
|
|
}
|
|
}
|
|
};
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
select_inputs_func_t make_fake_input_selection_callback(size_t num_ins = 0)
|
|
{
|
|
return [num_ins](const boost::multiprecision::int128_t &nominal_output_sum,
|
|
const std::map<std::size_t, rct::xmr_amount> &fee_per_input_count,
|
|
size_t,
|
|
size_t,
|
|
std::vector<CarrotSelectedInput> &selected_inputs)
|
|
{
|
|
const size_t nins = num_ins ? num_ins : 1;
|
|
selected_inputs.clear();
|
|
selected_inputs.reserve(nins);
|
|
|
|
const rct::xmr_amount fee = fee_per_input_count.at(nins);
|
|
rct::xmr_amount in_amount_sum_64 = boost::numeric_cast<rct::xmr_amount>(nominal_output_sum + fee);
|
|
|
|
for (size_t i = 0; i < nins - 1; ++i)
|
|
{
|
|
const rct::xmr_amount current_in_amount = in_amount_sum_64 ? crypto::rand_idx(in_amount_sum_64) : 0;
|
|
const crypto::key_image current_key_image = rct::rct2ki(rct::pkGen());
|
|
selected_inputs.push_back({current_in_amount, current_key_image});
|
|
in_amount_sum_64 -= current_in_amount;
|
|
}
|
|
|
|
selected_inputs.push_back({in_amount_sum_64, rct::rct2ki(rct::pkGen())});
|
|
};
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
static void subtest_multi_account_transfer_over_transaction(const unittest_transaction_preproposal &tx_preproposal)
|
|
{
|
|
// get payment proposals
|
|
std::vector<CarrotPaymentProposalV1> normal_payment_proposals;
|
|
std::vector<CarrotPaymentProposalVerifiableSelfSendV1> selfsend_payment_proposals;
|
|
std::set<size_t> subtractable_normal_payment_proposals;
|
|
std::set<size_t> subtractable_selfsend_payment_proposals;
|
|
tx_preproposal.get_flattened_payment_proposals(normal_payment_proposals,
|
|
selfsend_payment_proposals,
|
|
subtractable_normal_payment_proposals,
|
|
subtractable_selfsend_payment_proposals);
|
|
|
|
// get self-sender account
|
|
const mock::mock_carrot_and_legacy_keys &ss_keys =
|
|
tx_preproposal.per_account_payments.at(tx_preproposal.self_sender_index).first;
|
|
|
|
// make transaction proposal
|
|
CarrotTransactionProposalV1 tx_proposal;
|
|
make_carrot_transaction_proposal_v1_transfer(normal_payment_proposals,
|
|
selfsend_payment_proposals,
|
|
tx_preproposal.fee_per_weight,
|
|
tx_preproposal.extra_extra,
|
|
make_fake_input_selection_callback(),
|
|
ss_keys.carrot_account_spend_pubkey,
|
|
{},
|
|
{},
|
|
tx_proposal);
|
|
|
|
// make unsigned transaction
|
|
cryptonote::transaction tx;
|
|
make_pruned_transaction_from_proposal_v1(tx_proposal,
|
|
&ss_keys.s_view_balance_dev,
|
|
&ss_keys.k_view_incoming_dev,
|
|
tx);
|
|
|
|
// calculate acceptable fee margin between proposed amount and actual amount for subtractable outputs
|
|
const size_t num_subtractable = subtractable_normal_payment_proposals.size() +
|
|
subtractable_selfsend_payment_proposals.size();
|
|
const rct::xmr_amount acceptable_fee_margin = num_subtractable
|
|
? (tx.rct_signatures.txnFee / num_subtractable) + 1
|
|
: 0;
|
|
|
|
// load carrot stuff from tx
|
|
std::vector<CarrotEnoteV1> parsed_enotes;
|
|
std::vector<crypto::key_image> parsed_key_images;
|
|
rct::xmr_amount parsed_fee;
|
|
std::optional<encrypted_payment_id_t> parsed_encrypted_payment_id;
|
|
ASSERT_TRUE(try_load_carrot_from_transaction_v1(tx,
|
|
parsed_enotes,
|
|
parsed_key_images,
|
|
parsed_fee,
|
|
parsed_encrypted_payment_id));
|
|
ASSERT_TRUE(parsed_encrypted_payment_id);
|
|
|
|
// collect modified selfsend payment proposal cores
|
|
std::vector<CarrotPaymentProposalSelfSendV1> modified_selfsend_payment_proposals;
|
|
for (const auto &p : tx_proposal.selfsend_payment_proposals)
|
|
modified_selfsend_payment_proposals.push_back(p.proposal);
|
|
|
|
// sanity check that the enotes and pid_enc loaded from the transaction are equal to the enotes
|
|
// and pic_enc returned from get_output_enote_proposals() when called with the modified payment
|
|
// proposals. we do this so that the modified payment proposals from make_unsigned_transaction()
|
|
// can be passed to a hardware device for deterministic verification of the signable tx hash
|
|
std::vector<RCTOutputEnoteProposal> rederived_output_enote_proposals;
|
|
encrypted_payment_id_t rederived_encrypted_payment_id;
|
|
get_output_enote_proposals(tx_proposal.normal_payment_proposals,
|
|
modified_selfsend_payment_proposals,
|
|
*parsed_encrypted_payment_id,
|
|
&ss_keys.s_view_balance_dev,
|
|
&ss_keys.k_view_incoming_dev,
|
|
parsed_key_images.at(0),
|
|
rederived_output_enote_proposals,
|
|
rederived_encrypted_payment_id);
|
|
EXPECT_EQ(*parsed_encrypted_payment_id, rederived_encrypted_payment_id);
|
|
ASSERT_EQ(parsed_enotes.size(), rederived_output_enote_proposals.size());
|
|
for (size_t enote_idx = 0; enote_idx < parsed_enotes.size(); ++enote_idx)
|
|
{
|
|
EXPECT_EQ(parsed_enotes.at(enote_idx), rederived_output_enote_proposals.at(enote_idx).enote);
|
|
}
|
|
|
|
// collect accounts
|
|
std::vector<const mock::mock_carrot_and_legacy_keys *> accounts;
|
|
for (const auto &pa : tx_preproposal.per_account_payments)
|
|
accounts.push_back(&pa.first);
|
|
|
|
// do scanning of all accounts on every enotes
|
|
std::vector<std::vector<mock::mock_scan_result_t>> scan_results;
|
|
unittest_scan_enote_set_multi_account(parsed_enotes,
|
|
*parsed_encrypted_payment_id,
|
|
epee::to_span(accounts),
|
|
scan_results);
|
|
|
|
// check that the scan results for each *normal* account match the corresponding payment
|
|
// proposals for each account. also check that the accounts can each open their corresponding
|
|
// onetime outut pubkeys
|
|
ASSERT_EQ(scan_results.size(), accounts.size());
|
|
// for each normal account...
|
|
for (size_t account_idx = 0; account_idx < accounts.size(); ++account_idx)
|
|
{
|
|
// skip self-sender account
|
|
if (account_idx == tx_preproposal.self_sender_index)
|
|
continue;
|
|
|
|
const std::vector<mock::mock_scan_result_t> &account_scan_results = scan_results.at(account_idx);
|
|
const auto &account_payment_proposals = tx_preproposal.per_account_payments.at(account_idx).second;
|
|
ASSERT_EQ(account_payment_proposals.size(), account_scan_results.size());
|
|
std::set<size_t> matched_payment_proposals;
|
|
|
|
// for each scan result assigned to this account...
|
|
for (const mock::mock_scan_result_t &single_scan_res : account_scan_results)
|
|
{
|
|
// for each normal payment proposal to this account...
|
|
for (size_t norm_prop_idx = 0; norm_prop_idx < account_payment_proposals.size(); ++norm_prop_idx)
|
|
{
|
|
// calculate acceptable loss from fee subtraction
|
|
const CarrotPaymentProposalV1 &account_payment_proposal = account_payment_proposals.at(norm_prop_idx).first;
|
|
const bool is_subtractable = subtractable_normal_payment_proposals.count(norm_prop_idx);
|
|
const rct::xmr_amount acceptable_fee_margin_for_proposal = is_subtractable ? acceptable_fee_margin : 0;
|
|
|
|
// if the scan result matches the payment proposal...
|
|
if (compare_scan_result(single_scan_res, account_payment_proposal, acceptable_fee_margin_for_proposal))
|
|
{
|
|
// try opening Ko
|
|
const CarrotEnoteV1 &enote = parsed_enotes.at(single_scan_res.output_index);
|
|
EXPECT_TRUE(accounts.at(account_idx)->can_open_fcmp_onetime_address(single_scan_res.address_spend_pubkey,
|
|
single_scan_res.sender_extension_g,
|
|
single_scan_res.sender_extension_t,
|
|
enote.onetime_address));
|
|
|
|
// if this payment proposal isn't already marked as scanned, mark as scanned
|
|
if (!matched_payment_proposals.count(norm_prop_idx))
|
|
{
|
|
matched_payment_proposals.insert(norm_prop_idx);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// check that the number of matched payment proposals is equal to the original number of them
|
|
// doing it this way checks that the same payment proposal isn't marked twice and another left out
|
|
EXPECT_EQ(account_payment_proposals.size(), matched_payment_proposals.size());
|
|
}
|
|
|
|
// check that the scan results for the selfsend account match the corresponding payment
|
|
// proposals. also check that the accounts can each open their corresponding onetime outut pubkeys
|
|
const std::vector<mock::mock_scan_result_t> &account_scan_results = scan_results.at(tx_preproposal.self_sender_index);
|
|
ASSERT_EQ(selfsend_payment_proposals.size() + 1, account_scan_results.size());
|
|
std::set<size_t> matched_payment_proposals;
|
|
const mock::mock_scan_result_t* implicit_change_scan_res = nullptr;
|
|
// for each scan result assigned to the self-sender account...
|
|
for (const mock::mock_scan_result_t &single_scan_res : account_scan_results)
|
|
{
|
|
bool matched_payment = false;
|
|
// for each self-send payment proposal...
|
|
for (size_t ss_prop_idx = 0; ss_prop_idx < selfsend_payment_proposals.size(); ++ss_prop_idx)
|
|
{
|
|
// calculate acceptable loss from fee subtraction
|
|
const CarrotPaymentProposalSelfSendV1 &account_payment_proposal = selfsend_payment_proposals.at(ss_prop_idx).proposal;
|
|
const bool is_subtractable = subtractable_selfsend_payment_proposals.count(ss_prop_idx);
|
|
const rct::xmr_amount acceptable_fee_margin_for_proposal = is_subtractable ? acceptable_fee_margin : 0;
|
|
|
|
// if the scan result matches the payment proposal...
|
|
if (compare_scan_result(single_scan_res, account_payment_proposal, acceptable_fee_margin_for_proposal))
|
|
{
|
|
// try opening Ko
|
|
const CarrotEnoteV1 &enote = parsed_enotes.at(single_scan_res.output_index);
|
|
EXPECT_TRUE(ss_keys.can_open_fcmp_onetime_address(single_scan_res.address_spend_pubkey,
|
|
single_scan_res.sender_extension_g,
|
|
single_scan_res.sender_extension_t,
|
|
enote.onetime_address));
|
|
|
|
// if this payment proposal isn't already marked as scanned, mark as scanned
|
|
if (!matched_payment_proposals.count(ss_prop_idx))
|
|
{
|
|
matched_payment = true;
|
|
matched_payment_proposals.insert(ss_prop_idx);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if this scan result has no matching payment...
|
|
if (!matched_payment)
|
|
{
|
|
EXPECT_EQ(nullptr, implicit_change_scan_res); // only one non-matched scan result is allowed
|
|
implicit_change_scan_res = &single_scan_res; // save the implicit change scan result for later
|
|
}
|
|
}
|
|
EXPECT_EQ(selfsend_payment_proposals.size(), matched_payment_proposals.size());
|
|
EXPECT_NE(nullptr, implicit_change_scan_res);
|
|
// @TODO: assert properties of `implicit_change_scan_res`
|
|
}
|
|
} //namespace
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_1)
|
|
{
|
|
// two accounts, both carrot
|
|
// 1/2 tx
|
|
// 1 normal payment to main address
|
|
// 0 explicit selfsend payments
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(2);
|
|
auto &acc0 = tx_proposal.per_account_payments[0].first;
|
|
auto &acc1 = tx_proposal.per_account_payments[1].first;
|
|
acc0.generate();
|
|
acc1.generate();
|
|
|
|
// 1 normal payment
|
|
CarrotPaymentProposalV1 &normal_payment_proposal = tools::add_element( tx_proposal.per_account_payments[0].second).first;
|
|
normal_payment_proposal = CarrotPaymentProposalV1{
|
|
.destination = acc0.cryptonote_address(),
|
|
.amount = crypto::rand_idx((rct::xmr_amount) 1ull << 63),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 1;
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 20250510;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_2)
|
|
{
|
|
// four accounts, all carrot
|
|
// 1/4 tx
|
|
// 1 normal payment to main address, integrated address, and subaddress each
|
|
// 0 explicit selfsend payments
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate();
|
|
acc1.first.generate();
|
|
acc2.first.generate();
|
|
acc3.first.generate();
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 1 subaddress payment
|
|
tools::add_element(acc0.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 main address payment
|
|
tools::add_element(acc1.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 integrated address payment
|
|
tools::add_element(acc3.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_3)
|
|
{
|
|
// four accounts, all carrot
|
|
// 1/6 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 0 explicit selfsend payments
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate();
|
|
acc1.first.generate();
|
|
acc2.first.generate();
|
|
acc3.first.generate();
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment
|
|
tools::add_element(acc0.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 2 main address payment
|
|
tools::add_element(acc1.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 1 integrated address payment
|
|
tools::add_element(acc3.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_4)
|
|
{
|
|
// four accounts, all carrot
|
|
// 1/8 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 2 explicit selfsend payments: 1 main address destination, 1 subaddress destination
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate();
|
|
acc1.first.generate();
|
|
acc2.first.generate();
|
|
acc3.first.generate();
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment
|
|
tools::add_element(acc0.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 2 main address payment
|
|
tools::add_element(acc1.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 1 integrated address payment
|
|
tools::add_element(acc3.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 main address selfsend
|
|
tools::add_element(tx_proposal.explicit_selfsend_proposals).first.proposal = CarrotPaymentProposalSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.carrot_account_spend_pubkey,
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::PAYMENT,
|
|
.internal_message = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 subaddress selfsend
|
|
tools::add_element(tx_proposal.explicit_selfsend_proposals).first = CarrotPaymentProposalVerifiableSelfSendV1{
|
|
.proposal = CarrotPaymentProposalSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.subaddress({{4, 19}}).address_spend_pubkey,
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::CHANGE
|
|
},
|
|
.subaddr_index = {{4, 19}}
|
|
};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_5)
|
|
{
|
|
// two accounts, both legacy
|
|
// 1/2 tx
|
|
// 1 normal payment to main address
|
|
// 0 explicit selfsend payments
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(2);
|
|
auto &acc0 = tx_proposal.per_account_payments[0].first;
|
|
auto &acc1 = tx_proposal.per_account_payments[1].first;
|
|
acc0.generate(AddressDeriveType::PreCarrot);
|
|
acc1.generate(AddressDeriveType::PreCarrot);
|
|
|
|
// 1 normal payment
|
|
CarrotPaymentProposalV1 &normal_payment_proposal = tools::add_element( tx_proposal.per_account_payments[0].second).first;
|
|
normal_payment_proposal = CarrotPaymentProposalV1{
|
|
.destination = acc0.cryptonote_address(),
|
|
.amount = crypto::rand_idx((rct::xmr_amount) 1ull << 63),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 1;
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 20250510;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_6)
|
|
{
|
|
// four accounts, all legacy
|
|
// 1/4 tx
|
|
// 1 normal payment to main address, integrated address, and subaddress each
|
|
// 0 explicit selfsend payments
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate();
|
|
acc1.first.generate();
|
|
acc2.first.generate();
|
|
acc3.first.generate();
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 1 subaddress payment
|
|
tools::add_element(acc0.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 main address payment
|
|
tools::add_element(acc1.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 integrated address payment
|
|
tools::add_element(acc3.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_7)
|
|
{
|
|
// four accounts, all legacy
|
|
// 1/6 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 0 explicit selfsend payments
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate(AddressDeriveType::PreCarrot);
|
|
acc1.first.generate(AddressDeriveType::PreCarrot);
|
|
acc2.first.generate(AddressDeriveType::PreCarrot);
|
|
acc3.first.generate(AddressDeriveType::PreCarrot);
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment
|
|
tools::add_element(acc0.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 2 main address payment
|
|
tools::add_element(acc1.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 1 integrated address payment
|
|
tools::add_element(acc3.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_8)
|
|
{
|
|
// four accounts, all legacy
|
|
// 1/8 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 2 explicit selfsend payments: 1 main address destination, 1 subaddress destination
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate(AddressDeriveType::PreCarrot);
|
|
acc1.first.generate(AddressDeriveType::PreCarrot);
|
|
acc2.first.generate(AddressDeriveType::PreCarrot);
|
|
acc3.first.generate(AddressDeriveType::PreCarrot);
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment
|
|
tools::add_element(acc0.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 2 main address payment
|
|
tools::add_element(acc1.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 1 integrated address payment
|
|
tools::add_element(acc3.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 main address selfsend
|
|
tools::add_element(tx_proposal.explicit_selfsend_proposals).first.proposal = CarrotPaymentProposalSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.carrot_account_spend_pubkey,
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::PAYMENT,
|
|
// no internal messages for legacy self-sends
|
|
};
|
|
|
|
// 1 subaddress selfsend
|
|
tools::add_element(tx_proposal.explicit_selfsend_proposals).first = CarrotPaymentProposalVerifiableSelfSendV1{
|
|
.proposal = CarrotPaymentProposalSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.subaddress({{4, 19}}).address_spend_pubkey,
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::CHANGE
|
|
},
|
|
.subaddr_index = {{4, 19}}
|
|
};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_9)
|
|
{
|
|
// two accounts, both carrot
|
|
// 1/2 tx
|
|
// 1 normal payment to main address
|
|
// 0 explicit selfsend payments
|
|
// subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(2);
|
|
auto &acc0 = tx_proposal.per_account_payments[0].first;
|
|
auto &acc1 = tx_proposal.per_account_payments[1].first;
|
|
acc0.generate();
|
|
acc1.generate();
|
|
|
|
// 1 normal payment (subtractable)
|
|
CarrotPaymentProposalV1 &normal_payment_proposal = tools::add_element( tx_proposal.per_account_payments[0].second).first;
|
|
normal_payment_proposal = CarrotPaymentProposalV1{
|
|
.destination = acc0.cryptonote_address(),
|
|
.amount = crypto::rand_idx((rct::xmr_amount) 1ull << 63),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
tx_proposal.per_account_payments[0].second.back().second = true;
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 1;
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 20250510;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_10)
|
|
{
|
|
// four accounts, all carrot
|
|
// 1/4 tx
|
|
// 1 normal payment to main address, integrated address, and subaddress each
|
|
// 0 explicit selfsend payments
|
|
// subaddress and integrated address are subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate();
|
|
acc1.first.generate();
|
|
acc2.first.generate();
|
|
acc3.first.generate();
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 1 subaddress payment (subtractable)
|
|
tools::add_element(acc0.second) = {CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
|
|
// 1 main address payment
|
|
tools::add_element(acc1.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 integrated address payment
|
|
tools::add_element(acc3.second) = {CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_11)
|
|
{
|
|
// four accounts, all carrot
|
|
// 1/6 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 0 explicit selfsend payments
|
|
// 1 main and 1 subaddress is subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate();
|
|
acc1.first.generate();
|
|
acc2.first.generate();
|
|
acc3.first.generate();
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment
|
|
tools::add_element(acc0.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
acc0.second.back().second = true; //set copy as subtractable
|
|
|
|
// 2 main address payment
|
|
tools::add_element(acc1.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
acc1.second.back().second = true; //set copy as subtractable
|
|
|
|
// 1 integrated address payment
|
|
tools::add_element(acc3.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_12)
|
|
{
|
|
// four accounts, all carrot
|
|
// 1/8 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 2 explicit selfsend payments: 1 main address destination, 1 subaddress destination
|
|
// 1 normal main address, 1 integrated, and 1 self-send subaddress is subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate();
|
|
acc1.first.generate();
|
|
acc2.first.generate();
|
|
acc3.first.generate();
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment (1 subtractable)
|
|
tools::add_element(acc0.second) = {CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
acc0.second.back().second = false; //set not subtractable, first already is
|
|
|
|
// 2 main address payment
|
|
tools::add_element(acc1.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 1 integrated address payment (subtractable)
|
|
tools::add_element(acc3.second) = {CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
|
|
// 1 main address selfsend
|
|
tools::add_element(tx_proposal.explicit_selfsend_proposals).first.proposal = CarrotPaymentProposalSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.carrot_account_spend_pubkey,
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::PAYMENT,
|
|
.internal_message = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 subaddress selfsend (subtractable)
|
|
tools::add_element(tx_proposal.explicit_selfsend_proposals) = {CarrotPaymentProposalVerifiableSelfSendV1{
|
|
.proposal = CarrotPaymentProposalSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.subaddress({{4, 19}}).address_spend_pubkey,
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::CHANGE
|
|
},
|
|
.subaddr_index = {{4, 19}}
|
|
}, true};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_13)
|
|
{
|
|
// two accounts, both legacy
|
|
// 1/2 tx
|
|
// 1 normal payment to main address
|
|
// 0 explicit selfsend payments
|
|
// subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(2);
|
|
auto &acc0 = tx_proposal.per_account_payments[0].first;
|
|
auto &acc1 = tx_proposal.per_account_payments[1].first;
|
|
acc0.generate(AddressDeriveType::PreCarrot);
|
|
acc1.generate(AddressDeriveType::PreCarrot);
|
|
|
|
// 1 normal payment (subtractable)
|
|
CarrotPaymentProposalV1 &normal_payment_proposal = tools::add_element( tx_proposal.per_account_payments[0].second).first;
|
|
normal_payment_proposal = CarrotPaymentProposalV1{
|
|
.destination = acc0.cryptonote_address(),
|
|
.amount = crypto::rand_idx((rct::xmr_amount) 1ull << 63),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
tx_proposal.per_account_payments[0].second.back().second = true;
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 1;
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 20250510;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_14)
|
|
{
|
|
// four accounts, all legacy
|
|
// 1/4 tx
|
|
// 1 normal payment to main address, integrated address, and subaddress each
|
|
// 0 explicit selfsend payments
|
|
// 1 integrated and 1 subaddress subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate(AddressDeriveType::PreCarrot);
|
|
acc1.first.generate(AddressDeriveType::PreCarrot);
|
|
acc2.first.generate(AddressDeriveType::PreCarrot);
|
|
acc3.first.generate(AddressDeriveType::PreCarrot);
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 1 subaddress payment (subtractable)
|
|
tools::add_element(acc0.second) = {CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
|
|
// 1 main address payment
|
|
tools::add_element(acc1.second).first = CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
};
|
|
|
|
// 1 integrated address payment (subtractable)
|
|
tools::add_element(acc3.second) = {CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_15)
|
|
{
|
|
// four accounts, all legacy
|
|
// 1/6 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 0 explicit selfsend payments
|
|
// all subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate(AddressDeriveType::PreCarrot);
|
|
acc1.first.generate(AddressDeriveType::PreCarrot);
|
|
acc2.first.generate(AddressDeriveType::PreCarrot);
|
|
acc3.first.generate(AddressDeriveType::PreCarrot);
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment (subtractable)
|
|
tools::add_element(acc0.second) = {CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 2 main address payment (subtractable)
|
|
tools::add_element(acc1.second) = {CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 1 integrated address payment (subtractable)
|
|
tools::add_element(acc3.second) = {CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, multi_account_transfer_over_transaction_16)
|
|
{
|
|
// four accounts, all legacy
|
|
// 1/8 tx
|
|
// 2 normal payment to main address, 1 integrated address, and 2 subaddress, each copied except integrated
|
|
// 2 explicit selfsend payments: 1 main address destination, 1 subaddress destination
|
|
// all subtractable
|
|
|
|
unittest_transaction_preproposal tx_proposal;
|
|
tx_proposal.per_account_payments = std::vector<unittest_transaction_preproposal::per_account>(4);
|
|
auto &acc0 = tx_proposal.per_account_payments[0];
|
|
auto &acc1 = tx_proposal.per_account_payments[1];
|
|
auto &acc2 = tx_proposal.per_account_payments[2];
|
|
auto &acc3 = tx_proposal.per_account_payments[3];
|
|
acc0.first.generate(AddressDeriveType::PreCarrot);
|
|
acc1.first.generate(AddressDeriveType::PreCarrot);
|
|
acc2.first.generate(AddressDeriveType::PreCarrot);
|
|
acc3.first.generate(AddressDeriveType::PreCarrot);
|
|
|
|
// specify self-sender
|
|
tx_proposal.self_sender_index = 2;
|
|
|
|
// 2 subaddress payment (subtractable)
|
|
tools::add_element(acc0.second) = {CarrotPaymentProposalV1{
|
|
.destination = acc0.first.subaddress({{2, 3}}),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
acc0.second.push_back(acc0.second.front());
|
|
acc0.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 2 main address payment (subtractable)
|
|
tools::add_element(acc1.second) = {CarrotPaymentProposalV1{
|
|
.destination = acc1.first.cryptonote_address(),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
acc1.second.push_back(acc1.second.front());
|
|
acc1.second.back().first.randomness = gen_janus_anchor(); //mangle anchor_norm
|
|
|
|
// 1 integrated address payment (subtractable)
|
|
tools::add_element(acc3.second) = {CarrotPaymentProposalV1{
|
|
.destination = acc3.first.cryptonote_address(gen_payment_id()),
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.randomness = gen_janus_anchor()
|
|
}, true};
|
|
|
|
// 1 main address selfsend (subtractable)
|
|
tools::add_element(tx_proposal.explicit_selfsend_proposals) = {{CarrotPaymentProposalSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.carrot_account_spend_pubkey,
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::PAYMENT,
|
|
// no internal messages for legacy self-sends
|
|
}}, true};
|
|
|
|
// 1 subaddress selfsend (subtractable)
|
|
tools::add_element(tx_proposal.explicit_selfsend_proposals) = {CarrotPaymentProposalVerifiableSelfSendV1{
|
|
.proposal = CarrotPaymentProposalSelfSendV1{
|
|
.destination_address_spend_pubkey = acc2.first.subaddress({{4, 19}}).address_spend_pubkey,
|
|
.amount = crypto::rand_idx<rct::xmr_amount>(1000000),
|
|
.enote_type = CarrotEnoteType::CHANGE
|
|
},
|
|
.subaddr_index = {{4, 19}}
|
|
}, true};
|
|
|
|
// specify fee per weight
|
|
tx_proposal.fee_per_weight = 314159;
|
|
|
|
// test
|
|
subtest_multi_account_transfer_over_transaction(tx_proposal);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, make_single_transfer_input_selector_TwoInputsPreferOldest_1)
|
|
{
|
|
const std::vector<CarrotPreSelectedInput> input_candidates = {
|
|
CarrotPreSelectedInput {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 500,
|
|
.key_image = mock::gen_key_image(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 72
|
|
},
|
|
CarrotPreSelectedInput {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 200,
|
|
.key_image = mock::gen_key_image(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 34
|
|
}
|
|
};
|
|
|
|
const std::vector<input_selection_policy_t> policies = { &carrot::ispolicy::select_two_inputs_prefer_oldest };
|
|
|
|
const uint32_t flags = 0;
|
|
|
|
std::set<size_t> selected_input_indices;
|
|
select_inputs_func_t input_selector = make_single_transfer_input_selector(epee::to_span(input_candidates),
|
|
epee::to_span(policies),
|
|
flags,
|
|
&selected_input_indices);
|
|
|
|
boost::multiprecision::int128_t nominal_output_sum = 369;
|
|
|
|
const std::map<size_t, rct::xmr_amount> fee_by_input_count = {
|
|
{1, 50},
|
|
{2, 75}
|
|
};
|
|
|
|
const size_t num_normal_payment_proposals = 1;
|
|
const size_t num_selfsend_payment_proposals = 1;
|
|
|
|
ASSERT_GT(input_candidates[0].core.amount, nominal_output_sum + fee_by_input_count.crbegin()->second);
|
|
|
|
std::vector<CarrotSelectedInput> selected_inputs;
|
|
input_selector(nominal_output_sum,
|
|
fee_by_input_count,
|
|
num_normal_payment_proposals,
|
|
num_selfsend_payment_proposals,
|
|
selected_inputs);
|
|
|
|
ASSERT_EQ(2, input_candidates.size());
|
|
ASSERT_EQ(2, selected_inputs.size());
|
|
EXPECT_NE(input_candidates.at(0).core, input_candidates.at(1).core);
|
|
EXPECT_NE(selected_inputs.at(0), selected_inputs.at(1));
|
|
EXPECT_TRUE((selected_inputs.at(0) == input_candidates.at(0).core) ^ (selected_inputs.at(0) == input_candidates[1].core));
|
|
EXPECT_TRUE((selected_inputs.at(1) == input_candidates.at(0).core) ^ (selected_inputs.at(1) == input_candidates.at(1).core));
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, make_single_transfer_input_selector_greedy_aging_1)
|
|
{
|
|
const std::vector<CarrotPreSelectedInput> input_candidates = {
|
|
CarrotPreSelectedInput {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 500,
|
|
.key_image = mock::gen_key_image(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 72
|
|
},
|
|
CarrotPreSelectedInput {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 200,
|
|
.key_image = mock::gen_key_image(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 34
|
|
}
|
|
};
|
|
|
|
const std::vector<input_selection_policy_t> policies = { &carrot::ispolicy::select_greedy_aging };
|
|
|
|
const uint32_t flags = 0;
|
|
|
|
std::set<size_t> selected_input_indices;
|
|
select_inputs_func_t input_selector = make_single_transfer_input_selector(epee::to_span(input_candidates),
|
|
epee::to_span(policies),
|
|
flags,
|
|
&selected_input_indices);
|
|
|
|
boost::multiprecision::int128_t nominal_output_sum = 369;
|
|
|
|
const std::map<size_t, rct::xmr_amount> fee_by_input_count = {
|
|
{1, 50},
|
|
{2, 75}
|
|
};
|
|
|
|
const size_t num_normal_payment_proposals = 1;
|
|
const size_t num_selfsend_payment_proposals = 1;
|
|
|
|
ASSERT_GT(input_candidates[0].core.amount, nominal_output_sum + fee_by_input_count.crbegin()->second);
|
|
|
|
std::vector<CarrotSelectedInput> selected_inputs;
|
|
input_selector(nominal_output_sum,
|
|
fee_by_input_count,
|
|
num_normal_payment_proposals,
|
|
num_selfsend_payment_proposals,
|
|
selected_inputs);
|
|
|
|
// sometimes we choose 2 for default, sometimes 1 for default
|
|
ASSERT_EQ(2, input_candidates.size());
|
|
ASSERT_EQ(selected_inputs.size(), selected_input_indices.size());
|
|
if (selected_inputs.size() == 2)
|
|
{
|
|
EXPECT_NE(input_candidates.at(0).core, input_candidates.at(1).core);
|
|
EXPECT_NE(selected_inputs.at(0), selected_inputs.at(1));
|
|
EXPECT_TRUE((selected_inputs.at(0) == input_candidates.at(0).core) ^ (selected_inputs.at(0) == input_candidates[1].core));
|
|
EXPECT_TRUE((selected_inputs.at(1) == input_candidates.at(0).core) ^ (selected_inputs.at(1) == input_candidates.at(1).core));
|
|
}
|
|
else
|
|
{
|
|
ASSERT_EQ(std::set<size_t>{0}, selected_input_indices);
|
|
ASSERT_EQ(input_candidates.at(0).core, selected_inputs.at(0));
|
|
}
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, make_single_transfer_input_selector_greedy_aging_2)
|
|
{
|
|
const std::vector<CarrotPreSelectedInput> input_candidates = {
|
|
CarrotPreSelectedInput {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 500,
|
|
.key_image = mock::gen_key_image(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 72
|
|
}
|
|
};
|
|
|
|
const std::vector<input_selection_policy_t> policies = { &carrot::ispolicy::select_greedy_aging };
|
|
|
|
const uint32_t flags = 0;
|
|
|
|
std::set<size_t> selected_input_indices;
|
|
select_inputs_func_t input_selector = make_single_transfer_input_selector(epee::to_span(input_candidates),
|
|
epee::to_span(policies),
|
|
flags,
|
|
&selected_input_indices);
|
|
|
|
boost::multiprecision::int128_t nominal_output_sum = 369;
|
|
|
|
const std::map<size_t, rct::xmr_amount> fee_by_input_count = {
|
|
{1, 50},
|
|
{2, 75}
|
|
};
|
|
|
|
const size_t num_normal_payment_proposals = 1;
|
|
const size_t num_selfsend_payment_proposals = 1;
|
|
|
|
ASSERT_GT(input_candidates[0].core.amount, nominal_output_sum + fee_by_input_count.crbegin()->second);
|
|
|
|
std::vector<CarrotSelectedInput> selected_inputs;
|
|
input_selector(nominal_output_sum,
|
|
fee_by_input_count,
|
|
num_normal_payment_proposals,
|
|
num_selfsend_payment_proposals,
|
|
selected_inputs);
|
|
|
|
ASSERT_EQ(1, input_candidates.size());
|
|
ASSERT_EQ(std::set<size_t>{0}, selected_input_indices);
|
|
ASSERT_EQ(std::vector<CarrotSelectedInput>{input_candidates.at(0).core}, selected_inputs);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
TEST(carrot_impl, make_single_transfer_input_selector_greedy_aging_3)
|
|
{
|
|
// Give 5 input candidates, only 3 are needed to fill order. 4 should be used since it's the next power of 2
|
|
// They should be the four oldest
|
|
|
|
const std::vector<CarrotPreSelectedInput> input_candidates = {
|
|
CarrotPreSelectedInput {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.key_image = mock::gen_key_image(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 55
|
|
},
|
|
CarrotPreSelectedInput {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.key_image = mock::gen_key_image(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 22
|
|
},
|
|
CarrotPreSelectedInput {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.key_image = mock::gen_key_image(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 11
|
|
},
|
|
CarrotPreSelectedInput {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.key_image = mock::gen_key_image(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 88
|
|
},
|
|
CarrotPreSelectedInput {
|
|
.core = CarrotSelectedInput {
|
|
.amount = 100,
|
|
.key_image = mock::gen_key_image(),
|
|
},
|
|
.is_external = false,
|
|
.block_index = 72
|
|
}
|
|
};
|
|
|
|
const std::vector<input_selection_policy_t> policies = { &carrot::ispolicy::select_greedy_aging };
|
|
|
|
const uint32_t flags = 0;
|
|
|
|
std::set<size_t> selected_input_indices;
|
|
select_inputs_func_t input_selector = make_single_transfer_input_selector(epee::to_span(input_candidates),
|
|
epee::to_span(policies),
|
|
flags,
|
|
&selected_input_indices);
|
|
|
|
boost::multiprecision::int128_t nominal_output_sum = 223;
|
|
|
|
const std::map<size_t, rct::xmr_amount> fee_by_input_count = {
|
|
{1, 10},
|
|
{2, 20},
|
|
{3, 29},
|
|
{4, 37},
|
|
{5, 44},
|
|
{6, 50},
|
|
{7, 55},
|
|
{8, 59},
|
|
};
|
|
|
|
const size_t num_normal_payment_proposals = 1;
|
|
const size_t num_selfsend_payment_proposals = 1;
|
|
|
|
std::vector<CarrotSelectedInput> selected_inputs;
|
|
input_selector(nominal_output_sum,
|
|
fee_by_input_count,
|
|
num_normal_payment_proposals,
|
|
num_selfsend_payment_proposals,
|
|
selected_inputs);
|
|
|
|
ASSERT_EQ(4, selected_input_indices.size()); // discretization yay
|
|
ASSERT_EQ(4, selected_inputs.size());
|
|
std::set<crypto::key_image> selected_kis;
|
|
for (const CarrotSelectedInput &selected_input : selected_inputs)
|
|
selected_kis.insert(selected_input.key_image);
|
|
ASSERT_EQ(4, selected_kis.size());
|
|
|
|
// this particular set of 4 indices is the indices of the 4 oldest inputs
|
|
ASSERT_EQ(std::set<size_t>({0, 1, 2, 4}), selected_input_indices);
|
|
}
|
|
//----------------------------------------------------------------------------------------------------------------------
|