aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/simplewallet/simplewallet.cpp35
-rw-r--r--src/simplewallet/simplewallet.h2
-rw-r--r--src/wallet/wallet2.cpp104
-rw-r--r--src/wallet/wallet2.h7
-rw-r--r--src/wallet/wallet_errors.h9
-rw-r--r--src/wallet/wallet_rpc_server.cpp2
6 files changed, 129 insertions, 30 deletions
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 1856118f7..dd166ede5 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -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)
{
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index 7dadb8536..21bbfa566 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -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
);
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index aa638143c..7d308e615 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -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);
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index e7b002925..2b6cdab92 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -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;
diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h
index 6074e0858..652b19499 100644
--- a/src/wallet/wallet_errors.h
+++ b/src/wallet/wallet_errors.h
@@ -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)
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 418de327c..83e1f7535 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -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);