diff --git a/contrib/epee/include/net/net_utils_base.h b/contrib/epee/include/net/net_utils_base.h index 88586857..1ef06777 100644 --- a/contrib/epee/include/net/net_utils_base.h +++ b/contrib/epee/include/net/net_utils_base.h @@ -117,34 +117,34 @@ namespace net_utils //some helpers - inline - std::string print_connection_context(const connection_context_base& ctx) + inline std::string print_connection_context(const connection_context_base& ctx) { std::stringstream ss; ss << epee::string_tools::get_ip_string_from_int32(ctx.m_remote_ip) << ":" << ctx.m_remote_port << " " << epee::string_tools::get_str_from_guid_a(ctx.m_connection_id) << (ctx.m_is_income ? " INC":" OUT"); return ss.str(); } - inline - void print_connection_context_short(const connection_context_base& ctx, std::stringstream& ss) + inline std::ostream &operator <<(std::ostream &o, const connection_context_base& ctx) { - ss << epee::string_tools::get_ip_string_from_int32(ctx.m_remote_ip) << ":" << ctx.m_remote_port << (ctx.m_is_income ? " INC" : " OUT"); - } + o << epee::string_tools::get_ip_string_from_int32(ctx.m_remote_ip) << ":" << ctx.m_remote_port << (ctx.m_is_income ? " INC" : " OUT"); + return o; + } - inline - std::string print_connection_context_short(const connection_context_base& ctx) + inline std::string print_connection_context_short(const connection_context_base& ctx) { std::stringstream ss; - print_connection_context_short(ctx, ss); + ss << ctx; return ss.str(); } - inline std::string print_connection_context_list(const std::list& contexts) + inline std::string print_connection_context_list(const std::list& contexts, const std::string& delim = std::string("\n")) { std::stringstream ss; for (auto& c : contexts) { - ss << epee::string_tools::get_ip_string_from_int32(c.m_remote_ip) << ":" << c.m_remote_port << (c.m_is_income ? " INC" : " OUT") << ENDL; + if (ss.tellp()) + ss << delim; + ss << c; } return ss.str(); } @@ -179,7 +179,7 @@ namespace net_utils #define CHECK_AND_ASSERT_MES_CC(condition, return_val, err_message) CHECK_AND_ASSERT_MES(condition, return_val, "[" << epee::net_utils::print_connection_context_short(context) << "]" << err_message) -} -} +} // namespace net_utils +} // namespace epee #endif //_NET_UTILS_BASE_H_ diff --git a/src/currency_core/tx_pool.cpp b/src/currency_core/tx_pool.cpp index 3c29a609..ae8737a8 100644 --- a/src/currency_core/tx_pool.cpp +++ b/src/currency_core/tx_pool.cpp @@ -34,6 +34,8 @@ DISABLE_VS_WARNINGS(4244 4345 4503) //'boost::foreach_detail_::or_' : decorated #define CURRENCY_POOLDATA_FOLDERNAME_SUFFIX "_v1" +#define CONFLICT_KEY_IMAGE_SPENT_DEPTH_TO_REMOVE_TX_FROM_POOL 50 // if there's a conflict in key images between tx in the pool and in the blockchain this much depth in required to remove correspongin tx from pool + #undef LOG_DEFAULT_CHANNEL #define LOG_DEFAULT_CHANNEL "tx_pool" ENABLE_CHANNEL_BY_DEFAULT("tx_pool"); @@ -429,18 +431,41 @@ namespace currency int64_t tx_age = get_core_time() - tx_entry.receive_time; if ((tx_age > CURRENCY_MEMPOOL_TX_LIVETIME )) { - - LOG_PRINT_L0("Tx " << h << " removed from tx pool, reason: outdated, age: " << tx_age); + LOG_PRINT_L0("tx " << h << " is about to be removed from tx pool, reason: outdated, age: " << tx_age << " = " << misc_utils::get_time_interval_string(tx_age)); to_delete.push_back(tx_to_delete_entry(h, tx_entry.tx, tx_entry.kept_by_block)); } // expiration time check - remove expired if (is_tx_expired(tx_entry.tx, tx_expiration_ts_median) ) { - LOG_PRINT_L0("Tx " << h << " removed from tx pool, reason: expired, expiration time: " << get_tx_expiration_time(tx_entry.tx) << ", blockchain median: " << tx_expiration_ts_median); + LOG_PRINT_L0("tx " << h << " is about to be removed from tx pool, reason: expired, expiration time: " << get_tx_expiration_time(tx_entry.tx) << ", blockchain median: " << tx_expiration_ts_median); to_delete.push_back(tx_to_delete_entry(h, tx_entry.tx, tx_entry.kept_by_block)); } + // if a tx has at least one key image already used in blockchain (deep enough) -- remove such tx, as it cannot be added to any block + // although it will be removed by the age check above, we consider desireable + // to remove it from the pool faster in order to unblock related key images used in the same tx + uint64_t should_be_spent_before_height = m_blockchain.get_current_blockchain_size() - 1; + if (should_be_spent_before_height > CONFLICT_KEY_IMAGE_SPENT_DEPTH_TO_REMOVE_TX_FROM_POOL) + { + should_be_spent_before_height -= CONFLICT_KEY_IMAGE_SPENT_DEPTH_TO_REMOVE_TX_FROM_POOL; + for (auto& in : tx_entry.tx.vin) + { + if (in.type() == typeid(txin_to_key)) + { + // if at least one key image is spent deep enought -- remove such tx + const crypto::key_image& ki = boost::get(in).k_image; + if (m_blockchain.have_tx_keyimg_as_spent(ki, should_be_spent_before_height)) + { + LOG_PRINT_L0("tx " << h << " is about to be removed from tx pool, reason: ki was spent in the blockchain before height " << should_be_spent_before_height << ", tx age: " << misc_utils::get_time_interval_string(tx_age)); + to_delete.push_back(tx_to_delete_entry(h, tx_entry.tx, tx_entry.kept_by_block)); + return true; + } + } + } + } + + return true; }); diff --git a/src/currency_protocol/currency_protocol_handler.h b/src/currency_protocol/currency_protocol_handler.h index 08dd8ea8..5faa2efb 100644 --- a/src/currency_protocol/currency_protocol_handler.h +++ b/src/currency_protocol/currency_protocol_handler.h @@ -22,7 +22,7 @@ PUSH_VS_WARNINGS DISABLE_VS_WARNINGS(4355) -#define ASYNC_RELAY_MODE +#define ASYNC_RELAY_MODE // relay transactions asyncronously via m_relay_que namespace currency { @@ -117,25 +117,25 @@ namespace currency int64_t m_last_ntp2local_time_difference; template - bool post_notify(typename t_parametr::request& arg, currency_connection_context& context) - { - LOG_PRINT_L2("[POST]" << typeid(t_parametr).name()); - std::string blob; - epee::serialization::store_t_to_binary(arg, blob); - return m_p2p->invoke_notify_to_peer(t_parametr::ID, blob, context); - } + bool post_notify(typename t_parametr::request& arg, currency_connection_context& context) + { + LOG_PRINT_L3("[POST]" << typeid(t_parametr).name() << " to " << context); + std::string blob; + epee::serialization::store_t_to_binary(arg, blob); + return m_p2p->invoke_notify_to_peer(t_parametr::ID, blob, context); + } - template - bool relay_post_notify(typename t_parametr::request& arg, currency_connection_context& exlude_context) - { - std::string arg_buff; - epee::serialization::store_t_to_binary(arg, arg_buff); - std::list relayed_peers; - bool r = m_p2p->relay_notify_to_all(t_parametr::ID, arg_buff, exlude_context, relayed_peers); + template + bool relay_post_notify(typename t_parametr::request& arg, currency_connection_context& exlude_context) + { + std::string arg_buff; + epee::serialization::store_t_to_binary(arg, arg_buff); + std::list relayed_peers; + bool r = m_p2p->relay_notify_to_all(t_parametr::ID, arg_buff, exlude_context, relayed_peers); - LOG_PRINT_GREEN("[POST RELAY] " << typeid(t_parametr).name() << " relayed contexts list: " << ENDL << print_connection_context_list(relayed_peers), LOG_LEVEL_2); - return r; - } + LOG_PRINT_GREEN("[POST RELAY] " << typeid(t_parametr).name() << " to (" << relayed_peers.size() << "): " << print_connection_context_list(relayed_peers, ", "), LOG_LEVEL_2); + return r; + } }; } diff --git a/src/currency_protocol/currency_protocol_handler.inl b/src/currency_protocol/currency_protocol_handler.inl index d1bc6347..2566bd24 100644 --- a/src/currency_protocol/currency_protocol_handler.inl +++ b/src/currency_protocol/currency_protocol_handler.inl @@ -94,22 +94,32 @@ namespace currency << std::setw(20) << "Peer id" << std::setw(25) << "Recv/Sent (idle,sec)" << std::setw(25) << "State" - << std::setw(20) << "Livetime(seconds)" + << std::setw(20) << "Livetime" << std::setw(20) << "Client version" << ENDL; + size_t incoming_count = 0, outgoing_count = 0; + std::multimap conn_map; m_p2p->for_each_connection([&](const connection_context& cntxt, nodetool::peerid_type peer_id) { - ss << std::setw(29) << std::left << std::string(cntxt.m_is_income ? "[INC]":"[OUT]") + + std::stringstream conn_ss; + time_t livetime = time(NULL) - cntxt.m_started; + conn_ss << std::setw(29) << std::left << std::string(cntxt.m_is_income ? "[INC]":"[OUT]") + string_tools::get_ip_string_from_int32(cntxt.m_remote_ip) + ":" + std::to_string(cntxt.m_remote_port) << std::setw(20) << std::hex << peer_id << std::setw(25) << std::to_string(cntxt.m_recv_cnt)+ "(" + std::to_string(time(NULL) - cntxt.m_last_recv) + ")" + "/" + std::to_string(cntxt.m_send_cnt) + "(" + std::to_string(time(NULL) - cntxt.m_last_send) + ")" << std::setw(25) << get_protocol_state_string(cntxt.m_state) - << std::setw(20) << std::to_string(time(NULL) - cntxt.m_started) + << std::setw(20) << epee::misc_utils::get_time_interval_string(livetime) << std::setw(20) << cntxt.m_remote_version << ENDL; + conn_map.insert(std::make_pair(livetime, conn_ss.str())); + (cntxt.m_is_income ? incoming_count : outgoing_count) += 1; return true; }); - LOG_PRINT_L0("Connections: " << ENDL << ss.str()); + + for(auto it = conn_map.rbegin(); it != conn_map.rend(); ++it) + ss << it->second; + + LOG_PRINT_L0("Connections (" << incoming_count << " in, " << outgoing_count << " out, " << incoming_count + outgoing_count << " total):" << ENDL << ss.str()); } //------------------------------------------------------------------------------------------------------------------------ template @@ -235,7 +245,6 @@ namespace currency if(context.m_state != currency_connection_context::state_normal) return 1; - //check if block already exists block b = AUTO_VAL_INIT(b); block_verification_context bvc = AUTO_VAL_INIT(bvc); @@ -245,6 +254,7 @@ namespace currency m_p2p->drop_connection(context); return 1; } + crypto::hash block_id = get_block_hash(b); LOG_PRINT_GREEN("[HANDLE]NOTIFY_NEW_BLOCK " << block_id << " HEIGHT " << get_block_height(b) << " (hop " << arg.hop << ")", LOG_LEVEL_2); @@ -314,11 +324,11 @@ namespace currency m_p2p->drop_connection(context); return 1; } - LOG_PRINT_GREEN("[HANDLE]NOTIFY_NEW_BLOCK EXTRA: id: " << block_id - << ",bvc.m_added_to_main_chain " << bvc.m_added_to_main_chain - //<< ",prevalidate_result " << prevalidate_relayed - << ",bvc.added_to_altchain " << bvc.added_to_altchain - << ",bvc.m_marked_as_orphaned " << bvc.m_marked_as_orphaned, LOG_LEVEL_2); + LOG_PRINT_GREEN("[HANDLE]NOTIFY_NEW_BLOCK EXTRA " << block_id + << " bvc.m_added_to_main_chain=" << bvc.m_added_to_main_chain + //<< ", prevalidate_result=" << prevalidate_relayed + << ", bvc.added_to_altchain=" << bvc.added_to_altchain + << ", bvc.m_marked_as_orphaned=" << bvc.m_marked_as_orphaned, LOG_LEVEL_2); if (bvc.m_added_to_main_chain || (bvc.added_to_altchain && bvc.height_difference < 2)) { @@ -723,13 +733,14 @@ namespace currency if (req.txs.size()) { post_notify(req, cc); - print_connection_context_short(cc, debug_ss); - debug_ss << ": " << req.txs.size() << ENDL; + + if (debug_ss.tellp()) + debug_ss << ", "; + debug_ss << cc << ": " << req.txs.size(); } } TIME_MEASURE_FINISH_MS(ms); - LOG_PRINT_GREEN("[POST RELAY] NOTIFY_NEW_TRANSACTIONS relayed (" << ms << "ms)contexts list: " << debug_ss.str(), LOG_LEVEL_2); - + LOG_PRINT_GREEN("[POST RELAY] NOTIFY_NEW_TRANSACTIONS relayed (" << ms << "ms) to: " << debug_ss.str(), LOG_LEVEL_2); } //------------------------------------------------------------------------------------------------------------------------ template diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 2c0a840b..29ff8705 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -32,6 +32,7 @@ using namespace epee; #undef LOG_DEFAULT_CHANNEL #define LOG_DEFAULT_CHANNEL "p2p" +ENABLE_CHANNEL_BY_DEFAULT(LOG_DEFAULT_CHANNEL); #define CURRENT_P2P_STORAGE_ARCHIVE_VER (CURRENCY_FORMATION_VERSION+13) @@ -198,6 +199,7 @@ namespace nodetool bool make_new_connection_from_peerlist(bool use_white_list); bool try_to_connect_and_handshake_with_new_peer(const net_address& na, bool just_take_peerlist = false, uint64_t last_seen_stamp = 0, bool white = true); size_t get_random_index_with_fixed_probability(size_t max_index); + bool is_peer_id_used(const peerid_type id); bool is_peer_used(const peerlist_entry& peer); bool is_addr_connected(const net_address& peer); template diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index d81eaeea..71d9706c 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -511,6 +511,13 @@ namespace nodetool return; } + if (is_peer_id_used(rsp.node_data.peer_id)) + { + LOG_PRINT_L0("It seems that peer " << std::hex << rsp.node_data.peer_id << " has already been connected, dropping connection"); + hsh_result = false; + return; + } + pi = context.peer_id = rsp.node_data.peer_id; m_peerlist.set_peer_just_seen(rsp.node_data.peer_id, context.m_remote_ip, context.m_remote_port); @@ -535,7 +542,7 @@ namespace nodetool if(!hsh_result) { - LOG_PRINT_CC_L0(context_, "COMMAND_HANDSHAKE Failed"); + LOG_PRINT_CC_L0(context_, "COMMAND_HANDSHAKE Failed, closing connection"); m_net_server.get_config_object().close(context_.m_connection_id); } @@ -597,6 +604,26 @@ namespace nodetool } //----------------------------------------------------------------------------------- template + bool node_server::is_peer_id_used(const peerid_type id) + { + if (id == m_config.m_peer_id) + return true; // ourself + + bool used = false; + m_net_server.get_config_object().foreach_connection([&](const p2p_connection_context& cntxt) + { + if (id == cntxt.peer_id) + { + used = true; + return false; // stop enumerating + } + return true; + }); + + return used; + } + //----------------------------------------------------------------------------------- + template bool node_server::is_peer_used(const peerlist_entry& peer) { @@ -638,7 +665,7 @@ namespace nodetool template bool node_server::try_to_connect_and_handshake_with_new_peer(const net_address& na, bool just_take_peerlist, uint64_t last_seen_stamp, bool white) { - LOG_PRINT_L0("Connecting to " << string_tools::get_ip_string_from_int32(na.ip) << ":" << string_tools::num_to_string_fast(na.port) << "(white=" << white << ", last_seen: " << (last_seen_stamp?misc_utils::get_time_interval_string(time(NULL) - last_seen_stamp):"never" ) << ")..."); + LOG_PRINT_L1("Connecting to " << string_tools::get_ip_string_from_int32(na.ip) << ":" << string_tools::num_to_string_fast(na.port) << "(white=" << white << ", last_seen: " << (last_seen_stamp?misc_utils::get_time_interval_string(time(NULL) - last_seen_stamp):"never" ) << ")..."); typename net_server::t_connection_context con = AUTO_VAL_INIT(con); bool res = m_net_server.connect(string_tools::get_ip_string_from_int32(na.ip), @@ -647,7 +674,7 @@ namespace nodetool con); if(!res) { - LOG_PRINT_L0("Connect failed to " + LOG_PRINT_L1("Connect failed to " << string_tools::get_ip_string_from_int32(na.ip) << ":" << string_tools::num_to_string_fast(na.port) /*<< ", try " << try_count*/); @@ -661,9 +688,11 @@ namespace nodetool LOG_PRINT_CC_L0(con, "Failed to HANDSHAKE with peer " << string_tools::get_ip_string_from_int32(na.ip) << ":" << string_tools::num_to_string_fast(na.port) - /*<< ", try " << try_count*/); + << ", closing connection"); + m_net_server.get_config_object().close(con.m_connection_id); return false; } + if(just_take_peerlist) { m_net_server.get_config_object().close(con.m_connection_id); @@ -807,7 +836,10 @@ namespace nodetool if(is_addr_connected(na)) continue; - try_to_connect_and_handshake_with_new_peer(na); + if (!try_to_connect_and_handshake_with_new_peer(na)) + { + LOG_PRINT_L0("connection to priority node " << string_tools::get_ip_string_from_int32(na.ip) << ":" << string_tools::num_to_string_fast(na.port) << " failed"); + } } if(m_use_only_priority_peers) return true; @@ -1310,6 +1342,13 @@ namespace nodetool return 1; } + if (is_peer_id_used(arg.node_data.peer_id)) + { + LOG_PRINT_CCONTEXT_L1("COMMAND_HANDSHAKE came, but seems that peer " << std::hex << arg.node_data.peer_id << " has already been connected to this node, dropping connection"); + drop_connection(context); + return 1; + } + if (!tools::check_remote_client_version(arg.payload_data.client_version)) { LOG_PRINT_CCONTEXT_L2("COMMAND_HANDSHAKE: wrong client version: " << arg.payload_data.client_version << ", closing connection."); diff --git a/src/version.h.in b/src/version.h.in index bbd5c6c5..80faa1a2 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "2" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 67 +#define PROJECT_VERSION_BUILD_NO 68 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index c92fb801..5ffc9d8b 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2066,7 +2066,7 @@ void wallet2::store(const std::wstring& path_to_save, const std::string& passwor { LOG_PRINT_L0("(before storing: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); - check_for_free_space_and_throw_if_it_lacks(path_to_save); + // check_for_free_space_and_throw_if_it_lacks(path_to_save); temporary disabled, wallet saving implemented in two-stage scheme to avoid data loss due to lack of space std::string ascii_path_to_save = epee::string_encoding::convert_to_ansii(path_to_save); @@ -2099,8 +2099,9 @@ void wallet2::store(const std::wstring& path_to_save, const std::string& passwor r = tools::portble_serialize_obj_to_stream(*this, data_file); if (!r) { + data_file.close(); boost::filesystem::remove(tmp_file_path); // remove tmp file if smth went wrong - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(false, "portble_serialize_obj_to_stream failed for wallet " << tmp_file_path.string()); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(false, "IO error while storing wallet to " << tmp_file_path.string() << " (portble_serialize_obj_to_stream failed)"); } data_file.flush(); @@ -2229,7 +2230,7 @@ uint64_t wallet2::balance(uint64_t& unlocked, uint64_t& awaiting_in, uint64_t& a for(auto& td : m_transfers) { - if (td.is_spendable() || td.is_reserved_for_escrow()) + if (td.is_spendable() || (td.is_reserved_for_escrow() && !td.is_spent())) { balance_total += td.amount(); if (is_transfer_unlocked(td)) diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 230cf874..500bc1a1 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -731,6 +731,7 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(escrow_cancellation_acceptance_expiration); // GENERATE_AND_PLAY(escrow_proposal_acceptance_in_alt_chain); -- work in progress GENERATE_AND_PLAY(escrow_zero_amounts); + GENERATE_AND_PLAY(escrow_acceptance_and_balance); GENERATE_AND_PLAY(escrow_altchain_meta_test<0>); GENERATE_AND_PLAY(escrow_altchain_meta_test<1>); diff --git a/tests/core_tests/escrow_wallet_tests.cpp b/tests/core_tests/escrow_wallet_tests.cpp index 600339ae..9bbc919f 100644 --- a/tests/core_tests/escrow_wallet_tests.cpp +++ b/tests/core_tests/escrow_wallet_tests.cpp @@ -3095,3 +3095,200 @@ bool escrow_zero_amounts::c1(currency::core& c, size_t ev_index, const std::vect return true; } +//------------------------------------------------------------------------------ + +escrow_acceptance_and_balance::escrow_acceptance_and_balance() + : m_alice_bob_start_amount(0) + , m_alice_bob_start_chunk_amount(0) + , m_alice_fee_proposal(0) + , m_bob_fee_accept(0) + , m_bob_fee_release(0) +{ + REGISTER_CALLBACK_METHOD(escrow_acceptance_and_balance, check_balance_after_proposal_not_confirmed); + REGISTER_CALLBACK_METHOD(escrow_acceptance_and_balance, check_balance_after_proposal_confirmed); + REGISTER_CALLBACK_METHOD(escrow_acceptance_and_balance, check_balance_after_acceptance_not_confirmed); + REGISTER_CALLBACK_METHOD(escrow_acceptance_and_balance, check_balance_after_acceptance_confirmed); +} + +bool escrow_acceptance_and_balance::generate(std::vector& events) const +{ + m_accounts.resize(TOTAL_ACCS_COUNT); + account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); + account_base& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(); + account_base& bob_acc = m_accounts[BOB_ACC_IDX]; bob_acc.generate(); + + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + m_alice_bob_start_amount = MK_TEST_COINS(200); + uint64_t amount_chunks = 10; + m_alice_bob_start_chunk_amount = m_alice_bob_start_amount / 10; + + transaction tx_0 = AUTO_VAL_INIT(tx_0); + bool r = construct_tx_with_many_outputs(events, blk_0r, miner_acc.get_keys(), alice_acc.get_public_address(), m_alice_bob_start_amount, 10, TESTS_DEFAULT_FEE, tx_0); + CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs failed"); + events.push_back(tx_0); + + transaction tx_1 = AUTO_VAL_INIT(tx_1); + r = construct_tx_with_many_outputs(events, blk_0r, miner_acc.get_keys(), bob_acc.get_public_address(), m_alice_bob_start_amount, 10, TESTS_DEFAULT_FEE, tx_1); + CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs failed"); + events.push_back(tx_1); + + MAKE_NEXT_BLOCK_TX_LIST(events, blk_1, blk_0r, miner_acc, std::list({tx_0, tx_1})); + + REWIND_BLOCKS_N_WITH_TIME(events, blk_1r, blk_1, miner_acc, WALLET_DEFAULT_TX_SPENDABLE_AGE); + + // prepare contract details + m_cpd = AUTO_VAL_INIT(m_cpd); + m_cpd.amount_a_pledge = MK_TEST_COINS(7); + m_cpd.amount_b_pledge = MK_TEST_COINS(5); + m_cpd.amount_to_pay = MK_TEST_COINS(3); + m_cpd.a_addr = alice_acc.get_public_address(); + m_cpd.b_addr = bob_acc.get_public_address(); + m_alice_fee_proposal = MK_TEST_COINS(4); + m_bob_fee_accept = MK_TEST_COINS(2); + m_bob_fee_release = MK_TEST_COINS(9); // Alice states that Bob should pay this much money for upcoming contract release (which will be sent by Alice) + + std::vector used_sources; + + // escrow proposal + bc_services::proposal_body prop = AUTO_VAL_INIT(prop); + transaction escrow_proposal_tx = AUTO_VAL_INIT(escrow_proposal_tx); + r = build_custom_escrow_proposal(events, blk_1r, alice_acc.get_keys(), m_cpd, 0, 0, 0, blk_1r.timestamp + 36000, 0, m_alice_fee_proposal, m_bob_fee_release, eccf_normal, escrow_proposal_tx, used_sources, &prop); + CHECK_AND_ASSERT_MES(r, false, "build_custom_escrow_proposal failed"); + events.push_back(escrow_proposal_tx); + + DO_CALLBACK(events, "check_balance_after_proposal_not_confirmed"); + + MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1r, miner_acc, escrow_proposal_tx); + + DO_CALLBACK(events, "check_balance_after_proposal_confirmed"); + + MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_acc); + + DO_CALLBACK(events, "check_balance_after_proposal_confirmed"); + + // escrow proposal acceptance + transaction escrow_normal_acceptance_tx = prop.tx_template; + uint64_t normal_acceptance_mask = eccf_normal; + r = build_custom_escrow_accept_proposal(events, blk_2, 0, bob_acc.get_keys(), m_cpd, 0, 0, 0, 0, m_bob_fee_accept, m_bob_fee_release, normal_acceptance_mask, prop.tx_onetime_secret_key, escrow_normal_acceptance_tx, used_sources); + CHECK_AND_ASSERT_MES(r, false, "build_custom_escrow_accept_proposal failed"); + + events.push_back(escrow_normal_acceptance_tx); + + DO_CALLBACK(events, "check_balance_after_acceptance_not_confirmed"); + + MAKE_NEXT_BLOCK_TX1(events, blk_4, blk_3, miner_acc, escrow_normal_acceptance_tx); + + DO_CALLBACK(events, "check_balance_after_acceptance_confirmed"); + + MAKE_NEXT_BLOCK(events, blk_5, blk_4, miner_acc); + + DO_CALLBACK(events, "check_balance_after_acceptance_confirmed"); + + return true; +} + +bool escrow_acceptance_and_balance::check_balance_after_proposal_not_confirmed(currency::core& c, size_t ev_index, const std::vector& events) +{ + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX); + std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, BOB_ACC_IDX); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, + m_alice_bob_start_amount - m_alice_fee_proposal, // total + true, UINT64_MAX, + m_alice_bob_start_amount - 2 * m_alice_bob_start_chunk_amount, // unlocked + 0, // mined + MK_TEST_COINS(0), // awaited in + MK_TEST_COINS(0) // awainted out + ), false, ""); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, + m_alice_bob_start_amount, // total + true, UINT64_MAX, + m_alice_bob_start_amount, // unlocked + 0, // mined + MK_TEST_COINS(0), // awaited in + MK_TEST_COINS(0) // awaited out + ), false, ""); + + return true; +} + +bool escrow_acceptance_and_balance::check_balance_after_proposal_confirmed(currency::core& c, size_t ev_index, const std::vector& events) +{ + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX); + std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, BOB_ACC_IDX); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, + m_alice_bob_start_amount - m_alice_fee_proposal, // total + true, UINT64_MAX, + m_alice_bob_start_amount - 2 * m_alice_bob_start_chunk_amount, // unlocked + 0, // mined + MK_TEST_COINS(0), // awaited in + MK_TEST_COINS(0) // awaited out + ), false, ""); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, + m_alice_bob_start_amount, // total + true, UINT64_MAX, + m_alice_bob_start_amount, // unlocked + 0, // mined + MK_TEST_COINS(0), // awaited in + MK_TEST_COINS(0) // awaited out + ), false, ""); + + return true; +} + +bool escrow_acceptance_and_balance::check_balance_after_acceptance_not_confirmed(currency::core& c, size_t ev_index, const std::vector& events) +{ + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX); + std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, BOB_ACC_IDX); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, + m_alice_bob_start_amount - m_alice_fee_proposal - m_cpd.amount_a_pledge - m_cpd.amount_to_pay, // total + true, UINT64_MAX, + m_alice_bob_start_amount - 2 * m_alice_bob_start_chunk_amount, // unlocked + 0, // mined + MK_TEST_COINS(0), // awaited in + m_cpd.amount_a_pledge + m_cpd.amount_to_pay), // awaited out + false, ""); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, + m_alice_bob_start_amount - m_cpd.amount_b_pledge - m_bob_fee_release - m_bob_fee_accept, // total + true, UINT64_MAX, + m_alice_bob_start_amount - 1 * m_alice_bob_start_chunk_amount, // unlocked + 0, // mined + MK_TEST_COINS(0), // awaited in + m_cpd.amount_b_pledge + m_bob_fee_release), // awaited out + false, ""); + + return true; +} + +bool escrow_acceptance_and_balance::check_balance_after_acceptance_confirmed(currency::core& c, size_t ev_index, const std::vector& events) +{ + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX); + std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, BOB_ACC_IDX); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, + m_alice_bob_start_amount - m_alice_fee_proposal - m_cpd.amount_a_pledge - m_cpd.amount_to_pay, // total + true, UINT64_MAX, + m_alice_bob_start_amount - 2 * m_alice_bob_start_chunk_amount, // unlocked + 0, // mined + MK_TEST_COINS(0), // awaited in + MK_TEST_COINS(0) // awaited out + ), false, ""); + + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, + m_alice_bob_start_amount - m_cpd.amount_b_pledge - m_bob_fee_release - m_bob_fee_accept, // total + true, UINT64_MAX, + m_alice_bob_start_amount - 1 * m_alice_bob_start_chunk_amount, // unlocked + 0, // mined + MK_TEST_COINS(0), // awaited in + MK_TEST_COINS(0) // awaited out + ), false, ""); + + return true; +} diff --git a/tests/core_tests/escrow_wallet_tests.h b/tests/core_tests/escrow_wallet_tests.h index 33fdb457..332ac5a2 100644 --- a/tests/core_tests/escrow_wallet_tests.h +++ b/tests/core_tests/escrow_wallet_tests.h @@ -141,3 +141,20 @@ struct escrow_zero_amounts : public wallet_test bool generate(std::vector& events) const; bool c1(currency::core& c, size_t ev_index, const std::vector& events); }; + +struct escrow_acceptance_and_balance : public wallet_test +{ + escrow_acceptance_and_balance(); + bool generate(std::vector& events) const; + bool check_balance_after_proposal_not_confirmed(currency::core& c, size_t ev_index, const std::vector& events); + bool check_balance_after_proposal_confirmed(currency::core& c, size_t ev_index, const std::vector& events); + bool check_balance_after_acceptance_not_confirmed(currency::core& c, size_t ev_index, const std::vector& events); + bool check_balance_after_acceptance_confirmed(currency::core& c, size_t ev_index, const std::vector& events); + + mutable uint64_t m_alice_bob_start_amount; + mutable uint64_t m_alice_bob_start_chunk_amount; + mutable uint64_t m_alice_fee_proposal; + mutable uint64_t m_bob_fee_release; + mutable uint64_t m_bob_fee_accept; + mutable bc_services::contract_private_details m_cpd; +}; diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp index a71dae24..9833d5ef 100644 --- a/tests/core_tests/tx_validation.cpp +++ b/tests/core_tests/tx_validation.cpp @@ -1453,14 +1453,13 @@ bool tx_expiration_time_and_chain_switching::generate(std::vector& events) const { + // Test idea: check tx that is stuck in tx pool because one on its key images is already spent in the blockchain + // 1) if it's linked to an alt block -- tx will not be removed as long as linked alt block exists (in order to be able to switch) + // 2) if it's not linked to an alt block -- it will be removed after CONFLICT_KEY_IMAGE_SPENT_DEPTH_TO_REMOVE_TX_FROM_POOL confirmations of conflicted tx + // or it will be removed once tx is old enough (CURRENCY_MEMPOOL_TX_LIVETIME) + bool r = false; m_miner_acc.generate(); @@ -1534,44 +1533,49 @@ bool tx_key_image_pool_conflict::generate(std::vector& events) // however, it does not remove tx from the pool DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast(2)); + // + // make sure stuck tx will be removed from the pool when it's too old + // + MAKE_NEXT_BLOCK(events, blk_2, blk_1, m_miner_acc); - DO_CALLBACK(events, "remove_stuck_txs"); - // remove_stuck_txs should not remove anything, tx_1 and tx_2 should be in the pool + DO_CALLBACK(events, "remove_stuck_txs"); DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast(2)); // shift time by CURRENCY_MEMPOOL_TX_LIVETIME events.push_back(event_core_time(CURRENCY_MEMPOOL_TX_LIVETIME + 1, true)); - // remove_stuck_txs should remove only tx_2 and left tx_1 + // remove_stuck_txs should have removed tx_2 because it's too old DO_CALLBACK(events, "remove_stuck_txs"); + DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast(1)); + // + // make sure stuck tx will be removed from the pool as soon as one of its key images is spent deep enough in the blockchain + // (even if it's not too old to be removed by age) + // + + MAKE_NEXT_BLOCK(events, blk_3, blk_2, m_miner_acc); + + // re-add tx_2 with kept_by_block flag + events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); + events.push_back(tx_2); + events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); + + DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast(2)); + + // remove_stuck_txs should not remove anything, tx_1 and tx_2 should be in the pool + DO_CALLBACK(events, "remove_stuck_txs"); + DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast(2)); + + // rewind 50 blocks so tx_0 spending its key image will be deep enough + REWIND_BLOCKS_N_WITH_TIME(events, blk_3r, blk_3, m_miner_acc, 50); + + // remove_stuck_txs should remove only tx_2 and left tx_1 (linked to alt block) + DO_CALLBACK(events, "remove_stuck_txs"); DO_CALLBACK_PARAMS(events, "check_tx_pool_count", static_cast(1)); DO_CALLBACK(events, "print_tx_pool"); return true; } - -bool tx_key_image_pool_conflict::c1(currency::core& c, size_t ev_index, const std::vector& events) -{ - bool r = false; - - CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "incorrect tx pool count = " << c.get_pool_transactions_count()); - - // try to mine a block and make sure tx_1 is still in the pool (was not added to the blocktemplate) - block b; - r = mine_next_pow_block_in_playtime(m_miner_acc.get_public_address(), c, &b); - CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed"); - - // make sure tx_1 is still here - CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "incorrect tx pool count = " << c.get_pool_transactions_count()); - - return true; -} - -bool tx_key_image_pool_conflict::c2(currency::core& c, size_t ev_index, const std::vector& events) -{ - return true; -} diff --git a/tests/core_tests/tx_validation.h b/tests/core_tests/tx_validation.h index 5c8bb524..c72ac866 100644 --- a/tests/core_tests/tx_validation.h +++ b/tests/core_tests/tx_validation.h @@ -150,10 +150,6 @@ struct tx_expiration_time_and_chain_switching : public test_chain_unit_enchanced struct tx_key_image_pool_conflict : public test_chain_unit_enchanced { - tx_key_image_pool_conflict(); bool generate(std::vector& events) const; - bool c1(currency::core& c, size_t ev_index, const std::vector& events); - bool c2(currency::core& c, size_t ev_index, const std::vector& events); - mutable currency::account_base m_miner_acc; }; diff --git a/tests/performance_tests/free_space_check.h b/tests/performance_tests/free_space_check.h index 16ea0afb..9ab54e13 100644 --- a/tests/performance_tests/free_space_check.h +++ b/tests/performance_tests/free_space_check.h @@ -11,6 +11,8 @@ #include "misc_log_ex.h" +namespace fs = boost::filesystem; + std::string exec(const char* cmd) { std::array buffer; @@ -32,11 +34,59 @@ std::string exec(const char* cmd) return result; } +bool try_write_test_file(size_t size_bytes) +{ + + static const std::string filename = "test_out_file"; + static const fs::path filename_p = filename; + + try + { + fs::ofstream s; + s.open(filename, std::ios_base::binary | std::ios_base::out| std::ios::trunc); + if(s.fail()) + return false; + + uint8_t block[32 * 1024] = {}; + crypto::generate_random_bytes(sizeof block, &block); + size_t size_total = 0; + for (size_t i = 0; i < size_bytes / (sizeof block); ++i) + { + s.write((const char*)&block, sizeof block); + size_total += sizeof block; + } + + if (size_bytes > size_total) + s.write((const char*)&block, size_bytes - size_total); + + s.close(); + + size_t size_actual = fs::file_size(filename_p); + CHECK_AND_ASSERT_MES(size_bytes == size_actual, false, "size_bytes = " << size_bytes << ", size_actual = " << size_actual); + + CHECK_AND_ASSERT_MES(fs::remove(filename_p), false, "remove failed"); + } + catch (std::exception& e) + { + LOG_PRINT_RED("caught: " << e.what(), LOG_LEVEL_0); + return false; + } + catch (...) + { + LOG_PRINT_RED("caught unknown exception", LOG_LEVEL_0); + return false; + } + + return true; +} + void free_space_check() { + static const size_t test_file_size = 1024 * 1024; namespace fs = boost::filesystem; std::string output; + bool r = false; #ifdef WIN32 output = exec("dir"); @@ -46,9 +96,13 @@ void free_space_check() LOG_PRINT_L0("test command output:" << std::endl << output); + r = try_write_test_file(test_file_size); + LOG_PRINT_L0("test file write: " << (r ? "OK" : "fail")); + boost::filesystem::path current_path("."); size_t counter = 0; + bool need_backspace = false; while (true) { std::this_thread::sleep_for(std::chrono::milliseconds( 900 )); @@ -59,26 +113,43 @@ void free_space_check() { // free space is ok counter = (counter + 1) % 4; - std::cout << '\b'; + if (need_backspace) + std::cout << '\b'; std::cout << ( counter == 0 ? '*' : counter == 1 ? '\\' : counter == 2 ? '|' : '/' ); + std::cout << std::flush; + need_backspace = true; continue; } // free space is not ok! - LOG_PRINT_YELLOW("free space available: " << si.available, LOG_LEVEL_0); + LOG_PRINT_YELLOW("1) fs::space() : available: " << si.available << ", free: " << si.free << ", capacity: " << si.capacity, LOG_LEVEL_0); #ifdef WIN32 output = exec("dir"); #else output = exec("df -h"); #endif LOG_PRINT_YELLOW(output, LOG_LEVEL_0); + + // try one again asap + si = fs::space(current_path); + LOG_PRINT_YELLOW("2) fs::space() : available: " << si.available << ", free: " << si.free << ", capacity: " << si.capacity, LOG_LEVEL_0); + + if (!try_write_test_file(test_file_size)) + { + LOG_PRINT_YELLOW("try_write_test_file(" << test_file_size << ") failed", LOG_LEVEL_0); + } + + + need_backspace = false; } catch (std::exception& e) { LOG_ERROR("failed to determine free space: " << e.what()); + need_backspace = false; } catch (...) { LOG_ERROR("failed to determine free space: unknown exception"); + need_backspace = false; } }