wallet: add sweep_single command

This commit is contained in:
stoffu 2017-10-11 10:32:06 +09:00
parent 2751a477d8
commit b738f4b59d
No known key found for this signature in database
GPG Key ID: 41DAB8343A9EC012
7 changed files with 438 additions and 0 deletions

View File

@ -910,6 +910,7 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with ring_size 1"));
m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("sweep_all [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>] - Send all unlocked balance to an address. If the parameter \"index<N1>[,<N2>,...]\" is specified, the wallet sweeps outputs received by those address indices. If omitted, the wallet randomly chooses an address index to be used."));
m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), tr("sweep_below <amount_threshold> [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> [<payment_id>] - Send all unlocked outputs below the threshold to an address"));
m_cmd_binder.set_handler("sweep_single", boost::bind(&simple_wallet::sweep_single, this, _1), tr("sweep_single [<priority>] [<ring_size>] <key_image> <address> [<payment_id>] - Send a single output of the given key image to an address without change"));
m_cmd_binder.set_handler("donate", boost::bind(&simple_wallet::donate, this, _1), tr("donate [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <amount> [<payment_id>] - Donate <amount> to the development team (donate.getmonero.org)"));
m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), tr("Sign a transaction from a file"));
m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this, _1), tr("Submit a signed transaction from a file"));
@ -3357,6 +3358,275 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
{
if (m_wallet->ask_password() && !get_and_verify_password()) { return true; }
if (!try_connect_to_daemon())
return true;
std::vector<std::string> local_args = args_;
int priority = 0;
if(local_args.size() > 0) {
auto priority_pos = std::find(
allowed_priority_strings.begin(),
allowed_priority_strings.end(),
local_args[0]);
if(priority_pos != allowed_priority_strings.end()) {
local_args.erase(local_args.begin());
priority = std::distance(allowed_priority_strings.begin(), priority_pos);
}
}
size_t fake_outs_count = 0;
if(local_args.size() > 0) {
size_t ring_size;
if(!epee::string_tools::get_xtype_from_string(ring_size, local_args[0]))
{
fake_outs_count = m_wallet->default_mixin();
if (fake_outs_count == 0)
fake_outs_count = DEFAULT_MIX;
}
else
{
fake_outs_count = ring_size - 1;
local_args.erase(local_args.begin());
}
}
std::vector<uint8_t> extra;
bool payment_id_seen = false;
if (local_args.size() == 3)
{
crypto::hash payment_id;
crypto::hash8 payment_id8;
std::string extra_nonce;
if (tools::wallet2::parse_long_payment_id(local_args.back(), payment_id))
{
set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id);
}
else if(tools::wallet2::parse_short_payment_id(local_args.back(), payment_id8))
{
set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id8);
}
else
{
fail_msg_writer() << tr("failed to parse Payment ID");
return true;
}
if (!add_extra_nonce_to_tx_extra(extra, extra_nonce))
{
fail_msg_writer() << tr("failed to set up payment id, though it was decoded correctly");
return true;
}
local_args.pop_back();
payment_id_seen = true;
}
if (local_args.size() != 2)
{
fail_msg_writer() << tr("usage: sweep_single [<priority>] [<ring_size>] <key_image> <address> [<payment_id>]");
return true;
}
crypto::key_image ki;
if (!epee::string_tools::hex_to_pod(local_args[0], ki))
{
fail_msg_writer() << tr("failed to parse key image");
return true;
}
cryptonote::address_parse_info info;
if (!cryptonote::get_account_address_from_str_or_url(info, m_wallet->testnet(), local_args[1], oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
}
if (info.has_payment_id)
{
if (payment_id_seen)
{
fail_msg_writer() << tr("a single transaction cannot use more than one payment id: ") << local_args[0];
return true;
}
std::string extra_nonce;
set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id);
if (!add_extra_nonce_to_tx_extra(extra, extra_nonce))
{
fail_msg_writer() << tr("failed to set up payment id, though it was decoded correctly");
return true;
}
payment_id_seen = true;
}
// prompt if there is no payment id and confirmation is required
if (!payment_id_seen && m_wallet->confirm_missing_payment_id())
{
std::string accepted = command_line::input_line(tr("No payment id is included with this transaction. Is this okay? (Y/Yes/N/No): "));
if (std::cin.eof())
return true;
if (!command_line::is_yes(accepted))
{
fail_msg_writer() << tr("transaction cancelled.");
// would like to return false, because no tx made, but everything else returns true
// and I don't know what returning false might adversely affect. *sigh*
return true;
}
}
try
{
// figure out what tx will be necessary
auto ptx_vector = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, priority, extra, m_trusted_daemon);
if (ptx_vector.empty())
{
fail_msg_writer() << tr("No outputs found");
return true;
}
if (ptx_vector.size() > 1)
{
fail_msg_writer() << tr("Multiple transactions are created, which is not supposed to happen");
return true;
}
if (ptx_vector[0].selected_transfers.size() > 1)
{
fail_msg_writer() << tr("The transaction uses multiple inputs, which is not supposed to happen");
return true;
}
// give user total and fee, and prompt to confirm
uint64_t total_fee = ptx_vector[0].fee;
uint64_t total_sent = m_wallet->get_transfer_details(ptx_vector[0].selected_transfers.front()).amount();
std::ostringstream prompt;
if (!print_ring_members(ptx_vector, prompt))
return true;
prompt << boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) %
print_money(total_sent) %
print_money(total_fee);
std::string accepted = command_line::input_line(prompt.str());
if (std::cin.eof())
return true;
if (!command_line::is_yes(accepted))
{
fail_msg_writer() << tr("transaction cancelled.");
return true;
}
// actually commit the transactions
if (m_wallet->watch_only())
{
bool r = m_wallet->save_tx(ptx_vector, "unsigned_monero_tx");
if (!r)
{
fail_msg_writer() << tr("Failed to write transaction(s) to file");
}
else
{
success_msg_writer(true) << tr("Unsigned transaction(s) successfully written to file: ") << "unsigned_monero_tx";
}
}
else
{
m_wallet->commit_tx(ptx_vector[0]);
success_msg_writer(true) << tr("Money successfully sent, transaction: ") << get_transaction_hash(ptx_vector[0].tx);
}
}
catch (const tools::error::daemon_busy&)
{
fail_msg_writer() << tr("daemon is busy. Please try again later.");
}
catch (const tools::error::no_connection_to_daemon&)
{
fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running.");
}
catch (const tools::error::wallet_rpc_error& e)
{
LOG_ERROR("RPC error: " << e.to_string());
fail_msg_writer() << tr("RPC error: ") << e.what();
}
catch (const tools::error::get_random_outs_error &e)
{
fail_msg_writer() << tr("failed to get random outputs to mix: ") << e.what();
}
catch (const tools::error::not_enough_money& e)
{
LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, sent amount %s") %
print_money(e.available()) %
print_money(e.tx_amount()));
fail_msg_writer() << tr("Not enough money in unlocked balance");
}
catch (const tools::error::tx_not_possible& e)
{
LOG_PRINT_L0(boost::format("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)") %
print_money(e.available()) %
print_money(e.tx_amount() + e.fee()) %
print_money(e.tx_amount()) %
print_money(e.fee()));
fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees");
}
catch (const tools::error::not_enough_outs_to_mix& e)
{
auto writer = fail_msg_writer();
writer << tr("not enough outputs for specified mixin_count") << " = " << e.mixin_count() << ":";
for (std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs())
{
writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to mix") << " = " << outs_for_amount.second;
}
}
catch (const tools::error::tx_not_constructed&)
{
fail_msg_writer() << tr("transaction was not constructed");
}
catch (const tools::error::tx_rejected& e)
{
fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status();
std::string reason = e.reason();
if (!reason.empty())
fail_msg_writer() << tr("Reason: ") << reason;
}
catch (const tools::error::tx_sum_overflow& e)
{
fail_msg_writer() << e.what();
}
catch (const tools::error::zero_destination&)
{
fail_msg_writer() << tr("one of destinations is zero");
}
catch (const tools::error::tx_too_big& e)
{
fail_msg_writer() << tr("failed to find a suitable way to split transactions");
}
catch (const tools::error::transfer_error& e)
{
LOG_ERROR("unknown transfer error: " << e.to_string());
fail_msg_writer() << tr("unknown transfer error: ") << e.what();
}
catch (const tools::error::wallet_internal_error& e)
{
LOG_ERROR("internal error: " << e.to_string());
fail_msg_writer() << tr("internal error: ") << e.what();
}
catch (const std::exception& e)
{
LOG_ERROR("unexpected error: " << e.what());
fail_msg_writer() << tr("unexpected error: ") << e.what();
}
catch (...)
{
LOG_ERROR("unknown error");
fail_msg_writer() << tr("unknown error");
}
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::sweep_all(const std::vector<std::string> &args_)
{
return sweep_main(0, args_);

View File

@ -141,6 +141,7 @@ namespace cryptonote
bool sweep_main(uint64_t below, const std::vector<std::string> &args);
bool sweep_all(const std::vector<std::string> &args);
bool sweep_below(const std::vector<std::string> &args);
bool sweep_single(const std::vector<std::string> &args);
bool sweep_unmixable(const std::vector<std::string> &args);
bool donate(const std::vector<std::string> &args);
bool sign_transfer(const std::vector<std::string> &args);

View File

@ -5886,6 +5886,27 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon);
}
std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon)
{
std::vector<size_t> unused_transfers_indices;
std::vector<size_t> unused_dust_indices;
const bool use_rct = use_fork_rules(4, 0);
// find output with the given key image
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
if (td.m_key_image_known && td.m_key_image == ki && !td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td))
{
if (td.is_rct() || is_valid_decomposed_amount(td.amount()))
unused_transfers_indices.push_back(i);
else
unused_dust_indices.push_back(i);
break;
}
}
return create_transactions_from(address, is_subaddress, unused_transfers_indices, unused_dust_indices, fake_outs_count, unlock_time, priority, extra, trusted_daemon);
}
std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon)
{
uint64_t accumulated_fee, accumulated_outputs, accumulated_change;

View File

@ -524,6 +524,7 @@ namespace tools
std::vector<pending_tx> create_transactions(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon);
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, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon); // pass subaddr_indices by value on purpose
std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon);
std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon);
std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon);
std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon);
bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000);

