diff options
author | moneromooo-monero <moneromooo-monero@users.noreply.github.com> | 2015-05-30 09:13:52 +0100 |
---|---|---|
committer | moneromooo-monero <moneromooo-monero@users.noreply.github.com> | 2015-05-30 21:47:56 +0100 |
commit | 3204f0d53642588643312a742d13cc2b1d1e3cd9 (patch) | |
tree | 5db3c8fdadeaf4b3cf96fe36087294dfc1ce2020 /src/wallet | |
parent | Merge pull request #299 (diff) | |
download | monero-3204f0d53642588643312a742d13cc2b1d1e3cd9.tar.xz |
wallet: add a sweep_dust command
Sends all the dust to your own wallet. May fail (if the fee required
is more than the dust total). May end up paying most of the dust in fees.
Unlocked dust total is now also displayed in "balance".
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/wallet2.cpp | 211 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 4 |
2 files changed, 215 insertions, 0 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 5ff8ae408..a3deb5ac5 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1202,6 +1202,217 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto } } +uint64_t wallet2::unlocked_dust_balance(const tx_dust_policy &dust_policy) const +{ + uint64_t money = 0; + std::list<transfer_container::iterator> selected_transfers; + for (transfer_container::const_iterator i = m_transfers.begin(); i != m_transfers.end(); ++i) + { + const transfer_details& td = *i; + if (!td.m_spent && td.amount() < dust_policy.dust_threshold && is_transfer_unlocked(td)) + { + money += td.amount(); + } + } + return money; +} + +template<typename T> +void wallet2::transfer_dust(size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector<uint8_t> &extra, cryptonote::transaction& tx, pending_tx &ptx) +{ + using namespace cryptonote; + + // select all dust inputs for transaction + // throw if there are none + uint64_t money = 0; + std::list<transfer_container::iterator> selected_transfers; + for (transfer_container::iterator i = m_transfers.begin(); i != m_transfers.end(); ++i) + { + const transfer_details& td = *i; + if (!td.m_spent && td.amount() < dust_policy.dust_threshold && is_transfer_unlocked(td)) + { + selected_transfers.push_back (i); + money += td.amount(); + if (selected_transfers.size() >= num_outputs) + break; + } + } + + // we don't allow no output to self, easier, but one may want to burn the dust if = fee + THROW_WALLET_EXCEPTION_IF(money <= needed_fee, error::not_enough_money, money, needed_fee, needed_fee); + + typedef cryptonote::tx_source_entry::output_entry tx_output_entry; + + //prepare inputs + size_t i = 0; + std::vector<cryptonote::tx_source_entry> sources; + BOOST_FOREACH(transfer_container::iterator it, selected_transfers) + { + sources.resize(sources.size()+1); + cryptonote::tx_source_entry& src = sources.back(); + transfer_details& td = *it; + src.amount = td.amount(); + + //paste real transaction to the random index + auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) + { + return a.first >= td.m_global_output_index; + }); + tx_output_entry real_oe; + real_oe.first = td.m_global_output_index; + real_oe.second = boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key; + auto interted_it = src.outputs.insert(it_to_insert, real_oe); + src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); + src.real_output = interted_it - src.outputs.begin(); + src.real_output_in_tx_index = td.m_internal_output_index; + detail::print_source_entry(src); + ++i; + } + + cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts); + + std::vector<cryptonote::tx_destination_entry> dsts; + uint64_t money_back = money - needed_fee; + if (dust_policy.dust_threshold > 0) + money_back = money_back - money_back % dust_policy.dust_threshold; + dsts.push_back(cryptonote::tx_destination_entry(money_back, m_account_public_address)); + uint64_t dust = 0; + std::vector<cryptonote::tx_destination_entry> splitted_dsts; + destination_split_strategy(dsts, change_dts, dust_policy.dust_threshold, splitted_dsts, dust); + THROW_WALLET_EXCEPTION_IF(dust_policy.dust_threshold < dust, error::wallet_internal_error, "invalid dust value: dust = " + + std::to_string(dust) + ", dust_threshold = " + std::to_string(dust_policy.dust_threshold)); + + bool r = cryptonote::construct_tx(m_account.get_keys(), sources, splitted_dsts, extra, tx, unlock_time); + THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_testnet); + THROW_WALLET_EXCEPTION_IF(m_upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, m_upper_transaction_size_limit); + + 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 + { + CHECKED_GET_SPECIFIC_VARIANT(s_e, const txin_to_key, in, false); + key_images += boost::to_string(in.k_image) + " "; + return true; + }); + THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, tx); + + ptx.key_images = key_images; + ptx.fee = money - money_back; + ptx.dust = dust; + ptx.tx = tx; + ptx.change_dts = change_dts; + ptx.selected_transfers = selected_transfers; +} + +//---------------------------------------------------------------------------------------------------- +std::vector<wallet2::pending_tx> wallet2::create_dust_sweep_transactions() +{ + tx_dust_policy dust_policy(::config::DEFAULT_DUST_THRESHOLD); + + size_t num_dust_outputs = 0; + for (transfer_container::const_iterator i = m_transfers.begin(); i != m_transfers.end(); ++i) + { + const transfer_details& td = *i; + if (!td.m_spent && td.amount() < dust_policy.dust_threshold && is_transfer_unlocked(td)) + { + num_dust_outputs++; + } + } + + // failsafe split attempt counter + size_t attempt_count = 0; + + for(attempt_count = 1; ;attempt_count++) + { + size_t num_outputs_per_tx = (num_dust_outputs + attempt_count - 1) / attempt_count; + + std::vector<pending_tx> ptx_vector; + try + { + // for each new tx + for (size_t i=0; i<attempt_count;++i) + { + cryptonote::transaction tx; + pending_tx ptx; + std::vector<uint8_t> extra; + + // loop until fee is met without increasing tx size to next KB boundary. + uint64_t needed_fee = 0; + if (1) + { + transfer_dust(num_outputs_per_tx, (uint64_t)0 /* unlock_time */, 0, detail::digit_split_strategy, dust_policy, extra, tx, ptx); + auto txBlob = t_serializable_object_to_blob(ptx.tx); + uint64_t txSize = txBlob.size(); + uint64_t numKB = txSize / 1024; + if (txSize % 1024) + { + numKB++; + } + needed_fee = numKB * FEE_PER_KB; + + // reroll the tx with the actual amount minus the fee + // if there's not enough for the fee, it'll throw + transfer_dust(num_outputs_per_tx, (uint64_t)0 /* unlock_time */, needed_fee, detail::digit_split_strategy, dust_policy, extra, tx, ptx); + txBlob = t_serializable_object_to_blob(ptx.tx); + } + + ptx_vector.push_back(ptx); + + // mark transfers to be used as "spent" + BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers) + it->m_spent = true; + } + + // if we made it this far, we've selected our transactions. committing them will mark them spent, + // so this is a failsafe in case they don't go through + // unmark pending tx transfers as spent + for (auto & ptx : ptx_vector) + { + // mark transfers to be used as not spent + BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + it2->m_spent = false; + + } + + // if we made it this far, we're OK to actually send the transactions + return ptx_vector; + + } + // only catch this here, other exceptions need to pass through to the calling function + catch (const tools::error::tx_too_big& e) + { + + // unmark pending tx transfers as spent + for (auto & ptx : ptx_vector) + { + // mark transfers to be used as not spent + BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + it2->m_spent = false; + + } + + if (attempt_count >= MAX_SPLIT_ATTEMPTS) + { + throw; + } + } + catch (...) + { + // in case of some other exception, make sure any tx in queue are marked unspent again + + // unmark pending tx transfers as spent + for (auto & ptx : ptx_vector) + { + // mark transfers to be used as not spent + BOOST_FOREACH(transfer_container::iterator it2, ptx.selected_transfers) + it2->m_spent = false; + + } + + throw; + } + } +} + //---------------------------------------------------------------------------------------------------- void wallet2::generate_genesis(cryptonote::block& b) { if (m_testnet) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 712cda40a..a57501786 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -201,15 +201,19 @@ namespace tools uint64_t balance() const; uint64_t unlocked_balance() const; + uint64_t unlocked_dust_balance(const tx_dust_policy &dust_policy) const; template<typename T> void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy); template<typename T> void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, 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(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra); void transfer(const std::vector<cryptonote::tx_destination_entry>& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx); + template<typename T> + void transfer_dust(size_t num_outputs, uint64_t unlock_time, uint64_t needed_fee, T destination_split_strategy, const tx_dust_policy& dust_policy, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx &ptx); void commit_tx(pending_tx& ptx_vector); void commit_tx(std::vector<pending_tx>& ptx_vector); std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee, const std::vector<uint8_t> extra); + std::vector<pending_tx> create_dust_sweep_transactions(); bool check_connection(); void get_transfers(wallet2::transfer_container& incoming_transfers) const; void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0) const; |