// Copyright (c) 2014-2018 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // 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 #include #include #include "transactions_flow_test.h" #include "include_base_utils.h" using namespace epee; #include "console_handler.h" #include "storages/http_abstract_invoke.h" #include "wallet/wallet2.h" #include "string_coding.h" #include "math_helper.h" #include "common/variant_helper.h" using namespace currency; namespace ph = boost::placeholders; #define TESTS_DEFAULT_FEE TX_DEFAULT_FEE std::string get_random_hex_text(size_t len) { std::string buff(len / 2, 0); crypto::generate_random_bytes(len / 2, (void*)buff.data()); return string_tools::buff_to_hex_nodelimer(buff); } std::wstring generate_random_wallet_name() { std::wstringstream 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 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) { currency::tx_destination_entry de; de.addr.push_back(w2.get_account().get_keys().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); } try { uint64_t ticks = epee::misc_utils::get_tick_count(); w1.transfer(dsts, mix_in_factor, 0, w1.get_core_runtime_config().tx_default_fee, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(w1.get_core_runtime_config().tx_default_fee), tx); uint64_t ticks_for_tx = epee::misc_utils::get_tick_count() - ticks; LOG_PRINT_GREEN("Send tx took " << ticks_for_tx << "ms", LOG_LEVEL_0); return true; } catch (const std::exception& e) { LOG_PRINT_RED("Failed to send tx: " << e.what(), LOG_LEVEL_0); return false; } } bool do_register_alias(tools::wallet2& w1, const std::string& alias_name, const std::string& comment, const account_public_address& address, transaction& tx) { try { uint64_t ticks = epee::misc_utils::get_tick_count(); currency::extra_alias_entry eae = AUTO_VAL_INIT(eae); eae.m_address = address; eae.m_alias = alias_name; eae.m_text_comment = comment; w1.request_alias_registration(eae, tx, TX_DEFAULT_FEE, currency::get_alias_coast_from_fee(eae.m_alias, TX_DEFAULT_FEE*10)); uint64_t ticks_for_tx = epee::misc_utils::get_tick_count() - ticks; LOG_PRINT_GREEN("Send alias_registration_tx took " << ticks_for_tx << "ms", LOG_LEVEL_0); return true; } catch (const std::exception& e) { LOG_PRINT_RED("Failed to send tx(alias): " << e.what(), LOG_LEVEL_0); return false; } } bool do_register_offer(tools::wallet2& w1, transaction& tx) { const char* currencies[] = { "USD", "EUR", "CNY", "INR", "BRR", "GBP", "CHF", "JPY", "RUR", "BTC", "AUD", "CAD", "SGD", "MYR", "AZN", "ALL", "DZD", "AOA", "ARS", "AMD", "AWG", "AFA", "BSD", "BDT", "BBD", "BHD", "BZD", "BYB", "BGL", "BOB", "BWP", "BND", "BMD", "BIF", "VUV", "HUF", "VEB", "XCD", "VND", "HTG", "GMD", "GHC", "GTQ", "GNF", "GIP", "HML", "GEL", "ANG", "DKK", "RSD", "AED", "STD", "ZWD", "KYD", "SBD", "TTD", "FJD", "DOP", "EGP", "ZMK", "NIO", "ILS", "IDR", "JOD", "IQD", "IRR", "IEP", "ISK", "YER", "KHR", "QAR", "KES", "PGK", "CYP", "COP", "KMF", "BAM", "CRC", "CUP", "KWD", "HRK", "KGS", "MMK", "LAK", "LVL", "SLL", "LRD", "LBP", "LYD", "LTL", "LSL", "MUR", "MRO", "MKD", "MWK", "MGF", "MVR", "MTL", "MAD", "MXN", "MZM", "MDL", "MNT", "ERN", "BTN", "NPR", "NGN", "NZD", "PEN", "TWD", "NOK", "OMR", "TOP", "PKR", "PYG", "PLZ", "ROL", "SVC", "WST", "SAR", "SZL", "KPW", "SCR", "SYP", "SOS", "SDD", "SRG", "THB", "TZS", "KZT", "SIT", "TND", "TRY", "TMM", "UGS", "UZS", "UAH", "UYP", "PHP", "DJF", "XOF", "XAF", "XOP", "XPF", "RWF", "FKP", "CZK", "CLP", "SEK", "LKR", "ESC", "CVE", "EEK", "ETB", "ZAR", "KRW", "JMD" }; const char* countries[] = { "ZD", "AF", "AX", "AL", "DZ", "AS", "AD", "AO", "AI", "AG", "AR", "AM", "AW", "AU", "AT", "AZ", "BS", "BH", "BD", "BB", "BY", "BE", "BZ", "BJ", "BM", "BT", "BO", "BQ", "BA", "BW", "BV", "BR", "IO", "UM", "VG", "BN", "BG", "BF", "BI", "KH", "CM", "CA", "CV", "KY", "CF", "TD", "CL", "CN", "CX", "CC", "CO", "KM", "CG", "CD", "CK", "CR", "HR", "CU", "CW", "CY", "CZ", "DK", "DJ", "DM", "DO", "EC", "EG", "SV", "GQ", "ER", "EE", "ET", "FK", "FO", "FJ", "FI", "FR", "GF", "PF", "TF", "GA", "GM", "GE", "DE", "GH", "GI", "GR", "GL", "GD", "GP", "GU", "GT", "GG", "GN", "GW", "GY", "HT", "HM", "HN", "HK", "HU", "IS", "IN", "ID", "CI", "IR", "IQ", "IE", "IM", "IL", "IT", "JM", "JP", "JE", "JO", "KZ", "KE", "KI", "KW", "KG", "LA", "LV", "LB", "LS", "LR", "ZF", "LY", "LI", "LT", "LU", "MO", "MK", "MG", "MW", "MY", "MV", "ML", "MT", "MH", "MQ", "MR", "MU", "YT", "MX", "FM", "MD", "MC", "MN", "ME", "MS", "MA", "MZ", "MM", "NA", "NR", "NP", "NL", "NC", "NZ", "NI", "NE", "NG", "NU", "NF", "KP", "MP", "NO", "OM", "PK", "PW", "PS", "PA", "PG", "PY", "PE", "PH", "PN", "PL", "PT", "PR", "QA", "XK", "RE", "RO", "RU", "RW", "BL", "SH", "KN", "LC", "MF", "PM", "VC", "WS", "SM", "ST", "SA", "SN", "RS", "SC", "SL", "SG", "SX", "SK", "SI", "SB", "SO", "ZA", "GS", "KR", "ZE", "SS", "ES", "LK", "SD", "SR", "SJ", "SZ", "SE", "CH", "SY", "TW", "TJ", "TZ", "TH", "TL", "TG", "TK", "TO", "TT", "TN", "TR", "ZC", "TM", "TC", "TV", "UG", "UA", "AE", "GB", "US", "UY", "UZ", "VU", "VE", "VN", "WF", "EH", "YE", "ZM", "ZW"}; try { uint64_t ticks = epee::misc_utils::get_tick_count(); bc_services::offer_details_ex od = AUTO_VAL_INIT(od); od.amount_primary = rand()%1000000000000; od.amount_target = rand() % 1000000000000; od.bonus = get_random_hex_text(7); od.target = get_random_hex_text(7); od.primary = currencies[rand() % (sizeof(currencies) / sizeof(currencies[0]))]; od.location_country = countries[rand() % (sizeof(countries) / sizeof(countries[0]))]; od.location_city = get_random_hex_text(7); od.contacts = "[{\"EMAIL\":\""; od.contacts += get_random_hex_text(5) + "@" + get_random_hex_text(5) + ".com\"}]"; // [] (Skype, mail, ICQ, etc., website) od.comment = get_random_hex_text(7); od.payment_types = "[]"; // []money accept type(bank transaction, internet money, cash, etc) od.deal_option = ""; // []full amount, by parts od.category = "NAA,NAB"; // [] od.expiration_time = 10; // n-days od.fee = TX_DEFAULT_FEE; w1.push_offer(od, tx); uint64_t ticks_for_tx = epee::misc_utils::get_tick_count() - ticks; LOG_PRINT_GREEN("Send push_offer_tx took " << ticks_for_tx << "ms", LOG_LEVEL_0); return true; } catch (const std::exception& e) { LOG_PRINT_RED("Failed to send tx(offer): " << e.what(), LOG_LEVEL_0); return false; } } #define ESTIMATE_INPUTS_COUNT_LIMIT_FOR_TX_BLOWUP (CURRENCY_TX_MAX_ALLOWED_OUTS - 4) bool do_send_money_by_fractions(tools::wallet2& w1, tools::wallet2& w2, size_t mix_in_factor, uint64_t amount_to_transfer, transaction& tx, uint64_t fraction_size) { //send all of "amount_to_transfer" even with last big transfer to utilize all amount taken from transfers CHECK_AND_ASSERT_MES(fraction_size > 0, false, "parts must be > 0"); std::vector dsts; dsts.reserve(ESTIMATE_INPUTS_COUNT_LIMIT_FOR_TX_BLOWUP); uint64_t amount_used = 0; for (size_t i = 0; i < ESTIMATE_INPUTS_COUNT_LIMIT_FOR_TX_BLOWUP; ++i) { currency::tx_destination_entry de; de.addr.push_back(w2.get_account().get_keys().account_address); if (i == ESTIMATE_INPUTS_COUNT_LIMIT_FOR_TX_BLOWUP - 1) { de.amount = amount_to_transfer - amount_used; } else if (amount_used + fraction_size < amount_to_transfer) { de.amount = fraction_size; } else { de.amount = amount_to_transfer - amount_used; } if (!de.amount) break; amount_used += de.amount; dsts.push_back(de); } try { w1.transfer(dsts, mix_in_factor, 0, w1.get_core_runtime_config().tx_default_fee, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(w1.get_core_runtime_config().tx_default_fee), tx); LOG_PRINT_GREEN("Split transaction sent " << get_transaction_hash(tx) << ", destinations: " << dsts.size() << ", blob size: " << get_object_blobsize(tx), LOG_LEVEL_0); return true; } catch (const std::exception& e) { LOG_ERROR("exception while sending transfer: " << e.what()); return false; } } uint64_t got_money_in_first_transfers(const tools::transfer_container& incoming_transfers, size_t n_transfers) { uint64_t summ = 0; size_t count = 0; BOOST_FOREACH(const tools::transfer_details& td, incoming_transfers) { summ += boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]).amount; if(++count >= n_transfers) return summ; } return summ; } #define FIRST_N_TRANSFERS 10*10 struct flow_test_context { flow_test_context(tools::wallet2& w_) :w(w_), stop_end_exit(false), pause(false) {} tools::wallet2& w; std::atomic stop_end_exit; std::atomic pause; }; void wait_unlock_money(tools::wallet2& w, flow_test_context& control) { size_t blocks_fetched = 0; uint64_t wait_count = 0; while (!control.pause && !control.stop_end_exit) { LOG_PRINT_GREEN("Wait iteration " << wait_count++ << " ...", LOG_LEVEL_0); misc_utils::sleep_no_w(1000); bool received_money = false; w.refresh(blocks_fetched, received_money, control.stop_end_exit); if (blocks_fetched) { uint64_t unlocked_balance = 0; uint64_t balance = w.balance(unlocked_balance); LOG_PRINT_GREEN("Balance: " << balance << ", unlocked balance: " << unlocked_balance, LOG_LEVEL_0); if (unlocked_balance == balance) return; } } } std::string get_incoming_transfers_str(tools::wallet2& w) { tools::transfer_container transfers; w.get_transfers(transfers); uint64_t spent_count = 0; uint64_t unspent_count = 0; for (const auto& td : transfers) { if (td.m_flags&WALLET_TRANSFER_DETAIL_FLAG_SPENT) { ++spent_count; } else { ++unspent_count; } } std::stringstream ss; ss << "spent: " << spent_count << ", unspent: " << unspent_count; return ss.str(); } void wait_receive_and_unlock_half_balance(tools::wallet2& w, flow_test_context& control) { size_t blocks_fetched = 0; uint64_t wait_count = 0; while (w.unlocked_balance() < w.balance()/2 && !control.pause && !control.stop_end_exit) { LOG_PRINT_GREEN("Wait iteration " << wait_count++ << " ...", LOG_LEVEL_0); misc_utils::sleep_no_w(1000); bool received_money = false; w.refresh(blocks_fetched, received_money, control.stop_end_exit); if (blocks_fetched) { uint64_t unlocked_balance = 0; uint64_t balance = w.balance(unlocked_balance); LOG_PRINT_GREEN("Balance: " << balance << ", unlocked balance: " << unlocked_balance, LOG_LEVEL_0); } } LOG_PRINT_GREEN("Storing wallet... ", LOG_LEVEL_0); w.store(); LOG_PRINT_GREEN("Wallet stored OK", LOG_LEVEL_0); } void wait_receive_and_unlock_money(tools::wallet2& w, uint64_t amount_to_wait) { size_t blocks_fetched = 0; uint64_t wait_count = 0; while (w.unlocked_balance() < amount_to_wait ) { LOG_PRINT_GREEN("Wait iteration " << wait_count++ << " ...", LOG_LEVEL_0); misc_utils::sleep_no_w(1000); bool received_money = false; std::atomic stop(false); w.refresh(blocks_fetched, received_money, stop); if (blocks_fetched) { uint64_t unlocked_balance = 0; uint64_t balance = w.balance(unlocked_balance); LOG_PRINT_GREEN("Balance: " << balance << ", unlocked balance: " << unlocked_balance, LOG_LEVEL_0); if (unlocked_balance == balance && unlocked_balance >= amount_to_wait) return; } } } class flow_test_console_cmmands_handler { epee::console_handlers_binder m_cmd_binder; flow_test_context& m_context; public: flow_test_console_cmmands_handler(flow_test_context& contxt):m_context(contxt) { m_cmd_binder.set_handler("help", boost::bind(&console_handlers_binder::help, &m_cmd_binder, ph::_1), "Show this help"); m_cmd_binder.set_handler("exit", boost::bind(&flow_test_console_cmmands_handler::exit, this, ph::_1), "Exit"); m_cmd_binder.set_handler("pause", boost::bind(&flow_test_console_cmmands_handler::pause, this, ph::_1), "Pause"); m_cmd_binder.set_handler("continue", boost::bind(&flow_test_console_cmmands_handler::do_continue, this, ph::_1), "Continue"); m_cmd_binder.set_handler("refresh", boost::bind(&flow_test_console_cmmands_handler::refresh, this, ph::_1), "Refresh"); } bool start_handling() { m_cmd_binder.start_handling("#"); return true; } private: bool exit(const std::vector& args) { m_context.stop_end_exit = true; return true; } bool pause(const std::vector& args) { m_context.pause = true; return true; } bool do_continue(const std::vector& args) { m_context.pause = false; return true; } bool refresh(const std::vector& args) { LOG_PRINT_GREEN("Refreshing... ", LOG_LEVEL_0); m_context.w.refresh(); LOG_PRINT_GREEN("Storing wallet... ", LOG_LEVEL_0); m_context.w.store(); LOG_PRINT_GREEN("Wallet stored OK", LOG_LEVEL_0); return true; } }; uint64_t get_tx_pool_size(std::string& daemon_addr) { epee::net_utils::http::http_simple_client http_client; currency::COMMAND_RPC_GET_INFO::request req = AUTO_VAL_INIT(req); req.flags = 0; currency::COMMAND_RPC_GET_INFO::response res = AUTO_VAL_INIT(res); bool r = net_utils::invoke_http_json_remote_command2(daemon_addr + "/getinfo", req, res, http_client, 10000); if (!r) { std::cout << "ERROR: failed to invoke request to daemon" << ENDL; return false; } return res.tx_pool_size; } bool test_serialization() { extra_alias_entry ee = {}; ee.m_text_comment = "adadasdasd"; std::string ddd = currency::obj_to_json_str(ee); return true; } struct test_serialization_2 { uint64_t var1; uint64_t var2; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(var1) KV_SERIALIZE(var2) END_KV_SERIALIZE_MAP() }; bool test_serialization2() { test_serialization_2 ee = {}; std::string json_buf = "{\"var1\": 111, \"var2\": \"222\"}"; epee::serialization::load_t_from_json(ee, json_buf); std::string json_buf2 = epee::serialization::store_t_to_json(ee); return true; } bool transactions_flow_test( std::wstring path_source_wallet, std::string source_wallet_pass, std::wstring path_terget_wallet, std::string target_wallet_pass, std::string& daemon_addr_a, std::string& daemon_addr_b, size_t mix_in_factor, size_t action, uint64_t max_tx_in_pool) { LOG_PRINT_L0("-----------------------STARTING TRANSACTIONS FLOW TEST (action " << action << ")-----------------------"); LOG_PRINT_L0("Max tx in pool: " << max_tx_in_pool); tools::wallet2 w1, w2; flow_test_context control(w1); flow_test_console_cmmands_handler cc(control); cc.start_handling(); try { w1.load(path_source_wallet, source_wallet_pass); w2.load(path_terget_wallet, target_wallet_pass); } catch (const std::exception& e) { LOG_ERROR("failed to generate wallet: " << e.what()); return false; } w1.init(daemon_addr_a); w2.init(daemon_addr_b); size_t blocks_fetched = 0; bool received_money; bool ok; std::atomic stop; if(!w1.refresh(blocks_fetched, received_money, ok, stop)) { LOG_ERROR( "failed to refresh source wallet from " << daemon_addr_a ); return false; } LOG_PRINT_GREEN("Using wallets: " << ENDL << "Source: " << w1.get_account().get_public_address_str() << ENDL << "Path: " << epee::string_encoding::wstring_to_utf8(path_source_wallet) << ENDL << "Target: " << w2.get_account().get_public_address_str() << ENDL << "Path: " << epee::string_encoding::wstring_to_utf8(path_terget_wallet), LOG_LEVEL_1); //lets make a lot of small outs to ourselves //since it is not possible to start from transaction that bigger than 500Kb, we gonna make transactions //with 500 outs (about 400kb), and we have to wait appropriate count blocks, mined for test wallet LOG_PRINT_GREEN("Transfers: " << get_incoming_transfers_str(w1), LOG_LEVEL_0); uint64_t transfer_size = TX_DEFAULT_FEE;//amount_to_transfer / transactions_count; tools::transfer_container incoming_transfers; size_t prepared_transfers = 0; if (action == TRANSACTIONS_FLOW_TESTACTION_SPLIT_INITAL) { LOG_PRINT_GREEN("Waiting to unlock money...", LOG_LEVEL_0); wait_unlock_money(w1, control); while (!control.stop_end_exit) { LOG_PRINT_GREEN("Getting transfers...", LOG_LEVEL_0); w1.get_transfers(incoming_transfers); LOG_PRINT_GREEN("Got " << incoming_transfers.size() << " transfers, splitting to fractions...", LOG_LEVEL_0); //lets go! size_t count = 0; prepared_transfers = 0; BOOST_FOREACH(tools::transfer_details& td, incoming_transfers) { if (td.is_spent()) continue; VARIANT_SWITCH_BEGIN(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); VARIANT_CASE(currency::tx_out_bare, ob); if (ob.amount <= transfer_size + TX_DEFAULT_FEE) { ++prepared_transfers; continue; } VARIANT_CASE(currency::tx_out_zarcanum, oz); // @#@ VARIANT_SWITCH_END(); ++count; currency::transaction tx_s; if (w1.unlocked_balance() >= boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]).amount) { bool r = do_send_money_by_fractions(w1, w1, 0, boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]).amount - TX_DEFAULT_FEE, tx_s, transfer_size); CHECK_AND_ASSERT_MES(r, false, "Failed to send starter tx " << get_transaction_hash(tx_s)); } else { break; } } if (!count) break; //waiting to unlock money LOG_PRINT_GREEN("Waiting to unlock money...", LOG_LEVEL_0); wait_unlock_money(w1, control); } LOG_PRINT_GREEN("Storing wallet...", LOG_LEVEL_0); w1.store(); LOG_PRINT_GREEN("Wallet splitted, " << prepared_transfers << " prepared_transfers.", LOG_LEVEL_0); return true; } //do actual transfer uint64_t transfered_money = 0; size_t i = 0; struct tx_test_entry { transaction tx; size_t m_received_count; uint64_t amount_transfered; }; size_t current_action = action; if (current_action == TRANSACTIONS_FLOW_TESTACTION_MIXED) current_action = TRANSACTIONS_FLOW_TESTACTION_DEFAULT; crypto::key_image lst_sent_ki = AUTO_VAL_INIT(lst_sent_ki); std::unordered_map txs; enum test_state { inflate_pool = 0, deflate_pool = 1 }; test_state state = inflate_pool; epee::math_helper::once_a_time_seconds<10> state_check_interval; while (!control.stop_end_exit) { state_check_interval.do_call([&]() { uint64_t pool_size = get_tx_pool_size(daemon_addr_a); switch (state) { case inflate_pool: if (pool_size > max_tx_in_pool) { LOG_PRINT_GREEN("POOL SIZE " << pool_size << ", switched to deflate mode...", LOG_LEVEL_0); state = deflate_pool; } break; case deflate_pool: if (pool_size == 0) { LOG_PRINT_GREEN("POOL SIZE " << pool_size << ", switched to inflate mode...", LOG_LEVEL_0); state = inflate_pool; } else if (pool_size < 20) { LOG_PRINT_GREEN("Pool is about to be empty: " << pool_size, LOG_LEVEL_0); } break; default: break; } return true; }); if (control.pause || state == deflate_pool) { --i; epee::misc_utils::sleep_no_w(100); continue; } //uint64_t amount_to_tx = (amount_to_transfer - transfered_money) > transfer_size ? transfer_size : (amount_to_transfer - transfered_money); uint64_t sent_amount = 0; transaction tx; bool res = false; if (current_action == TRANSACTIONS_FLOW_TESTACTION_DEFAULT) { res = do_send_money(w1, w2, mix_in_factor, TX_DEFAULT_FEE, tx); } else if (current_action == TRANSACTIONS_FLOW_TESTACTION_ALIAS) { sent_amount = currency::get_alias_coast_from_fee(get_random_hex_text(12), TX_DEFAULT_FEE); currency::account_base acc; acc.generate(); res = do_register_alias(w1, get_random_hex_text(12), get_random_hex_text(15), acc.get_public_address(), tx); } else if (current_action == TRANSACTIONS_FLOW_TESTACTION_OFFERS) { sent_amount = TX_DEFAULT_FEE; currency::account_base acc; acc.generate(); res = do_register_offer(w1, tx); } if(!res) { LOG_PRINT_GREEN("Sync wallet(transfers: " << get_incoming_transfers_str(w1) << ")...", LOG_LEVEL_0); wait_receive_and_unlock_half_balance(w1, control); LOG_PRINT_GREEN("Sync wallet finished(transfers: " << get_incoming_transfers_str(w1) << ")", LOG_LEVEL_0); continue; } lst_sent_ki = boost::get(tx.vin[0]).k_image; transfered_money += sent_amount; LOG_PRINT_L0("Transferred[type: "<< current_action << "]" << print_money(get_outs_money_amount(tx)) << "(+fee:" << print_money(get_tx_fee(tx)) << "), i=" << i ); tx_test_entry& ent = txs[get_transaction_hash(tx)] = boost::value_initialized(); ent.amount_transfered = sent_amount; ent.tx = tx; if (action == TRANSACTIONS_FLOW_TESTACTION_MIXED) { current_action++; if (current_action >= TRANSACTIONS_FLOW_TESTACTION_OFFERS) current_action = TRANSACTIONS_FLOW_TESTACTION_DEFAULT; } } LOG_PRINT_GREEN("Exiting, storing wallet... ", LOG_LEVEL_0); w1.store(); LOG_PRINT_GREEN("Wallet stored OK", LOG_LEVEL_0); return true; }