View File

@ -812,6 +812,101 @@ namespace tools
}
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_sweep_single(const wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::request& req, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::response& res, epee::json_rpc::error& er)
{
std::vector<cryptonote::tx_destination_entry> dsts;
std::vector<uint8_t> extra;
if (!m_wallet) return not_open(er);
if (m_wallet->restricted())
{
er.code = WALLET_RPC_ERROR_CODE_DENIED;
er.message = "Command unavailable in restricted mode.";
return false;
}
// validate the transfer requested and populate dsts & extra
std::list<wallet_rpc::transfer_destination> destination;
destination.push_back(wallet_rpc::transfer_destination());
destination.back().amount = 0;
destination.back().address = req.address;
if (!validate_transfer(destination, req.payment_id, dsts, extra, er))
{
return false;
}
crypto::key_image ki;
if (!epee::string_tools::hex_to_pod(req.key_image, ki))
{
er.code = WALLET_RPC_ERROR_CODE_WRONG_KEY_IMAGE;
er.message = "failed to parse key image";
return false;
}
try
{
uint64_t mixin = adjust_mixin(req.mixin);
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, mixin, req.unlock_time, req.priority, extra, m_trusted_daemon);
if (ptx_vector.empty())
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "No outputs found";
return false;
}
if (ptx_vector.size() > 1)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "Multiple transactions are created, which is not supposed to happen";
return false;
}
if (ptx_vector[0].selected_transfers.size() > 1)
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
er.message = "The transaction uses multiple inputs, which is not supposed to happen";
return false;
}
if (!req.do_not_relay)
m_wallet->commit_tx(ptx_vector);
// populate response with tx hashes
const wallet2::pending_tx &ptx = ptx_vector[0];
res.tx_hash = epee::string_tools::pod_to_hex(cryptonote::get_transaction_hash(ptx.tx));
if (req.get_tx_key)
{
res.tx_key = epee::string_tools::pod_to_hex(ptx.tx_key);
}
if (req.get_tx_hex)
{
cryptonote::blobdata blob;
tx_to_blob(ptx.tx, blob);
res.tx_blob = epee::string_tools::buff_to_hex_nodelimer(blob);
}
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_make_integrated_address(const wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er)
{
@ -1068,6 +1163,7 @@ namespace tools
rpc_transfers.tx_hash = epee::string_tools::pod_to_hex(td.m_txid);
rpc_transfers.tx_size = txBlob.size();
rpc_transfers.subaddr_index = td.m_subaddr_index.minor;
rpc_transfers.key_image = req.verbose && td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : "";
res.transfers.push_back(rpc_transfers);
}
}

