2018-12-27 18:50:45 +03:00
// Copyright (c) 2014-2018 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// Copyright (c) 2012-2013 The Cryptonote developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
# include <algorithm>
# include <boost/filesystem.hpp>
# include <unordered_set>
# include <vector>
2019-08-31 14:41:18 +02:00
# include "common/db_backend_selector.h"
2018-12-27 18:50:45 +03:00
# include "tx_pool.h"
# include "currency_boost_serialization.h"
# include "currency_core/currency_config.h"
# include "blockchain_storage.h"
# include "common/boost_serialization_helper.h"
# include "common/int-util.h"
# include "misc_language.h"
# include "warnings.h"
# include "crypto/hash.h"
# include "profile_tools.h"
2020-03-16 17:48:02 +03:00
# include "common/db_backend_selector.h"
2018-12-27 18:50:45 +03:00
DISABLE_VS_WARNINGS ( 4244 4345 4503 ) //'boost::foreach_detail_::or_' : decorated name length exceeded, name was truncated
# define TRANSACTION_POOL_CONTAINER_TRANSACTIONS "transactions"
# define TRANSACTION_POOL_CONTAINER_BLACK_TX_LIST "black_tx_list"
# define TRANSACTION_POOL_CONTAINER_ALIAS_NAMES "alias_names"
# define TRANSACTION_POOL_CONTAINER_ALIAS_ADDRESSES "alias_addresses"
# define TRANSACTION_POOL_CONTAINER_KEY_IMAGES "key_images"
# define TRANSACTION_POOL_CONTAINER_SOLO_OPTIONS "solo"
2019-08-17 07:10:37 +03:00
# define TRANSACTION_POOL_OPTIONS_ID_STORAGE_MAJOR_COMPATIBILITY_VERSION 92 // DON'T CHANGE THIS, if you need to resync db! Change TRANSACTION_POOL_MAJOR_COMPATIBILITY_VERSION instead!
2018-12-27 18:50:45 +03:00
# define TRANSACTION_POOL_MAJOR_COMPATIBILITY_VERSION BLOCKCHAIN_STORAGE_MAJOR_COMPATIBILITY_VERSION + 1
2019-10-25 00:13:38 +02:00
2019-10-17 14:07:32 +03:00
# 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
2018-12-27 18:50:45 +03:00
# undef LOG_DEFAULT_CHANNEL
# define LOG_DEFAULT_CHANNEL "tx_pool"
ENABLE_CHANNEL_BY_DEFAULT ( " tx_pool " ) ;
namespace currency
{
//---------------------------------------------------------------------------------
tx_memory_pool : : tx_memory_pool ( blockchain_storage & bchs , i_currency_protocol * pprotocol ) :
m_blockchain ( bchs ) ,
m_pprotocol ( pprotocol ) ,
2019-10-25 00:13:38 +02:00
m_db ( nullptr , m_dummy_rw_lock ) ,
2018-12-27 18:50:45 +03:00
m_db_transactions ( m_db ) ,
m_db_black_tx_list ( m_db ) ,
m_db_solo_options ( m_db ) ,
2019-11-16 20:26:40 +01:00
// m_db_key_images_set(m_db),
2018-12-27 18:50:45 +03:00
m_db_alias_names ( m_db ) ,
m_db_alias_addresses ( m_db ) ,
2019-08-17 07:10:37 +03:00
m_db_storage_major_compatibility_version ( TRANSACTION_POOL_OPTIONS_ID_STORAGE_MAJOR_COMPATIBILITY_VERSION , m_db_solo_options )
2018-12-27 18:50:45 +03:00
{
}
bool tx_memory_pool : : is_valid_contract_finalization_tx ( const transaction & tx ) const
{
if ( tx . vin . size ( ) ! = 1 | | tx . vin [ 0 ] . type ( ) ! = typeid ( txin_multisig ) )
{
return false ;
}
const txin_multisig & ms_in = boost : : get < const txin_multisig > ( tx . vin [ 0 ] ) ;
crypto : : hash related_tx_id = null_hash ; uint64_t out_no = 0 ;
if ( ! m_blockchain . get_multisig_id_details ( ms_in . multisig_out_id , related_tx_id , out_no ) )
{
LOG_ERROR ( " Related multisig tx not found, multisig_out_id= " < < ms_in . multisig_out_id ) ;
return false ;
}
auto related_tx_ptr = m_blockchain . get_tx_chain_entry ( related_tx_id ) ;
if ( ! related_tx_ptr )
{
LOG_ERROR ( " Tx " < < related_tx_id < < " related to multisig id " < < ms_in . multisig_out_id
< < " (discovered by reviewing tx " < < get_transaction_hash ( tx ) < < " ) tx not found in blockchain, multisig_out_id= " < < ms_in . multisig_out_id ) ;
return false ;
}
if ( get_tx_fee ( tx ) < get_tx_fee ( related_tx_ptr - > tx ) )
{
LOG_ERROR ( " Tx " < < get_transaction_hash ( tx ) < < " fee= " < < get_tx_fee ( tx ) < < " less then parent multisig tx " < < related_tx_id < < " fee= " < < get_tx_fee ( related_tx_ptr - > tx ) ) ;
return false ;
}
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : add_tx ( const transaction & tx , const crypto : : hash & id , uint64_t blob_size , tx_verification_context & tvc , bool kept_by_block , bool from_core )
{
2020-02-13 15:55:58 +03:00
if ( ! kept_by_block & & ! from_core & & m_blockchain . is_in_checkpoint_zone ( ) )
{
// BCS is in CP zone, tx verification is impossible until it gets synchronized
tvc . m_added_to_pool = false ;
tvc . m_should_be_relayed = false ;
tvc . m_verification_failed = false ;
tvc . m_verification_impossible = true ;
return false ;
}
2021-03-08 02:09:08 +03:00
2021-03-10 22:35:34 +03:00
if ( ! m_blockchain . validate_tx_for_hardfork_specific_terms ( tx , id ) )
2021-03-08 02:09:08 +03:00
{
//
LOG_ERROR ( " Transaction " < < id < < " doesn't fit current hardfork " ) ;
tvc . m_verification_failed = true ;
return false ;
}
2020-02-13 15:55:58 +03:00
2018-12-27 18:50:45 +03:00
TIME_MEASURE_START_PD ( tx_processing_time ) ;
TIME_MEASURE_START_PD ( check_inputs_types_supported_time ) ;
if ( ! check_inputs_types_supported ( tx ) )
{
tvc . m_verification_failed = true ;
return false ;
}
TIME_MEASURE_FINISH_PD ( check_inputs_types_supported_time ) ;
TIME_MEASURE_START_PD ( expiration_validate_time ) ;
if ( ! from_core & & ! kept_by_block & & m_blockchain . is_tx_expired ( tx ) )
{
uint64_t tx_expiration_time = get_tx_expiration_time ( tx ) ;
uint64_t ts_median = m_blockchain . get_tx_expiration_median ( ) ;
LOG_PRINT_L0 ( " transaction " < < id < < " is expired, rejected by tx pool (tx timestamp: " < < tx_expiration_time < < " - " < < TX_EXPIRATION_MEDIAN_SHIFT < < " (median shift) = " < <
tx_expiration_time - TX_EXPIRATION_MEDIAN_SHIFT < < " , blockchain timestamp median: " < < ts_median < <
" , diff: " < < epee : : misc_utils : : get_time_interval_string ( ts_median + TX_EXPIRATION_MEDIAN_SHIFT - tx_expiration_time ) < < " ) " ) ;
tvc . m_verification_failed = true ;
return false ;
}
TIME_MEASURE_FINISH_PD ( expiration_validate_time ) ;
TIME_MEASURE_START_PD ( validate_amount_time ) ;
uint64_t inputs_amount = 0 ;
if ( ! get_inputs_money_amount ( tx , inputs_amount ) )
{
tvc . m_verification_failed = true ;
return false ;
}
CHECK_AND_ASSERT_MES_CUSTOM ( tx . vout . size ( ) < = CURRENCY_TX_MAX_ALLOWED_OUTS , false , tvc . m_verification_failed = true , " transaction has too many outs = " < < tx . vout . size ( ) ) ;
uint64_t outputs_amount = get_outs_money_amount ( tx ) ;
if ( outputs_amount > inputs_amount )
{
LOG_PRINT_L0 ( " transaction use more money then it has: use " < < outputs_amount < < " , have " < < inputs_amount ) ;
tvc . m_verification_failed = true ;
return false ;
}
TIME_MEASURE_FINISH_PD ( validate_amount_time ) ;
TIME_MEASURE_START_PD ( validate_alias_time ) ;
if ( ! from_core & & ! validate_alias_info ( tx , kept_by_block ) )
{
LOG_PRINT_RED_L0 ( " validate_alias_info failed " ) ;
tvc . m_verification_failed = true ;
return false ;
}
TIME_MEASURE_FINISH_PD ( validate_alias_time ) ;
TIME_MEASURE_START_PD ( check_keyimages_ws_ms_time ) ;
//check key images for transaction if it is not kept by block
if ( ! from_core & & ! kept_by_block )
{
2019-02-27 00:59:02 +03:00
crypto : : key_image spent_ki = AUTO_VAL_INIT ( spent_ki ) ;
if ( have_tx_keyimges_as_spent ( tx , & spent_ki ) )
2018-12-27 18:50:45 +03:00
{
2019-02-27 00:59:02 +03:00
LOG_ERROR ( " Transaction " < < id < < " uses already spent key image " < < spent_ki ) ;
2018-12-27 18:50:45 +03:00
tvc . m_verification_failed = true ;
return false ;
}
//transaction spam protection, soft rule
uint64_t tx_fee = inputs_amount - outputs_amount ;
if ( tx_fee < m_blockchain . get_core_runtime_config ( ) . tx_pool_min_fee )
{
2019-07-11 19:38:14 +02:00
if ( is_valid_contract_finalization_tx ( tx ) )
2018-12-27 18:50:45 +03:00
{
// that means tx has less fee then allowed by current tx pull rules, but this transaction is actually
// a finalization of contract, and template of this contract finalization tx was prepared actually before
// fee rules had been changed, so it's ok, let it in.
}
else
{
2019-07-11 19:38:14 +02:00
// this tx has no fee
2019-09-08 20:02:42 +02:00
LOG_PRINT_RED_L0 ( " Transaction with id= " < < id < < " has too small fee: " < < tx_fee < < " , expected fee: " < < m_blockchain . get_core_runtime_config ( ) . tx_pool_min_fee ) ;
2018-12-27 18:50:45 +03:00
tvc . m_verification_failed = false ;
tvc . m_should_be_relayed = false ;
tvc . m_added_to_pool = false ;
return true ;
}
}
// check tx multisig inputs/output against tx in the pool and in the blockchain
if ( ! check_tx_multisig_ins_and_outs ( tx , true ) )
{
tvc . m_verification_failed = true ;
return false ;
}
}
TIME_MEASURE_FINISH_PD ( check_keyimages_ws_ms_time ) ;
TIME_MEASURE_START_PD ( check_inputs_time ) ;
crypto : : hash max_used_block_id = null_hash ;
uint64_t max_used_block_height = 0 ;
bool ch_inp_res = m_blockchain . check_tx_inputs ( tx , id , max_used_block_height , max_used_block_id ) ;
if ( ! ch_inp_res & & ! kept_by_block & & ! from_core )
{
LOG_PRINT_L0 ( " tx used wrong inputs, rejected " ) ;
tvc . m_verification_failed = true ;
return false ;
}
TIME_MEASURE_FINISH_PD ( check_inputs_time ) ;
do_insert_transaction ( tx , id , blob_size , kept_by_block , inputs_amount - outputs_amount , ch_inp_res ? max_used_block_id : null_hash , ch_inp_res ? max_used_block_height : 0 ) ;
TIME_MEASURE_FINISH_PD ( tx_processing_time ) ;
tvc . m_added_to_pool = true ;
tvc . m_should_be_relayed = ch_inp_res ; // relay tx only if it has valid inputs (i.e. do not relay kept_by_block tx with wrong inputs)
tvc . m_verification_impossible = ! ch_inp_res ; // mark 'kept_by_block' tx with wrong inputs as impossible to be verified
tvc . m_verification_failed = false ;
//succeed
LOG_PRINT_L2 ( " [TX_POOL ADD_TX] timing(micsec) : " < < print_fixed_decimal_point ( tx_processing_time , 3 )
< < " ( " < < m_performance_data . check_inputs_types_supported_time . get_last_val ( )
< < " / " < < m_performance_data . expiration_validate_time . get_last_val ( )
< < " / " < < m_performance_data . validate_amount_time . get_last_val ( )
< < " / " < < m_performance_data . validate_alias_time . get_last_val ( )
< < " / " < < m_performance_data . check_keyimages_ws_ms_time . get_last_val ( )
< < " / " < < m_performance_data . check_inputs_time . get_last_val ( )
< < " / " < < m_performance_data . begin_tx_time . get_last_val ( )
< < " / " < < m_performance_data . update_db_time . get_last_val ( )
< < " / " < < m_performance_data . db_commit_time . get_last_val ( ) < < " ) " ) ;
return true ;
}
//---------------------------------------------------------------------------------
# define LOCAL_READONLY_TRANSACTION() \
m_db . begin_readonly_transaction ( ) ; \
misc_utils : : auto_scope_leave_caller db_tx_closer = misc_utils : : create_scope_leave_handler ( [ & ] ( ) \
{ \
m_db . commit_transaction ( ) ; \
} ) ;
bool tx_memory_pool : : do_insert_transaction ( const transaction & tx , const crypto : : hash & id , uint64_t blob_size , bool kept_by_block , uint64_t fee , const crypto : : hash & max_used_block_id , uint64_t max_used_block_height )
{
TIME_MEASURE_START_PD ( begin_tx_time ) ;
m_db . begin_transaction ( ) ;
TIME_MEASURE_FINISH_PD ( begin_tx_time ) ;
TIME_MEASURE_START_PD ( update_db_time ) ;
misc_utils : : auto_scope_leave_caller seh = misc_utils : : create_scope_leave_handler ( [ & ] ( )
{
TIME_MEASURE_START_PD ( db_commit_time ) ;
m_db . commit_transaction ( ) ;
TIME_MEASURE_FINISH_PD ( db_commit_time ) ;
} ) ;
//CHECK_AND_ASSERT_MES_CUSTOM(!m_db_transactions.get(id), false, (tvc.m_added_to_pool = false, tvc.m_verification_failed = true), "internal error: failed to add transaction " << id << " to the pool as it already exists");
tx_details td = AUTO_VAL_INIT ( td ) ;
td . blob_size = blob_size ;
td . tx = tx ;
td . kept_by_block = kept_by_block ;
td . fee = fee ;
td . max_used_block_id = max_used_block_id ;
td . max_used_block_height = max_used_block_height ;
td . last_failed_height = 0 ;
td . last_failed_id = null_hash ;
td . receive_time = get_core_time ( ) ;
m_db_transactions . set ( id , td ) ;
2019-11-16 20:26:40 +01:00
on_tx_add ( id , tx , kept_by_block ) ;
2018-12-27 18:50:45 +03:00
TIME_MEASURE_FINISH_PD ( update_db_time ) ;
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : check_is_taken ( const crypto : : hash & id ) const
{
CRITICAL_REGION_LOCAL ( m_taken_txs_lock ) ;
return m_taken_txs . count ( id ) ? true : false ;
}
//---------------------------------------------------------------------------------
void tx_memory_pool : : set_taken ( const crypto : : hash & id )
{
CRITICAL_REGION_LOCAL ( m_taken_txs_lock ) ;
m_taken_txs . insert ( id ) ;
}
//---------------------------------------------------------------------------------
void tx_memory_pool : : reset_all_taken ( )
{
CRITICAL_REGION_LOCAL ( m_taken_txs_lock ) ;
m_taken_txs . clear ( ) ;
}
2019-07-11 19:38:14 +02:00
//---------------------------------------------------------------------------------
2018-12-27 18:50:45 +03:00
bool tx_memory_pool : : get_aliases_from_tx_pool ( std : : list < extra_alias_entry > & aliases ) const
{
//TODO: OPTIMIZATION put cache here!!!!!
m_db_transactions . enumerate_items ( [ & ] ( uint64_t i , const crypto : : hash & h , const tx_details & tx_entry )
{
tx_extra_info ei = AUTO_VAL_INIT ( ei ) ;
bool r = parse_and_validate_tx_extra ( tx_entry . tx , ei ) ;
CHECK_AND_ASSERT_MES ( r , false , " failed to validate transaction extra on unprocess_blockchain_tx_extra " ) ;
if ( ei . m_alias . m_alias . size ( ) )
aliases . push_back ( ei . m_alias ) ;
return true ;
} ) ;
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : get_aliases_from_tx_pool ( std : : map < std : : string , size_t > & aliases ) const
{
std : : list < extra_alias_entry > aliases_local ;
get_aliases_from_tx_pool ( aliases_local ) ;
for ( auto & alias_info : aliases_local )
{
+ + aliases [ alias_info . m_alias ] ;
}
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : validate_alias_info ( const transaction & tx , bool is_in_block ) const
{
LOCAL_READONLY_TRANSACTION ( ) ;
extra_alias_entry eai = AUTO_VAL_INIT ( eai ) ;
2020-06-25 16:43:05 +03:00
bool r = false ;
bool found = handle_2_alternative_types_in_variant_container < extra_alias_entry , extra_alias_entry_old > ( tx . extra , [ this , & r , & tx , is_in_block ] ( const extra_alias_entry & eai ) {
2018-12-27 18:50:45 +03:00
//check in blockchain
extra_alias_entry eai2 = AUTO_VAL_INIT ( eai2 ) ;
bool already_have_alias_registered = m_blockchain . get_alias_info ( eai . m_alias , eai2 ) ;
//size_t alias_size = eai.m_alias.size();
if ( ! is_in_block & & ! eai . m_sign . size ( ) & & already_have_alias_registered )
{
LOG_PRINT_L0 ( " Alias \" " < < eai . m_alias < < " \" already registered in blockchain, transaction rejected " ) ;
2020-06-25 16:43:05 +03:00
r = false ;
return false ; // stop handling
2018-12-27 18:50:45 +03:00
}
std : : string prev_alias = m_blockchain . get_alias_by_address ( eai . m_address ) ;
if ( ! is_in_block & & ! eai . m_sign . size ( ) & &
prev_alias . size ( ) )
{
LOG_PRINT_L0 ( " Address \" " < < get_account_address_as_str ( eai . m_address )
< < " \" already registered with \" " < < prev_alias
2020-06-25 16:43:05 +03:00
< < " \" alias in blockchain (new alias: \" " < < eai . m_alias < < " \" ), transaction rejected " ) ;
r = false ;
return false ; // stop handling
2018-12-27 18:50:45 +03:00
}
if ( ! is_in_block )
{
if ( m_db_alias_names . get ( eai . m_alias ) )
{
LOG_PRINT_L0 ( " Alias \" " < < eai . m_alias < < " \" already in transaction pool, transaction rejected " ) ;
2020-06-25 16:43:05 +03:00
r = false ;
return false ; // stop handling
2018-12-27 18:50:45 +03:00
}
if ( m_db_alias_addresses . get ( eai . m_address ) )
{
LOG_PRINT_L0 ( " Alias \" " < < eai . m_alias < < " \" already in transaction pool by it's address( " < < get_account_address_as_str ( eai . m_address ) < < " ) , transaction rejected " ) ;
2020-06-25 16:43:05 +03:00
r = false ;
return false ; // stop handling
2018-12-27 18:50:45 +03:00
}
//validate alias reward
if ( ! m_blockchain . prevalidate_alias_info ( tx , eai ) )
{
LOG_PRINT_L0 ( " Alias \" " < < eai . m_alias < < " \" reward validation failed, transaction rejected " ) ;
2020-06-25 16:43:05 +03:00
r = false ;
return false ; // stop handling
2018-12-27 18:50:45 +03:00
}
}
2020-06-25 16:43:05 +03:00
r = true ;
return true ; // continue handling
} ) ;
2018-12-27 18:50:45 +03:00
2020-06-25 16:43:05 +03:00
return ! found | | r ; // if found, r must be true for success
2018-12-27 18:50:45 +03:00
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : add_tx ( const transaction & tx , tx_verification_context & tvc , bool kept_by_block , bool from_core )
{
crypto : : hash h = null_hash ;
uint64_t blob_size = 0 ;
get_transaction_hash ( tx , h , blob_size ) ;
return add_tx ( tx , h , blob_size , tvc , kept_by_block , from_core ) ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : take_tx ( const crypto : : hash & id , transaction & tx , size_t & blob_size , uint64_t & fee )
{
m_db_transactions . begin_transaction ( ) ;
misc_utils : : auto_scope_leave_caller seh = misc_utils : : create_scope_leave_handler ( [ & ] ( ) { m_db_transactions . commit_transaction ( ) ; } ) ;
auto txe_tr = m_db_transactions . find ( id ) ;
if ( txe_tr = = m_db_transactions . end ( ) )
return false ;
tx = txe_tr - > tx ;
blob_size = txe_tr - > blob_size ;
fee = txe_tr - > fee ;
m_db_transactions . erase ( id ) ;
2019-11-16 20:26:40 +01:00
on_tx_remove ( id , tx , txe_tr - > kept_by_block ) ;
2018-12-27 18:50:45 +03:00
set_taken ( id ) ;
return true ;
}
//---------------------------------------------------------------------------------
void tx_memory_pool : : on_idle ( )
{
m_remove_stuck_tx_interval . do_call ( [ this ] ( ) { return remove_stuck_transactions ( ) ; } ) ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : remove_stuck_transactions ( )
{
if ( ! CRITICAL_SECTION_TRY_LOCK ( m_remove_stuck_txs_lock ) )
return true ;
CRITICAL_REGION_LOCAL ( m_remove_stuck_txs_lock ) ;
CRITICAL_SECTION_UNLOCK ( m_remove_stuck_txs_lock ) ; // release try_lock iteration
m_db_transactions . begin_transaction ( ) ;
misc_utils : : auto_scope_leave_caller seh = misc_utils : : create_scope_leave_handler ( [ & ] ( ) { m_db_transactions . commit_transaction ( ) ; } ) ;
struct tx_to_delete_entry
{
tx_to_delete_entry ( const crypto : : hash & hash , const transaction & tx , bool kept_by_block )
: hash ( hash ) , tx ( tx ) , kept_by_block ( kept_by_block )
{ }
crypto : : hash hash ;
transaction tx ;
bool kept_by_block ;
} ;
std : : vector < tx_to_delete_entry > to_delete ;
const uint64_t tx_expiration_ts_median = m_blockchain . get_tx_expiration_median ( ) ;
m_db_transactions . enumerate_items ( [ & ] ( uint64_t i , const crypto : : hash & h , const tx_details & tx_entry )
{
//never remove transactions which related to alt blocks,
//or we can get network split as a worst case (impossible to switch to
//altchain if it won, and node stuck forever).
if ( m_blockchain . is_tx_related_to_altblock ( h ) )
return true ;
// maximum age check - remove too old
2019-04-30 16:58:01 +03:00
int64_t tx_age = get_core_time ( ) - tx_entry . receive_time ;
2018-12-27 18:50:45 +03:00
if ( ( tx_age > CURRENCY_MEMPOOL_TX_LIVETIME ) )
{
2019-10-17 14:07:32 +03:00
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 ) ) ;
2018-12-27 18:50:45 +03:00
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 ) )
{
2019-10-17 14:07:32 +03:00
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 ) ;
2018-12-27 18:50:45 +03:00
to_delete . push_back ( tx_to_delete_entry ( h , tx_entry . tx , tx_entry . kept_by_block ) ) ;
}
2019-10-17 14:07:32 +03:00
// 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 < txin_to_key > ( 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 ;
}
}
}
}
2018-12-27 18:50:45 +03:00
return true ;
} ) ;
for ( auto & e : to_delete )
{
m_db_transactions . erase ( e . hash ) ;
2019-11-16 20:26:40 +01:00
on_tx_remove ( e . hash , e . tx , e . kept_by_block ) ;
2018-12-27 18:50:45 +03:00
}
return true ;
}
//---------------------------------------------------------------------------------
size_t tx_memory_pool : : get_transactions_count ( ) const
{
return m_db_transactions . size ( ) ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : get_transactions ( std : : list < transaction > & txs ) const
{
m_db_transactions . enumerate_items ( [ & ] ( uint64_t i , const crypto : : hash & h , const tx_details & tx_entry )
{
txs . push_back ( tx_entry . tx ) ;
return true ;
} ) ;
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : get_all_transactions_details ( std : : list < tx_rpc_extended_info > & txs ) const
{
m_db_transactions . enumerate_items ( [ & ] ( uint64_t i , const crypto : : hash & h , const tx_details & tx_entry )
{
txs . push_back ( tx_rpc_extended_info ( ) ) ;
tx_rpc_extended_info & trei = txs . back ( ) ;
trei . blob_size = tx_entry . blob_size ;
fill_tx_rpc_details ( trei , tx_entry . tx , nullptr , h , tx_entry . receive_time , true ) ;
return true ;
} ) ;
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : get_all_transactions_brief_details ( std : : list < tx_rpc_brief_info > & txs ) const
{
m_db_transactions . enumerate_items ( [ & ] ( uint64_t i , const crypto : : hash & h , const tx_details & tx_entry )
{
txs . push_back ( tx_rpc_brief_info ( ) ) ;
tx_rpc_brief_info & trbi = txs . back ( ) ;
trbi . id = epee : : string_tools : : pod_to_hex ( h ) ;
trbi . fee = tx_entry . fee ;
trbi . sz = tx_entry . blob_size ;
trbi . total_amount = get_outs_money_amount ( tx_entry . tx ) ;
return true ;
} ) ;
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : get_all_transactions_list ( std : : list < std : : string > & txs ) const
{
m_db_transactions . enumerate_items ( [ & ] ( uint64_t i , const crypto : : hash & h , const tx_details & tx_entry )
{
txs . push_back ( epee : : string_tools : : pod_to_hex ( h ) ) ;
return true ;
} ) ;
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : get_transactions_details ( const std : : list < std : : string > & ids , std : : list < tx_rpc_extended_info > & txs ) const
{
for ( auto & id_str : ids )
{
crypto : : hash id = null_hash ;
if ( ! epee : : string_tools : : hex_to_pod ( id_str , id ) )
{
LOG_ERROR ( " Failed to parse id in list: " < < id_str ) ;
return false ;
}
auto ptei = m_db_transactions . get ( id ) ;
if ( ! ptei )
return false ;
txs . push_back ( tx_rpc_extended_info ( ) ) ;
tx_rpc_extended_info & trei = txs . back ( ) ;
trei . blob_size = ptei - > blob_size ;
fill_tx_rpc_details ( trei , ptei - > tx , nullptr , id , ptei - > receive_time , false ) ;
}
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : get_transactions_brief_details ( const std : : list < std : : string > & ids , std : : list < tx_rpc_brief_info > & txs ) const
{
LOCAL_READONLY_TRANSACTION ( ) ;
for ( auto & id_str : ids )
{
crypto : : hash id = null_hash ;
if ( ! epee : : string_tools : : hex_to_pod ( id_str , id ) )
{
LOG_ERROR ( " Failed to parse id in list: " < < id_str ) ;
return false ;
}
auto ptei = m_db_transactions . get ( id ) ;
if ( ! ptei )
return false ;
txs . push_back ( tx_rpc_brief_info ( ) ) ;
tx_rpc_brief_info & trbi = txs . back ( ) ;
trbi . sz = ptei - > blob_size ;
trbi . id = id_str ;
trbi . fee = ptei - > fee ;
trbi . total_amount = get_outs_money_amount ( ptei - > tx ) ;
}
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : get_transaction_details ( const crypto : : hash & id , tx_rpc_extended_info & trei ) const
{
auto ptei = m_db_transactions . get ( id ) ;
if ( ! ptei )
return false ;
fill_tx_rpc_details ( trei , ptei - > tx , nullptr , id , ptei - > receive_time , false ) ;
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : get_transaction ( const crypto : : hash & id , transaction & tx ) const
{
auto tx_ptr = m_db_transactions . find ( id ) ;
if ( tx_ptr = = m_db_transactions . end ( ) )
return false ;
tx = tx_ptr - > tx ;
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : get_transaction ( const crypto : : hash & id , tx_details & txd ) const
{
auto tx_ptr = m_db_transactions . find ( id ) ;
if ( tx_ptr = = m_db_transactions . end ( ) )
return false ;
txd = * tx_ptr ;
return true ;
}
//---------------------------------------------------------------------------------
2019-11-16 20:26:40 +01:00
bool tx_memory_pool : : on_blockchain_inc ( uint64_t new_block_height , const crypto : : hash & top_block_id , const std : : list < crypto : : key_image > & bsk )
2018-12-27 18:50:45 +03:00
{
2019-11-16 20:26:40 +01:00
2018-12-27 18:50:45 +03:00
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : on_blockchain_dec ( uint64_t new_block_height , const crypto : : hash & top_block_id )
{
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : on_finalize_db_transaction ( )
{
reset_all_taken ( ) ;
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : set_protocol ( i_currency_protocol * pprotocol )
{
m_pprotocol = pprotocol ;
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : force_relay_pool ( ) const
{
LOG_PRINT_GREEN ( " Preparing relay message... " , LOG_LEVEL_0 ) ;
2022-03-21 16:47:11 +02:00
NOTIFY_OR_INVOKE_NEW_TRANSACTIONS : : request r = AUTO_VAL_INIT ( r ) ;
2018-12-27 18:50:45 +03:00
m_db_transactions . enumerate_items ( [ & ] ( uint64_t i , const crypto : : hash & k , const tx_details & v )
{
r . txs . push_back ( t_serializable_object_to_blob ( v . tx ) ) ;
return true ;
} ) ;
LOG_PRINT_GREEN ( " Sending.... " , LOG_LEVEL_0 ) ;
CHECK_AND_ASSERT_MES ( m_pprotocol , false , " m_pprotocol is not set " ) ;
currency_connection_context fake_context = AUTO_VAL_INIT ( fake_context ) ;
m_pprotocol - > relay_transactions ( r , fake_context ) ;
LOG_PRINT_GREEN ( " Sent. " , LOG_LEVEL_0 ) ;
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : add_transaction_to_black_list ( const transaction & tx )
{
// atm:
// 1) the only side effect of a tx being blacklisted is the one is just ignored by fill_block_template(), but it still can be added to blockchain/pool
// 2) it's permanent
LOG_PRINT_YELLOW ( " TX ADDED TO POOL'S BLACKLIST: " < < get_transaction_hash ( tx ) , LOG_LEVEL_0 ) ;
m_db . begin_transaction ( ) ;
m_db_black_tx_list . set ( get_transaction_hash ( tx ) , true ) ;
m_db . commit_transaction ( ) ;
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : have_tx ( const crypto : : hash & id ) const
{
if ( m_db_transactions . get ( id ) )
return true ;
if ( check_is_taken ( id ) )
return true ;
return false ;
}
//---------------------------------------------------------------------------------
2019-02-27 00:59:02 +03:00
bool tx_memory_pool : : have_tx_keyimges_as_spent ( const transaction & tx , crypto : : key_image * p_spent_ki /* = nullptr */ ) const
2018-12-27 18:50:45 +03:00
{
for ( const auto & in : tx . vin )
{
if ( in . type ( ) = = typeid ( txin_to_key ) )
{
CHECKED_GET_SPECIFIC_VARIANT ( in , const txin_to_key , tokey_in , true ) ; //should never fail
2019-02-27 00:59:02 +03:00
if ( have_tx_keyimg_as_spent ( tokey_in . k_image ) )
{
if ( p_spent_ki )
* p_spent_ki = tokey_in . k_image ;
return true ;
}
2018-12-27 18:50:45 +03:00
}
}
return false ;
}
//---------------------------------------------------------------------------------
2019-11-16 20:26:40 +01:00
bool tx_memory_pool : : insert_key_images ( const crypto : : hash & tx_id , const transaction & tx , bool kept_by_block )
2018-12-27 18:50:45 +03:00
{
2019-11-16 20:26:40 +01:00
CRITICAL_REGION_LOCAL ( m_key_images_lock ) ;
2018-12-27 18:50:45 +03:00
for ( const auto & in : tx . vin )
{
if ( in . type ( ) = = typeid ( txin_to_key ) )
{
2019-11-16 20:26:40 +01:00
const txin_to_key & tokey_in = boost : : get < txin_to_key > ( in ) ;
auto & id_set = m_key_images [ tokey_in . k_image ] ;
size_t sz_before = id_set . size ( ) ;
id_set . insert ( tx_id ) ;
LOG_PRINT_L2 ( " tx pool: key image added: " < < tokey_in . k_image < < " , from tx " < < tx_id < < " , counter: " < < sz_before < < " -> " < < id_set . size ( ) ) ;
2018-12-27 18:50:45 +03:00
}
}
return false ;
}
//---------------------------------------------------------------------------------
2019-11-16 20:26:40 +01:00
bool tx_memory_pool : : on_tx_add ( crypto : : hash tx_id , const transaction & tx , bool kept_by_block )
2018-12-27 18:50:45 +03:00
{
2019-11-16 20:26:40 +01:00
insert_key_images ( tx_id , tx , kept_by_block ) ;
2018-12-27 18:50:45 +03:00
insert_alias_info ( tx ) ;
return true ;
}
//---------------------------------------------------------------------------------
2019-11-16 20:26:40 +01:00
bool tx_memory_pool : : on_tx_remove ( const crypto : : hash & id , const transaction & tx , bool kept_by_block )
2018-12-27 18:50:45 +03:00
{
2019-11-16 20:26:40 +01:00
remove_key_images ( id , tx , kept_by_block ) ;
2018-12-27 18:50:45 +03:00
remove_alias_info ( tx ) ;
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : insert_alias_info ( const transaction & tx )
{
tx_extra_info ei = AUTO_VAL_INIT ( ei ) ;
bool r = parse_and_validate_tx_extra ( tx , ei ) ;
CHECK_AND_ASSERT_MES ( r , false , " failed to validate transaction extra on unprocess_blockchain_tx_extra " ) ;
if ( ei . m_alias . m_alias . size ( ) )
{
m_db_alias_names . set ( ei . m_alias . m_alias , true ) ;
m_db_alias_addresses . set ( ei . m_alias . m_address , true ) ;
}
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : remove_alias_info ( const transaction & tx )
{
tx_extra_info ei = AUTO_VAL_INIT ( ei ) ;
bool r = parse_and_validate_tx_extra ( tx , ei ) ;
CHECK_AND_ASSERT_MES ( r , false , " failed to validate transaction extra on unprocess_blockchain_tx_extra " ) ;
if ( ei . m_alias . m_alias . size ( ) )
{
m_db_alias_names . erase ( ei . m_alias . m_alias ) ;
m_db_alias_addresses . erase ( ei . m_alias . m_address ) ;
}
return true ;
}
//---------------------------------------------------------------------------------
2019-11-16 20:26:40 +01:00
bool tx_memory_pool : : remove_key_images ( const crypto : : hash & tx_id , const transaction & tx , bool kept_by_block )
2018-12-27 18:50:45 +03:00
{
2019-11-16 20:26:40 +01:00
CRITICAL_REGION_LOCAL ( m_key_images_lock ) ;
2018-12-27 18:50:45 +03:00
for ( const auto & in : tx . vin )
{
if ( in . type ( ) = = typeid ( txin_to_key ) )
2019-11-16 20:26:40 +01:00
{
const txin_to_key & tokey_in = boost : : get < txin_to_key > ( in ) ;
auto it_map = epee : : misc_utils : : it_get_or_insert_value_initialized ( m_key_images , tokey_in . k_image ) ;
auto & id_set = it_map - > second ;
size_t count_before = id_set . size ( ) ;
auto it_set = id_set . find ( tx_id ) ;
if ( it_set ! = id_set . end ( ) )
id_set . erase ( it_set ) ;
size_t count_after = id_set . size ( ) ;
if ( id_set . size ( ) = = 0 )
m_key_images . erase ( it_map ) ;
LOG_PRINT_L2 ( " tx pool: key image removed: " < < tokey_in . k_image < < " , from tx " < < tx_id < < " , counter: " < < count_before < < " -> " < < count_after ) ;
2018-12-27 18:50:45 +03:00
}
}
return false ;
}
//---------------------------------------------------------------------------------
2019-11-16 20:26:40 +01:00
bool tx_memory_pool : : get_key_images_from_tx_pool ( key_image_cache & key_images ) const
2018-12-27 18:50:45 +03:00
{
m_db_transactions . enumerate_items ( [ & ] ( uint64_t i , const crypto : : hash & h , const tx_details & tx_entry )
{
for ( auto & in : tx_entry . tx . vin )
{
if ( in . type ( ) = = typeid ( txin_to_key ) )
{
2019-11-16 20:26:40 +01:00
key_images [ boost : : get < txin_to_key > ( in ) . k_image ] . insert ( h ) ;
2018-12-27 18:50:45 +03:00
}
}
return true ;
} ) ;
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : have_tx_keyimg_as_spent ( const crypto : : key_image & key_im ) const
{
2019-11-16 20:26:40 +01:00
CRITICAL_REGION_LOCAL ( m_key_images_lock ) ;
auto it = m_key_images . find ( key_im ) ;
if ( it ! = m_key_images . end ( ) )
2018-12-27 18:50:45 +03:00
return true ;
return false ;
}
//---------------------------------------------------------------------------------
void tx_memory_pool : : lock ( )
{
CRITICAL_SECTION_LOCK ( m_remove_stuck_txs_lock ) ;
}
//---------------------------------------------------------------------------------
void tx_memory_pool : : unlock ( )
{
CRITICAL_SECTION_UNLOCK ( m_remove_stuck_txs_lock ) ;
}
//---------------------------------------------------------------------------------
void tx_memory_pool : : purge_transactions ( )
{
m_db . begin_transaction ( ) ;
m_db_transactions . clear ( ) ;
m_db . commit_transaction ( ) ;
// should m_db_black_tx_list be cleared here?
2019-11-16 20:26:40 +01:00
CIRITCAL_OPERATION ( m_key_images , clear ( ) ) ;
2018-12-27 18:50:45 +03:00
}
//---------------------------------------------------------------------------------
void tx_memory_pool : : clear ( )
{
m_db . begin_transaction ( ) ;
m_db_transactions . clear ( ) ;
m_db_black_tx_list . clear ( ) ;
m_db . commit_transaction ( ) ;
2019-11-16 20:26:40 +01:00
CIRITCAL_OPERATION ( m_key_images , clear ( ) ) ;
2018-12-27 18:50:45 +03:00
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : is_transaction_ready_to_go ( tx_details & txd , const crypto : : hash & id ) const
{
//not the best implementation at this time, sorry :(
if ( m_db_black_tx_list . get ( get_transaction_hash ( txd . tx ) ) )
return false ;
//check is ring_signature already checked ?
if ( txd . max_used_block_id = = null_hash )
{ //not checked, lets try to check
if ( txd . last_failed_id ! = null_hash & & m_blockchain . get_current_blockchain_size ( ) > txd . last_failed_height & & txd . last_failed_id = = m_blockchain . get_block_id_by_height ( txd . last_failed_height ) )
return false ; //we already sure that this tx is broken for this height
if ( ! m_blockchain . check_tx_inputs ( txd . tx , id , txd . max_used_block_height , txd . max_used_block_id ) )
{
txd . last_failed_height = m_blockchain . get_top_block_height ( ) ;
txd . last_failed_id = m_blockchain . get_block_id_by_height ( txd . last_failed_height ) ;
return false ;
}
} else
{
if ( txd . max_used_block_height > = m_blockchain . get_current_blockchain_size ( ) )
return false ;
if ( m_blockchain . get_block_id_by_height ( txd . max_used_block_height ) ! = txd . max_used_block_id )
{
//if we already failed on this height and id, skip actual ring signature check
if ( txd . last_failed_id = = m_blockchain . get_block_id_by_height ( txd . last_failed_height ) )
return false ;
//check ring signature again, it is possible (with very small chance) that this transaction become again valid
if ( ! m_blockchain . check_tx_inputs ( txd . tx , id , txd . max_used_block_height , txd . max_used_block_id ) )
{
txd . last_failed_height = m_blockchain . get_top_block_height ( ) ;
txd . last_failed_id = m_blockchain . get_block_id_by_height ( txd . last_failed_height ) ;
return false ;
}
}
}
//if we here, transaction seems valid, but, anyway, check for key_images collisions with blockchain, just to be sure
2019-04-25 01:41:49 +02:00
if ( m_blockchain . have_tx_keyimges_as_spent ( txd . tx ) )
{
2018-12-27 18:50:45 +03:00
return false ;
2019-04-25 01:41:49 +02:00
}
2018-12-27 18:50:45 +03:00
if ( ! check_tx_multisig_ins_and_outs ( txd . tx , false ) )
return false ;
//transaction is ok.
return true ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : have_key_images ( const std : : unordered_set < crypto : : key_image > & k_images , const transaction & tx ) const
{
LOCAL_READONLY_TRANSACTION ( ) ;
for ( size_t i = 0 ; i ! = tx . vin . size ( ) ; i + + )
{
if ( tx . vin [ i ] . type ( ) = = typeid ( txin_to_key ) )
{
CHECKED_GET_SPECIFIC_VARIANT ( tx . vin [ i ] , const txin_to_key , itk , false ) ;
if ( k_images . count ( itk . k_image ) )
return true ;
}
}
return false ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : append_key_images ( std : : unordered_set < crypto : : key_image > & k_images , const transaction & tx )
{
for ( size_t i = 0 ; i ! = tx . vin . size ( ) ; i + + )
{
if ( tx . vin [ i ] . type ( ) = = typeid ( txin_to_key ) )
{
CHECKED_GET_SPECIFIC_VARIANT ( tx . vin [ i ] , const txin_to_key , itk , false ) ;
auto i_res = k_images . insert ( itk . k_image ) ;
CHECK_AND_ASSERT_MES ( i_res . second , false , " internal error: key images pool cache - inserted duplicate image in set: " < < itk . k_image ) ;
}
}
return true ;
}
//---------------------------------------------------------------------------------
std : : string tx_memory_pool : : print_pool ( bool short_format ) const
{
std : : stringstream ss ;
if ( short_format )
{
std : : list < std : : pair < crypto : : hash , tx_details > > txs ;
{
m_db_transactions . enumerate_items ( [ & txs ] ( uint64_t i , const crypto : : hash & h , const tx_details & tx_entry ) { txs . push_back ( std : : make_pair ( h , tx_entry ) ) ; return true ; } ) ;
}
if ( txs . empty ( ) )
return " (no transactions, the pool is empty) " ;
// sort output by receive time
txs . sort ( [ ] ( const std : : pair < crypto : : hash , tx_details > & lhs , const std : : pair < crypto : : hash , tx_details > & rhs ) - > bool { return lhs . second . receive_time < rhs . second . receive_time ; } ) ;
ss < < " # | transaction id | size | fee | ins | outs | outs money | live_time | max used block | last failed block | kept by a block? " < < ENDL ;
// 1234 <f99fe6d4335fc0ddd69e6880a4d95e0f6ea398de0324a6837021a61c6a31cacd> 87157 0.10000111 2000 2000 112000.12345678 d0.h10.m16.s17 123456 <12345..> 123456 <12345..> YES
size_t i = 0 ;
for ( auto & tx : txs )
{
auto & txd = tx . second ;
ss < < std : : left
< < std : : setw ( 4 ) < < i + + < < " "
< < tx . first < < " "
< < std : : setw ( 5 ) < < txd . blob_size < < " "
< < std : : setw ( 10 ) < < print_money_brief ( txd . fee ) < < " "
< < std : : setw ( 4 ) < < txd . tx . vin . size ( ) < < " "
< < std : : setw ( 4 ) < < txd . tx . vout . size ( ) < < " "
< < std : : right < < std : : setw ( 15 ) < < print_money ( get_outs_money_amount ( txd . tx ) ) < < std : : left < < " "
< < std : : setw ( 14 ) < < epee : : misc_utils : : get_time_interval_string ( get_core_time ( ) - txd . receive_time ) < < " "
< < std : : setw ( 6 ) < < txd . max_used_block_height < < " "
< < std : : setw ( 9 ) < < print16 ( txd . max_used_block_id ) < < " "
< < std : : setw ( 6 ) < < txd . last_failed_height < < " "
< < std : : setw ( 9 ) < < print16 ( txd . last_failed_id ) < < " "
< < ( txd . kept_by_block ? " YES " : " no " )
< < ENDL ;
}
return ss . str ( ) ;
}
// long format
m_db_transactions . enumerate_items ( [ & ] ( uint64_t i , const crypto : : hash & h , const tx_details & tx_entry )
{
auto & txd = tx_entry ;
ss < < " id: " < < h < < ENDL
< < obj_to_json_str ( txd . tx ) < < ENDL
< < " blob_size: " < < txd . blob_size < < ENDL
< < " fee: " < < txd . fee < < ENDL
< < " kept_by_block: " < < ( txd . kept_by_block ? " true " : " false " ) < < ENDL
< < " max_used_block_height: " < < txd . max_used_block_height < < ENDL
< < " max_used_block_id: " < < txd . max_used_block_id < < ENDL
< < " last_failed_height: " < < txd . last_failed_height < < ENDL
< < " last_failed_id: " < < txd . last_failed_id < < ENDL
< < " live_time: " < < epee : : misc_utils : : get_time_interval_string ( get_core_time ( ) - txd . receive_time ) < < ENDL ;
return true ;
} ) ;
return ss . str ( ) ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : fill_block_template ( block & bl ,
bool pos ,
size_t median_size ,
2019-04-10 03:00:29 +02:00
const boost : : multiprecision : : uint128_t & already_generated_coins ,
2018-12-27 18:50:45 +03:00
size_t & total_size ,
uint64_t & fee ,
2019-11-15 01:04:51 +01:00
uint64_t height ,
const std : : list < transaction > & explicit_txs
)
2018-12-27 18:50:45 +03:00
{
LOCAL_READONLY_TRANSACTION ( ) ;
//typedef transactions_container::value_type txv;
typedef std : : pair < crypto : : hash , std : : shared_ptr < const tx_details > > txv ;
std : : vector < txv > txs_v ;
txs_v . reserve ( m_db_transactions . size ( ) ) ;
2020-02-17 13:13:07 +03:00
std : : vector < size_t > txs ; // selected transactions, vector of indices of txs_v
2018-12-27 18:50:45 +03:00
2019-11-15 01:04:51 +01:00
//keep getting it as a values cz db items cache will keep it as unserialised object stored by shared ptrs
2018-12-27 18:50:45 +03:00
m_db_transactions . enumerate_keys ( [ & ] ( uint64_t i , crypto : : hash & k ) { txs_v . resize ( i + 1 ) ; txs_v [ i ] . first = k ; return true ; } ) ;
2020-02-17 13:13:07 +03:00
txs . resize ( txs_v . size ( ) , SIZE_MAX ) ;
2018-12-27 18:50:45 +03:00
for ( uint64_t i = 0 ; i ! = txs_v . size ( ) ; i + + )
{
auto & k = txs_v [ i ] . first ;
txs_v [ i ] . second = m_db_transactions . get ( k ) ;
if ( ! txs_v [ i ] . second )
{
LOG_ERROR ( " Internal tx pool db error: key " < < k < < " was enumerated as key but couldn't get value " ) ;
2020-02-14 15:53:39 +03:00
return false ;
2018-12-27 18:50:45 +03:00
}
2020-02-17 13:13:07 +03:00
txs [ i ] = i ;
2018-12-27 18:50:45 +03:00
}
2020-02-17 13:13:07 +03:00
std : : sort ( txs . begin ( ) , txs . end ( ) , [ & txs_v ] ( size_t a , size_t b ) - > bool {
2018-12-27 18:50:45 +03:00
boost : : multiprecision : : uint128_t a_ , b_ ;
2020-02-17 13:13:07 +03:00
a_ = boost : : multiprecision : : uint128_t ( txs_v [ a ] . second - > fee ) * txs_v [ b ] . second - > blob_size ;
b_ = boost : : multiprecision : : uint128_t ( txs_v [ b ] . second - > fee ) * txs_v [ a ] . second - > blob_size ;
2018-12-27 18:50:45 +03:00
return a_ > b_ ;
} ) ;
2019-11-15 01:04:51 +01:00
size_t explicit_total_size = get_objects_blobsize ( explicit_txs ) ;
size_t current_size = explicit_total_size ;
2018-12-27 18:50:45 +03:00
uint64_t current_fee = 0 ;
uint64_t best_money ;
if ( ! get_block_reward ( pos , median_size , CURRENCY_COINBASE_BLOB_RESERVED_SIZE , already_generated_coins , best_money , height ) ) {
LOG_ERROR ( " Block with just a miner transaction is already too large! " ) ;
return false ;
}
size_t best_position = 0 ;
total_size = 0 ;
fee = 0 ;
uint64_t alias_count = 0 ;
// scan txs for alias reg requests - if there are such requests, don't process alias updates
bool alias_regs_exist = false ;
2020-02-17 13:13:07 +03:00
for ( auto txi : txs )
2018-12-27 18:50:45 +03:00
{
tx_extra_info ei = AUTO_VAL_INIT ( ei ) ;
2020-02-17 13:13:07 +03:00
bool r = parse_and_validate_tx_extra ( txs_v [ txi ] . second - > tx , ei ) ;
2018-12-27 18:50:45 +03:00
CHECK_AND_ASSERT_MES ( r , false , " parse_and_validate_tx_extra failed while looking up the tx pool " ) ;
if ( ! ei . m_alias . m_alias . empty ( ) & & ! ei . m_alias . m_sign . size ( ) ) {
alias_regs_exist = true ;
break ;
}
}
const uint64_t tx_expiration_ts_median = m_blockchain . get_tx_expiration_median ( ) ;
std : : unordered_set < crypto : : key_image > k_images ;
for ( size_t i = 0 ; i < txs . size ( ) ; i + + )
{
2020-02-17 13:13:07 +03:00
txv & tx ( txs_v [ txs [ i ] ] ) ;
2018-12-27 18:50:45 +03:00
// expiration time check -- skip expired transactions
if ( is_tx_expired ( tx . second - > tx , tx_expiration_ts_median ) )
{
2020-02-17 13:13:07 +03:00
txs [ i ] = SIZE_MAX ;
2018-12-27 18:50:45 +03:00
continue ;
}
// alias checks
tx_extra_info ei = AUTO_VAL_INIT ( ei ) ;
bool r = parse_and_validate_tx_extra ( tx . second - > tx , ei ) ;
CHECK_AND_ASSERT_MES ( r , false , " failed to validate transaction extra on unprocess_blockchain_tx_extra " ) ;
if ( ! ei . m_alias . m_alias . empty ( ) )
{
bool update_an_alias = ! ei . m_alias . m_sign . empty ( ) ;
if ( ( alias_count > = MAX_ALIAS_PER_BLOCK ) | | // IF this tx registers/updates an alias AND alias per block threshold exceeded
( update_an_alias & & alias_regs_exist ) ) // OR this tx updates an alias AND there are alias reg requests...
{
2020-02-17 13:13:07 +03:00
txs [ i ] = SIZE_MAX ; // ...skip this tx
2018-12-27 18:50:45 +03:00
continue ;
}
}
//is_transaction_ready_to_go can change tx_details in case of some errors, so we make local copy,
//do check if it's changed and reassign it to db if needed
tx_details local_copy_txd = * ( tx . second ) ;
bool is_tx_ready_to_go_result = is_transaction_ready_to_go ( local_copy_txd , tx . first ) ;
if ( ! is_tx_ready_to_go_result & &
( local_copy_txd . last_failed_height ! = tx . second - > last_failed_height | | local_copy_txd . last_failed_id ! = tx . second - > last_failed_id ) )
{
m_db_transactions . begin_transaction ( ) ;
m_db_transactions . set ( get_transaction_hash ( local_copy_txd . tx ) , local_copy_txd ) ;
m_db_transactions . commit_transaction ( ) ;
}
if ( ! is_tx_ready_to_go_result | | have_key_images ( k_images , tx . second - > tx ) ) {
2020-02-17 13:13:07 +03:00
txs [ i ] = SIZE_MAX ;
2018-12-27 18:50:45 +03:00
continue ;
}
append_key_images ( k_images , tx . second - > tx ) ;
current_size + = tx . second - > blob_size ;
current_fee + = tx . second - > fee ;
uint64_t current_reward ;
if ( ! get_block_reward ( pos , median_size , current_size + CURRENCY_COINBASE_BLOB_RESERVED_SIZE , already_generated_coins , current_reward , height ) )
{
break ; // current block size is too big
}
if ( best_money < current_reward + current_fee ) {
best_money = current_reward + current_fee ;
best_position = i + 1 ;
total_size = current_size ;
fee = current_fee ;
}
if ( ! ei . m_alias . m_alias . empty ( ) )
+ + alias_count ;
}
for ( size_t i = 0 ; i ! = txs . size ( ) ; i + + )
{
2020-02-17 13:13:07 +03:00
if ( txs [ i ] ! = SIZE_MAX )
2018-12-27 18:50:45 +03:00
{
2020-02-17 13:13:07 +03:00
txv & tx ( txs_v [ txs [ i ] ] ) ;
2018-12-27 18:50:45 +03:00
if ( i < best_position )
{
2020-02-17 13:13:07 +03:00
bl . tx_hashes . push_back ( tx . first ) ;
2018-12-27 18:50:45 +03:00
}
2020-02-17 13:13:07 +03:00
else if ( have_attachment_service_in_container ( tx . second - > tx . attachment , BC_OFFERS_SERVICE_ID , BC_OFFERS_SERVICE_INSTRUCTION_DEL ) )
2018-12-27 18:50:45 +03:00
{
// BC_OFFERS_SERVICE_INSTRUCTION_DEL transactions has zero fee, so include them here regardless of reward effectiveness
2020-02-17 13:13:07 +03:00
bl . tx_hashes . push_back ( tx . first ) ;
total_size + = tx . second - > blob_size ;
2018-12-27 18:50:45 +03:00
}
}
}
2019-11-15 01:04:51 +01:00
// add explicit transactions
for ( const auto & tx : explicit_txs )
{
2019-11-29 21:43:17 +01:00
fee + = get_tx_fee ( tx ) ;
2019-11-15 01:04:51 +01:00
bl . tx_hashes . push_back ( get_transaction_hash ( tx ) ) ;
}
2018-12-27 18:50:45 +03:00
return true ;
}
//---------------------------------------------------------------------------------
2019-08-17 07:10:37 +03:00
void tx_memory_pool : : store_db_solo_options_values ( )
2018-12-27 18:50:45 +03:00
{
m_db . begin_transaction ( ) ;
m_db_storage_major_compatibility_version = TRANSACTION_POOL_MAJOR_COMPATIBILITY_VERSION ;
m_db . commit_transaction ( ) ;
}
//---------------------------------------------------------------------------------
2020-03-16 17:48:02 +03:00
bool tx_memory_pool : : init ( const std : : string & config_folder , const boost : : program_options : : variables_map & vm )
2018-12-27 18:50:45 +03:00
{
2020-03-16 17:48:02 +03:00
tools : : db : : db_backend_selector dbbs ;
dbbs . init ( vm ) ;
2020-03-12 13:21:22 +03:00
auto p_backend = dbbs . create_backend ( ) ;
if ( ! p_backend )
2019-10-25 00:13:38 +02:00
{
2020-03-12 13:21:22 +03:00
LOG_PRINT_RED_L0 ( " Failed to create db engine " ) ;
2019-10-25 00:13:38 +02:00
return false ;
}
2020-03-12 13:21:22 +03:00
m_db . reset_backend ( p_backend ) ;
2019-10-25 00:13:38 +02:00
LOG_PRINT_L0 ( " DB ENGINE USED BY POOL: " < < m_db . get_backend ( ) - > name ( ) ) ;
2018-12-27 18:50:45 +03:00
m_config_folder = config_folder ;
2019-08-28 17:58:35 +03:00
uint64_t cache_size_l1 = CACHE_SIZE ;
LOG_PRINT_GREEN ( " Using pool db file cache size(L1): " < < cache_size_l1 , LOG_LEVEL_0 ) ;
2020-01-14 23:27:05 +01:00
// remove old incompatible DB
2019-08-28 17:58:35 +03:00
const std : : string old_db_folder_path = m_config_folder + " / " CURRENCY_POOLDATA_FOLDERNAME_OLD ;
2019-09-25 17:09:38 +03:00
if ( boost : : filesystem : : exists ( epee : : string_encoding : : utf8_to_wstring ( old_db_folder_path ) ) )
2019-08-28 17:58:35 +03:00
{
LOG_PRINT_YELLOW ( " Removing old DB in " < < old_db_folder_path < < " ... " , LOG_LEVEL_0 ) ;
2019-09-25 17:09:38 +03:00
boost : : filesystem : : remove_all ( epee : : string_encoding : : utf8_to_wstring ( old_db_folder_path ) ) ;
2019-08-28 17:58:35 +03:00
}
2020-03-16 17:48:02 +03:00
const std : : string db_folder_path = dbbs . get_pool_db_folder_path ( ) ;
2019-10-25 00:13:38 +02:00
2019-08-28 17:58:35 +03:00
LOG_PRINT_L0 ( " Loading blockchain from " < < db_folder_path < < " ... " ) ;
2019-08-17 07:10:37 +03:00
bool db_opened_okay = false ;
for ( size_t loading_attempt_no = 0 ; loading_attempt_no < 2 ; + + loading_attempt_no )
2018-12-27 18:50:45 +03:00
{
2019-08-28 17:58:35 +03:00
bool res = m_db . open ( db_folder_path , cache_size_l1 ) ;
2019-08-17 07:10:37 +03:00
if ( ! res )
{
// if DB could not be opened -- try to remove the whole folder and re-open DB
LOG_PRINT_YELLOW ( " Failed to initialize database in folder: " < < db_folder_path < < " , first attempt " , LOG_LEVEL_0 ) ;
2019-09-25 17:09:38 +03:00
boost : : filesystem : : remove_all ( epee : : string_encoding : : utf8_to_wstring ( db_folder_path ) ) ;
2019-08-28 17:58:35 +03:00
res = m_db . open ( db_folder_path , cache_size_l1 ) ;
2019-08-17 07:10:37 +03:00
CHECK_AND_ASSERT_MES ( res , false , " Failed to initialize database in folder: " < < db_folder_path < < " , second attempt " ) ;
}
res = m_db_transactions . init ( TRANSACTION_POOL_CONTAINER_TRANSACTIONS ) ;
CHECK_AND_ASSERT_MES ( res , false , " Unable to init db container " ) ;
res = m_db_black_tx_list . init ( TRANSACTION_POOL_CONTAINER_BLACK_TX_LIST ) ;
CHECK_AND_ASSERT_MES ( res , false , " Unable to init db container " ) ;
res = m_db_alias_names . init ( TRANSACTION_POOL_CONTAINER_ALIAS_NAMES ) ;
CHECK_AND_ASSERT_MES ( res , false , " Unable to init db container " ) ;
res = m_db_alias_addresses . init ( TRANSACTION_POOL_CONTAINER_ALIAS_ADDRESSES ) ;
CHECK_AND_ASSERT_MES ( res , false , " Unable to init db container " ) ;
res = m_db_solo_options . init ( TRANSACTION_POOL_CONTAINER_SOLO_OPTIONS ) ;
CHECK_AND_ASSERT_MES ( res , false , " Unable to init db container " ) ;
m_db_transactions . set_cache_size ( 1000 ) ;
m_db_alias_names . set_cache_size ( 10000 ) ;
m_db_alias_addresses . set_cache_size ( 10000 ) ;
m_db_black_tx_list . set_cache_size ( 1000 ) ;
bool need_reinit = false ;
if ( m_db_storage_major_compatibility_version > 0 & & m_db_storage_major_compatibility_version ! = TRANSACTION_POOL_MAJOR_COMPATIBILITY_VERSION )
{
need_reinit = true ;
LOG_PRINT_MAGENTA ( " Tx pool DB needs reinit because it has major compatibility ver is " < < m_db_storage_major_compatibility_version < < " , expected: " < < TRANSACTION_POOL_MAJOR_COMPATIBILITY_VERSION , LOG_LEVEL_0 ) ;
}
if ( need_reinit )
{
LOG_PRINT_L1 ( " DB at " < < db_folder_path < < " is about to be deleted and re-created... " ) ;
m_db_transactions . deinit ( ) ;
2019-11-16 20:26:40 +01:00
// m_db_key_images_set.deinit();
2019-08-17 07:10:37 +03:00
m_db_black_tx_list . deinit ( ) ;
m_db_alias_names . deinit ( ) ;
m_db_alias_addresses . deinit ( ) ;
m_db_solo_options . deinit ( ) ;
m_db . close ( ) ;
2019-09-25 17:09:38 +03:00
size_t files_removed = boost : : filesystem : : remove_all ( epee : : string_encoding : : utf8_to_wstring ( db_folder_path ) ) ;
2019-08-17 07:10:37 +03:00
LOG_PRINT_L1 ( files_removed < < " files at " < < db_folder_path < < " removed " ) ;
// try to re-create DB and re-init containers
continue ;
}
db_opened_okay = true ;
break ;
2018-12-27 18:50:45 +03:00
}
2019-08-17 07:10:37 +03:00
CHECK_AND_ASSERT_MES ( db_opened_okay , false , " All attempts to open DB at " < < db_folder_path < < " failed " ) ;
store_db_solo_options_values ( ) ;
LOG_PRINT_GREEN ( " tx pool loaded ok from " < < db_folder_path < < " , loaded " < < m_db_transactions . size ( ) < < " transactions " , LOG_LEVEL_0 ) ;
if ( epee : : log_space : : log_singletone : : get_log_detalisation_level ( ) > = LOG_LEVEL_2 & & m_db_transactions . size ( ) ! = 0 )
{
std : : stringstream ss ;
m_db_transactions . enumerate_items ( [ & ] ( uint64_t i , const crypto : : hash & h , const tx_details & tx_entry )
{
ss < < h < < " sz: " < < std : : setw ( 5 ) < < tx_entry . blob_size < < " rcv: " < < misc_utils : : get_time_interval_string ( time ( nullptr ) - tx_entry . receive_time ) < < " ago " < < ENDL ;
return true ;
} ) ;
LOG_PRINT_L2 ( ss . str ( ) ) ;
}
2019-11-16 20:26:40 +01:00
load_keyimages_cache ( ) ;
2018-12-27 18:50:45 +03:00
return true ;
}
2019-11-16 20:26:40 +01:00
//---------------------------------------------------------------------------------
bool tx_memory_pool : : load_keyimages_cache ( )
{
CRITICAL_REGION_LOCAL ( m_key_images_lock ) ;
return get_key_images_from_tx_pool ( m_key_images ) ;
}
2018-12-27 18:50:45 +03:00
//---------------------------------------------------------------------------------
bool tx_memory_pool : : deinit ( )
{
m_db . close ( ) ;
return true ;
}
//---------------------------------------------------------------------------------
uint64_t tx_memory_pool : : get_core_time ( ) const
{
return m_blockchain . get_core_runtime_config ( ) . get_core_time ( ) ;
}
//---------------------------------------------------------------------------------
namespace
{
struct ms_out_info
{
crypto : : hash multisig_id ;
size_t input_output_index ;
bool is_input ;
} ;
}
//---------------------------------------------------------------------------------
void collect_multisig_ids_from_tx ( const transaction & tx , std : : vector < ms_out_info > & result )
{
result . reserve ( tx . vin . size ( ) + tx . vout . size ( ) ) ;
size_t idx = 0 ;
for ( const auto & in : tx . vin )
{
if ( in . type ( ) = = typeid ( txin_multisig ) )
result . push_back ( ms_out_info ( { boost : : get < txin_multisig > ( in ) . multisig_out_id , idx , true } ) ) ;
+ + idx ;
}
idx = 0 ;
for ( const auto & out : tx . vout )
{
if ( out . target . type ( ) = = typeid ( txout_multisig ) )
result . push_back ( ms_out_info ( { get_multisig_out_id ( tx , idx ) , idx , false } ) ) ;
+ + idx ;
}
}
//---------------------------------------------------------------------------------
bool does_tx_have_given_multisig_id ( const transaction & tx , const crypto : : hash & multisig_id )
{
for ( const auto & in : tx . vin )
{
if ( in . type ( ) = = typeid ( txin_multisig ) & & boost : : get < txin_multisig > ( in ) . multisig_out_id = = multisig_id )
return true ;
}
size_t idx = 0 ;
for ( const auto & out : tx . vout )
{
if ( out . target . type ( ) = = typeid ( txout_multisig ) & & get_multisig_out_id ( tx , idx ) = = multisig_id )
return true ;
+ + idx ;
}
return false ;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool : : check_tx_multisig_ins_and_outs ( const transaction & tx , bool check_against_pool_txs ) const
{
LOCAL_READONLY_TRANSACTION ( ) ;
std : : vector < ms_out_info > ms_ids ;
collect_multisig_ids_from_tx ( tx , ms_ids ) ;
for ( auto & el : ms_ids )
{
// check given multisig output against all blockchain's multisig outputs
if ( ! el . is_input & & m_blockchain . has_multisig_output ( el . multisig_id ) )
{
LOG_PRINT_L0 ( " Transaction " < < get_transaction_hash ( tx ) < < " : output # " < < el . input_output_index < < " has multisig id " < < el . multisig_id < < " that is already in the blockchain " ) ;
return false ;
}
// check given multisig input/output against all transactions in the pool
if ( check_against_pool_txs )
{
bool has_found = false ;
m_db_transactions . enumerate_items ( [ & ] ( uint64_t i , const crypto : : hash & h , const tx_details & tx_entry )
{
if ( does_tx_have_given_multisig_id ( tx_entry . tx , el . multisig_id ) )
{
has_found = true ;
LOG_PRINT_L0 ( " Transaction " < < get_transaction_hash ( tx ) < < ( el . is_input ? " : input # " : " : output # " ) < < el . input_output_index < < " has multisig id " < < el . multisig_id < <
" that is already in the pool in tx " < < get_transaction_hash ( tx_entry . tx ) ) ;
return false ;
}
return true ;
} ) ;
if ( has_found )
return false ;
}
}
return true ;
}
}
# undef LOG_DEFAULT_CHANNEL
# define LOG_DEFAULT_CHANNEL NULL