wallet: change sweep_dust to sweep_unmixable
With the change in mixin rules for v2, the "annoying" outputs are slightly changed. There is high correlation between dust and unmixable, but no equivalence.
This commit is contained in:
parent
600a3cf0c0
commit
12146daeed
6 changed files with 129 additions and 30 deletions
|
@ -541,7 +541,7 @@ simple_wallet::simple_wallet()
|
|||
m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), tr("Show blockchain height"));
|
||||
m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), tr("transfer [<mixin_count>] <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] [payment_id] - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. <mixin_count> is the number of extra inputs to include for untraceability (from 0 to maximum available)"));
|
||||
m_cmd_binder.set_handler("transfer_new", boost::bind(&simple_wallet::transfer_new, this, _1), tr("Same as transfer, but using a new transaction building algorithm"));
|
||||
m_cmd_binder.set_handler("sweep_dust", boost::bind(&simple_wallet::sweep_dust, this, _1), tr("Send all dust outputs to yourself with mixin 0"));
|
||||
m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with mixin 0"));
|
||||
m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level> - Change current log detail level, <0-4>"));
|
||||
m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("Show current wallet public address"));
|
||||
m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::print_integrated_address, this, _1), tr("integrated_address [PID] - Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID"));
|
||||
|
@ -1722,8 +1722,7 @@ bool simple_wallet::refresh(const std::vector<std::string>& args)
|
|||
bool simple_wallet::show_balance(const std::vector<std::string>& args/* = std::vector<std::string>()*/)
|
||||
{
|
||||
success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance()) << ", "
|
||||
<< tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance()) << ", "
|
||||
<< tr("including unlocked dust: ") << print_money(m_wallet->unlocked_dust_balance(tools::tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD)));
|
||||
<< tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance());
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
|
@ -2208,7 +2207,7 @@ bool simple_wallet::transfer_new(const std::vector<std::string> &args_)
|
|||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool simple_wallet::sweep_dust(const std::vector<std::string> &args_)
|
||||
bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_)
|
||||
{
|
||||
if (!try_connect_to_daemon())
|
||||
return true;
|
||||
|
@ -2221,28 +2220,37 @@ bool simple_wallet::sweep_dust(const std::vector<std::string> &args_)
|
|||
|
||||
try
|
||||
{
|
||||
uint64_t total_dust = m_wallet->unlocked_dust_balance(tools::tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD));
|
||||
|
||||
// figure out what tx will be necessary
|
||||
auto ptx_vector = m_wallet->create_dust_sweep_transactions();
|
||||
auto ptx_vector = m_wallet->create_unmixable_sweep_transactions();
|
||||
|
||||
if (ptx_vector.empty())
|
||||
{
|
||||
fail_msg_writer() << tr("No unmixable outputs found");
|
||||
return true;
|
||||
}
|
||||
|
||||
// give user total and fee, and prompt to confirm
|
||||
uint64_t total_fee = 0;
|
||||
uint64_t total_fee = 0, total_unmixable = 0;
|
||||
for (size_t n = 0; n < ptx_vector.size(); ++n)
|
||||
{
|
||||
total_fee += ptx_vector[n].fee;
|
||||
for (const auto &vin: ptx_vector[n].tx.vin)
|
||||
{
|
||||
if (vin.type() == typeid(txin_to_key))
|
||||
total_unmixable += boost::get<txin_to_key>(vin).amount;
|
||||
}
|
||||
}
|
||||
|
||||
std::string prompt_str = tr("Sweeping ") + print_money(total_dust);
|
||||
std::string prompt_str = tr("Sweeping ") + print_money(total_unmixable);
|
||||
if (ptx_vector.size() > 1) {
|
||||
prompt_str = (boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No)")) %
|
||||
print_money(total_dust) %
|
||||
print_money(total_unmixable) %
|
||||
((unsigned long long)ptx_vector.size()) %
|
||||
print_money(total_fee)).str();
|
||||
}
|
||||
else {
|
||||
prompt_str = (boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) %
|
||||
print_money(total_dust) %
|
||||
print_money(total_unmixable) %
|
||||
print_money(total_fee)).str();
|
||||
}
|
||||
std::string accepted = command_line::input_line(prompt_str);
|
||||
|
@ -2285,11 +2293,12 @@ bool simple_wallet::sweep_dust(const std::vector<std::string> &args_)
|
|||
}
|
||||
catch (const tools::error::not_enough_money& e)
|
||||
{
|
||||
fail_msg_writer() << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) %
|
||||
fail_msg_writer() << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee).\n%s")) %
|
||||
print_money(e.available()) %
|
||||
print_money(e.tx_amount() + e.fee()) %
|
||||
print_money(e.tx_amount()) %
|
||||
print_money(e.fee());
|
||||
print_money(e.fee()) %
|
||||
tr("This is usually due to dust which is so small it cannot pay for itself in fees");
|
||||
}
|
||||
catch (const tools::error::not_enough_outs_to_mix& e)
|
||||
{
|
||||
|
|
|
@ -121,7 +121,7 @@ namespace cryptonote
|
|||
bool transfer_main(bool new_algorithm, const std::vector<std::string> &args);
|
||||
bool transfer(const std::vector<std::string> &args);
|
||||
bool transfer_new(const std::vector<std::string> &args);
|
||||
bool sweep_dust(const std::vector<std::string> &args);
|
||||
bool sweep_unmixable(const std::vector<std::string> &args);
|
||||
std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts(
|
||||
std::vector<cryptonote::tx_destination_entry> dsts, size_t num_splits
|
||||
);
|
||||
|
|
|
@ -2508,7 +2508,7 @@ uint64_t wallet2::unlocked_dust_balance(const tx_dust_policy &dust_policy) const
|
|||
}
|
||||
|
||||
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)
|
||||
void wallet2::transfer_from(const std::vector<size_t> &outs, 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;
|
||||
|
||||
|
@ -2518,6 +2518,19 @@ void wallet2::transfer_dust(size_t num_outputs, uint64_t unlock_time, uint64_t n
|
|||
// throw if there are none
|
||||
uint64_t money = 0;
|
||||
std::list<transfer_container::iterator> selected_transfers;
|
||||
#if 1
|
||||
for (size_t n = 0; n < outs.size(); ++n)
|
||||
{
|
||||
const transfer_details& td = m_transfers[outs[n]];
|
||||
if (!td.m_spent)
|
||||
{
|
||||
selected_transfers.push_back (m_transfers.begin() + outs[n]);
|
||||
money += td.amount();
|
||||
if (selected_transfers.size() >= num_outputs)
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
for (transfer_container::iterator i = m_transfers.begin(); i != m_transfers.end(); ++i)
|
||||
{
|
||||
const transfer_details& td = *i;
|
||||
|
@ -2529,6 +2542,7 @@ void wallet2::transfer_dust(size_t num_outputs, uint64_t unlock_time, uint64_t n
|
|||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// 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);
|
||||
|
@ -2622,8 +2636,8 @@ bool wallet2::use_fork_rules(uint8_t version)
|
|||
r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
CHECK_AND_ASSERT_MES(r, false, "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(res.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(res.status == CORE_RPC_STATUS_OK, false, "Failed to get hard fork status");
|
||||
CHECK_AND_ASSERT_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, false, "Failed to connect to daemon");
|
||||
CHECK_AND_ASSERT_MES(resp_t.result.status == CORE_RPC_STATUS_OK, false, "Failed to get hard fork status");
|
||||
|
||||
bool close_enough = res.height >= resp_t.result.earliest_height - 10; // start using the rules a bit beforehand
|
||||
if (close_enough)
|
||||
|
@ -2641,20 +2655,84 @@ uint64_t wallet2::get_upper_tranaction_size_limit()
|
|||
return ((full_reward_zone * 125) / 100) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
std::vector<wallet2::pending_tx> wallet2::create_dust_sweep_transactions()
|
||||
std::vector<size_t> wallet2::select_available_outputs(std::function<bool(const transfer_details &td)> f)
|
||||
{
|
||||
std::vector<size_t> outputs;
|
||||
size_t n = 0;
|
||||
for (transfer_container::const_iterator i = m_transfers.begin(); i != m_transfers.end(); ++i, ++n)
|
||||
{
|
||||
if (i->m_spent)
|
||||
continue;
|
||||
if (!is_transfer_unlocked(*i))
|
||||
continue;
|
||||
if (f(*i))
|
||||
outputs.push_back(n);
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
std::vector<uint64_t> wallet2::get_unspent_amounts_vector()
|
||||
{
|
||||
std::set<uint64_t> set;
|
||||
for (const auto &td: m_transfers)
|
||||
{
|
||||
if (!td.m_spent)
|
||||
set.insert(td.amount());
|
||||
}
|
||||
std::vector<uint64_t> vector;
|
||||
vector.reserve(set.size());
|
||||
for (const auto &i: set)
|
||||
{
|
||||
vector.push_back(i);
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
std::vector<size_t> wallet2::select_available_unmixable_outputs()
|
||||
{
|
||||
// request all outputs with at least 3 instances, so we can use mixin 2 with
|
||||
epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request> req_t = AUTO_VAL_INIT(req_t);
|
||||
epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response, std::string> resp_t = AUTO_VAL_INIT(resp_t);
|
||||
m_daemon_rpc_mutex.lock();
|
||||
req_t.jsonrpc = "2.0";
|
||||
req_t.id = epee::serialization::storage_entry(0);
|
||||
req_t.method = "get_output_histogram";
|
||||
req_t.params.amounts = get_unspent_amounts_vector();
|
||||
req_t.params.min_count = 3;
|
||||
req_t.params.max_count = 0;
|
||||
bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client);
|
||||
m_daemon_rpc_mutex.unlock();
|
||||
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "select_available_unmixable_outputs");
|
||||
THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram");
|
||||
THROW_WALLET_EXCEPTION_IF(resp_t.result.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.result.status);
|
||||
|
||||
std::set<uint64_t> mixable;
|
||||
for (const auto &i: resp_t.result.histogram)
|
||||
{
|
||||
mixable.insert(i.amount);
|
||||
}
|
||||
|
||||
return select_available_outputs([mixable](const transfer_details &td) {
|
||||
const uint64_t amount = td.amount();
|
||||
if (mixable.find(amount) == mixable.end())
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
std::vector<wallet2::pending_tx> wallet2::create_unmixable_sweep_transactions()
|
||||
{
|
||||
// From hard fork 1, we don't consider small amounts to be dust anymore
|
||||
const bool hf1_rules = use_fork_rules(2); // first hard fork has version 2
|
||||
tx_dust_policy dust_policy(hf1_rules ? 0 : ::config::DEFAULT_DUST_THRESHOLD);
|
||||
|
||||
size_t num_dust_outputs = 0;
|
||||
for (transfer_container::const_iterator i = m_transfers.begin(); i != m_transfers.end(); ++i)
|
||||
// may throw
|
||||
std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs();
|
||||
size_t num_dust_outputs = unmixable_outputs.size();
|
||||
|
||||
if (num_dust_outputs == 0)
|
||||
{
|
||||
const transfer_details& td = *i;
|
||||
if (!td.m_spent && (td.amount() < dust_policy.dust_threshold || !is_valid_decomposed_amount(td.amount())) && is_transfer_unlocked(td))
|
||||
{
|
||||
num_dust_outputs++;
|
||||
}
|
||||
return std::vector<wallet2::pending_tx>();
|
||||
}
|
||||
|
||||
// failsafe split attempt counter
|
||||
|
@ -2679,13 +2757,13 @@ std::vector<wallet2::pending_tx> wallet2::create_dust_sweep_transactions()
|
|||
uint64_t needed_fee = 0;
|
||||
do
|
||||
{
|
||||
transfer_dust(num_outputs_per_tx, (uint64_t)0 /* unlock_time */, 0, detail::digit_split_strategy, dust_policy, extra, tx, ptx);
|
||||
transfer_from(unmixable_outputs, 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);
|
||||
needed_fee = calculate_fee(txBlob);
|
||||
|
||||
// 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);
|
||||
transfer_from(unmixable_outputs, 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);
|
||||
needed_fee = calculate_fee(txBlob);
|
||||
} while (ptx.fee < needed_fee);
|
||||
|
|
|
@ -280,7 +280,7 @@ namespace tools
|
|||
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 transfer_from(const std::vector<size_t> &outs, 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);
|
||||
template<typename T>
|
||||
void transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, 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);
|
||||
|
@ -289,7 +289,7 @@ namespace tools
|
|||
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<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee_UNUSED, const std::vector<uint8_t> extra);
|
||||
std::vector<pending_tx> create_dust_sweep_transactions();
|
||||
std::vector<pending_tx> create_unmixable_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;
|
||||
|
@ -402,6 +402,9 @@ namespace tools
|
|||
void parse_block_round(const cryptonote::blobdata &blob, cryptonote::block &bl, crypto::hash &bl_id, bool &error) const;
|
||||
uint64_t get_upper_tranaction_size_limit();
|
||||
void check_pending_txes();
|
||||
std::vector<uint64_t> get_unspent_amounts_vector();
|
||||
std::vector<size_t> select_available_outputs(std::function<bool(const transfer_details &td)> f);
|
||||
std::vector<size_t> select_available_unmixable_outputs();
|
||||
|
||||
cryptonote::account_base m_account;
|
||||
std::string m_daemon_address;
|
||||
|
|
|
@ -76,6 +76,7 @@ namespace tools
|
|||
// daemon_busy
|
||||
// no_connection_to_daemon
|
||||
// is_key_image_spent_error
|
||||
// get_histogram_error
|
||||
// wallet_files_doesnt_correspond
|
||||
//
|
||||
// * - class with protected ctor
|
||||
|
@ -600,6 +601,14 @@ namespace tools
|
|||
}
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
struct get_histogram_error : public wallet_rpc_error
|
||||
{
|
||||
explicit get_histogram_error(std::string&& loc, const std::string& request)
|
||||
: wallet_rpc_error(std::move(loc), "failed to get output histogram", request)
|
||||
{
|
||||
}
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
struct wallet_files_doesnt_correspond : public wallet_logic_error
|
||||
{
|
||||
explicit wallet_files_doesnt_correspond(std::string&& loc, const std::string& keys_file, const std::string& wallet_file)
|
||||
|
|
|
@ -347,7 +347,7 @@ namespace tools
|
|||
|
||||
try
|
||||
{
|
||||
std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_dust_sweep_transactions();
|
||||
std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_unmixable_sweep_transactions();
|
||||
|
||||
m_wallet.commit_tx(ptx_vector);
|
||||
|
||||
|
|
Loading…
Reference in a new issue