From 79040fa9b3e72b43a95e848299ebbdf3ed1338b8 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 20 Jan 2021 23:16:28 +0100 Subject: [PATCH] validate_alt_block_input deep refactoring related to htlc --- src/currency_core/blockchain_storage.cpp | 176 ++++++++++++++++++----- src/currency_core/blockchain_storage.h | 26 +++- 2 files changed, 162 insertions(+), 40 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index a98d2799..1247420e 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -6184,6 +6184,9 @@ bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, CHECK_AND_ASSERT_MES(pub_keys.size() == abs_key_offsets.size(), false, "pub_keys.size()==" << pub_keys.size() << " != abs_key_offsets.size()==" << abs_key_offsets.size()); // just a little bit of paranoia std::vector pub_key_pointers; + + uint64_t height_of_current_alt_block = alt_chain.size() ? alt_chain.back()->second.height + 1 : split_height + 1; + for (size_t pk_n = 0; pk_n < pub_keys.size(); ++pk_n) { crypto::public_key& pk = pub_keys[pk_n]; @@ -6218,7 +6221,34 @@ bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, uint64_t local_offset = offset_gindex - it_aag->second; auto& alt_keys = (*alt_it)->second.outputs_pub_keys; CHECK_AND_ASSERT_MES(local_offset < alt_keys[input_to_key.amount].size(), false, "Internal error: local_offset=" << local_offset << " while alt_keys[" << input_to_key.amount << " ].size()=" << alt_keys.size()); - pk = alt_keys[input_to_key.amount][local_offset]; + const output_key_or_htlc_v& out_in_alt = alt_keys[input_to_key.amount][local_offset]; + + /* + here we do validation against compatibility of input and output type + + TxOutput | TxInput | Allowed + ---------------------------- + HTLC | HTLC | ONLY IF HTLC NOT EXPIRED + HTLC | TO_KEY | ONLY IF HTLC IS EXPIRED + TO_KEY | HTLC | NOT + TO_KEY | TO_KEY | YES + */ + uint64_t height_of_source_block = (*alt_it)->second.height; + CHECK_AND_ASSERT_MES(height_of_current_alt_block > height_of_source_block, false, "Intenral error: height_of_current_alt_block > height_of_source_block failed"); + bool r = is_output_allowed_for_input(out_in_alt, input_v, height_of_current_alt_block - height_of_source_block); + CHECK_AND_ASSERT_MES(r, false, "Input and output incompatible type"); + + if (out_in_alt.type() == typeid(crypto::public_key)) + { + pk = boost::get(out_in_alt); + } + else + { + const txout_htlc& out_htlc = boost::get(out_in_alt); + bool htlc_expired = 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; + //input_v + } pub_key_pointers.push_back(&pk); found_the_key = true; break; @@ -6242,34 +6272,37 @@ bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, auto it = alt_chain_tx_ids.find(tx_id); if (it != alt_chain_tx_ids.end()) { + uint64_t height_of_source_block = it->second.second; + CHECK_AND_ASSERT_MES(height_of_current_alt_block > height_of_source_block, false, "Intenral error: height_of_current_alt_block > height_of_source_block failed"); + + /* + here we do validation against compatibility of input and output type + + TxOutput | TxInput | Allowed + ---------------------------- + HTLC | HTLC | ONLY IF HTLC NOT EXPIRED + HTLC | TO_KEY | ONLY IF HTLC IS EXPIRED + TO_KEY | HTLC | NOT + TO_KEY | TO_KEY | YES + */ + + bool r = is_output_allowed_for_input(out_target_v, input_v, height_of_current_alt_block - height_of_source_block); + CHECK_AND_ASSERT_MES(r, false, "Input and output incompatible type"); + //source tx found in altchain CHECK_AND_ASSERT_MES(it->second.first.vout.size() > out_n, false, "Internal error: out_n(" << out_n << ") >= it->second.vout.size()(" << it->second.first.vout.size() << ")"); txout_target_v out_target_v = it->second.first.vout[out_n].target; if (out_target_v.type() == typeid(txout_htlc)) { //source is hltc out - const txout_htlc& htlc = boost::get(out_target_v); - uint64_t height_of_source_block = it->second.second; - uint64_t height_of_current_alt_block = alt_chain.size() ? alt_chain.back()->second.height + 1 : split_height + 1; - CHECK_AND_ASSERT_MES(height_of_current_alt_block > height_of_source_block, false, "Intenral error: height_of_current_alt_block > height_of_source_block failed"); - if (htlc.expiration > height_of_current_alt_block - height_of_source_block) - { - //HTLC IS NOT expired, can be used ONLY by pkey_before_expiration and ONLY by HTLC input - CHECK_AND_ASSERT_MES(input_v.type() == typeid(txin_htlc), false, "[TXOUT_HTLC]: Unexpected output type of non-HTLC input"); - pk = htlc.pkey_before_expiration; - } - else - { - //HTLC IS expired, can be used ONLY by pkey_after_expiration and ONLY by to_key input - CHECK_AND_ASSERT_MES(input_v.type() == typeid(txin_to_key), false, "[TXOUT_HTLC]: Unexpected output type of HTLC input"); - pk = htlc.pkey_after_expiration; - } + 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; pub_key_pointers.push_back(&pk); continue; } else if (out_target_v.type() == typeid(txout_to_key)) { - CHECK_AND_ASSERT_MES(input_v.type() != typeid(txin_htlc), false, "Forbidden output type referenced by in tx ( input txin_htlc refered to txout_to_key )"); //source is to_key out pk = boost::get(out_target_v).key; pub_key_pointers.push_back(&pk); @@ -6279,14 +6312,6 @@ bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, { ASSERT_MES_AND_THROW("Unexpected out type for tx_in in altblock: " << out_target_v.type().name()); } - - - //let's validate against htlc&to_key - pk = alt_keys[input_to_key.amount][local_offset]; - pub_key_pointers.push_back(&pk); - found_the_key = true; - - } } @@ -6294,11 +6319,36 @@ bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, auto p = m_db_transactions.get(tx_id); CHECK_AND_ASSERT_MES(p != nullptr && out_n < p->tx.vout.size(), false, "can't find output #" << out_n << " for tx " << tx_id << " referred by offset #" << pk_n); auto &t = p->tx.vout[out_n].target; - CHECK_AND_ASSERT_MES(t.type() == typeid(txout_to_key), false, "txin_to_key input offset #" << pk_n << " refers to incorrect output type " << t.type().name()); - auto& out_tk = boost::get(t); - pk = out_tk.key; - bool mixattr_ok = is_mixattr_applicable_for_fake_outs_counter(out_tk.mix_attr, abs_key_offsets.size() - 1); - CHECK_AND_ASSERT_MES(mixattr_ok, false, "input offset #" << pk_n << " violates mixin restrictions: mix_attr = " << static_cast(out_tk.mix_attr) << ", input's key_offsets.size = " << abs_key_offsets.size()); + + /* + here we do validation against compatibility of input and output type + + TxOutput | TxInput | Allowed + ---------------------------- + HTLC | HTLC | ONLY IF HTLC NOT EXPIRED + HTLC | TO_KEY | ONLY IF HTLC IS EXPIRED + TO_KEY | HTLC | NOT + TO_KEY | TO_KEY | YES + */ + uint64_t height_of_source_block = p->m_keeper_block_height; + bool r = is_output_allowed_for_input(t, input_v, height_of_current_alt_block - height_of_source_block); + CHECK_AND_ASSERT_MES(r, false, "Input and output incompatible type"); + + if (t.type() == typeid(txout_to_key)) + { + const txout_to_key& out_tk = boost::get(t); + pk = out_tk.key; + + bool mixattr_ok = is_mixattr_applicable_for_fake_outs_counter(out_tk.mix_attr, abs_key_offsets.size() - 1); + CHECK_AND_ASSERT_MES(mixattr_ok, false, "input offset #" << pk_n << " violates mixin restrictions: mix_attr = " << static_cast(out_tk.mix_attr) << ", input's key_offsets.size = " << abs_key_offsets.size()); + + } + else if (t.type() == typeid(txout_htlc)) + { + 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; + } // case b4 (make sure source tx in the main chain is preceding split point, otherwise this referece is invalid) CHECK_AND_ASSERT_MES(p->m_keeper_block_height < split_height, false, "input offset #" << pk_n << " refers to main chain tx " << tx_id << " at height " << p->m_keeper_block_height << " while split height is " << split_height); @@ -6324,6 +6374,58 @@ bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, return true; } //------------------------------------------------------------------ +bool blockchain_storage::is_output_allowed_for_input(const txout_target_v& out_v, const txin_v& in_v, uint64_t top_minus_source_height) +{ + + /* + TxOutput | TxInput | Allowed + ---------------------------- + HTLC | HTLC | ONLY IF HTLC NOT EXPIRED + HTLC | TO_KEY | ONLY IF HTLC IS EXPIRED + TO_KEY | HTLC | NOT + TO_KEY | TO_KEY | YES + */ + + + if (out_v.type() == typeid(txout_to_key)) + { + return is_output_allowed_for_input(boost::get(out_v), in_v); + } + else if (out_v.type() == typeid(txout_htlc)) + { + return is_output_allowed_for_input(boost::get(out_v), in_v, top_minus_source_height); + } + else + { + LOG_ERROR("[scan_outputkeys_for_indexes]: Wrong output type in : " << out_v.type().name()); + return false; + } + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::is_output_allowed_for_input(const txout_htlc& out_v, const txin_v& in_v, uint64_t top_minus_source_height) +{ + bool htlc_expired = out_v.expiration > (top_minus_source_height) ? false : true; + if (!hltc_expired) + { + //HTLC IS NOT expired, can be used ONLY by pkey_before_expiration and ONLY by HTLC input + CHECK_AND_ASSERT_MES(in_v.type() == typeid(txin_htlc), false, "[TXOUT_HTLC]: Unexpected output type of non-HTLC input"); + } + else + { + //HTLC IS expired, can be used ONLY by pkey_after_expiration and ONLY by to_key input + CHECK_AND_ASSERT_MES(in_v.type() == typeid(txin_to_key), false, "[TXOUT_HTLC]: Unexpected output type of HTLC input"); + } + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::is_output_allowed_for_input(const txout_to_key& out_v, const txin_v& in_v) +{ + //HTLC input CAN'T refer to regular to_key output + CHECK_AND_ASSERT_MES(in_v.type() != typeid(txin_htlc), false, "[TXOUT_TO_KEY]: Unexpected output type of HTLC input"); + return true; +} +//------------------------------------------------------------------ bool blockchain_storage::validate_alt_block_ms_input(const transaction& input_tx, const crypto::hash& input_tx_hash, size_t input_index, const std::vector& input_sigs, uint64_t split_height, const alt_chain_type& alt_chain) const { // Main and alt chain outline: @@ -6465,7 +6567,7 @@ bool blockchain_storage::update_alt_out_indexes_for_tx_in_block(const transactio //add tx outputs to gindex_lookup_table for (auto o : tx.vout) { - if (o.target.type() == typeid(txout_to_key)) + if (o.target.type() == typeid(txout_to_key) || o.target.type() == typeid(txout_htlc)) { //LOG_PRINT_MAGENTA("ALT_OUT KEY ON H[" << abei.height << "] AMOUNT: " << o.amount, LOG_LEVEL_0); // first, look at local gindexes tables @@ -6475,7 +6577,15 @@ bool blockchain_storage::update_alt_out_indexes_for_tx_in_block(const transactio abei.gindex_lookup_table[o.amount] = m_db_outputs.get_item_size(o.amount); //LOG_PRINT_MAGENTA("FIRST TOUCH: size=" << abei.gindex_lookup_table[o.amount], LOG_LEVEL_0); } - abei.outputs_pub_keys[o.amount].push_back(boost::get(o.target).key); + if (o.target.type() == typeid(txout_to_key)) + { + abei.outputs_pub_keys[o.amount].push_back(boost::get(o.target).key); + } + else + { + abei.outputs_pub_keys[o.amount].push_back(boost::get(o.target)); + } + //TODO: At the moment we ignore check of mix_attr again mixing to simplify alt chain check, but in future consider it for stronger validation } } diff --git a/src/currency_core/blockchain_storage.h b/src/currency_core/blockchain_storage.h index b0b5f826..7220d3cf 100644 --- a/src/currency_core/blockchain_storage.h +++ b/src/currency_core/blockchain_storage.h @@ -150,7 +150,9 @@ namespace currency // retrieve gindex from local_gindex_lookup_table # there are outputs having given amount after the given height // else: // retrieve gindex from main chain gindex table # not outputs having given amount are present after the given height - // + // + + typedef boost::variant output_key_or_htlc_v; struct alt_block_extended_info: public block_extended_info { @@ -158,7 +160,7 @@ namespace currency std::map gindex_lookup_table; // {amount -> pub_keys} map of outputs' pub_keys appeared in this alt block ( index_in_vector == output_gindex - gindex_lookup_table[output_amount] ) - std::map > outputs_pub_keys; + std::map > outputs_pub_keys; //date added to alt chain storage uint64_t timestamp; @@ -641,6 +643,9 @@ namespace currency void calculate_local_gindex_lookup_table_for_height(uint64_t split_height, std::map& increments) const; void do_erase_altblock(alt_chain_container::iterator it); uint64_t get_blockchain_launch_timestamp()const; + bool is_output_allowed_for_input(const txout_target_v& out_v, const txin_v& in_v, uint64_t top_minus_source_height); + bool is_output_allowed_for_input(const txout_to_key& out_v, const txin_v& in_v); + bool is_output_allowed_for_input(const txout_htlc& out_v, const txin_v& in_v, uint64_t top_minus_source_height); @@ -739,11 +744,20 @@ namespace currency //CHECKED_GET_SPECIFIC_VARIANT(tx_ptr->tx.vout[n].target, const txout_to_key, outtk, false); CHECK_AND_ASSERT_MES(key_offsets.size() >= 1, false, "internal error: tx input has empty key_offsets"); // should never happen as input correctness must be handled by the caller + /* + TxOutput | TxInput | Allowed + ---------------------------- + HTLC | HTLC | ONLY IF HTLC NOT EXPIRED + HTLC | TO_KEY | ONLY IF HTLC IS EXPIRED + TO_KEY | HTLC | NOT + TO_KEY | TO_KEY | YES + */ + + bool r = is_output_allowed_for_input(tx_ptr->tx.vout[n].target, verified_input, get_current_blockchain_size() - tx_ptr->m_keeper_block_height); + CHECK_AND_ASSERT_MES(r, false, "Input and output incompatible type"); + if (tx_ptr->tx.vout[n].target.type() == typeid(txout_to_key)) { - //HTLC input CAN'T refer to regular to_key output - CHECK_AND_ASSERT_MES(verified_input.type() != typeid(txin_htlc), false, "[TXOUT_TO_KEY]: Unexpected output type of HTLC input"); - CHECKED_GET_SPECIFIC_VARIANT(tx_ptr->tx.vout[n].target, const txout_to_key, outtk, false); //fix for burned money patch_out_if_needed(const_cast(outtk), tx_id, n); @@ -757,13 +771,11 @@ namespace currency if (htlc_out.expiration > get_current_blockchain_size() - tx_ptr->m_keeper_block_height) { //HTLC IS NOT expired, can be used ONLY by pkey_before_expiration and ONLY by HTLC input - CHECK_AND_ASSERT_MES(verified_input.type() == typeid(txin_htlc), false, "[TXOUT_HTLC]: Unexpected output type of non-HTLC input"); scan_context.htlc_is_expired = false; } else { //HTLC IS expired, can be used ONLY by pkey_after_expiration and ONLY by to_key input - CHECK_AND_ASSERT_MES(verified_input.type() == typeid(txin_to_key), false, "[TXOUT_HTLC]: Unexpected output type of HTLC input"); scan_context.htlc_is_expired = true; } }else