wallet2: minimize the number of construct_tx calls
This commit is contained in:
parent
9d505d26b8
commit
0b726be703
@ -455,6 +455,59 @@ void drop_from_short_history(std::list<crypto::hash> &short_chain_history, size_
|
||||
}
|
||||
}
|
||||
|
||||
size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs, size_t extra_size)
|
||||
{
|
||||
size_t size = 0;
|
||||
|
||||
// tx prefix
|
||||
|
||||
// first few bytes
|
||||
size += 1 + 6;
|
||||
|
||||
// vin
|
||||
size += n_inputs * (1+6+(mixin+1)*2+32);
|
||||
|
||||
// vout
|
||||
size += n_outputs * (6+32);
|
||||
|
||||
// extra
|
||||
size += extra_size;
|
||||
|
||||
// rct signatures
|
||||
|
||||
// type
|
||||
size += 1;
|
||||
|
||||
// rangeSigs
|
||||
size += (2*64*32+32+64*32) * n_outputs;
|
||||
|
||||
// MGs
|
||||
size += n_inputs * (64 * (mixin+1) + 32);
|
||||
|
||||
// mixRing - not serialized, can be reconstructed
|
||||
/* size += 2 * 32 * (mixin+1) * n_inputs; */
|
||||
|
||||
// pseudoOuts
|
||||
size += 32 * n_inputs;
|
||||
// ecdhInfo
|
||||
size += 2 * 32 * n_outputs;
|
||||
// outPk - only commitment is saved
|
||||
size += 32 * n_outputs;
|
||||
// txnFee
|
||||
size += 4;
|
||||
|
||||
LOG_PRINT_L2("estimated rct tx size for " << n_inputs << " with ring size " << (mixin+1) << " and " << n_outputs << ": " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)");
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs, size_t extra_size)
|
||||
{
|
||||
if (use_rct)
|
||||
return estimate_rct_tx_size(n_inputs, mixin, n_outputs + 1, extra_size);
|
||||
else
|
||||
return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES + extra_size;
|
||||
}
|
||||
|
||||
} //namespace
|
||||
|
||||
namespace tools
|
||||
@ -3990,7 +4043,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
|
||||
pending_tx ptx;
|
||||
|
||||
// loop until fee is met without increasing tx size to next KB boundary.
|
||||
uint64_t needed_fee = 0;
|
||||
const size_t estimated_tx_size = estimate_tx_size(false, unused_transfers_indices.size(), fake_outs_count, dst_vector.size(), extra.size());
|
||||
uint64_t needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier);
|
||||
do
|
||||
{
|
||||
transfer(dst_vector, fake_outs_count, unused_transfers_indices, unlock_time, needed_fee, extra, tx, ptx, trusted_daemon);
|
||||
@ -4725,59 +4779,6 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
|
||||
LOG_PRINT_L2("transfer_selected_rct done");
|
||||
}
|
||||
|
||||
static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs)
|
||||
{
|
||||
size_t size = 0;
|
||||
|
||||
// tx prefix
|
||||
|
||||
// first few bytes
|
||||
size += 1 + 6;
|
||||
|
||||
// vin
|
||||
size += n_inputs * (1+6+(mixin+1)*2+32);
|
||||
|
||||
// vout
|
||||
size += n_outputs * (6+32);
|
||||
|
||||
// extra
|
||||
size += 40;
|
||||
|
||||
// rct signatures
|
||||
|
||||
// type
|
||||
size += 1;
|
||||
|
||||
// rangeSigs
|
||||
size += (2*64*32+32+64*32) * n_outputs;
|
||||
|
||||
// MGs
|
||||
size += n_inputs * (64 * (mixin+1) + 32);
|
||||
|
||||
// mixRing - not serialized, can be reconstructed
|
||||
/* size += 2 * 32 * (mixin+1) * n_inputs; */
|
||||
|
||||
// pseudoOuts
|
||||
size += 32 * n_inputs;
|
||||
// ecdhInfo
|
||||
size += 2 * 32 * n_outputs;
|
||||
// outPk - only commitment is saved
|
||||
size += 32 * n_outputs;
|
||||
// txnFee
|
||||
size += 4;
|
||||
|
||||
LOG_PRINT_L2("estimated rct tx size for " << n_inputs << " with ring size " << (mixin+1) << " and " << n_outputs << ": " << size << " (" << ((32 * n_inputs/*+1*/) + 2 * 32 * (mixin+1) * n_inputs + 32 * n_outputs) << " saved)");
|
||||
return size;
|
||||
}
|
||||
|
||||
static size_t estimate_tx_size(bool use_rct, int n_inputs, int mixin, int n_outputs)
|
||||
{
|
||||
if (use_rct)
|
||||
return estimate_rct_tx_size(n_inputs, mixin, n_outputs + 1);
|
||||
else
|
||||
return n_inputs * (mixin+1) * APPROXIMATE_INPUT_BYTES;
|
||||
}
|
||||
|
||||
std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices) const
|
||||
{
|
||||
std::vector<size_t> picks;
|
||||
@ -4785,19 +4786,7 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui
|
||||
|
||||
LOG_PRINT_L2("pick_preferred_rct_inputs: needed_money " << print_money(needed_money));
|
||||
|
||||
// try to find a rct input of enough size
|
||||
for (size_t i = 0; i < m_transfers.size(); ++i)
|
||||
{
|
||||
const transfer_details& td = m_transfers[i];
|
||||
if (!td.m_spent && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
|
||||
{
|
||||
LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount()));
|
||||
picks.push_back(i);
|
||||
return picks;
|
||||
}
|
||||
}
|
||||
|
||||
// then try to find two outputs
|
||||
// try to find two outputs
|
||||
// this could be made better by picking one of the outputs to be a small one, since those
|
||||
// are less useful since often below the needed money, so if one can be used in a pair,
|
||||
// it gets rid of it for the future
|
||||
@ -5551,7 +5540,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
|
||||
{
|
||||
// this is used to build a tx that's 1 or 2 inputs, and 2 outputs, which
|
||||
// will get us a known fee.
|
||||
uint64_t estimated_fee = calculate_fee(fee_per_kb, estimate_rct_tx_size(2, fake_outs_count + 1, 2), fee_multiplier);
|
||||
uint64_t estimated_fee = calculate_fee(fee_per_kb, estimate_rct_tx_size(2, fake_outs_count, 2, extra.size()), fee_multiplier);
|
||||
preferred_inputs = pick_preferred_rct_inputs(needed_money + estimated_fee, subaddr_account, subaddr_indices);
|
||||
if (!preferred_inputs.empty())
|
||||
{
|
||||
@ -5588,7 +5577,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
|
||||
unsigned int original_output_index = 0;
|
||||
std::vector<size_t>* unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second;
|
||||
std::vector<size_t>* unused_dust_indices = &unused_dust_indices_per_subaddr[0].second;
|
||||
while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), *unused_transfers_indices, *unused_dust_indices)) {
|
||||
while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || !preferred_inputs.empty() || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), *unused_transfers_indices, *unused_dust_indices)) {
|
||||
TX &tx = txes.back();
|
||||
|
||||
LOG_PRINT_L2("Start of loop with " << unused_transfers_indices->size() << " " << unused_dust_indices->size());
|
||||
@ -5606,7 +5595,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
|
||||
// get a random unspent output and use it to pay part (or all) of the current destination (and maybe next one, etc)
|
||||
// This could be more clever, but maybe at the cost of making probabilistic inferences easier
|
||||
size_t idx;
|
||||
if ((dsts.empty() || dsts[0].amount == 0) && !adding_fee) {
|
||||
if (!preferred_inputs.empty()) {
|
||||
idx = pop_back(preferred_inputs);
|
||||
pop_if_present(*unused_transfers_indices, idx);
|
||||
pop_if_present(*unused_dust_indices, idx);
|
||||
} else if ((dsts.empty() || dsts[0].amount == 0) && !adding_fee) {
|
||||
// the "make rct txes 2/2" case - we pick a small value output to "clean up" the wallet too
|
||||
std::vector<size_t> indices = get_only_rct(*unused_dust_indices, *unused_transfers_indices);
|
||||
idx = pop_best_value(indices, tx.selected_transfers, true);
|
||||
@ -5629,10 +5622,6 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
|
||||
}
|
||||
pop_if_present(*unused_transfers_indices, idx);
|
||||
pop_if_present(*unused_dust_indices, idx);
|
||||
} else if (!preferred_inputs.empty()) {
|
||||
idx = pop_back(preferred_inputs);
|
||||
pop_if_present(*unused_transfers_indices, idx);
|
||||
pop_if_present(*unused_dust_indices, idx);
|
||||
} else
|
||||
idx = pop_best_value(unused_transfers_indices->empty() ? *unused_dust_indices : *unused_transfers_indices, tx.selected_transfers);
|
||||
|
||||
@ -5654,7 +5643,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
|
||||
}
|
||||
else
|
||||
{
|
||||
while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()) < TX_SIZE_TARGET(upper_transaction_size_limit))
|
||||
while (!dsts.empty() && dsts[0].amount <= available_amount && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size()) < TX_SIZE_TARGET(upper_transaction_size_limit))
|
||||
{
|
||||
// we can fully pay that destination
|
||||
LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_testnet, dsts[0].is_subaddress, dsts[0].addr) <<
|
||||
@ -5666,7 +5655,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
|
||||
++original_output_index;
|
||||
}
|
||||
|
||||
if (available_amount > 0 && !dsts.empty() && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) {
|
||||
if (available_amount > 0 && !dsts.empty() && estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size()) < TX_SIZE_TARGET(upper_transaction_size_limit)) {
|
||||
// we can partially fill that destination
|
||||
LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_testnet, dsts[0].is_subaddress, dsts[0].addr) <<
|
||||
" for " << print_money(available_amount) << "/" << print_money(dsts[0].amount));
|
||||
@ -5679,26 +5668,31 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
|
||||
// here, check if we need to sent tx and start a new one
|
||||
LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit "
|
||||
<< upper_transaction_size_limit);
|
||||
bool try_tx;
|
||||
if (adding_fee)
|
||||
bool try_tx = false;
|
||||
// if we have preferred picks, but haven't yet used all of them, continue
|
||||
if (preferred_inputs.empty())
|
||||
{
|
||||
/* might not actually be enough if adding this output bumps size to next kB, but we need to try */
|
||||
try_tx = available_for_fee >= needed_fee;
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size());
|
||||
try_tx = dsts.empty() || (estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit));
|
||||
if (adding_fee)
|
||||
{
|
||||
/* might not actually be enough if adding this output bumps size to next kB, but we need to try */
|
||||
try_tx = available_for_fee >= needed_fee;
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size());
|
||||
try_tx = dsts.empty() || (estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit));
|
||||
}
|
||||
}
|
||||
|
||||
if (try_tx) {
|
||||
cryptonote::transaction test_tx;
|
||||
pending_tx test_ptx;
|
||||
|
||||
needed_fee = 0;
|
||||
const size_t estimated_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size());
|
||||
needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier);
|
||||
|
||||
LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " <<
|
||||
tx.selected_transfers.size() << " outputs");
|
||||
LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " outputs and " <<
|
||||
tx.selected_transfers.size() << " inputs");
|
||||
if (use_rct)
|
||||
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
|
||||
test_tx, test_ptx);
|
||||
@ -5740,8 +5734,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_PRINT_L2("We made a tx, adjusting fee and saving it");
|
||||
do {
|
||||
LOG_PRINT_L2("We made a tx, adjusting fee and saving it, we need " << print_money(needed_fee) << " and we have " << print_money(test_ptx.fee));
|
||||
while (needed_fee > test_ptx.fee) {
|
||||
if (use_rct)
|
||||
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, unlock_time, needed_fee, extra,
|
||||
test_tx, test_ptx);
|
||||
@ -5752,7 +5746,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
|
||||
needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier);
|
||||
LOG_PRINT_L2("Made an attempt at a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) <<
|
||||
" fee and " << print_money(test_ptx.change_dts.amount) << " change");
|
||||
} while (needed_fee > test_ptx.fee);
|
||||
}
|
||||
|
||||
LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) <<
|
||||
" fee and " << print_money(test_ptx.change_dts.amount) << " change");
|
||||
@ -5934,14 +5928,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
|
||||
// here, check if we need to sent tx and start a new one
|
||||
LOG_PRINT_L2("Considering whether to create a tx now, " << tx.selected_transfers.size() << " inputs, tx limit "
|
||||
<< upper_transaction_size_limit);
|
||||
const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1);
|
||||
const size_t estimated_rct_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1, extra.size());
|
||||
bool try_tx = (unused_dust_indices.empty() && unused_transfers_indices.empty()) || ( estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit));
|
||||
|
||||
if (try_tx) {
|
||||
cryptonote::transaction test_tx;
|
||||
pending_tx test_ptx;
|
||||
|
||||
needed_fee = 0;
|
||||
const size_t estimated_tx_size = estimate_tx_size(use_rct, tx.selected_transfers.size(), fake_outs_count, tx.dsts.size(), extra.size());
|
||||
needed_fee = calculate_fee(fee_per_kb, estimated_tx_size, fee_multiplier);
|
||||
|
||||
tx.dsts.push_back(tx_destination_entry(1, address, is_subaddress));
|
||||
|
||||
@ -5961,7 +5956,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
|
||||
|
||||
THROW_WALLET_EXCEPTION_IF(needed_fee > available_for_fee, error::wallet_internal_error, "Transaction cannot pay for itself");
|
||||
|
||||
do {
|
||||
while (needed_fee > test_ptx.fee) {
|
||||
LOG_PRINT_L2("We made a tx, adjusting fee and saving it");
|
||||
tx.dsts[0].amount = available_for_fee - needed_fee;
|
||||
if (use_rct)
|
||||
@ -5974,7 +5969,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
|
||||
needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier);
|
||||
LOG_PRINT_L2("Made an attempt at a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) <<
|
||||
" fee and " << print_money(test_ptx.change_dts.amount) << " change");
|
||||
} while (needed_fee > test_ptx.fee);
|
||||
}
|
||||
|
||||
LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) <<
|
||||
" fee and " << print_money(test_ptx.change_dts.amount) << " change");
|
||||
|
Loading…
Reference in New Issue
Block a user