wallet2: reuse fake outs when adjusting fee on transfer

This avoids indirectly leaking the real output to the daemon,
and is faster.

This will still happen for more complex cases, especially
when cancelling a tx and "re-rolling" it.
This commit is contained in:
moneromooo-monero 2017-01-07 16:06:07 +00:00
parent 64da0983d5
commit d86ae2bec6
No known key found for this signature in database
GPG Key ID: 686F07454D6CEFC3
3 changed files with 55 additions and 23 deletions

View File

@ -3400,8 +3400,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
}
}
template<typename entry>
void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count)
void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count)
{
LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count);
outs.clear();
@ -3492,6 +3491,7 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si
uint64_t num_found = 1;
seen_indices.emplace(td.m_global_output_index);
req.outputs.push_back({amount, td.m_global_output_index});
LOG_PRINT_L1("Selecting real output: " << td.m_global_output_index << " for " << print_money(amount));
// while we still need more mixins
while (num_found < requested_outputs_count)
@ -3563,7 +3563,7 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si
{
const transfer_details &td = m_transfers[idx];
size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0);
outs.push_back(std::vector<entry>());
outs.push_back(std::vector<get_outs_entry>());
outs.back().reserve(fake_outputs_count + 1);
const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount());
@ -3616,7 +3616,7 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si
else
{
// sort the subsection, so any spares are reset in order
std::sort(outs.back().begin(), outs.back().end(), [](const entry &a, const entry &b) { return std::get<0>(a) < std::get<0>(b); });
std::sort(outs.back().begin(), outs.back().end(), [](const get_outs_entry &a, const get_outs_entry &b) { return std::get<0>(a) < std::get<0>(b); });
}
base += requested_outputs_count;
}
@ -3627,7 +3627,7 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si
for (size_t idx: selected_transfers)
{
const transfer_details &td = m_transfers[idx];
std::vector<entry> v;
std::vector<get_outs_entry> v;
const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount());
v.push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask));
outs.push_back(v);
@ -3637,6 +3637,7 @@ void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<si
template<typename T>
void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx)
{
using namespace cryptonote;
@ -3666,11 +3667,11 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee));
THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee);
typedef std::tuple<uint64_t, crypto::public_key, rct::key> entry;
std::vector<std::vector<entry>> outs;
get_outs(outs, selected_transfers, fake_outputs_count); // may throw
if (outs.empty())
get_outs(outs, selected_transfers, fake_outputs_count); // may throw
//prepare inputs
LOG_PRINT_L2("preparing outputs");
typedef cryptonote::tx_source_entry::output_entry tx_output_entry;
size_t i = 0, out_index = 0;
std::vector<cryptonote::tx_source_entry> sources;
@ -3713,6 +3714,7 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
detail::print_source_entry(src);
++out_index;
}
LOG_PRINT_L2("outputs prepared");
cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts);
if (needed_money < found_money)
@ -3735,7 +3737,9 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
}
crypto::secret_key tx_key;
LOG_PRINT_L2("constructing tx");
bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time, tx_key);
LOG_PRINT_L2("constructed tx, r="<<r);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet);
THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit);
@ -3771,9 +3775,11 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
ptx.construction_data.unlock_time = unlock_time;
ptx.construction_data.use_rct = false;
ptx.construction_data.dests = dsts;
LOG_PRINT_L2("transfer_selected done");
}
void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx)
{
using namespace cryptonote;
@ -3782,7 +3788,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit();
uint64_t needed_money = fee;
LOG_PRINT_L2("transfer: starting with fee " << print_money (needed_money));
LOG_PRINT_L2("transfer_selected_rct: starting with fee " << print_money (needed_money));
LOG_PRINT_L0("selected transfers: ");
for (auto t: selected_transfers)
LOG_PRINT_L2(" " << t);
@ -3806,11 +3812,11 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee));
THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee);
typedef std::tuple<uint64_t, crypto::public_key, rct::key> entry;
std::vector<std::vector<entry>> outs;
get_outs(outs, selected_transfers, fake_outputs_count); // may throw
if (outs.empty())
get_outs(outs, selected_transfers, fake_outputs_count); // may throw
//prepare inputs
LOG_PRINT_L2("preparing outputs");
size_t i = 0, out_index = 0;
std::vector<cryptonote::tx_source_entry> sources;
BOOST_FOREACH(size_t idx, selected_transfers)
@ -3853,6 +3859,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
detail::print_source_entry(src);
++out_index;
}
LOG_PRINT_L2("outputs prepared");
// we still keep a copy, since we want to keep dsts free of change for user feedback purposes
std::vector<cryptonote::tx_destination_entry> splitted_dsts = dsts;
@ -3864,9 +3871,11 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
// the sender with a 0 amount output. We send a 0 amount in order to avoid
// letting the destination be able to work out which of the inputs is the
// real one in our rings
LOG_PRINT_L2("generating dummy address for 0 change");
cryptonote::account_base dummy;
dummy.generate();
change_dts.addr = dummy.get_keys().m_account_address;
LOG_PRINT_L2("generated dummy address for 0 change");
}
else
{
@ -3875,10 +3884,13 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
splitted_dsts.push_back(change_dts);
crypto::secret_key tx_key;
LOG_PRINT_L2("constructing tx");
bool r = cryptonote::construct_tx_and_get_tx_key(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time, tx_key, true);
LOG_PRINT_L2("constructed tx, r="<<r);
THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, dsts, unlock_time, m_testnet);
THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit);
LOG_PRINT_L2("gathering key images");
std::string key_images;
bool all_are_txin_to_key = std::all_of(tx.vin.begin(), tx.vin.end(), [&](const txin_v& s_e) -> bool
{
@ -3887,6 +3899,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
return true;
});
THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, tx);
LOG_PRINT_L2("gathered key images");
ptx.key_images = key_images;
ptx.fee = fee;
@ -3905,6 +3918,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
ptx.construction_data.unlock_time = unlock_time;
ptx.construction_data.use_rct = true;
ptx.construction_data.dests = dsts;
LOG_PRINT_L2("transfer_selected_rct done");
}
static size_t estimate_rct_tx_size(int n_inputs, int mixin, int n_outputs)
@ -4106,12 +4120,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
accumulated_change = 0;
adding_fee = false;
needed_fee = 0;
std::vector<std::vector<tools::wallet2::get_outs_entry>> outs;
// 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.
LOG_PRINT_L2("checking preferred");
std::vector<size_t> prefered_inputs;
uint64_t rct_outs_needed = 2 * (fake_outs_count + 1);
rct_outs_needed += 100; // some fudge factor since we don't know how many are locked
@ -4128,6 +4144,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
LOG_PRINT_L1("Found prefered rct inputs for rct tx: " << s);
}
}
LOG_PRINT_L2("done checking preferred");
// while:
// - we have something to send
@ -4161,6 +4178,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
uint64_t available_amount = td.amount();
accumulated_outputs += available_amount;
// clear any fake outs we'd already gathered, since we'll need a new set
outs.clear();
if (adding_fee)
{
LOG_PRINT_L2("We need more fee, adding it to fee");
@ -4217,10 +4237,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " <<
tx.selected_transfers.size() << " outputs");
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, 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,
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, 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(fee_per_kb, txBlob, fee_multiplier);
@ -4260,10 +4280,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
LOG_PRINT_L2("We made a tx, adjusting fee and saving it");
do {
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, 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,
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, 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);
needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier);
@ -4351,6 +4371,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
std::vector<TX> txes;
uint64_t needed_fee, available_for_fee = 0;
uint64_t upper_transaction_size_limit = get_upper_tranaction_size_limit();
std::vector<std::vector<get_outs_entry>> outs;
const bool use_rct = fake_outs_count > 0 && use_fork_rules(4, 0);
const bool use_new_fee = use_fork_rules(3, -720 * 14);
@ -4386,6 +4407,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
uint64_t available_amount = td.amount();
accumulated_outputs += available_amount;
// clear any fake outs we'd already gathered, since we'll need a new set
outs.clear();
// 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);
@ -4407,10 +4431,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
LOG_PRINT_L2("Trying to create a tx now, with " << tx.dsts.size() << " destinations and " <<
tx.selected_transfers.size() << " outputs");
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, 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,
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, 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(fee_per_kb, txBlob, fee_multiplier);
@ -4424,10 +4448,10 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
LOG_PRINT_L2("We made a tx, adjusting fee and saving it");
tx.dsts[0].amount = available_for_fee - needed_fee;
if (use_rct)
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra,
transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, outs, 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,
transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, outs, 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);
needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier);

