1
0
Fork 0
forked from lthn/blockchain

sweep_below implemented

This commit is contained in:
sowle 2019-12-14 18:31:19 +03:00
parent 897b53cd93
commit 7138a70642
No known key found for this signature in database
GPG key ID: C07A24B2D89D49FC
4 changed files with 333 additions and 0 deletions

View file

@ -209,6 +209,7 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("scan_transfers_for_id", boost::bind(&simple_wallet::scan_transfers_for_id, this, _1), "Rescan transfers for tx_id");
m_cmd_binder.set_handler("scan_transfers_for_ki", boost::bind(&simple_wallet::scan_transfers_for_ki, this, _1), "Rescan transfers for key image");
m_cmd_binder.set_handler("print_utxo_distribution", boost::bind(&simple_wallet::print_utxo_distribution, this, _1), "Prints utxo distribution");
m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), "sweep_below <mixin_count> <address> <amount_lower_limit> [payment_id] - Tries to transfers all coins with amount below the given limit to the given address");
m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), "Show current wallet public address");
m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::integrated_address, this, _1), "integrated_address [<payment_id>|<integrated_address] - encodes given payment_id along with wallet's address into an integrated address (random payment_id will be used if none is provided). Decodes given integrated_address into standard address");
@ -1528,6 +1529,89 @@ bool simple_wallet::submit_transfer(const std::vector<std::string> &args)
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::sweep_below(const std::vector<std::string> &args)
{
bool r = false;
if (args.size() < 3 || args.size() > 4)
{
fail_msg_writer() << "invalid agruments count: " << args.size() << ", expected 3 or 4";
return true;
}
size_t fake_outs_count = 0;
if (!string_tools::get_xtype_from_string(fake_outs_count, args[0]))
{
fail_msg_writer() << "mixin_count should be non-negative integer, got " << args[0];
return true;
}
// parse payment_id
currency::payment_id_t payment_id;
if (args.size() == 4)
{
const std::string &payment_id_str = args.back();
r = parse_payment_id_from_hex_str(payment_id_str, payment_id);
if (!r)
{
fail_msg_writer() << "payment id has invalid format: \"" << payment_id_str << "\", expected hex string";
return true;
}
}
currency::account_public_address addr;
currency::payment_id_t integrated_payment_id;
if (!m_wallet->get_transfer_address(args[1], addr, integrated_payment_id))
{
fail_msg_writer() << "wrong address: " << args[1];
return true;
}
// handle integrated payment id
if (!integrated_payment_id.empty())
{
if (!payment_id.empty())
{
fail_msg_writer() << "address " << args[1] << " has integrated payment id " << epee::string_tools::buff_to_hex_nodelimer(integrated_payment_id) <<
" which is incompatible with payment id " << epee::string_tools::buff_to_hex_nodelimer(payment_id) << " that was already assigned to this transfer";
return true;
}
payment_id = integrated_payment_id; // remember integrated payment id as the main payment id
success_msg_writer() << "NOTE: using payment id " << epee::string_tools::buff_to_hex_nodelimer(payment_id) << " from integrated address " << args[1];
}
uint64_t amount = 0;
r = currency::parse_amount(amount, args[2]);
if (!r || amount == 0)
{
fail_msg_writer() << "incorrect amount: " << args[2];
return true;
}
try
{
uint64_t fee = m_wallet->get_core_runtime_config().tx_default_fee;
size_t outs_total = 0, outs_swept = 0;
uint64_t amount_total = 0, amount_swept = 0;
currency::transaction result_tx = AUTO_VAL_INIT(result_tx);
m_wallet->sweep_below(fake_outs_count, addr, amount, payment_id, fee, outs_total, amount_total, outs_swept, &result_tx);
if (!get_inputs_money_amount(result_tx, amount_swept))
LOG_ERROR("get_inputs_money_amount failed, tx: " << obj_to_json_str(result_tx));
success_msg_writer(false) << outs_swept << " outputs (" << print_money_brief(amount_swept) << " coins) of " << outs_total << " total (" << print_money_brief(amount_total)
<< ") below the specified limit of " << print_money_brief(amount) << " were successfully swept";
success_msg_writer(true) << "tx: " << get_transaction_hash(result_tx) << " size: " << get_object_blobsize(result_tx) << " bytes";
}
catch (const std::exception& e)
{
LOG_ERROR(e.what());
fail_msg_writer() << e.what();
return true;
}
return true;
}
//----------------------------------------------------------------------------------------------------
#ifdef WIN32
int wmain( int argc, wchar_t* argv_w[ ], wchar_t* envp[ ] )
#else

