aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
authorRiccardo Spagni <ric@spagni.net>2015-05-30 22:53:14 +0200
committerRiccardo Spagni <ric@spagni.net>2015-05-30 22:53:16 +0200
commit72248cfe6f11932303c1ee7c718ed71f48607b87 (patch)
tree7bf6a4567fe039667f8149fe255ab477b9e4c8b1 /src/wallet
parentMerge pull request #299 (diff)
parentwallet_rpc_server: add a sweep_dust RPC command as well (diff)
downloadmonero-72248cfe6f11932303c1ee7c718ed71f48607b87.tar.xz
Merge pull request #296
a62daeb wallet_rpc_server: add a sweep_dust RPC command as well (moneromooo-monero) 3204f0d wallet: add a sweep_dust command (moneromooo-monero)
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/wallet2.cpp211
-rw-r--r--src/wallet/wallet2.h4
-rw-r--r--src/wallet/wallet_rpc_server.cpp44
-rw-r--r--src/wallet/wallet_rpc_server.h2
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h18
5 files changed, 279 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;
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index e39d9ba7b..9ef19f739 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -273,6 +273,50 @@ namespace tools
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er)
+ {
+ if (m_wallet.restricted())
+ {
+ er.code = WALLET_RPC_ERROR_CODE_DENIED;
+ er.message = "Command unavailable in restricted mode.";
+ return false;
+ }
+
+ try
+ {
+ std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_dust_sweep_transactions();
+
+ m_wallet.commit_tx(ptx_vector);
+
+ // populate response with tx hashes
+ for (auto & ptx : ptx_vector)
+ {
+ res.tx_hash_list.push_back(boost::lexical_cast<std::string>(cryptonote::get_transaction_hash(ptx.tx)));
+ }
+
+ return true;
+ }
+ catch (const tools::error::daemon_busy& e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY;
+ er.message = e.what();
+ return false;
+ }
+ catch (const std::exception& e)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR;
+ er.message = e.what();
+ return false;
+ }
+ catch (...)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
+ er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR";
+ return false;
+ }
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_store(const wallet_rpc::COMMAND_RPC_STORE::request& req, wallet_rpc::COMMAND_RPC_STORE::response& res, epee::json_rpc::error& er)
{
if (m_wallet.restricted())
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index cfdc1c147..8538eed2b 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -65,6 +65,7 @@ namespace tools
MAP_JON_RPC_WE("getaddress", on_getaddress, wallet_rpc::COMMAND_RPC_GET_ADDRESS)
MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER)
MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT)
+ MAP_JON_RPC_WE("sweep_dust", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST)
MAP_JON_RPC_WE("store", on_store, wallet_rpc::COMMAND_RPC_STORE)
MAP_JON_RPC_WE("get_payments", on_get_payments, wallet_rpc::COMMAND_RPC_GET_PAYMENTS)
MAP_JON_RPC_WE("get_bulk_payments", on_get_bulk_payments, wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS)
@@ -79,6 +80,7 @@ namespace tools
bool validate_transfer(const std::list<wallet_rpc::transfer_destination> destinations, const std::string payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, epee::json_rpc::error& er);
bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er);
bool on_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er);
+ bool on_sweep_dust(const wallet_rpc::COMMAND_RPC_SWEEP_DUST::request& req, wallet_rpc::COMMAND_RPC_SWEEP_DUST::response& res, epee::json_rpc::error& er);
bool on_store(const wallet_rpc::COMMAND_RPC_STORE::request& req, wallet_rpc::COMMAND_RPC_STORE::response& res, epee::json_rpc::error& er);
bool on_get_payments(const wallet_rpc::COMMAND_RPC_GET_PAYMENTS::request& req, wallet_rpc::COMMAND_RPC_GET_PAYMENTS::response& res, epee::json_rpc::error& er);
bool on_get_bulk_payments(const wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS::request& req, wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS::response& res, epee::json_rpc::error& er);
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index 35783a189..ecb3cc5e9 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -146,6 +146,24 @@ namespace wallet_rpc
};
};
+ struct COMMAND_RPC_SWEEP_DUST
+ {
+ struct request
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response
+ {
+ std::list<std::string> tx_hash_list;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(tx_hash_list)
+ END_KV_SERIALIZE_MAP()
+ };
+ };
+
struct COMMAND_RPC_STORE
{
struct request