|
|
|
@ -43,6 +43,9 @@ void do_prepare_file_names(const std::string& file_path, std::string& keys_file,
|
|
|
|
|
|
|
|
|
|
namespace tools
|
|
|
|
|
{
|
|
|
|
|
// for now, limit to 30 attempts. TODO: discuss a good number to limit to.
|
|
|
|
|
const size_t MAX_SPLIT_ATTEMPTS = 30;
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
|
|
|
void wallet2::init(const std::string& daemon_address, uint64_t upper_transaction_size_limit)
|
|
|
|
|
{
|
|
|
|
@ -630,10 +633,17 @@ namespace
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
|
|
|
// Select random input sources for transaction.
|
|
|
|
|
// returns:
|
|
|
|
|
// direct return: amount of money found
|
|
|
|
|
// modified reference: selected_transfers, a list of iterators/indices of input sources
|
|
|
|
|
uint64_t wallet2::select_transfers(uint64_t needed_money, bool add_dust, uint64_t dust, std::list<transfer_container::iterator>& selected_transfers)
|
|
|
|
|
{
|
|
|
|
|
std::vector<size_t> unused_transfers_indices;
|
|
|
|
|
std::vector<size_t> unused_dust_indices;
|
|
|
|
|
|
|
|
|
|
// aggregate sources available for transfers
|
|
|
|
|
// if dust needed, take dust from only one source (so require source has at least dust amount)
|
|
|
|
|
for (size_t i = 0; i < m_transfers.size(); ++i)
|
|
|
|
|
{
|
|
|
|
|
const transfer_details& td = m_transfers[i];
|
|
|
|
@ -678,16 +688,188 @@ void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t cha
|
|
|
|
|
}
|
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
|
|
|
void wallet2::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)
|
|
|
|
|
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, cryptonote::transaction& tx, pending_tx& ptx)
|
|
|
|
|
{
|
|
|
|
|
transfer(dsts, fake_outputs_count, unlock_time, fee, extra, detail::digit_split_strategy, tx_dust_policy(fee), tx);
|
|
|
|
|
transfer(dsts, fake_outputs_count, unlock_time, fee, extra, detail::digit_split_strategy, tx_dust_policy(fee), tx, ptx);
|
|
|
|
|
}
|
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
|
|
|
void wallet2::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;
|
|
|
|
|
transfer(dsts, fake_outputs_count, unlock_time, fee, extra, tx);
|
|
|
|
|
pending_tx ptx;
|
|
|
|
|
transfer(dsts, fake_outputs_count, unlock_time, fee, extra, tx, ptx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
// split_amounts(vector<cryptonote::tx_destination_entry> dsts, size_t num_splits)
|
|
|
|
|
//
|
|
|
|
|
// split amount for each dst in dsts into num_splits parts
|
|
|
|
|
// and make num_splits new vector<crypt...> instances to hold these new amounts
|
|
|
|
|
std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts(
|
|
|
|
|
std::vector<cryptonote::tx_destination_entry> dsts, size_t num_splits)
|
|
|
|
|
{
|
|
|
|
|
std::vector<std::vector<cryptonote::tx_destination_entry>> retVal;
|
|
|
|
|
|
|
|
|
|
if (num_splits <= 1)
|
|
|
|
|
{
|
|
|
|
|
retVal.push_back(dsts);
|
|
|
|
|
return retVal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// for each split required
|
|
|
|
|
for (size_t i=0; i < num_splits; i++)
|
|
|
|
|
{
|
|
|
|
|
std::vector<cryptonote::tx_destination_entry> new_dsts;
|
|
|
|
|
|
|
|
|
|
// for each destination
|
|
|
|
|
for (size_t j=0; j < dsts.size(); j++)
|
|
|
|
|
{
|
|
|
|
|
cryptonote::tx_destination_entry de;
|
|
|
|
|
uint64_t amount;
|
|
|
|
|
|
|
|
|
|
amount = dsts[j].amount;
|
|
|
|
|
amount = amount / num_splits;
|
|
|
|
|
|
|
|
|
|
// if last split, add remainder
|
|
|
|
|
if (i + 1 == num_splits)
|
|
|
|
|
{
|
|
|
|
|
amount += dsts[j].amount % num_splits;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
de.addr = dsts[j].addr;
|
|
|
|
|
de.amount = amount;
|
|
|
|
|
|
|
|
|
|
new_dsts.push_back(de);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
retVal.push_back(new_dsts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return retVal;
|
|
|
|
|
}
|
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
|
|
|
// take a pending tx and actually send it to the daemon
|
|
|
|
|
void wallet2::commit_tx(pending_tx& ptx)
|
|
|
|
|
{
|
|
|
|
|
using namespace cryptonote;
|
|
|
|
|
COMMAND_RPC_SEND_RAW_TX::request req;
|
|
|
|
|
req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ptx.tx));
|
|
|
|
|
COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp;
|
|
|
|
|
bool r = epee::net_utils::invoke_http_json_remote_command2(m_daemon_address + "/sendrawtransaction", req, daemon_send_resp, m_http_client, 200000);
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "sendrawtransaction");
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "sendrawtransaction");
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(daemon_send_resp.status != CORE_RPC_STATUS_OK, error::tx_rejected, ptx.tx, daemon_send_resp.status);
|
|
|
|
|
|
|
|
|
|
add_unconfirmed_tx(ptx.tx, ptx.change_dts.amount);
|
|
|
|
|
|
|
|
|
|
LOG_PRINT_L2("transaction " << get_transaction_hash(ptx.tx) << " generated ok and sent to daemon, key_images: [" << ptx.key_images << "]");
|
|
|
|
|
|
|
|
|
|
BOOST_FOREACH(transfer_container::iterator it, ptx.selected_transfers)
|
|
|
|
|
it->m_spent = true;
|
|
|
|
|
|
|
|
|
|
LOG_PRINT_L0("Transaction successfully sent. <" << get_transaction_hash(ptx.tx) << ">" << ENDL
|
|
|
|
|
<< "Commission: " << print_money(ptx.fee+ptx.dust) << " (dust: " << print_money(ptx.dust) << ")" << ENDL
|
|
|
|
|
<< "Balance: " << print_money(balance()) << ENDL
|
|
|
|
|
<< "Unlocked: " << print_money(unlocked_balance()) << ENDL
|
|
|
|
|
<< "Please, wait for confirmation for your balance to be unlocked.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void wallet2::commit_tx(std::vector<pending_tx>& ptx_vector)
|
|
|
|
|
{
|
|
|
|
|
for (auto & ptx : ptx_vector)
|
|
|
|
|
{
|
|
|
|
|
commit_tx(ptx);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------
|
|
|
|
|
// separated the call(s) to wallet2::transfer into their own function
|
|
|
|
|
//
|
|
|
|
|
// this function will make multiple calls to wallet2::transfer if multiple
|
|
|
|
|
// transactions will be required
|
|
|
|
|
std::vector<wallet2::pending_tx> wallet2::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)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
// failsafe split attempt counter
|
|
|
|
|
size_t attempt_count = 0;
|
|
|
|
|
|
|
|
|
|
for(attempt_count = 1; ;attempt_count++)
|
|
|
|
|
{
|
|
|
|
|
auto split_values = split_amounts(dsts, attempt_count);
|
|
|
|
|
|
|
|
|
|
// Throw if split_amounts comes back with a vector of size different than it should
|
|
|
|
|
if (split_values.size() != attempt_count)
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error("Splitting transactions returned a number of potential tx not equal to what was requested");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::vector<pending_tx> ptx_vector;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// for each new destination vector (i.e. for each new tx)
|
|
|
|
|
for (auto & dst_vector : split_values)
|
|
|
|
|
{
|
|
|
|
|
cryptonote::transaction tx;
|
|
|
|
|
pending_tx ptx;
|
|
|
|
|
transfer(dst_vector, fake_outs_count, unlock_time, fee, extra, tx, ptx);
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|