1
0
Fork 0
forked from lthn/blockchain
blockchain/src/currency_core/tx_pool.cpp
2025-09-30 16:48:13 +01:00

1514 lines
59 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2014-2018 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// Copyright (c) 2012-2013 The Boolberry developers
// Copyright (c) 2017-2025 Lethean (https://lt.hn)
//
// Licensed under the European Union Public Licence (EUPL) version 1.2.
// You may obtain a copy of the licence at:
//
// https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
//
// The EUPL is a copyleft licence that is compatible with the MIT/X11
// licence used by the original projects; the MIT terms are therefore
// considered “grandfathered” under the EUPL for this code.
//
// SPDXLicenseIdentifier: EUPL-1.2
//
#include <algorithm>
#include <boost/filesystem.hpp>
#include <unordered_set>
#include <vector>
#include "common/db_backend_selector.h"
#include "tx_pool.h"
#include "currency_boost_serialization.h"
#include "config/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"
#include "common/db_backend_selector.h"
#include "tx_semantic_validation.h"
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"
#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!
#define TRANSACTION_POOL_MAJOR_COMPATIBILITY_VERSION BLOCKCHAIN_STORAGE_MAJOR_COMPATIBILITY_VERSION + 1
#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
#undef LOG_DEFAULT_CHANNEL
#define LOG_DEFAULT_CHANNEL "tx_pool"
ENABLE_CHANNEL_BY_DEFAULT("tx_pool");
using namespace epee;
namespace currency
{
//---------------------------------------------------------------------------------
tx_memory_pool::tx_memory_pool(blockchain_storage& bchs, i_currency_protocol* pprotocol) :
m_blockchain(bchs),
m_pprotocol(pprotocol),
m_db(nullptr, m_dummy_rw_lock),
m_db_transactions(m_db),
m_db_black_tx_list(m_db),
m_db_solo_options(m_db),
// m_db_key_images_set(m_db),
m_db_alias_names(m_db),
m_db_alias_addresses(m_db),
m_db_storage_major_compatibility_version(TRANSACTION_POOL_OPTIONS_ID_STORAGE_MAJOR_COMPATIBILITY_VERSION, m_db_solo_options)
{
}
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::check_tx_fee(const transaction &tx, uint64_t amount_fee) const
{
if (amount_fee < m_blockchain.get_core_runtime_config().tx_pool_min_fee)
return false;
//m_blockchain.get
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)
{
// ------------------ UNSECURE CODE FOR TESTS ---------------------
if (m_unsecure_disable_tx_validation_on_addition)
{
uint64_t tx_fee = 0;
CHECK_AND_ASSERT_MES(get_tx_fee(tx, tx_fee), false, "get_tx_fee failed");
do_insert_transaction(tx, id, blob_size, kept_by_block, tx_fee, null_hash, 0);
tvc.m_added_to_pool = true;
tvc.m_should_be_relayed = true;
tvc.m_verification_failed = false;
tvc.m_verification_impossible = false;
return true;
}
// ---------------- END OF UNSECURE CODE FOR TESTS -------------------
bool r = false;
// defaults
tvc.m_added_to_pool = false;
tvc.m_verification_failed = true;
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;
}
r = m_blockchain.validate_tx_for_hardfork_specific_terms(tx, id);
CHECK_AND_ASSERT_MES(r, false, "Transaction " << id <<" doesn't fit current hardfork");
TIME_MEASURE_START_PD(tx_processing_time);
TIME_MEASURE_START_PD(check_inputs_types_supported_time);
r = check_inputs_types_supported(tx);
CHECK_AND_ASSERT_MES(r, false, "tx " << id << " has wrong inputs types");
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);
CHECK_AND_ASSERT_MES(tx.vout.size() <= CURRENCY_TX_MAX_ALLOWED_OUTS, false, "transaction has too many outs = " << tx.vout.size());
uint64_t tx_fee = 0;
r = get_tx_fee(tx, tx_fee);
CHECK_AND_ASSERT_MES(r, false, "get_tx_fee failed");
// @#@# consider removing the following
//if (!check_tx_balance(tx)) // TODO (performance): check_tx_balance calls get_tx_fee as well, consider refactoring -- sowle
//{
// LOG_PRINT_L0("balance check failed for tx " << id);
// tvc.m_verification_failed = true;
// return false;
//}
TIME_MEASURE_FINISH_PD(validate_amount_time);
TIME_MEASURE_START_PD(validate_alias_time);
r = from_core || validate_alias_info(tx, kept_by_block);
CHECK_AND_ASSERT_MES(r, false, "validate_alias_info failed");
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)
{
if(!validate_tx_semantic(tx, blob_size))
{
// tx semantics check failed
LOG_PRINT_RED_L0("Transaction " << id << " semantics check failed ");
tvc.m_verification_failed = true;
tvc.m_should_be_relayed = false;
tvc.m_added_to_pool = false;
return false;
}
crypto::key_image spent_ki = AUTO_VAL_INIT(spent_ki);
r = !have_tx_keyimges_as_spent(tx, &spent_ki);
CHECK_AND_ASSERT_MES(r, false, "Transaction " << id << " uses already spent key image " << spent_ki);
//transaction spam protection, soft rule
if (!check_tx_fee(tx, tx_fee))
{
//if (is_valid_contract_finalization_tx(tx))
//{
// 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
{
// this tx has no fee
LOG_PRINT_RED_L0("Transaction " << id << " has too small fee: " << print_money_brief(tx_fee) << ", minimum fee: " << print_money_brief(m_blockchain.get_core_runtime_config().tx_pool_min_fee));
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);
blockchain_storage::check_tx_inputs_context ctic{};
ctic.calculate_max_used_block_id = true;
ctic.check_hf4_coinage_rule = false;
bool ch_inp_res = m_blockchain.check_tx_inputs(tx, id, ctic);
if (!ch_inp_res && !kept_by_block && !from_core)
{
LOG_PRINT_L0("check_tx_inputs failed, tx rejected");
tvc.m_verification_failed = true;
return false;
}
TIME_MEASURE_FINISH_PD(check_inputs_time);
if (!from_core)
{
if (tx.version > TRANSACTION_VERSION_PRE_HF4)
{
TIME_MEASURE_START_PD(check_post_hf4_balance);
r = check_tx_balance(tx, id);
CHECK_AND_ASSERT_MES_CUSTOM(r, false, { tvc.m_verification_failed = true; }, "post-HF4 tx: balance proof is invalid");
TIME_MEASURE_FINISH_PD(check_post_hf4_balance);
r = process_type_in_variant_container_and_make_sure_its_unique<asset_descriptor_operation>(tx.extra, [&](const asset_descriptor_operation& ado) {
asset_op_verification_context avc = { tx, id, ado };
return m_blockchain.validate_asset_operation(avc, m_blockchain.get_current_blockchain_size());
}, true);
CHECK_AND_ASSERT_MES_CUSTOM(r, false, { tvc.m_verification_failed = true; }, "post-HF4 tx: asset operation is invalid");
}
}
do_insert_transaction(tx, id, blob_size, kept_by_block, tx_fee, ch_inp_res ? ctic.max_used_block_id : null_hash, ch_inp_res ? ctic.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()
<< "/b"<< m_performance_data.check_post_hf4_balance.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);
on_tx_add(id, tx, kept_by_block);
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();
}
//---------------------------------------------------------------------------------
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);
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) {
//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");
r = false;
return false; // stop handling
}
#ifndef TESTNET
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
<< "\" alias in blockchain (new alias: \"" << eai.m_alias << "\"), transaction rejected");
r = false;
return false; // stop handling
}
#endif
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");
r = false;
return false; // stop handling
}
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");
r = false;
return false; // stop handling
}
//validate alias reward
if (!m_blockchain.prevalidate_alias_info(tx, eai))
{
LOG_PRINT_L0("Alias \"" << eai.m_alias << "\" reward validation failed, transaction rejected");
r = false;
return false; // stop handling
}
}
r = true;
return true; // continue handling
});
return !found || r; // if found, r must be true for success
}
//---------------------------------------------------------------------------------
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);
on_tx_remove(id, tx, txe_tr->kept_by_block);
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
int64_t tx_age = get_core_time() - tx_entry.receive_time;
if ((tx_age > CURRENCY_MEMPOOL_TX_LIVETIME ))
{
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));
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) )
{
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);
to_delete.push_back(tx_to_delete_entry(h, tx_entry.tx, tx_entry.kept_by_block));
}
// 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)
{
crypto::key_image ki = AUTO_VAL_INIT(ki);
if (get_key_image_from_txin_v(in, ki))
{
// if at least one key image is spent deep enought -- remove such tx
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;
}
}
}
}
return true;
});
for (auto& e : to_delete)
{
m_db_transactions.erase(e.hash);
on_tx_remove(e.hash, e.tx, e.kept_by_block);
}
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;
m_blockchain.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;
m_blockchain.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;
m_blockchain.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;
}
//---------------------------------------------------------------------------------
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)
{
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);
NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request r = AUTO_VAL_INIT(r);
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;
}
//---------------------------------------------------------------------------------
std::string tx_memory_pool::get_blacklisted_txs_string() const
{
std::stringstream ss;
m_db_black_tx_list.enumerate_items([&](uint64_t i, const crypto::hash& td_id, const bool& /*dummy */ )
{
ss << td_id << ENDL;
return true;
});
return ss.str();
}
//---------------------------------------------------------------------------------
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
m_db.begin_transaction();
m_db_black_tx_list.set(get_transaction_hash(tx), true);
m_db.commit_transaction();
LOG_PRINT_YELLOW("TX ADDED TO POOL'S BLACKLIST: " << get_transaction_hash(tx) << ", full black list: " << ENDL << get_blacklisted_txs_string(), LOG_LEVEL_0);
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;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx, crypto::key_image* p_spent_ki /* = nullptr */) const
{
for(const auto& in : tx.vin)
{
crypto::key_image k_image = AUTO_VAL_INIT(k_image);
if (get_key_image_from_txin_v(in, k_image))
{
if (have_tx_keyimg_as_spent(k_image))
{
if (p_spent_ki)
*p_spent_ki = k_image;
return true;
}
}
}
return false;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::insert_key_images(const crypto::hash &tx_id, const transaction& tx, bool kept_by_block)
{
CRITICAL_REGION_LOCAL(m_key_images_lock);
for(const auto& in : tx.vin)
{
crypto::key_image k_image = AUTO_VAL_INIT(k_image);
if (get_key_image_from_txin_v(in, k_image))
{
auto& id_set = m_key_images[k_image];
size_t sz_before = id_set.size();
id_set.insert(tx_id);
LOG_PRINT_L2("tx pool: key image added: " << k_image << ", from tx " << tx_id << ", counter: " << sz_before << " -> " << id_set.size());
}
}
return false;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::on_tx_add(crypto::hash tx_id, const transaction& tx, bool kept_by_block)
{
insert_key_images(tx_id, tx, kept_by_block);
insert_alias_info(tx);
return true;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::on_tx_remove(const crypto::hash &id, const transaction& tx, bool kept_by_block)
{
remove_key_images(id, tx, kept_by_block);
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;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::remove_key_images(const crypto::hash &tx_id, const transaction& tx, bool kept_by_block)
{
CRITICAL_REGION_LOCAL(m_key_images_lock);
for(const auto& in : tx.vin)
{
crypto::key_image k_image = AUTO_VAL_INIT(k_image);
if (get_key_image_from_txin_v(in, k_image))
{
auto it_map = epee::misc_utils::it_get_or_insert_value_initialized(m_key_images, 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: " << k_image << ", from tx " << tx_id << ", counter: " << count_before << " -> " << count_after);
}
}
return false;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::get_key_images_from_tx_pool(key_image_cache& key_images) const
{
m_db_transactions.enumerate_items([&](uint64_t i, const crypto::hash& h, const tx_details &tx_entry)
{
for (auto& in : tx_entry.tx.vin)
{
crypto::key_image k_image = AUTO_VAL_INIT(k_image);
if (get_key_image_from_txin_v(in, k_image))
{
key_images[k_image].insert(h);
}
}
return true;
});
return true;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::have_tx_keyimg_as_spent(const crypto::key_image& key_im)const
{
CRITICAL_REGION_LOCAL(m_key_images_lock);
auto it = m_key_images.find(key_im);
if (it != m_key_images.end())
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?
CIRITCAL_OPERATION(m_key_images,clear());
}
//---------------------------------------------------------------------------------
void tx_memory_pool::clear()
{
m_db.begin_transaction();
m_db_transactions.clear();
m_db_black_tx_list.clear();
m_db.commit_transaction();
CIRITCAL_OPERATION(m_key_images,clear());
}
//---------------------------------------------------------------------------------
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 (is_tx_blacklisted(id))
{
LOG_PRINT_L2("[is_transaction_ready_to_go]Tx " << id << " skipped as it blacklisted");
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
blockchain_storage::check_tx_inputs_context ctic{};
ctic.check_hf4_coinage_rule = false;
if(!m_blockchain.check_tx_inputs(txd.tx, id, ctic))
{
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;
}
txd.max_used_block_height = ctic.max_used_block_height;
txd.max_used_block_id = ctic.max_used_block_id;
}
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
blockchain_storage::check_tx_inputs_context ctic{};
ctic.check_hf4_coinage_rule = false;
if(!m_blockchain.check_tx_inputs(txd.tx, id, ctic))
{
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;
}
txd.max_used_block_height = ctic.max_used_block_height;
txd.max_used_block_id = ctic.max_used_block_id;
}
}
if (txd.tx.version > TRANSACTION_VERSION_PRE_HF4 && m_blockchain.get_current_blockchain_size() < txd.max_used_block_height + CURRENCY_HF4_MANDATORY_MIN_COINAGE) // coinage rule since HF4, s.a. scan_outputkeys_for_indexes()
return false;
//if we here, transaction seems valid, but, anyway, check for key_images collisions with blockchain, just to be sure
if (m_blockchain.have_tx_keyimges_as_spent(txd.tx))
{
return false;
}
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++)
{
crypto::key_image k_image = AUTO_VAL_INIT(k_image);
if (get_key_image_from_txin_v(tx.vin[i], k_image))
{
if (k_images.count(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++)
{
crypto::key_image k_image = AUTO_VAL_INIT(k_image);
if (get_key_image_from_txin_v(tx.vin[i], k_image))
{
auto i_res = k_images.insert(k_image);
CHECK_AND_ASSERT_MES(i_res.second, false, "internal error: key images pool cache - inserted duplicate image in set: " << 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 | live_time | max used block | last failed block | ver | status " << ENDL;
// 1234 f99fe6d4335fc0ddd69e6880a4d95e0f6ea398de0324a6837021a61c6a31cacd 187157 0.10000111 2000 2000 d0.h10.m16.s17 1234567 <12345..> 1234567 <12345..> 2 kept_by_block BLACKLISTED
size_t i = 0;
for (auto& tx : txs)
{
auto& txd = tx.second;
ss << std::left
<< std::setw(4) << i++ << " "
<< tx.first << " "
<< std::setw(6) << 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::setw(14) << epee::misc_utils::get_time_interval_string(get_core_time() - txd.receive_time) << " "
<< std::setw(7) << txd.max_used_block_height << " "
<< std::setw(9) << print16(txd.max_used_block_id) << " "
<< std::setw(7) << txd.last_failed_height << " "
<< std::setw(9) << print16(txd.last_failed_id) << " "
<< std::setw(3) << txd.tx.version << " "
<< (txd.kept_by_block ? "kept_by_block " : "") << (is_tx_blacklisted(tx.first) ? "BLACKLISTED " : "")
<< 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,
const boost::multiprecision::uint128_t& already_generated_coins,
size_t &total_size,
uint64_t &fee,
uint64_t height,
const std::list<transaction>& explicit_txs
)
{
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());
std::vector<size_t> txs; // selected transactions, vector of indices of txs_v
//keep getting it as a values cz db items cache will keep it as unserialised object stored by shared ptrs
m_db_transactions.enumerate_keys([&](uint64_t i, crypto::hash& k){txs_v.resize(i + 1); txs_v[i].first = k; return true;});
txs.resize(txs_v.size(), SIZE_MAX);
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");
return false;
}
txs[i] = i;
}
std::sort(txs.begin(), txs.end(), [&txs_v](size_t a, size_t b) -> bool {
boost::multiprecision::uint128_t a_, b_;
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;
return a_ > b_;
});
size_t explicit_total_size = get_objects_blobsize(explicit_txs);
size_t current_size = explicit_total_size;
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;
for (auto txi : txs)
{
tx_extra_info ei = AUTO_VAL_INIT(ei);
bool r = parse_and_validate_tx_extra(txs_v[txi].second->tx, ei);
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++)
{
txv &tx(txs_v[txs[i]]);
// expiration time check -- skip expired transactions
if (is_tx_expired(tx.second->tx, tx_expiration_ts_median))
{
txs[i] = SIZE_MAX;
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...
{
txs[i] = SIZE_MAX; // ...skip this tx
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)) {
txs[i] = SIZE_MAX;
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++)
{
if (txs[i] != SIZE_MAX)
{
txv &tx(txs_v[txs[i]]);
if (i < best_position)
{
bl.tx_hashes.push_back(tx.first);
LOG_PRINT_L2("[fill_block_template]: Added tx to block: " << tx.first);
}
/*else if (have_attachment_service_in_container(tx.second->tx.attachment, BC_OFFERS_SERVICE_ID, BC_OFFERS_SERVICE_INSTRUCTION_DEL))
{
// BC_OFFERS_SERVICE_INSTRUCTION_DEL transactions has zero fee, so include them here regardless of reward effectiveness
bl.tx_hashes.push_back(tx.first);
total_size += tx.second->blob_size;
}*/
}
}
// add explicit transactions
for (const auto& tx : explicit_txs)
{
fee += get_tx_fee(tx);
bl.tx_hashes.push_back(get_transaction_hash(tx));
}
return true;
}
//---------------------------------------------------------------------------------
void tx_memory_pool::store_db_solo_options_values()
{
m_db.begin_transaction();
m_db_storage_major_compatibility_version = TRANSACTION_POOL_MAJOR_COMPATIBILITY_VERSION;
m_db.commit_transaction();
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::init(const std::string& config_folder, const boost::program_options::variables_map& vm)
{
tools::db::db_backend_selector dbbs;
dbbs.init(vm);
auto p_backend = dbbs.create_backend();
if (!p_backend)
{
LOG_PRINT_RED_L0("Failed to create db engine");
return false;
}
m_db.reset_backend(p_backend);
LOG_PRINT_L0("DB ENGINE USED BY POOL: " << m_db.get_backend()->name());
m_config_folder = config_folder;
uint64_t cache_size_l1 = CACHE_SIZE;
LOG_PRINT_GREEN("Using pool db file cache size(L1): " << cache_size_l1, LOG_LEVEL_0);
// remove old incompatible DB
const std::string old_db_folder_path = m_config_folder + "/" CURRENCY_POOLDATA_FOLDERNAME_OLD;
if (boost::filesystem::exists(epee::string_encoding::utf8_to_wstring(old_db_folder_path)))
{
LOG_PRINT_YELLOW("Removing old DB in " << old_db_folder_path << "...", LOG_LEVEL_0);
boost::filesystem::remove_all(epee::string_encoding::utf8_to_wstring(old_db_folder_path));
}
const std::string db_folder_path = dbbs.get_pool_db_folder_path();
LOG_PRINT_L0("Loading blockchain from " << db_folder_path << "...");
bool db_opened_okay = false;
for(size_t loading_attempt_no = 0; loading_attempt_no < 2; ++loading_attempt_no)
{
bool res = m_db.open(db_folder_path, cache_size_l1);
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);
boost::filesystem::remove_all(epee::string_encoding::utf8_to_wstring(db_folder_path));
res = m_db.open(db_folder_path, cache_size_l1);
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();
// m_db_key_images_set.deinit();
m_db_black_tx_list.deinit();
m_db_alias_names.deinit();
m_db_alias_addresses.deinit();
m_db_solo_options.deinit();
m_db.close();
size_t files_removed = boost::filesystem::remove_all(epee::string_encoding::utf8_to_wstring(db_folder_path));
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;
}
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());
}
load_keyimages_cache();
return true;
}
//---------------------------------------------------------------------------------
void tx_memory_pool::remove_incompatible_txs()
{
std::vector<crypto::hash> invalid_tx_ids;
m_db_transactions.enumerate_items([&](uint64_t i, const crypto::hash& h, const tx_details &tx_entry)
{
if (!m_blockchain.validate_tx_for_hardfork_specific_terms(tx_entry.tx, h))
invalid_tx_ids.push_back(h);
return true;
});
for(const auto& id : invalid_tx_ids)
{
transaction tx{};
size_t blob_size = 0;
uint64_t fee = 0;
take_tx(id, tx, blob_size, fee);
LOG_PRINT_L0("tx " << id << " was incompatible with the hardfork rules and removed");
}
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::is_tx_blacklisted(const crypto::hash& id) const
{
return m_db_black_tx_list.get(id) != nullptr;
}
//---------------------------------------------------------------------------------
bool tx_memory_pool::load_keyimages_cache()
{
CRITICAL_REGION_LOCAL(m_key_images_lock);
return get_key_images_from_tx_pool(m_key_images);
}
//---------------------------------------------------------------------------------
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)
{
VARIANT_SWITCH_BEGIN(out);
VARIANT_CASE_CONST(tx_out_bare, o)
if (o.target.type() == typeid(txout_multisig))
result.push_back(ms_out_info({ get_multisig_out_id(tx, idx), idx, false }));
VARIANT_SWITCH_END();
++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)
{
VARIANT_SWITCH_BEGIN(out);
VARIANT_CASE_CONST(tx_out_bare, o)
if (o.target.type() == typeid(txout_multisig) && get_multisig_out_id(tx, idx) == multisig_id)
return true;
VARIANT_SWITCH_END();
++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