diff options
Diffstat (limited to 'src/wallet/wallet2.cpp')
-rw-r--r-- | src/wallet/wallet2.cpp | 293 |
1 files changed, 40 insertions, 253 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 3565945d4..2a873d32e 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2974,247 +2974,6 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry ptx.dests = dsts; } -// Another implementation of transaction creation that is hopefully better -// While there is anything left to pay, it goes through random outputs and tries -// to fill the next destination/amount. If it fully fills it, it will use the -// remainder to try to fill the next one as well. -// The tx size if roughly estimated as a linear function of only inputs, and a -// new tx will be created when that size goes above a given fraction of the -// max tx size. At that point, more outputs may be added if the fee cannot be -// satisfied. -// If the next output in the next tx would go to the same destination (ie, we -// cut off at a tx boundary in the middle of paying a given destination), the -// fee will be carved out of the current input if possible, to avoid having to -// add another output just for the fee and getting change. -// This system allows for sending (almost) the entire balance, since it does -// not generate spurious change in all txes, thus decreasing the instantaneous -// usable balance. -std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint64_t fee_multiplier, const std::vector<uint8_t> extra, bool trusted_daemon) -{ - std::vector<size_t> unused_transfers_indices; - std::vector<size_t> unused_dust_indices; - uint64_t needed_money; - uint64_t accumulated_fee, accumulated_outputs, accumulated_change; - struct TX { - std::list<transfer_container::iterator> selected_transfers; - std::vector<cryptonote::tx_destination_entry> dsts; - cryptonote::transaction tx; - pending_tx ptx; - size_t bytes; - - void add(const account_public_address &addr, uint64_t amount) { - std::vector<cryptonote::tx_destination_entry>::iterator i; - i = std::find_if(dsts.begin(), dsts.end(), [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &addr, sizeof(addr)); }); - if (i == dsts.end()) - dsts.push_back(tx_destination_entry(amount,addr)); - else - i->amount += amount; - } - }; - std::vector<TX> txes; - bool adding_fee; // true if new outputs go towards fee, rather than destinations - uint64_t needed_fee, available_for_fee = 0; - uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); - - // throw if attempting a transaction with no destinations - THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination); - - fee_multiplier = sanitize_fee_multiplier (fee_multiplier); - - // calculate total amount being sent to all destinations - // throw if total amount overflows uint64_t - needed_money = 0; - BOOST_FOREACH(auto& dt, dsts) - { - THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination); - needed_money += dt.amount; - LOG_PRINT_L2("transfer: adding " << print_money(dt.amount) << ", for a total of " << print_money (needed_money)); - THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, 0, m_testnet); - } - - // throw if attempting a transaction with no money - THROW_WALLET_EXCEPTION_IF(needed_money == 0, error::zero_destination); - - // gather all our dust and non dust outputs - for (size_t i = 0; i < m_transfers.size(); ++i) - { - const transfer_details& td = m_transfers[i]; - if (!td.m_spent && !td.is_rct() && is_transfer_unlocked(td)) - { - if (is_valid_decomposed_amount(td.amount())) - unused_transfers_indices.push_back(i); - else - unused_dust_indices.push_back(i); - } - } - LOG_PRINT_L2("Starting with " << unused_transfers_indices.size() << " non-dust outputs and " << unused_dust_indices.size() << " dust outputs"); - - // start with an empty tx - txes.push_back(TX()); - accumulated_fee = 0; - accumulated_outputs = 0; - accumulated_change = 0; - adding_fee = false; - needed_fee = 0; - - // while we have something to send - while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee) { - TX &tx = txes.back(); - - // if we need to spend money and don't have any left, we fail - if (unused_dust_indices.empty() && unused_transfers_indices.empty()) { - LOG_PRINT_L2("No more outputs to choose from"); - THROW_WALLET_EXCEPTION_IF(1, error::not_enough_money, unlocked_balance(), needed_money, accumulated_fee + needed_fee); - } - - // 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 = !unused_transfers_indices.empty() ? pop_random_value(unused_transfers_indices) : pop_random_value(unused_dust_indices); - - const transfer_details &td = m_transfers[idx]; - LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount())); - - // add this output to the list to spend - tx.selected_transfers.push_back(m_transfers.begin() + idx); - uint64_t available_amount = td.amount(); - accumulated_outputs += available_amount; - - if (adding_fee) - { - LOG_PRINT_L2("We need more fee, adding it to fee"); - available_for_fee += available_amount; - } - else - { - while (!dsts.empty() && dsts[0].amount <= available_amount) - { - // we can fully pay that destination - LOG_PRINT_L2("We can fully pay " << get_account_address_as_str(m_testnet, dsts[0].addr) << - " for " << print_money(dsts[0].amount)); - tx.add(dsts[0].addr, dsts[0].amount); - available_amount -= dsts[0].amount; - dsts[0].amount = 0; - pop_index(dsts, 0); - } - - if (available_amount > 0 && !dsts.empty()) { - // we can partially fill that destination - LOG_PRINT_L2("We can partially pay " << get_account_address_as_str(m_testnet, dsts[0].addr) << - " for " << print_money(available_amount) << "/" << print_money(dsts[0].amount)); - tx.add(dsts[0].addr, available_amount); - dsts[0].amount -= available_amount; - available_amount = 0; - } - } - - // 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) - { - /* 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 - { - try_tx = dsts.empty() || (tx.selected_transfers.size() * (fake_outs_count+1) * APPROXIMATE_INPUT_BYTES >= TX_SIZE_TARGET(upper_transaction_size_limit)); - } - - if (try_tx) { - cryptonote::transaction test_tx; - pending_tx test_ptx; - - needed_fee = 0; - - LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << - tx.selected_transfers.size() << " outputs"); - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); - auto txBlob = t_serializable_object_to_blob(test_ptx.tx); - needed_fee = calculate_fee(txBlob, fee_multiplier); - available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0); - LOG_PRINT_L2("Made a " << txBlob.size() << " kB tx, with " << print_money(available_for_fee) << " available for fee (" << - print_money(needed_fee) << " needed)"); - - if (needed_fee > available_for_fee && dsts[0].amount > 0) - { - // we don't have enough for the fee, but we've only partially paid the current address, - // so we can take the fee from the paid amount, since we'll have to make another tx anyway - std::vector<cryptonote::tx_destination_entry>::iterator i; - i = std::find_if(tx.dsts.begin(), tx.dsts.end(), - [&](const cryptonote::tx_destination_entry &d) { return !memcmp (&d.addr, &dsts[0].addr, sizeof(dsts[0].addr)); }); - THROW_WALLET_EXCEPTION_IF(i == tx.dsts.end(), error::wallet_internal_error, "paid address not found in outputs"); - if (i->amount > needed_fee) - { - uint64_t new_paid_amount = i->amount /*+ test_ptx.fee*/ - needed_fee; - LOG_PRINT_L2("Adjusting amount paid to " << get_account_address_as_str(m_testnet, i->addr) << " from " << - print_money(i->amount) << " to " << print_money(new_paid_amount) << " to accomodate " << - print_money(needed_fee) << " fee"); - dsts[0].amount += i->amount - new_paid_amount; - i->amount = new_paid_amount; - test_ptx.fee = needed_fee; - available_for_fee = needed_fee; - } - } - - if (needed_fee > available_for_fee) - { - LOG_PRINT_L2("We could not make a tx, switching to fee accumulation"); - - adding_fee = true; - } - else - { - LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); - txBlob = t_serializable_object_to_blob(test_ptx.tx); - 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"); - - tx.tx = test_tx; - tx.ptx = test_ptx; - tx.bytes = txBlob.size(); - accumulated_fee += test_ptx.fee; - accumulated_change += test_ptx.change_dts.amount; - adding_fee = false; - if (!dsts.empty()) - { - LOG_PRINT_L2("We have more to pay, starting another tx"); - txes.push_back(TX()); - } - } - } - } - - if (adding_fee) - { - LOG_PRINT_L1("We ran out of outputs while trying to gather final fee"); - THROW_WALLET_EXCEPTION_IF(1, error::not_enough_money, unlocked_balance(), needed_money, accumulated_fee + needed_fee); - } - - LOG_PRINT_L1("Done creating " << txes.size() << " transactions, " << print_money(accumulated_fee) << - " total fee, " << print_money(accumulated_change) << " total change"); - - std::vector<wallet2::pending_tx> ptx_vector; - for (std::vector<TX>::iterator i = txes.begin(); i != txes.end(); ++i) - { - TX &tx = *i; - uint64_t tx_money = 0; - for (std::list<transfer_container::iterator>::const_iterator mi = tx.selected_transfers.begin(); mi != tx.selected_transfers.end(); ++mi) - tx_money += (*mi)->amount(); - LOG_PRINT_L1(" Transaction " << (1+std::distance(txes.begin(), i)) << "/" << txes.size() << - ": " << (tx.bytes+1023)/1024 << " kB, sending " << print_money(tx_money) << " in " << tx.selected_transfers.size() << - " outputs to " << tx.dsts.size() << " destination(s), including " << - print_money(tx.ptx.fee) << " fee, " << print_money(tx.ptx.change_dts.amount) << " change"); - ptx_vector.push_back(tx.ptx); - } - - // if we made it this far, we're OK to actually send the transactions - return ptx_vector; -} - static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs) { size_t size = 0; @@ -3253,7 +3012,22 @@ static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs) return size; } -std::vector<wallet2::pending_tx> wallet2::create_transactions_rct(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint64_t fee_multiplier, const std::vector<uint8_t> extra, bool trusted_daemon) +// Another implementation of transaction creation that is hopefully better +// While there is anything left to pay, it goes through random outputs and tries +// to fill the next destination/amount. If it fully fills it, it will use the +// remainder to try to fill the next one as well. +// The tx size if roughly estimated as a linear function of only inputs, and a +// new tx will be created when that size goes above a given fraction of the +// max tx size. At that point, more outputs may be added if the fee cannot be +// satisfied. +// If the next output in the next tx would go to the same destination (ie, we +// cut off at a tx boundary in the middle of paying a given destination), the +// fee will be carved out of the current input if possible, to avoid having to +// add another output just for the fee and getting change. +// This system allows for sending (almost) the entire balance, since it does +// not generate spurious change in all txes, thus decreasing the instantaneous +// usable balance. +std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint64_t fee_multiplier, const std::vector<uint8_t> extra, bool trusted_daemon) { std::vector<size_t> unused_transfers_indices; std::vector<size_t> unused_dust_indices; @@ -3279,6 +3053,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_rct(std::vector<cr bool adding_fee; // true if new outputs go towards fee, rather than destinations uint64_t needed_fee, available_for_fee = 0; uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit(); + const bool use_rct = use_fork_rules(3); fee_multiplier = sanitize_fee_multiplier(fee_multiplier); @@ -3303,9 +3078,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_rct(std::vector<cr for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details& td = m_transfers[i]; - if (!td.m_spent && is_transfer_unlocked(td)) + if (!td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td)) { - if (is_valid_decomposed_amount(td.amount()) || td.is_rct()) + if ((td.is_rct()) || is_valid_decomposed_amount(td.amount())) unused_transfers_indices.push_back(i); else unused_dust_indices.push_back(i); @@ -3382,7 +3157,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_rct(std::vector<cr } else { - size_t estimated_rct_tx_size = estimate_rct_tx_size(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1); + size_t estimated_rct_tx_size; + if (use_rct) + estimated_rct_tx_size = estimate_rct_tx_size(tx.selected_transfers.size(), fake_outs_count, tx.dsts.size() + 1); + else + estimated_rct_tx_size = tx.selected_transfers.size() * (fake_outs_count+1) * APPROXIMATE_INPUT_BYTES; try_tx = dsts.empty() || (estimated_rct_tx_size >= TX_SIZE_TARGET(upper_transaction_size_limit)); } @@ -3394,8 +3173,12 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_rct(std::vector<cr LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " << tx.selected_transfers.size() << " outputs"); - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, - test_tx, test_ptx); + if (use_rct) + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + test_tx, test_ptx); + else + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); auto txBlob = t_serializable_object_to_blob(test_ptx.tx); needed_fee = calculate_fee(txBlob, fee_multiplier); available_for_fee = test_ptx.fee + test_ptx.change_dts.amount + (!test_ptx.dust_added_to_fee ? test_ptx.dust : 0); @@ -3432,11 +3215,15 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_rct(std::vector<cr else { LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, - test_tx, test_ptx); + if (use_rct) + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + test_tx, test_ptx); + else + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); txBlob = t_serializable_object_to_blob(test_ptx.tx); - 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"); + 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"); tx.tx = test_tx; tx.ptx = test_ptx; @@ -3757,9 +3544,9 @@ bool wallet2::use_fork_rules(uint8_t version) bool close_enough = res.height >= resp_t.result.earliest_height - 10; // start using the rules a bit beforehand if (close_enough) - LOG_PRINT_L2("Using HF1 rules"); + LOG_PRINT_L2("Using v" << (unsigned)version << " rules"); else - LOG_PRINT_L2("Not using HF1 rules"); + LOG_PRINT_L2("Not using v" << (unsigned)version << " rules"); return close_enough; } //---------------------------------------------------------------------------------------------------- |