forked from lthn/blockchain
lmdb v 24 requires manual resizing/growing during it's normal function Zano core is purely async so it's not easy to prevent all DB txs from starting on lmdb adapter level, because it will lead to random deadlocks in the core due to many high-level cross-thread dependencies. We will rethink this later. Many thanks to @leo-yuriev who helped us to discover these issues!
6022 lines
254 KiB
C++
6022 lines
254 KiB
C++
// Copyright (c) 2014-2018 Zano Project
|
||
// Copyright (c) 2014-2018 The Louisdor Project
|
||
// Copyright (c) 2012-2013 The Cryptonote developers
|
||
// Copyright (c) 2012-2013 The Boolberry developers
|
||
// Distributed under the MIT/X11 software license, see the accompanying
|
||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||
|
||
#include <set>
|
||
#include <algorithm>
|
||
#include <cstdio>
|
||
#include <boost/archive/binary_oarchive.hpp>
|
||
#include <boost/archive/binary_iarchive.hpp>
|
||
#include <boost/algorithm/string/replace.hpp>
|
||
|
||
#include "include_base_utils.h"
|
||
|
||
#include "common/db_backend_lmdb.h"
|
||
#include "common/command_line.h"
|
||
|
||
#include "blockchain_storage.h"
|
||
#include "currency_format_utils.h"
|
||
#include "currency_boost_serialization.h"
|
||
#include "currency_core/currency_config.h"
|
||
#include "miner.h"
|
||
#include "misc_language.h"
|
||
#include "profile_tools.h"
|
||
#include "file_io_utils.h"
|
||
#include "common/boost_serialization_helper.h"
|
||
#include "warnings.h"
|
||
#include "crypto/hash.h"
|
||
#include "miner_common.h"
|
||
#include "storages/portable_storage_template_helper.h"
|
||
#include "common/db_backend_lmdb.h"
|
||
#include "basic_pow_helpers.h"
|
||
#include "version.h"
|
||
|
||
#undef LOG_DEFAULT_CHANNEL
|
||
#define LOG_DEFAULT_CHANNEL "core"
|
||
ENABLE_CHANNEL_BY_DEFAULT("core");
|
||
|
||
using namespace std;
|
||
using namespace epee;
|
||
using namespace currency;
|
||
|
||
#define BLOCKCHAIN_STORAGE_CONTAINER_BLOCKS "blocks"
|
||
#define BLOCKCHAIN_STORAGE_CONTAINER_BLOCKS_INDEX "blocks_index"
|
||
#define BLOCKCHAIN_STORAGE_CONTAINER_TRANSACTIONS "transactions"
|
||
#define BLOCKCHAIN_STORAGE_CONTAINER_SPENT_KEYS "spent_keys"
|
||
#define BLOCKCHAIN_STORAGE_CONTAINER_OUTPUTS "outputs"
|
||
#define BLOCKCHAIN_STORAGE_CONTAINER_MULTISIG_OUTS "multisig_outs"
|
||
#define BLOCKCHAIN_STORAGE_CONTAINER_INVALID_BLOCKS "invalid_blocks"
|
||
#define BLOCKCHAIN_STORAGE_CONTAINER_SOLO_OPTIONS "solo"
|
||
#define BLOCKCHAIN_STORAGE_CONTAINER_ALIASES "aliases"
|
||
#define BLOCKCHAIN_STORAGE_CONTAINER_ADDR_TO_ALIAS "addr_to_alias"
|
||
#define BLOCKCHAIN_STORAGE_CONTAINER_TX_FEE_MEDIAN "median_fee2"
|
||
#define BLOCKCHAIN_STORAGE_CONTAINER_GINDEX_INCS "gindex_increments"
|
||
|
||
#define BLOCKCHAIN_STORAGE_OPTIONS_ID_CURRENT_BLOCK_CUMUL_SZ_LIMIT 0
|
||
#define BLOCKCHAIN_STORAGE_OPTIONS_ID_CURRENT_PRUNED_RS_HEIGHT 1
|
||
#define BLOCKCHAIN_STORAGE_OPTIONS_ID_LAST_WORKED_VERSION 2
|
||
#define BLOCKCHAIN_STORAGE_OPTIONS_ID_STORAGE_MAJOR_COMPATIBILITY_VERSION 3 //DON'T CHANGE THIS, if you need to resync db change BLOCKCHAIN_STORAGE_MAJOR_COMPATIBILITY_VERSION
|
||
#define BLOCKCHAIN_STORAGE_OPTIONS_ID_STORAGE_MINOR_COMPATIBILITY_VERSION 4 //mismatch here means some reinitializations
|
||
|
||
#define TARGETDATA_CACHE_SIZE DIFFICULTY_WINDOW + 10
|
||
|
||
#ifndef TESTNET
|
||
#define BLOCKCHAIN_HEIGHT_FOR_POS_STRICT_SEQUENCE_LIMITATION 57000
|
||
#else
|
||
#define BLOCKCHAIN_HEIGHT_FOR_POS_STRICT_SEQUENCE_LIMITATION 18000
|
||
#endif
|
||
#define BLOCK_POS_STRICT_SEQUENCE_LIMIT 20
|
||
|
||
|
||
|
||
DISABLE_VS_WARNINGS(4267)
|
||
|
||
namespace
|
||
{
|
||
const command_line::arg_descriptor<uint32_t> arg_db_cache_l1 = { "db-cache-l1", "Specify size of memory mapped db cache file", 0, true };
|
||
const command_line::arg_descriptor<uint32_t> arg_db_cache_l2 = { "db-cache-l2", "Specify cached elements in db helpers", 0, true };
|
||
}
|
||
|
||
//------------------------------------------------------------------
|
||
blockchain_storage::blockchain_storage(tx_memory_pool& tx_pool) :m_db(std::shared_ptr<tools::db::i_db_backend>(new tools::db::lmdb_db_backend), m_rw_lock),
|
||
m_db_blocks(m_db),
|
||
m_db_blocks_index(m_db),
|
||
m_db_transactions(m_db),
|
||
m_db_spent_keys(m_db),
|
||
m_db_outputs(m_db),
|
||
m_db_multisig_outs(m_db),
|
||
m_db_solo_options(m_db),
|
||
m_db_aliases(m_db),
|
||
m_db_addr_to_alias(m_db),
|
||
m_read_lock(m_rw_lock),
|
||
m_db_current_block_cumul_sz_limit(BLOCKCHAIN_STORAGE_OPTIONS_ID_CURRENT_BLOCK_CUMUL_SZ_LIMIT, m_db_solo_options),
|
||
m_db_current_pruned_rs_height(BLOCKCHAIN_STORAGE_OPTIONS_ID_CURRENT_PRUNED_RS_HEIGHT, m_db_solo_options),
|
||
m_db_last_worked_version(BLOCKCHAIN_STORAGE_OPTIONS_ID_LAST_WORKED_VERSION, m_db_solo_options),
|
||
m_db_storage_major_compatibility_version(BLOCKCHAIN_STORAGE_OPTIONS_ID_STORAGE_MAJOR_COMPATIBILITY_VERSION, m_db_solo_options),
|
||
m_db_storage_minor_compatibility_version(BLOCKCHAIN_STORAGE_OPTIONS_ID_STORAGE_MINOR_COMPATIBILITY_VERSION, m_db_solo_options),
|
||
m_db_per_block_gindex_incs(m_db),
|
||
m_tx_pool(tx_pool),
|
||
m_is_in_checkpoint_zone(false),
|
||
m_is_blockchain_storing(false),
|
||
m_core_runtime_config(get_default_core_runtime_config()),
|
||
//m_bei_stub(AUTO_VAL_INIT(m_bei_stub)),
|
||
m_event_handler(&m_event_handler_stub),
|
||
m_services_mgr(nullptr),
|
||
m_interprocess_locker_file(0),
|
||
m_current_fee_median(0),
|
||
m_current_fee_median_effective_index(0),
|
||
m_is_reorganize_in_process(false),
|
||
m_deinit_is_done(false),
|
||
m_cached_next_pow_difficulty(0),
|
||
m_cached_next_pos_difficulty(0)
|
||
|
||
|
||
{
|
||
m_services_mgr.set_core_runtime_config(m_core_runtime_config);
|
||
m_performance_data.epic_failure_happend = false;
|
||
}
|
||
blockchain_storage::~blockchain_storage()
|
||
{
|
||
if (!m_deinit_is_done)
|
||
deinit();
|
||
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::have_tx(const crypto::hash &id) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
return m_db_transactions.find(id) != m_db_transactions.end();
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::have_tx_keyimg_as_spent(const crypto::key_image &key_im, uint64_t before_height /* = UINT64_MAX */) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
auto it_ptr = m_db_spent_keys.get(key_im);
|
||
if (!it_ptr)
|
||
return false;
|
||
return *it_ptr < before_height;
|
||
}
|
||
//------------------------------------------------------------------
|
||
std::shared_ptr<transaction> blockchain_storage::get_tx(const crypto::hash &id) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
auto it = m_db_transactions.find(id);
|
||
if (it == m_db_transactions.end())
|
||
return std::shared_ptr<transaction>(nullptr);
|
||
|
||
return std::make_shared<transaction>(it->tx);
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::init_options(boost::program_options::options_description& desc)
|
||
{
|
||
command_line::add_arg(desc, arg_db_cache_l1);
|
||
command_line::add_arg(desc, arg_db_cache_l2);
|
||
}
|
||
//------------------------------------------------------------------
|
||
|
||
uint64_t blockchain_storage::get_block_h_older_then(uint64_t timestamp) const
|
||
{
|
||
// get avarage block position
|
||
uint64_t last_block_timestamp = m_db_blocks.back()->bl.timestamp;
|
||
if (timestamp >= last_block_timestamp)
|
||
return get_top_block_height();
|
||
uint64_t difference = last_block_timestamp - timestamp;
|
||
uint64_t n_blocks = difference / (DIFFICULTY_TOTAL_TARGET);
|
||
if (n_blocks >= get_top_block_height())
|
||
return 0;
|
||
uint64_t index = get_top_block_height() - n_blocks;
|
||
while (true)
|
||
{
|
||
if (index == 0)
|
||
return 0;
|
||
if (m_db_blocks[index]->bl.timestamp < timestamp)
|
||
return index;
|
||
index--;
|
||
}
|
||
return 0;
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::get_current_blockchain_size() const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
return m_db_blocks.size();
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::get_top_block_height() const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
return m_db_blocks.size() - 1;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::validate_instance(const std::string& path)
|
||
{
|
||
std::string locker_name = path + "/" + std::string(CURRENCY_CORE_INSTANCE_LOCK_FILE);
|
||
bool r = epee::file_io_utils::open_and_lock_file(locker_name, m_interprocess_locker_file);
|
||
|
||
if (r)
|
||
return true;
|
||
else
|
||
{
|
||
LOG_ERROR("Failed to initialize db: some other instance is already running");
|
||
return false;
|
||
}
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::init(const std::string& config_folder, const boost::program_options::variables_map& vm)
|
||
{
|
||
// CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
if (!validate_instance(config_folder))
|
||
{
|
||
LOG_ERROR("Failed to initialize instance");
|
||
return false;
|
||
}
|
||
|
||
uint64_t cache_size_l1 = CACHE_SIZE;
|
||
if (command_line::has_arg(vm, arg_db_cache_l1))
|
||
{
|
||
cache_size_l1 = command_line::get_arg(vm, arg_db_cache_l1);
|
||
}
|
||
LOG_PRINT_GREEN("Using db file cache size(L1): " << cache_size_l1, LOG_LEVEL_0);
|
||
|
||
m_config_folder = config_folder;
|
||
|
||
// remove old incompartible DB
|
||
const std::string old_db_folder_path = m_config_folder + "/" CURRENCY_BLOCKCHAINDATA_FOLDERNAME_OLD;
|
||
if (boost::filesystem::exists(old_db_folder_path))
|
||
{
|
||
LOG_PRINT_YELLOW("Removing old DB in " << old_db_folder_path << "...", LOG_LEVEL_0);
|
||
boost::filesystem::remove_all(old_db_folder_path);
|
||
}
|
||
|
||
const std::string db_folder_path = m_config_folder + "/" CURRENCY_BLOCKCHAINDATA_FOLDERNAME;
|
||
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(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_blocks.init(BLOCKCHAIN_STORAGE_CONTAINER_BLOCKS);
|
||
CHECK_AND_ASSERT_MES(res, false, "Unable to init db container");
|
||
res = m_db_blocks_index.init(BLOCKCHAIN_STORAGE_CONTAINER_BLOCKS_INDEX);
|
||
CHECK_AND_ASSERT_MES(res, false, "Unable to init db container");
|
||
res = m_db_transactions.init(BLOCKCHAIN_STORAGE_CONTAINER_TRANSACTIONS);
|
||
CHECK_AND_ASSERT_MES(res, false, "Unable to init db container");
|
||
res = m_db_spent_keys.init(BLOCKCHAIN_STORAGE_CONTAINER_SPENT_KEYS);
|
||
CHECK_AND_ASSERT_MES(res, false, "Unable to init db container");
|
||
res = m_db_outputs.init(BLOCKCHAIN_STORAGE_CONTAINER_OUTPUTS);
|
||
CHECK_AND_ASSERT_MES(res, false, "Unable to init db container");
|
||
res = m_db_multisig_outs.init(BLOCKCHAIN_STORAGE_CONTAINER_MULTISIG_OUTS);
|
||
CHECK_AND_ASSERT_MES(res, false, "Unable to init db container");
|
||
res = m_db_solo_options.init(BLOCKCHAIN_STORAGE_CONTAINER_SOLO_OPTIONS);
|
||
CHECK_AND_ASSERT_MES(res, false, "Unable to init db container");
|
||
res = m_db_aliases.init(BLOCKCHAIN_STORAGE_CONTAINER_ALIASES);
|
||
CHECK_AND_ASSERT_MES(res, false, "Unable to init db container");
|
||
res = m_db_addr_to_alias.init(BLOCKCHAIN_STORAGE_CONTAINER_ADDR_TO_ALIAS);
|
||
CHECK_AND_ASSERT_MES(res, false, "Unable to init db container");
|
||
res = m_db_per_block_gindex_incs.init(BLOCKCHAIN_STORAGE_CONTAINER_GINDEX_INCS);
|
||
CHECK_AND_ASSERT_MES(res, false, "Unable to init db container");
|
||
|
||
if (command_line::has_arg(vm, arg_db_cache_l2))
|
||
{
|
||
uint64_t cache_size = command_line::get_arg(vm, arg_db_cache_l2);
|
||
LOG_PRINT_GREEN("Using db items cache size(L2): " << cache_size, LOG_LEVEL_0);
|
||
m_db_blocks_index.set_cache_size(cache_size);
|
||
m_db_blocks.set_cache_size(cache_size);
|
||
m_db_blocks_index.set_cache_size(cache_size);
|
||
m_db_transactions.set_cache_size(cache_size);
|
||
m_db_spent_keys.set_cache_size(cache_size);
|
||
//m_db_outputs.set_cache_size(cache_size);
|
||
m_db_multisig_outs.set_cache_size(cache_size);
|
||
m_db_solo_options.set_cache_size(cache_size);
|
||
m_db_aliases.set_cache_size(cache_size);
|
||
m_db_addr_to_alias.set_cache_size(cache_size);
|
||
}
|
||
|
||
bool need_reinit = false;
|
||
if (m_db_blocks.size() != 0)
|
||
{
|
||
if (m_db_storage_major_compatibility_version != BLOCKCHAIN_STORAGE_MAJOR_COMPATIBILITY_VERSION)
|
||
{
|
||
need_reinit = true;
|
||
LOG_PRINT_MAGENTA("DB storage needs reinit because it has major compatibility ver " << m_db_storage_major_compatibility_version << ", expected : " << BLOCKCHAIN_STORAGE_MAJOR_COMPATIBILITY_VERSION, LOG_LEVEL_0);
|
||
}
|
||
else if (m_db_storage_minor_compatibility_version != BLOCKCHAIN_STORAGE_MINOR_COMPATIBILITY_VERSION)
|
||
{
|
||
// nothing
|
||
}
|
||
}
|
||
|
||
if (need_reinit)
|
||
{
|
||
LOG_PRINT_L1("DB at " << db_folder_path << " is about to be deleted and re-created...");
|
||
m_db_blocks.deinit();
|
||
m_db_blocks_index.deinit();
|
||
m_db_transactions.deinit();
|
||
m_db_spent_keys.deinit();
|
||
m_db_outputs.deinit();
|
||
m_db_multisig_outs.deinit();
|
||
m_db_solo_options.deinit();
|
||
m_db_aliases.deinit();
|
||
m_db_addr_to_alias.deinit();
|
||
m_db_per_block_gindex_incs.deinit();
|
||
m_db.close();
|
||
size_t files_removed = boost::filesystem::remove_all(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");
|
||
|
||
if (!m_db_blocks.size())
|
||
{
|
||
// empty DB: generate and add genesis block
|
||
block bl = boost::value_initialized<block>();
|
||
block_verification_context bvc = boost::value_initialized<block_verification_context>();
|
||
generate_genesis_block(bl);
|
||
add_new_block(bl, bvc);
|
||
CHECK_AND_ASSERT_MES(!bvc.m_verification_failed, false, "Failed to add genesis block to blockchain");
|
||
LOG_PRINT_MAGENTA("Storage initialized with genesis", LOG_LEVEL_0);
|
||
}
|
||
|
||
store_db_solo_options_values();
|
||
|
||
m_services_mgr.init(config_folder, vm);
|
||
|
||
//print information message
|
||
uint64_t timestamp_diff = m_core_runtime_config.get_core_time() - m_db_blocks.back()->bl.timestamp;
|
||
if(!m_db_blocks.back()->bl.timestamp)
|
||
timestamp_diff = m_core_runtime_config.get_core_time() - 1341378000;
|
||
|
||
m_db.begin_transaction();
|
||
set_lost_tx_unmixable();
|
||
m_db.commit_transaction();
|
||
|
||
LOG_PRINT_GREEN("Blockchain initialized. (v:" << m_db_storage_major_compatibility_version << ") last block: " << m_db_blocks.size() - 1 << ENDL
|
||
<< "genesis: " << get_block_hash(m_db_blocks[0]->bl) << ENDL
|
||
<< "last block: " << m_db_blocks.size() - 1 << ", " << misc_utils::get_time_interval_string(timestamp_diff) << " time ago" << ENDL
|
||
<< "current pos difficulty: " << get_next_diff_conditional(true) << ENDL
|
||
<< "current pow difficulty: " << get_next_diff_conditional(false) << ENDL
|
||
<< "total transactions: " << m_db_transactions.size(),
|
||
LOG_LEVEL_0);
|
||
|
||
return true;
|
||
}
|
||
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::set_lost_tx_unmixable_for_height(uint64_t height)
|
||
{
|
||
if (height == 75738)
|
||
return set_lost_tx_unmixable();
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::set_lost_tx_unmixable()
|
||
{
|
||
if (m_db_blocks.size() > 75738)
|
||
{
|
||
crypto::hash tx_id_1 = epee::string_tools::parse_tpod_from_hex_string<crypto::hash>("c2a2229d614e7c026433efbcfdbd0be1f68d9b419220336df3e2c209f5d57314");
|
||
crypto::hash tx_id_2 = epee::string_tools::parse_tpod_from_hex_string<crypto::hash>("647f936c6ffbd136f5c95d9a90ad554bdb4c01541c6eb5755ad40b984d80da67");
|
||
|
||
auto tx_ptr_1 = m_db_transactions.find(tx_id_1);
|
||
CHECK_AND_ASSERT_MES(tx_ptr_1, false, "Internal error: filed to find lost tx");
|
||
transaction_chain_entry tx1_local_entry(*tx_ptr_1);
|
||
for (size_t i = 0; i != tx1_local_entry.m_spent_flags.size(); i++)
|
||
{
|
||
tx1_local_entry.m_spent_flags[i] = true;
|
||
}
|
||
m_db_transactions.set(tx_id_1, tx1_local_entry);
|
||
|
||
auto tx_ptr_2 = m_db_transactions.find(tx_id_2);
|
||
transaction_chain_entry tx2_local_entry(*tx_ptr_2);
|
||
CHECK_AND_ASSERT_MES(tx_ptr_1, false, "Internal error: filed to find lost tx");
|
||
for (size_t i = 0; i != tx2_local_entry.m_spent_flags.size(); i++)
|
||
{
|
||
tx2_local_entry.m_spent_flags[i] = true;
|
||
}
|
||
m_db_transactions.set(tx_id_2, tx2_local_entry);
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::patch_out_if_needed(txout_to_key& out, const crypto::hash& tx_id, uint64_t n) const
|
||
{
|
||
static crypto::hash tx_id_1 = epee::string_tools::parse_tpod_from_hex_string<crypto::hash>("c2a2229d614e7c026433efbcfdbd0be1f68d9b419220336df3e2c209f5d57314");
|
||
static crypto::hash tx_id_2 = epee::string_tools::parse_tpod_from_hex_string<crypto::hash>("647f936c6ffbd136f5c95d9a90ad554bdb4c01541c6eb5755ad40b984d80da67");
|
||
|
||
if (tx_id == tx_id_1 && n == 12)
|
||
{
|
||
out.mix_attr = CURRENCY_TO_KEY_OUT_FORCED_NO_MIX;
|
||
}else if(tx_id == tx_id_2 && n == 5)
|
||
{
|
||
out.mix_attr = CURRENCY_TO_KEY_OUT_FORCED_NO_MIX;
|
||
}
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::store_db_solo_options_values()
|
||
{
|
||
m_db.begin_transaction();
|
||
m_db_storage_major_compatibility_version = BLOCKCHAIN_STORAGE_MAJOR_COMPATIBILITY_VERSION;
|
||
m_db_storage_minor_compatibility_version = BLOCKCHAIN_STORAGE_MINOR_COMPATIBILITY_VERSION;
|
||
m_db_last_worked_version = std::string(PROJECT_VERSION_LONG);
|
||
m_db.commit_transaction();
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::deinit()
|
||
{
|
||
m_db.close();
|
||
epee::file_io_utils::unlock_and_close_file(m_interprocess_locker_file);
|
||
m_deinit_is_done = true;
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::pop_block_from_blockchain()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
CHECK_AND_ASSERT_MES(m_db_blocks.size() > 1, false, "pop_block_from_blockchain: can't pop from blockchain with size = " << m_db_blocks.size());
|
||
size_t h = m_db_blocks.size()-1;
|
||
auto bei_ptr = m_db_blocks[h];
|
||
CHECK_AND_ASSERT_MES(bei_ptr.get(), false, "pop_block_from_blockchain: can't pop from blockchain");
|
||
|
||
uint64_t fee_total = 0;
|
||
bool r = purge_block_data_from_blockchain(bei_ptr->bl, bei_ptr->bl.tx_hashes.size(), fee_total);
|
||
CHECK_AND_ASSERT_MES(r, false, "Failed to purge_block_data_from_blockchain for block " << get_block_hash(bei_ptr->bl) << " on height " << h);
|
||
|
||
pop_block_from_per_block_increments(bei_ptr->height);
|
||
|
||
//remove from index
|
||
r = m_db_blocks_index.erase_validate(get_block_hash(bei_ptr->bl));
|
||
CHECK_AND_ASSERT_MES_NO_RET(r, "pop_block_from_blockchain: block id not found in m_blocks_index while trying to delete it");
|
||
|
||
//pop block from core
|
||
m_db_blocks.pop_back();
|
||
|
||
on_block_removed(*bei_ptr);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::set_checkpoints(checkpoints&& chk_pts)
|
||
{
|
||
m_checkpoints = chk_pts;
|
||
try
|
||
{
|
||
m_db.begin_transaction();
|
||
if (m_db_blocks.size() < m_checkpoints.get_top_checkpoint_height())
|
||
m_is_in_checkpoint_zone = true;
|
||
prune_ring_signatures_and_attachments_if_need();
|
||
m_db.commit_transaction();
|
||
return true;
|
||
}
|
||
catch (const std::exception& ex)
|
||
{
|
||
m_db.abort_transaction();
|
||
LOG_ERROR("UNKNOWN EXCEPTION WHILE ADDINIG NEW BLOCK: " << ex.what());
|
||
return false;
|
||
}
|
||
catch (...)
|
||
{
|
||
m_db.abort_transaction();
|
||
LOG_ERROR("UNKNOWN EXCEPTION WHILE ADDINIG NEW BLOCK.");
|
||
return false;
|
||
}
|
||
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::prune_ring_signatures_and_attachments(uint64_t height, uint64_t& transactions_pruned, uint64_t& signatures_pruned, uint64_t& attachments_pruned)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
CHECK_AND_ASSERT_MES(height < m_db_blocks.size(), false, "prune_ring_signatures called with wrong parameter: " << height << ", m_blocks.size() " << m_db_blocks.size());
|
||
auto vptr = m_db_blocks[height];
|
||
CHECK_AND_ASSERT_MES(vptr.get(), false, "Failed to get block on height");
|
||
|
||
for (const auto& h : vptr->bl.tx_hashes)
|
||
{
|
||
auto it = m_db_transactions.find(h);
|
||
CHECK_AND_ASSERT_MES(it != m_db_transactions.end(), false, "failed to find transaction " << h << " in blockchain index, in block on height = " << height);
|
||
|
||
|
||
CHECK_AND_ASSERT_MES(it->m_keeper_block_height == height, false,
|
||
"failed to validate extra check, it->second.m_keeper_block_height = " << it->m_keeper_block_height <<
|
||
"is mot equal to height = " << height << " in blockchain index, for block on height = " << height);
|
||
|
||
transaction_chain_entry lolcal_chain_entry = *it;
|
||
signatures_pruned += lolcal_chain_entry.tx.signatures.size();
|
||
attachments_pruned += lolcal_chain_entry.tx.attachment.size();
|
||
lolcal_chain_entry.tx.signatures.clear();
|
||
lolcal_chain_entry.tx.attachment.clear();
|
||
|
||
//reassign to db
|
||
m_db_transactions.set(h, lolcal_chain_entry);
|
||
|
||
++transactions_pruned;
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::prune_ring_signatures_and_attachments_if_need()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
if(m_db_blocks.size() && m_checkpoints.get_top_checkpoint_height() && m_checkpoints.get_top_checkpoint_height() > m_db_current_pruned_rs_height)
|
||
{
|
||
LOG_PRINT_CYAN("Starting pruning ring signatues and attachments...", LOG_LEVEL_0);
|
||
uint64_t tx_count = 0, sig_count = 0, attach_count = 0;
|
||
for(uint64_t height = m_db_current_pruned_rs_height; height < m_db_blocks.size() && height <= m_checkpoints.get_top_checkpoint_height(); height++)
|
||
{
|
||
bool res = prune_ring_signatures_and_attachments(height, tx_count, sig_count, attach_count);
|
||
CHECK_AND_ASSERT_MES(res, false, "failed to prune_ring_signatures_and_attachments for height = " << height);
|
||
}
|
||
m_db_current_pruned_rs_height = m_checkpoints.get_top_checkpoint_height();
|
||
LOG_PRINT_CYAN("Transaction pruning finished: " << sig_count << " signatures and " << attach_count << " attachments released in " << tx_count << " transactions.", LOG_LEVEL_0);
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::clear()
|
||
{
|
||
//CRITICAL_REGION_LOCAL(m_read_lock);
|
||
m_db.begin_transaction();
|
||
|
||
m_db_blocks.clear();
|
||
m_db_blocks_index.clear();
|
||
m_db_transactions.clear();
|
||
m_db_spent_keys.clear();
|
||
m_db_solo_options.clear();
|
||
store_db_solo_options_values();
|
||
m_db_outputs.clear();
|
||
m_db_multisig_outs.clear();
|
||
m_db_aliases.clear();
|
||
m_db_addr_to_alias.clear();
|
||
m_db_per_block_gindex_incs.clear();
|
||
m_pos_targetdata_cache.clear();
|
||
m_pow_targetdata_cache.clear();
|
||
|
||
m_db.commit_transaction();
|
||
|
||
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_invalid_blocks_lock);
|
||
m_invalid_blocks.clear(); // crypto::hash -> block_extended_info
|
||
}
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_alternative_chains_lock);
|
||
m_alternative_chains.clear();
|
||
m_altblocks_keyimages.clear();
|
||
m_alternative_chains_txs.clear();
|
||
}
|
||
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::reset_and_set_genesis_block(const block& b)
|
||
{
|
||
clear();
|
||
block_verification_context bvc = boost::value_initialized<block_verification_context>();
|
||
add_new_block(b, bvc);
|
||
if(!bvc.m_added_to_main_chain || bvc.m_verification_failed)
|
||
{
|
||
LOG_ERROR("Blockchain reset failed.")
|
||
return false;
|
||
}
|
||
LOG_PRINT_GREEN("Blockchain reset. Genesis block: " << get_block_hash(b) << ", " << misc_utils::get_time_interval_string(m_core_runtime_config.get_core_time() - b.timestamp) << " ago", LOG_LEVEL_0);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::purge_transaction_keyimages_from_blockchain(const transaction& tx, bool strict_check)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
struct purge_transaction_visitor: public boost::static_visitor<bool>
|
||
{
|
||
blockchain_storage& m_bcs;
|
||
key_images_container& m_spent_keys;
|
||
bool m_strict_check;
|
||
purge_transaction_visitor(blockchain_storage& bcs, key_images_container& spent_keys, bool strict_check):
|
||
m_bcs(bcs),
|
||
m_spent_keys(spent_keys),
|
||
m_strict_check(strict_check){}
|
||
|
||
bool operator()(const txin_to_key& inp) const
|
||
{
|
||
bool r = m_spent_keys.erase_validate(inp.k_image);
|
||
CHECK_AND_ASSERT_MES( !(!r && m_strict_check), false, "purge_transaction_keyimages_from_blockchain: key image " << inp.k_image << " was not found");
|
||
|
||
if(inp.key_offsets.size() == 1)
|
||
{
|
||
//direct spend detected
|
||
if(!m_bcs.update_spent_tx_flags_for_input(inp.amount, inp.key_offsets[0], false))
|
||
{
|
||
//internal error
|
||
LOG_PRINT_L0("Failed to update_spent_tx_flags_for_input");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
bool operator()(const txin_gen& inp) const
|
||
{
|
||
return true;
|
||
}
|
||
bool operator()(const txin_multisig& inp) const
|
||
{
|
||
if (!m_bcs.update_spent_tx_flags_for_input(inp.multisig_out_id, 0))
|
||
{
|
||
LOG_PRINT_L0("update_spent_tx_flags_for_input failed for multisig id " << inp.multisig_out_id << " amount: " << inp.amount);
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
};
|
||
|
||
BOOST_FOREACH(const txin_v& in, tx.vin)
|
||
{
|
||
bool r = boost::apply_visitor(purge_transaction_visitor(*this, m_db_spent_keys, strict_check), in);
|
||
CHECK_AND_ASSERT_MES(!strict_check || r, false, "failed to process purge_transaction_visitor");
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::purge_transaction_from_blockchain(const crypto::hash& tx_id, uint64_t& fee)
|
||
{
|
||
fee = 0;
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
auto tx_res_ptr = m_db_transactions.find(tx_id);
|
||
CHECK_AND_ASSERT_MES(tx_res_ptr != m_db_transactions.end(), false, "transaction " << tx_id << " is not found in blockchain index!!");
|
||
const transaction& tx = tx_res_ptr->tx;
|
||
|
||
fee = get_tx_fee(tx_res_ptr->tx);
|
||
purge_transaction_keyimages_from_blockchain(tx, true);
|
||
|
||
bool r = unprocess_blockchain_tx_extra(tx);
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to unprocess_blockchain_tx_extra for tx " << tx_id);
|
||
|
||
r = unprocess_blockchain_tx_attachments(tx, get_current_blockchain_size(), 0/*TODO: add valid timestamp here in future if need*/);
|
||
|
||
bool added_to_the_pool = false;
|
||
if(!is_coinbase(tx))
|
||
{
|
||
currency::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
|
||
added_to_the_pool = m_tx_pool.add_tx(tx, tvc, true, true);
|
||
CHECK_AND_ASSERT_MES(added_to_the_pool, false, "failed to add transaction " << tx_id << " to transaction pool");
|
||
}
|
||
|
||
bool res = pop_transaction_from_global_index(tx, tx_id);
|
||
CHECK_AND_ASSERT_MES_NO_RET(res, "pop_transaction_from_global_index failed for tx " << tx_id);
|
||
bool res_erase = m_db_transactions.erase_validate(tx_id);
|
||
CHECK_AND_ASSERT_MES_NO_RET(res_erase, "Failed to m_transactions.erase with id = " << tx_id);
|
||
|
||
LOG_PRINT_L1("transaction " << tx_id << (added_to_the_pool ? " was removed from blockchain history -> to the pool" : " was removed from blockchain history"));
|
||
return res;
|
||
}
|
||
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::purge_block_data_from_blockchain(const block& b, size_t processed_tx_count)
|
||
{
|
||
uint64_t total_fee = 0;
|
||
return purge_block_data_from_blockchain(b, processed_tx_count, total_fee);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::purge_block_data_from_blockchain(const block& bl, size_t processed_tx_count, uint64_t& fee_total)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
fee_total = 0;
|
||
uint64_t fee = 0;
|
||
bool res = true;
|
||
CHECK_AND_ASSERT_MES(processed_tx_count <= bl.tx_hashes.size(), false, "wrong processed_tx_count in purge_block_data_from_blockchain");
|
||
for(size_t count = 0; count != processed_tx_count; count++)
|
||
{
|
||
res = purge_transaction_from_blockchain(bl.tx_hashes[(processed_tx_count -1)- count], fee) && res;
|
||
fee_total += fee;
|
||
}
|
||
|
||
res = purge_transaction_from_blockchain(get_transaction_hash(bl.miner_tx), fee) && res;
|
||
return res;
|
||
}
|
||
//------------------------------------------------------------------
|
||
crypto::hash blockchain_storage::get_top_block_id(uint64_t& height) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
height = get_top_block_height();
|
||
return get_top_block_id();
|
||
}
|
||
//------------------------------------------------------------------
|
||
crypto::hash blockchain_storage::get_top_block_id() const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
crypto::hash id = null_hash;
|
||
if(m_db_blocks.size())
|
||
{
|
||
auto val_ptr = m_db_blocks.back();
|
||
CHECK_AND_ASSERT_MES(val_ptr, null_hash, "m_blocks.back() returned null");
|
||
get_block_hash(val_ptr->bl, id);
|
||
}
|
||
return id;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_top_block(block& b) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
CHECK_AND_ASSERT_MES(m_db_blocks.size(), false, "Wrong blockchain state, m_blocks.size()=0!");
|
||
auto val_ptr = m_db_blocks.back();
|
||
CHECK_AND_ASSERT_MES(val_ptr.get(), false, "m_blocks.back() returned null");
|
||
b = val_ptr->bl;
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_short_chain_history(std::list<crypto::hash>& ids)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
size_t i = 0;
|
||
size_t current_multiplier = 1;
|
||
size_t sz = m_db_blocks.size();
|
||
if(!sz)
|
||
return true;
|
||
size_t current_back_offset = 1;
|
||
bool genesis_included = false;
|
||
while(current_back_offset < sz)
|
||
{
|
||
ids.push_back(get_block_hash(m_db_blocks[sz-current_back_offset]->bl));
|
||
if(sz-current_back_offset == 0)
|
||
genesis_included = true;
|
||
if(i < 10)
|
||
{
|
||
++current_back_offset;
|
||
}else
|
||
{
|
||
current_back_offset += current_multiplier *= 2;
|
||
}
|
||
++i;
|
||
}
|
||
if(!genesis_included)
|
||
ids.push_back(get_block_hash(m_db_blocks[0]->bl));
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
crypto::hash blockchain_storage::get_block_id_by_height(uint64_t height) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if(height >= m_db_blocks.size())
|
||
return null_hash;
|
||
|
||
return get_block_hash(m_db_blocks[height]->bl);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_block_by_hash(const crypto::hash &h, block &blk) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
// try to find block in main chain
|
||
auto it = m_db_blocks_index.find(h);
|
||
if (m_db_blocks_index.end() != it)
|
||
{
|
||
blk = m_db_blocks[*it]->bl;
|
||
return true;
|
||
}
|
||
|
||
// try to find block in alternative chain
|
||
CRITICAL_REGION_LOCAL1(m_alternative_chains_lock);
|
||
|
||
auto it_alt = m_alternative_chains.find(h);
|
||
if (m_alternative_chains.end() != it_alt)
|
||
{
|
||
blk = it_alt->second.bl;
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
bool blockchain_storage::is_tx_related_to_altblock(crypto::hash tx_id) const
|
||
{
|
||
CRITICAL_REGION_LOCAL1(m_alternative_chains_lock);
|
||
auto it = m_alternative_chains_txs.find(tx_id);
|
||
return it != m_alternative_chains_txs.end();
|
||
}
|
||
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_block_extended_info_by_hash(const crypto::hash &h, block_extended_info &blk) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
// try to find block in main chain
|
||
auto vptr = m_db_blocks_index.find(h);
|
||
if (vptr)
|
||
{
|
||
return get_block_extended_info_by_height(*vptr, blk);
|
||
}
|
||
|
||
// try to find block in alternative chain
|
||
CRITICAL_REGION_LOCAL1(m_alternative_chains_lock);
|
||
auto it_alt = m_alternative_chains.find(h);
|
||
if (m_alternative_chains.end() != it_alt)
|
||
{
|
||
blk = it_alt->second;
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_block_extended_info_by_height(uint64_t h, block_extended_info &blk) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
if (h >= m_db_blocks.size())
|
||
return false;
|
||
|
||
blk = *m_db_blocks[h];
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_block_by_height(uint64_t h, block &blk) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if(h >= m_db_blocks.size() )
|
||
return false;
|
||
blk = m_db_blocks[h]->bl;
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
// void blockchain_storage::get_all_known_block_ids(std::list<crypto::hash> &main, std::list<crypto::hash> &alt, std::list<crypto::hash> &invalid) const
|
||
// {
|
||
// CRITICAL_REGION_LOCAL(m_blockchain_lock);
|
||
//
|
||
// for (auto &v : m_blocks_index)
|
||
// main.push_back(v.first);
|
||
//
|
||
// for (auto &v : m_alternative_chains)
|
||
// alt.push_back(v.first);
|
||
//
|
||
// for(auto &v: m_invalid_blocks)
|
||
// invalid.push_back(v.first);
|
||
// }
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::rollback_blockchain_switching(std::list<block>& original_chain, size_t rollback_height)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
//remove failed subchain
|
||
for(size_t i = m_db_blocks.size()-1; i >=rollback_height; i--)
|
||
{
|
||
bool r = pop_block_from_blockchain();
|
||
CHECK_AND_ASSERT_MES(r, false, "PANIC!!! failed to remove block while chain switching during the rollback!");
|
||
}
|
||
//return back original chain
|
||
BOOST_FOREACH(auto& bl, original_chain)
|
||
{
|
||
block_verification_context bvc = boost::value_initialized<block_verification_context>();
|
||
bool r = handle_block_to_main_chain(bl, bvc);
|
||
CHECK_AND_ASSERT_MES(r && bvc.m_added_to_main_chain, false, "PANIC!!! failed to add (again) block while chain switching during the rollback!");
|
||
}
|
||
|
||
LOG_PRINT_L0("Rollback success.");
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::add_alt_block_txs_hashs(const block& b)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_alternative_chains_lock);
|
||
for (const auto& tx_hash : b.tx_hashes)
|
||
{
|
||
m_alternative_chains_txs[tx_hash]++;
|
||
}
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::purge_alt_block_txs_hashs(const block& b)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_alternative_chains_lock);
|
||
for (const auto& h : b.tx_hashes)
|
||
{
|
||
auto it = m_alternative_chains_txs.find(h);
|
||
if (it == m_alternative_chains_txs.end())
|
||
{
|
||
LOG_ERROR("Internal error: tx with hash " << h << " not found in m_alternative_chains_txs while removing block " << get_block_hash(b));
|
||
continue;
|
||
}
|
||
|
||
if (it->second >= 1)
|
||
{
|
||
it->second--;
|
||
}
|
||
else
|
||
{
|
||
LOG_ERROR("Internal error: tx with hash " << h << " has invalid m_alternative_chains_txs entry (zero count) while removing block " << get_block_hash(b));
|
||
}
|
||
if (it->second == 0)
|
||
m_alternative_chains_txs.erase(it);
|
||
}
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::do_erase_altblock(alt_chain_container::iterator it)
|
||
{
|
||
purge_altblock_keyimages_from_big_heap(it->second.bl, get_block_hash(it->second.bl));
|
||
purge_alt_block_txs_hashs(it->second.bl);
|
||
m_alternative_chains.erase(it);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::switch_to_alternative_blockchain(alt_chain_type& alt_chain)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
CHECK_AND_ASSERT_MES(validate_blockchain_prev_links(), false, "EPIC FAIL!");
|
||
|
||
CHECK_AND_ASSERT_MES(alt_chain.size(), false, "switch_to_alternative_blockchain: empty chain passed");
|
||
|
||
size_t split_height = alt_chain.front()->second.height;
|
||
CHECK_AND_ASSERT_MES(m_db_blocks.size() >= split_height, false, "switch_to_alternative_blockchain: blockchain size is lower than split height" << ENDL
|
||
<< " alt chain: " << ENDL << print_alt_chain(alt_chain) << ENDL
|
||
<< " main chain: " << ENDL << get_blockchain_string(m_db_blocks.size() - 10, CURRENCY_MAX_BLOCK_NUMBER)
|
||
);
|
||
|
||
//disconnecting old chain
|
||
std::list<block> disconnected_chain;
|
||
for(size_t i = m_db_blocks.size()-1; i >=split_height; i--)
|
||
{
|
||
block b = m_db_blocks[i]->bl;
|
||
bool r = pop_block_from_blockchain();
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to remove block " << get_block_hash(b) << " @ " << get_block_height(b) << " on chain switching");
|
||
disconnected_chain.push_front(b);
|
||
CHECK_AND_ASSERT_MES(validate_blockchain_prev_links(), false, "EPIC FAIL!");
|
||
}
|
||
|
||
//connecting new alternative chain
|
||
for(auto alt_ch_iter = alt_chain.begin(); alt_ch_iter != alt_chain.end(); alt_ch_iter++)
|
||
{
|
||
auto ch_ent = *alt_ch_iter;
|
||
block_verification_context bvc = boost::value_initialized<block_verification_context>();
|
||
bool r = handle_block_to_main_chain(ch_ent->second.bl, bvc);
|
||
if(!r || !bvc.m_added_to_main_chain)
|
||
{
|
||
LOG_PRINT_L0("Failed to switch to alternative blockchain");
|
||
rollback_blockchain_switching(disconnected_chain, split_height);
|
||
LOG_PRINT_L0("The block was inserted as invalid while connecting new alternative chain, block_id: " << get_block_hash(ch_ent->second.bl));
|
||
|
||
for(; alt_ch_iter != alt_chain.end(); ++alt_ch_iter)
|
||
{
|
||
add_block_as_invalid((*alt_ch_iter)->second, (*alt_ch_iter)->first);
|
||
do_erase_altblock(*alt_ch_iter);
|
||
}
|
||
CHECK_AND_ASSERT_MES(validate_blockchain_prev_links(), false, "EPIC FAIL!");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
//pushing old chain as alternative chain
|
||
for(auto& old_ch_ent : disconnected_chain)
|
||
{
|
||
block_verification_context bvc = boost::value_initialized<block_verification_context>();
|
||
bool r = handle_alternative_block(old_ch_ent, get_block_hash(old_ch_ent), bvc);
|
||
if(!r)
|
||
{
|
||
LOG_ERROR("Failed to push ex-main chain blocks to alternative chain ");
|
||
rollback_blockchain_switching(disconnected_chain, split_height);
|
||
CHECK_AND_ASSERT_MES(validate_blockchain_prev_links(), false, "EPIC FAIL!");
|
||
|
||
//can't do return false here, because of the risc to get stuck in "PANIC" mode when nor of
|
||
//new chain nor altchain can be inserted into main chain. Got core caught in this trap when
|
||
//when machine time was wrongly set for a few hours back, then blocks which was detached from main chain
|
||
//couldn't be added as alternative due to timestamps validation(timestamps assumed as from future)
|
||
//thanks @Gigabyted for reporting this problem
|
||
break;
|
||
}
|
||
}
|
||
|
||
//removing all_chain entries from alternative chain
|
||
for(auto ch_ent : alt_chain)
|
||
{
|
||
do_erase_altblock(ch_ent);
|
||
}
|
||
|
||
LOG_PRINT_GREEN("REORGANIZE SUCCESS! on height: " << split_height << ", new blockchain size: " << m_db_blocks.size(), LOG_LEVEL_0);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
wide_difficulty_type blockchain_storage::get_next_diff_conditional(bool pos) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
std::vector<uint64_t> timestamps;
|
||
std::vector<wide_difficulty_type> commulative_difficulties;
|
||
if (!m_db_blocks.size())
|
||
return DIFFICULTY_STARTER;
|
||
//skip genesis timestamp
|
||
TIME_MEASURE_START_PD(target_calculating_enum_blocks);
|
||
CRITICAL_REGION_BEGIN(m_targetdata_cache_lock);
|
||
std::list<std::pair<wide_difficulty_type, uint64_t>>& targetdata_cache = pos ? m_pos_targetdata_cache : m_pow_targetdata_cache;
|
||
//if (targetdata_cache.empty())
|
||
load_targetdata_cache(pos);
|
||
|
||
size_t count = 0;
|
||
for (auto it = targetdata_cache.rbegin(); it != targetdata_cache.rend() && count < DIFFICULTY_WINDOW; it++)
|
||
{
|
||
timestamps.push_back(it->second);
|
||
commulative_difficulties.push_back(it->first);
|
||
++count;
|
||
}
|
||
CRITICAL_REGION_END();
|
||
|
||
wide_difficulty_type& dif = pos ? m_cached_next_pos_difficulty : m_cached_next_pow_difficulty;
|
||
TIME_MEASURE_FINISH_PD(target_calculating_enum_blocks);
|
||
TIME_MEASURE_START_PD(target_calculating_calc);
|
||
if (m_db_blocks.size() > m_core_runtime_config.hard_fork1_starts_after_height)
|
||
{
|
||
dif = next_difficulty_2(timestamps, commulative_difficulties, pos ? DIFFICULTY_POS_TARGET : DIFFICULTY_POW_TARGET);
|
||
}
|
||
else
|
||
{
|
||
dif = next_difficulty_1(timestamps, commulative_difficulties, pos ? DIFFICULTY_POS_TARGET : DIFFICULTY_POW_TARGET);
|
||
}
|
||
|
||
|
||
TIME_MEASURE_FINISH_PD(target_calculating_calc);
|
||
return dif;
|
||
}
|
||
//------------------------------------------------------------------
|
||
wide_difficulty_type blockchain_storage::get_next_diff_conditional2(bool pos, const alt_chain_type& alt_chain, uint64_t split_height, const alt_block_extended_info& abei) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
std::vector<uint64_t> timestamps;
|
||
std::vector<wide_difficulty_type> commulative_difficulties;
|
||
size_t count = 0;
|
||
if (!m_db_blocks.size())
|
||
return DIFFICULTY_STARTER;
|
||
|
||
auto cb = [&](const block_extended_info& bei, bool is_main){
|
||
if (!bei.height)
|
||
return false;
|
||
bool is_pos_bl = is_pos_block(bei.bl);
|
||
if (pos != is_pos_bl)
|
||
return true;
|
||
timestamps.push_back(bei.bl.timestamp);
|
||
commulative_difficulties.push_back(bei.cumulative_diff_precise);
|
||
++count;
|
||
if (count >= DIFFICULTY_WINDOW)
|
||
return false;
|
||
return true;
|
||
};
|
||
enum_blockchain(cb, alt_chain, split_height);
|
||
|
||
wide_difficulty_type diff = 0;
|
||
if(abei.height > m_core_runtime_config.hard_fork1_starts_after_height)
|
||
diff = next_difficulty_2(timestamps, commulative_difficulties, pos ? DIFFICULTY_POS_TARGET : DIFFICULTY_POW_TARGET);
|
||
else
|
||
diff = next_difficulty_1(timestamps, commulative_difficulties, pos ? DIFFICULTY_POS_TARGET : DIFFICULTY_POW_TARGET);
|
||
return diff;
|
||
}
|
||
//------------------------------------------------------------------
|
||
wide_difficulty_type blockchain_storage::get_cached_next_difficulty(bool pos) const
|
||
{
|
||
wide_difficulty_type res = pos ? m_cached_next_pos_difficulty : m_cached_next_pow_difficulty;
|
||
if (!res)
|
||
{
|
||
get_next_diff_conditional(pos);
|
||
res = pos ? m_cached_next_pos_difficulty : m_cached_next_pow_difficulty;
|
||
}
|
||
return res;
|
||
}
|
||
//------------------------------------------------------------------
|
||
std::shared_ptr<block_extended_info> blockchain_storage::get_last_block_of_type(bool looking_for_pos, const alt_chain_type& alt_chain, uint64_t split_height) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
std::shared_ptr<block_extended_info> pbei(nullptr);
|
||
|
||
auto cb = [&](const block_extended_info& bei_local, bool is_main){
|
||
if (looking_for_pos == is_pos_block(bei_local.bl))
|
||
{
|
||
pbei.reset(new block_extended_info(bei_local));
|
||
return false;
|
||
}
|
||
return true;
|
||
};
|
||
|
||
|
||
enum_blockchain(cb, alt_chain, split_height);
|
||
return pbei;
|
||
}
|
||
//------------------------------------------------------------------
|
||
wide_difficulty_type blockchain_storage::get_next_difficulty_for_alternative_chain(const alt_chain_type& alt_chain, block_extended_info& bei, bool pos) const
|
||
{
|
||
std::vector<uint64_t> timestamps;
|
||
std::vector<wide_difficulty_type> commulative_difficulties;
|
||
|
||
for (auto it = alt_chain.rbegin(); it != alt_chain.rend() && timestamps.size() < DIFFICULTY_BLOCKS_COUNT; it++)
|
||
{
|
||
bool is_pos_bl = is_pos_block((*it)->second.bl);
|
||
if (pos != is_pos_bl)
|
||
continue;
|
||
timestamps.push_back((*it)->second.bl.timestamp);
|
||
commulative_difficulties.push_back((*it)->second.cumulative_diff_precise);
|
||
}
|
||
|
||
size_t main_chain_start_offset = (alt_chain.size() ? alt_chain.front()->second.height : bei.height)-1;
|
||
|
||
for (uint64_t i = main_chain_start_offset; i != 0 && timestamps.size() < DIFFICULTY_BLOCKS_COUNT; --i)
|
||
{
|
||
bool is_pos_bl = is_pos_block(m_db_blocks[i]->bl);
|
||
if (pos != is_pos_bl)
|
||
continue;
|
||
|
||
timestamps.push_back(m_db_blocks[i]->bl.timestamp);
|
||
commulative_difficulties.push_back(m_db_blocks[i]->cumulative_diff_precise);
|
||
}
|
||
|
||
return next_difficulty_1(timestamps, commulative_difficulties, pos ? DIFFICULTY_POS_TARGET:DIFFICULTY_POW_TARGET);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::prevalidate_miner_transaction(const block& b, uint64_t height, bool pos) const
|
||
{
|
||
CHECK_AND_ASSERT_MES((pos ? (b.miner_tx.vin.size() == 2) : (b.miner_tx.vin.size() == 1)), false, "coinbase transaction in the block has no inputs");
|
||
CHECK_AND_ASSERT_MES(b.miner_tx.vin[0].type() == typeid(txin_gen), false, "coinbase transaction in the block has the wrong type");
|
||
if(boost::get<txin_gen>(b.miner_tx.vin[0]).height != height)
|
||
{
|
||
LOG_PRINT_RED_L0("The miner transaction in block has invalid height: " << boost::get<txin_gen>(b.miner_tx.vin[0]).height << ", expected: " << height);
|
||
return false;
|
||
}
|
||
if (pos)
|
||
{
|
||
CHECK_AND_ASSERT_MES(b.miner_tx.vin[1].type() == typeid(txin_to_key), false, "coinstake transaction in the block has the wrong type");
|
||
}
|
||
|
||
if (height > m_core_runtime_config.hard_fork1_starts_after_height)
|
||
{
|
||
// new rules that allow different unlock time in coinbase outputs
|
||
uint64_t max_unlock_time = 0;
|
||
uint64_t min_unlock_time = 0;
|
||
bool r = get_tx_max_min_unlock_time(b.miner_tx, max_unlock_time, min_unlock_time);
|
||
CHECK_AND_ASSERT_MES(r && min_unlock_time >= height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW,
|
||
false,
|
||
"coinbase transaction has wrong min_unlock_time: " << min_unlock_time << ", expected: " << height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
|
||
}
|
||
else
|
||
{
|
||
//------------------------------------------------------------------
|
||
//bool blockchain_storage::
|
||
// pre-hard fork rules that don't allow different unlock time in coinbase outputs
|
||
uint64_t max_unlock_time = 0;
|
||
uint64_t min_unlock_time = 0;
|
||
bool r = get_tx_max_min_unlock_time(b.miner_tx, max_unlock_time, min_unlock_time);
|
||
CHECK_AND_ASSERT_MES(r && max_unlock_time == min_unlock_time && min_unlock_time == height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW,
|
||
false,
|
||
"coinbase transaction has wrong min_unlock_time: " << min_unlock_time << ", expected: " << height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
|
||
}
|
||
|
||
|
||
//check outs overflow
|
||
if(!check_outs_overflow(b.miner_tx))
|
||
{
|
||
LOG_PRINT_RED_L0("miner transaction have money overflow in block " << get_block_hash(b));
|
||
return false;
|
||
}
|
||
|
||
CHECK_AND_ASSERT_MES(b.miner_tx.attachment.empty(), false, "coinbase transaction has attachments; attachments are not allowed for coinbase transactions.");
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::validate_miner_transaction(const block& b,
|
||
size_t cumulative_block_size,
|
||
uint64_t fee,
|
||
uint64_t& base_reward,
|
||
const boost::multiprecision::uint128_t& already_generated_coins) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
//validate reward
|
||
uint64_t money_in_use = get_outs_money_amount(b.miner_tx);
|
||
|
||
uint64_t pos_income = 0;
|
||
if (is_pos_block(b))
|
||
{
|
||
CHECK_AND_ASSERT_MES(b.miner_tx.vin[1].type() == typeid(txin_to_key), false, "Wrong miner tx_in");
|
||
pos_income = boost::get<txin_to_key>(b.miner_tx.vin[1]).amount;
|
||
}
|
||
|
||
std::vector<size_t> last_blocks_sizes;
|
||
get_last_n_blocks_sizes(last_blocks_sizes, CURRENCY_REWARD_BLOCKS_WINDOW);
|
||
size_t blocks_size_median = misc_utils::median(last_blocks_sizes);
|
||
if (!get_block_reward(is_pos_block(b), blocks_size_median, cumulative_block_size, already_generated_coins, base_reward, get_block_height(b)))
|
||
{
|
||
LOG_PRINT_L0("block size " << cumulative_block_size << " is bigger than allowed for this blockchain");
|
||
return false;
|
||
}
|
||
if (base_reward + pos_income + fee < money_in_use)
|
||
{
|
||
LOG_ERROR("coinbase transaction spend too much money (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward + pos_income + fee) << "(" << print_money(base_reward) << "+" << print_money(pos_income) << "+" << print_money(fee)
|
||
<< ", blocks_size_median = " << blocks_size_median
|
||
<< ", cumulative_block_size = " << cumulative_block_size
|
||
<< ", fee = " << fee
|
||
<< ", already_generated_coins = " << already_generated_coins
|
||
<< "), tx:");
|
||
LOG_PRINT_L0(currency::obj_to_json_str(b.miner_tx));
|
||
return false;
|
||
}
|
||
if (base_reward + pos_income + fee != money_in_use)
|
||
{
|
||
LOG_ERROR("coinbase transaction doesn't use full amount of block reward: spent: (" << print_money(money_in_use) << "). Block reward is " << print_money(base_reward + pos_income + fee) << "(" << print_money(base_reward) << "+" << print_money(pos_income) << "+" << print_money(fee)
|
||
<< ", blocks_size_median = " << blocks_size_median
|
||
<< ", cumulative_block_size = " << cumulative_block_size
|
||
<< ", fee = " << fee
|
||
<< ", already_generated_coins = " << already_generated_coins
|
||
<< "), tx:");
|
||
LOG_PRINT_L0(currency::obj_to_json_str(b.miner_tx));
|
||
return false;
|
||
}
|
||
LOG_PRINT_MAGENTA("Mining tx verification ok, blocks_size_median = " << blocks_size_median, LOG_LEVEL_2);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
blockchain_storage::performnce_data& blockchain_storage::get_performnce_data()const
|
||
{
|
||
m_db.get_backend()->get_stat_info(m_performance_data.si);
|
||
return m_performance_data;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_backward_blocks_sizes(size_t from_height, std::vector<size_t>& sz, size_t count)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
CHECK_AND_ASSERT_MES(from_height < m_db_blocks.size(), false, "Internal error: get_backward_blocks_sizes called with from_height=" << from_height << ", blockchain height = " << m_db_blocks.size());
|
||
|
||
size_t start_offset = (from_height+1) - std::min((from_height+1), count);
|
||
for(size_t i = start_offset; i != from_height+1; i++)
|
||
sz.push_back(m_db_blocks[i]->block_cumulative_size);
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_last_n_blocks_sizes(std::vector<size_t>& sz, size_t count) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if(!m_db_blocks.size())
|
||
return true;
|
||
return get_backward_blocks_sizes(m_db_blocks.size() -1, sz, count);
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::get_current_comulative_blocksize_limit() const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
return m_db_current_block_cumul_sz_limit;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::create_block_template(block& b,
|
||
const account_public_address& miner_address,
|
||
wide_difficulty_type& diffic,
|
||
uint64_t& height,
|
||
const blobdata& ex_nonce) const
|
||
{
|
||
return create_block_template(b, miner_address, miner_address, diffic, height, ex_nonce, false, pos_entry());
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::create_block_template(block& b,
|
||
const account_public_address& miner_address,
|
||
const account_public_address& stakeholder_address,
|
||
wide_difficulty_type& diffic,
|
||
uint64_t& height,
|
||
const blobdata& ex_nonce,
|
||
bool pos,
|
||
const pos_entry& pe,
|
||
fill_block_template_func_t custom_fill_block_template_func /* = nullptr */) const
|
||
{
|
||
size_t median_size;
|
||
boost::multiprecision::uint128_t already_generated_coins;
|
||
CRITICAL_REGION_BEGIN(m_read_lock);
|
||
height = m_db_blocks.size();
|
||
if(height <= m_core_runtime_config.hard_fork1_starts_after_height)
|
||
b.major_version = BLOCK_MAJOR_VERSION_INITAL;
|
||
else
|
||
b.major_version = CURRENT_BLOCK_MAJOR_VERSION;
|
||
|
||
b.minor_version = CURRENT_BLOCK_MINOR_VERSION;
|
||
b.prev_id = get_top_block_id();
|
||
b.timestamp = m_core_runtime_config.get_core_time();
|
||
b.nonce = 0;
|
||
b.flags = 0;
|
||
if (pos)
|
||
{
|
||
b.flags |= CURRENCY_BLOCK_FLAG_POS_BLOCK;
|
||
b.timestamp = 0;
|
||
}
|
||
|
||
diffic = get_next_diff_conditional(pos);
|
||
|
||
CHECK_AND_ASSERT_MES(diffic, false, "difficulty owverhead.");
|
||
|
||
|
||
|
||
|
||
median_size = m_db_current_block_cumul_sz_limit / 2;
|
||
already_generated_coins = m_db_blocks.back()->already_generated_coins;
|
||
|
||
CRITICAL_REGION_END();
|
||
|
||
size_t txs_size;
|
||
uint64_t fee;
|
||
bool block_filled = false;
|
||
if (custom_fill_block_template_func == nullptr)
|
||
block_filled = m_tx_pool.fill_block_template(b, pos, median_size, already_generated_coins, txs_size, fee, height);
|
||
else
|
||
block_filled = (*custom_fill_block_template_func)(b, pos, median_size, already_generated_coins, txs_size, fee, height);
|
||
|
||
if (!block_filled)
|
||
return false;
|
||
|
||
/*
|
||
instead of complicated two-phase template construction and adjustment of cumulative size with block reward we
|
||
use CURRENCY_COINBASE_BLOB_RESERVED_SIZE as penalty-free coinbase transaction reservation.
|
||
*/
|
||
bool r = construct_miner_tx(height, median_size, already_generated_coins,
|
||
txs_size,
|
||
fee,
|
||
miner_address,
|
||
stakeholder_address,
|
||
b.miner_tx, ex_nonce,
|
||
CURRENCY_MINER_TX_MAX_OUTS,
|
||
pos,
|
||
pe);
|
||
CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, first chance");
|
||
uint64_t coinbase_size = get_object_blobsize(b.miner_tx);
|
||
// "- 100" - to reserve room for PoS additions into miner tx
|
||
CHECK_AND_ASSERT_MES(coinbase_size < CURRENCY_COINBASE_BLOB_RESERVED_SIZE - 100, false, "Failed to get block template (coinbase_size = " << coinbase_size << ") << coinbase structue: "
|
||
<< ENDL << obj_to_json_str(b.miner_tx));
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::print_transactions_statistics() const
|
||
{
|
||
LOG_ERROR("print_transactions_statistics not implemented yet");
|
||
// CRITICAL_REGION_LOCAL(m_blockchain_lock);
|
||
// LOG_PRINT_L0("Started to collect transaction statistics, pleas wait...");
|
||
// size_t total_count = 0;
|
||
// size_t coinbase_count = 0;
|
||
// size_t total_full_blob = 0;
|
||
// size_t total_cropped_blob = 0;
|
||
// for(auto tx_entry: m_db_transactions)
|
||
// {
|
||
// ++total_count;
|
||
// if(is_coinbase(tx_entry.second.tx))
|
||
// ++coinbase_count;
|
||
// else
|
||
// {
|
||
// total_full_blob += get_object_blobsize<transaction>(tx_entry.second.tx);
|
||
// transaction tx = tx_entry.second.tx;
|
||
// tx.signatures.clear();
|
||
// total_cropped_blob += get_object_blobsize<transaction>(tx);
|
||
// }
|
||
// }
|
||
// LOG_PRINT_L0("Done" << ENDL
|
||
// << "total transactions: " << total_count << ENDL
|
||
// << "coinbase transactions: " << coinbase_count << ENDL
|
||
// << "avarage size of transaction: " << total_full_blob/(total_count-coinbase_count) << ENDL
|
||
// << "avarage size of transaction without ring signatures: " << total_cropped_blob/(total_count-coinbase_count) << ENDL
|
||
// );
|
||
return true;
|
||
}
|
||
void blockchain_storage::reset_db_cache() const
|
||
{
|
||
m_db_blocks.clear_cache();
|
||
m_db_blocks_index.clear_cache();
|
||
m_db_transactions.clear_cache();
|
||
m_db_spent_keys.clear_cache();
|
||
m_db_solo_options.clear_cache();
|
||
m_db_multisig_outs.clear_cache();
|
||
m_db_aliases.clear_cache();
|
||
m_db_addr_to_alias.clear_cache();
|
||
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::clear_altblocks()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_alternative_chains_lock);
|
||
m_alternative_chains.clear();
|
||
m_alternative_chains_txs.clear();
|
||
m_altblocks_keyimages.clear();
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::complete_timestamps_vector(uint64_t start_top_height, std::vector<uint64_t>& timestamps)
|
||
{
|
||
|
||
if(timestamps.size() >= BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW)
|
||
return true;
|
||
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
size_t need_elements = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW - timestamps.size();
|
||
CHECK_AND_ASSERT_MES(start_top_height < m_db_blocks.size(), false, "internal error: passed start_height = " << start_top_height << " not less then m_blocks.size()=" << m_db_blocks.size());
|
||
size_t stop_offset = start_top_height > need_elements ? start_top_height - need_elements:0;
|
||
do
|
||
{
|
||
timestamps.push_back(m_db_blocks[start_top_height]->bl.timestamp);
|
||
if(start_top_height == 0)
|
||
break;
|
||
--start_top_height;
|
||
}while(start_top_height != stop_offset);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
std::string blockchain_storage::print_alt_chain(alt_chain_type alt_chain)
|
||
{
|
||
std::stringstream ss;
|
||
for (auto it = alt_chain.begin(); it != alt_chain.end(); it++)
|
||
{
|
||
ss << "[" << (*it)->second.height << "] " << (*it)->first << "(cumul_dif: " << (*it)->second.cumulative_diff_adjusted << "), parent_id: " << (*it)->second.bl.prev_id << ENDL;
|
||
}
|
||
return ss.str();
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::append_altblock_keyimages_to_big_heap(const crypto::hash& block_id, const std::set<crypto::key_image>& alt_block_keyimages)
|
||
{
|
||
for (auto& ki : alt_block_keyimages)
|
||
m_altblocks_keyimages[ki].push_back(block_id);
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::purge_keyimage_from_big_heap(const crypto::key_image& ki, const crypto::hash& id)
|
||
{
|
||
auto it = m_altblocks_keyimages.find(ki);
|
||
CHECK_AND_ASSERT_MES(it != m_altblocks_keyimages.end(), false, "internal error: keyimage " << ki
|
||
<< "from altblock " << id << "not found in m_altblocks_keyimages in purge_keyimage_from_big_heap");
|
||
|
||
//TODO: at the moment here is simple liner algo since having the same txs in few altblocks is rare case
|
||
std::list<crypto::hash>& ki_blocks_ids = it->second;
|
||
bool found = false;
|
||
for (auto it_in_blocks = ki_blocks_ids.begin(); it_in_blocks != ki_blocks_ids.end(); it_in_blocks++)
|
||
{
|
||
if (*it_in_blocks == id)
|
||
{
|
||
//found, erase this entry
|
||
ki_blocks_ids.erase(it_in_blocks);
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
CHECK_AND_ASSERT_MES(found, false, "internal error: keyimage " << ki
|
||
<< "from altblock " << id << "not found in m_altblocks_keyimages in purge_keyimage_from_big_heap");
|
||
if (!ki_blocks_ids.size())
|
||
m_altblocks_keyimages.erase(it);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::purge_altblock_keyimages_from_big_heap(const block& b, const crypto::hash& id)
|
||
{
|
||
if (is_pos_block(b))
|
||
{
|
||
CHECK_AND_ASSERT_MES(b.miner_tx.vin.size()>=2, false, "paranoid check failed");
|
||
CHECK_AND_ASSERT_MES(b.miner_tx.vin[1].type() == typeid(txin_to_key), false, "paranoid type check failed");
|
||
purge_keyimage_from_big_heap(boost::get<txin_to_key>(b.miner_tx.vin[1]).k_image, id);
|
||
}
|
||
for (auto tx_id : b.tx_hashes)
|
||
{
|
||
std::shared_ptr<transaction> tx_ptr;
|
||
if (!get_transaction_from_pool_or_db(tx_id, tx_ptr))
|
||
{
|
||
LOG_ERROR("failed to get alt block tx " << tx_id << " on block detach from alts");
|
||
continue;
|
||
}
|
||
transaction& tx = *tx_ptr;
|
||
for (size_t n = 0; n < tx.vin.size(); ++n)
|
||
{
|
||
if (tx.vin[n].type() == typeid(txin_to_key))
|
||
{
|
||
purge_keyimage_from_big_heap(boost::get<txin_to_key>(tx.vin[n]).k_image, id);
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc)
|
||
{
|
||
uint64_t coinbase_height = get_block_height(b);
|
||
if (m_checkpoints.is_height_passed_zone(coinbase_height, get_top_block_height()))
|
||
{
|
||
LOG_PRINT_RED_L0("Block with id: " << id << "[" << coinbase_height << "]" << ENDL << " for alternative chain, is under checkpoint zone, declined");
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
|
||
TRY_ENTRY();
|
||
|
||
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
CRITICAL_REGION_LOCAL1(m_alternative_chains_lock);
|
||
|
||
//block is not related with head of main chain
|
||
//first of all - look in alternative chains container
|
||
|
||
auto ptr_main_prev = m_db_blocks_index.find(b.prev_id);
|
||
auto it_prev = m_alternative_chains.find(b.prev_id);
|
||
if (it_prev != m_alternative_chains.end() || ptr_main_prev != m_db_blocks_index.end())
|
||
{
|
||
alt_chain_type alt_chain;
|
||
{
|
||
//we have new block in alternative chain
|
||
//build alternative subchain, front -> mainchain, back -> alternative head
|
||
alt_chain_container::iterator alt_it = it_prev; //m_alternative_chains.find()
|
||
std::vector<uint64_t> timestamps;
|
||
std::list<alt_chain_container::iterator> temp_container;
|
||
while (alt_it != m_alternative_chains.end())
|
||
{
|
||
temp_container.push_front(alt_it);
|
||
timestamps.push_back(alt_it->second.bl.timestamp);
|
||
alt_it = m_alternative_chains.find(alt_it->second.bl.prev_id);
|
||
}
|
||
//TODO: refactoring needed: vector push_front is dramatically ineffective
|
||
alt_chain.resize(temp_container.size());
|
||
auto it_vec = alt_chain.begin();
|
||
for (auto it = temp_container.begin(); it != temp_container.end();it++, it_vec++)
|
||
{
|
||
*it_vec = *it;
|
||
}
|
||
|
||
|
||
if (alt_chain.size())
|
||
{
|
||
//make sure that it has right connection to main chain
|
||
CHECK_AND_ASSERT_MES_CUSTOM(m_db_blocks.size() >= alt_chain.front()->second.height, false, bvc.m_verification_failed = true, "main blockchain wrong height: m_db_blocks.size() = " << m_db_blocks.size()
|
||
<< " and alt_chain.front()->second.height = " << alt_chain.front()->second.height
|
||
<< " for block " << id << ", prev_id=" << b.prev_id << ENDL
|
||
<< " alt chain: " << ENDL << print_alt_chain(alt_chain) << ENDL
|
||
<< " main chain: " << ENDL << get_blockchain_string(m_db_blocks.size() - 10, CURRENCY_MAX_BLOCK_NUMBER)
|
||
);
|
||
|
||
crypto::hash h = null_hash;
|
||
get_block_hash(m_db_blocks[alt_chain.front()->second.height - 1]->bl, h);
|
||
CHECK_AND_ASSERT_MES_CUSTOM(h == alt_chain.front()->second.bl.prev_id, false, bvc.m_verification_failed = true, "alternative chain have wrong connection to main chain");
|
||
complete_timestamps_vector(alt_chain.front()->second.height - 1, timestamps);
|
||
}
|
||
else
|
||
{
|
||
CHECK_AND_ASSERT_MES_CUSTOM(ptr_main_prev != m_db_blocks_index.end(), false, bvc.m_verification_failed = true, "internal error: broken imperative condition it_main_prev != m_blocks_index.end()");
|
||
complete_timestamps_vector(*ptr_main_prev, timestamps);
|
||
}
|
||
//check timestamp correct
|
||
if (!check_block_timestamp(std::move(timestamps), b))
|
||
{
|
||
LOG_PRINT_RED_L0("Block with id: " << id
|
||
<< ENDL << " for alternative chain, have invalid timestamp: " << b.timestamp);
|
||
//add_block_as_invalid(b, id);//do not add blocks to invalid storage before proof of work check was passed
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
alt_block_extended_info abei = AUTO_VAL_INIT(abei);
|
||
abei.bl = b;
|
||
abei.timestamp = m_core_runtime_config.get_core_time();
|
||
abei.height = alt_chain.size() ? it_prev->second.height + 1 : *ptr_main_prev + 1;
|
||
CHECK_AND_ASSERT_MES_CUSTOM(coinbase_height == abei.height, false, bvc.m_verification_failed = true, "block coinbase height doesn't match with altchain height, declined");
|
||
uint64_t connection_height = alt_chain.size() ? alt_chain.front()->second.height:abei.height;
|
||
CHECK_AND_ASSERT_MES_CUSTOM(connection_height, false, bvc.m_verification_failed = true, "INTERNAL ERROR: Wrong connection_height==0 in handle_alternative_block");
|
||
|
||
if (!m_checkpoints.is_in_checkpoint_zone(abei.height))
|
||
{
|
||
m_is_in_checkpoint_zone = false;
|
||
}
|
||
else
|
||
{
|
||
m_is_in_checkpoint_zone = true;
|
||
if (!m_checkpoints.check_block(abei.height, id))
|
||
{
|
||
LOG_ERROR("CHECKPOINT VALIDATION FAILED");
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
bool pos_block = is_pos_block(abei.bl);
|
||
//check if PoS block allowed on this height
|
||
CHECK_AND_ASSERT_MES_CUSTOM(!(pos_block && abei.height < m_core_runtime_config.pos_minimum_heigh), false, bvc.m_verification_failed = true, "PoS block is not allowed on this height");
|
||
|
||
|
||
wide_difficulty_type current_diff = get_next_diff_conditional2(pos_block, alt_chain, connection_height, abei);
|
||
|
||
CHECK_AND_ASSERT_MES_CUSTOM(current_diff, false, bvc.m_verification_failed = true, "!!!!!!! DIFFICULTY OVERHEAD !!!!!!!");
|
||
|
||
crypto::hash proof_of_work = null_hash;
|
||
uint64_t pos_amount = 0;
|
||
wide_difficulty_type pos_diff_final = 0;
|
||
if (pos_block)
|
||
{
|
||
//POS
|
||
bool res = validate_pos_block(abei.bl, current_diff, pos_amount, pos_diff_final, abei.stake_hash, id, true, alt_chain, connection_height);
|
||
CHECK_AND_ASSERT_MES_CUSTOM(res, false, bvc.m_verification_failed = true, "Failed to validate_pos_block on alternative block, height = "
|
||
<< abei.height
|
||
<< ", block id: " << get_block_hash(abei.bl));
|
||
}
|
||
else
|
||
{
|
||
proof_of_work = get_block_longhash(abei.bl);
|
||
|
||
if (!check_hash(proof_of_work, current_diff))
|
||
{
|
||
LOG_PRINT_RED_L0("Block with id: " << id
|
||
<< ENDL << " for alternative chain, have not enough proof of work: " << proof_of_work
|
||
<< ENDL << " expected difficulty: " << current_diff);
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
//
|
||
}
|
||
|
||
if (!prevalidate_miner_transaction(b, abei.height, pos_block))
|
||
{
|
||
LOG_PRINT_RED_L0("Block with id: " << string_tools::pod_to_hex(id)
|
||
<< " (as alternative) have wrong miner transaction.");
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
std::set<crypto::key_image> alt_block_keyimages;
|
||
uint64_t ki_lookup_total = 0;
|
||
if (!validate_alt_block_txs(b, id, alt_block_keyimages, abei, alt_chain, connection_height, ki_lookup_total))
|
||
{
|
||
LOG_PRINT_RED_L0("Alternative block " << id << " @ " << abei.height << " has invalid transactions. Rejected.");
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
|
||
abei.difficulty = current_diff;
|
||
wide_difficulty_type cumulative_diff_delta = 0;
|
||
abei.cumulative_diff_adjusted = alt_chain.size() ? it_prev->second.cumulative_diff_adjusted : m_db_blocks[*ptr_main_prev]->cumulative_diff_adjusted;
|
||
|
||
if (pos_block)
|
||
cumulative_diff_delta = get_adjusted_cumulative_difficulty_for_next_alt_pos(alt_chain, abei.height, current_diff, connection_height);
|
||
else
|
||
cumulative_diff_delta = current_diff;
|
||
|
||
size_t sequence_factor = get_current_sequence_factor_for_alt(alt_chain, pos_block, connection_height);
|
||
if (abei.height >= m_core_runtime_config.pos_minimum_heigh)
|
||
cumulative_diff_delta = correct_difficulty_with_sequence_factor(sequence_factor, cumulative_diff_delta);
|
||
|
||
if (abei.height > BLOCKCHAIN_HEIGHT_FOR_POS_STRICT_SEQUENCE_LIMITATION && abei.height <= m_core_runtime_config.hard_fork1_starts_after_height && pos_block && sequence_factor > BLOCK_POS_STRICT_SEQUENCE_LIMIT)
|
||
{
|
||
LOG_PRINT_RED_L0("Alternative block " << id << " @ " << abei.height << " has too big sequence factor: " << sequence_factor << ", rejected");
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
|
||
abei.cumulative_diff_adjusted += cumulative_diff_delta;
|
||
wide_difficulty_type last_x_cumul_dif_precise_adj = 0;
|
||
abei.cumulative_diff_precise = get_last_alt_x_block_cumulative_precise_difficulty(alt_chain, abei.height-1, pos_block, last_x_cumul_dif_precise_adj);
|
||
abei.cumulative_diff_precise += current_diff;
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
wide_difficulty_type diff_precise_adj = correct_difficulty_with_sequence_factor(sequence_factor, current_diff);
|
||
abei.cumulative_diff_precise_adjusted = last_x_cumul_dif_precise_adj + diff_precise_adj;
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
#ifdef _DEBUG
|
||
auto i_dres = m_alternative_chains.find(id);
|
||
CHECK_AND_ASSERT_MES_CUSTOM(i_dres == m_alternative_chains.end(), false, bvc.m_verification_failed = true, "insertion of new alternative block " << id << " returned as it already exist");
|
||
#endif
|
||
auto i_res = m_alternative_chains.insert(alt_chain_container::value_type(id, abei));
|
||
CHECK_AND_ASSERT_MES_CUSTOM(i_res.second, false, bvc.m_verification_failed = true, "insertion of new alternative block " << id << " returned as it already exist");
|
||
append_altblock_keyimages_to_big_heap(id, alt_block_keyimages);
|
||
add_alt_block_txs_hashs(i_res.first->second.bl);
|
||
alt_chain.push_back(i_res.first);
|
||
//check if difficulty bigger then in main chain
|
||
|
||
bvc.height_difference = get_top_block_height() >= abei.height ? get_top_block_height() - abei.height : 0;
|
||
|
||
crypto::hash proof = null_hash;
|
||
std::stringstream ss_pow_pos_info;
|
||
if (pos_block)
|
||
{
|
||
ss_pow_pos_info << "PoS:\t" << abei.stake_hash << ", stake amount: " << print_money(pos_amount) << ", final_difficulty: " << pos_diff_final;
|
||
proof = abei.stake_hash;
|
||
}
|
||
else
|
||
{
|
||
ss_pow_pos_info << "PoW:\t" << proof_of_work;
|
||
proof = proof_of_work;
|
||
}
|
||
|
||
LOG_PRINT_BLUE("----- BLOCK ADDED AS ALTERNATIVE ON HEIGHT " << abei.height << (pos_block ? " [PoS] Sq: " : " [PoW] Sq: ") << sequence_factor << ", altchain sz: " << alt_chain.size() << ", split h: " << connection_height
|
||
<< ENDL << "id:\t" << id
|
||
<< ENDL << "prev\t" << abei.bl.prev_id
|
||
<< ENDL << ss_pow_pos_info.str()
|
||
<< ENDL << "HEIGHT " << abei.height << ", difficulty: " << abei.difficulty << ", cumul_diff_precise: " << abei.cumulative_diff_precise << ", cumul_diff_adj: " << abei.cumulative_diff_adjusted << " (current mainchain cumul_diff_adj: " << m_db_blocks.back()->cumulative_diff_adjusted << ", ki lookup total: " << ki_lookup_total <<")"
|
||
, LOG_LEVEL_0);
|
||
|
||
if (is_reorganize_required(*m_db_blocks.back(), alt_chain, proof))
|
||
{
|
||
auto a = epee::misc_utils::create_scope_leave_handler([&]() { m_is_reorganize_in_process = false; });
|
||
CHECK_AND_ASSERT_THROW_MES(!m_is_reorganize_in_process, "Detected recursive reorganzie");
|
||
m_is_reorganize_in_process = true;
|
||
//do reorganize!
|
||
LOG_PRINT_GREEN("###### REORGANIZE on height: " << alt_chain.front()->second.height << " of " << m_db_blocks.size() - 1 << " with cumulative_diff_adjusted " << m_db_blocks.back()->cumulative_diff_adjusted
|
||
<< ENDL << " alternative blockchain size: " << alt_chain.size() << " with cumulative_diff_adjusted " << abei.cumulative_diff_adjusted, LOG_LEVEL_0);
|
||
bool r = switch_to_alternative_blockchain(alt_chain);
|
||
if(r)
|
||
bvc.m_added_to_main_chain = true;
|
||
else
|
||
bvc.m_verification_failed = true;
|
||
return r;
|
||
}
|
||
bvc.added_to_altchain = true;
|
||
|
||
//protect ourself from altchains container flood
|
||
if (m_alternative_chains.size() > m_core_runtime_config.max_alt_blocks)
|
||
prune_aged_alt_blocks();
|
||
|
||
return true;
|
||
}else
|
||
{
|
||
//block orphaned
|
||
bvc.m_marked_as_orphaned = true;
|
||
|
||
if (m_invalid_blocks.count(id) != 0)
|
||
{
|
||
LOG_PRINT_RED_L0("Block recognized as blacklisted and rejected, id = " << id << "," << ENDL << "parent id = " << b.prev_id << ENDL << "height = " << coinbase_height);
|
||
}
|
||
else if (m_invalid_blocks.count(b.prev_id) != 0)
|
||
{
|
||
LOG_PRINT_RED_L0("Block recognized as orphaned (parent " << b.prev_id << " is in blacklist) and rejected, id = " << id << "," << ENDL << "parent id = " << b.prev_id << ENDL << "height = " << coinbase_height);
|
||
}
|
||
else
|
||
{
|
||
LOG_PRINT_RED_L0("Block recognized as orphaned and rejected, id = " << id << "," << ENDL << "parent id = " << b.prev_id << ENDL << "height = " << coinbase_height);
|
||
}
|
||
}
|
||
|
||
CHECK_AND_ASSERT_MES(validate_blockchain_prev_links(), false, "EPIC FAIL!");
|
||
return true;
|
||
CATCH_ENTRY_CUSTOM("blockchain_storage::handle_alternative_block", bvc.m_verification_failed = true, false);
|
||
}
|
||
//------------------------------------------------------------------
|
||
wide_difficulty_type blockchain_storage::get_x_difficulty_after_height(uint64_t height, bool is_pos)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
CHECK_AND_ASSERT_THROW_MES(height < m_db_blocks.size(), "Internal error: condition failed: height (" << height << ") < m_db_blocks.size() " << m_db_blocks.size());
|
||
wide_difficulty_type diff = 0;
|
||
for (uint64_t i = height + 1; i != m_db_blocks.size(); i++)
|
||
{
|
||
auto bei_ptr = m_db_blocks[i];
|
||
if (is_pos_block(bei_ptr->bl) == is_pos)
|
||
{
|
||
diff = bei_ptr->difficulty;
|
||
break;
|
||
}
|
||
}
|
||
if (diff == 0)
|
||
{
|
||
//never met x type of block, that meanst that difficulty is current
|
||
diff = get_cached_next_difficulty(is_pos);
|
||
}
|
||
return diff;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::is_reorganize_required(const block_extended_info& main_chain_bei, const alt_chain_type& alt_chain, const crypto::hash& proof_alt)
|
||
{
|
||
//alt_chain - back is latest(top), first - connection with main chain
|
||
const block_extended_info& alt_chain_bei = alt_chain.back()->second;
|
||
const block_extended_info& connection_point = alt_chain.front()->second;
|
||
|
||
if (connection_point.height <= m_core_runtime_config.hard_fork1_starts_after_height)
|
||
{
|
||
//use pre-hard fork, old-style comparing
|
||
if (main_chain_bei.cumulative_diff_adjusted < alt_chain_bei.cumulative_diff_adjusted)
|
||
return true;
|
||
else if (main_chain_bei.cumulative_diff_adjusted > alt_chain_bei.cumulative_diff_adjusted)
|
||
return false;
|
||
else // main_chain_bei.cumulative_diff_adjusted == alt_chain_bei.cumulative_diff_adjusted
|
||
{
|
||
if (!is_pos_block(main_chain_bei.bl))
|
||
return false; // do not reorganize on the same cummul diff if it's a PoW block
|
||
|
||
//in case of simultaneous PoS blocks are happened on the same height (quite common for PoS)
|
||
//we also try to weight them to guarantee consensus in network
|
||
if (std::memcmp(&main_chain_bei.stake_hash, &proof_alt, sizeof(main_chain_bei.stake_hash)) >= 0)
|
||
return false;
|
||
|
||
LOG_PRINT_L2("[is_reorganize_required]:TRUE, \"by order of memcmp\" main_stake_hash:" << &main_chain_bei.stake_hash << ", alt_stake_hash" << proof_alt);
|
||
return true;
|
||
}
|
||
}
|
||
else if (alt_chain_bei.height > m_core_runtime_config.hard_fork1_starts_after_height)
|
||
{
|
||
//new rules, applied after HARD_FORK_1
|
||
//to learn this algo please read https://github.com/hyle-team/docs/blob/master/zano/PoS_Analysis_and_improvements_proposal.pdf
|
||
|
||
wide_difficulty_type difficulty_pos_at_split_point = get_x_difficulty_after_height(connection_point.height - 1, true);
|
||
wide_difficulty_type difficulty_pow_at_split_point = get_x_difficulty_after_height(connection_point.height - 1, false);
|
||
|
||
difficulties main_cumul_diff = AUTO_VAL_INIT(main_cumul_diff);
|
||
difficulties alt_cumul_diff = AUTO_VAL_INIT(alt_cumul_diff);
|
||
//we use get_last_alt_x_block_cumulative_precise_adj_difficulty for getting both alt chain and main chain diff of given block types
|
||
|
||
wide_difficulty_type alt_pos_diff_end = get_last_alt_x_block_cumulative_precise_adj_difficulty(alt_chain, alt_chain_bei.height, true);
|
||
wide_difficulty_type alt_pos_diff_begin = get_last_alt_x_block_cumulative_precise_adj_difficulty(alt_chain_type(), connection_point.height-1, true);
|
||
alt_cumul_diff.pos_diff = alt_pos_diff_end - alt_pos_diff_begin;
|
||
|
||
wide_difficulty_type alt_pow_diff_end = get_last_alt_x_block_cumulative_precise_adj_difficulty(alt_chain, alt_chain_bei.height, false);
|
||
wide_difficulty_type alt_pow_diff_begin = get_last_alt_x_block_cumulative_precise_adj_difficulty(alt_chain_type(), connection_point.height - 1, false);
|
||
alt_cumul_diff.pow_diff = alt_pow_diff_end - alt_pow_diff_begin;
|
||
|
||
wide_difficulty_type main_pos_diff_end = get_last_alt_x_block_cumulative_precise_adj_difficulty(alt_chain_type(), m_db_blocks.size()-1, true);
|
||
wide_difficulty_type main_pos_diff_begin = get_last_alt_x_block_cumulative_precise_adj_difficulty(alt_chain_type(), connection_point.height - 1, true);
|
||
main_cumul_diff.pos_diff = main_pos_diff_end - main_pos_diff_begin;
|
||
|
||
wide_difficulty_type main_pow_diff_end = get_last_alt_x_block_cumulative_precise_adj_difficulty(alt_chain_type(), m_db_blocks.size() - 1, false);
|
||
wide_difficulty_type main_pow_diff_begin = get_last_alt_x_block_cumulative_precise_adj_difficulty(alt_chain_type(), connection_point.height - 1, false);
|
||
main_cumul_diff.pow_diff = main_pow_diff_end - main_pow_diff_begin;
|
||
|
||
//TODO: measurment of precise cumulative difficult
|
||
wide_difficulty_type alt = get_a_to_b_relative_cumulative_difficulty(difficulty_pos_at_split_point, difficulty_pow_at_split_point, alt_cumul_diff, main_cumul_diff);
|
||
wide_difficulty_type main = get_a_to_b_relative_cumulative_difficulty(difficulty_pos_at_split_point, difficulty_pow_at_split_point, main_cumul_diff, alt_cumul_diff);
|
||
|
||
if (main < alt)
|
||
return true;
|
||
else if (main > alt)
|
||
return false;
|
||
else
|
||
{
|
||
if (!is_pos_block(main_chain_bei.bl))
|
||
return false; // do not reorganize on the same cummul diff if it's a PoW block
|
||
|
||
//in case of simultaneous PoS blocks are happened on the same height (quite common for PoS)
|
||
//we also try to weight them to guarantee consensus in network
|
||
if (std::memcmp(&main_chain_bei.stake_hash, &proof_alt, sizeof(main_chain_bei.stake_hash)) >= 0)
|
||
return false;
|
||
|
||
LOG_PRINT_L2("[is_reorganize_required]:TRUE, \"by order of memcmp\" main_stake_hash:" << &main_chain_bei.stake_hash << ", alt_stake_hash" << proof_alt);
|
||
return true;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ASSERT_MES_AND_THROW("Unknown version of block");
|
||
}
|
||
}
|
||
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::pre_validate_relayed_block(block& bl, block_verification_context& bvc, const crypto::hash& id)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if (!(bl.prev_id == get_top_block_id()))
|
||
{
|
||
bvc.m_added_to_main_chain = false;
|
||
bvc.m_verification_failed = false;
|
||
return true;
|
||
}
|
||
//check proof of work
|
||
bool is_pos_bl = is_pos_block(bl);
|
||
wide_difficulty_type current_diffic = get_next_diff_conditional(is_pos_bl);
|
||
CHECK_AND_ASSERT_MES_CUSTOM(current_diffic, false, bvc.m_verification_failed = true, "!!!!!!!!! difficulty overhead !!!!!!!!!");
|
||
crypto::hash proof_hash = AUTO_VAL_INIT(proof_hash);
|
||
if (is_pos_bl)
|
||
{
|
||
wide_difficulty_type this_coin_diff = 0;
|
||
uint64_t amount = 0;
|
||
bool r = validate_pos_block(bl, current_diffic, amount, this_coin_diff, proof_hash, id, false);
|
||
CHECK_AND_ASSERT_MES_CUSTOM(r, false, bvc.m_verification_failed = true, "validate_pos_block failed!!");
|
||
bvc.m_added_to_main_chain = true;
|
||
}
|
||
else
|
||
{
|
||
proof_hash = get_block_longhash(bl); //get_block_longhash(bl);
|
||
|
||
if (!check_hash(proof_hash, current_diffic))
|
||
{
|
||
LOG_PRINT_L0("Block with id: " << id << ENDL
|
||
<< " : " << proof_hash << ENDL
|
||
<< "unexpected difficulty: " << current_diffic);
|
||
bvc.m_verification_failed = true;
|
||
bvc.m_added_to_main_chain = true;
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks, std::list<transaction>& txs) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if(start_offset >= m_db_blocks.size())
|
||
return false;
|
||
for(size_t i = start_offset; i < start_offset + count && i < m_db_blocks.size();i++)
|
||
{
|
||
blocks.push_back(m_db_blocks[i]->bl);
|
||
std::list<crypto::hash> missed_ids;
|
||
get_transactions(m_db_blocks[i]->bl.tx_hashes, txs, missed_ids);
|
||
CHECK_AND_ASSERT_MES(!missed_ids.size(), false, "have missed transactions in own block in main blockchain");
|
||
}
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_tx_rpc_details(const crypto::hash& h, tx_rpc_extended_info& tei, uint64_t timestamp, bool is_short) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
auto tx_ptr = m_db_transactions.get(h);
|
||
if (!tx_ptr)
|
||
{
|
||
tei.keeper_block = -1; // tx is not confirmed yet, probably it's in the pool
|
||
return false;
|
||
}
|
||
|
||
|
||
if (tx_ptr && !timestamp)
|
||
{
|
||
timestamp = get_actual_timestamp(m_db_blocks[tx_ptr->m_keeper_block_height]->bl);
|
||
}
|
||
tei.keeper_block = static_cast<int64_t>(tx_ptr->m_keeper_block_height);
|
||
fill_tx_rpc_details(tei, tx_ptr->tx, &(*tx_ptr), h, timestamp, is_short);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::search_by_id(const crypto::hash& id, std::list<std::string>& res) const
|
||
{
|
||
auto block_ptr = m_db_blocks_index.get(id);
|
||
if (block_ptr)
|
||
{
|
||
res.push_back("block");
|
||
}
|
||
|
||
auto tx_ptr = m_db_transactions.get(id);
|
||
if (tx_ptr)
|
||
{
|
||
res.push_back("tx");
|
||
}
|
||
|
||
auto ki_ptr = m_db_spent_keys.get( *reinterpret_cast<const crypto::key_image*>(&id));
|
||
if (ki_ptr)
|
||
{
|
||
res.push_back("key_image");
|
||
}
|
||
|
||
auto ms_ptr = m_db_multisig_outs.get(id);
|
||
if (ms_ptr)
|
||
{
|
||
res.push_back(std::string("multisig_id:") + epee::string_tools::pod_to_hex(ms_ptr->tx_id) + ":" + std::to_string(ms_ptr->out_no));
|
||
}
|
||
|
||
if (m_alternative_chains.end() != m_alternative_chains.find(id))
|
||
{
|
||
res.push_back("alt_block");
|
||
}
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_global_index_details(const COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES_BY_AMOUNT::request& req, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES_BY_AMOUNT::response & resp) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
try
|
||
{
|
||
auto out_ptr = m_db_outputs.get_subitem(req.amount, req.i); // get_subitem can rise an out_of_range exception
|
||
if (!out_ptr)
|
||
return false;
|
||
resp.tx_id = out_ptr->tx_id;
|
||
resp.out_no = out_ptr->out_no;
|
||
return true;
|
||
}
|
||
catch (std::out_of_range&)
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_multisig_id_details(const COMMAND_RPC_GET_MULTISIG_INFO::request& req, COMMAND_RPC_GET_MULTISIG_INFO::response & resp) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
return get_multisig_id_details(req.ms_id, resp.tx_id, resp.out_no);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_multisig_id_details(const crypto::hash& ms_id, crypto::hash& tx_id, uint64_t& out_no) const
|
||
{
|
||
auto out_ptr = m_db_multisig_outs.get(ms_id);
|
||
if (!out_ptr)
|
||
return false;
|
||
|
||
tx_id = out_ptr->tx_id;
|
||
out_no = out_ptr->out_no;
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_main_block_rpc_details(uint64_t i, block_rpc_extended_info& bei) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
auto core_bei_ptr = m_db_blocks[i];
|
||
crypto::hash id = get_block_hash(core_bei_ptr->bl);
|
||
bei.is_orphan = false;
|
||
bei.total_fee = 0;
|
||
bei.total_txs_size = 0;
|
||
if (true/*!ignore_transactions*/)
|
||
{
|
||
crypto::hash coinbase_id = get_transaction_hash(core_bei_ptr->bl.miner_tx);
|
||
//load transactions details
|
||
bei.transactions_details.push_back(tx_rpc_extended_info());
|
||
get_tx_rpc_details(coinbase_id, bei.transactions_details.back(), get_actual_timestamp(core_bei_ptr->bl), true);
|
||
for (auto& h : core_bei_ptr->bl.tx_hashes)
|
||
{
|
||
bei.transactions_details.push_back(tx_rpc_extended_info());
|
||
get_tx_rpc_details(h, bei.transactions_details.back(), get_actual_timestamp(core_bei_ptr->bl), true);
|
||
bei.total_fee += bei.transactions_details.back().fee;
|
||
bei.total_txs_size += bei.transactions_details.back().blob_size;
|
||
}
|
||
}
|
||
fill_block_rpc_details(bei, *core_bei_ptr, id);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_main_block_rpc_details(const crypto::hash& id, block_rpc_extended_info& bei) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
auto iptr = m_db_blocks_index.get(id);
|
||
if (!iptr)
|
||
return false;
|
||
return get_main_block_rpc_details(*iptr, bei);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_alt_blocks_rpc_details(uint64_t start_offset, uint64_t count, std::vector<block_rpc_extended_info>& blocks) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
CRITICAL_REGION_LOCAL1(m_alternative_chains_lock);
|
||
|
||
if (start_offset >= m_alternative_chains.size() || count == 0)
|
||
return true; // empty result
|
||
|
||
if (start_offset + count >= m_alternative_chains.size())
|
||
count = m_alternative_chains.size() - start_offset; // correct count if it's too big
|
||
|
||
// collect iterators to all the alt blocks for speedy sorting
|
||
std::vector<alt_chain_container::const_iterator> blocks_its;
|
||
blocks_its.reserve(m_alternative_chains.size());
|
||
for (alt_chain_container::const_iterator it = m_alternative_chains.begin(); it != m_alternative_chains.end(); ++it)
|
||
blocks_its.push_back(it);
|
||
|
||
// partially sort blocks by height, so only 0...(start_offset+count-1) first blocks are sorted
|
||
std::partial_sort(blocks_its.begin(), blocks_its.begin() + start_offset + count, blocks_its.end(),
|
||
[](const alt_chain_container::const_iterator &lhs, const alt_chain_container::const_iterator& rhs) ->bool {
|
||
return lhs->second.height < rhs->second.height;
|
||
}
|
||
);
|
||
|
||
// erase blocks from 0 till start_offset-1
|
||
blocks_its.erase(blocks_its.begin(), blocks_its.begin() + start_offset);
|
||
|
||
// erase the tail
|
||
blocks_its.erase(blocks_its.begin() + count, blocks_its.end());
|
||
|
||
// populate the result
|
||
blocks.reserve(blocks_its.size());
|
||
for(auto it : blocks_its)
|
||
{
|
||
blocks.push_back(block_rpc_extended_info());
|
||
get_alt_block_rpc_details(it->second, it->first, blocks.back());
|
||
}
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_alt_block_rpc_details(const crypto::hash& id, block_rpc_extended_info& bei) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
CRITICAL_REGION_LOCAL1(m_alternative_chains_lock);
|
||
auto it = m_alternative_chains.find(id);
|
||
if (it == m_alternative_chains.end())
|
||
return false;
|
||
|
||
const block_extended_info& bei_core = it->second;
|
||
return get_alt_block_rpc_details(bei_core, id, bei);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_alt_block_rpc_details(const block_extended_info& bei_core, const crypto::hash& id, block_rpc_extended_info& bei) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
CRITICAL_REGION_LOCAL1(m_alternative_chains_lock);
|
||
|
||
bei.is_orphan = true;
|
||
|
||
crypto::hash coinbase_id = get_transaction_hash(bei_core.bl.miner_tx);
|
||
//load transactions details
|
||
bei.transactions_details.push_back(tx_rpc_extended_info());
|
||
fill_tx_rpc_details(bei.transactions_details.back(), bei_core.bl.miner_tx, nullptr, coinbase_id, get_actual_timestamp(bei_core.bl));
|
||
|
||
bei.total_fee = 0;
|
||
for (auto& h : bei_core.bl.tx_hashes)
|
||
{
|
||
bei.transactions_details.push_back(tx_rpc_extended_info());
|
||
if (!get_tx_rpc_details(h, bei.transactions_details.back(), get_actual_timestamp(bei_core.bl), true))
|
||
{
|
||
//tx not in blockchain, supposed to be in tx pool
|
||
m_tx_pool.get_transaction_details(h, bei.transactions_details.back());
|
||
}
|
||
bei.total_fee += bei.transactions_details.back().fee;
|
||
}
|
||
|
||
fill_block_rpc_details(bei, bei_core, id);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_main_blocks_rpc_details(uint64_t start_offset, size_t count, bool ignore_transactions, std::list<block_rpc_extended_info>& blocks) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if (start_offset >= m_db_blocks.size())
|
||
return false;
|
||
|
||
for (size_t i = start_offset; i < start_offset + count && i < m_db_blocks.size(); i++)
|
||
{
|
||
blocks.push_back(block_rpc_extended_info());
|
||
block_rpc_extended_info& bei = blocks.back();
|
||
get_main_block_rpc_details(i, bei);
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_blocks(uint64_t start_offset, size_t count, std::list<block>& blocks)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if(start_offset >= m_db_blocks.size())
|
||
return false;
|
||
|
||
for(size_t i = start_offset; i < start_offset + count && i < m_db_blocks.size();i++)
|
||
blocks.push_back(m_db_blocks[i]->bl);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::handle_get_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, NOTIFY_RESPONSE_GET_OBJECTS::request& rsp)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
rsp.current_blockchain_height = get_current_blockchain_size();
|
||
std::list<block> blocks;
|
||
get_blocks(arg.blocks, blocks, rsp.missed_ids);
|
||
|
||
BOOST_FOREACH(const auto& bl, blocks)
|
||
{
|
||
std::list<transaction> txs;
|
||
get_transactions(bl.tx_hashes, txs, rsp.missed_ids);
|
||
|
||
CHECK_AND_ASSERT_MES(!rsp.missed_ids.size(), false, "Host have requested block with missed transactions missed_tx_id.size()=" << rsp.missed_ids.size()
|
||
<< ENDL << "for block id = " << get_block_hash(bl));
|
||
rsp.blocks.push_back(block_complete_entry());
|
||
block_complete_entry& e = rsp.blocks.back();
|
||
//pack block
|
||
e.block = t_serializable_object_to_blob(bl);
|
||
//pack transactions
|
||
BOOST_FOREACH(transaction& tx, txs)
|
||
e.txs.push_back(t_serializable_object_to_blob(tx));
|
||
|
||
}
|
||
//get another transactions, if need
|
||
std::list<transaction> txs;
|
||
get_transactions(arg.txs, txs, rsp.missed_ids);
|
||
//pack aside transactions
|
||
BOOST_FOREACH(const auto& tx, txs)
|
||
rsp.txs.push_back(t_serializable_object_to_blob(tx));
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_transactions_daily_stat(uint64_t& daily_cnt, uint64_t& daily_volume) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
daily_cnt = daily_volume = 0;
|
||
for(size_t i = (m_db_blocks.size() > CURRENCY_BLOCKS_PER_DAY ? m_db_blocks.size() - CURRENCY_BLOCKS_PER_DAY:0 ); i!=m_db_blocks.size(); i++)
|
||
{
|
||
auto ptr = m_db_blocks[i];
|
||
for(auto& h : ptr->bl.tx_hashes)
|
||
{
|
||
++daily_cnt;
|
||
auto tx_ptr = m_db_transactions.find(h);
|
||
CHECK_AND_ASSERT_MES(tx_ptr, false, "Wrong transaction hash " << h << " in block on height " << i);
|
||
uint64_t am = 0;
|
||
bool r = get_inputs_money_amount(tx_ptr->tx, am);
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to get_inputs_money_amount");
|
||
daily_volume += am;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::check_keyimages(const std::list<crypto::key_image>& images, std::list<uint64_t>& images_stat) const
|
||
{
|
||
//true - unspent, false - spent
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
for (auto& ki : images)
|
||
{
|
||
auto ki_ptr = m_db_spent_keys.get(ki);
|
||
if(ki_ptr)
|
||
images_stat.push_back(*ki_ptr);
|
||
else
|
||
images_stat.push_back(0);
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::get_seconds_between_last_n_block(size_t n) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if (m_db_blocks.size() <= n)
|
||
return 0;
|
||
|
||
uint64_t top_block_ts = get_actual_timestamp(m_db_blocks[m_db_blocks.size() - 1]->bl);
|
||
uint64_t n_block_ts = get_actual_timestamp(m_db_blocks[m_db_blocks.size() - 1 - n]->bl);
|
||
|
||
return top_block_ts > n_block_ts ? top_block_ts - n_block_ts : 0;
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::get_current_hashrate(size_t aprox_count) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if (aprox_count == 0 || m_db_blocks.size() <= aprox_count)
|
||
return 0; // incorrect parameters
|
||
|
||
uint64_t nearest_front_pow_block_i = m_db_blocks.size() - 1;
|
||
while (nearest_front_pow_block_i != 0)
|
||
{
|
||
if (!is_pos_block(m_db_blocks[nearest_front_pow_block_i]->bl))
|
||
break;
|
||
--nearest_front_pow_block_i;
|
||
}
|
||
|
||
uint64_t nearest_back_pow_block_i = m_db_blocks.size() - aprox_count;
|
||
while (nearest_back_pow_block_i != 0)
|
||
{
|
||
if (!is_pos_block(m_db_blocks[nearest_back_pow_block_i]->bl))
|
||
break;
|
||
--nearest_back_pow_block_i;
|
||
}
|
||
|
||
std::shared_ptr<const block_extended_info> front_blk_ptr = m_db_blocks[nearest_front_pow_block_i];
|
||
std::shared_ptr<const block_extended_info> back_blk_ptr = m_db_blocks[nearest_back_pow_block_i];
|
||
uint64_t front_blk_ts = front_blk_ptr->bl.timestamp;
|
||
uint64_t back_blk_ts = back_blk_ptr->bl.timestamp;
|
||
|
||
uint64_t ts_delta = front_blk_ts > back_blk_ts ? front_blk_ts - back_blk_ts : DIFFICULTY_POW_TARGET;
|
||
|
||
wide_difficulty_type w_hr = (front_blk_ptr->cumulative_diff_precise - back_blk_ptr->cumulative_diff_precise) / ts_delta;
|
||
|
||
return w_hr.convert_to<uint64_t>();
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_alternative_blocks(std::list<block>& blocks) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
CRITICAL_REGION_LOCAL1(m_alternative_chains_lock);
|
||
BOOST_FOREACH(const auto& alt_bl, m_alternative_chains)
|
||
{
|
||
blocks.push_back(alt_bl.second.bl);
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
size_t blockchain_storage::get_alternative_blocks_count() const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
CRITICAL_REGION_LOCAL1(m_alternative_chains_lock);
|
||
return m_alternative_chains.size();
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::add_out_to_get_random_outs(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, uint64_t amount, size_t i, uint64_t mix_count, bool use_only_forced_to_mix) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
auto out_ptr = m_db_outputs.get_subitem(amount, i);
|
||
auto tx_ptr = m_db_transactions.find(out_ptr->tx_id);
|
||
CHECK_AND_ASSERT_MES(tx_ptr, false, "internal error: transaction with id " << out_ptr->tx_id << ENDL <<
|
||
", used in mounts global index for amount=" << amount << ": i=" << i << "not found in transactions index");
|
||
CHECK_AND_ASSERT_MES(tx_ptr->tx.vout.size() > out_ptr->out_no, false, "internal error: in global outs index, transaction out index="
|
||
<< out_ptr->out_no << " more than transaction outputs = " << tx_ptr->tx.vout.size() << ", for tx id = " << out_ptr->tx_id);
|
||
|
||
const transaction& tx = tx_ptr->tx;
|
||
CHECK_AND_ASSERT_MES(tx.vout[out_ptr->out_no].target.type() == typeid(txout_to_key), false, "unknown tx out type");
|
||
|
||
CHECK_AND_ASSERT_MES(tx_ptr->m_spent_flags.size() == tx.vout.size(), false, "internal error");
|
||
|
||
//do not use outputs that obviously spent for mixins
|
||
if (tx_ptr->m_spent_flags[out_ptr->out_no])
|
||
return false;
|
||
|
||
//check if transaction is unlocked
|
||
if (!is_tx_spendtime_unlocked(get_tx_unlock_time(tx, out_ptr->out_no)))
|
||
return false;
|
||
|
||
//use appropriate mix_attr out
|
||
uint8_t mix_attr = boost::get<txout_to_key>(tx.vout[out_ptr->out_no].target).mix_attr;
|
||
|
||
if(mix_attr == CURRENCY_TO_KEY_OUT_FORCED_NO_MIX)
|
||
return false; //COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS call means that ring signature will have more than one entry.
|
||
else if(use_only_forced_to_mix && mix_attr == CURRENCY_TO_KEY_OUT_RELAXED)
|
||
return false; //relaxed not allowed
|
||
else if(mix_attr != CURRENCY_TO_KEY_OUT_RELAXED && mix_attr > mix_count)
|
||
return false;//mix_attr set to specific minimum, and mix_count is less then desired count
|
||
|
||
|
||
COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry& oen = *result_outs.outs.insert(result_outs.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry());
|
||
oen.global_amount_index = i;
|
||
oen.out_key = boost::get<txout_to_key>(tx.vout[out_ptr->out_no].target).key;
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
size_t blockchain_storage::find_end_of_allowed_index(uint64_t amount) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
uint64_t sz = m_db_outputs.get_item_size(amount);
|
||
|
||
if (!sz)
|
||
return 0;
|
||
uint64_t i = sz;
|
||
do
|
||
{
|
||
--i;
|
||
auto out_ptr = m_db_outputs.get_subitem(amount, i);
|
||
auto tx_ptr = m_db_transactions.find(out_ptr->tx_id);
|
||
CHECK_AND_ASSERT_MES(tx_ptr, 0, "internal error: failed to find transaction from outputs index with tx_id=" << out_ptr->tx_id);
|
||
if (tx_ptr->m_keeper_block_height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW <= get_current_blockchain_size())
|
||
return i+1;
|
||
} while (i != 0);
|
||
return 0;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
BOOST_FOREACH(uint64_t amount, req.amounts)
|
||
{
|
||
COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs = *res.outs.insert(res.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount());
|
||
result_outs.amount = amount;
|
||
uint64_t outs_container_size = m_db_outputs.get_item_size(amount);
|
||
if (!outs_container_size)
|
||
{
|
||
LOG_ERROR("COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS: not outs for amount " << amount << ", wallet should use some real outs when it lookup for some mix, so, at least one out for this amount should exist");
|
||
continue;//actually this is strange situation, wallet should use some real outs when it lookup for some mix, so, at least one out for this amount should exist
|
||
}
|
||
//it is not good idea to use top fresh outs, because it increases possibility of transaction canceling on split
|
||
//lets find upper bound of not fresh outs
|
||
size_t up_index_limit = find_end_of_allowed_index(amount);
|
||
CHECK_AND_ASSERT_MES(up_index_limit <= outs_container_size, false, "internal error: find_end_of_allowed_index returned wrong index=" << up_index_limit << ", with amount_outs.size = " << outs_container_size);
|
||
if (up_index_limit >= req.outs_count)
|
||
{
|
||
std::set<size_t> used;
|
||
size_t try_count = 0;
|
||
for(uint64_t j = 0; j != req.outs_count && try_count < up_index_limit;)
|
||
{
|
||
size_t i = crypto::rand<size_t>()%up_index_limit;
|
||
if(used.count(i))
|
||
continue;
|
||
bool added = add_out_to_get_random_outs(result_outs, amount, i, req.outs_count, req.use_forced_mix_outs);
|
||
used.insert(i);
|
||
if(added)
|
||
++j;
|
||
++try_count;
|
||
}
|
||
if (result_outs.outs.size() < req.outs_count)
|
||
{
|
||
LOG_PRINT_RED_L0("Not enough inputs for amount " << amount << ", needed " << req.outs_count << ", added " << result_outs.outs.size() << " good outs from " << up_index_limit << " unlocked of " << outs_container_size << " total");
|
||
}
|
||
}else
|
||
{
|
||
size_t added = 0;
|
||
for (size_t i = 0; i != up_index_limit; i++)
|
||
added += add_out_to_get_random_outs(result_outs, amount, i, req.outs_count, req.use_forced_mix_outs) ? 1 : 0;
|
||
LOG_PRINT_RED_L0("Not enough inputs for amount " << amount << ", needed " << req.outs_count << ", added " << added << " good outs from " << up_index_limit << " unlocked of " << outs_container_size << " total - respond with all good outs");
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
boost::multiprecision::uint128_t blockchain_storage::total_coins() const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if (!m_db_blocks.size())
|
||
return 0;
|
||
|
||
return m_db_blocks.back()->already_generated_coins;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::is_pos_allowed() const
|
||
{
|
||
return get_top_block_height() >= m_core_runtime_config.pos_minimum_heigh;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::update_spent_tx_flags_for_input(uint64_t amount, const txout_v& o, bool spent)
|
||
{
|
||
if (o.type() == typeid(ref_by_id))
|
||
return update_spent_tx_flags_for_input(boost::get<ref_by_id>(o).tx_id, boost::get<ref_by_id>(o).n, spent);
|
||
else if (o.type() == typeid(uint64_t))
|
||
return update_spent_tx_flags_for_input(amount, boost::get<uint64_t>(o), spent);
|
||
|
||
LOG_ERROR("Unknown txout_v type");
|
||
return false;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::update_spent_tx_flags_for_input(uint64_t amount, uint64_t global_index, bool spent)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
uint64_t outs_count = m_db_outputs.get_item_size(amount);
|
||
CHECK_AND_ASSERT_MES(outs_count, false, "Amount " << amount << " have not found during update_spent_tx_flags_for_input()");
|
||
CHECK_AND_ASSERT_MES(global_index < outs_count, false, "Global index" << global_index << " for amount " << amount << " bigger value than amount's vector size()=" << outs_count);
|
||
auto out_ptr = m_db_outputs.get_subitem(amount, global_index);
|
||
return update_spent_tx_flags_for_input(out_ptr->tx_id, out_ptr->out_no, spent);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::update_spent_tx_flags_for_input(const crypto::hash& tx_id, size_t n, bool spent)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
auto tx_ptr = m_db_transactions.find(tx_id);
|
||
CHECK_AND_ASSERT_MES(tx_ptr, false, "Can't find transaction id: " << tx_id);
|
||
transaction_chain_entry tce_local = *tx_ptr;
|
||
|
||
CHECK_AND_ASSERT_MES(n < tce_local.m_spent_flags.size(), false, "Wrong input offset: " << n << " in transaction id: " << tx_id);
|
||
|
||
tce_local.m_spent_flags[n] = spent;
|
||
m_db_transactions.set(tx_id, tce_local);
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::update_spent_tx_flags_for_input(const crypto::hash& multisig_id, uint64_t spent_height)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
auto ms_ptr = m_db_multisig_outs.find(multisig_id);
|
||
CHECK_AND_ASSERT_MES(ms_ptr, false, "unable to find multisig_id " << multisig_id);
|
||
|
||
// update spent height at ms container
|
||
ms_output_entry msoe_local = *ms_ptr;
|
||
if (msoe_local.spent_height != 0 && spent_height != 0)
|
||
{
|
||
LOG_PRINT_YELLOW(LOCATION_SS << ": WARNING: ms out " << multisig_id << " was already marked as SPENT at height " << msoe_local.spent_height << ", new spent_height: " << spent_height , LOG_LEVEL_0);
|
||
}
|
||
msoe_local.spent_height = spent_height;
|
||
m_db_multisig_outs.set(multisig_id, msoe_local);
|
||
|
||
return update_spent_tx_flags_for_input(ms_ptr->tx_id, ms_ptr->out_no, spent_height != 0);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::has_multisig_output(const crypto::hash& multisig_id) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
return static_cast<bool>(m_db_multisig_outs.find(multisig_id));
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::is_multisig_output_spent(const crypto::hash& multisig_id) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
auto multisig_ptr = m_db_multisig_outs.find(multisig_id);
|
||
if (!multisig_ptr)
|
||
return false; // there's no such output - treat as not spent
|
||
|
||
const crypto::hash& source_tx_id = multisig_ptr->tx_id;
|
||
size_t ms_out_index = multisig_ptr->out_no; // index of multisig output in source tx
|
||
|
||
auto source_tx_ptr = m_db_transactions.find(source_tx_id);
|
||
CHECK_AND_ASSERT_MES(source_tx_ptr, true, "Internal error: source tx not found for ms out " << multisig_id << ", ms out is treated as spent for safety");
|
||
CHECK_AND_ASSERT_MES(ms_out_index < source_tx_ptr->m_spent_flags.size(), true, "Internal error: ms out " << multisig_id << " has incorrect index " << ms_out_index << " in source tx " << source_tx_id << ", ms out is treated as spent for safety");
|
||
|
||
return source_tx_ptr->m_spent_flags[ms_out_index];
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, uint64_t& starter_offset)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
if(!qblock_ids.size() /*|| !req.m_total_height*/)
|
||
{
|
||
LOG_ERROR("Client sent wrong NOTIFY_REQUEST_CHAIN: m_block_ids.size()=" << qblock_ids.size() << /*", m_height=" << req.m_total_height <<*/ ", dropping connection");
|
||
return false;
|
||
}
|
||
//check genesis match
|
||
if(qblock_ids.back() != get_block_hash(m_db_blocks[0]->bl))
|
||
{
|
||
LOG_ERROR("Client sent wrong NOTIFY_REQUEST_CHAIN: genesis block missmatch: " << ENDL << "id: "
|
||
<< string_tools::pod_to_hex(qblock_ids.back()) << ", " << ENDL << "expected: " << get_block_hash(m_db_blocks[0]->bl)
|
||
<< "," << ENDL << " dropping connection");
|
||
return false;
|
||
}
|
||
|
||
/* Figure out what blocks we should request to get state_normal */
|
||
for(auto& bl: qblock_ids)
|
||
{
|
||
auto block_index_ptr = m_db_blocks_index.find(bl);
|
||
if(block_index_ptr)
|
||
{
|
||
//we start to put block ids INCLUDING last known id, just to make other side be sure
|
||
starter_offset = *block_index_ptr;
|
||
return true;
|
||
}
|
||
}
|
||
|
||
LOG_ERROR("Internal error handling connection, can't find split point");
|
||
return false;
|
||
}
|
||
//------------------------------------------------------------------
|
||
wide_difficulty_type blockchain_storage::block_difficulty(size_t i) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
CHECK_AND_ASSERT_MES(i < m_db_blocks.size(), false, "wrong block index i = " << i << " at blockchain_storage::block_difficulty()");
|
||
return m_db_blocks[i]->difficulty;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::forecast_difficulty(std::vector<std::pair<uint64_t, wide_difficulty_type>> &out_height_2_diff_vector, bool pos) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
std::vector<uint64_t> timestamps;
|
||
std::vector<wide_difficulty_type> cumulative_difficulties;
|
||
|
||
uint64_t blocks_size = m_db_blocks.size();
|
||
size_t count = 0;
|
||
|
||
uint64_t max_block_height_for_this_type = 0;
|
||
uint64_t min_block_height_for_this_type = UINT64_MAX;
|
||
wide_difficulty_type last_block_diff_for_this_type = 0;
|
||
|
||
for (uint64_t cur_ind = blocks_size - 1; cur_ind != 0 && count < DIFFICULTY_WINDOW; cur_ind--)
|
||
{
|
||
auto beiptr = m_db_blocks[cur_ind];
|
||
bool is_pos_bl = is_pos_block(beiptr->bl);
|
||
if (pos != is_pos_bl)
|
||
continue;
|
||
|
||
if (max_block_height_for_this_type < beiptr->height)
|
||
max_block_height_for_this_type = beiptr->height;
|
||
if (min_block_height_for_this_type > beiptr->height)
|
||
min_block_height_for_this_type = beiptr->height;
|
||
if (last_block_diff_for_this_type == 0)
|
||
last_block_diff_for_this_type = beiptr->difficulty;
|
||
|
||
timestamps.push_back(beiptr->bl.timestamp);
|
||
cumulative_difficulties.push_back(beiptr->cumulative_diff_precise);
|
||
++count;
|
||
}
|
||
|
||
if (count != DIFFICULTY_WINDOW)
|
||
return false;
|
||
|
||
const uint64_t target_seconds = pos ? DIFFICULTY_POS_TARGET : DIFFICULTY_POW_TARGET;
|
||
const uint64_t avg_interval = std::max(static_cast<uint64_t>(1), (max_block_height_for_this_type - min_block_height_for_this_type) / count);
|
||
uint64_t height = max_block_height_for_this_type;
|
||
out_height_2_diff_vector.clear();
|
||
out_height_2_diff_vector.push_back(std::make_pair(height, last_block_diff_for_this_type)); // the first element corresponds to the last block of this type
|
||
for (size_t i = 0; i < DIFFICULTY_CUT; ++i)
|
||
{
|
||
wide_difficulty_type diff = next_difficulty_1(timestamps, cumulative_difficulties, target_seconds);
|
||
height += avg_interval;
|
||
out_height_2_diff_vector.push_back(std::make_pair(height, diff));
|
||
|
||
timestamps.pop_back(); // keep sorted in descending order
|
||
timestamps.insert(timestamps.begin(), timestamps.front() + target_seconds); // increment time so it won't be affected by sorting in next_difficulty
|
||
|
||
cumulative_difficulties.pop_back();
|
||
cumulative_difficulties.insert(cumulative_difficulties.begin(), 0); // does not matter
|
||
}
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
size_t blockchain_storage::get_current_sequence_factor(bool pos) const
|
||
{
|
||
size_t n = 0;
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
uint64_t sz = m_db_blocks.size();
|
||
if (!sz)
|
||
return n;
|
||
|
||
for (uint64_t i = sz - 1; i != 0; --i, n++)
|
||
{
|
||
if (pos != is_pos_block(m_db_blocks[i]->bl))
|
||
break;
|
||
}
|
||
return n;
|
||
}
|
||
//------------------------------------------------------------------
|
||
size_t blockchain_storage::get_current_sequence_factor_for_alt(alt_chain_type& alt_chain, bool pos, uint64_t connection_height) const
|
||
{
|
||
size_t n = 0;
|
||
for (auto it = alt_chain.rbegin(); it != alt_chain.rend(); it++, n++)
|
||
{
|
||
if (pos != is_pos_block((*it)->second.bl))
|
||
return n;
|
||
}
|
||
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
for (uint64_t h = connection_height - 1; h != 0; --h, n++)
|
||
{
|
||
if (pos != is_pos_block(m_db_blocks[h]->bl))
|
||
{
|
||
return n;
|
||
}
|
||
}
|
||
return n;
|
||
}
|
||
//------------------------------------------------------------------
|
||
std::string blockchain_storage::get_blockchain_string(uint64_t start_index, uint64_t end_index) const
|
||
{
|
||
std::stringstream ss;
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if (start_index >= m_db_blocks.size())
|
||
{
|
||
LOG_PRINT_L0("Wrong starter index set: " << start_index << ", expected max index " << m_db_blocks.size() - 1);
|
||
return ss.str();
|
||
}
|
||
|
||
for (size_t i = start_index; i != m_db_blocks.size() && i != end_index; i++)
|
||
{
|
||
ss << (is_pos_block(m_db_blocks[i]->bl) ? "[PoS]" : "[PoW]") << "h: " << i << ", timestamp: " << m_db_blocks[i]->bl.timestamp << "(" << epee::misc_utils::get_time_str_v2(m_db_blocks[i]->bl.timestamp) << ")"
|
||
<< ", cumul_diff_adj: " << m_db_blocks[i]->cumulative_diff_adjusted
|
||
<< ", cumul_diff_pcs: " << m_db_blocks[i]->cumulative_diff_precise
|
||
<< ", cumul_size: " << m_db_blocks[i]->block_cumulative_size
|
||
<< ", id: " << get_block_hash(m_db_blocks[i]->bl)
|
||
<< ", difficulty: " << block_difficulty(i) << ", nonce " << m_db_blocks[i]->bl.nonce << ", tx_count " << m_db_blocks[i]->bl.tx_hashes.size() << ENDL;
|
||
}
|
||
return ss.str();
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::print_blockchain(uint64_t start_index, uint64_t end_index) const
|
||
{
|
||
//LOG_ERROR("NOT IMPLEMENTED YET");
|
||
|
||
LOG_PRINT_L1("Current blockchain:" << ENDL << get_blockchain_string(start_index, end_index));
|
||
LOG_PRINT_L0("Blockchain printed with log level 1");
|
||
}
|
||
|
||
void blockchain_storage::print_blockchain_with_tx(uint64_t start_index, uint64_t end_index) const
|
||
{
|
||
boost::filesystem::ofstream ss;
|
||
ss.exceptions(/*std::ifstream::failbit |*/ std::ifstream::badbit);
|
||
ss.open(log_space::log_singletone::get_default_log_folder() + "/blockchain_with_tx.txt", std::ios_base::binary | std::ios_base::out | std::ios_base::trunc);
|
||
|
||
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if (start_index >= m_db_blocks.size())
|
||
{
|
||
LOG_PRINT_L0("Wrong starter index set: " << start_index << ", expected max index " << m_db_blocks.size() - 1);
|
||
return;
|
||
}
|
||
|
||
for (size_t i = start_index; i != m_db_blocks.size() && i != end_index; i++)
|
||
{
|
||
ss << (is_pos_block(m_db_blocks[i]->bl) ? "[PoS]" : "[PoW]") << "h: " << i << ", timestamp: " << m_db_blocks[i]->bl.timestamp << "(" << epee::misc_utils::get_time_str_v2(m_db_blocks[i]->bl.timestamp) << ")"
|
||
<< ", cumul_diff_adj: " << m_db_blocks[i]->cumulative_diff_adjusted
|
||
<< ", cumul_diff_pcs: " << m_db_blocks[i]->cumulative_diff_precise
|
||
<< ", cumul_size: " << m_db_blocks[i]->block_cumulative_size
|
||
<< ", id: " << get_block_hash(m_db_blocks[i]->bl)
|
||
<< ", difficulty: " << block_difficulty(i) << ", nonce " << m_db_blocks[i]->bl.nonce << ", tx_count " << m_db_blocks[i]->bl.tx_hashes.size() << ENDL;
|
||
|
||
ss << "[miner id]: " << get_transaction_hash(m_db_blocks[i]->bl.miner_tx) << ENDL << currency::obj_to_json_str(m_db_blocks[i]->bl.miner_tx) << ENDL;
|
||
|
||
for (size_t j = 0; j != m_db_blocks[i]->bl.tx_hashes.size(); j++)
|
||
{
|
||
auto tx_it = m_db_transactions.find(m_db_blocks[i]->bl.tx_hashes[j]);
|
||
if (tx_it == m_db_transactions.end())
|
||
{
|
||
LOG_ERROR("internal error: tx id " << m_db_blocks[i]->bl.tx_hashes[j] << " not found in transactions index");
|
||
continue;
|
||
}
|
||
ss << "[id]:" << m_db_blocks[i]->bl.tx_hashes[j] << ENDL << currency::obj_to_json_str(tx_it->tx) << ENDL;
|
||
}
|
||
|
||
}
|
||
ss.close();
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::print_blockchain_index() const
|
||
{
|
||
LOG_ERROR("NOT IMPLEMENTED YET");
|
||
// std::stringstream ss;
|
||
// CRITICAL_REGION_LOCAL(m_blockchain_lock);
|
||
// BOOST_FOREACH(const blocks_by_id_index::value_type& v, m_db_blocks_index)
|
||
// ss << "id\t\t" << v.first << " height" << v.second << ENDL << "";
|
||
//
|
||
// LOG_PRINT_L0("Current blockchain index:" << ENDL << ss.str());
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::print_db_cache_perfeormance_data() const
|
||
{
|
||
#define DB_CONTAINER_PERF_DATA_ENTRY(container_name) \
|
||
<< #container_name << ": hit_percent: " << m_db_blocks.get_performance_data().hit_percent.get_avg() << "%," \
|
||
<< " read_cache: " << container_name.get_performance_data().read_cache_microsec.get_avg() \
|
||
<< " read_db: " << container_name.get_performance_data().read_db_microsec.get_avg() \
|
||
<< " upd_cache: " << container_name.get_performance_data().update_cache_microsec.get_avg() \
|
||
<< " write_cache: " << container_name.get_performance_data().write_to_cache_microsec.get_avg() \
|
||
<< " write_db: " << container_name.get_performance_data().write_to_db_microsec.get_avg() \
|
||
<< " native_db_set_t: " << container_name.get_performance_data_native().backend_set_t_time.get_avg() \
|
||
<< " native_db_set_pod: " << container_name.get_performance_data_native().backend_set_pod_time.get_avg() \
|
||
<< " native_db_seriz: " << container_name.get_performance_data_native().set_serialize_t_time.get_avg()
|
||
|
||
|
||
LOG_PRINT_L0("DB_PERFORMANCE_DATA: " << ENDL
|
||
DB_CONTAINER_PERF_DATA_ENTRY(m_db_blocks) << ENDL
|
||
DB_CONTAINER_PERF_DATA_ENTRY(m_db_blocks_index) << ENDL
|
||
DB_CONTAINER_PERF_DATA_ENTRY(m_db_transactions) << ENDL
|
||
DB_CONTAINER_PERF_DATA_ENTRY(m_db_spent_keys) << ENDL
|
||
//DB_CONTAINER_PERF_DATA_ENTRY(m_db_outputs) << ENDL
|
||
DB_CONTAINER_PERF_DATA_ENTRY(m_db_multisig_outs) << ENDL
|
||
DB_CONTAINER_PERF_DATA_ENTRY(m_db_solo_options) << ENDL
|
||
DB_CONTAINER_PERF_DATA_ENTRY(m_db_aliases) << ENDL
|
||
DB_CONTAINER_PERF_DATA_ENTRY(m_db_addr_to_alias) << ENDL
|
||
//DB_CONTAINER_PERF_DATA_ENTRY(m_db_per_block_gindex_incs) << ENDL
|
||
//DB_CONTAINER_PERF_DATA_ENTRY(m_tx_fee_median) << ENDL
|
||
);
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::get_last_n_x_blocks(uint64_t n, bool pos_blocks, std::list<std::shared_ptr<const block_extended_info>>& blocks) const
|
||
{
|
||
uint64_t count = 0;
|
||
bool looking_for_a_pos = true;
|
||
for (uint64_t i = m_db_blocks.size() - 1; i != 0; --i)
|
||
{
|
||
auto block_ptr = m_db_blocks[i];
|
||
if (is_pos_block(block_ptr->bl) == pos_blocks)
|
||
{
|
||
blocks.push_back(block_ptr);
|
||
++count;
|
||
if (count >= n)
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::print_last_n_difficulty_numbers(uint64_t n) const
|
||
{
|
||
|
||
std::stringstream ss;
|
||
std::list<std::shared_ptr<const block_extended_info>> pos_blocks;
|
||
std::list<std::shared_ptr<const block_extended_info>> pow_blocks;
|
||
|
||
get_last_n_x_blocks(n, true, pos_blocks);
|
||
get_last_n_x_blocks(n, false, pow_blocks);
|
||
ss << "PoS blocks difficulty:" << ENDL;
|
||
for (auto& bl_ptr : pos_blocks)
|
||
{
|
||
ss << bl_ptr->difficulty << ENDL;
|
||
}
|
||
|
||
ss << "PoW blocks difficulty:" << ENDL;
|
||
for (auto& bl_ptr : pow_blocks)
|
||
{
|
||
ss << bl_ptr->difficulty << ENDL;
|
||
}
|
||
LOG_PRINT_L0("LAST BLOCKS:" << ss.str());
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::print_blockchain_outs_stat() const
|
||
{
|
||
LOG_ERROR("NOT IMPLEMENTED YET");
|
||
// std::stringstream ss;
|
||
// CRITICAL_REGION_LOCAL(m_blockchain_lock);
|
||
// BOOST_FOREACH(const outputs_container::value_type& v, m_db_outputs)
|
||
// {
|
||
// const std::vector<std::pair<crypto::hash, size_t> >& vals = v.second;
|
||
// if (vals.size())
|
||
// {
|
||
// ss << "amount: " << print_money(v.first);
|
||
// uint64_t total_count = vals.size();
|
||
// uint64_t unused_count = 0;
|
||
// for (size_t i = 0; i != vals.size(); i++)
|
||
// {
|
||
// bool used = false;
|
||
// auto it_tx = m_db_transactions.find(vals[i].first);
|
||
// if (it_tx == m_db_transactions.end())
|
||
// {
|
||
// LOG_ERROR("Tx with id not found " << vals[i].first);
|
||
// }
|
||
// else
|
||
// {
|
||
// if (vals[i].second >= it_tx->second.m_spent_flags.size())
|
||
// {
|
||
// LOG_ERROR("Tx with id " << vals[i].first << " in global index have wrong entry in global index, offset in tx = " << vals[i].second
|
||
// << ", it_tx->second.m_spent_flags.size()=" << it_tx->second.m_spent_flags.size()
|
||
// << ", it_tx->second.tx.vin.size()=" << it_tx->second.tx.vin.size());
|
||
// }
|
||
// used = it_tx->second.m_spent_flags[vals[i].second];
|
||
//
|
||
// }
|
||
// if (!used)
|
||
// ++unused_count;
|
||
// }
|
||
// ss << "\t total: " << total_count << "\t unused: " << unused_count << ENDL;
|
||
// }
|
||
// }
|
||
// LOG_PRINT_L0("OUTS: " << ENDL << ss.str());
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::print_blockchain_outs(const std::string& file) const
|
||
{
|
||
LOG_ERROR("NOT IMPLEMENTED YET");
|
||
// std::stringstream ss;
|
||
// CRITICAL_REGION_LOCAL(m_blockchain_lock);
|
||
// BOOST_FOREACH(const outputs_container::value_type& v, m_db_outputs)
|
||
// {
|
||
// const std::vector<std::pair<crypto::hash, size_t> >& vals = v.second;
|
||
// if(vals.size())
|
||
// {
|
||
// ss << "amount: " << print_money(v.first) << ENDL;
|
||
// for (size_t i = 0; i != vals.size(); i++)
|
||
// {
|
||
// bool used = false;
|
||
// auto it_tx = m_db_transactions.find(vals[i].first);
|
||
// if (it_tx == m_db_transactions.end())
|
||
// {
|
||
// LOG_ERROR("Tx with id not found " << vals[i].first);
|
||
// }
|
||
// else
|
||
// {
|
||
// if (vals[i].second >= it_tx->second.m_spent_flags.size())
|
||
// {
|
||
// LOG_ERROR("Tx with id " << vals[i].first << " in global index have wrong entry in global index, offset in tx = " << vals[i].second
|
||
// << ", it_tx->second.m_spent_flags.size()=" << it_tx->second.m_spent_flags.size()
|
||
// << ", it_tx->second.tx.vin.size()=" << it_tx->second.tx.vin.size());
|
||
// }
|
||
// used = it_tx->second.m_spent_flags[vals[i].second];
|
||
// }
|
||
//
|
||
// ss << "\t" << vals[i].first << ": " << vals[i].second << ",used:" << used << ENDL;
|
||
// }
|
||
// }
|
||
// }
|
||
// if(file_io_utils::save_string_to_file(file, ss.str()))
|
||
// {
|
||
// LOG_PRINT_L0("Current outputs index writen to file: " << file);
|
||
// }else
|
||
// {
|
||
// LOG_PRINT_L0("Failed to write current outputs index to file: " << file);
|
||
// }
|
||
}
|
||
//------------------------------------------------------------------
|
||
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, NOTIFY_RESPONSE_CHAIN_ENTRY::request& resp)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if(!find_blockchain_supplement(qblock_ids, resp.start_height))
|
||
return false;
|
||
|
||
resp.total_height = get_current_blockchain_size();
|
||
size_t count = 0;
|
||
|
||
block_context_info* pprevinfo = nullptr;
|
||
size_t i = 0;
|
||
for (i = resp.start_height; i != m_db_blocks.size() && count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT; i++, count++)
|
||
{
|
||
resp.m_block_ids.push_back(block_context_info());
|
||
|
||
if (pprevinfo)
|
||
pprevinfo->h = m_db_blocks[i]->bl.prev_id;
|
||
resp.m_block_ids.back().cumul_size = m_db_blocks[i]->block_cumulative_size;
|
||
pprevinfo = &resp.m_block_ids.back();
|
||
}
|
||
if (pprevinfo)
|
||
pprevinfo->h = get_block_hash(m_db_blocks[--i]->bl);
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, std::list<std::pair<block, std::list<transaction> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
blocks_direct_container blocks_direct;
|
||
if (!find_blockchain_supplement(qblock_ids, blocks_direct, total_height, start_height, max_count))
|
||
return false;
|
||
|
||
for (auto& bd : blocks_direct)
|
||
{
|
||
blocks.push_back(std::pair<block, std::list<transaction> >());
|
||
blocks.back().first = bd.first->bl;
|
||
for (auto& tx_ptr : bd.second)
|
||
{
|
||
blocks.back().second.push_back(tx_ptr->tx);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::find_blockchain_supplement(const std::list<crypto::hash>& qblock_ids, blocks_direct_container& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if (!find_blockchain_supplement(qblock_ids, start_height))
|
||
return false;
|
||
|
||
total_height = get_current_blockchain_size();
|
||
size_t count = 0;
|
||
for (size_t i = start_height; i != m_db_blocks.size() && count < max_count; i++, count++)
|
||
{
|
||
blocks.resize(blocks.size() + 1);
|
||
blocks.back().first = m_db_blocks[i];
|
||
std::list<crypto::hash> mis;
|
||
get_transactions_direct(m_db_blocks[i]->bl.tx_hashes, blocks.back().second, mis);
|
||
CHECK_AND_ASSERT_MES(!mis.size(), false, "internal error, block " << get_block_hash(m_db_blocks[i]->bl) << " [" << i << "] contains missing transactions: " << mis);
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::add_block_as_invalid(const block& bl, const crypto::hash& h)
|
||
{
|
||
block_extended_info bei = AUTO_VAL_INIT(bei);
|
||
bei.bl = bl;
|
||
return add_block_as_invalid(bei, h);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::add_block_as_invalid(const block_extended_info& bei, const crypto::hash& h)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
CRITICAL_REGION_LOCAL1(m_invalid_blocks_lock);
|
||
auto i_res = m_invalid_blocks.insert(std::map<crypto::hash, block_extended_info>::value_type(h, bei));
|
||
CHECK_AND_ASSERT_MES(i_res.second, false, "at insertion invalid by tx returned status existed");
|
||
LOG_PRINT_L0("BLOCK ADDED AS INVALID: " << h << ENDL << ", prev_id=" << bei.bl.prev_id << ", m_invalid_blocks count=" << m_invalid_blocks.size());
|
||
CHECK_AND_ASSERT_MES(validate_blockchain_prev_links(), false, "EPIC FAIL!");
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::inspect_blocks_index()const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
LOG_PRINT_L0("Started block index inspecting....");
|
||
m_db_blocks_index.enumerate_items([&](uint64_t count, const crypto::hash& id, uint64_t index)
|
||
{
|
||
CHECK_AND_ASSERT_MES(index < m_db_blocks.size(), true, "invalid index " << index << "(m_db_blocks.size()=" << m_db_blocks.size() << ") for id " << id << " ");
|
||
crypto::hash calculated_id = get_block_hash(m_db_blocks[index]->bl);
|
||
CHECK_AND_ASSERT_MES(id == calculated_id, true, "ID MISSMATCH ON INDEX " << index << ENDL
|
||
<< "m_db_blocks_index keeps: " << id << ENDL
|
||
<< "referenced to block with id " << calculated_id
|
||
);
|
||
return true;
|
||
});
|
||
LOG_PRINT_L0("Block index inspecting finished");
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::have_block(const crypto::hash& id)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
if(m_db_blocks_index.find(id))
|
||
return true;
|
||
{
|
||
CRITICAL_REGION_LOCAL1(m_alternative_chains_lock);
|
||
if (m_alternative_chains.count(id))
|
||
return true;
|
||
|
||
}
|
||
/*if(m_orphaned_blocks.get<by_id>().count(id))
|
||
return true;*/
|
||
|
||
/*if(m_orphaned_by_tx.count(id))
|
||
return true;*/
|
||
{
|
||
CRITICAL_REGION_LOCAL1(m_invalid_blocks_lock);
|
||
if (m_invalid_blocks.count(id))
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::handle_block_to_main_chain(const block& bl, block_verification_context& bvc)
|
||
{
|
||
crypto::hash id = get_block_hash(bl);
|
||
return handle_block_to_main_chain(bl, id, bvc);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::push_transaction_to_global_outs_index(const transaction& tx, const crypto::hash& tx_id, std::vector<uint64_t>& global_indexes)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
size_t i = 0;
|
||
BOOST_FOREACH(const auto& ot, tx.vout)
|
||
{
|
||
if (ot.target.type() == typeid(txout_to_key))
|
||
{
|
||
m_db_outputs.push_back_item(ot.amount, global_output_entry::construct(tx_id, i));
|
||
global_indexes.push_back(m_db_outputs.get_item_size(ot.amount) - 1);
|
||
}
|
||
else if (ot.target.type() == typeid(txout_multisig))
|
||
{
|
||
|
||
crypto::hash multisig_out_id = get_multisig_out_id(tx, i);
|
||
CHECK_AND_ASSERT_MES(multisig_out_id != null_hash, false, "internal error during handling get_multisig_out_id() with tx id " << tx_id);
|
||
CHECK_AND_ASSERT_MES(!m_db_multisig_outs.find(multisig_out_id), false, "Internal error: already have multisig_out_id " << multisig_out_id << "in multisig outs index");
|
||
m_db_multisig_outs.set(multisig_out_id, ms_output_entry::construct(tx_id, i));
|
||
global_indexes.push_back(0); // just stub to make other code easier
|
||
}
|
||
|
||
++i;
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
size_t blockchain_storage::get_total_transactions()const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
return m_db_transactions.size();
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_outs(uint64_t amount, std::list<crypto::public_key>& pkeys)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
uint64_t sz = m_db_outputs.get_item_size(amount);
|
||
|
||
if (!sz)
|
||
return true;
|
||
|
||
for (uint64_t i = 0; i != sz; i++)
|
||
{
|
||
auto out_entry_ptr = m_db_outputs.get_subitem(amount, i);
|
||
|
||
auto tx_ptr = m_db_transactions.find(out_entry_ptr->tx_id);
|
||
CHECK_AND_ASSERT_MES(tx_ptr, false, "transactions outs global index consistency broken: can't find tx " << out_entry_ptr->tx_id << " in DB, for amount: " << amount << ", gindex: " << i);
|
||
CHECK_AND_ASSERT_MES(tx_ptr->tx.vout.size() > out_entry_ptr->out_no, false, "transactions outs global index consistency broken: index in tx_outx == " << out_entry_ptr->out_no << " is greather than tx.vout size == " << tx_ptr->tx.vout.size() << ", for amount: " << amount << ", gindex: " << i);
|
||
CHECK_AND_ASSERT_MES(tx_ptr->tx.vout[out_entry_ptr->out_no].target.type() == typeid(txout_to_key), false, "transactions outs global index consistency broken: out #" << out_entry_ptr->out_no << " in tx " << out_entry_ptr->tx_id << " has wrong type, for amount: " << amount << ", gindex: " << i);
|
||
pkeys.push_back(boost::get<txout_to_key>(tx_ptr->tx.vout[out_entry_ptr->out_no].target).key);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::pop_transaction_from_global_index(const transaction& tx, const crypto::hash& tx_id)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
size_t i = tx.vout.size()-1;
|
||
BOOST_REVERSE_FOREACH(const auto& ot, tx.vout)
|
||
{
|
||
if (ot.target.type() == typeid(txout_to_key))
|
||
{
|
||
uint64_t sz= m_db_outputs.get_item_size(ot.amount);
|
||
CHECK_AND_ASSERT_MES(sz, false, "transactions outs global index: empty index for amount: " << ot.amount);
|
||
auto back_item = m_db_outputs.get_subitem(ot.amount, sz - 1);
|
||
CHECK_AND_ASSERT_MES(back_item->tx_id == tx_id, false, "transactions outs global index consistency broken: tx id missmatch");
|
||
CHECK_AND_ASSERT_MES(back_item->out_no == i, false, "transactions outs global index consistency broken: in transaction index missmatch");
|
||
m_db_outputs.pop_back_item(ot.amount);
|
||
//if (!it->second.size())
|
||
// m_db_outputs.erase(it);
|
||
}
|
||
else if (ot.target.type() == typeid(txout_multisig))
|
||
{
|
||
crypto::hash multisig_out_id = get_multisig_out_id(tx, i);
|
||
CHECK_AND_ASSERT_MES(multisig_out_id != null_hash, false, "internal error during handling get_multisig_out_id() with tx id " << tx_id);
|
||
bool res = m_db_multisig_outs.erase_validate(multisig_out_id);
|
||
CHECK_AND_ASSERT_MES(res, false, "Internal error: multisig out not found, multisig_out_id " << multisig_out_id << "in multisig outs index");
|
||
}
|
||
--i;
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::unprocess_blockchain_tx_extra(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())
|
||
{
|
||
r = pop_alias_info(ei.m_alias);
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to pop_alias_info");
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_alias_info(const std::string& alias, extra_alias_entry_base& info)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
auto al_ptr = m_db_aliases.find(alias);
|
||
if (al_ptr)
|
||
{
|
||
if (al_ptr->size())
|
||
{
|
||
info = al_ptr->back();
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::get_aliases_count() const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
return m_db_aliases.size();
|
||
}
|
||
//------------------------------------------------------------------
|
||
std::string blockchain_storage::get_alias_by_address(const account_public_address& addr)const
|
||
{
|
||
auto alias_ptr = m_db_addr_to_alias.find(addr);
|
||
if (alias_ptr && alias_ptr->size())
|
||
{
|
||
return *(alias_ptr->begin());
|
||
}
|
||
|
||
return "";
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::pop_alias_info(const extra_alias_entry& ai)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
CHECK_AND_ASSERT_MES(ai.m_alias.size(), false, "empty name in pop_alias_info");
|
||
auto alias_history_ptr = m_db_aliases.find(ai.m_alias);
|
||
CHECK_AND_ASSERT_MES(alias_history_ptr && alias_history_ptr->size(), false, "empty name list in pop_alias_info");
|
||
|
||
auto addr_to_alias_ptr = m_db_addr_to_alias.find(alias_history_ptr->back().m_address);
|
||
if (addr_to_alias_ptr)
|
||
{
|
||
//update db
|
||
address_to_aliases_container::t_value_type local_v = *addr_to_alias_ptr;
|
||
|
||
auto it_in_set = local_v.find(ai.m_alias);
|
||
CHECK_AND_ASSERT_MES(it_in_set != local_v.end(), false, "it_in_set != it->second.end() validation failed");
|
||
|
||
local_v.erase(it_in_set);
|
||
if (!local_v.size())
|
||
{
|
||
//delete the whole record from db
|
||
m_db_addr_to_alias.erase(alias_history_ptr->back().m_address);
|
||
}
|
||
else
|
||
{
|
||
//update db
|
||
m_db_addr_to_alias.set(alias_history_ptr->back().m_address, local_v);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
LOG_ERROR("In m_addr_to_alias not found " << get_account_address_as_str(alias_history_ptr->back().m_address));
|
||
}
|
||
|
||
aliases_container::t_value_type local_alias_hist = *alias_history_ptr;
|
||
local_alias_hist.pop_back();
|
||
if(local_alias_hist.size())
|
||
m_db_aliases.set(ai.m_alias, local_alias_hist);
|
||
else
|
||
m_db_aliases.erase(ai.m_alias);
|
||
|
||
if (local_alias_hist.size())
|
||
{
|
||
address_to_aliases_container::t_value_type local_copy = AUTO_VAL_INIT(local_copy);
|
||
auto set_ptr = m_db_addr_to_alias.get(local_alias_hist.back().m_address);
|
||
if (set_ptr)
|
||
local_copy = *set_ptr;
|
||
|
||
local_copy.insert(ai.m_alias);
|
||
m_db_addr_to_alias.set(local_alias_hist.back().m_address, local_copy);
|
||
}
|
||
|
||
LOG_PRINT_MAGENTA("[ALIAS_UNREGISTERED]: " << ai.m_alias << ": " << get_account_address_as_str(ai.m_address) << " -> " << (!local_alias_hist.empty() ? get_account_address_as_str(local_alias_hist.back().m_address) : "(available)"), LOG_LEVEL_1);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::put_alias_info(const transaction & tx, extra_alias_entry & ai)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
CHECK_AND_ASSERT_MES(ai.m_alias.size(), false, "empty name in put_alias_info");
|
||
aliases_container::t_value_type local_alias_history = AUTO_VAL_INIT(local_alias_history);
|
||
auto alias_history_ptr_ = m_db_aliases.get(ai.m_alias);
|
||
if (alias_history_ptr_)
|
||
local_alias_history = *alias_history_ptr_;
|
||
|
||
if (!local_alias_history.size())
|
||
{
|
||
|
||
//update alias entry in db
|
||
local_alias_history.push_back(ai);
|
||
m_db_aliases.set(ai.m_alias, local_alias_history);
|
||
|
||
//update addr-to-alias db entry
|
||
address_to_aliases_container::t_value_type addr_to_alias_local = AUTO_VAL_INIT(addr_to_alias_local);
|
||
auto addr_to_alias_ptr_ = m_db_addr_to_alias.get(local_alias_history.back().m_address);
|
||
if (addr_to_alias_ptr_)
|
||
addr_to_alias_local = *addr_to_alias_ptr_;
|
||
|
||
addr_to_alias_local.insert(ai.m_alias);
|
||
m_db_addr_to_alias.set(local_alias_history.back().m_address, addr_to_alias_local);
|
||
|
||
//@@ remove get_tx_fee_median();
|
||
LOG_PRINT_MAGENTA("[ALIAS_REGISTERED]: " << ai.m_alias << ": " << get_account_address_as_str(ai.m_address) << ", fee median: " << get_tx_fee_median(), LOG_LEVEL_1);
|
||
rise_core_event(CORE_EVENT_ADD_ALIAS, alias_info_to_rpc_alias_info(ai));
|
||
}else
|
||
{
|
||
//update procedure
|
||
CHECK_AND_ASSERT_MES(ai.m_sign.size() == 1, false, "alias " << ai.m_alias << " can't be update, wrong ai.m_sign.size() count: " << ai.m_sign.size());
|
||
//std::string signed_buff;
|
||
//make_tx_extra_alias_entry(signed_buff, ai, true);
|
||
std::string old_address = currency::get_account_address_as_str(local_alias_history.back().m_address);
|
||
bool r = crypto::check_signature(get_sign_buff_hash_for_alias_update(ai), local_alias_history.back().m_address.m_spend_public_key, ai.m_sign.back());
|
||
CHECK_AND_ASSERT_MES(r, false, "Failed to check signature, alias update failed." << ENDL
|
||
<< "alias: " << ai.m_alias << ENDL
|
||
<< "signed_buff_hash: " << get_sign_buff_hash_for_alias_update(ai) << ENDL
|
||
<< "public key: " << local_alias_history.back().m_address.m_spend_public_key << ENDL
|
||
<< "new_address: " << get_account_address_as_str(ai.m_address) << ENDL
|
||
<< "signature: " << epee::string_tools::pod_to_hex(ai.m_sign) << ENDL
|
||
<< "alias_history.size() = " << local_alias_history.size());
|
||
|
||
//update adr-to-alias db
|
||
auto addr_to_alias_ptr_ = m_db_addr_to_alias.find(local_alias_history.back().m_address);
|
||
if (addr_to_alias_ptr_)
|
||
{
|
||
address_to_aliases_container::t_value_type addr_to_alias_local = *addr_to_alias_ptr_;
|
||
auto it_in_set = addr_to_alias_local.find(ai.m_alias);
|
||
if (it_in_set == addr_to_alias_local.end())
|
||
{
|
||
LOG_ERROR("it_in_set == it->second.end()");
|
||
}
|
||
else
|
||
{
|
||
addr_to_alias_local.erase(it_in_set);
|
||
}
|
||
|
||
if (!addr_to_alias_local.size())
|
||
m_db_addr_to_alias.erase(local_alias_history.back().m_address);
|
||
else
|
||
m_db_addr_to_alias.set(local_alias_history.back().m_address, addr_to_alias_local);
|
||
}
|
||
else
|
||
{
|
||
LOG_ERROR("Wrong m_addr_to_alias state: address not found " << get_account_address_as_str(local_alias_history.back().m_address));
|
||
}
|
||
|
||
//update alias db
|
||
local_alias_history.push_back(ai);
|
||
m_db_aliases.set(ai.m_alias, local_alias_history);
|
||
|
||
//update addr_to_alias db
|
||
address_to_aliases_container::t_value_type addr_to_alias_local2 = AUTO_VAL_INIT(addr_to_alias_local2);
|
||
auto addr_to_alias_ptr_2 = m_db_addr_to_alias.get(local_alias_history.back().m_address);
|
||
if (addr_to_alias_ptr_2)
|
||
addr_to_alias_local2 = *addr_to_alias_ptr_2;
|
||
|
||
addr_to_alias_local2.insert(ai.m_alias);
|
||
m_db_addr_to_alias.set(local_alias_history.back().m_address, addr_to_alias_local2);
|
||
|
||
LOG_PRINT_MAGENTA("[ALIAS_UPDATED]: " << ai.m_alias << ": from: " << old_address << " to " << get_account_address_as_str(ai.m_address), LOG_LEVEL_1);
|
||
rise_core_event(CORE_EVENT_UPDATE_ALIAS, alias_info_to_rpc_update_alias_info(ai, old_address));
|
||
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::set_event_handler(i_core_event_handler* event_handler) const
|
||
{
|
||
if (event_handler == nullptr)
|
||
{
|
||
m_event_handler = &m_event_handler_stub;
|
||
m_services_mgr.set_event_handler(nullptr);
|
||
}
|
||
else
|
||
{
|
||
m_event_handler = event_handler;
|
||
m_services_mgr.set_event_handler(event_handler);
|
||
}
|
||
|
||
}
|
||
//------------------------------------------------------------------
|
||
i_core_event_handler* blockchain_storage::get_event_handler() const
|
||
{
|
||
return m_event_handler;
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::validate_alias_reward(const transaction& tx, const std::string& alias) const
|
||
{
|
||
|
||
//validate alias coast
|
||
uint64_t fee_for_alias = get_alias_coast(alias);
|
||
|
||
//validate the price had been paid
|
||
uint64_t found_alias_reward = get_amount_for_zero_pubkeys(tx);
|
||
|
||
//@#@
|
||
//work around for net 68's generation
|
||
#if CURRENCY_FORMATION_VERSION == 68
|
||
if (alias == "bhrfrrrtret" && get_transaction_hash(tx) == epee::string_tools::parse_tpod_from_hex_string<crypto::hash>("760b85546678d2235a1843e18d8a016a2e4d9b8273cc4d7c09bebff1f6fa7eaf") )
|
||
return true;
|
||
if (alias == "test-420" && get_transaction_hash(tx) == epee::string_tools::parse_tpod_from_hex_string<crypto::hash>("10f8a2539b2551bd0919bf7e3b1dfbae7553eca63e58cd2264ae60f90030edf8"))
|
||
return true;
|
||
#endif
|
||
|
||
CHECK_AND_ASSERT_MES(found_alias_reward >= fee_for_alias, false, "registration of alias '"
|
||
<< alias << "' goes with a reward of " << print_money(found_alias_reward) << " which is less than expected: " << print_money(fee_for_alias)
|
||
<<"(fee median: " << get_tx_fee_median() << ")"
|
||
<< ", tx: " << get_transaction_hash(tx));
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::prevalidate_alias_info(const transaction& tx, extra_alias_entry& eae)
|
||
{
|
||
|
||
bool r = validate_alias_name(eae.m_alias);
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to validate alias name!");
|
||
bool already_have_alias = false;
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
auto existing_ptr = m_db_aliases.find(eae.m_alias);
|
||
if (existing_ptr && existing_ptr->size())
|
||
{
|
||
already_have_alias = true;
|
||
}
|
||
|
||
}
|
||
//auto alias_history_ptr_ = m_db_aliases.get(eae.m_alias);
|
||
if (!already_have_alias)
|
||
{
|
||
bool r = validate_alias_reward(tx, eae.m_alias);
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to validate_alias_reward");
|
||
|
||
if (eae.m_alias.size() < ALIAS_MINIMUM_PUBLIC_SHORT_NAME_ALLOWED)
|
||
{
|
||
//short alias name, this aliases should be issued only by specific authority
|
||
CHECK_AND_ASSERT_MES(eae.m_sign.size() == 1, false, "alias " << eae.m_alias << " can't be update, wrong ai.m_sign.size() count: " << eae.m_sign.size());
|
||
|
||
bool r = crypto::check_signature(get_sign_buff_hash_for_alias_update(eae), m_core_runtime_config.alias_validation_pubkey, eae.m_sign.back());
|
||
CHECK_AND_ASSERT_MES(r, false, "Failed to check signature, short alias registration failed." << ENDL
|
||
<< "alias: " << eae.m_alias << ENDL
|
||
<< "signed_buff_hash: " << get_sign_buff_hash_for_alias_update(eae) << ENDL
|
||
<< "public key: " << m_core_runtime_config.alias_validation_pubkey << ENDL
|
||
<< "new_address: " << get_account_address_as_str(eae.m_address) << ENDL
|
||
<< "signature: " << epee::string_tools::pod_to_hex(eae.m_sign));
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::process_blockchain_tx_extra(const transaction& tx)
|
||
{
|
||
//check transaction extra
|
||
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");
|
||
if (ei.m_alias.m_alias.size())
|
||
{
|
||
r = prevalidate_alias_info(tx, ei.m_alias);
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to prevalidate_alias_info");
|
||
|
||
r = put_alias_info(tx, ei.m_alias);
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to put_alias_info");
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_outs_index_stat(outs_index_stat& outs_stat) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
outs_stat.amount_0_001 = m_db_outputs.get_item_size(COIN / 1000);
|
||
outs_stat.amount_0_01 = m_db_outputs.get_item_size(COIN / 100);
|
||
outs_stat.amount_0_1 = m_db_outputs.get_item_size(COIN / 10);
|
||
outs_stat.amount_1 = m_db_outputs.get_item_size(COIN);
|
||
outs_stat.amount_10 = m_db_outputs.get_item_size(COIN * 10);
|
||
outs_stat.amount_100 = m_db_outputs.get_item_size(COIN * 100);
|
||
outs_stat.amount_1000 = m_db_outputs.get_item_size(COIN * 1000);
|
||
outs_stat.amount_10000 = m_db_outputs.get_item_size(COIN * 10000);
|
||
outs_stat.amount_100000 = m_db_outputs.get_item_size(COIN * 100000);
|
||
outs_stat.amount_1000000 = m_db_outputs.get_item_size(COIN * 1000000);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::process_blockchain_tx_attachments(const transaction& tx, uint64_t h, const crypto::hash& bl_id, uint64_t timestamp)
|
||
{
|
||
//check transaction extra
|
||
uint64_t count = 0;
|
||
|
||
for (const auto& at : tx.attachment)
|
||
{
|
||
if (at.type() == typeid(tx_service_attachment))
|
||
{
|
||
m_services_mgr.handle_entry_push(boost::get<tx_service_attachment>(at), count, tx, h, bl_id, timestamp); //handle service
|
||
++count;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::get_tx_fee_median() const
|
||
{
|
||
uint64_t h = m_db_blocks.size();
|
||
if (m_current_fee_median_effective_index != get_tx_fee_median_effective_index(h))
|
||
{
|
||
m_current_fee_median = tx_fee_median_for_height(h);
|
||
m_current_fee_median_effective_index = get_tx_fee_median_effective_index(h);
|
||
}
|
||
|
||
if (!m_current_fee_median)
|
||
m_current_fee_median = ALIAS_VERY_INITAL_COAST;
|
||
return m_current_fee_median;
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::get_alias_coast(const std::string& alias) const
|
||
{
|
||
uint64_t median_fee = get_tx_fee_median();
|
||
//CHECK_AND_ASSERT_MES_NO_RET(median_fee, "can't calculate median");
|
||
|
||
if (!median_fee)
|
||
median_fee = ALIAS_VERY_INITAL_COAST;
|
||
|
||
|
||
return get_alias_coast_from_fee(alias, median_fee);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::unprocess_blockchain_tx_attachments(const transaction& tx, uint64_t h, uint64_t timestamp)
|
||
{
|
||
|
||
size_t cnt_serv_attach = get_service_attachments_count_in_tx(tx);
|
||
if (cnt_serv_attach == 0)
|
||
return true;
|
||
|
||
--cnt_serv_attach;
|
||
for (auto it = tx.attachment.rbegin(); it != tx.attachment.rend(); it++)
|
||
{
|
||
auto& at = *it;
|
||
if (at.type() == typeid(tx_service_attachment))
|
||
{
|
||
m_services_mgr.handle_entry_pop(boost::get<tx_service_attachment>(at), cnt_serv_attach, tx, h, timestamp);
|
||
--cnt_serv_attach;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::validate_tx_service_attachmens_in_services(const tx_service_attachment& a, size_t i, const transaction& tx) const
|
||
{
|
||
return m_services_mgr.validate_entry(a, i, tx);
|
||
}
|
||
//------------------------------------------------------------------
|
||
namespace currency
|
||
{
|
||
struct add_transaction_input_visitor : public boost::static_visitor<bool>
|
||
{
|
||
blockchain_storage& m_bcs;
|
||
blockchain_storage::key_images_container& m_db_spent_keys;
|
||
const crypto::hash& m_tx_id;
|
||
const crypto::hash& m_bl_id;
|
||
const uint64_t m_bl_height;
|
||
add_transaction_input_visitor(blockchain_storage& bcs, blockchain_storage::key_images_container& m_db_spent_keys, const crypto::hash& tx_id, const crypto::hash& bl_id, const uint64_t bl_height) :
|
||
m_bcs(bcs),
|
||
m_db_spent_keys(m_db_spent_keys),
|
||
m_tx_id(tx_id),
|
||
m_bl_id(bl_id),
|
||
m_bl_height(bl_height)
|
||
{}
|
||
bool operator()(const txin_to_key& in) const
|
||
{
|
||
const crypto::key_image& ki = in.k_image;
|
||
|
||
auto ki_ptr = m_db_spent_keys.get(ki);
|
||
if (ki_ptr)
|
||
{
|
||
//double spend detected
|
||
LOG_PRINT_RED_L0("tx with id: " << m_tx_id << " in block id: " << m_bl_id << " have input marked as spent with key image: " << ki << ", block declined");
|
||
return false;
|
||
}
|
||
m_db_spent_keys.set(ki, m_bl_height);
|
||
|
||
if (in.key_offsets.size() == 1)
|
||
{
|
||
//direct spend detected
|
||
if (!m_bcs.update_spent_tx_flags_for_input(in.amount, in.key_offsets[0], true))
|
||
{
|
||
//internal error
|
||
LOG_PRINT_RED_L0("Failed to update_spent_tx_flags_for_input");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
bool operator()(const txin_gen& in) const { return true; }
|
||
bool operator()(const txin_multisig& in) const
|
||
{
|
||
//mark out as spent
|
||
if (!m_bcs.update_spent_tx_flags_for_input(in.multisig_out_id, m_bl_height))
|
||
{
|
||
//internal error
|
||
LOG_PRINT_RED_L0("Failed to update_spent_tx_flags_for_input");
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
};
|
||
}
|
||
|
||
|
||
bool blockchain_storage::add_transaction_from_block(const transaction& tx, const crypto::hash& tx_id, const crypto::hash& bl_id, uint64_t bl_height, uint64_t timestamp)
|
||
{
|
||
bool need_to_profile = !is_coinbase(tx);
|
||
TIME_MEASURE_START_PD(tx_append_rl_wait);
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
TIME_MEASURE_FINISH_PD_COND(need_to_profile, tx_append_rl_wait);
|
||
|
||
TIME_MEASURE_START_PD(tx_append_is_expired);
|
||
CHECK_AND_ASSERT_MES(!is_tx_expired(tx), false, "Transaction can't be added to the blockchain since it's already expired. tx expiration time: " << get_tx_expiration_time(tx) << ", blockchain median time: " << get_tx_expiration_median());
|
||
TIME_MEASURE_FINISH_PD_COND(need_to_profile, tx_append_is_expired);
|
||
|
||
CHECK_AND_ASSERT_MES(validate_tx_for_hardfork_specific_terms(tx, tx_id, bl_height), false, "tx " << tx_id << ": hardfork-specific validation failed");
|
||
|
||
TIME_MEASURE_START_PD(tx_process_extra);
|
||
bool r = process_blockchain_tx_extra(tx);
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to process_blockchain_tx_extra");
|
||
TIME_MEASURE_FINISH_PD_COND(need_to_profile, tx_process_extra);
|
||
|
||
TIME_MEASURE_START_PD(tx_process_attachment);
|
||
process_blockchain_tx_attachments(tx, bl_height, bl_id, timestamp);
|
||
TIME_MEASURE_FINISH_PD_COND(need_to_profile, tx_process_attachment);
|
||
|
||
|
||
TIME_MEASURE_START_PD(tx_process_inputs);
|
||
for(const txin_v& in : tx.vin)
|
||
{
|
||
if(!boost::apply_visitor(add_transaction_input_visitor(*this, m_db_spent_keys, tx_id, bl_id, bl_height), in))
|
||
{
|
||
LOG_ERROR("critical internal error: add_transaction_input_visitor failed. but key_images should be already checked");
|
||
purge_transaction_keyimages_from_blockchain(tx, false);
|
||
bool r = unprocess_blockchain_tx_extra(tx);
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to unprocess_blockchain_tx_extra");
|
||
|
||
unprocess_blockchain_tx_attachments(tx, bl_height, timestamp);
|
||
|
||
return false;
|
||
}
|
||
}
|
||
TIME_MEASURE_FINISH_PD_COND(need_to_profile, tx_process_inputs);
|
||
|
||
//check if there is already transaction with this hash
|
||
TIME_MEASURE_START_PD(tx_check_exist);
|
||
auto tx_entry_ptr = m_db_transactions.get(tx_id);
|
||
if (tx_entry_ptr)
|
||
{
|
||
LOG_ERROR("critical internal error: tx with id: " << tx_id << " in block id: " << bl_id << " already in blockchain");
|
||
purge_transaction_keyimages_from_blockchain(tx, true);
|
||
bool r = unprocess_blockchain_tx_extra(tx);
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to unprocess_blockchain_tx_extra");
|
||
|
||
unprocess_blockchain_tx_attachments(tx, bl_height, timestamp);
|
||
return false;
|
||
}
|
||
TIME_MEASURE_FINISH_PD_COND(need_to_profile, tx_check_exist);
|
||
TIME_MEASURE_START_PD(tx_push_global_index);
|
||
transaction_chain_entry ch_e;
|
||
ch_e.m_keeper_block_height = bl_height;
|
||
ch_e.m_spent_flags.resize(tx.vout.size(), false);
|
||
ch_e.tx = tx;
|
||
r = push_transaction_to_global_outs_index(tx, tx_id, ch_e.m_global_output_indexes);
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to return push_transaction_to_global_outs_index tx id " << tx_id);
|
||
TIME_MEASURE_FINISH_PD_COND(need_to_profile, tx_push_global_index);
|
||
|
||
//store everything to db
|
||
TIME_MEASURE_START_PD(tx_store_db);
|
||
m_db_transactions.set(tx_id, ch_e);
|
||
TIME_MEASURE_FINISH_PD_COND(need_to_profile, tx_store_db);
|
||
|
||
TIME_MEASURE_START_PD(tx_print_log);
|
||
LOG_PRINT_L1("Added tx to blockchain: " << tx_id << " via block at " << bl_height << " id " << print16(bl_id)
|
||
<< ", ins: " << tx.vin.size() << ", outs: " << tx.vout.size() << ", outs sum: " << print_money_brief(get_outs_money_amount(tx)) << " (fee: " << (is_coinbase(tx) ? "0[coinbase]" : print_money_brief(get_tx_fee(tx))) << ")");
|
||
TIME_MEASURE_FINISH_PD_COND(need_to_profile, tx_print_log);
|
||
//@#@ del me
|
||
// LOG_PRINT_L0("APPEND_TX_TIME_INNER: " << m_performance_data.tx_append_rl_wait.get_last_val()
|
||
// << " | " << m_performance_data.tx_append_is_expired.get_last_val()
|
||
// << " | " << m_performance_data.tx_process_extra.get_last_val()
|
||
// << " | " << m_performance_data.tx_process_attachment.get_last_val()
|
||
// << " | " << m_performance_data.tx_process_inputs.get_last_val()
|
||
// << " | " << m_performance_data.tx_check_exist.get_last_val()
|
||
// << " | " << m_performance_data.tx_push_global_index.get_last_val()
|
||
// << " | " << m_performance_data.tx_store_db.get_last_val()
|
||
// << " | " << m_performance_data.tx_print_log.get_last_val()
|
||
// );
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<uint64_t>& indexs)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
auto tx_ptr = m_db_transactions.find(tx_id);
|
||
if (!tx_ptr)
|
||
{
|
||
LOG_PRINT_RED_L0("warning: get_tx_outputs_gindexs failed to find transaction with id = " << tx_id);
|
||
return false;
|
||
}
|
||
|
||
CHECK_AND_ASSERT_MES(tx_ptr->m_global_output_indexes.size(), false, "internal error: global indexes for transaction " << tx_id << " is empty");
|
||
indexs = tx_ptr->m_global_output_indexes;
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::check_tx_inputs(const transaction& tx, const crypto::hash& tx_prefix_hash, uint64_t& max_used_block_height, crypto::hash& max_used_block_id) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
bool res = check_tx_inputs(tx, tx_prefix_hash, max_used_block_height);
|
||
if(!res) return false;
|
||
CHECK_AND_ASSERT_MES(max_used_block_height < m_db_blocks.size(), false, "internal error: max used block index=" << max_used_block_height << " is not less then blockchain size = " << m_db_blocks.size());
|
||
get_block_hash(m_db_blocks[max_used_block_height]->bl, max_used_block_id);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
#define PERIOD_DISABLED 0xffffffffffffffffLL
|
||
|
||
bool blockchain_storage::rebuild_tx_fee_medians()
|
||
{
|
||
uint64_t sz = m_db_blocks.size();
|
||
m_db.begin_transaction();
|
||
LOG_PRINT_L0("Started reinitialization of median fee...");
|
||
math_helper::once_a_time_seconds<10> log_idle;
|
||
|
||
epee::misc_utils::median_helper<uint64_t, uint64_t> blocks_median;
|
||
for (uint64_t i = 0; i != sz; i++)
|
||
{
|
||
log_idle.do_call([&]() {std::cout << "block " << i << " of " << sz << std::endl; return true; });
|
||
|
||
auto bptr = m_db_blocks[i];
|
||
block_extended_info new_bei = *bptr;
|
||
//assign effective median
|
||
new_bei.effective_tx_fee_median = blocks_median.get_median();
|
||
|
||
//calculate current median for this particular block
|
||
std::vector<uint64_t> fees;
|
||
for (auto& h: new_bei.bl.tx_hashes)
|
||
{
|
||
auto txptr = get_tx_chain_entry(h);
|
||
CHECK_AND_ASSERT_MES(txptr, false, "failed to find tx id " << h << " from block " << i);
|
||
fees.push_back(get_tx_fee(txptr->tx));
|
||
}
|
||
new_bei.this_block_tx_fee_median = epee::misc_utils::median(fees);
|
||
m_db_blocks.set(i, new_bei);
|
||
|
||
|
||
//prepare median helper for next block
|
||
if(new_bei.this_block_tx_fee_median)
|
||
blocks_median.push_item(new_bei.this_block_tx_fee_median, i);
|
||
|
||
//create callbacks
|
||
|
||
bool is_pos_allowed_l = i >= m_core_runtime_config.pos_minimum_heigh;
|
||
uint64_t period = is_pos_allowed_l ? ALIAS_COAST_PERIOD : ALIAS_COAST_PERIOD / 2;
|
||
if (period >= i+1)
|
||
continue;
|
||
uint64_t starter_block_index = i+1 - period;
|
||
|
||
uint64_t purge_recent_period = is_pos_allowed_l ? ALIAS_COAST_RECENT_PERIOD : ALIAS_COAST_RECENT_PERIOD / 2;
|
||
uint64_t purge_recent_block_index = 0;
|
||
if (purge_recent_period >= i + 1)
|
||
purge_recent_block_index = PERIOD_DISABLED;
|
||
else
|
||
purge_recent_block_index = i + 1 - purge_recent_period;
|
||
|
||
|
||
auto cb = [&](uint64_t fee, uint64_t height)
|
||
{
|
||
if (height >= starter_block_index)
|
||
return true;
|
||
return false;
|
||
};
|
||
|
||
auto cb_final_eraser = [&](uint64_t fee, uint64_t height)
|
||
{
|
||
if (purge_recent_block_index == PERIOD_DISABLED)
|
||
return true;
|
||
if (height >= purge_recent_block_index)
|
||
return true;
|
||
|
||
return false;
|
||
};
|
||
|
||
blocks_median.scan_items(cb, cb_final_eraser);
|
||
}
|
||
|
||
m_db.commit_transaction();
|
||
LOG_PRINT_L0("Reinitialization of median fee finished!")
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::get_tx_fee_median_effective_index(uint64_t h)const
|
||
{
|
||
if (h <= ALIAS_MEDIAN_RECALC_INTERWAL + 1)
|
||
return 0;
|
||
|
||
h -= 1; // for transactions of block that h%ALIAS_MEDIAN_RECALC_INTERWAL==0 we still handle fee median from previous day
|
||
return h - (h % ALIAS_MEDIAN_RECALC_INTERWAL);
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::tx_fee_median_for_height(uint64_t h)const
|
||
{
|
||
uint64_t effective_index = get_tx_fee_median_effective_index(h);
|
||
CHECK_AND_ASSERT_THROW_MES(effective_index < m_db_blocks.size(), "internal error: effective_index= " << effective_index << " while m_db_blocks.size()=" << m_db_blocks.size());
|
||
return m_db_blocks[effective_index]->effective_tx_fee_median;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::validate_all_aliases_for_new_median_mode()
|
||
{
|
||
LOG_PRINT_L0("Started reinitialization of median fee...");
|
||
math_helper::once_a_time_seconds<10> log_idle;
|
||
uint64_t sz = m_db_blocks.size();
|
||
for (uint64_t i = 0; i != sz; i++)
|
||
{
|
||
log_idle.do_call([&]() {std::cout << "block " << i << " of " << sz << std::endl; return true; });
|
||
|
||
auto bptr = m_db_blocks[i];
|
||
for (auto& tx_id : bptr->bl.tx_hashes)
|
||
{
|
||
auto tx_ptr = m_db_transactions.find(tx_id);
|
||
CHECK_AND_ASSERT_MES(tx_ptr, false, "Internal error: tx " << tx_id << " from block " << i << " not found");
|
||
tx_extra_info tei = AUTO_VAL_INIT(tei);
|
||
bool r = parse_and_validate_tx_extra(tx_ptr->tx, tei);
|
||
CHECK_AND_ASSERT_MES(r, false, "Internal error: tx " << tx_id << " from block " << i << " was failed to parse");
|
||
if (tei.m_alias.m_alias.size() && !tei.m_alias.m_sign.size())
|
||
{
|
||
//reward
|
||
//validate alias coast
|
||
uint64_t median_fee = tx_fee_median_for_height(i);
|
||
uint64_t fee_for_alias = get_alias_coast_from_fee(tei.m_alias.m_alias, median_fee);
|
||
|
||
//validate the price had been paid
|
||
uint64_t found_alias_reward = get_amount_for_zero_pubkeys(tx_ptr->tx);
|
||
if (found_alias_reward < fee_for_alias)
|
||
{
|
||
LOG_PRINT_RED_L0("[" << i << "]Found collision on alias: " << tei.m_alias.m_alias
|
||
<< ", expected fee: " << print_money(fee_for_alias) << "(median:" << print_money(median_fee) << ")"
|
||
<<" found reward: " << print_money(found_alias_reward) <<". tx_id: " << tx_id);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
LOG_PRINT_L0("Finished.");
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::print_tx_outputs_lookup(const crypto::hash& tx_id)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
auto tx_ptr = m_db_transactions.find(tx_id);
|
||
|
||
if (!tx_ptr)
|
||
{
|
||
LOG_PRINT_RED_L0("Tx " << tx_id << " not found");
|
||
return true;
|
||
}
|
||
|
||
//amount -> index -> [{tx_id, rind_count}]
|
||
std::map<uint64_t, std::map<uint64_t, std::list<std::pair<crypto::hash,uint64_t > > > > usage_stat;
|
||
std::stringstream strm_tx;
|
||
CHECK_AND_ASSERT_MES(tx_ptr->tx.vout.size() == tx_ptr->m_global_output_indexes.size(), false, "Internal error: output size missmatch");
|
||
for (uint64_t i = 0; i!= tx_ptr->tx.vout.size();i++)
|
||
{
|
||
strm_tx << "[" << i << "]: " << print_money(tx_ptr->tx.vout[i].amount) << ENDL;
|
||
if (tx_ptr->tx.vout[i].target.type() != typeid(currency::txout_to_key))
|
||
continue;
|
||
|
||
usage_stat[tx_ptr->tx.vout[i].amount][tx_ptr->m_global_output_indexes[i]];
|
||
|
||
}
|
||
|
||
LOG_PRINT_L0("Lookup in all transactions....");
|
||
for (uint64_t i = 0; i != m_db_blocks.size(); i++)
|
||
{
|
||
auto block_ptr = m_db_blocks[i];
|
||
for (auto block_tx_id : block_ptr->bl.tx_hashes)
|
||
{
|
||
auto block_tx_ptr = m_db_transactions.find(block_tx_id);
|
||
for (auto txi_in : block_tx_ptr->tx.vin)
|
||
{
|
||
if(txi_in.type() != typeid(currency::txin_to_key))
|
||
continue;
|
||
currency::txin_to_key& txi_in_tokey = boost::get<currency::txin_to_key>(txi_in);
|
||
uint64_t amount = txi_in_tokey.amount;
|
||
auto amount_it = usage_stat.find(amount);
|
||
if(amount_it == usage_stat.end())
|
||
continue;
|
||
|
||
for (txout_v& off : txi_in_tokey.key_offsets)
|
||
{
|
||
if(off.type() != typeid(uint64_t))
|
||
continue;
|
||
uint64_t index = boost::get<uint64_t>(off);
|
||
auto index_it = amount_it->second.find(index);
|
||
if(index_it == amount_it->second.end())
|
||
continue;
|
||
index_it->second.push_back(std::pair<crypto::hash, uint64_t>(block_tx_id, txi_in_tokey.key_offsets.size()));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
std::stringstream ss;
|
||
for (auto& amount : usage_stat)
|
||
{
|
||
for (auto& index : amount.second)
|
||
{
|
||
ss << "[" << print_money(amount.first) << ":" << index.first << "]" << ENDL ;
|
||
for (auto& list_entry : index.second)
|
||
{
|
||
ss << " " << list_entry.first << ": " << list_entry.second << ENDL;
|
||
}
|
||
}
|
||
}
|
||
|
||
LOG_PRINT_L0("Results: " << ENDL << strm_tx.str() << ENDL << ss.str());
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::have_tx_keyimges_as_spent(const transaction &tx) const
|
||
{
|
||
// check all tx's inputs for being already spent
|
||
for (const txin_v& in : tx.vin)
|
||
{
|
||
if (in.type() == typeid(txin_to_key))
|
||
{
|
||
if (have_tx_keyimg_as_spent(boost::get<const txin_to_key>(in).k_image))
|
||
{
|
||
return true;
|
||
}
|
||
|
||
}
|
||
else if (in.type() == typeid(txin_multisig))
|
||
{
|
||
if (is_multisig_output_spent(boost::get<const txin_multisig>(in).multisig_out_id))
|
||
return true;
|
||
}
|
||
else if (in.type() == typeid(txin_gen))
|
||
{
|
||
// skip txin_gen
|
||
}
|
||
else
|
||
{
|
||
LOG_ERROR("Unexpected input type: " << in.type().name());
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::check_tx_inputs(const transaction& tx, const crypto::hash& tx_prefix_hash) const
|
||
{
|
||
uint64_t stub = 0;
|
||
return check_tx_inputs(tx, tx_prefix_hash, stub);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::check_tx_inputs(const transaction& tx, const crypto::hash& tx_prefix_hash, uint64_t& max_used_block_height) const
|
||
{
|
||
size_t sig_index = 0;
|
||
max_used_block_height = 0;
|
||
|
||
std::vector<crypto::signature> sig_stub;
|
||
const std::vector<crypto::signature>* psig = &sig_stub;
|
||
|
||
TIME_MEASURE_START_PD(tx_check_inputs_loop);
|
||
BOOST_FOREACH(const auto& txin, tx.vin)
|
||
{
|
||
if (!m_is_in_checkpoint_zone)
|
||
{
|
||
CHECK_AND_ASSERT_MES(sig_index < tx.signatures.size(), false, "Wrong transaction: missing signature entry for input #" << sig_index << " tx: " << tx_prefix_hash);
|
||
psig = &tx.signatures[sig_index];
|
||
}
|
||
|
||
if (txin.type() == typeid(txin_to_key))
|
||
{
|
||
const txin_to_key& in_to_key = boost::get<txin_to_key>(txin);
|
||
|
||
CHECK_AND_ASSERT_MES(in_to_key.key_offsets.size(), false, "Empty in_to_key.key_offsets for input #" << sig_index << " tx: " << tx_prefix_hash);
|
||
TIME_MEASURE_START_PD(tx_check_inputs_loop_kimage_check);
|
||
if (have_tx_keyimg_as_spent(in_to_key.k_image))
|
||
{
|
||
LOG_ERROR("Key image was already spent in blockchain: " << string_tools::pod_to_hex(in_to_key.k_image) << " for input #" << sig_index << " tx: " << tx_prefix_hash);
|
||
return false;
|
||
}
|
||
TIME_MEASURE_FINISH_PD(tx_check_inputs_loop_kimage_check);
|
||
uint64_t max_unlock_time = 0;
|
||
if (!check_tx_input(tx, sig_index, in_to_key, tx_prefix_hash, *psig, max_used_block_height, max_unlock_time))
|
||
{
|
||
LOG_ERROR("Failed to validate input #" << sig_index << " tx: " << tx_prefix_hash);
|
||
return false;
|
||
}
|
||
}
|
||
else if (txin.type() == typeid(txin_multisig))
|
||
{
|
||
const txin_multisig& in_ms = boost::get<txin_multisig>(txin);
|
||
if (!check_tx_input(tx, sig_index, in_ms, tx_prefix_hash, *psig, max_used_block_height))
|
||
{
|
||
LOG_ERROR("Failed to validate multisig input #" << sig_index << " (ms out id: " << in_ms.multisig_out_id << ") in tx: " << tx_prefix_hash);
|
||
return false;
|
||
}
|
||
|
||
}
|
||
sig_index++;
|
||
}
|
||
TIME_MEASURE_FINISH_PD(tx_check_inputs_loop);
|
||
|
||
TIME_MEASURE_START_PD(tx_check_inputs_attachment_check);
|
||
if (!m_is_in_checkpoint_zone)
|
||
{
|
||
CHECK_AND_ASSERT_MES(tx.signatures.size() == sig_index, false, "tx signatures count differs from inputs");
|
||
if (!(get_tx_flags(tx) & TX_FLAG_SIGNATURE_MODE_SEPARATE))
|
||
{
|
||
bool r = validate_attachment_info(tx.extra, tx.attachment, false);
|
||
CHECK_AND_ASSERT_MES(r, false, "Failed to validate attachments in tx " << tx_prefix_hash << ": incorrect extra_attachment_info in tx.extra");
|
||
}
|
||
}
|
||
TIME_MEASURE_FINISH_PD(tx_check_inputs_attachment_check);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::is_tx_spendtime_unlocked(uint64_t unlock_time) const
|
||
{
|
||
return currency::is_tx_spendtime_unlocked(unlock_time, get_current_blockchain_size(), m_core_runtime_config.get_core_time());
|
||
}
|
||
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::check_tx_input(const transaction& tx, size_t in_index, const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, uint64_t& max_related_block_height, uint64_t& max_unlock_time) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
//TIME_MEASURE_START_PD(tx_check_inputs_loop_ch_in_get_keys_loop);
|
||
|
||
std::vector<crypto::public_key> output_keys;
|
||
if(!get_output_keys_for_input_with_checks(tx, txin, output_keys, max_related_block_height, max_unlock_time))
|
||
{
|
||
LOG_PRINT_L0("Failed to get output keys for input #" << in_index << " (amount = " << print_money(txin.amount) << ", key_offset.size = " << txin.key_offsets.size() << ")");
|
||
return false;
|
||
}
|
||
//TIME_MEASURE_FINISH_PD(tx_check_inputs_loop_ch_in_get_keys_loop);
|
||
|
||
std::vector<const crypto::public_key *> output_keys_ptrs;
|
||
output_keys_ptrs.reserve(output_keys.size());
|
||
for (auto& ptr : output_keys)
|
||
output_keys_ptrs.push_back(&ptr);
|
||
|
||
return check_tokey_input(tx, in_index, txin, tx_prefix_hash, sig, output_keys_ptrs);
|
||
}
|
||
//------------------------------------------------------------------
|
||
// Checks each referenced output for:
|
||
// 1) source tx unlock time validity
|
||
// 2) mixin restrictions
|
||
// 3) general gindex/ref_by_id corectness
|
||
bool blockchain_storage::get_output_keys_for_input_with_checks(const transaction& tx, const txin_to_key& txin, std::vector<crypto::public_key>& output_keys, uint64_t& max_related_block_height, uint64_t& max_unlock_time) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
struct outputs_visitor
|
||
{
|
||
std::vector<crypto::public_key >& m_results_collector;
|
||
const blockchain_storage& m_bch;
|
||
uint64_t& m_max_unlock_time;
|
||
outputs_visitor(std::vector<crypto::public_key>& results_collector,
|
||
const blockchain_storage& bch,
|
||
uint64_t& max_unlock_time) :m_results_collector(results_collector), m_bch(bch), m_max_unlock_time(max_unlock_time)
|
||
{}
|
||
bool handle_output(const transaction& source_tx, const transaction& validated_tx, const tx_out& out, uint64_t out_i)
|
||
{
|
||
//check tx unlock time
|
||
uint64_t source_out_unlock_time = get_tx_unlock_time(source_tx, out_i);
|
||
//let coinbase sources for PoS block to have locked inputs, the outputs supposed to be locked same way, except the reward
|
||
if (is_coinbase(validated_tx) && is_pos_block(validated_tx))
|
||
{
|
||
if (source_out_unlock_time > m_max_unlock_time)
|
||
m_max_unlock_time = source_out_unlock_time;
|
||
}
|
||
else
|
||
{
|
||
if (!m_bch.is_tx_spendtime_unlocked(source_out_unlock_time))
|
||
{
|
||
LOG_PRINT_L0("One of outputs for one of inputs have wrong tx.unlock_time = " << get_tx_unlock_time(source_tx, out_i));
|
||
return false;
|
||
}
|
||
}
|
||
|
||
|
||
if(out.target.type() != typeid(txout_to_key))
|
||
{
|
||
LOG_PRINT_L0("Output have wrong type id, which=" << out.target.which());
|
||
return false;
|
||
}
|
||
crypto::public_key pk = boost::get<txout_to_key>(out.target).key;
|
||
m_results_collector.push_back(pk);
|
||
return true;
|
||
}
|
||
};
|
||
|
||
outputs_visitor vi(output_keys, *this, max_unlock_time);
|
||
return scan_outputkeys_for_indexes(tx, txin, vi, max_related_block_height);
|
||
}
|
||
//------------------------------------------------------------------
|
||
// Note: this function can be used for checking to_key inputs against either main chain or alt chain, that's why it has output_keys_ptrs parameter
|
||
// Doesn't check spent flags, the caller must check it.
|
||
bool blockchain_storage::check_tokey_input(const transaction& tx, size_t in_index, const txin_to_key& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, const std::vector<const crypto::public_key*>& output_keys_ptrs) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
TIME_MEASURE_START_PD(tx_check_inputs_loop_ch_in_val_sig);
|
||
|
||
if (txin.key_offsets.size() != output_keys_ptrs.size())
|
||
{
|
||
LOG_PRINT_L0("Output keys for tx with amount = " << txin.amount << " and count indexes " << txin.key_offsets.size() << " returned wrong keys count " << output_keys_ptrs.size());
|
||
return false;
|
||
}
|
||
|
||
if(m_is_in_checkpoint_zone)
|
||
return true;
|
||
|
||
if (get_tx_flags(tx) & TX_FLAG_SIGNATURE_MODE_SEPARATE)
|
||
{
|
||
// check attachments, mentioned directly in this input
|
||
bool r = validate_attachment_info(txin.etc_details, tx.attachment, in_index != tx.vin.size() - 1); // attachment info can be omitted for all inputs, except the last one
|
||
CHECK_AND_ASSERT_MES(r, false, "Failed to validate attachments in tx " << tx_prefix_hash << ": incorrect extra_attachment_info in etc_details in input #" << in_index);
|
||
}
|
||
else
|
||
{
|
||
// make sure normal tx does not have extra_attachment_info in etc_details
|
||
CHECK_AND_ASSERT_MES(!have_type_in_variant_container<extra_attachment_info>(txin.etc_details), false, "Incorrect using of extra_attachment_info in etc_details in input #" << in_index << " for tx " << tx_prefix_hash);
|
||
}
|
||
|
||
// check signatures
|
||
size_t expected_signatures_count = output_keys_ptrs.size();
|
||
bool need_to_check_extra_sign = false;
|
||
if (get_tx_flags(tx)&TX_FLAG_SIGNATURE_MODE_SEPARATE && in_index == tx.vin.size() - 1)
|
||
{
|
||
expected_signatures_count++;
|
||
need_to_check_extra_sign = true;
|
||
}
|
||
|
||
CHECK_AND_ASSERT_MES(expected_signatures_count == sig.size(), false, "internal error: tx signatures count=" << sig.size() << " mismatch with outputs keys count for inputs=" << expected_signatures_count);
|
||
crypto::hash tx_hash_for_signature = prepare_prefix_hash_for_sign(tx, in_index, tx_prefix_hash);
|
||
CHECK_AND_ASSERT_MES(tx_hash_for_signature != null_hash, false, "failed to prepare_prefix_hash_for_sign");
|
||
|
||
LOG_PRINT_L4("CHECK RING SIGNATURE: tx_prefix_hash " << tx_prefix_hash
|
||
<< "tx_hash_for_signature" << tx_hash_for_signature
|
||
<< "txin.k_image" << txin.k_image
|
||
<< "key_ptr:" << *output_keys_ptrs[0]
|
||
<< "signature:" << sig[0]);
|
||
bool r = crypto::validate_key_image(txin.k_image);
|
||
CHECK_AND_ASSERT_MES(r, false, "key image for input #" << in_index << " is invalid: " << txin.k_image);
|
||
|
||
r = crypto::check_ring_signature(tx_hash_for_signature, txin.k_image, output_keys_ptrs, sig.data());
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to check ring signature for input #" << in_index << ENDL << dump_ring_sig_data(tx_hash_for_signature, txin.k_image, output_keys_ptrs, sig));
|
||
if (need_to_check_extra_sign)
|
||
{
|
||
//here we check extra signature to validate that transaction was finalized by authorized subject
|
||
r = crypto::check_signature(tx_prefix_hash, get_tx_pub_key_from_extra(tx), sig.back());
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to check extra signature for last input with TX_FLAG_SIGNATURE_MODE_SEPARATE");
|
||
}
|
||
|
||
TIME_MEASURE_FINISH_PD(tx_check_inputs_loop_ch_in_val_sig);
|
||
return r;
|
||
}
|
||
//------------------------------------------------------------------
|
||
// Note: this function doesn't check spent flags by design (to be able to use either for main chain and alt chains).
|
||
// The caller MUST check spent flags.
|
||
bool blockchain_storage::check_ms_input(const transaction& tx, size_t in_index, const txin_multisig& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, const transaction& source_tx, size_t out_n) const
|
||
{
|
||
#define LOC_CHK(cond, msg) CHECK_AND_ASSERT_MES(cond, false, "ms input check failed: ms_id: " << txin.multisig_out_id << ", input #" << in_index << " in tx " << tx_prefix_hash << ", refers to ms output #" << out_n << " in source tx " << get_transaction_hash(source_tx) << ENDL << msg)
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
uint64_t unlock_time = get_tx_unlock_time(source_tx, out_n);
|
||
LOC_CHK(is_tx_spendtime_unlocked(unlock_time), "Source transaction is LOCKED! unlock_time: " << unlock_time << ", now is " << m_core_runtime_config.get_core_time() << ", blockchain size is " << get_current_blockchain_size());
|
||
|
||
LOC_CHK(source_tx.vout.size() > out_n, "internal error: out_n==" << out_n << " is out-of-bounds of source_tx.vout, size=" << source_tx.vout.size());
|
||
const tx_out& source_tx_out = source_tx.vout[out_n];
|
||
const txout_multisig& source_ms_out_target = boost::get<txout_multisig>(source_tx_out.target);
|
||
|
||
LOC_CHK(txin.sigs_count == source_ms_out_target.minimum_sigs,
|
||
"ms input's sigs_count (" << txin.sigs_count << ") does not match to ms output's minimum signatures expected (" << source_ms_out_target.minimum_sigs << ")"
|
||
<< ", source_tx_out.amount=" << print_money(source_tx_out.amount)
|
||
<< ", txin.amount = " << print_money(txin.amount));
|
||
|
||
LOC_CHK(source_tx_out.amount == txin.amount,
|
||
"amount missmatch"
|
||
<< ", source_tx_out.amount=" << print_money(source_tx_out.amount)
|
||
<< ", txin.amount = " << print_money(txin.amount));
|
||
|
||
if (m_is_in_checkpoint_zone)
|
||
return true;
|
||
|
||
if (get_tx_flags(tx) & TX_FLAG_SIGNATURE_MODE_SEPARATE)
|
||
{
|
||
// check attachments, mentioned directly in this input
|
||
bool r = validate_attachment_info(txin.etc_details, tx.attachment, in_index != tx.vin.size() - 1); // attachment info can be omitted for all inputs, except the last one
|
||
LOC_CHK(r, "Failed to validate attachments in tx " << tx_prefix_hash << ": incorrect extra_attachment_info in etc_details in input #" << in_index);
|
||
}
|
||
else
|
||
{
|
||
// make sure normal tx does not have extra_attachment_info in etc_details
|
||
LOC_CHK(!have_type_in_variant_container<extra_attachment_info>(txin.etc_details), "Incorrect using of extra_attachment_info in etc_details in input #" << in_index << " for tx " << tx_prefix_hash);
|
||
}
|
||
|
||
LOC_CHK(tx.signatures.size() > in_index, "ms input index is out of signatures container bounds, tx.signatures.size() = " << tx.signatures.size());
|
||
const std::vector<crypto::signature>& input_signatures = tx.signatures[in_index];
|
||
|
||
size_t expected_signatures_count = txin.sigs_count;
|
||
bool need_to_check_extra_sign = false;
|
||
if (get_tx_flags(tx)&TX_FLAG_SIGNATURE_MODE_SEPARATE && in_index == tx.vin.size() - 1) // last input in TX_FLAG_SIGNATURE_MODE_SEPARATE must contain one more signature to ensure that tx was completed by an authorized subject
|
||
{
|
||
expected_signatures_count++;
|
||
need_to_check_extra_sign = true;
|
||
}
|
||
|
||
LOC_CHK(expected_signatures_count == input_signatures.size(), "Invalid input's signatures count: " << input_signatures.size() << ", expected: " << expected_signatures_count);
|
||
|
||
crypto::hash tx_hash_for_signature = prepare_prefix_hash_for_sign(tx, in_index, tx_prefix_hash);
|
||
LOC_CHK(tx_hash_for_signature != null_hash, "prepare_prefix_hash_for_sign failed");
|
||
|
||
LOC_CHK(txin.sigs_count <= source_ms_out_target.keys.size(), "source tx invariant failed: ms output's minimum sigs == ms input's sigs_count (" << txin.sigs_count << ") is GREATHER than keys.size() = " << source_ms_out_target.keys.size()); // NOTE: sig_count == minimum_sigs as checked above
|
||
size_t out_key_index = 0; // index in source_ms_out_target.keys
|
||
for (size_t i = 0; i != txin.sigs_count; /* nothing */)
|
||
{
|
||
// if we run out of keys for this signature, then it's invalid signature
|
||
LOC_CHK(out_key_index < source_ms_out_target.keys.size(), "invalid signature #" << i << ": " << input_signatures[i]);
|
||
|
||
// check signature #i against ms output key #out_key_index
|
||
if (crypto::check_signature(tx_hash_for_signature, source_ms_out_target.keys[out_key_index], input_signatures[i]))
|
||
{
|
||
// match: go for the next signature and the next key
|
||
i++;
|
||
out_key_index++;
|
||
}
|
||
else
|
||
{
|
||
// missmatch: go for the next key for this signature
|
||
out_key_index++;
|
||
}
|
||
}
|
||
if (need_to_check_extra_sign)
|
||
{
|
||
//here we check extra signature to validate that transaction was finilized by authorized subject
|
||
bool r = crypto::check_signature(tx_prefix_hash, get_tx_pub_key_from_extra(tx), tx.signatures[in_index].back());
|
||
LOC_CHK(r, "failed to check extra signature for last out with TX_FLAG_SIGNATURE_MODE_SEPARATE");
|
||
}
|
||
|
||
return true;
|
||
#undef LOC_CHK
|
||
}
|
||
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::check_tx_input(const transaction& tx, size_t in_index, const txin_multisig& txin, const crypto::hash& tx_prefix_hash, const std::vector<crypto::signature>& sig, uint64_t& max_related_block_height) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
auto multisig_ptr = m_db_multisig_outs.find(txin.multisig_out_id);
|
||
CHECK_AND_ASSERT_MES(multisig_ptr, false, "Unable to find txin.multisig_out_id=" << txin.multisig_out_id << " for ms input #" << in_index << " in tx " << tx_prefix_hash);
|
||
|
||
const crypto::hash& source_tx_id = multisig_ptr->tx_id; // source tx hash
|
||
size_t n = multisig_ptr->out_no; // index of multisig output in source tx
|
||
|
||
#define LOC_CHK(cond, msg) CHECK_AND_ASSERT_MES(cond, false, "ms input check failed: ms_id: " << txin.multisig_out_id << ", input #" << in_index << " in tx " << tx_prefix_hash << ", refers to ms output #" << n << " in source tx " << source_tx_id << ENDL << msg)
|
||
|
||
LOC_CHK(multisig_ptr->spent_height == 0, "ms output is already spent on height " << multisig_ptr->spent_height);
|
||
|
||
auto source_tx_ptr = m_db_transactions.find(source_tx_id);
|
||
LOC_CHK(source_tx_ptr, "Can't find source transaction");
|
||
LOC_CHK(source_tx_ptr->tx.vout.size() > n, "ms output index is incorrect, source tx's vout size is " << source_tx_ptr->tx.vout.size());
|
||
LOC_CHK(source_tx_ptr->tx.vout[n].target.type() == typeid(txout_multisig), "ms output has wrong type, txout_multisig expected");
|
||
LOC_CHK(source_tx_ptr->m_spent_flags.size() > n, "Internal error, m_spent_flags size (" << source_tx_ptr->m_spent_flags.size() << ") less then expected, n: " << n);
|
||
LOC_CHK(source_tx_ptr->m_spent_flags[n] == false, "Internal error, ms output is already spent"); // should never happen as multisig_ptr->spent_height is checked above
|
||
|
||
if (!check_ms_input(tx, in_index, txin, tx_prefix_hash, sig, source_tx_ptr->tx, n))
|
||
return false;
|
||
|
||
max_related_block_height = source_tx_ptr->m_keeper_block_height;
|
||
|
||
return true;
|
||
#undef LOC_CHK
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::get_adjusted_time() const
|
||
{
|
||
//TODO: add collecting median time
|
||
return m_core_runtime_config.get_core_time();
|
||
}
|
||
//------------------------------------------------------------------
|
||
std::vector<uint64_t> blockchain_storage::get_last_n_blocks_timestamps(size_t n) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
std::vector<uint64_t> timestamps;
|
||
size_t offset = m_db_blocks.size() <= n ? 0 : m_db_blocks.size() - n;
|
||
for (; offset != m_db_blocks.size(); ++offset)
|
||
timestamps.push_back(m_db_blocks[offset]->bl.timestamp);
|
||
return timestamps;
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::get_last_n_blocks_timestamps_median(size_t n) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
auto it = m_timestamps_median_cache.find(n);
|
||
if (it != m_timestamps_median_cache.end())
|
||
return it->second;
|
||
|
||
std::vector<uint64_t> timestamps = get_last_n_blocks_timestamps(n);
|
||
uint64_t median_res = epee::misc_utils::median(timestamps);
|
||
m_timestamps_median_cache[n] = median_res;
|
||
return median_res;
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::get_last_timestamps_check_window_median() const
|
||
{
|
||
return get_last_n_blocks_timestamps_median(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW);
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::get_tx_expiration_median() const
|
||
{
|
||
return get_last_n_blocks_timestamps_median(TX_EXPIRATION_TIMESTAMP_CHECK_WINDOW);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::check_block_timestamp_main(const block& b) const
|
||
{
|
||
if(b.timestamp > get_adjusted_time() + CURRENCY_BLOCK_FUTURE_TIME_LIMIT)
|
||
{
|
||
LOG_PRINT_L0("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + " + epee::misc_utils::get_time_interval_string(CURRENCY_BLOCK_FUTURE_TIME_LIMIT));
|
||
return false;
|
||
}
|
||
if (is_pos_block(b) && b.timestamp > get_adjusted_time() + CURRENCY_POS_BLOCK_FUTURE_TIME_LIMIT)
|
||
{
|
||
LOG_PRINT_L0("Timestamp of PoS block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + " + epee::misc_utils::get_time_interval_string(CURRENCY_POS_BLOCK_FUTURE_TIME_LIMIT) + ": " << get_adjusted_time() << " (" << b.timestamp - get_adjusted_time() << ")");
|
||
return false;
|
||
}
|
||
|
||
std::vector<uint64_t> timestamps = get_last_n_blocks_timestamps(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW);
|
||
|
||
return check_block_timestamp(std::move(timestamps), b);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::check_block_timestamp(std::vector<uint64_t> timestamps, const block& b) const
|
||
{
|
||
if(timestamps.size() < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW)
|
||
return true;
|
||
|
||
if (is_pos_block(b) && b.timestamp > get_adjusted_time() + CURRENCY_POS_BLOCK_FUTURE_TIME_LIMIT)
|
||
{
|
||
LOG_PRINT_L0("Timestamp of PoS block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + " + epee::misc_utils::get_time_interval_string(CURRENCY_POS_BLOCK_FUTURE_TIME_LIMIT) + ": " << get_adjusted_time() << " (" << b.timestamp - get_adjusted_time() << ")");
|
||
return false;
|
||
}
|
||
|
||
uint64_t median_ts = epee::misc_utils::median(timestamps);
|
||
|
||
if(b.timestamp < median_ts)
|
||
{
|
||
LOG_PRINT_L0("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", less than median of last " << BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW << " blocks, " << median_ts);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::is_tx_expired(const transaction& tx) const
|
||
{
|
||
return currency::is_tx_expired(tx, get_tx_expiration_median());
|
||
}
|
||
//------------------------------------------------------------------
|
||
std::shared_ptr<const transaction_chain_entry> blockchain_storage::find_key_image_and_related_tx(const crypto::key_image& ki, crypto::hash& id_result) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
auto ki_index_ptr = m_db_spent_keys.find(ki);
|
||
if (!ki_index_ptr)
|
||
return std::shared_ptr<transaction_chain_entry>();
|
||
|
||
auto block_entry = m_db_blocks[*ki_index_ptr];
|
||
if (!block_entry)
|
||
{
|
||
LOG_ERROR("Internal error: broken index, key image " << ki
|
||
<< " reffered to height " << *ki_index_ptr << " but couldnot find block on this height");
|
||
return std::shared_ptr<const transaction_chain_entry>();
|
||
}
|
||
|
||
//look up coinbase
|
||
for (auto& in: block_entry->bl.miner_tx.vin)
|
||
{
|
||
if (in.type() == typeid(txin_to_key))
|
||
{
|
||
if (boost::get<txin_to_key>(in).k_image == ki)
|
||
{
|
||
id_result = get_transaction_hash(block_entry->bl.miner_tx);
|
||
return get_tx_chain_entry(id_result);
|
||
}
|
||
}
|
||
}
|
||
//lookup transactions
|
||
for (auto& tx_id : block_entry->bl.tx_hashes)
|
||
{
|
||
auto tx_chain_entry = get_tx_chain_entry(tx_id);
|
||
if (!tx_chain_entry)
|
||
{
|
||
LOG_ERROR("Internal error: broken index, tx_id " << tx_id
|
||
<< " not found in tx index, block no " << *ki_index_ptr);
|
||
return std::shared_ptr<const transaction_chain_entry>();
|
||
}
|
||
for (auto& in : tx_chain_entry->tx.vin)
|
||
{
|
||
if (in.type() == typeid(txin_to_key))
|
||
{
|
||
if (boost::get<txin_to_key>(in).k_image == ki)
|
||
{
|
||
id_result = get_transaction_hash(tx_chain_entry->tx);
|
||
return tx_chain_entry;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return std::shared_ptr<const transaction_chain_entry>();
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::prune_aged_alt_blocks()
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
CRITICAL_REGION_LOCAL1(m_alternative_chains_lock);
|
||
uint64_t current_height = get_current_blockchain_size();
|
||
|
||
size_t count_to_delete = 0;
|
||
if(m_alternative_chains.size() > m_core_runtime_config.max_alt_blocks)
|
||
count_to_delete = m_alternative_chains.size() - m_core_runtime_config.max_alt_blocks;
|
||
|
||
std::map<uint64_t, alt_chain_container::iterator> alts_to_delete;
|
||
|
||
for(auto it = m_alternative_chains.begin(); it != m_alternative_chains.end();)
|
||
{
|
||
if (current_height > it->second.height && current_height - it->second.height > CURRENCY_ALT_BLOCK_LIVETIME_COUNT)
|
||
{
|
||
do_erase_altblock(it++);
|
||
}
|
||
else
|
||
{
|
||
if (count_to_delete)
|
||
{
|
||
if (!alts_to_delete.size())
|
||
alts_to_delete[it->second.timestamp] = it;
|
||
else
|
||
{
|
||
if (it->second.timestamp >= alts_to_delete.rbegin()->first)
|
||
alts_to_delete[it->second.timestamp] = it;
|
||
|
||
if (alts_to_delete.size() > count_to_delete)
|
||
alts_to_delete.erase(alts_to_delete.begin());
|
||
}
|
||
}
|
||
|
||
++it;
|
||
}
|
||
}
|
||
//now, if there was count_to_delete we should erase most oldest entries of altblocks
|
||
for (auto& itd : alts_to_delete)
|
||
{
|
||
m_alternative_chains.erase(itd.second);
|
||
}
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::validate_pos_block(const block& b, const crypto::hash& id, bool for_altchain) const
|
||
{
|
||
//validate
|
||
wide_difficulty_type basic_diff = get_next_diff_conditional(true);
|
||
return validate_pos_block(b, basic_diff, id, for_altchain);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::validate_pos_block(const block& b, wide_difficulty_type basic_diff, const crypto::hash& id, bool for_altchain) const
|
||
{
|
||
uint64_t coin_age = 0;
|
||
wide_difficulty_type final_diff = 0;
|
||
crypto::hash proof_hash = null_hash;
|
||
return validate_pos_block(b, basic_diff, coin_age, final_diff, proof_hash, id, for_altchain);
|
||
}
|
||
//------------------------------------------------------------------
|
||
#define POS_STAKE_TO_DIFF_COEFF 100 // total_coins_in_minting * POS_STAKE_TO_DIFF_COEFF =~ pos_difficulty
|
||
|
||
void blockchain_storage::get_pos_mining_estimate(uint64_t amount_coins,
|
||
uint64_t time,
|
||
uint64_t& estimate_result,
|
||
uint64_t& pos_diff_and_amount_rate,
|
||
std::vector<uint64_t>& days) const
|
||
{
|
||
estimate_result = 0;
|
||
if (!is_pos_allowed())
|
||
return;
|
||
|
||
// new algo
|
||
// 1. get (CURRENCY_BLOCKS_PER_DAY / 2) PoS blocks (a day in case of PoS/PoW==50/50)
|
||
// 2. calculate total minted money C (normalized for 1 day interval) and average difficulty D (based on last (CURRENCY_BLOCKS_PER_DAY / 2) PoS blocks)
|
||
// 3. calculate total coins participating in PoS minting as M = D / pos_diff_ratio
|
||
// 4. calculate owner's money proportion as P = amount_coins / (M + amount_coins)
|
||
// 5. estimate PoS minting income for this day as I = C * P
|
||
// 6. amount_coins += I, goto 3
|
||
|
||
epee::critical_region_t<decltype(m_read_lock)> read_lock_region(m_read_lock);
|
||
|
||
size_t estimated_pos_blocks_count_per_day = CURRENCY_BLOCKS_PER_DAY / 2; // 50% of all blocks in a perfect world
|
||
|
||
uint64_t pos_ts_min = UINT64_MAX, pos_ts_max = 0;
|
||
size_t pos_blocks_count = 0;
|
||
uint64_t pos_total_minted_money = 0;
|
||
wide_difficulty_type pos_avg_difficulty = 0;
|
||
// scan blockchain backward for PoS blocks and collect data
|
||
for (size_t h = m_db_blocks.size() - 1; h != 0 && pos_blocks_count < estimated_pos_blocks_count_per_day; --h)
|
||
{
|
||
auto bei = m_db_blocks[h];
|
||
if (!is_pos_block(bei->bl))
|
||
continue;
|
||
uint64_t ts = get_actual_timestamp(bei->bl);
|
||
pos_ts_min = min(pos_ts_min, ts);
|
||
pos_ts_max = max(pos_ts_max, ts);
|
||
pos_total_minted_money += get_reward_from_miner_tx(bei->bl.miner_tx);
|
||
pos_avg_difficulty += bei->difficulty;
|
||
++pos_blocks_count;
|
||
}
|
||
if (pos_blocks_count < estimated_pos_blocks_count_per_day || pos_ts_max <= pos_ts_min)
|
||
return; // too little pos blocks found or invalid ts
|
||
pos_avg_difficulty /= pos_blocks_count;
|
||
uint64_t found_blocks_interval = pos_ts_max - pos_ts_min; // will be close to 24 * 60 * 60 in case of PoS/PoW == 50/50
|
||
uint64_t pos_last_day_total_minted_money = pos_total_minted_money * (24 * 60 * 60) / found_blocks_interval; // total minted money normalized for 1 day interval
|
||
|
||
uint64_t estimated_total_minting_coins = static_cast<uint64_t>(pos_avg_difficulty / POS_STAKE_TO_DIFF_COEFF);
|
||
|
||
uint64_t current_amount = amount_coins;
|
||
uint64_t days_count = time / (60 * 60 * 24);
|
||
for (uint64_t d = 0; d != days_count; d++)
|
||
{
|
||
double user_minting_coins_proportion = static_cast<double>(current_amount) / (estimated_total_minting_coins + current_amount);
|
||
current_amount += pos_last_day_total_minted_money * user_minting_coins_proportion;
|
||
days.push_back(current_amount);
|
||
}
|
||
estimate_result = current_amount;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::validate_tx_for_hardfork_specific_terms(const transaction& tx, const crypto::hash& tx_id, uint64_t block_height) const
|
||
{
|
||
if (block_height <= m_core_runtime_config.hard_fork1_starts_after_height)
|
||
{
|
||
// before hardfork 1
|
||
|
||
for (const auto& el : tx.extra)
|
||
{
|
||
// etc_tx_details_unlock_time2 is not allowed in txs in blocks prior to hardfork 1
|
||
CHECK_AND_ASSERT_MES(el.type() != typeid(etc_tx_details_unlock_time2), false, "tx " << tx_id << " contains etc_tx_details_unlock_time2 which is not allowed on height " << block_height);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::validate_pos_coinbase_outs_unlock_time(const transaction& miner_tx, uint64_t staked_amount, uint64_t max_unlock_time)const
|
||
{
|
||
uint64_t major_unlock_time = get_tx_x_detail<etc_tx_details_unlock_time>(miner_tx);
|
||
if (major_unlock_time)
|
||
{
|
||
//if there was etc_tx_details_unlock_time present in tx, then ignore etc_tx_details_unlock_time2
|
||
if (major_unlock_time < max_unlock_time)
|
||
return false;
|
||
else
|
||
return true;
|
||
}
|
||
|
||
CHECK_AND_ASSERT_MES(get_block_height(miner_tx) > m_core_runtime_config.hard_fork1_starts_after_height, false, "error in block [" << get_block_height(miner_tx) << "] etc_tx_details_unlock_time2 can exist only after hard fork point : " << m_core_runtime_config.hard_fork1_starts_after_height);
|
||
|
||
//etc_tx_details_unlock_time2 can be kept only after hard_fork_1 point
|
||
etc_tx_details_unlock_time2 ut2 = AUTO_VAL_INIT(ut2);
|
||
get_type_in_variant_container(miner_tx.extra, ut2);
|
||
CHECK_AND_ASSERT_MES(ut2.unlock_time_array.size() == miner_tx.vout.size(), false, "ut2.unlock_time_array.size()<" << ut2.unlock_time_array.size()
|
||
<< "> != miner_tx.vout.size()<" << miner_tx.vout.size() << ">");
|
||
|
||
uint64_t amount_of_coins_in_unlock_in_range = 0;
|
||
|
||
for (uint64_t i = 0; i != miner_tx.vout.size(); i++)
|
||
{
|
||
if (ut2.unlock_time_array[i] >= max_unlock_time)
|
||
amount_of_coins_in_unlock_in_range += miner_tx.vout[i].amount;
|
||
}
|
||
|
||
if (amount_of_coins_in_unlock_in_range >= staked_amount)
|
||
return true;
|
||
LOG_ERROR("amount_of_coins_in_unlock_in_range<" << amount_of_coins_in_unlock_in_range << "> is less then staked_amount<" << staked_amount);
|
||
return false;
|
||
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::validate_pos_block(const block& b,
|
||
wide_difficulty_type basic_diff,
|
||
uint64_t& amount,
|
||
wide_difficulty_type& final_diff,
|
||
crypto::hash& proof_hash,
|
||
const crypto::hash& id,
|
||
bool for_altchain,
|
||
const alt_chain_type& alt_chain,
|
||
uint64_t split_height
|
||
)const
|
||
{
|
||
bool is_pos = is_pos_block(b);
|
||
CHECK_AND_ASSERT_MES(is_pos, false, "is_pos_block() returned false validate_pos_block()");
|
||
|
||
//check timestamp
|
||
CHECK_AND_ASSERT_MES(b.timestamp%POS_SCAN_STEP == 0, false, "wrong timestamp in PoS block(b.timestamp%POS_SCAN_STEP == 0), b.timestamp = " <<b.timestamp);
|
||
|
||
//check keyimage
|
||
CHECK_AND_ASSERT_MES(b.miner_tx.vin[1].type() == typeid(txin_to_key), false, "coinstake transaction in the block has the wrong type");
|
||
const txin_to_key& in_to_key = boost::get<txin_to_key>(b.miner_tx.vin[1]);
|
||
if (!for_altchain && have_tx_keyimg_as_spent(in_to_key.k_image))
|
||
{
|
||
LOG_PRINT_L0("Key image in coinstake already spent in blockchain: " << string_tools::pod_to_hex(in_to_key.k_image));
|
||
return false;
|
||
}
|
||
|
||
|
||
//check actual time if it there
|
||
uint64_t actual_ts = get_actual_timestamp(b);
|
||
if ((actual_ts > b.timestamp && actual_ts - b.timestamp > POS_MAX_ACTUAL_TIMESTAMP_TO_MINED) ||
|
||
(actual_ts < b.timestamp && b.timestamp - actual_ts > POS_MAX_ACTUAL_TIMESTAMP_TO_MINED)
|
||
)
|
||
{
|
||
LOG_PRINT_L0("PoS block actual timestamp " << actual_ts << " differs from b.timestamp " << b.timestamp << " by " << ((int64_t)actual_ts - (int64_t)b.timestamp) << " s, it's more than allowed " << POS_MAX_ACTUAL_TIMESTAMP_TO_MINED << " s.");
|
||
return false;
|
||
}
|
||
|
||
//check kernel
|
||
stake_kernel sk = AUTO_VAL_INIT(sk);
|
||
|
||
stake_modifier_type sm = AUTO_VAL_INIT(sm);
|
||
bool r = build_stake_modifier(sm, alt_chain, split_height);
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to build_stake_modifier");
|
||
amount = 0;
|
||
r = build_kernel(b, sk, amount, sm);
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to build kernel_stake");
|
||
CHECK_AND_ASSERT_MES(amount!=0, false, "failed to build kernel_stake, amount == 0");
|
||
|
||
proof_hash = crypto::cn_fast_hash(&sk, sizeof(sk));
|
||
|
||
LOG_PRINT_L2("STAKE KERNEL for bl ID: " << get_block_hash(b) << ENDL
|
||
<< print_stake_kernel_info(sk)
|
||
<< "amount: " << print_money(amount) << ENDL
|
||
<< "kernel_hash: " << proof_hash);
|
||
|
||
|
||
final_diff = basic_diff / amount;
|
||
if (!check_hash(proof_hash, final_diff))
|
||
{
|
||
LOG_ERROR("PoS difficulty check failed for block " << get_block_hash(b) << " @ HEIGHT " << get_block_height(b) << ":" << ENDL
|
||
<< " basic_diff: " << basic_diff << ENDL
|
||
<< " final_diff: " << final_diff << ENDL
|
||
<< " amount: " << print_money_brief(amount) << ENDL
|
||
<< " kernel_hash: " << proof_hash << ENDL
|
||
);
|
||
return false;
|
||
}
|
||
|
||
//validate signature
|
||
uint64_t max_related_block_height = 0;
|
||
const txin_to_key& coinstake_in = boost::get<txin_to_key>(b.miner_tx.vin[1]);
|
||
CHECK_AND_ASSERT_MES(b.miner_tx.signatures.size() == 1, false, "PoS block's miner_tx has incorrect signatures size = " << b.miner_tx.signatures.size() << ", block_id = " << get_block_hash(b));
|
||
if (!for_altchain)
|
||
{
|
||
// Do coinstake input validation for main chain only.
|
||
// Txs in alternative PoS blocks (including miner_tx) are validated by validate_alt_block_txs()
|
||
uint64_t max_unlock_time = 0;
|
||
r = check_tx_input(b.miner_tx, 1, coinstake_in, id, b.miner_tx.signatures[0], max_related_block_height, max_unlock_time);
|
||
CHECK_AND_ASSERT_MES(r, false, "Failed to validate coinstake input in miner tx, block_id = " << get_block_hash(b));
|
||
|
||
if (get_block_height(b) > m_core_runtime_config.hard_fork1_starts_after_height)
|
||
{
|
||
uint64_t last_pow_h = get_last_x_block_height(false);
|
||
CHECK_AND_ASSERT_MES(max_related_block_height <= last_pow_h, false, "Failed to failed<65>to validate coinbase in pos block, condition failed: max_related_block_height(" << max_related_block_height << ") < last_pow_h(" << last_pow_h << ")");
|
||
//let's check that coinbase amount and unlock time
|
||
r = validate_pos_coinbase_outs_unlock_time(b.miner_tx, coinstake_in.amount, max_unlock_time);
|
||
CHECK_AND_ASSERT_MES(r, false, "Failed to validate_pos_coinbase_outs_unlock_time() in miner tx, block_id = " << get_block_hash(b)
|
||
<< "max_unlock_time=" << max_unlock_time);
|
||
}
|
||
else
|
||
{
|
||
CHECK_AND_ASSERT_MES(is_tx_spendtime_unlocked(max_unlock_time), false, "Failed to failed<65>to validate coinbase in pos block, condition failed: is_tx_spendtime_unlocked(max_unlock_time)(" << max_unlock_time << ")");
|
||
}
|
||
}
|
||
|
||
uint64_t block_height = for_altchain ? split_height + alt_chain.size() : m_db_blocks.size();
|
||
uint64_t coinstake_age = block_height - max_related_block_height - 1;
|
||
|
||
CHECK_AND_ASSERT_MES(coinstake_age >= m_core_runtime_config.min_coinstake_age, false,
|
||
"Coinstake age is: " << coinstake_age << " is less than minimum expected: " << m_core_runtime_config.min_coinstake_age);
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
wide_difficulty_type blockchain_storage::get_adjusted_cumulative_difficulty_for_next_pos(wide_difficulty_type next_diff) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
wide_difficulty_type last_pow_diff = 0;
|
||
wide_difficulty_type last_pos_diff = 0;
|
||
uint64_t sz = m_db_blocks.size();
|
||
if (!sz)
|
||
return next_diff;
|
||
|
||
uint64_t i = sz - 1;
|
||
|
||
for (; i < sz && !(last_pow_diff && last_pos_diff); i--)
|
||
{
|
||
if (is_pos_block(m_db_blocks[i]->bl))
|
||
{
|
||
if (!last_pos_diff)
|
||
{
|
||
last_pos_diff = m_db_blocks[i]->difficulty;
|
||
}
|
||
}else
|
||
{
|
||
if (!last_pow_diff)
|
||
{
|
||
last_pow_diff = m_db_blocks[i]->difficulty;
|
||
}
|
||
}
|
||
}
|
||
if (!last_pos_diff)
|
||
return next_diff;
|
||
return next_diff*last_pow_diff/last_pos_diff;
|
||
}
|
||
//------------------------------------------------------------------
|
||
wide_difficulty_type blockchain_storage::get_adjusted_cumulative_difficulty_for_next_alt_pos(alt_chain_type& alt_chain, uint64_t block_height, wide_difficulty_type next_diff, uint64_t connection_height) const
|
||
{
|
||
|
||
wide_difficulty_type last_pow_diff = 0;
|
||
wide_difficulty_type last_pos_diff = 0;
|
||
|
||
for (auto it = alt_chain.rbegin(); it != alt_chain.rend() && !(last_pos_diff && last_pow_diff); it++)
|
||
{
|
||
if (is_pos_block((*it)->second.bl ))
|
||
{
|
||
if (!last_pos_diff)
|
||
{
|
||
last_pos_diff = (*it)->second.difficulty;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!last_pow_diff)
|
||
{
|
||
last_pow_diff = (*it)->second.difficulty;
|
||
}
|
||
}
|
||
}
|
||
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
for (uint64_t h = connection_height - 1; h != 0 && !(last_pos_diff && last_pow_diff); --h)
|
||
{
|
||
if (is_pos_block(m_db_blocks[h]->bl))
|
||
{
|
||
if (!last_pos_diff)
|
||
{
|
||
last_pos_diff = m_db_blocks[h]->difficulty;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (!last_pow_diff)
|
||
{
|
||
last_pow_diff = m_db_blocks[h]->difficulty;
|
||
}
|
||
}
|
||
}
|
||
if (!last_pos_diff)
|
||
return next_diff;
|
||
return next_diff*last_pow_diff / last_pos_diff;
|
||
}
|
||
//------------------------------------------------------------------
|
||
uint64_t blockchain_storage::get_last_x_block_height(bool pos) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
uint64_t sz = m_db_blocks.size();
|
||
if (!sz)
|
||
return 0;
|
||
|
||
uint64_t i = sz-1;
|
||
|
||
for (; i < sz; i--)
|
||
{
|
||
if (is_pos_block(m_db_blocks[i]->bl) == pos)
|
||
return i;
|
||
}
|
||
return 0;
|
||
}
|
||
//------------------------------------------------------------------
|
||
wide_difficulty_type blockchain_storage::get_last_alt_x_block_cumulative_precise_adj_difficulty(const alt_chain_type& alt_chain, uint64_t block_height, bool pos) const
|
||
{
|
||
wide_difficulty_type res = 0;
|
||
get_last_alt_x_block_cumulative_precise_difficulty(alt_chain, block_height, pos, res);
|
||
return res;
|
||
}
|
||
//------------------------------------------------------------------
|
||
wide_difficulty_type blockchain_storage::get_last_alt_x_block_cumulative_precise_difficulty(const alt_chain_type& alt_chain, uint64_t block_height, bool pos, wide_difficulty_type& cumulative_diff_precise_adj) const
|
||
{
|
||
uint64_t main_chain_first_block = block_height;
|
||
for (auto it = alt_chain.rbegin(); it != alt_chain.rend(); it++)
|
||
{
|
||
if (is_pos_block((*it)->second.bl) == pos)
|
||
{
|
||
cumulative_diff_precise_adj = (*it)->second.cumulative_diff_precise_adjusted;
|
||
return (*it)->second.cumulative_diff_precise;
|
||
}
|
||
main_chain_first_block = (*it)->second.height - 1;
|
||
}
|
||
|
||
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
CHECK_AND_ASSERT_MES(main_chain_first_block < m_db_blocks.size(), false, "Intrnal error: main_chain_first_block(" << main_chain_first_block << ") < m_blocks.size() (" << m_db_blocks.size() << ")");
|
||
|
||
for (uint64_t i = main_chain_first_block; i != 0; i--)
|
||
{
|
||
if (is_pos_block(m_db_blocks[i]->bl) == pos)
|
||
{
|
||
cumulative_diff_precise_adj = m_db_blocks[i]->cumulative_diff_precise_adjusted;
|
||
return m_db_blocks[i]->cumulative_diff_precise;
|
||
}
|
||
}
|
||
cumulative_diff_precise_adj = 0;
|
||
return 0;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc)
|
||
{
|
||
TIME_MEASURE_START_PD_MS(block_processing_time_0_ms);
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
TIME_MEASURE_START_PD(block_processing_time_1);
|
||
if(bl.prev_id != get_top_block_id())
|
||
{
|
||
LOG_PRINT_L0("Block with id: " << id << ENDL
|
||
<< "have wrong prev_id: " << bl.prev_id << ENDL
|
||
<< "expected: " << get_top_block_id());
|
||
return false;
|
||
}
|
||
|
||
if(!check_block_timestamp_main(bl))
|
||
{
|
||
LOG_PRINT_L0("Block with id: " << id << ENDL
|
||
<< "have invalid timestamp: " << bl.timestamp);
|
||
//add_block_as_invalid(bl, id);//do not add blocks to invalid storage befor proof of work check was passed
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
|
||
if (m_checkpoints.is_in_checkpoint_zone(get_current_blockchain_size()))
|
||
{
|
||
m_is_in_checkpoint_zone = true;
|
||
if (!m_checkpoints.check_block(get_current_blockchain_size(), id))
|
||
{
|
||
LOG_ERROR("CHECKPOINT VALIDATION FAILED");
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
}
|
||
else
|
||
m_is_in_checkpoint_zone = false;
|
||
|
||
crypto::hash proof_hash = null_hash;
|
||
uint64_t pos_coinstake_amount = 0;
|
||
wide_difficulty_type this_coin_diff = 0;
|
||
bool is_pos_bl = is_pos_block(bl);
|
||
//check if PoS allowed in this height
|
||
CHECK_AND_ASSERT_MES_CUSTOM(!(is_pos_bl && m_db_blocks.size() < m_core_runtime_config.pos_minimum_heigh), false, bvc.m_verification_failed = true, "PoS block not allowed on height " << m_db_blocks.size());
|
||
|
||
//check proof of work
|
||
TIME_MEASURE_START_PD(target_calculating_time_2);
|
||
wide_difficulty_type current_diffic = get_next_diff_conditional(is_pos_bl);
|
||
CHECK_AND_ASSERT_MES_CUSTOM(current_diffic, false, bvc.m_verification_failed = true, "!!!!!!!!! difficulty overhead !!!!!!!!!");
|
||
TIME_MEASURE_FINISH_PD(target_calculating_time_2);
|
||
|
||
TIME_MEASURE_START_PD(longhash_calculating_time_3);
|
||
if (is_pos_bl)
|
||
{
|
||
bool r = validate_pos_block(bl, current_diffic, pos_coinstake_amount, this_coin_diff, proof_hash, id, false);
|
||
CHECK_AND_ASSERT_MES_CUSTOM(r, false, bvc.m_verification_failed = true, "validate_pos_block failed!!");
|
||
}
|
||
else
|
||
{
|
||
|
||
proof_hash = get_block_longhash(bl);
|
||
|
||
if (!check_hash(proof_hash, current_diffic))
|
||
{
|
||
LOG_ERROR("Block with id: " << id << ENDL
|
||
<< "PoW hash: " << proof_hash << ENDL
|
||
<< "nonce: " << bl.nonce << ENDL
|
||
<< "header_mining_hash: " << get_block_header_mining_hash(bl) << ENDL
|
||
<< "expected difficulty: " << current_diffic);
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
}
|
||
TIME_MEASURE_FINISH_PD(longhash_calculating_time_3);
|
||
|
||
size_t aliases_count_befor_block = m_db_aliases.size();
|
||
|
||
if (!prevalidate_miner_transaction(bl, m_db_blocks.size(), is_pos_bl))
|
||
{
|
||
LOG_PRINT_L0("Block with id: " << id
|
||
<< " failed to pass prevalidation");
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
size_t cumulative_block_size = 0;
|
||
size_t coinbase_blob_size = get_object_blobsize(bl.miner_tx);
|
||
|
||
/*
|
||
instead of complicated two-phase template construction and adjustment of cumulative size with block reward we
|
||
use CURRENCY_COINBASE_BLOB_RESERVED_SIZE as penalty-free coinbase transaction reservation.
|
||
*/
|
||
if (coinbase_blob_size > CURRENCY_COINBASE_BLOB_RESERVED_SIZE)
|
||
{
|
||
cumulative_block_size += coinbase_blob_size;
|
||
LOG_PRINT_CYAN("Big coinbase transaction detected: coinbase_blob_size = " << coinbase_blob_size, LOG_LEVEL_0);
|
||
}
|
||
|
||
std::vector<uint64_t> block_fees;
|
||
block_fees.reserve(bl.tx_hashes.size());
|
||
//process transactions
|
||
TIME_MEASURE_START_PD(all_txs_insert_time_5);
|
||
if (!add_transaction_from_block(bl.miner_tx, get_transaction_hash(bl.miner_tx), id, get_current_blockchain_size(), get_actual_timestamp(bl)))
|
||
{
|
||
LOG_PRINT_L0("Block with id: " << id << " failed to add transaction to blockchain storage");
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
//preserve extra data to support alt pos blocks validation
|
||
//amount = > gindex_increment; a map to store how many txout_to_key outputs with such amount this block has in its transactions(including miner tx)
|
||
std::unordered_map<uint64_t, uint32_t> gindices;
|
||
append_per_block_increments_for_tx(bl.miner_tx, gindices);
|
||
|
||
|
||
|
||
size_t tx_processed_count = 0;
|
||
uint64_t fee_summary = 0;
|
||
uint64_t burned_coins = 0;
|
||
|
||
for(const crypto::hash& tx_id : bl.tx_hashes)
|
||
{
|
||
transaction tx;
|
||
size_t blob_size = 0;
|
||
uint64_t fee = 0;
|
||
if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee))
|
||
{
|
||
LOG_PRINT_L0("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id);
|
||
purge_block_data_from_blockchain(bl, tx_processed_count);
|
||
//add_block_as_invalid(bl, id);
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
|
||
append_per_block_increments_for_tx(tx, gindices);
|
||
|
||
//If we under checkpoints, ring signatures should be pruned
|
||
if(m_is_in_checkpoint_zone)
|
||
{
|
||
tx.signatures.clear();
|
||
tx.attachment.clear();
|
||
}
|
||
TIME_MEASURE_START_PD(tx_add_one_tx_time);
|
||
TIME_MEASURE_START_PD(tx_check_inputs_time);
|
||
if(!check_tx_inputs(tx, tx_id))
|
||
{
|
||
LOG_PRINT_L0("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs.");
|
||
currency::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
|
||
bool add_res = m_tx_pool.add_tx(tx, tvc, true, true);
|
||
m_tx_pool.add_transaction_to_black_list(tx);
|
||
CHECK_AND_ASSERT_MES_NO_RET(add_res, "handle_block_to_main_chain: failed to add transaction back to transaction pool");
|
||
purge_block_data_from_blockchain(bl, tx_processed_count);
|
||
add_block_as_invalid(bl, id);
|
||
LOG_PRINT_L0("Block with id " << id << " added as invalid becouse of wrong inputs in transactions");
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
TIME_MEASURE_FINISH_PD(tx_check_inputs_time);
|
||
burned_coins += get_burned_amount(tx);
|
||
|
||
TIME_MEASURE_START_PD(tx_prapare_append);
|
||
uint64_t current_bc_size = get_current_blockchain_size();
|
||
uint64_t actual_timestamp = get_actual_timestamp(bl);
|
||
TIME_MEASURE_FINISH_PD(tx_prapare_append);
|
||
TIME_MEASURE_START_PD(tx_append_time);
|
||
if(!add_transaction_from_block(tx, tx_id, id, current_bc_size, actual_timestamp))
|
||
{
|
||
LOG_PRINT_L0("Block with id: " << id << " failed to add transaction to blockchain storage");
|
||
currency::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
|
||
bool add_res = m_tx_pool.add_tx(tx, tvc, true, true);
|
||
m_tx_pool.add_transaction_to_black_list(tx);
|
||
CHECK_AND_ASSERT_MES_NO_RET(add_res, "handle_block_to_main_chain: failed to add transaction back to transaction pool");
|
||
purge_block_data_from_blockchain(bl, tx_processed_count);
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
TIME_MEASURE_FINISH_PD(tx_append_time);
|
||
//LOG_PRINT_L0("APPEND_TX_TIME: " << m_performance_data.tx_append_time.get_last_val());
|
||
TIME_MEASURE_FINISH_PD(tx_add_one_tx_time);
|
||
fee_summary += fee;
|
||
cumulative_block_size += blob_size;
|
||
++tx_processed_count;
|
||
if (fee)
|
||
block_fees.push_back(fee);
|
||
}
|
||
TIME_MEASURE_FINISH_PD(all_txs_insert_time_5);
|
||
|
||
TIME_MEASURE_START_PD(etc_stuff_6);
|
||
//check aliases count
|
||
if (m_db_aliases.size() - aliases_count_befor_block > MAX_ALIAS_PER_BLOCK)
|
||
{
|
||
LOG_PRINT_L0("Block with id: " << id
|
||
<< " have registered too many aliases " << m_db_aliases.size() - aliases_count_befor_block << ", expected no more than " << MAX_ALIAS_PER_BLOCK);
|
||
purge_block_data_from_blockchain(bl, tx_processed_count);
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
|
||
uint64_t base_reward = 0;
|
||
boost::multiprecision::uint128_t already_generated_coins = m_db_blocks.size() ? m_db_blocks.back()->already_generated_coins:0;
|
||
if (!validate_miner_transaction(bl, cumulative_block_size, fee_summary, base_reward, already_generated_coins))
|
||
{
|
||
LOG_PRINT_L0("Block with id: " << id
|
||
<< " have wrong miner transaction");
|
||
purge_block_data_from_blockchain(bl, tx_processed_count);
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
|
||
//fill block_extended_info
|
||
block_extended_info bei = boost::value_initialized<block_extended_info>();
|
||
bei.bl = bl;
|
||
bei.height = m_db_blocks.size();
|
||
bei.block_cumulative_size = cumulative_block_size;
|
||
bei.difficulty = current_diffic;
|
||
if (is_pos_bl)
|
||
bei.stake_hash = proof_hash;
|
||
|
||
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
//old style cumulative difficulty collecting
|
||
|
||
//precise difficulty - difficulty used to calculate next difficulty
|
||
uint64_t last_x_h = get_last_x_block_height(is_pos_bl);
|
||
if (!last_x_h)
|
||
bei.cumulative_diff_precise = current_diffic;
|
||
else
|
||
bei.cumulative_diff_precise = m_db_blocks[last_x_h]->cumulative_diff_precise + current_diffic;
|
||
|
||
if (m_db_blocks.size())
|
||
{
|
||
bei.cumulative_diff_adjusted = m_db_blocks.back()->cumulative_diff_adjusted;
|
||
}
|
||
|
||
//adjusted difficulty - difficulty used to switch blockchain
|
||
wide_difficulty_type cumulative_diff_delta = 0;
|
||
if (is_pos_bl)
|
||
cumulative_diff_delta = get_adjusted_cumulative_difficulty_for_next_pos(current_diffic);
|
||
else
|
||
cumulative_diff_delta = current_diffic;
|
||
|
||
|
||
size_t sequence_factor = get_current_sequence_factor(is_pos_bl);
|
||
if (bei.height >= m_core_runtime_config.pos_minimum_heigh)
|
||
cumulative_diff_delta = correct_difficulty_with_sequence_factor(sequence_factor, cumulative_diff_delta);
|
||
|
||
if (bei.height > BLOCKCHAIN_HEIGHT_FOR_POS_STRICT_SEQUENCE_LIMITATION && is_pos_bl && sequence_factor > BLOCK_POS_STRICT_SEQUENCE_LIMIT)
|
||
{
|
||
LOG_PRINT_RED_L0("Block " << id << " @ " << bei.height << " has too big sequence factor: " << sequence_factor << ", rejected");
|
||
purge_block_data_from_blockchain(bl, tx_processed_count);
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
|
||
bei.cumulative_diff_adjusted += cumulative_diff_delta;
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
// rebuild cumulative_diff_precise_adjusted for whole period
|
||
wide_difficulty_type diff_precise_adj = correct_difficulty_with_sequence_factor(sequence_factor, current_diffic);
|
||
bei.cumulative_diff_precise_adjusted = last_x_h ? m_db_blocks[last_x_h]->cumulative_diff_precise_adjusted + diff_precise_adj : diff_precise_adj;
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
//etc
|
||
if (already_generated_coins < burned_coins)
|
||
{
|
||
LOG_ERROR("Condition failed: already_generated_coins(" << already_generated_coins << ") >= burned_coins(" << burned_coins << ")");
|
||
purge_block_data_from_blockchain(bl, tx_processed_count);
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
bei.already_generated_coins = already_generated_coins - burned_coins + base_reward;
|
||
|
||
auto blocks_index_ptr = m_db_blocks_index.get(id);
|
||
if (blocks_index_ptr)
|
||
{
|
||
LOG_ERROR("block with id: " << id << " already in block indexes");
|
||
purge_block_data_from_blockchain(bl, tx_processed_count);
|
||
bvc.m_verification_failed = true;
|
||
return false;
|
||
}
|
||
if (bei.height%ALIAS_MEDIAN_RECALC_INTERWAL)
|
||
{
|
||
bei.effective_tx_fee_median = get_tx_fee_median();
|
||
}
|
||
else
|
||
{
|
||
if (bei.height == 0)
|
||
bei.effective_tx_fee_median = 0;
|
||
else
|
||
{
|
||
LOG_PRINT_L0("Recalculating median fee...");
|
||
std::vector<uint64_t> blocks_medians;
|
||
blocks_medians.reserve(ALIAS_COAST_PERIOD);
|
||
for (uint64_t i = bei.height - 1; i != 0 && ALIAS_COAST_PERIOD >= bei.height - i ; i--)
|
||
{
|
||
uint64_t i_median = m_db_blocks[i]->this_block_tx_fee_median;
|
||
if (i_median)
|
||
blocks_medians.push_back(i_median);
|
||
}
|
||
bei.effective_tx_fee_median = epee::misc_utils::median(blocks_medians);
|
||
LOG_PRINT_L0("Median fee recalculated for h = " << bei.height << " as " << print_money(bei.effective_tx_fee_median));
|
||
}
|
||
}
|
||
if (block_fees.size())
|
||
{
|
||
uint64_t block_fee_median = epee::misc_utils::median(block_fees);
|
||
bei.this_block_tx_fee_median = block_fee_median;
|
||
}
|
||
|
||
m_db_blocks_index.set(id, bei.height);
|
||
push_block_to_per_block_increments(bei.height, gindices);
|
||
TIME_MEASURE_FINISH_PD(etc_stuff_6);
|
||
|
||
|
||
TIME_MEASURE_START_PD(insert_time_4);
|
||
m_db_blocks.push_back(bei);
|
||
TIME_MEASURE_FINISH_PD(insert_time_4);
|
||
TIME_MEASURE_FINISH_PD(block_processing_time_1);
|
||
TIME_MEASURE_FINISH_PD_MS(block_processing_time_0_ms);
|
||
|
||
//print result
|
||
stringstream powpos_str_entry, timestamp_str_entry;
|
||
if (is_pos_bl)
|
||
{ // PoS
|
||
int64_t actual_ts = get_actual_timestamp(bei.bl); // signed int is intentionally used here
|
||
int64_t ts_diff = actual_ts - m_core_runtime_config.get_core_time();
|
||
powpos_str_entry << "PoS:\t" << proof_hash << ", stake amount: " << print_money_brief(pos_coinstake_amount) << ", final_difficulty: " << this_coin_diff;
|
||
timestamp_str_entry << ", actual ts: " << actual_ts << " (diff: " << std::showpos << ts_diff << "s) block ts: " << std::noshowpos << bei.bl.timestamp << " (shift: " << std::showpos << static_cast<int64_t>(bei.bl.timestamp) - actual_ts << ")";
|
||
}
|
||
else
|
||
{ // PoW
|
||
int64_t ts_diff = bei.bl.timestamp - static_cast<int64_t>(m_core_runtime_config.get_core_time());
|
||
powpos_str_entry << "PoW:\t" << proof_hash;
|
||
timestamp_str_entry << ", block ts: " << bei.bl.timestamp << " (diff: " << std::showpos << ts_diff << "s)";
|
||
}
|
||
//explanation of this code will be provided later with public announce
|
||
set_lost_tx_unmixable_for_height(bei.height);
|
||
|
||
|
||
LOG_PRINT_L1("+++++ BLOCK SUCCESSFULLY ADDED " << (is_pos_bl ? "[PoS]" : "[PoW]") << " Sq: " << sequence_factor
|
||
<< ENDL << "id:\t" << id << timestamp_str_entry.str()
|
||
<< ENDL << powpos_str_entry.str()
|
||
<< ENDL << "HEIGHT " << bei.height << ", difficulty: " << current_diffic << ", cumul_diff_precise: " << bei.cumulative_diff_precise << ", cumul_diff_adj: " << bei.cumulative_diff_adjusted << " (+" << cumulative_diff_delta << ")"
|
||
<< ENDL << "block reward: " << print_money_brief(base_reward + fee_summary) << " (" << print_money_brief(base_reward) << " + " << print_money_brief(fee_summary)
|
||
<< ")" << ", coinbase_blob_size: " << coinbase_blob_size << ", cumulative size: " << cumulative_block_size << ", tx_count: " << bei.bl.tx_hashes.size()
|
||
<< ", timing: " << block_processing_time_0_ms << "ms"
|
||
<< "(micrsec:" << block_processing_time_1
|
||
<< "(" << target_calculating_time_2 << "(" << m_performance_data.target_calculating_enum_blocks.get_last_val() << "/" << m_performance_data.target_calculating_calc.get_last_val() << ")"
|
||
<< "/" << longhash_calculating_time_3
|
||
<< "/" << insert_time_4
|
||
<< "/" << all_txs_insert_time_5
|
||
<< "/" << etc_stuff_6
|
||
<< "))");
|
||
|
||
on_block_added(bei, id);
|
||
|
||
bvc.m_added_to_main_chain = true;
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::on_block_added(const block_extended_info& bei, const crypto::hash& id)
|
||
{
|
||
update_next_comulative_size_limit();
|
||
m_timestamps_median_cache.clear();
|
||
m_tx_pool.on_blockchain_inc(bei.height, id);
|
||
|
||
update_targetdata_cache_on_block_added(bei);
|
||
|
||
TIME_MEASURE_START_PD(raise_block_core_event);
|
||
rise_core_event(CORE_EVENT_BLOCK_ADDED, void_struct());
|
||
TIME_MEASURE_FINISH_PD(raise_block_core_event);
|
||
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::on_block_removed(const block_extended_info& bei)
|
||
{
|
||
m_tx_pool.on_blockchain_dec(m_db_blocks.size() - 1, get_top_block_id());
|
||
m_timestamps_median_cache.clear();
|
||
update_targetdata_cache_on_block_removed(bei);
|
||
LOG_PRINT_L2("block at height " << bei.height << " was removed from the blockchain");
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::update_targetdata_cache_on_block_added(const block_extended_info& bei)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_targetdata_cache_lock);
|
||
if (bei.height == 0)
|
||
return; //skip genesis
|
||
std::list<std::pair<wide_difficulty_type, uint64_t>>& targetdata_cache = is_pos_block(bei.bl) ? m_pos_targetdata_cache : m_pow_targetdata_cache;
|
||
targetdata_cache.push_back(std::pair<wide_difficulty_type, uint64_t>(bei.cumulative_diff_precise, bei.bl.timestamp));
|
||
while (targetdata_cache.size() > TARGETDATA_CACHE_SIZE)
|
||
targetdata_cache.pop_front();
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::update_targetdata_cache_on_block_removed(const block_extended_info& bei)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_targetdata_cache_lock);
|
||
std::list<std::pair<wide_difficulty_type, uint64_t>>& targetdata_cache = is_pos_block(bei.bl) ? m_pos_targetdata_cache : m_pow_targetdata_cache;
|
||
if (targetdata_cache.size())
|
||
targetdata_cache.pop_back();
|
||
if (targetdata_cache.size() < DIFFICULTY_WINDOW)
|
||
targetdata_cache.clear();
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::load_targetdata_cache(bool is_pos)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_targetdata_cache_lock);
|
||
std::list<std::pair<wide_difficulty_type, uint64_t>>& targetdata_cache = is_pos? m_pos_targetdata_cache: m_pow_targetdata_cache;
|
||
targetdata_cache.clear();
|
||
uint64_t stop_ind = 0;
|
||
uint64_t blocks_size = m_db_blocks.size();
|
||
size_t count = 0;
|
||
for (uint64_t cur_ind = blocks_size - 1; cur_ind != stop_ind && count < DIFFICULTY_WINDOW + 5; cur_ind--)
|
||
{
|
||
auto beiptr = m_db_blocks[cur_ind];
|
||
|
||
bool is_pos_bl = is_pos_block(beiptr->bl);
|
||
if (is_pos != is_pos_bl)
|
||
continue;
|
||
targetdata_cache.push_front(std::pair<wide_difficulty_type, uint64_t>(beiptr->cumulative_diff_precise, beiptr->bl.timestamp));
|
||
++count;
|
||
}
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::on_abort_transaction()
|
||
{
|
||
if (m_event_handler) m_event_handler->on_clear_events();
|
||
CHECK_AND_ASSERT_MES_NO_RET(validate_blockchain_prev_links(), "EPIC FAIL! 4");
|
||
m_timestamps_median_cache.clear();
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::update_next_comulative_size_limit()
|
||
{
|
||
std::vector<size_t> sz;
|
||
get_last_n_blocks_sizes(sz, CURRENCY_REWARD_BLOCKS_WINDOW);
|
||
|
||
uint64_t median = misc_utils::median(sz);
|
||
if(median <= CURRENCY_BLOCK_GRANTED_FULL_REWARD_ZONE)
|
||
median = CURRENCY_BLOCK_GRANTED_FULL_REWARD_ZONE;
|
||
|
||
m_db_current_block_cumul_sz_limit = median * 2;
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::prevalidate_block(const block& bl)
|
||
{
|
||
if (bl.major_version == BLOCK_MAJOR_VERSION_INITAL && get_block_height(bl) <= m_core_runtime_config.hard_fork1_starts_after_height)
|
||
return true;
|
||
if (bl.major_version != CURRENT_BLOCK_MAJOR_VERSION)
|
||
{
|
||
LOG_ERROR("prevalidation failed for block " << get_block_hash(bl) << ": major block version " << static_cast<size_t>(bl.major_version) << " is incorrect, " << CURRENT_BLOCK_MAJOR_VERSION << " is expected" << ENDL
|
||
<< obj_to_json_str(bl));
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::add_new_block(const block& bl, block_verification_context& bvc)
|
||
{
|
||
try
|
||
{
|
||
m_db.begin_transaction();
|
||
|
||
//block bl = bl_;
|
||
crypto::hash id = get_block_hash(bl);
|
||
CRITICAL_REGION_LOCAL(m_tx_pool);
|
||
//CRITICAL_REGION_LOCAL1(m_read_lock);
|
||
if (have_block(id))
|
||
{
|
||
LOG_PRINT_L3("block with id = " << id << " already exists");
|
||
bvc.m_already_exists = true;
|
||
m_db.commit_transaction();
|
||
CHECK_AND_ASSERT_MES(validate_blockchain_prev_links(), false, "EPIC FAIL! 1");
|
||
return false;
|
||
}
|
||
|
||
if (!prevalidate_block(bl))
|
||
{
|
||
LOG_PRINT_RED_L0("block with id = " << id << " failed to prevalidate");
|
||
bvc.m_added_to_main_chain = false;
|
||
bvc.m_verification_failed = true;
|
||
m_db.commit_transaction();
|
||
return false;
|
||
}
|
||
|
||
|
||
//check that block refers to chain tail
|
||
|
||
|
||
if (!(bl.prev_id == get_top_block_id()))
|
||
{
|
||
//chain switching or wrong block
|
||
bvc.m_added_to_main_chain = false;
|
||
bool r = handle_alternative_block(bl, id, bvc);
|
||
if (!r || bvc.m_verification_failed)
|
||
{
|
||
m_db.abort_transaction();
|
||
CHECK_AND_ASSERT_MES(validate_blockchain_prev_links(), false, "EPIC FAIL! 2.2");
|
||
m_tx_pool.on_finalize_db_transaction();
|
||
return r;
|
||
}
|
||
m_db.commit_transaction();
|
||
CHECK_AND_ASSERT_MES(validate_blockchain_prev_links(), false, "EPIC FAIL! 2");
|
||
m_tx_pool.on_finalize_db_transaction();
|
||
return r;
|
||
//never relay alternative blocks
|
||
}
|
||
|
||
|
||
bool res = handle_block_to_main_chain(bl, id, bvc);
|
||
if (bvc.m_verification_failed || !res)
|
||
{
|
||
m_db.abort_transaction();
|
||
m_tx_pool.on_finalize_db_transaction();
|
||
on_abort_transaction();
|
||
if (m_event_handler) m_event_handler->on_clear_events();
|
||
return res;
|
||
}
|
||
m_db.commit_transaction();
|
||
CHECK_AND_ASSERT_MES(validate_blockchain_prev_links(), false, "EPIC FAIL! 3");
|
||
m_tx_pool.on_finalize_db_transaction();
|
||
if (m_event_handler) m_event_handler->on_complete_events();
|
||
|
||
return res;
|
||
}
|
||
catch (const std::exception& ex)
|
||
{
|
||
bvc.m_verification_failed = true;
|
||
bvc.m_added_to_main_chain = false;
|
||
m_db.abort_transaction();
|
||
m_tx_pool.on_finalize_db_transaction();
|
||
on_abort_transaction();
|
||
LOG_ERROR("UNKNOWN EXCEPTION WHILE ADDINIG NEW BLOCK: " << ex.what());
|
||
|
||
return false;
|
||
}
|
||
catch (...)
|
||
{
|
||
bvc.m_verification_failed = true;
|
||
bvc.m_added_to_main_chain = false;
|
||
m_db.abort_transaction();
|
||
m_tx_pool.on_finalize_db_transaction();
|
||
on_abort_transaction();
|
||
LOG_ERROR("UNKNOWN EXCEPTION WHILE ADDINIG NEW BLOCK.");
|
||
return false;
|
||
}
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::truncate_blockchain(uint64_t to_height)
|
||
{
|
||
m_db.begin_transaction();
|
||
uint64_t inital_height = get_current_blockchain_size();
|
||
while (get_current_blockchain_size() > to_height)
|
||
{
|
||
pop_block_from_blockchain();
|
||
}
|
||
CRITICAL_REGION_LOCAL(m_alternative_chains_lock);
|
||
m_alternative_chains.clear();
|
||
m_altblocks_keyimages.clear();
|
||
m_alternative_chains_txs.clear();
|
||
LOG_PRINT_MAGENTA("Blockchain truncated from " << inital_height << " to " << get_current_blockchain_size(), LOG_LEVEL_0);
|
||
m_db.commit_transaction();
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::calc_tx_cummulative_blob(const block& bl)const
|
||
{
|
||
uint64_t cummulative_size_pool = 0;
|
||
uint64_t cummulative_size_calc = 0;
|
||
uint64_t cummulative_size_serialized = 0;
|
||
uint64_t i = 0;
|
||
std::stringstream ss;
|
||
uint64_t calculated_sz_miner = get_object_blobsize(bl.miner_tx);
|
||
blobdata b = t_serializable_object_to_blob(bl.miner_tx);
|
||
uint64_t serialized_size_miner = b.size();
|
||
|
||
ss << "[COINBASE]: " << calculated_sz_miner << "|" << serialized_size_miner << ENDL;
|
||
|
||
for (auto& h : bl.tx_hashes)
|
||
{
|
||
uint64_t calculated_sz = 0;
|
||
uint64_t serialized_size = 0;
|
||
tx_memory_pool::tx_details td = AUTO_VAL_INIT(td);
|
||
bool in_pool = m_tx_pool.get_transaction(h, td);
|
||
if (in_pool)
|
||
{
|
||
calculated_sz = get_object_blobsize(td.tx);
|
||
blobdata b = t_serializable_object_to_blob(td.tx);
|
||
serialized_size = b.size();
|
||
if (td.blob_size != calculated_sz || calculated_sz != serialized_size)
|
||
{
|
||
LOG_PRINT_RED("BLOB SIZE MISMATCH IN TX: " << h << " calculated_sz: " << calculated_sz
|
||
<< " serialized_size: " << serialized_size
|
||
<< " td.blob_size: " << td.blob_size, LOG_LEVEL_0);
|
||
}
|
||
cummulative_size_pool += td.blob_size;
|
||
}
|
||
else
|
||
{
|
||
auto tx_ptr = m_db_transactions.get(h);
|
||
CHECK_AND_ASSERT_MES(tx_ptr, false, "tx " << h << " not found in blockchain nor tx_pool");
|
||
calculated_sz = get_object_blobsize(tx_ptr->tx);
|
||
blobdata b = t_serializable_object_to_blob(tx_ptr->tx);
|
||
serialized_size = b.size();
|
||
if (calculated_sz != serialized_size)
|
||
{
|
||
LOG_PRINT_RED("BLOB SIZE MISMATCH IN TX: " << h << " calculated_sz: " << calculated_sz
|
||
<< " serialized_size: " << serialized_size, LOG_LEVEL_0);
|
||
}
|
||
}
|
||
cummulative_size_calc += calculated_sz;
|
||
cummulative_size_serialized += serialized_size;
|
||
ss << "[" << i << "]" << h << ": " << calculated_sz << ENDL;
|
||
i++;
|
||
|
||
}
|
||
LOG_PRINT_MAGENTA("CUMMULATIVE_BLOCK_SIZE_TRACE: " << get_block_hash(bl) << ENDL
|
||
<< "cummulative_size_calc: " << cummulative_size_calc << ENDL
|
||
<< "cummulative_size_serialized: " << cummulative_size_serialized << ENDL
|
||
<< "cummulative_size_pool: " << cummulative_size_pool << ENDL
|
||
<< ss.str(), LOG_LEVEL_0);
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::build_kernel(const block& bl, stake_kernel& kernel, uint64_t& amount, const stake_modifier_type& stake_modifier) const
|
||
{
|
||
CHECK_AND_ASSERT_MES(bl.miner_tx.vin.size() == 2, false, "wrong miner transaction");
|
||
CHECK_AND_ASSERT_MES(bl.miner_tx.vin[0].type() == typeid(txin_gen), false, "wrong miner transaction");
|
||
CHECK_AND_ASSERT_MES(bl.miner_tx.vin[1].type() == typeid(txin_to_key), false, "wrong miner transaction");
|
||
|
||
const txin_to_key& txin = boost::get<txin_to_key>(bl.miner_tx.vin[1]);
|
||
CHECK_AND_ASSERT_MES(txin.key_offsets.size(), false, "wrong miner transaction");
|
||
amount = txin.amount;
|
||
|
||
return build_kernel(txin.amount, txin.k_image, kernel, stake_modifier, bl.timestamp);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::build_stake_modifier(stake_modifier_type& sm, const alt_chain_type& alt_chain, uint64_t split_height, crypto::hash *p_last_block_hash /* = nullptr */) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
sm = stake_modifier_type();
|
||
|
||
auto pbei_last_pos = get_last_block_of_type(true, alt_chain, split_height);
|
||
auto pbei_last_pow = get_last_block_of_type(false, alt_chain, split_height);
|
||
CHECK_AND_ASSERT_THROW_MES(pbei_last_pow, "Internal error: pbei_last_pow is null");
|
||
|
||
if (pbei_last_pos)
|
||
sm.last_pos_kernel_id = pbei_last_pos->stake_hash;
|
||
else
|
||
{
|
||
bool r = string_tools::parse_tpod_from_hex_string(POS_STARTER_KERNEL_HASH, sm.last_pos_kernel_id);
|
||
CHECK_AND_ASSERT_MES(r, false, "Failed to parse POS_STARTER_MODFIFIER");
|
||
}
|
||
|
||
sm.last_pow_id = get_block_hash(pbei_last_pow->bl);
|
||
|
||
if (p_last_block_hash != nullptr)
|
||
*p_last_block_hash = get_block_hash(m_db_blocks.back()->bl);
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::build_kernel(uint64_t amount,
|
||
const crypto::key_image& ki,
|
||
stake_kernel& kernel,
|
||
const stake_modifier_type& stake_modifier,
|
||
uint64_t timestamp)const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
kernel = stake_kernel();
|
||
kernel.kimage = ki;
|
||
kernel.stake_modifier = stake_modifier;
|
||
kernel.block_timestamp = timestamp;
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::scan_pos(const COMMAND_RPC_SCAN_POS::request& sp, COMMAND_RPC_SCAN_POS::response& rsp) const
|
||
{
|
||
uint64_t timstamp_start = 0;
|
||
wide_difficulty_type basic_diff = 0;
|
||
CRITICAL_REGION_BEGIN(m_read_lock);
|
||
timstamp_start = m_db_blocks.back()->bl.timestamp;
|
||
basic_diff = get_next_diff_conditional(true);
|
||
CRITICAL_REGION_END();
|
||
|
||
stake_modifier_type sm = AUTO_VAL_INIT(sm);
|
||
bool r = build_stake_modifier(sm);
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to build_stake_modifier");
|
||
|
||
for (size_t i = 0; i != sp.pos_entries.size(); i++)
|
||
{
|
||
stake_kernel sk = AUTO_VAL_INIT(sk);
|
||
build_kernel(sp.pos_entries[i].amount, sp.pos_entries[i].keyimage, sk, sm, 0);
|
||
|
||
for (uint64_t ts = timstamp_start; ts < timstamp_start + POS_SCAN_WINDOW; ts++)
|
||
{
|
||
sk.block_timestamp = ts;
|
||
crypto::hash kernel_hash = crypto::cn_fast_hash(&sk, sizeof(sk));
|
||
wide_difficulty_type this_coin_diff = basic_diff / sp.pos_entries[i].amount;
|
||
if (!check_hash(kernel_hash, this_coin_diff))
|
||
continue;
|
||
else
|
||
{
|
||
//found kernel
|
||
LOG_PRINT_GREEN("Found kernel: amount=" << print_money(sp.pos_entries[i].amount) << ", key_image" << sp.pos_entries[i].keyimage, LOG_LEVEL_0);
|
||
rsp.index = i;
|
||
rsp.block_timestamp = ts;
|
||
rsp.status = CORE_RPC_STATUS_OK;
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
rsp.status = CORE_RPC_STATUS_NOT_FOUND;
|
||
return false;
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::set_core_runtime_config(const core_runtime_config& pc) const
|
||
{
|
||
m_core_runtime_config = pc;
|
||
m_services_mgr.set_core_runtime_config(pc);
|
||
}
|
||
//------------------------------------------------------------------
|
||
const core_runtime_config& blockchain_storage::get_core_runtime_config() const
|
||
{
|
||
return m_core_runtime_config;
|
||
}
|
||
//------------------------------------------------------------------
|
||
std::shared_ptr<const transaction_chain_entry> blockchain_storage::get_tx_chain_entry(const crypto::hash& tx_hash) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
return m_db_transactions.find(tx_hash);
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_tx_chain_entry(const crypto::hash& tx_hash, transaction_chain_entry& entry) const
|
||
{
|
||
auto ch_entry_ptr = get_tx_chain_entry(tx_hash);
|
||
if (!ch_entry_ptr)
|
||
return false;
|
||
entry = *ch_entry_ptr;
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::validate_blockchain_prev_links(size_t last_n_blocks_to_check /* = 10 */) const
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
TRY_ENTRY()
|
||
|
||
if (m_db_blocks.size() < 2 || last_n_blocks_to_check < 1)
|
||
return true;
|
||
|
||
bool ok = true;
|
||
for(size_t attempt_counter = 1; attempt_counter <= 2; ++attempt_counter)
|
||
{
|
||
ok = true;
|
||
for (size_t height = m_db_blocks.size() - 1, blocks_to_check = last_n_blocks_to_check; height != 0 && blocks_to_check != 0; --height, --blocks_to_check)
|
||
{
|
||
auto bei_ptr = m_db_blocks[height];
|
||
auto& bei = *bei_ptr;
|
||
if (bei.height != height)
|
||
{
|
||
LOG_ERROR("bei.height = " << bei.height << ", expected: " << height);
|
||
ok = false;
|
||
}
|
||
|
||
auto prev_bei_ptr = m_db_blocks[height - 1];
|
||
auto& prev_bei = *prev_bei_ptr;
|
||
crypto::hash prev_id = get_block_hash(prev_bei.bl);
|
||
if (bei.bl.prev_id != prev_id)
|
||
{
|
||
LOG_ERROR("EPIC FAIL: Block " << get_block_hash(bei.bl) << " @ " << height << " has prev_id == " << bei.bl.prev_id <<
|
||
" while block @ " << height - 1 << " has id: " << prev_id << ENDL <<
|
||
"Block @" << height << ":" << ENDL << currency::obj_to_json_str(bei.bl) << ENDL <<
|
||
"Block @" << height - 1 << ":" << ENDL << currency::obj_to_json_str(prev_bei.bl));
|
||
m_performance_data.epic_failure_happend = true;
|
||
ok = false;
|
||
}
|
||
}
|
||
|
||
if (ok && attempt_counter == 1)
|
||
{
|
||
break;
|
||
}
|
||
else if (!ok && attempt_counter == 1)
|
||
{
|
||
LOG_PRINT_YELLOW("************* EPIC FAIL workaround attempt: try to reset DB cache and re-check *************", LOG_LEVEL_0);
|
||
reset_db_cache();
|
||
continue;
|
||
}
|
||
else if (!ok && attempt_counter == 2)
|
||
{
|
||
LOG_PRINT_RED("************* EPIC FAIL workaround attempt failed *************", LOG_LEVEL_0);
|
||
break;
|
||
}
|
||
else if (ok && attempt_counter == 2)
|
||
{
|
||
LOG_PRINT_GREEN("************* EPIC FAIL workaround attempt succeded! *************", LOG_LEVEL_0);
|
||
break;
|
||
}
|
||
else
|
||
{
|
||
LOG_ERROR("should never get here");
|
||
return false;
|
||
}
|
||
|
||
LOG_ERROR("should never get here also");
|
||
return false;
|
||
}
|
||
|
||
return ok;
|
||
|
||
CATCH_ENTRY2(false);
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::push_block_to_per_block_increments(uint64_t height_, std::unordered_map<uint64_t, uint32_t>& gindices)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
uint32_t height = static_cast<uint32_t>(height_);
|
||
|
||
block_gindex_increments bgi = AUTO_VAL_INIT(bgi);
|
||
bgi.increments.reserve(gindices.size());
|
||
for (auto& v : gindices)
|
||
bgi.increments.push_back(gindex_increment::construct(v.first, v.second));
|
||
|
||
m_db_per_block_gindex_incs.set(height, bgi);
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::pop_block_from_per_block_increments(uint64_t height_)
|
||
{
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
uint32_t height = static_cast<uint32_t>(height_);
|
||
m_db_per_block_gindex_incs.erase(height);
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::calculate_local_gindex_lookup_table_for_height(uint64_t height, std::map<uint64_t, uint64_t>& gindexes) const
|
||
{
|
||
gindexes.clear();
|
||
|
||
CHECK_AND_ASSERT_THROW_MES(m_db_per_block_gindex_incs.size() == m_db_blocks.size(), "invariant failure: m_db_per_block_gindex_incs.size() == " << m_db_per_block_gindex_incs.size() << ", m_db_blocks.size() == " << m_db_blocks.size());
|
||
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
|
||
// Calculate total number of outputs for each amount in the main chain from given height
|
||
size_t top_block_height = static_cast<size_t>(m_db_blocks.size()) - 1;
|
||
uint64_t h = height;
|
||
while (h <= top_block_height)
|
||
{
|
||
auto p = m_db_per_block_gindex_incs.get(h);
|
||
CHECK_AND_ASSERT_THROW_MES(p, "null p for height " << h);
|
||
|
||
for (auto& el : p->increments)
|
||
gindexes[el.amount] += el.increment;
|
||
|
||
++h;
|
||
}
|
||
|
||
// Translate total number of outputs for each amount into global output indexes these amounts had at given height.
|
||
// (those amounts which are not present in the main chain after given height have the same gindexes at given height and at the most recent block)
|
||
for (auto it = gindexes.begin(); it != gindexes.end(); ++it)
|
||
{
|
||
uint64_t amount = it->first;
|
||
uint64_t gindexes_in_mainchain = m_db_outputs.get_item_size(amount);
|
||
CHECK_AND_ASSERT_THROW_MES(gindexes_in_mainchain >= it->second, "inconsistent gindex increments for amount " << amount << ": gindexes_in_mainchain == " << gindexes_in_mainchain << ", gindex increment for height " << height << " is " << it->second);
|
||
it->second = gindexes_in_mainchain - it->second;
|
||
}
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, std::set<crypto::key_image>& collected_keyimages, const crypto::hash& bl_id, const crypto::hash& input_tx_hash, size_t input_index,
|
||
const std::vector<crypto::signature>& input_sigs, uint64_t split_height, const alt_chain_type& alt_chain, const std::set<crypto::hash>& alt_chain_block_ids, uint64_t& ki_lookuptime,
|
||
uint64_t* p_max_related_block_height /* = nullptr */) const
|
||
{
|
||
// Main and alt chain outline:
|
||
//
|
||
// A- A- A- B- B- B- B- <-- main chain
|
||
// |
|
||
// \- C- C- C- <-- alt chain
|
||
// ^
|
||
// |
|
||
// split height
|
||
//
|
||
// List of possible cases
|
||
//
|
||
// | src tx | another | this tx |
|
||
// # | output | tx input | input | meaning
|
||
// ------------ bad cases ------------------
|
||
// b1 A A C already spent in main chain
|
||
// b2 A C C already spent in alt chain
|
||
// b3 C C C already spent in alt chain
|
||
// b4 B B/- C output not found (in case there's no valid outs in altchain C)
|
||
// ------------ good cases ------------------
|
||
// g1 A - C normal spending output from main chain A
|
||
// g2 A B C normal spending output from main chain A (although it is spent in main chain B as well)
|
||
// g3 C - C normal spending output from alt chain C
|
||
// g4 B,C - C src tx added to both main chain B and alt chain C
|
||
// g5 B,C B C src tx added to both main chain B and alt chain C, also spent in B
|
||
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
bool r = false;
|
||
|
||
if (p_max_related_block_height != nullptr)
|
||
*p_max_related_block_height = 0;
|
||
|
||
CHECK_AND_ASSERT_MES(input_index < input_tx.vin.size() && input_tx.vin[input_index].type() == typeid(txin_to_key), false, "invalid input index: " << input_index);
|
||
const txin_to_key& input = boost::get<txin_to_key>(input_tx.vin[input_index]);
|
||
|
||
// check case b1: key_image spent status in main chain, should be either non-spent or has spent height >= split_height
|
||
auto p = m_db_spent_keys.get(input.k_image);
|
||
CHECK_AND_ASSERT_MES(p == nullptr || *p >= split_height, false, "key image " << input.k_image << " has been already spent in main chain at height " << *p << ", split height: " << split_height);
|
||
|
||
TIME_MEASURE_START(ki_lookup_time);
|
||
//check key_image in altchain
|
||
//check among this alt block already collected key images first
|
||
if (collected_keyimages.find(input.k_image) != collected_keyimages.end())
|
||
{
|
||
// cases b2, b3
|
||
LOG_ERROR("key image " << input.k_image << " already spent in this alt block");
|
||
return false;
|
||
}
|
||
auto ki_it = m_altblocks_keyimages.find(input.k_image);
|
||
if (ki_it != m_altblocks_keyimages.end())
|
||
{
|
||
//have some entry for this key image. Check if this key image belongs to this alt chain
|
||
const std::list<crypto::hash>& key_image_block_ids = ki_it->second;
|
||
for (auto& h : key_image_block_ids)
|
||
{
|
||
if (alt_chain_block_ids.find(h) != alt_chain_block_ids.end())
|
||
{
|
||
// cases b2, b3
|
||
LOG_ERROR("key image " << input.k_image << " already spent in altchain");
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
//update altchain with key image
|
||
collected_keyimages.insert(input.k_image);
|
||
TIME_MEASURE_FINISH(ki_lookup_time);
|
||
ki_lookuptime = ki_lookup_time;
|
||
|
||
std::vector<txout_v> abs_key_offsets = relative_output_offsets_to_absolute(input.key_offsets);
|
||
CHECK_AND_ASSERT_MES(abs_key_offsets.size() > 0 && abs_key_offsets.size() == input.key_offsets.size(), false, "internal error: abs_key_offsets.size()==" << abs_key_offsets.size() << ", input.key_offsets.size()==" << input.key_offsets.size());
|
||
// eventually we should found all public keys for all outputs this input refers to, for checking ring signature
|
||
std::vector<crypto::public_key> pub_keys(abs_key_offsets.size(), null_pkey);
|
||
|
||
//
|
||
// let's try to resolve references and populate pub keys container for check_tokey_input()
|
||
//
|
||
uint64_t global_outs_for_amount = 0;
|
||
//figure out if this amount touched alt_chain amount's index and if it is, get
|
||
bool amount_touched_altchain = false;
|
||
//auto abg_it = abei.gindex_lookup_table.find(input.amount);
|
||
//if (abg_it == abei.gindex_lookup_table.end())
|
||
|
||
if (!alt_chain.empty())
|
||
{
|
||
auto abg_it = alt_chain.back()->second.gindex_lookup_table.find(input.amount);
|
||
if (abg_it != alt_chain.back()->second.gindex_lookup_table.end())
|
||
{
|
||
amount_touched_altchain = true;
|
||
//Notice: since transactions is not allowed to refer to each other in one block, then we can consider that index in
|
||
//tx input would be always less then top for previous block, so just take it
|
||
global_outs_for_amount = abg_it->second;
|
||
}
|
||
else
|
||
{
|
||
//quite easy,
|
||
global_outs_for_amount = m_db_outputs.get_item_size(input.amount);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//quite easy,
|
||
global_outs_for_amount = m_db_outputs.get_item_size(input.amount);
|
||
}
|
||
|
||
CHECK_AND_ASSERT_MES(pub_keys.size() == abs_key_offsets.size(), false, "pub_keys.size()==" << pub_keys.size() << " != abs_key_offsets.size()==" << abs_key_offsets.size()); // just a little bit of paranoia
|
||
std::vector<const crypto::public_key*> pub_key_pointers;
|
||
for (size_t pk_n = 0; pk_n < pub_keys.size(); ++pk_n)
|
||
{
|
||
crypto::public_key& pk = pub_keys[pk_n];
|
||
crypto::hash tx_id = null_hash;
|
||
uint64_t out_n = UINT64_MAX;
|
||
auto &off = abs_key_offsets[pk_n];
|
||
if (off.type() == typeid(uint64_t))
|
||
{
|
||
uint64_t offset_gindex = boost::get<uint64_t>(off);
|
||
CHECK_AND_ASSERT_MES(offset_gindex < global_outs_for_amount, false,
|
||
"invalid global output index " << offset_gindex << " for amount=" << input.amount <<
|
||
", max is " << global_outs_for_amount <<
|
||
", referred to by offset #" << pk_n <<
|
||
", amount_touched_altchain = " << amount_touched_altchain);
|
||
if (amount_touched_altchain)
|
||
{
|
||
bool found_the_key = false;
|
||
for (auto alt_it = alt_chain.rbegin(); alt_it != alt_chain.rend(); alt_it++)
|
||
{
|
||
auto it_aag = (*alt_it)->second.gindex_lookup_table.find(input.amount);
|
||
if (it_aag == (*alt_it)->second.gindex_lookup_table.end())
|
||
{
|
||
CHECK_AND_ASSERT_MES(alt_it != alt_chain.rbegin(), false, "internal error: was marked as amount_touched_altchain but unable to find on first entry");
|
||
//item supposed to be in main chain
|
||
break;
|
||
}
|
||
if (offset_gindex >= it_aag->second)
|
||
{
|
||
//GOT IT!!
|
||
//TODO: At the moment we ignore check of mix_attr again mixing to simplify alt chain check, but in future consider it for stronger validation
|
||
uint64_t local_offset = offset_gindex - it_aag->second;
|
||
auto& alt_keys = (*alt_it)->second.outputs_pub_keys;
|
||
CHECK_AND_ASSERT_MES(local_offset < alt_keys[input.amount].size(), false, "Internal error: local_offset=" << local_offset << " while alt_keys[" << input.amount << " ].size()=" << alt_keys.size());
|
||
pk = alt_keys[input.amount][local_offset];
|
||
pub_key_pointers.push_back(&pk);
|
||
found_the_key = true;
|
||
break;
|
||
}
|
||
}
|
||
if (found_the_key)
|
||
break;
|
||
//otherwise lookup in main chain index
|
||
}
|
||
auto p = m_db_outputs.get_subitem(input.amount, offset_gindex);
|
||
CHECK_AND_ASSERT_MES(p != nullptr, false, "global output was not found, amount: " << input.amount << ", gindex: " << offset_gindex << ", referred to by offset #" << pk_n);
|
||
tx_id = p->tx_id;
|
||
out_n = p->out_no;
|
||
}
|
||
else if (off.type() == typeid(ref_by_id))
|
||
{
|
||
auto &rbi = boost::get<ref_by_id>(off);
|
||
tx_id = rbi.tx_id;
|
||
out_n = rbi.n;
|
||
}
|
||
|
||
auto p = m_db_transactions.get(tx_id);
|
||
CHECK_AND_ASSERT_MES(p != nullptr && out_n < p->tx.vout.size(), false, "can't find output #" << out_n << " for tx " << tx_id << " referred by offset #" << pk_n);
|
||
auto &t = p->tx.vout[out_n].target;
|
||
CHECK_AND_ASSERT_MES(t.type() == typeid(txout_to_key), false, "txin_to_key input offset #" << pk_n << " refers to incorrect output type " << t.type().name());
|
||
auto& out_tk = boost::get<txout_to_key>(t);
|
||
pk = out_tk.key;
|
||
bool mixattr_ok = is_mixattr_applicable_for_fake_outs_counter(out_tk.mix_attr, abs_key_offsets.size() - 1);
|
||
CHECK_AND_ASSERT_MES(mixattr_ok, false, "input offset #" << pk_n << " violates mixin restrictions: mix_attr = " << static_cast<uint32_t>(out_tk.mix_attr) << ", input's key_offsets.size = " << abs_key_offsets.size());
|
||
|
||
// case b4 (make sure source tx in the main chain is preceding split point, otherwise this referece is invalid)
|
||
CHECK_AND_ASSERT_MES(p->m_keeper_block_height < split_height, false, "input offset #" << pk_n << " refers to main chain tx " << tx_id << " at height " << p->m_keeper_block_height << " while split height is " << split_height);
|
||
|
||
if (p_max_related_block_height != nullptr && *p_max_related_block_height < p->m_keeper_block_height)
|
||
*p_max_related_block_height = p->m_keeper_block_height;
|
||
|
||
// TODO: consider checking p->tx for unlock time validity as it's checked in get_output_keys_for_input_with_checks()
|
||
// make sure it was actually found
|
||
|
||
// let's disable this check due to missing equal check in main chain validation code
|
||
//TODO: implement more strict validation with next hard fork
|
||
//CHECK_AND_ASSERT_MES(pk != null_pkey, false, "Can't determine output public key for offset " << pk_n << " in related tx: " << tx_id << ", out_n = " << out_n);
|
||
pub_key_pointers.push_back(&pk);
|
||
}
|
||
|
||
// do input checks (attachment_info, ring signature and extra signature, etc.)
|
||
r = check_tokey_input(input_tx, input_index, input, input_tx_hash, input_sigs, pub_key_pointers);
|
||
CHECK_AND_ASSERT_MES(r, false, "to_key input validation failed");
|
||
|
||
// TODO: consider checking input_tx for valid extra attachment info as it's checked in check_tx_inputs()
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::validate_alt_block_ms_input(const transaction& input_tx, const crypto::hash& input_tx_hash, size_t input_index, const std::vector<crypto::signature>& input_sigs, uint64_t split_height, const alt_chain_type& alt_chain) const
|
||
{
|
||
// Main and alt chain outline:
|
||
//
|
||
// A- A- A- B- B- B- B- <-- main chain
|
||
// |
|
||
// \- C- C- C- <-- alt chain
|
||
// ^
|
||
// |
|
||
// split height
|
||
//
|
||
// List of possible cases
|
||
//
|
||
// | src tx | another | this tx |
|
||
// # | output | tx input | input | meaning
|
||
// ------------ bad cases ------------------
|
||
// b1 A A C already spent in main chain
|
||
// b2 A C C already spent in alt chain
|
||
// b3 C C C already spent in alt chain
|
||
// b4 B B/- C output not found (in case there's no valid outs in altchain C)
|
||
// ------------ good cases ------------------
|
||
// g1 A - C normal spending output from main chain A
|
||
// g2 A B C normal spending output from main chain A (although it is spent in main chain B as well)
|
||
// g3 C - C normal spending output from alt chain C
|
||
// g4 B,C - C src tx added to both main chain B and alt chain C
|
||
// g5 B,C B C src tx added to both main chain B and alt chain C, also spent in B
|
||
|
||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||
bool r = false;
|
||
|
||
CHECK_AND_ASSERT_MES(input_index < input_tx.vin.size() && input_tx.vin[input_index].type() == typeid(txin_multisig), false, "invalid ms input index: " << input_index << " or type");
|
||
const txin_multisig& input = boost::get<txin_multisig>(input_tx.vin[input_index]);
|
||
|
||
// check corresponding ms out in the main chain
|
||
auto p = m_db_multisig_outs.get(input.multisig_out_id);
|
||
if (p != nullptr)
|
||
{
|
||
// check case b1 (note: need to distinguish from case g2)
|
||
CHECK_AND_ASSERT_MES(p->spent_height == 0 || p->spent_height >= split_height, false, "ms output was already spent in main chain at height " << p->spent_height << " while split_height is " << split_height);
|
||
|
||
const crypto::hash& source_tx_id = p->tx_id;
|
||
auto p_source_tx = m_db_transactions.get(source_tx_id);
|
||
CHECK_AND_ASSERT_MES(p_source_tx != nullptr, false, "source tx for ms output " << source_tx_id << " was not found, ms out id: " << input.multisig_out_id);
|
||
|
||
if (p_source_tx->m_keeper_block_height < split_height)
|
||
{
|
||
// cases g1, g2
|
||
return check_ms_input(input_tx, input_index, input, input_tx_hash, input_sigs, p_source_tx->tx, p->out_no);
|
||
}
|
||
|
||
// p_source_tx is above split_height in main chain B, so it can't be a source for this input
|
||
// do nohting here and proceed to alt chain scan for possible cases b4, g4, g5
|
||
}
|
||
else
|
||
{
|
||
// ms out was not found in DB
|
||
// do nothing here and proceed to alt chain scan for possible cases b2, b3, g3, g4, g5
|
||
}
|
||
|
||
|
||
// walk the alt chain backward (from the the last added alt block towards split point -- it important as we stop scanning when find correct output, need to make sure it was not already spent)
|
||
for (alt_chain_type::const_reverse_iterator it = alt_chain.rbegin(); it != alt_chain.rend(); ++it)
|
||
{
|
||
const block_extended_info& bei = (*it)->second;
|
||
const block& b = bei.bl;
|
||
bool output_found = false;
|
||
|
||
auto tx_altchain_checker = [&](const transaction& tx, const crypto::hash& tx_id) -> bool
|
||
{
|
||
// check ms out being already spent in current alt chain
|
||
for (auto& in : tx.vin)
|
||
{
|
||
if (in.type() == typeid(txin_multisig))
|
||
{
|
||
// check cases b2, b3
|
||
CHECK_AND_ASSERT_MES(input.multisig_out_id != boost::get<txin_multisig>(in).multisig_out_id, false, "ms out " << input.multisig_out_id << " has been already spent in altchain by tx " << tx_id << " in block " << get_block_hash(b) << " height " << bei.height);
|
||
}
|
||
}
|
||
|
||
for (size_t out_n = 0; out_n < tx.vout.size(); ++out_n)
|
||
{
|
||
const tx_out& out = tx.vout[out_n];
|
||
if (out.target.type() == typeid(txout_multisig))
|
||
{
|
||
const crypto::hash& ms_out_id = get_multisig_out_id(tx, out_n);
|
||
if (ms_out_id == input.multisig_out_id)
|
||
{
|
||
// cases g3, g4, g5
|
||
output_found = true;
|
||
return check_ms_input(input_tx, input_index, input, input_tx_hash, input_sigs, tx, out_n);
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
};
|
||
|
||
// for each alt block look into miner_tx and txs
|
||
CHECK_AND_ASSERT_MES(tx_altchain_checker(b.miner_tx, get_transaction_hash(b.miner_tx)), false, "tx_altchain_checker failed for miner tx");
|
||
if (output_found)
|
||
return true; // g3, g4, g5
|
||
for (auto& tx_id : b.tx_hashes)
|
||
{
|
||
std::shared_ptr<transaction> tx_ptr;
|
||
r = get_transaction_from_pool_or_db(tx_id, tx_ptr, split_height);
|
||
CHECK_AND_ASSERT_MES(r, false, "can't get transaction " << tx_id << " for alt block " << get_block_hash(b) << " height " << bei.height << ", split height is " << split_height);
|
||
transaction& tx = *tx_ptr;
|
||
CHECK_AND_ASSERT_MES(tx_altchain_checker(tx, tx_id), false, "tx_altchain_checker failed for tx " << tx_id);
|
||
if (output_found)
|
||
return true; // g3, g4, g5
|
||
}
|
||
}
|
||
|
||
// case b4
|
||
LOG_ERROR("ms outout " << input.multisig_out_id << " was not found neither in main chain, nor in alt chain");
|
||
return false;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::get_transaction_from_pool_or_db(const crypto::hash& tx_id, std::shared_ptr<transaction>& tx_ptr, uint64_t min_allowed_block_height /* = 0 */) const
|
||
{
|
||
tx_ptr.reset(new transaction());
|
||
|
||
if (!m_tx_pool.get_transaction(tx_id, *tx_ptr)) // first try to get from the pool
|
||
{
|
||
auto p = m_db_transactions.get(tx_id); // if not found in the pool -- get from the DB
|
||
CHECK_AND_ASSERT_MES(p != nullptr, false, "can't get tx " << tx_id << " neither from the pool, nor from db_transactions");
|
||
CHECK_AND_ASSERT_MES(p->m_keeper_block_height >= min_allowed_block_height, false, "tx " << tx_id << " found in the main chain at height " << p->m_keeper_block_height << " while required min allowed height is " << min_allowed_block_height);
|
||
*tx_ptr = p->tx;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
//------------------------------------------------------------------
|
||
bool blockchain_storage::update_alt_out_indexes_for_tx_in_block(const transaction& tx, alt_block_extended_info& abei) const
|
||
{
|
||
//add tx outputs to gindex_lookup_table
|
||
for (auto o : tx.vout)
|
||
{
|
||
if (o.target.type() == typeid(txout_to_key))
|
||
{
|
||
//LOG_PRINT_MAGENTA("ALT_OUT KEY ON H[" << abei.height << "] AMOUNT: " << o.amount, LOG_LEVEL_0);
|
||
// first, look at local gindexes tables
|
||
if (abei.gindex_lookup_table.find(o.amount) == abei.gindex_lookup_table.end())
|
||
{
|
||
// amount was not found in altchain gindexes container, start indexing from current main chain gindex
|
||
abei.gindex_lookup_table[o.amount] = m_db_outputs.get_item_size(o.amount);
|
||
//LOG_PRINT_MAGENTA("FIRST TOUCH: size=" << abei.gindex_lookup_table[o.amount], LOG_LEVEL_0);
|
||
}
|
||
abei.outputs_pub_keys[o.amount].push_back(boost::get<txout_to_key>(o.target).key);
|
||
//TODO: At the moment we ignore check of mix_attr again mixing to simplify alt chain check, but in future consider it for stronger validation
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool blockchain_storage::validate_alt_block_txs(const block& b, const crypto::hash& id, std::set<crypto::key_image>& collected_keyimages, alt_block_extended_info& abei, const alt_chain_type& alt_chain, uint64_t split_height, uint64_t& ki_lookup_time_total) const
|
||
{
|
||
uint64_t height = abei.height;
|
||
bool r = false;
|
||
std::set<crypto::hash> alt_chain_block_ids;
|
||
|
||
alt_chain_block_ids.insert(id);
|
||
// prepare data structure for output global indexes tracking within current alt chain
|
||
if (alt_chain.size())
|
||
{
|
||
//TODO: in this two lines we scarify memory to earn speed for algo, but need to be careful with RAM consuming on long switches
|
||
abei.gindex_lookup_table = alt_chain.back()->second.gindex_lookup_table;
|
||
|
||
//adjust indices for next alt block entry according to emount of pubkeys in txs of prev block
|
||
auto& prev_alt_keys = alt_chain.back()->second.outputs_pub_keys;
|
||
for (auto it = prev_alt_keys.begin(); it != prev_alt_keys.end(); it++)
|
||
{
|
||
auto it_amont_in_abs_ind = abei.gindex_lookup_table.find(it->first);
|
||
CHECK_AND_ASSERT_MES(it_amont_in_abs_ind != abei.gindex_lookup_table.end(), false, "internal error: not found amount " << it->first << "in gindex_lookup_table");
|
||
//increase index starter for amount of outputs in prev block
|
||
it_amont_in_abs_ind->second += it->second.size();
|
||
}
|
||
//generate set of alt block ids
|
||
for (auto& ch : alt_chain)
|
||
{
|
||
alt_chain_block_ids.insert(get_block_hash(ch->second.bl));
|
||
}
|
||
|
||
}
|
||
else
|
||
{
|
||
// initialize alt chain entry with initial gindexes
|
||
calculate_local_gindex_lookup_table_for_height(split_height, abei.gindex_lookup_table);
|
||
}
|
||
|
||
if (is_pos_block(b))
|
||
{
|
||
// check PoS block miner tx in a special way
|
||
CHECK_AND_ASSERT_MES(b.miner_tx.signatures.size() == 1 && b.miner_tx.vin.size() == 2, false, "invalid PoS block's miner_tx, signatures size = " << b.miner_tx.signatures.size() << ", miner_tx.vin.size() = " << b.miner_tx.vin.size());
|
||
uint64_t max_related_block_height = 0;
|
||
uint64_t ki_lookup = 0;
|
||
r = validate_alt_block_input(b.miner_tx, collected_keyimages, id, get_block_hash(b), 1, b.miner_tx.signatures[0], split_height, alt_chain, alt_chain_block_ids, ki_lookup, &max_related_block_height);
|
||
CHECK_AND_ASSERT_MES(r, false, "miner tx " << get_transaction_hash(b.miner_tx) << ": validation failed");
|
||
ki_lookup_time_total += ki_lookup;
|
||
// check stake age
|
||
uint64_t coinstake_age = height - max_related_block_height - 1;
|
||
CHECK_AND_ASSERT_MES(coinstake_age >= m_core_runtime_config.min_coinstake_age, false,
|
||
"miner tx's coinstake age is " << coinstake_age << ", that is less than minimum required " << m_core_runtime_config.min_coinstake_age << "; max_related_block_height == " << max_related_block_height);
|
||
}
|
||
update_alt_out_indexes_for_tx_in_block(b.miner_tx, abei);
|
||
|
||
CHECK_AND_ASSERT_MES(validate_tx_for_hardfork_specific_terms(b.miner_tx, null_hash, height), false, "miner tx hardfork-specific validation failed");
|
||
|
||
for (auto tx_id : b.tx_hashes)
|
||
{
|
||
std::shared_ptr<transaction> tx_ptr;
|
||
CHECK_AND_ASSERT_MES(get_transaction_from_pool_or_db(tx_id, tx_ptr, split_height), false, "failed to get alt block tx " << tx_id << " with split_height == " << split_height);
|
||
transaction& tx = *tx_ptr;
|
||
CHECK_AND_ASSERT_MES(tx.signatures.size() == tx.vin.size(), false, "invalid tx: tx.signatures.size() == " << tx.signatures.size() << ", tx.vin.size() == " << tx.vin.size());
|
||
for (size_t n = 0; n < tx.vin.size(); ++n)
|
||
{
|
||
if (tx.vin[n].type() == typeid(txin_to_key))
|
||
{
|
||
uint64_t ki_lookup = 0;
|
||
r = validate_alt_block_input(tx, collected_keyimages, id, tx_id, n, tx.signatures[n], split_height, alt_chain, alt_chain_block_ids, ki_lookup);
|
||
CHECK_AND_ASSERT_MES(r, false, "tx " << tx_id << ", input #" << n << ": validation failed");
|
||
ki_lookup_time_total += ki_lookup;
|
||
}
|
||
else if (tx.vin[n].type() == typeid(txin_multisig))
|
||
{
|
||
r = validate_alt_block_ms_input(tx, tx_id, n, tx.signatures[n], split_height, alt_chain);
|
||
CHECK_AND_ASSERT_MES(r, false, "tx " << tx_id << ", input #" << n << " (multisig): validation failed");
|
||
}
|
||
else if (tx.vin[n].type() == typeid(txin_gen))
|
||
{
|
||
// genesis can't be in tx_hashes
|
||
CHECK_AND_ASSERT_MES(false, false, "input #" << n << " has unexpected type (" << tx.vin[n].type().name() << ", genesis can't be in tx_hashes), tx " << tx_id);
|
||
}
|
||
else
|
||
{
|
||
CHECK_AND_ASSERT_MES(false, false, "input #" << n << " has unexpected type (" << tx.vin[n].type().name() << "), tx " << tx_id);
|
||
}
|
||
}
|
||
|
||
CHECK_AND_ASSERT_MES(validate_tx_for_hardfork_specific_terms(tx, tx_id, height), false, "tx " << tx_id << ": hardfork-specific validation failed");
|
||
|
||
// Updating abei (and not updating alt_chain) during this cycle is safe because txs in the same block can't reference one another,
|
||
// so only valid references are either to previous alt blocks (accessed via alt_chain) or to main chain blocks.
|
||
update_alt_out_indexes_for_tx_in_block(tx, abei);
|
||
}
|
||
|
||
|
||
return true;
|
||
}
|
||
|
||
|