View File

@ -79,6 +79,7 @@ namespace tools
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("sweep_all", on_sweep_all, wallet_rpc::COMMAND_RPC_SWEEP_ALL)
MAP_JON_RPC_WE("sweep_single", on_sweep_single, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE)
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)
@ -126,6 +127,7 @@ namespace tools
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_sweep_all(const wallet_rpc::COMMAND_RPC_SWEEP_ALL::request& req, wallet_rpc::COMMAND_RPC_SWEEP_ALL::response& res, epee::json_rpc::error& er);
bool on_sweep_single(const wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::request& req, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE::response& res, epee::json_rpc::error& er);
bool on_make_integrated_address(const wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er);
bool on_split_integrated_address(const wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::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);

View File

@ -476,6 +476,49 @@ namespace wallet_rpc
};
};
struct COMMAND_RPC_SWEEP_SINGLE
{
struct request
{
std::string address;
uint32_t priority;
uint64_t mixin;
uint64_t unlock_time;
std::string payment_id;
bool get_tx_key;
std::string key_image;
bool do_not_relay;
bool get_tx_hex;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(address)
KV_SERIALIZE(priority)
KV_SERIALIZE(mixin)
KV_SERIALIZE(unlock_time)
KV_SERIALIZE(payment_id)
KV_SERIALIZE(get_tx_key)
KV_SERIALIZE(key_image)
KV_SERIALIZE_OPT(do_not_relay, false)
KV_SERIALIZE_OPT(get_tx_hex, false)
END_KV_SERIALIZE_MAP()
};
struct response
{
std::string tx_hash;
std::string tx_key;
uint64_t fee;
std::string tx_blob;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_hash)
KV_SERIALIZE(tx_key)
KV_SERIALIZE(fee)
KV_SERIALIZE(tx_blob)
END_KV_SERIALIZE_MAP()
};
};
struct COMMAND_RPC_STORE
{
struct request
@ -562,6 +605,7 @@ namespace wallet_rpc
std::string tx_hash;
uint64_t tx_size;
uint32_t subaddr_index;
std::string key_image;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(amount)
@ -570,6 +614,7 @@ namespace wallet_rpc
KV_SERIALIZE(tx_hash)
KV_SERIALIZE(tx_size)
KV_SERIALIZE(subaddr_index)
KV_SERIALIZE(key_image)
END_KV_SERIALIZE_MAP()
};
@ -580,11 +625,13 @@ namespace wallet_rpc
std::string transfer_type;
uint32_t account_index;
std::set<uint32_t> subaddr_indices;
bool verbose;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(transfer_type)
KV_SERIALIZE(account_index)
KV_SERIALIZE(subaddr_indices)
KV_SERIALIZE(verbose)
END_KV_SERIALIZE_MAP()
};