View File

@ -275,6 +275,8 @@ namespace tools
std::string m_description;
};
typedef std::tuple<uint64_t, crypto::public_key, rct::key> get_outs_entry;
/*!
* \brief Generates a wallet or restores one.
* \param wallet_ Name of wallet file
@ -387,8 +389,10 @@ namespace tools
void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, const size_t fake_outputs_count, const std::vector<size_t> &unused_transfers_indices, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx, bool trusted_daemon);
template<typename T>
void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx);
void transfer_selected_rct(std::vector<cryptonote::tx_destination_entry> dsts, const std::list<size_t> selected_transfers, size_t fake_outputs_count,
std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx);
void commit_tx(pending_tx& ptx_vector);
@ -607,8 +611,7 @@ namespace tools
std::vector<size_t> pick_preferred_rct_inputs(uint64_t needed_money) const;
void set_spent(size_t idx, uint64_t height);
void set_unspent(size_t idx);
template<typename entry>
void get_outs(std::vector<std::vector<entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count);
void get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::list<size_t> &selected_transfers, size_t fake_outputs_count);
bool wallet_generate_key_image_helper(const cryptonote::account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, cryptonote::keypair& in_ephemeral, crypto::key_image& ki);
crypto::public_key get_tx_pub_key_from_received_outs(const tools::wallet2::transfer_details &td) const;

View File

@ -396,6 +396,7 @@ namespace tools
std::vector<cryptonote::tx_destination_entry> dsts;
std::vector<uint8_t> extra;
LOG_PRINT_L3("on_transfer_split starts");
if (m_wallet.restricted())
{
er.code = WALLET_RPC_ERROR_CODE_DENIED;
@ -485,9 +486,13 @@ namespace tools
mixin = 2;
}
std::vector<wallet2::pending_tx> ptx_vector;
LOG_PRINT_L2("on_transfer_split calling create_transactions_2");
ptx_vector = m_wallet.create_transactions_2(dsts, mixin, req.unlock_time, req.priority, extra, req.trusted_daemon);
LOG_PRINT_L2("on_transfer_split called create_transactions_2");
LOG_PRINT_L2("on_transfer_split calling commit_txyy");
m_wallet.commit_tx(ptx_vector);
LOG_PRINT_L2("on_transfer_split called commit_txyy");
// populate response with tx hashes
for (auto & ptx : ptx_vector)