wallet RPC converted to use new transaction semantics

wallet RPC now uses wallet2::create_transactions and wallet2::commit_tx instead
of wallet2::transfer.  This made it possible to add the RPC call /transfer_split, which
will split transactions automatically if they are too large.  The old call to
/transfer will return an error stating to use /transfer_split if multiple
transactions are needed to fulfill the request.
This commit is contained in:
Thomas Winget
2014-06-17 18:15:21 -04:00
parent 2011b50280
commit d433a696e5
7 changed files with 306 additions and 185 deletions

View File

@@ -786,169 +786,6 @@ bool simple_wallet::show_blockchain_height(const std::vector<std::string>& args)
return true;
}
// 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>> simple_wallet::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;
}
//----------------------------------------------------------------------------------------------------
// 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
void simple_wallet::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)
{
// for now, limit to 30 attempts. TODO: discuss a good number to limit to.
const size_t MAX_ATTEMPTS = 30;
// 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<tools::wallet2::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;
tools::wallet2::pending_tx ptx;
m_wallet->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(tools::wallet2::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(tools::wallet2::transfer_container::iterator it2, ptx.selected_transfers)
it2->m_spent = false;
}
// prompt user to confirm splits (if needed)
if (attempt_count > 1)
{
std::string prompt_str = "Your transaction needs to be split into ";
prompt_str += std::to_string(attempt_count);
prompt_str += " transactions. This will result in a fee of ";
prompt_str += print_money(attempt_count * DEFAULT_FEE);
prompt_str += ". Is this okay? (Y/Yes/N/No)";
std::string accepted = command_line::input_line(prompt_str);
if (accepted != "Y" && accepted != "y" && accepted != "Yes" && accepted != "yes")
{
fail_msg_writer() << "Transaction cancelled.";
return;
}
}
// if we made it this far, we're OK to actually send the transactions
while (!ptx_vector.empty())
{
auto & ptx = ptx_vector.back();
m_wallet->commit_tx(ptx);
success_msg_writer(true) << "Money successfully sent, transaction " << get_transaction_hash(ptx.tx);
// if no exception, remove element from vector
ptx_vector.pop_back();
}
return;
}
// 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(tools::wallet2::transfer_container::iterator it2, ptx.selected_transfers)
it2->m_spent = false;
}
if (attempt_count >= MAX_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(tools::wallet2::transfer_container::iterator it2, ptx.selected_transfers)
it2->m_spent = false;
}
throw;
}
}
//success_msg_writer(true) << "Money successfully sent, transaction " << get_transaction_hash(tx);
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::transfer(const std::vector<std::string> &args_)
{
@@ -1015,7 +852,38 @@ bool simple_wallet::transfer(const std::vector<std::string> &args_)
try
{
create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, DEFAULT_FEE, extra);
// figure out what tx will be necessary
auto ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, DEFAULT_FEE, extra);
// if more than one tx necessary, prompt user to confirm
if (ptx_vector.size() > 1)
{
std::string prompt_str = "Your transaction needs to be split into ";
prompt_str += std::to_string(ptx_vector.size());
prompt_str += " transactions. This will result in a fee of ";
prompt_str += print_money(ptx_vector.size() * DEFAULT_FEE);
prompt_str += ". Is this okay? (Y/Yes/N/No)";
std::string accepted = command_line::input_line(prompt_str);
if (accepted != "Y" && accepted != "y" && accepted != "Yes" && accepted != "yes")
{
fail_msg_writer() << "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;
}
}
// actually commit the transactions
while (!ptx_vector.empty())
{
auto & ptx = ptx_vector.back();
m_wallet->commit_tx(ptx);
success_msg_writer(true) << "Money successfully sent, transaction " << get_transaction_hash(ptx.tx);
// if no exception, remove element from vector
ptx_vector.pop_back();
}
}
catch (const tools::error::daemon_busy&)
{