View file

@ -84,6 +84,7 @@ namespace currency
bool save_watch_only(const std::vector<std::string> &args);
bool sign_transfer(const std::vector<std::string> &args);
bool submit_transfer(const std::vector<std::string> &args);
bool sweep_below(const std::vector<std::string> &args);
bool get_alias_from_daemon(const std::string& alias_name, currency::extra_alias_entry_base& ai);
bool get_transfer_address(const std::string& adr_str, currency::account_public_address& addr);

View file

@ -4386,6 +4386,251 @@ void wallet2::transfer(const construct_tx_param& ctp,
print_tx_sent_message(tx, std::string() + "(transfer)", ctp.fee);
}
//----------------------------------------------------------------------------------------------------
void wallet2::sweep_below(size_t fake_outs_count, const currency::account_public_address& destination_addr, uint64_t threshold_amount, const currency::payment_id_t& payment_id,
uint64_t fee, size_t& outs_total, uint64_t& amount_total, size_t& outs_swept, currency::transaction* p_result_tx /* = nullptr */)
{
bool r = false;
outs_total = 0;
amount_total = 0;
outs_swept = 0;
std::vector<size_t> selected_transfers;
selected_transfers.reserve(m_transfers.size());
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
uint64_t amount = td.amount();
if (amount < threshold_amount &&
is_transfer_ready_to_go(td, fake_outs_count))
{
selected_transfers.push_back(i);
outs_total += 1;
amount_total += amount;
}
}
// sort by amount descending in order to spend bigger outputs first
std::sort(selected_transfers.begin(), selected_transfers.end(), [this](size_t a, size_t b) { return m_transfers[b].amount() < m_transfers[a].amount(); });
WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!selected_transfers.empty(), "No spendable outputs meet the criterion");
typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry;
typedef currency::tx_source_entry::output_entry tx_output_entry;
COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response rpc_get_random_outs_resp = AUTO_VAL_INIT(rpc_get_random_outs_resp);
if (fake_outs_count > 0)
{
COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req = AUTO_VAL_INIT(req);
req.use_forced_mix_outs = false;
req.outs_count = fake_outs_count + 1;
for (size_t i : selected_transfers)
req.amounts.push_back(m_transfers[i].amount());
r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(req, rpc_get_random_outs_resp);
THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs.bin");
THROW_IF_FALSE_WALLET_EX(rpc_get_random_outs_resp.status != CORE_RPC_STATUS_BUSY, error::daemon_busy, "getrandom_outs.bin");
THROW_IF_FALSE_WALLET_EX(rpc_get_random_outs_resp.status == CORE_RPC_STATUS_OK, error::get_random_outs_error, rpc_get_random_outs_resp.status);
WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(rpc_get_random_outs_resp.outs.size() == selected_transfers.size(),
"daemon returned wrong number of amounts for getrandom_outs.bin: " << rpc_get_random_outs_resp.outs.size() << ", requested: " << selected_transfers.size());
std::vector<COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount> scanty_outs;
for (COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_outs : rpc_get_random_outs_resp.outs)
{
if (amount_outs.outs.size() < fake_outs_count)
scanty_outs.push_back(amount_outs);
}
THROW_IF_FALSE_WALLET_EX(scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, fake_outs_count);
}
finalize_tx_param ftp = AUTO_VAL_INIT(ftp);
if (!payment_id.empty())
set_payment_id_to_tx(ftp.attachments, payment_id);
// put encrypted payer info into the extra
ftp.crypt_address = destination_addr;
currency::tx_payer txp = AUTO_VAL_INIT(txp);
txp.acc_addr = m_account.get_public_address();
ftp.extra.push_back(txp);
//ftp.flags;
//ftp.multisig_id;
ftp.prepared_destinations;
// ftp.selected_transfers -- needed only at stage of broadcasting or storing unsigned tx
ftp.shuffle = false;
// ftp.sources -- will be filled in try_construct_tx
ftp.spend_pub_key = m_account.get_public_address().m_spend_public_key; // needed for offline signing
ftp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED;
ftp.unlock_time = 0;
enum try_construct_result_t {rc_ok = 0, rc_too_few_outputs = 1, rc_too_many_outputs = 2, rc_create_tx_failed = 3 };
auto try_construct_tx = [this, &selected_transfers, &rpc_get_random_outs_resp, &fake_outs_count, &fee, &destination_addr]
(size_t st_index_upper_boundary, finalize_tx_param& ftp, uint64_t& amount_swept) -> try_construct_result_t
{
// prepare inputs
amount_swept = 0;
ftp.sources.clear();
ftp.sources.resize(st_index_upper_boundary);
WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(st_index_upper_boundary <= selected_transfers.size(), "index_upper_boundary = " << st_index_upper_boundary << ", selected_transfers.size() = " << selected_transfers.size());
for (size_t st_index = 0; st_index < st_index_upper_boundary; ++st_index)
{
currency::tx_source_entry& src = ftp.sources[st_index];
size_t tr_index = selected_transfers[st_index];
transfer_details& td = m_transfers[tr_index];
src.transfer_index = tr_index;
src.amount = td.amount();
amount_swept += src.amount;
// populate src.outputs with mix-ins
if (rpc_get_random_outs_resp.outs.size())
{
// TODO: is the folllowing line neccesary?
rpc_get_random_outs_resp.outs[st_index].outs.sort([](const out_entry& a, const out_entry& b) { return a.global_amount_index < b.global_amount_index; });
for (out_entry& daemon_oe : rpc_get_random_outs_resp.outs[st_index].outs)
{
if (td.m_global_output_index == daemon_oe.global_amount_index)
continue;
tx_output_entry oe;
oe.first = daemon_oe.global_amount_index;
oe.second = daemon_oe.out_key;
src.outputs.push_back(oe);
if (src.outputs.size() >= fake_outs_count)
break;
}
}
// insert real output into src.outputs
auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a)
{
if (a.first.type().hash_code() == typeid(uint64_t).hash_code())
return boost::get<uint64_t>(a.first) >= td.m_global_output_index;
return false; // TODO: implement deterministics real output placement in case there're ref_by_id outs
});
tx_output_entry real_oe;
real_oe.first = td.m_global_output_index;
real_oe.second = boost::get<txout_to_key>(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].target).key;
auto inserted_it = src.outputs.insert(it_to_insert, real_oe);
src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx);
src.real_output = inserted_it - src.outputs.begin();
src.real_output_in_tx_index = td.m_internal_output_index;
//detail::print_source_entry(src);
}
if (amount_swept <= fee)
return rc_too_few_outputs;
// try to construct a transaction
std::vector<currency::tx_destination_entry> dsts({ tx_destination_entry(amount_swept - fee, destination_addr) });
prepare_tx_destinations(0, 0, detail::ssi_digit, tools::tx_dust_policy(), dsts, ftp.prepared_destinations);
currency::transaction tx = AUTO_VAL_INIT(tx);
crypto::secret_key tx_key = AUTO_VAL_INIT(tx_key);
try
{
finalize_transaction(ftp, tx, tx_key, false, false);
}
catch (error::tx_too_big)
{
return rc_too_many_outputs;
}
catch (...)
{
return rc_create_tx_failed;
}
return rc_ok;
};
static const size_t estimated_bytes_per_input = 78;
const size_t estimated_max_inputs = static_cast<size_t>(CURRENCY_MAX_TRANSACTION_BLOB_SIZE / (estimated_bytes_per_input * (fake_outs_count + 1.5)));
size_t st_index_upper_boundary = std::min(selected_transfers.size(), estimated_max_inputs); // selected_transfers.size();
uint64_t amount_swept = 0;
try_construct_result_t res = try_construct_tx(st_index_upper_boundary, ftp, amount_swept);
WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(res != rc_too_few_outputs, st_index_upper_boundary << " biggest unspent outputs have total amount of " << print_money_brief(amount_swept)
<< " which is less than required fee: " << print_money_brief(fee) << ", transaction cannot be constructed");
if (res == rc_too_many_outputs)
{
// TODO: add logs
size_t low_bound = 0;
size_t high_bound = st_index_upper_boundary;
finalize_tx_param ftp_ok = ftp;
for (;;)
{
if (low_bound + 1 >= high_bound)
{
st_index_upper_boundary = low_bound;
res = rc_ok;
ftp = ftp_ok;
break;
}
st_index_upper_boundary = (low_bound + high_bound) / 2;
try_construct_result_t res = try_construct_tx(st_index_upper_boundary, ftp, amount_swept);
if (res == rc_ok)
{
low_bound = st_index_upper_boundary;
ftp_ok = ftp;
}
else if (res == rc_too_many_outputs)
{
high_bound = st_index_upper_boundary;
}
else
break;
}
}
if (res != rc_ok)
{
uint64_t amount_min = UINT64_MAX, amount_max = 0, amount_sum = 0;
for (auto& i : selected_transfers)
{
uint64_t amount = m_transfers[i].amount();
amount_min = std::min(amount_min, amount);
amount_max = std::max(amount_max, amount);
amount_sum += amount;
}
WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(false, "try_construct_tx failed with result: " << res <<
", selected_transfers stats:\n" <<
" outs: " << selected_transfers.size() << ENDL <<
" amount min: " << print_money(amount_min) << ENDL <<
" amount max: " << print_money(amount_max) << ENDL <<
" amount avg: " << (selected_transfers.empty() ? std::string("n/a") : print_money(amount_sum / selected_transfers.size())));
}
// populate ftp.selected_transfers from ftp.sources
ftp.selected_transfers.clear();
for (size_t i = 0; i < ftp.sources.size(); ++i)
ftp.selected_transfers.push_back(ftp.sources[i].transfer_index);
outs_swept = ftp.sources.size();
if (m_watch_only)
{
bool r = store_unsigned_tx_to_file_and_reserve_transfers(ftp, "zano_tx_unsigned");
WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store unsigned tx");
return;
}
mark_transfers_as_spent(ftp.selected_transfers, "sweep_below");
transaction local_tx;
transaction* p_tx = p_result_tx != nullptr ? p_result_tx : &local_tx;
*p_tx = AUTO_VAL_INIT_T(transaction);
try
{
crypto::secret_key sk = AUTO_VAL_INIT(sk);
finalize_transaction(ftp, *p_tx, sk, true);
}
catch (...)
{
clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception on sweep_below, tx id (might be wrong): ") + epee::string_tools::pod_to_hex(get_transaction_hash(*p_tx)));
throw;
}
}
} // namespace tools

View file

@ -622,6 +622,9 @@ namespace tools
void submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx);
void submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx);
void sweep_below(size_t fake_outs_count, const currency::account_public_address& destination_addr, uint64_t threshold_amount, const currency::payment_id_t& payment_id,
uint64_t fee, size_t& outs_total, uint64_t& amount_total, size_t& outs_swept, currency::transaction* p_result_tx = nullptr);
bool get_transfer_address(const std::string& adr_str, currency::account_public_address& addr, std::string& payment_id);
uint64_t get_blockchain_current_height() const { return m_blockchain.size(); }