From fca90b0146faaf76653378abaa42ea2cd07c48c0 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 24 Jan 2021 21:00:43 +0100 Subject: [PATCH] implemented tracking of htlc in wallet(creation is not implemented yet) --- src/currency_core/blockchain_storage.cpp | 6 +- src/currency_core/blockchain_storage_basic.h | 2 + src/currency_core/currency_basic.h | 8 +- .../currency_boost_serialization.h | 4 +- src/currency_core/currency_config.h | 2 +- src/currency_core/currency_format_utils.cpp | 24 ++- src/currency_core/currency_format_utils.h | 5 + src/wallet/wallet2.cpp | 149 ++++++++++++++++-- src/wallet/wallet2.h | 16 ++ 9 files changed, 193 insertions(+), 23 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index ca203bbc..37e14534 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -6246,7 +6246,7 @@ bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, { const txout_htlc& out_htlc = boost::get(out_in_alt); bool htlc_expired = out_htlc.expiration > (height_of_current_alt_block - height_of_source_block) ? false:true; - pk = htlc_expired ? out_htlc.pkey_after_expiration : out_htlc.pkey_before_expiration; + pk = htlc_expired ? out_htlc.pkey_refund : out_htlc.pkey_redeem; //input_v } pub_key_pointers.push_back(&pk); @@ -6299,7 +6299,7 @@ bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, //source is hltc out const txout_htlc& htlc = boost::get(out_target_v); bool htlc_expired = htlc.expiration > (height_of_current_alt_block - height_of_source_block) ? false : true; - pk = htlc_expired ? htlc.pkey_after_expiration : htlc.pkey_before_expiration; + pk = htlc_expired ? htlc.pkey_refund : htlc.pkey_redeem; pub_key_pointers.push_back(&pk); continue; } @@ -6350,7 +6350,7 @@ bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, { const txout_htlc& htlc = boost::get(t); bool htlc_expired = htlc.expiration > (height_of_current_alt_block - height_of_source_block) ? false : true; - pk = htlc_expired ? htlc.pkey_after_expiration : htlc.pkey_before_expiration; + pk = htlc_expired ? htlc.pkey_refund : htlc.pkey_redeem; } // case b4 (make sure source tx in the main chain is preceding split point, otherwise this referece is invalid) diff --git a/src/currency_core/blockchain_storage_basic.h b/src/currency_core/blockchain_storage_basic.h index 6d2a1300..8a487003 100644 --- a/src/currency_core/blockchain_storage_basic.h +++ b/src/currency_core/blockchain_storage_basic.h @@ -154,4 +154,6 @@ namespace currency transactions_map onboard_transactions; }; + + } \ No newline at end of file diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index b5b0d8d4..274a2f25 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -265,15 +265,15 @@ namespace currency crypto::hash htlc_hash; uint8_t flags; //select type of the hash, may be some extra info in future uint64_t expiration; - crypto::public_key pkey_before_expiration; - crypto::public_key pkey_after_expiration; + crypto::public_key pkey_redeem; //works before expiration + crypto::public_key pkey_refund; //works after expiration BEGIN_SERIALIZE_OBJECT() FIELD(htlc_hash) FIELD(flags) VARINT_FIELD(expiration) - FIELD(pkey_before_expiration) - FIELD(pkey_after_expiration) + FIELD(pkey_redeem) + FIELD(pkey_refund) END_SERIALIZE() }; diff --git a/src/currency_core/currency_boost_serialization.h b/src/currency_core/currency_boost_serialization.h index 25cce5e8..96f95bdc 100644 --- a/src/currency_core/currency_boost_serialization.h +++ b/src/currency_core/currency_boost_serialization.h @@ -62,8 +62,8 @@ namespace boost a & x.expiration; a & x.flags; a & x.htlc_hash; - a & x.pkey_before_expiration; - a & x.pkey_after_expiration; + a & x.pkey_redeem; + a & x.pkey_refund; } template diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index c6b106b4..8b576556 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -222,7 +222,7 @@ #define BC_OFFERS_CURRENCY_MARKET_FILENAME "market.bin" -#define WALLET_FILE_SERIALIZATION_VERSION (CURRENCY_FORMATION_VERSION+68) +#define WALLET_FILE_SERIALIZATION_VERSION (CURRENCY_FORMATION_VERSION+69) #define CURRENT_MEMPOOL_ARCHIVE_VER (CURRENCY_FORMATION_VERSION+31) diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index d5ab6cff..74992272 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -1674,6 +1674,12 @@ namespace currency } //--------------------------------------------------------------- bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& money_transfered, crypto::key_derivation& derivation) + { + std::list htlc_info_list; + return lookup_acc_outs(acc, tx, tx_pub_key, outs, money_transfered, derivation, htlc_info_list); + } + //--------------------------------------------------------------- + bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& money_transfered, crypto::key_derivation& derivation, std::list& htlc_info_list) { money_transfered = 0; bool r = generate_key_derivation(tx_pub_key, acc.view_secret_key, derivation); @@ -1685,7 +1691,6 @@ namespace currency return lookup_acc_outs_genesis(acc, tx, tx_pub_key, outs, money_transfered, derivation); } - if (!check_tx_derivation_hint(tx, derivation)) return true; @@ -1708,6 +1713,23 @@ namespace currency //don't count this money } } + else if (o.target.type() == typeid(txout_htlc)) + { + htlc_info hi = AUTO_VAL_INIT(hi); + const txout_htlc& htlc = boost::get(o.target); + if (is_out_to_acc(acc, htlc.pkey_redeem, derivation, i)) + { + hi.hltc_our_out_is_before_expiration = true; + htlc_info_list.push_back(hi); + outs.push_back(i); + } + else if (is_out_to_acc(acc, htlc.pkey_refund, derivation, i)) + { + hi.hltc_our_out_is_before_expiration = false; + htlc_info_list.push_back(hi); + outs.push_back(i); + } + } else { LOG_ERROR("Wrong type at lookup_acc_outs, unexpected type is: " << o.target.type().name()); diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 4c4dde90..67fc8f97 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -132,6 +132,11 @@ namespace currency END_KV_SERIALIZE_MAP() }; + struct htlc_info + { + bool hltc_our_out_is_before_expiration; + }; + //--------------------------------------------------------------- bool construct_miner_tx(size_t height, size_t median_size, const boost::multiprecision::uint128_t& already_generated_coins, diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 005b6cd9..51110bcd 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -374,6 +374,31 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t mtd.spent_indices.push_back(i); } } + else if (in.type() == typeid(currency::txin_htlc)) + { + const currency::txin_htlc& in_htlc = boost::get(in); + if (in_htlc.key_offsets.size() != 1) + { + LOG_ERROR("in_htlc.key_offsets.size() != 1, skip inout"); + continue; + } + + if (in_htlc.key_offsets[0].type() != typeid(uint64_t)) + { + LOG_ERROR("HTLC with ref_by_id is not supported by wallet yet"); + continue; + } + + auto it = m_active_htlcs.find(std::make_pair(in_htlc.amount, boost::get<>(in_htlc.key_offsets[0]))); + if (it != m_active_htlcs.end()) + { + transfer_details& td = m_transfers[it->second]; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td->m_ptx_wallet_info->m_tx.vout.size() > td.m_internal_output_index, "Internal error: wrong index in m_transfers"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td->m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].target.type() > typeid(), "Internal error: wrong index in m_transfers"); + //input spend active htlc + m_transfers[it->second].m_spent_height = height; + } + } i++; } @@ -386,7 +411,8 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t //check for transaction income crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - r = lookup_acc_outs(m_account.get_keys(), tx, tx_pub_key, outs, tx_money_got_in_outs, derivation); + std::list htlc_info_list; + r = lookup_acc_outs(m_account.get_keys(), tx, tx_pub_key, outs, tx_money_got_in_outs, derivation, htlc_info_list); THROW_IF_TRUE_WALLET_EX(!r, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); if(!outs.empty() /*&& tx_money_got_in_outs*/) @@ -408,7 +434,8 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t if (!pglobal_indexes || (pglobal_indexes->size() == 0 && tx.vout.size() != 0)) { - if (m_use_deffered_global_outputs) + //if tx contain htlc_out, then we would need global_indexes anyway, to be able later detect redeem of htlc + if (m_use_deffered_global_outputs && htlc_info_list.size() == 0) { pglobal_indexes = nullptr; } @@ -423,9 +450,32 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t { size_t o = outs[i_in_outs]; WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(o < tx.vout.size(), "wrong out in transaction: internal index=" << o << ", total_outs=" << tx.vout.size()); - if (tx.vout[o].target.type() == typeid(txout_to_key)) + if (tx.vout[o].target.type() == typeid(txout_to_key) || tx.vout[o].target.type() == typeid(txout_htlc)) { - const currency::txout_to_key& otk = boost::get(tx.vout[o].target); + bool hltc_our_out_is_before_expiration = false; + crypto::public_key out_key = null_pkey; + if (tx.vout[o].target.type() == typeid(txout_to_key)) + { + out_key = boost::get(tx.vout[o].target).key; + } + else if (tx.vout[o].target.type() == typeid(txout_htlc)) + { + THROW_IF_FALSE_WALLET_EX(htlc_info_list.size() > 0, "Found txout_htlc out but htlc_info_list is empty"); + if (htlc_info_list.front().hltc_our_out_is_before_expiration) + { + out_key = boost::get(tx.vout[o].target).pkey_redeem; + } + else + { + out_key = boost::get(tx.vout[o].target).pkey_refund; + } + htlc_info_list.pop_front(); + } + else + { + THROW_IF_FALSE_WALLET_EX(false, "Unexpected out type im wallet: " << tx.vout[o].target.type().name()); + } + //const currency::txout_to_key& otk = boost::get(tx.vout[o].target); // obtain key image for this output crypto::key_image ki = currency::null_ki; @@ -435,16 +485,16 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t { // don't have spend secret key, so we unable to calculate key image for an output // look it up in special container instead - auto it = m_pending_key_images.find(otk.key); + auto it = m_pending_key_images.find(out_key); if (it != m_pending_key_images.end()) { ki = it->second; - WLT_LOG_L1("pending key image " << ki << " was found by out pub key " << otk.key); + WLT_LOG_L1("pending key image " << ki << " was found by out pub key " << out_key); } else { ki = currency::null_ki; - WLT_LOG_L1("can't find pending key image by out pub key: " << otk.key << ", key image temporarily set to null"); + WLT_LOG_L1("can't find pending key image by out pub key: " << out_key << ", key image temporarily set to null"); } } } @@ -453,7 +503,7 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t // normal wallet, calculate and store key images for own outs currency::keypair in_ephemeral; currency::generate_key_image_helper(m_account.get_keys(), tx_pub_key, o, in_ephemeral, ki); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(in_ephemeral.pub == otk.key, "key_image generated ephemeral public key that does not match with output_key"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(in_ephemeral.pub == out_key, "key_image generated ephemeral public key that does not match with output_key"); } if (ki != currency::null_ki) @@ -477,11 +527,12 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t } } - if (is_auditable() && otk.mix_attr != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX) + if (is_auditable() && tx.vout[o].target.type() == typeid(txout_to_key) && + boost::get(tx.vout[o].target).mix_attr != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX) { std::stringstream ss; ss << "output #" << o << " from tx " << get_transaction_hash(tx) << " with amount " << print_money_brief(tx.vout[o].amount) - << " is targeted to this auditable wallet and has INCORRECT mix_attr = " << (uint64_t)otk.mix_attr << ". Output IGNORED."; + << " is targeted to this auditable wallet and has INCORRECT mix_attr = " << (uint64_t)boost::get(tx.vout[o].target).mix_attr << ". Output IGNORED."; WLT_LOG_RED(ss.str(), LOG_LEVEL_0); if (m_wcallback) m_wcallback->on_message(i_wallet2_callback::ms_red, ss.str()); @@ -517,11 +568,28 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER; } } + uint64_t amount = tx.vout[o].amount; + if (tx.vout[o].target.type() == typeid(txout_htlc)) + { + const txout_htlc& hltc = boost::get(tx.vout[o].target); + //mark this as spent + td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; + //create entry for htlc input + htlc_expiration_trigger het = AUTO_VAL_INIT(het); + het.is_wallet_owns_redeem = (out_key == hltc.pkey_redeem) ? true:false; + het.transfer_index = m_transfers.size() - 1; + uint64_t expired_if_more_then = td.m_ptx_wallet_info->m_block_height + hltc.expiration; + m_htlcs[expired_if_more_then] = het; + + //active htlc + auto amount_gindex_pair = std::make_pair(amount, td.m_global_output_index); + m_active_htlcs[amount_gindex_pair] = transfer_index; + } size_t transfer_index = m_transfers.size()-1; if (td.m_key_image != currency::null_ki) m_key_images[td.m_key_image] = transfer_index; - add_transfer_to_transfers_cache(tx.vout[o].amount, transfer_index); - uint64_t amount = tx.vout[o].amount; + + add_transfer_to_transfers_cache(amount, transfer_index); if (is_watch_only() && is_auditable()) { @@ -545,6 +613,10 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t tdb.m_internal_output_index = o; WLT_LOG_L0("Received multisig, multisig out id: " << multisig_id << ", amount: " << tdb.amount() << ", with tx: " << get_transaction_hash(tx)); } + else if (tx.vout[o].target.type() == typeid(txout_htlc)) + { + + } } } @@ -1222,6 +1294,49 @@ 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 deactivated and set to innactive + if (it->second.is_wallet_owns_redeem) + { + // this means that wallet received atomic as proposal but never activated it, money returned to initiator + tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //re assure that it has spent flag + tr.m_spent_height = height; + } + else + { + // this means that wallet created atomic by itself, and second part didn't redeem it, so refund money should become available + 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; + } + //remove it from active contracts + auto it_active_htlc = m_active_htlcs.find(std::make_pair(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].amount, tr.m_global_output_index)); + if (it_active_htlc == m_active_htlcs.end()) + { + LOG_ERROR("Erasing htlc, but it seems to be already erased"); + } + else + { + m_active_htlcs.erase(it); + } + } +} +//---------------------------------------------------------------------------------------------------- void wallet2::process_new_blockchain_entry(const currency::block& b, const currency::block_direct_data_entry& bche, const crypto::hash& bl_id, uint64_t height) { //handle transactions from new block @@ -1229,6 +1344,9 @@ 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 const std::vector* pglobal_index = nullptr; if (b.timestamp + 60 * 60 * 24 > m_account.get_createtime()) @@ -2082,6 +2200,13 @@ 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 == typeid(txout_htlc)) + { + 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; + } } } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 37bf0b09..3ae9bcdd 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -735,6 +735,12 @@ namespace tools { wipeout_extra_if_needed(m_transfer_history); } + + if (ver < 153) + return; + + a & m_htlcs; + } void wipeout_extra_if_needed(std::vector& transfer_history); @@ -761,6 +767,7 @@ namespace tools const currency::block_direct_data_entry& bche, const crypto::hash& bl_id, uint64_t height); + void process_htlc_triggers_on_block_added(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); @@ -969,6 +976,15 @@ private: std::unordered_set m_unconfirmed_multisig_transfers; std::unordered_map m_tx_keys; + //used in wallet + struct htlc_expiration_trigger + { + bool is_wallet_owns_redeem; //specify if this HTLC belong to this wallet by pkey_redeem or by pkey_refund + uint64_t transfer_index; + }; + std::multimap m_htlcs; //uint64_t -> height of expiration + amount_gindex_to_transfer_id_container m_active_htlcs; // map [amount; gindex] -> tid + std::shared_ptr m_core_proxy; std::shared_ptr m_wcallback; uint64_t m_height_of_start_sync;