diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 737a24fe8..75c912461 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -97,14 +97,18 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file, } } -uint64_t calculate_fee(const cryptonote::blobdata &blob, uint64_t fee_multiplier) +uint64_t calculate_fee(size_t bytes, uint64_t fee_multiplier) { THROW_WALLET_EXCEPTION_IF(fee_multiplier <= 0 || fee_multiplier > 3, tools::error::invalid_fee_multiplier); - uint64_t bytes = blob.size(); uint64_t kB = (bytes + 1023) / 1024; return kB * FEE_PER_KB * fee_multiplier; } +uint64_t calculate_fee(const cryptonote::blobdata &blob, uint64_t fee_multiplier) +{ + return calculate_fee(blob.size(), fee_multiplier); +} + } //namespace namespace tools @@ -2098,6 +2102,16 @@ namespace size_t idx = crypto::rand() % vec.size(); return pop_index (vec, idx); } + + template + T pop_back(std::vector& vec) + { + CHECK_AND_ASSERT_MES(!vec.empty(), T(), "Vector must be non-empty"); + + T res = vec.back(); + vec.pop_back(); + return res; + } } //---------------------------------------------------------------------------------------------------- // Select random input sources for transaction. @@ -3012,6 +3026,92 @@ static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs) return size; } +// This returns a handwavy estimation of how much two outputs are related +// If they're from the same tx, then they're fully related. From close block +// heights, they're kinda related. The actual values don't matter, just +// their ordering, but it could become more murky if we add scores later. +float wallet2::get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const +{ +#if 0 + // expensive test, and same tx will fall onto the same block height below + if (get_transaction_hash(td0.m_tx) == get_transaction_hash(td1.m_tx)) + return 1.0f; +#endif + + // same block height -> possibly tx burst, or same tx (since above is disabled) + if (td0.m_block_height == td1.m_block_height) + return 0.9f; + + // could extract the payment id, and compare them, but this is a bit expensive too + + // similar block heights + if ((td0.m_block_height > td1.m_block_height ? td0.m_block_height - td1.m_block_height : td1.m_block_height - td0.m_block_height) < 10) + return 0.2f; + + // don't think these are particularly related + return 0.0f; +} + +std::vector wallet2::pick_prefered_rct_inputs(uint64_t needed_money) const +{ + std::vector picks; + float current_output_relatdness = 1.0f; + + LOG_PRINT_L2("pick_prefered_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)) + { + LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount())); + picks.push_back(i); + return picks; + } + } + + // then 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 + 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)) + { + LOG_PRINT_L2("Considering input " << i << ", " << print_money(td.amount())); + for (size_t j = i + 1; j < m_transfers.size(); ++j) + { + const transfer_details& td2 = m_transfers[j]; + if (!td2.m_spent && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2)) + { + // update our picks if those outputs are less related than any we + // already found. If the same, don't update, and oldest suitable outputs + // will be used in preference. + float relatedness = get_output_relatedness(td, td2); + LOG_PRINT_L2(" with input " << j << ", " << print_money(td2.amount()) << ", relatedness " << relatedness); + if (relatedness < current_output_relatdness) + { + // reset the current picks with those, and return them directly + // if they're unrelated. If they are related, we'll end up returning + // them if we find nothing better + picks.clear(); + picks.push_back(i); + picks.push_back(j); + LOG_PRINT_L0("we could use " << i << " and " << j); + if (relatedness == 0.0f) + return picks; + current_output_relatdness = relatedness; + } + } + } + } + } + + return picks; +} + // 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 @@ -3096,6 +3196,26 @@ std::vector wallet2::create_transactions_2(std::vector prefered_inputs; + if (use_rct) + { + // 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(estimate_rct_tx_size(2, fake_outs_count + 1, 2), fee_multiplier); + prefered_inputs = pick_prefered_rct_inputs(needed_money + estimated_fee); + if (!prefered_inputs.empty()) + { + string s; + for (auto i: prefered_inputs) s += print_money(m_transfers[i].amount()) + " "; + LOG_PRINT_L1("Found prefered rct inputs for rct tx: " << s); + } + } + // while we have something to send while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee) { TX &tx = txes.back(); @@ -3108,7 +3228,7 @@ std::vector wallet2::create_transactions_2(std::vector get_unspent_amounts_vector(); uint64_t sanitize_fee_multiplier(uint64_t fee_multiplier) const; + float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const; + std::vector pick_prefered_rct_inputs(uint64_t needed_money) const; cryptonote::account_base m_account; std::string m_daemon_address;