From 499f822e97704d464d8f7374d68672de584aaa98 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 15 Nov 2019 23:58:09 +0100 Subject: [PATCH] moved tx semantics validation to separate funtion, added to pool validation and to core validation --- src/currency_core/blockchain_storage.cpp | 47 ++++- src/currency_core/currency_core.cpp | 103 +--------- src/currency_core/currency_core.h | 7 - src/currency_core/currency_format_utils.cpp | 10 +- .../currency_format_utils_transactions.h | 2 +- src/currency_core/tx_semantic_validation.cpp | 179 ++++++++++++++++++ src/currency_core/tx_semantic_validation.h | 16 ++ 7 files changed, 242 insertions(+), 122 deletions(-) create mode 100644 src/currency_core/tx_semantic_validation.cpp create mode 100644 src/currency_core/tx_semantic_validation.h diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index bd72ce8c..b90cc1bc 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -32,6 +32,7 @@ #include "storages/portable_storage_template_helper.h" #include "basic_pow_helpers.h" #include "version.h" +#include "tx_semantic_validation.h" #undef LOG_DEFAULT_CHANNEL #define LOG_DEFAULT_CHANNEL "core" @@ -4762,6 +4763,18 @@ wide_difficulty_type blockchain_storage::get_last_alt_x_block_cumulative_precise return 0; } //------------------------------------------------------------------ +bool get_tx_from_cache(const crypto::hash& tx_id, std::unordered_map& tx_cache, transaction& tx, size_t& blob_size, uint64_t fee) +{ + auto it = tx_cache.find(tx_id); + if (it == tx_cache.end()) + return false; + + tx = it->second; + blob_size = get_object_blobsize(tx); + fee = get_tx_fee(tx); + return true; +} +//------------------------------------------------------------------ bool blockchain_storage::handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc) { TIME_MEASURE_START_PD_MS(block_processing_time_0_ms); @@ -4882,7 +4895,10 @@ bool blockchain_storage::handle_block_to_main_chain(const block& bl, const crypt transaction tx; size_t blob_size = 0; uint64_t fee = 0; - if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee)) + + bool taken_from_cache = get_tx_from_cache(tx_id, bvc.m_onboard_transactions, tx, blob_size, fee); + bool taken_from_pool = m_tx_pool.take_tx(tx_id, tx, blob_size, fee); + if(!taken_from_cache && !taken_from_pool) { LOG_PRINT_L0("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id); purge_block_data_from_blockchain(bl, tx_processed_count); @@ -4891,6 +4907,15 @@ bool blockchain_storage::handle_block_to_main_chain(const block& bl, const crypt return false; } + if (!validate_tx_semantic(tx, blob_size)) + { + LOG_PRINT_L0("Block with id: " << id << " has at least one transaction with wrong semantic, tx_id: " << tx_id); + purge_block_data_from_blockchain(bl, tx_processed_count); + //add_block_as_invalid(bl, id); + bvc.m_verification_failed = true; + return false; + } + append_per_block_increments_for_tx(tx, gindices); //If we under checkpoints, ring signatures should be pruned @@ -4905,9 +4930,12 @@ bool blockchain_storage::handle_block_to_main_chain(const block& bl, const crypt { LOG_PRINT_L0("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs."); currency::tx_verification_context tvc = AUTO_VAL_INIT(tvc); - bool add_res = m_tx_pool.add_tx(tx, tvc, true, true); - m_tx_pool.add_transaction_to_black_list(tx); - CHECK_AND_ASSERT_MES_NO_RET(add_res, "handle_block_to_main_chain: failed to add transaction back to transaction pool"); + if (taken_from_pool) + { + bool add_res = m_tx_pool.add_tx(tx, tvc, true, true); + m_tx_pool.add_transaction_to_black_list(tx); + CHECK_AND_ASSERT_MES_NO_RET(add_res, "handle_block_to_main_chain: failed to add transaction back to transaction pool"); + } purge_block_data_from_blockchain(bl, tx_processed_count); add_block_as_invalid(bl, id); LOG_PRINT_L0("Block with id " << id << " added as invalid because of wrong inputs in transactions"); @@ -4925,10 +4953,13 @@ bool blockchain_storage::handle_block_to_main_chain(const block& bl, const crypt if(!add_transaction_from_block(tx, tx_id, id, current_bc_size, actual_timestamp)) { LOG_PRINT_L0("Block with id: " << id << " failed to add transaction to blockchain storage"); - currency::tx_verification_context tvc = AUTO_VAL_INIT(tvc); - bool add_res = m_tx_pool.add_tx(tx, tvc, true, true); - m_tx_pool.add_transaction_to_black_list(tx); - CHECK_AND_ASSERT_MES_NO_RET(add_res, "handle_block_to_main_chain: failed to add transaction back to transaction pool"); + if (taken_from_pool) + { + currency::tx_verification_context tvc = AUTO_VAL_INIT(tvc); + bool add_res = m_tx_pool.add_tx(tx, tvc, true, true); + m_tx_pool.add_transaction_to_black_list(tx); + CHECK_AND_ASSERT_MES_NO_RET(add_res, "handle_block_to_main_chain: failed to add transaction back to transaction pool"); + } purge_block_data_from_blockchain(bl, tx_processed_count); bvc.m_verification_failed = true; return false; diff --git a/src/currency_core/currency_core.cpp b/src/currency_core/currency_core.cpp index 20e81fed..77052259 100644 --- a/src/currency_core/currency_core.cpp +++ b/src/currency_core/currency_core.cpp @@ -19,6 +19,7 @@ using namespace epee; #include "currency_format_utils.h" #include "misc_language.h" #include "string_coding.h" +#include "tx_semantic_validation.h" #define MINIMUM_REQUIRED_FREE_SPACE_BYTES (1024 * 1024 * 100) @@ -185,13 +186,15 @@ namespace currency //----------------------------------------------------------------------------------------------- bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, bool kept_by_block) { + CHECK_AND_ASSERT_MES(!kept_by_block, false, "Transaction associated with block came throw handle_incoming_tx!(not allowed anymore)"); + tvc = boost::value_initialized(); //want to process all transactions sequentially TIME_MEASURE_START_MS(wait_lock_time); CRITICAL_REGION_LOCAL(m_incoming_tx_lock); TIME_MEASURE_FINISH_MS(wait_lock_time); - if(tx_blob.size() > get_max_tx_size()) + if(tx_blob.size() > CURRENCY_MAX_TRANSACTION_BLOB_SIZE) { LOG_PRINT_L0("WRONG TRANSACTION BLOB, too big size " << tx_blob.size() << ", rejected"); tvc.m_verification_failed = true; @@ -210,19 +213,10 @@ namespace currency TIME_MEASURE_FINISH_MS(parse_tx_time); - TIME_MEASURE_START_MS(check_tx_syntax_time); - if(!check_tx_syntax(tx)) - { - LOG_PRINT_L0("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " syntax, rejected"); - tvc.m_verification_failed = true; - return false; - } - TIME_MEASURE_FINISH_MS(check_tx_syntax_time); - TIME_MEASURE_START_MS(check_tx_semantic_time); - if(!check_tx_semantic(tx, kept_by_block)) + if(!validate_tx_semantic(tx, tx_blob.size())) { - LOG_PRINT_L0("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " semantic, rejected"); + LOG_PRINT_L0("WRONG TRANSACTION SEMANTICS, Failed to check tx " << tx_hash << " semantic, rejected"); tvc.m_verification_failed = true; return false; } @@ -243,7 +237,6 @@ namespace currency } LOG_PRINT_L2("[CORE HANDLE_INCOMING_TX]: timing " << wait_lock_time << "/" << parse_tx_time - << "/" << check_tx_syntax_time << "/" << check_tx_semantic_time << "/" << add_new_tx_time); return r; @@ -296,88 +289,9 @@ namespace currency return true; } - //----------------------------------------------------------------------------------------------- - bool core::check_tx_semantic(const transaction& tx, bool kept_by_block) - { - if(!tx.vin.size()) - { - LOG_PRINT_RED_L0("tx with empty inputs, rejected for tx id= " << get_transaction_hash(tx)); - return false; - } - if(!check_inputs_types_supported(tx)) - { - LOG_PRINT_RED_L0("unsupported input types for tx id= " << get_transaction_hash(tx)); - return false; - } - if(!check_outs_valid(tx)) - { - LOG_PRINT_RED_L0("tx with invalid outputs, rejected for tx id= " << get_transaction_hash(tx)); - return false; - } - if(!check_money_overflow(tx)) - { - LOG_PRINT_RED_L0("tx has money overflow, rejected for tx id= " << get_transaction_hash(tx)); - return false; - } - - uint64_t amount_in = 0; - get_inputs_money_amount(tx, amount_in); - uint64_t amount_out = get_outs_money_amount(tx); - - if(amount_in < amount_out) - { - LOG_PRINT_RED_L0("tx with wrong amounts: ins " << amount_in << ", outs " << amount_out << ", rejected for tx id= " << get_transaction_hash(tx)); - return false; - } - - if(!kept_by_block && get_object_blobsize(tx) >= m_blockchain_storage.get_current_comulative_blocksize_limit() - CURRENCY_COINBASE_BLOB_RESERVED_SIZE) - { - LOG_PRINT_RED_L0("tx has too big size " << get_object_blobsize(tx) << ", expected no bigger than " << m_blockchain_storage.get_current_comulative_blocksize_limit() - CURRENCY_COINBASE_BLOB_RESERVED_SIZE); - return false; - } - - //check if tx use different key images - if(!check_tx_inputs_keyimages_diff(tx)) - { - LOG_PRINT_RED_L0("tx inputs have the same key images"); - return false; - } - - if(!check_tx_extra(tx)) - { - LOG_PRINT_RED_L0("tx has wrong extra, rejected"); - return false; - } - - return true; - } - //----------------------------------------------------------------------------------------------- - bool core::check_tx_extra(const transaction& tx) - { - tx_extra_info ei = AUTO_VAL_INIT(ei); - bool r = parse_and_validate_tx_extra(tx, ei); - if(!r) - return false; - return true; - } - //----------------------------------------------------------------------------------------------- - bool core::check_tx_inputs_keyimages_diff(const transaction& tx) - { - std::unordered_set ki; - BOOST_FOREACH(const auto& in, tx.vin) - { - if (in.type() == typeid(txin_to_key)) - { - CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); - if (!ki.insert(tokey_in.k_image).second) - return false; - } - } - return true; - } //----------------------------------------------------------------------------------------------- bool core::add_new_tx(const transaction& tx, tx_verification_context& tvc, bool kept_by_block) { @@ -627,11 +541,6 @@ namespace currency { return parse_and_validate_tx_from_blob(blob, tx, tx_hash); } - //----------------------------------------------------------------------------------------------- - bool core::check_tx_syntax(const transaction& tx) - { - return true; - } //----------------------------------------------------------------------------------------------- bool core::get_pool_transactions(std::list& txs) { diff --git a/src/currency_core/currency_core.h b/src/currency_core/currency_core.h index 7fa9313a..cdadf317 100644 --- a/src/currency_core/currency_core.h +++ b/src/currency_core/currency_core.h @@ -118,12 +118,6 @@ namespace currency bool add_new_block(const block& b, block_verification_context& bvc); bool load_state_data(); bool parse_tx_from_blob(transaction& tx, crypto::hash& tx_hash, const blobdata& blob); - bool check_tx_extra(const transaction& tx); - - bool check_tx_syntax(const transaction& tx); - //check correct values, amounts and all lightweight checks not related with database - bool check_tx_semantic(const transaction& tx, bool kept_by_block); - //check if tx already in memory pool or in main blockchain bool is_key_image_spent(const crypto::key_image& key_im); @@ -132,7 +126,6 @@ namespace currency bool update_miner_block_template(); bool handle_command_line(const boost::program_options::variables_map& vm); bool on_update_blocktemplate_interval(); - bool check_tx_inputs_keyimages_diff(const transaction& tx); void notify_blockchain_update_listeners(); diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index 79a63b06..e6ec0d01 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -2426,15 +2426,7 @@ namespace currency return true; return false; } - size_t get_max_block_size() - { - return CURRENCY_MAX_BLOCK_SIZE; - } - //----------------------------------------------------------------------------------------------- - size_t get_max_tx_size() - { - return CURRENCY_MAX_TRANSACTION_BLOB_SIZE; - } + //----------------------------------------------------------------------------------------------- uint64_t get_base_block_reward(bool is_pos, const boost::multiprecision::uint128_t& already_generated_coins, uint64_t height) { diff --git a/src/currency_core/currency_format_utils_transactions.h b/src/currency_core/currency_format_utils_transactions.h index 626fc156..d903f38e 100644 --- a/src/currency_core/currency_format_utils_transactions.h +++ b/src/currency_core/currency_format_utils_transactions.h @@ -92,7 +92,7 @@ namespace currency inline void set_tx_flags(transaction& tx, uint64_t v) { set_tx_x_detail(tx, v); } inline void set_tx_expiration_time(transaction& tx, uint64_t v) { set_tx_x_detail(tx, v); } account_public_address get_crypt_address_from_destinations(const account_keys& sender_account_keys, const std::vector& destinations); - + //----------------------------------------------------------------------------------------------- bool is_tx_expired(const transaction& tx, uint64_t expiration_ts_median); uint64_t get_burned_amount(const transaction& tx); diff --git a/src/currency_core/tx_semantic_validation.cpp b/src/currency_core/tx_semantic_validation.cpp new file mode 100644 index 00000000..db29bce1 --- /dev/null +++ b/src/currency_core/tx_semantic_validation.cpp @@ -0,0 +1,179 @@ +// Copyright (c) 2018-2019 Zano Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + + + +#include "tx_semantic_validation.h" +#include "currency_format_utils.h" + +namespace currency +{ + //----------------------------------------------------------------------------------------------- + bool check_tx_extra(const transaction& tx) + { + tx_extra_info ei = AUTO_VAL_INIT(ei); + bool r = parse_and_validate_tx_extra(tx, ei); + if (!r) + return false; + return true; + } + //----------------------------------------------------------------------------------------------- + bool check_tx_inputs_keyimages_diff(const transaction& tx) + { + std::unordered_set ki; + BOOST_FOREACH(const auto& in, tx.vin) + { + if (in.type() == typeid(txin_to_key)) + { + CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); + if (!ki.insert(tokey_in.k_image).second) + return false; + } + } + return true; + } + //----------------------------------------------------------------------------------------------- + bool validate_tx_semantic(const transaction& tx, size_t tx_block_size) + { + if (!tx.vin.size()) + { + LOG_PRINT_RED_L0("tx with empty inputs, rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + + if (!check_inputs_types_supported(tx)) + { + LOG_PRINT_RED_L0("unsupported input types for tx id= " << get_transaction_hash(tx)); + return false; + } + + if (!check_outs_valid(tx)) + { + LOG_PRINT_RED_L0("tx with invalid outputs, rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + + if (!check_money_overflow(tx)) + { + LOG_PRINT_RED_L0("tx has money overflow, rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + + uint64_t amount_in = 0; + get_inputs_money_amount(tx, amount_in); + uint64_t amount_out = get_outs_money_amount(tx); + + if (amount_in < amount_out) + { + LOG_PRINT_RED_L0("tx with wrong amounts: ins " << amount_in << ", outs " << amount_out << ", rejected for tx id= " << get_transaction_hash(tx)); + return false; + } + + if (tx_block_size >= CURRENCY_MAX_TRANSACTION_BLOB_SIZE) + { + LOG_PRINT_RED_L0("tx has too big size " << tx_block_size << ", expected no bigger than " << CURRENCY_BLOCK_GRANTED_FULL_REWARD_ZONE); + return false; + } + + //check if tx use different key images + if (!check_tx_inputs_keyimages_diff(tx)) + { + LOG_PRINT_RED_L0("tx inputs have the same key images"); + return false; + } + + if (!check_tx_extra(tx)) + { + LOG_PRINT_RED_L0("tx has wrong extra, rejected"); + return false; + } + + return true; + } + //--------------------------------------------------------------- + bool get_transaction_hash(const transaction& t, crypto::hash& res) + { + uint64_t blob_size = 0; + return get_object_hash(static_cast(t), res, blob_size); + } + //--------------------------------------------------------------- + bool get_transaction_hash(const transaction& t, crypto::hash& res, uint64_t& blob_size) + { + blob_size = 0; + bool r = get_object_hash(static_cast(t), res, blob_size); + blob_size = get_object_blobsize(t, blob_size); + return r; + } + //--------------------------------------------------------------- + size_t get_object_blobsize(const transaction& t) + { + size_t tx_blob_size = get_object_blobsize(static_cast(t)); + return get_object_blobsize(t, tx_blob_size); + } + //--------------------------------------------------------------- + size_t get_objects_blobsize(const std::list& ls) + { + size_t total = 0; + for (const auto& tx : ls) + { + total += get_object_blobsize(tx); + } + return total; + } + //--------------------------------------------------------------- + size_t get_object_blobsize(const transaction& t, uint64_t prefix_blob_size) + { + size_t tx_blob_size = prefix_blob_size; + + if (is_coinbase(t)) + return tx_blob_size; + + // for purged tx, with empty signatures and attachments, this function should return the blob size + // which the tx would have if the signatures and attachments were correctly filled with actual data + + // 1. signatures + bool separately_signed_tx = get_tx_flags(t) & TX_FLAG_SIGNATURE_MODE_SEPARATE; + + tx_blob_size += tools::get_varint_packed_size(t.vin.size()); // size of transaction::signatures (equals to total inputs count) + + for (size_t i = 0; i != t.vin.size(); i++) + { + size_t sig_count = get_input_expected_signatures_count(t.vin[i]); + if (separately_signed_tx && i == t.vin.size() - 1) + ++sig_count; // count in one more signature for the last input in a complete separately signed tx + tx_blob_size += tools::get_varint_packed_size(sig_count); // size of transaction::signatures[i] + tx_blob_size += sizeof(crypto::signature) * sig_count; // size of signatures' data itself + } + + // 2. attachments (try to find extra_attachment_info in tx prefix and count it in if succeed) + extra_attachment_info eai = AUTO_VAL_INIT(eai); + bool got_eai = false; + if (separately_signed_tx) + { + // for separately-signed tx, try to obtain extra_attachment_info from the last input's etc_details + const std::vector* p_etc_details = get_input_etc_details(t.vin.back()); + got_eai = p_etc_details != nullptr && get_type_in_variant_container(*p_etc_details, eai); + } + if (!got_eai) + got_eai = get_type_in_variant_container(t.extra, eai); // then from the extra + + if (got_eai) + tx_blob_size += eai.sz; // sz is a size of whole serialized attachment blob, including attachments vector size + else + tx_blob_size += tools::get_varint_packed_size(static_cast(0)); // no extra_attachment_info found - just add zero vector's size, 'cause it's serialized anyway + + return tx_blob_size; + } + //--------------------------------------------------------------- + blobdata tx_to_blob(const transaction& tx) + { + return t_serializable_object_to_blob(tx); + } + //--------------------------------------------------------------- + bool tx_to_blob(const transaction& tx, blobdata& b_blob) + { + return t_serializable_object_to_blob(tx, b_blob); + } + +} \ No newline at end of file diff --git a/src/currency_core/tx_semantic_validation.h b/src/currency_core/tx_semantic_validation.h new file mode 100644 index 00000000..2e6f4790 --- /dev/null +++ b/src/currency_core/tx_semantic_validation.h @@ -0,0 +1,16 @@ +// Copyright (c) 2018-2019 Zano Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + + +#include "include_base_utils.h" +#include "currency_format_utils_transactions.h" + + +namespace currency +{ + //check correct values, amounts and all lightweight checks not related with database + bool validate_tx_semantic(const transaction& tx, size_t tx_block_size); +}