diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index bebc1c62..a0aae121 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1302,6 +1302,68 @@ void wallet2::process_unconfirmed(const currency::transaction& tx, std::vector m_htlcs.rbegin()->first) + { + //there is no active htlc that at this height + CHECK_AND_ASSERT_MES(m_active_htlcs.size() == 0, void(), "Self check failed: m_active_htlcs.size() = " << m_active_htlcs.size()); + return; + } + //we have to check if there is a htlc that has to become deactivated + auto pair_of_it = m_htlcs.equal_range(height); + for (auto it = pair_of_it.first; it != pair_of_it.second; it++) + { + auto& tr = m_transfers[it->second.transfer_index]; + //found contract that supposed to be re-activated and set to active + if (it->second.is_wallet_owns_redeem) + { + // this means that wallet received atomic as proposal but never activated it, and now we back to phase where out can be activated + //but we keep spend flag anyway + tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //re assure that it has spent flag + tr.m_spent_height = 0; + } + else + { + // this means that wallet created atomic by itself, and second part didn't redeem it, + // so refund money became available, and now we back again to unavailable state + tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //reset spent flag + m_found_free_amounts.clear(); //reset free amounts cache + tr.m_spent_height = 0; + } + //re-add to active contracts + auto pair_key = std::make_pair(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].amount, tr.m_global_output_index); + auto it_active_htlc = m_active_htlcs.find(pair_key); + if (it_active_htlc != m_active_htlcs.end()) + { + LOG_ERROR("Error at putting back htlc: already exist?"); + it_active_htlc->second = it->second.transfer_index; + + } + else + { + m_active_htlcs[pair_key] = it->second.transfer_index; + m_active_htlcs_txid[tr.tx_hash()] = it->second.transfer_index; + } + + const crypto::hash tx_id = tr.tx_hash(); + auto tx_id_it = m_active_htlcs_txid.find(tx_id); + if (tx_id_it != m_active_htlcs_txid.end()) + { + LOG_ERROR("Error at putting back htlc_txid: already exist?"); + tx_id_it->second = it->second.transfer_index; + + } + else + { + m_active_htlcs_txid[tx_id] = it->second.transfer_index; + } + } +} void wallet2::process_htlc_triggers_on_block_added(uint64_t height) { if (!m_htlcs.size()) @@ -1361,7 +1423,7 @@ void wallet2::process_new_blockchain_entry(const currency::block& b, const curre !(height == m_minimum_height || get_blockchain_current_size() <= 1), error::wallet_internal_error, "current_index=" + std::to_string(height) + ", get_blockchain_current_height()=" + std::to_string(get_blockchain_current_size())); - process_htlc_triggers_on_block_added(height); + //optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup @@ -1393,6 +1455,8 @@ void wallet2::process_new_blockchain_entry(const currency::block& b, const curre if (!is_pos_block(b)) m_last_pow_block_h = height; + + process_htlc_triggers_on_block_added(height); m_wcallback->on_new_block(height, b); } //---------------------------------------------------------------------------------------------------- @@ -2213,6 +2277,10 @@ void wallet2::detach_blockchain(uint64_t including_height) } } + for (uint64_t i = get_top_block_height(); i != including_height - 1 && i != 0; i--) + { + unprocess_htlc_triggers_on_block_removed(i); + } size_t blocks_detached = detach_from_block_ids(including_height); //rollback spends @@ -2226,14 +2294,6 @@ void wallet2::detach_blockchain(uint64_t including_height) WLT_LOG_BLUE("Transfer [" << i << "] spent height: " << tr.m_spent_height << " -> 0, reason: detaching blockchain", LOG_LEVEL_1); tr.m_spent_height = 0; //check if it's hltc contract - if (tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].target.type() == typeid(txout_htlc) && tr.m_flags & WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM) - { - //only if htlc was spent as a redeem, then we put htlc back as active - const txout_htlc& htlc = boost::get(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].target); - auto amount_gindex_pair = std::make_pair(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].amount, tr.m_global_output_index); - m_active_htlcs[amount_gindex_pair] = i; - m_active_htlcs_txid[tr.tx_hash()] = i; - } } } @@ -4035,6 +4095,7 @@ void wallet2::create_htlc_proposal(uint64_t amount, const currency::account_publ finalized_tx ft = AUTO_VAL_INIT(ft); this->transfer(ctp, ft, true, nullptr); origin = ft.htlc_origin; + tx = ft.tx; } //---------------------------------------------------------------------------------------------------- void wallet2::get_list_of_active_htlc(std::list& htlcs, bool only_redeem_txs) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 733bf554..203fd272 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -802,6 +802,7 @@ namespace tools const crypto::hash& bl_id, uint64_t height); void process_htlc_triggers_on_block_added(uint64_t height); + void unprocess_htlc_triggers_on_block_removed(uint64_t height); bool get_pos_entries(currency::COMMAND_RPC_SCAN_POS::request& req); bool build_minted_block(const currency::COMMAND_RPC_SCAN_POS::request& req, const currency::COMMAND_RPC_SCAN_POS::response& rsp, uint64_t new_block_expected_height = UINT64_MAX); bool build_minted_block(const currency::COMMAND_RPC_SCAN_POS::request& req, const currency::COMMAND_RPC_SCAN_POS::response& rsp, const currency::account_public_address& miner_address, uint64_t new_block_expected_height = UINT64_MAX); diff --git a/tests/core_tests/atomic_tests.cpp b/tests/core_tests/atomic_tests.cpp index 22ce50c5..fc8fdcc5 100644 --- a/tests/core_tests/atomic_tests.cpp +++ b/tests/core_tests/atomic_tests.cpp @@ -454,3 +454,195 @@ bool atomic_test_wrong_redeem_wrong_refund::c1(currency::core& c, size_t ev_inde return true; } + + +//------------------------------------------------------------------------------ +//============================================================================== +//============================================================================== +//============================================================================== + +atomic_test_altchain_simple::atomic_test_altchain_simple() +{ + REGISTER_CALLBACK_METHOD(atomic_test_altchain_simple, c1); +} + +bool atomic_test_altchain_simple::generate(std::vector& events) const +{ + epee::debug::get_set_enable_assert(true, true); + + currency::account_base genesis_acc; + genesis_acc.generate(); + m_mining_accunt.generate(); + + + block blk_0 = AUTO_VAL_INIT(blk_0); + generator.construct_genesis_block(blk_0, genesis_acc, test_core_time::get_time()); + events.push_back(blk_0); + + REWIND_BLOCKS_N(events, blk_0r, blk_0, m_mining_accunt, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 5); + + DO_CALLBACK(events, "c1"); + epee::debug::get_set_enable_assert(true, false); + return true; +} + + + +bool atomic_test_altchain_simple::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + /* + 1. Create HTLC proposal + 2. Do switch to altchain to the point before HTLC proposal creation (core and wallet) + 3. Do redeem in altchain + 4. Do switch to altchain to the point before HTLC redeem (core and wallet) + 5. Do redeem in altchain + 6. Validate correct state of core and wallet + 7. Do redeem in altchain + 8. Validate state + + TODO: play with expirations + TODO: do refund, and then redeem in altchain + */ + + LOG_PRINT_MAGENTA("Mining Address: " << currency::get_account_address_as_str(m_mining_accunt.get_public_address()), LOG_LEVEL_0); + + //create wallet instances and calculate balances + INIT_RUNTIME_WALLET(alice_a_wlt_instance); + INIT_RUNTIME_WALLET(alice_b_wlt_instance); + + +#define AMOUNT_TO_TRANSFER_HTLC (TESTS_DEFAULT_FEE*10) + + std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, m_mining_accunt); + + size_t blocks_fetched = 0; + bool received_money = false; + std::atomic atomic_false = ATOMIC_VAR_INIT(false); + miner_wlt->refresh(blocks_fetched, received_money, atomic_false); + CHECK_AND_FORCE_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool"); + + + uint64_t transfer_amount = AMOUNT_TO_TRANSFER_HTLC + TESTS_DEFAULT_FEE; + miner_wlt->transfer(transfer_amount, alice_a_wlt_instance->get_account().get_public_address()); + LOG_PRINT_MAGENTA("Transaction sent to Alice A: " << transfer_amount, LOG_LEVEL_0); + + bool r = mine_next_pow_blocks_in_playtime(m_mining_accunt.get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); + + alice_a_wlt_instance->refresh(); + alice_b_wlt_instance->refresh(); + + std::string alice_origin; //will be deterministically generated by Alice's A wallet + currency::transaction htlc_proposal_tx = AUTO_VAL_INIT(htlc_proposal_tx); + alice_a_wlt_instance->create_htlc_proposal(transfer_amount - TESTS_DEFAULT_FEE, alice_b_wlt_instance->get_account().get_public_address(), 12, htlc_proposal_tx, currency::null_hash, alice_origin); + + crypto::hash split_id = c.get_blockchain_storage().get_top_block_id(); + uint64_t split_height = c.get_blockchain_storage().get_top_block_height(); + + //forward blockchain to create redeem transaction + r = mine_next_pow_blocks_in_playtime(m_mining_accunt.get_public_address(), c, 3); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); + + alice_a_wlt_instance->refresh(); + alice_b_wlt_instance->refresh(); + + //memorize block id at split height before split to make sure split happened + crypto::hash id_first_splited_block = c.get_blockchain_storage().get_block_id_by_height(split_height + 1); + + + //validate state of a + std::list htlcs_a; + alice_a_wlt_instance->get_list_of_active_htlc(htlcs_a, false); + CHECK_AND_FORCE_ASSERT_MES(htlcs_a.size() == 1, false, "Epected htlc not found"); + CHECK_AND_FORCE_ASSERT_MES(htlcs_a.back().is_redeem == false, false, "type of htlc mismatched"); + + //validate state of b + std::list htlcs_b; + alice_b_wlt_instance->get_list_of_active_htlc(htlcs_b, true); + CHECK_AND_FORCE_ASSERT_MES(htlcs_b.size() == 1, false, "Epected htlc not found"); + CHECK_AND_FORCE_ASSERT_MES(htlcs_b.back().is_redeem == true, false, "type of htlc mismatched"); + + //create altchain + std::vector txs; + txs.push_back(htlc_proposal_tx); + r = mine_next_pow_blocks_in_playtime_with_given_txs(m_mining_accunt.get_public_address(), txs, c, 10, split_id); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); + + //make sure reorganize happened + crypto::hash id_new_chain = c.get_blockchain_storage().get_block_id_by_height(split_height + 1); + CHECK_AND_ASSERT_MES(id_new_chain != id_first_splited_block, false, "Reorganize didn't happen"); + + alice_a_wlt_instance->refresh(); + alice_b_wlt_instance->refresh(); + + htlcs_a.clear(); + alice_a_wlt_instance->get_list_of_active_htlc(htlcs_a, false); + CHECK_AND_FORCE_ASSERT_MES(htlcs_a.size() == 1, false, "Epected htlc not found"); + CHECK_AND_FORCE_ASSERT_MES(htlcs_a.back().is_redeem == false, false, "type of htlc mismatched"); + + //validate state of b + htlcs_b.clear(); + alice_b_wlt_instance->get_list_of_active_htlc(htlcs_b, true); + CHECK_AND_FORCE_ASSERT_MES(htlcs_b.size() == 1, false, "Epected htlc not found"); + CHECK_AND_FORCE_ASSERT_MES(htlcs_b.back().is_redeem == true, false, "type of htlc mismatched"); + + currency::transaction result_redeem_tx = AUTO_VAL_INIT(result_redeem_tx); + alice_b_wlt_instance->redeem_htlc(htlcs_b.front().tx_id, alice_origin, result_redeem_tx); + + crypto::hash split_id_2 = c.get_blockchain_storage().get_top_block_id(); + uint64_t split_height_2 = c.get_blockchain_storage().get_top_block_height(); + + //forward blockchain + r = mine_next_pow_blocks_in_playtime(m_mining_accunt.get_public_address(), c, 3); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); + + alice_a_wlt_instance->refresh(); + alice_b_wlt_instance->refresh(); + + + //memorize block id at split height before split to make sure split happened + crypto::hash id_first_splited_block_2 = c.get_blockchain_storage().get_block_id_by_height(split_height_2 + 1); + + txs.clear(); + txs.push_back(result_redeem_tx); + r = mine_next_pow_blocks_in_playtime_with_given_txs(m_mining_accunt.get_public_address(), txs, c, 10, split_id_2); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); + + //make sure reorganize happened + crypto::hash id_new_chain_2 = c.get_blockchain_storage().get_block_id_by_height(split_height_2 + 1); + CHECK_AND_ASSERT_MES(id_new_chain_2 != id_first_splited_block_2, false, "Reorganize didn't happen"); + + alice_a_wlt_instance->refresh(); + alice_b_wlt_instance->refresh(); + + htlcs_a.clear(); + alice_a_wlt_instance->get_list_of_active_htlc(htlcs_a, false); + CHECK_AND_FORCE_ASSERT_MES(htlcs_a.size() == 1, false, "Epected htlc not found"); + CHECK_AND_FORCE_ASSERT_MES(htlcs_a.back().is_redeem == false, false, "type of htlc mismatched"); + + //validate state of b + htlcs_b.clear(); + alice_b_wlt_instance->get_list_of_active_htlc(htlcs_b, true); + CHECK_AND_FORCE_ASSERT_MES(htlcs_b.size() == 1, false, "Epected htlc not found"); + CHECK_AND_FORCE_ASSERT_MES(htlcs_b.back().is_redeem == true, false, "type of htlc mismatched"); + + //forward blockchain + r = mine_next_pow_blocks_in_playtime(m_mining_accunt.get_public_address(), c, 3); + CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); + + alice_a_wlt_instance->refresh(); + alice_b_wlt_instance->refresh(); + + htlcs_a.clear(); + alice_a_wlt_instance->get_list_of_active_htlc(htlcs_a, false); + CHECK_AND_FORCE_ASSERT_MES(htlcs_a.size() == 0, false, "htlc contracts count is wrong"); + + //validate state of b + htlcs_b.clear(); + alice_b_wlt_instance->get_list_of_active_htlc(htlcs_b, true); + CHECK_AND_FORCE_ASSERT_MES(htlcs_b.size() == 0, false, "htlc contracts count is wrong"); + + + return true; +} + diff --git a/tests/core_tests/atomic_tests.h b/tests/core_tests/atomic_tests.h index 74a283a5..39caca07 100644 --- a/tests/core_tests/atomic_tests.h +++ b/tests/core_tests/atomic_tests.h @@ -26,3 +26,12 @@ private: mutable currency::account_base m_mining_accunt; }; + +struct atomic_test_altchain_simple : public wallet_test +{ + atomic_test_altchain_simple(); + bool generate(std::vector& events) const; + bool c1(currency::core& c, size_t ev_index, const std::vector& events); +private: + mutable currency::account_base m_mining_accunt; +}; diff --git a/tests/core_tests/chaingen_helpers.h b/tests/core_tests/chaingen_helpers.h index dacef62c..0fc0879e 100644 --- a/tests/core_tests/chaingen_helpers.h +++ b/tests/core_tests/chaingen_helpers.h @@ -137,6 +137,27 @@ inline bool mine_next_pow_blocks_in_playtime(const currency::account_public_addr return true; } +inline bool mine_next_pow_blocks_in_playtime_with_given_txs(const currency::account_public_address& miner_addr, const std::vector& txs, currency::core& c, size_t blocks_count, const crypto::hash& prev_id) +{ + std::vector txs_local = txs; + + crypto::hash prev_id_internal = prev_id; + currency::block prv_block = AUTO_VAL_INIT(prv_block); + bool r = c.get_blockchain_storage().get_block_by_hash(prev_id, prv_block); + CHECK_AND_ASSERT_MES(r, false, "block with id " << prev_id << " not found"); + + for (size_t i = 0; i != blocks_count; i++) + { + if (!mine_next_pow_block_in_playtime_with_given_txs(miner_addr, c, txs_local, prev_id_internal, currency::get_block_height(prv_block)+1, &prv_block)) + return false; + prev_id_internal = get_block_hash(prv_block); + txs_local.clear(); + } + return true; +} + + + // NOTE: stake coins return back to the wallet, newly generated coins go to miner_address (by default they are the same destinations) inline bool mine_next_pos_block_in_playtime_with_wallet(tools::wallet2& w, const currency::account_public_address& miner_address, size_t& pos_entries_count) { diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 54ad220c..82ecd9c8 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -1023,6 +1023,7 @@ int main(int argc, char* argv[]) // atomics GENERATE_AND_PLAY(atomic_simple_test); GENERATE_AND_PLAY(atomic_test_wrong_redeem_wrong_refund); + GENERATE_AND_PLAY(atomic_test_altchain_simple); // GENERATE_AND_PLAY(gen_block_reward); // END OF TESTS */