diff options
Diffstat (limited to 'tests/functional_tests/transactions_flow_test.cpp')
-rw-r--r-- | tests/functional_tests/transactions_flow_test.cpp | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/tests/functional_tests/transactions_flow_test.cpp b/tests/functional_tests/transactions_flow_test.cpp new file mode 100644 index 000000000..af42f06a5 --- /dev/null +++ b/tests/functional_tests/transactions_flow_test.cpp @@ -0,0 +1,269 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <boost/uuid/uuid.hpp> +#include <boost/uuid/random_generator.hpp> +#include <unordered_map> + +#include "include_base_utils.h" +using namespace epee; +#include "wallet/wallet2.h" +using namespace cryptonote; + +std::string generate_random_wallet_name() +{ + std::stringstream ss; + ss << boost::uuids::random_generator()(); + return ss.str(); +} + +inline uint64_t random(const uint64_t max_value) { + return (uint64_t(rand()) ^ + (uint64_t(rand())<<16) ^ + (uint64_t(rand())<<32) ^ + (uint64_t(rand())<<48)) % max_value; +} + +bool do_send_money(tools::wallet2& w1, tools::wallet2& w2, size_t mix_in_factor, uint64_t amount_to_transfer, transaction& tx, size_t parts=1) +{ + CHECK_AND_ASSERT_MES(parts > 0, false, "parts must be > 0"); + + std::vector<cryptonote::tx_destination_entry> dsts; + dsts.reserve(parts); + uint64_t amount_used = 0; + uint64_t max_part = amount_to_transfer / parts; + + for (size_t i = 0; i < parts; ++i) + { + cryptonote::tx_destination_entry de; + de.addr = w2.get_account().get_keys().m_account_address; + + if (i < parts - 1) + de.amount = random(max_part); + else + de.amount = amount_to_transfer - amount_used; + amount_used += de.amount; + + //std::cout << "PARTS (" << amount_to_transfer << ") " << amount_used << " " << de.amount << std::endl; + + dsts.push_back(de); + } + + return w1.transfer(dsts, mix_in_factor, 0, DEFAULT_FEE, tools::detail::null_split_strategy, tools::wallet2::tx_dust_policy(DEFAULT_FEE), tx); +} + +uint64_t get_money_in_first_transfers(const tools::wallet2::transfer_container& incoming_transfers, size_t n_transfers) +{ + uint64_t summ = 0; + size_t count = 0; + BOOST_FOREACH(const tools::wallet2::transfer_details& td, incoming_transfers) + { + summ += td.m_tx.vout[td.m_internal_output_index].amount; + if(++count >= n_transfers) + return summ; + } + return summ; +} + +#define FIRST_N_TRANSFERS 10*10 + +bool transactions_flow_test(std::string& working_folder, + std::string path_source_wallet, + std::string path_terget_wallet, + std::string& daemon_addr_a, + std::string& daemon_addr_b, + uint64_t amount_to_transfer, size_t mix_in_factor, size_t transactions_count, size_t transactions_per_second) +{ + LOG_PRINT_L0("-----------------------STARTING TRANSACTIONS FLOW TEST-----------------------"); + tools::wallet2 w1, w2; + if(path_source_wallet.empty()) + path_source_wallet = generate_random_wallet_name(); + + if(path_terget_wallet.empty()) + path_terget_wallet = generate_random_wallet_name(); + + + if(!w1.generate(working_folder + "/" + path_source_wallet, "")) + { + LOG_ERROR("failed to load source wallet from " << path_source_wallet); + return false; + } + + if(!w2.generate(working_folder + "/" + path_terget_wallet, "")) + { + LOG_ERROR( "failed to generate target load wallet from " << path_source_wallet ); + return false; + } + + if(!w1.init(daemon_addr_a)) + { + LOG_ERROR("failed to init source wallet from " << daemon_addr_a ); + return false; + } + size_t blocks_fetched = 0; + if(!w1.refresh(blocks_fetched)) + { + LOG_ERROR( "failed to refresh source wallet from " << daemon_addr_a ); + return false; + } + + if(!w2.init(daemon_addr_b)) + { + LOG_ERROR( "failed to init target wallet from " << daemon_addr_b ); + return false; + } + LOG_PRINT_GREEN("Using wallets: " << ENDL + << "Source: " << w1.get_account().get_public_address_str() << ENDL << "Path: " << working_folder + "/" + path_source_wallet << ENDL + << "Target: " << w2.get_account().get_public_address_str() << ENDL << "Path: " << working_folder + "/" + path_terget_wallet, LOG_LEVEL_1); + + //lets do some money + epee::net_utils::http::http_simple_client http_client; + COMMAND_RPC_STOP_MINING::request daemon1_req = AUTO_VAL_INIT(daemon1_req); + COMMAND_RPC_STOP_MINING::response daemon1_rsp = AUTO_VAL_INIT(daemon1_rsp); + bool r = net_utils::invoke_http_json_remote_command2(daemon_addr_a + "/stop_mine", daemon1_req, daemon1_rsp, http_client, 10000); + CHECK_AND_ASSERT_MES(r, false, "failed to stop mining"); + + + COMMAND_RPC_START_MINING::request daemon_req = AUTO_VAL_INIT(daemon_req); + COMMAND_RPC_START_MINING::response daemon_rsp = AUTO_VAL_INIT(daemon_rsp); + daemon_req.miner_address = w1.get_account().get_public_address_str(); + daemon_req.threads_count = 9; + r = net_utils::invoke_http_json_remote_command2(daemon_addr_a + "/start_mining", daemon_req, daemon_rsp, http_client, 10000); + CHECK_AND_ASSERT_MES(r, false, "failed to get getrandom_outs"); + CHECK_AND_ASSERT_MES(daemon_rsp.status == CORE_RPC_STATUS_OK, false, "failed to getrandom_outs.bin"); + + //wait for money, until balance will have enough money + w1.refresh(blocks_fetched); + while(w1.unlocked_balance() < amount_to_transfer) + { + misc_utils::sleep_no_w(1000); + w1.refresh(blocks_fetched); + } + + //lets make a lot of small outs to ourselves + //since it is not possible to start from transaction that bigger than 20Kb, we gonna make transactions + //with 500 outs (about 18kb), and we have to wait appropriate count blocks, mined for test wallet + while(true) + { + tools::wallet2::transfer_container incoming_transfers; + w1.get_transfers(incoming_transfers); + if(incoming_transfers.size() > FIRST_N_TRANSFERS && get_money_in_first_transfers(incoming_transfers, FIRST_N_TRANSFERS) < w1.unlocked_balance() ) + { + //lets go! + size_t count = 0; + BOOST_FOREACH(tools::wallet2::transfer_details& td, incoming_transfers) + { + cryptonote::transaction tx_s; + bool r = do_send_money(w1, w1, 0, td.m_tx.vout[td.m_internal_output_index].amount - DEFAULT_FEE, tx_s, 50); + CHECK_AND_ASSERT_MES(r, false, "Failed to send starter tx " << get_transaction_hash(tx_s)); + LOG_PRINT_GREEN("Starter transaction sent " << get_transaction_hash(tx_s), LOG_LEVEL_0); + if(++count >= FIRST_N_TRANSFERS) + break; + } + break; + }else + { + misc_utils::sleep_no_w(1000); + w1.refresh(); + } + } + //do actual transfer + uint64_t transfered_money = 0; + uint64_t transfer_size = amount_to_transfer/transactions_count; + size_t i = 0; + struct tx_test_entry + { + transaction tx; + size_t m_received_count; + uint64_t amount_transfered; + }; + crypto::key_image lst_sent_ki = AUTO_VAL_INIT(lst_sent_ki); + std::unordered_map<crypto::hash, tx_test_entry> txs; + for(i = 0; i != transactions_count; i++) + { + uint64_t amount_to_tx = (amount_to_transfer - transfered_money) > transfer_size ? transfer_size: (amount_to_transfer - transfered_money); + while(w1.unlocked_balance() < amount_to_tx + DEFAULT_FEE) + { + misc_utils::sleep_no_w(1000); + LOG_PRINT_L0("not enough money, waiting for cashback or mining"); + w1.refresh(blocks_fetched); + } + + transaction tx; + /*size_t n_attempts = 0; + while (!do_send_money(w1, w2, mix_in_factor, amount_to_tx, tx)) { + n_attempts++; + std::cout << "failed to transfer money, refresh and try again (attempts=" << n_attempts << ")" << std::endl; + w1.refresh(); + }*/ + + + if(!do_send_money(w1, w2, mix_in_factor, amount_to_tx, tx)) + { + LOG_PRINT_L0("failed to transfer money, tx: " << get_transaction_hash(tx) << ", refresh and try again" ); + w1.refresh(blocks_fetched); + if(!do_send_money(w1, w2, mix_in_factor, amount_to_tx, tx)) + { + LOG_PRINT_L0( "failed to transfer money, second chance. tx: " << get_transaction_hash(tx) << ", exit" ); + LOCAL_ASSERT(false); + return false; + } + } + lst_sent_ki = boost::get<txin_to_key>(tx.vin[0]).k_image; + + transfered_money += amount_to_tx; + + LOG_PRINT_L0("transferred " << amount_to_tx << ", i=" << i ); + tx_test_entry& ent = txs[get_transaction_hash(tx)] = boost::value_initialized<tx_test_entry>(); + ent.amount_transfered = amount_to_tx; + ent.tx = tx; + //if(i % transactions_per_second) + // misc_utils::sleep_no_w(1000); + } + + + LOG_PRINT_L0( "waiting some new blocks..."); + misc_utils::sleep_no_w(DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN*20*1000);//wait two blocks before sync on another wallet on another daemon + LOG_PRINT_L0( "refreshing..."); + bool recvd_money = false; + while(w2.refresh(blocks_fetched, recvd_money) && ( (blocks_fetched && recvd_money) || !blocks_fetched ) ) + { + misc_utils::sleep_no_w(DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN*1000);//wait two blocks before sync on another wallet on another daemon + } + + uint64_t money_2 = w2.balance(); + if(money_2 == transfered_money) + { + LOG_PRINT_GREEN("-----------------------FINISHING TRANSACTIONS FLOW TEST OK-----------------------", LOG_LEVEL_0); + LOG_PRINT_GREEN("transferred " << print_money(transfered_money) << " via " << i << " transactions" , LOG_LEVEL_0); + return true; + }else + { + tools::wallet2::transfer_container tc; + w2.get_transfers(tc); + BOOST_FOREACH(tools::wallet2::transfer_details& td, tc) + { + auto it = txs.find(get_transaction_hash(td.m_tx)); + CHECK_AND_ASSERT_MES(it != txs.end(), false, "transaction not found in local cache"); + it->second.m_received_count += 1; + } + + BOOST_FOREACH(auto& tx_pair, txs) + { + if(tx_pair.second.m_received_count != 1) + { + LOG_PRINT_RED_L0("Transaction lost: " << get_transaction_hash(tx_pair.second.tx)); + } + + } + + LOG_PRINT_RED_L0("-----------------------FINISHING TRANSACTIONS FLOW TEST FAILED-----------------------" ); + LOG_PRINT_RED_L0("income " << print_money(money_2) << " via " << i << " transactions, expected money = " << print_money(transfered_money) ); + LOCAL_ASSERT(false); + return false; + } + + return true; +} + |