aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Winget <tewinget@gmail.com>2014-07-06 13:13:06 -0400
committerThomas Winget <tewinget@gmail.com>2014-07-06 13:13:06 -0400
commitc6ffd810aff30c321a411f47544584ab6522713f (patch)
tree850d6b51e86f9c91221f67b6acd59ff158115d87
parentMerge pull request #55 from monero-project/revert-54-master (diff)
parentneeded to remove REQUIRED from find_package(Threads) (diff)
downloadmonero-c6ffd810aff30c321a411f47544584ab6522713f.tar.xz
Merge pull request #56 from tewinget/master
transaction splitting
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/simplewallet/simplewallet.cpp46
-rw-r--r--src/simplewallet/simplewallet.h3
-rw-r--r--src/wallet/wallet2.cpp188
-rw-r--r--src/wallet/wallet2.h53
-rw-r--r--src/wallet/wallet_rpc_server.cpp93
-rw-r--r--src/wallet/wallet_rpc_server.h5
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h (renamed from src/wallet/wallet_rpc_server_commans_defs.h)33
-rw-r--r--tests/functional_tests/transactions_flow_test.cpp4
9 files changed, 378 insertions, 49 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6f084ba22..c5a7220e9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -23,7 +23,7 @@ set(STATIC ${MSVC} CACHE BOOL "Link libraries statically")
if (UNIX AND NOT APPLE)
# Note that at the time of this writing the -Wstrict-prototypes flag added below will make this fail
- find_package(Threads REQUIRED)
+ find_package(Threads)
endif()
if(MSVC)
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 6af0de9f9..8ceb583be 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -785,6 +785,7 @@ bool simple_wallet::show_blockchain_height(const std::vector<std::string>& args)
fail_msg_writer() << "failed to get blockchain height: " << err;
return true;
}
+
//----------------------------------------------------------------------------------------------------
bool simple_wallet::transfer(const std::vector<std::string> &args_)
{
@@ -851,9 +852,38 @@ bool simple_wallet::transfer(const std::vector<std::string> &args_)
try
{
- cryptonote::transaction tx;
- m_wallet->transfer(dsts, fake_outs_count, 0, DEFAULT_FEE, extra, tx);
- success_msg_writer(true) << "Money successfully sent, transaction " << get_transaction_hash(tx);
+ // 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&)
{
@@ -899,16 +929,14 @@ bool simple_wallet::transfer(const std::vector<std::string> &args_)
{
fail_msg_writer() << e.what();
}
- catch (const tools::error::tx_too_big& e)
- {
- cryptonote::transaction tx = e.tx();
- fail_msg_writer() << "transaction " << get_transaction_hash(e.tx()) << " is too big. Transaction size: " <<
- get_object_blobsize(e.tx()) << " bytes, transaction size limit: " << e.tx_size_limit() << " bytes";
- }
catch (const tools::error::zero_destination&)
{
fail_msg_writer() << "one of destinations is zero";
}
+ catch (const tools::error::tx_too_big& e)
+ {
+ fail_msg_writer() << "Failed to find a suitable way to split transactions";
+ }
catch (const tools::error::transfer_error& e)
{
LOG_ERROR("unknown transfer error: " << e.to_string());
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index 006cdd08e..e919cfeda 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -54,6 +54,9 @@ namespace cryptonote
bool show_payments(const std::vector<std::string> &args);
bool show_blockchain_height(const std::vector<std::string> &args);
bool transfer(const std::vector<std::string> &args);
+ std::vector<std::vector<cryptonote::tx_destination_entry>> split_amounts(
+ std::vector<cryptonote::tx_destination_entry> dsts, size_t num_splits
+ );
bool print_address(const std::vector<std::string> &args = std::vector<std::string>());
bool save(const std::vector<std::string> &args);
bool set_log(const std::vector<std::string> &args);
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index a63eb4be7..5b284c619 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -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;
+ }
+ }
+}
}
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 534a0517b..1f5ae7062 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -23,6 +23,7 @@
#include "wallet_errors.h"
+#include <iostream>
#define DEFAULT_TX_SPENDABLE_AGE 10
#define WALLET_RCP_CONNECTION_TIMEOUT 200000
@@ -86,6 +87,15 @@ namespace tools
typedef std::vector<transfer_details> transfer_container;
typedef std::unordered_multimap<crypto::hash, payment_details> payment_container;
+ struct pending_tx
+ {
+ cryptonote::transaction tx;
+ uint64_t dust, fee;
+ cryptonote::tx_destination_entry change_dts;
+ std::list<transfer_container::iterator> selected_transfers;
+ std::string key_images;
+ };
+
struct keys_file_data
{
crypto::chacha8_iv iv;
@@ -125,9 +135,12 @@ namespace tools
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);
+ 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);
+ 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);
+ 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);
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) const;
@@ -295,18 +308,23 @@ namespace tools
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, T destination_split_strategy, const tx_dust_policy& dust_policy)
{
+ pending_tx ptx;
cryptonote::transaction tx;
- transfer(dsts, fake_outputs_count, unlock_time, fee, extra, destination_split_strategy, dust_policy, tx);
+ transfer(dsts, fake_outputs_count, unlock_time, fee, extra, destination_split_strategy, dust_policy, tx, ptx);
}
template<typename T>
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, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction &tx)
+ 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)
{
using namespace cryptonote;
+ // throw if attempting a transaction with no destinations
THROW_WALLET_EXCEPTION_IF(dsts.empty(), error::zero_destination);
uint64_t needed_money = fee;
+
+ // calculate total amount being sent to all destinations
+ // throw if total amount overflows uint64_t
BOOST_FOREACH(auto& dt, dsts)
{
THROW_WALLET_EXCEPTION_IF(0 == dt.amount, error::zero_destination);
@@ -314,6 +332,8 @@ namespace tools
THROW_WALLET_EXCEPTION_IF(needed_money < dt.amount, error::tx_sum_overflow, dsts, fee);
}
+ // randomly select inputs for transaction
+ // throw if requested send amount is greater than amount available to send
std::list<transfer_container::iterator> selected_transfers;
uint64_t found_money = select_transfers(needed_money, 0 == fake_outputs_count, dust_policy.dust_threshold, selected_transfers);
THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee);
@@ -426,25 +446,14 @@ namespace tools
});
THROW_WALLET_EXCEPTION_IF(!all_are_txin_to_key, error::unexpected_txin_type, tx);
- COMMAND_RPC_SEND_RAW_TX::request req;
- req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx));
- COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp;
- 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, tx, daemon_send_resp.status);
+ ptx.key_images = key_images;
+ ptx.fee = fee;
+ ptx.dust = dust;
+ ptx.tx = tx;
+ ptx.change_dts = change_dts;
+ ptx.selected_transfers = selected_transfers;
- add_unconfirmed_tx(tx, change_dts.amount);
-
- LOG_PRINT_L2("transaction " << get_transaction_hash(tx) << " generated ok and sent to daemon, key_images: [" << key_images << "]");
+ }
- BOOST_FOREACH(transfer_container::iterator it, selected_transfers)
- it->m_spent = true;
- LOG_PRINT_L0("Transaction successfully sent. <" << get_transaction_hash(tx) << ">" << ENDL
- << "Commission: " << print_money(fee+dust) << " (dust: " << print_money(dust) << ")" << ENDL
- << "Balance: " << print_money(balance()) << ENDL
- << "Unlocked: " << print_money(unlocked_balance()) << ENDL
- << "Please, wait for confirmation for your balance to be unlocked.");
- }
}
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index a0816946b..ec31d4625 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -10,6 +10,7 @@ using namespace epee;
#include "common/command_line.h"
#include "cryptonote_core/cryptonote_format_utils.h"
#include "cryptonote_core/account.h"
+#include "wallet_rpc_server_commands_defs.h"
#include "misc_language.h"
#include "string_tools.h"
#include "crypto/hash.h"
@@ -85,12 +86,11 @@ namespace tools
}
return true;
}
+
//------------------------------------------------------------------------------------------------------------------------------
- bool wallet_rpc_server::on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx)
+ bool wallet_rpc_server::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)
{
-
- std::vector<cryptonote::tx_destination_entry> dsts;
- for (auto it = req.destinations.begin(); it != req.destinations.end(); it++)
+ for (auto it = destinations.begin(); it != destinations.end(); it++)
{
cryptonote::tx_destination_entry de;
if(!get_account_address_from_str(de.addr, it->address))
@@ -103,11 +103,11 @@ namespace tools
dsts.push_back(de);
}
- std::vector<uint8_t> extra;
- if (!req.payment_id.empty()) {
+ if (!payment_id.empty())
+ {
/* Just to clarify */
- const std::string& payment_id_str = req.payment_id;
+ const std::string& payment_id_str = payment_id;
crypto::hash payment_id;
/* Parse payment ID */
@@ -128,12 +128,85 @@ namespace tools
}
}
+ return true;
+ }
+
+ //------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx)
+ {
+
+ std::vector<cryptonote::tx_destination_entry> dsts;
+ std::vector<uint8_t> extra;
+
+ // validate the transfer requested and populate dsts & extra
+ if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, er))
+ {
+ return false;
+ }
+
+ try
+ {
+ std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_transactions(dsts, req.mixin, req.unlock_time, req.fee, extra);
+
+ // reject proposed transactions if there are more than one. see on_transfer_split below.
+ if (ptx_vector.size() != 1)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR;
+ er.message = "Transaction would be too large. try /transfer_split.";
+ return false;
+ }
+
+ m_wallet.commit_tx(ptx_vector);
+
+ // populate response with tx hash
+ res.tx_hash = boost::lexical_cast<std::string>(cryptonote::get_transaction_hash(ptx_vector.back().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_transfer_split(const wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::request& req, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT::response& res, epee::json_rpc::error& er, connection_context& cntx)
+ {
+
+ std::vector<cryptonote::tx_destination_entry> dsts;
+ std::vector<uint8_t> extra;
+
+ // validate the transfer requested and populate dsts & extra; RPC_TRANSFER::request and RPC_TRANSFER_SPLIT::request are identical types.
+ if (!validate_transfer(req.destinations, req.payment_id, dsts, extra, er))
+ {
+ return false;
+ }
try
{
- cryptonote::transaction tx;
- m_wallet.transfer(dsts, req.mixin, req.unlock_time, req.fee, extra, tx);
- res.tx_hash = boost::lexical_cast<std::string>(cryptonote::get_transaction_hash(tx));
+ std::vector<wallet2::pending_tx> ptx_vector = m_wallet.create_transactions(dsts, req.mixin, req.unlock_time, req.fee, extra);
+
+ 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)
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index b8373d27d..f9893566a 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -7,7 +7,7 @@
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include "net/http_server_impl_base.h"
-#include "wallet_rpc_server_commans_defs.h"
+#include "wallet_rpc_server_commands_defs.h"
#include "wallet2.h"
#include "common/command_line.h"
namespace tools
@@ -38,6 +38,7 @@ namespace tools
MAP_JON_RPC_WE("getbalance", on_getbalance, wallet_rpc::COMMAND_RPC_GET_BALANCE)
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("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("incoming_transfers", on_incoming_transfers, wallet_rpc::COMMAND_RPC_INCOMING_TRANSFERS)
@@ -47,7 +48,9 @@ namespace tools
//json_rpc
bool on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_getaddress(const wallet_rpc::COMMAND_RPC_GET_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS::response& res, epee::json_rpc::error& er, connection_context& cntx);
+ 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, connection_context& cntx);
+ 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, connection_context& cntx);
bool on_store(const wallet_rpc::COMMAND_RPC_STORE::request& req, wallet_rpc::COMMAND_RPC_STORE::response& res, epee::json_rpc::error& er, connection_context& cntx);
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, connection_context& cntx);
bool on_incoming_transfers(const wallet_rpc::COMMAND_RPC_INCOMING_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_INCOMING_TRANSFERS::response& res, epee::json_rpc::error& er, connection_context& cntx);
diff --git a/src/wallet/wallet_rpc_server_commans_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index 7ffbcfc18..33130da06 100644
--- a/src/wallet/wallet_rpc_server_commans_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -52,7 +52,7 @@ namespace wallet_rpc
};
};
- struct trnsfer_destination
+ struct transfer_destination
{
uint64_t amount;
std::string address;
@@ -66,7 +66,7 @@ namespace wallet_rpc
{
struct request
{
- std::list<trnsfer_destination> destinations;
+ std::list<transfer_destination> destinations;
uint64_t fee;
uint64_t mixin;
uint64_t unlock_time;
@@ -91,6 +91,35 @@ namespace wallet_rpc
};
};
+ struct COMMAND_RPC_TRANSFER_SPLIT
+ {
+ struct request
+ {
+ std::list<transfer_destination> destinations;
+ uint64_t fee;
+ uint64_t mixin;
+ uint64_t unlock_time;
+ std::string payment_id;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(destinations)
+ KV_SERIALIZE(fee)
+ KV_SERIALIZE(mixin)
+ KV_SERIALIZE(unlock_time)
+ KV_SERIALIZE(payment_id)
+ 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
diff --git a/tests/functional_tests/transactions_flow_test.cpp b/tests/functional_tests/transactions_flow_test.cpp
index 4c6d5b07f..17cfc23c4 100644
--- a/tests/functional_tests/transactions_flow_test.cpp
+++ b/tests/functional_tests/transactions_flow_test.cpp
@@ -52,7 +52,9 @@ bool do_send_money(tools::wallet2& w1, tools::wallet2& w2, size_t mix_in_factor,
try
{
- w1.transfer(dsts, mix_in_factor, 0, DEFAULT_FEE, std::vector<uint8_t>(), tools::detail::null_split_strategy, tools::tx_dust_policy(DEFAULT_FEE), tx);
+ tools::wallet2::pending_tx ptx;
+ w1.transfer(dsts, mix_in_factor, 0, DEFAULT_FEE, std::vector<uint8_t>(), tools::detail::null_split_strategy, tools::tx_dust_policy(DEFAULT_FEE), tx, ptx);
+ w1.commit_tx(ptx);
return true;
}
catch (const std::exception&)