diff options
-rw-r--r-- | src/wallet/wallet2.cpp | 126 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 2 |
2 files changed, 125 insertions, 3 deletions
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<size_t>() % vec.size(); return pop_index (vec, idx); } + + template<typename T> + T pop_back(std::vector<T>& 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<size_t> wallet2::pick_prefered_rct_inputs(uint64_t needed_money) const +{ + std::vector<size_t> 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::pending_tx> wallet2::create_transactions_2(std::vector<cryp adding_fee = false; needed_fee = 0; + // for rct, since we don't see the amounts, we will try to make all transactions + // look the same, with 1 or 2 inputs, and 2 outputs. One input is preferable, as + // this prevents linking to another by provenance analysis, but two is ok if we + // try to pick outputs not from the same block. We will get two outputs, one for + // the destination, and one for change. + std::vector<size_t> 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::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 = !unused_transfers_indices.empty() ? pop_random_value(unused_transfers_indices) : pop_random_value(unused_dust_indices); + size_t idx = !prefered_inputs.empty() ? pop_back(prefered_inputs) : !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())); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 80e8f7f49..ca9d9fb70 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -450,6 +450,8 @@ namespace tools uint64_t get_upper_tranaction_size_limit(); std::vector<uint64_t> 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<size_t> pick_prefered_rct_inputs(uint64_t needed_money) const; cryptonote::account_base m_account; std::string m_daemon_address; |