From 2e048a467976f6f620a3e9f55d26744139456147 Mon Sep 17 00:00:00 2001 From: Thomas Winget Date: Sun, 15 Jun 2014 20:36:44 -0400 Subject: [PATCH] final changes to get transaction splitting building. needs testing. --- src/simplewallet/simplewallet.cpp | 92 ++++++++++++++++--- src/simplewallet/simplewallet.h | 6 +- src/wallet/wallet2.cpp | 33 ++++++- src/wallet/wallet2.h | 46 +++++----- src/wallet/wallet_rpc_server.cpp | 3 +- .../transactions_flow_test.cpp | 4 +- 6 files changed, 140 insertions(+), 44 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 6ef3ec214..a54d92a41 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -790,10 +790,10 @@ bool simple_wallet::show_blockchain_height(const std::vector& args) // // split amount for each dst in dsts into num_splits parts // and make num_splits new vector instances to hold these new amounts -vector> simple_wallet::split_amounts( - vector dsts, size_t num_splits) +std::vector> simple_wallet::split_amounts( + std::vector dsts, size_t num_splits) { - vector> retVal; + std::vector> retVal; if (num_splits <= 1) { @@ -804,7 +804,7 @@ vector> simple_wallet::split_amounts( // for each split required for (size_t i=0; i < num_splits; i++) { - vector new_dsts; + std::vector new_dsts; // for each destination for (size_t j=0; j < dsts.size(); j++) @@ -836,33 +836,94 @@ vector> simple_wallet::split_amounts( // // this function will make multiple calls to wallet2::transfer if multiple // transactions will be required -void simple_wallet::create_transactions(vector dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee, const std::vector extra) +void simple_wallet::create_transactions(std::vector dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee, const std::vector extra) { + // for now, limit to 5 attempts. TODO: discuss a good number to limit to. + const size_t MAX_ATTEMPTS = 5; + // failsafe split attempt counter size_t attempt_count = 0; - for(attempt_count = 1;;attempt_count++) + for(attempt_count = 1; attempt_count <= 5 ;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 ptx_vector; try { - vector tx_vector; for (size_t i=0; i < attempt_count; i++) { cryptonote::transaction tx; - m_wallet->transfer(dsts, fake_outs_count, 0, DEFAULT_FEE, extra, tx); - tx_vector.push_back(tx); + tools::wallet2::pending_tx ptx; + m_wallet->transfer(dsts, 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; + + } + // if we made it this far, we're OK to actually send the transactions + while (!ptx_vector.empty()) + { + m_wallet->commit_tx(ptx_vector.back()); + // if no exception, remove element from vector + ptx_vector.pop_back(); + } + } - success_msg_writer(true) << "Money successfully sent, transaction " << get_transaction_hash(tx); // only catch this here, other exceptions need to pass through to the calling function 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"; + + // 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); } //---------------------------------------------------------------------------------------------------- @@ -931,6 +992,7 @@ bool simple_wallet::transfer(const std::vector &args_) try { + create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, DEFAULT_FEE, extra); } catch (const tools::error::daemon_busy&) { @@ -980,6 +1042,10 @@ bool simple_wallet::transfer(const std::vector &args_) { 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 2f0d17313..8a6ac389a 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -54,10 +54,10 @@ namespace cryptonote bool show_payments(const std::vector &args); bool show_blockchain_height(const std::vector &args); bool transfer(const std::vector &args); - vector> simple_wallet::split_amounts( - vector dsts, size_t num_splits + std::vector> split_amounts( + std::vector dsts, size_t num_splits ); - void create_transactions(vector dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee, const std::vector extra); + void create_transactions(std::vector dsts, const size_t fake_outs_count, const uint64_t unlock_time, const uint64_t fee, const std::vector extra); bool print_address(const std::vector &args = std::vector()); bool save(const std::vector &args); bool set_log(const std::vector &args); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index fb4673f00..7dfbc7f7f 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -685,16 +685,43 @@ void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t cha } //---------------------------------------------------------------------------------------------------- void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, - uint64_t unlock_time, uint64_t fee, const std::vector& extra, cryptonote::transaction& tx) + uint64_t unlock_time, uint64_t fee, const std::vector& 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& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& 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); } //---------------------------------------------------------------------------------------------------- +// 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."); +} + } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 64b8b337b..e8b9ec46e 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -86,6 +86,15 @@ namespace tools typedef std::vector transfer_container; typedef std::unordered_multimap payment_container; + struct pending_tx + { + cryptonote::transaction tx; + uint64_t dust, fee; + cryptonote::tx_destination_entry change_dts; + std::list selected_transfers; + std::string key_images; + }; + struct keys_file_data { crypto::chacha8_iv iv; @@ -125,9 +134,10 @@ namespace tools template void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy); template - void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction &tx); + void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx& ptx); void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra); - void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, cryptonote::transaction& tx); + void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, cryptonote::transaction& tx, pending_tx& ptx); + void commit_tx(pending_tx& ptx); bool check_connection(); void get_transfers(wallet2::transfer_container& incoming_transfers) const; void get_payments(const crypto::hash& payment_id, std::list& payments) const; @@ -295,13 +305,14 @@ namespace tools void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& 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 void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, - uint64_t unlock_time, uint64_t fee, const std::vector& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction &tx) + uint64_t unlock_time, uint64_t fee, const std::vector& 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 @@ -432,25 +443,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..95c71fc68 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -132,7 +132,8 @@ namespace tools try { cryptonote::transaction tx; - m_wallet.transfer(dsts, req.mixin, req.unlock_time, req.fee, extra, tx); + wallet2::pending_tx ptx; + m_wallet.transfer(dsts, req.mixin, req.unlock_time, req.fee, extra, tx, ptx); res.tx_hash = boost::lexical_cast(cryptonote::get_transaction_hash(tx)); return true; } 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(), 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(), tools::detail::null_split_strategy, tools::tx_dust_policy(DEFAULT_FEE), tx, ptx); + w1.commit_tx(ptx); return true; } catch (const std::exception&)