forked from lthn/blockchain
5864 lines
247 KiB
C++
5864 lines
247 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 //mismatch here means full resync
|
||
#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
|
||
|
||
|
||
|
||
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 = CACHE_SIZE;
|
||
if (command_line::has_arg(vm, arg_db_cache_l1))
|
||
{
|
||
cache_size = command_line::get_arg(vm, arg_db_cache_l1);
|
||
}
|
||
LOG_PRINT_GREEN("Using db file cache size(L1): " << cache_size, LOG_LEVEL_0);
|
||
|
||
|
||
m_config_folder = config_folder;
|
||
LOG_PRINT_L0("Loading blockchain...");
|
||
const std::string folder_name = m_config_folder + "/" CURRENCY_BLOCKCHAINDATA_FOLDERNAME;
|
||
tools::create_directories_if_necessary(folder_name);
|
||
bool res = m_db.open(folder_name, cache_size);
|
||
CHECK_AND_ASSERT_MES(res, false, "Failed to initialize database in folder: " << folder_name);
|
||
|
||
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;
|
||
bool need_reinit_medians = false;
|
||
if (!m_db_blocks.size())
|
||
{
|
||
need_reinit = true;
|
||
}
|
||
else 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)
|
||
{
|
||
if (m_db_storage_minor_compatibility_version < 1)
|
||
need_reinit_medians = true;
|
||
}
|
||
if (need_reinit)
|
||
{
|
||
clear();
|
||
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);
|
||
}
|
||
if (need_reinit_medians)
|
||
{
|
||
bool r = rebuild_tx_fee_medians();
|
||
CHECK_AND_ASSERT_MES(r, false, "failed to rebuild_tx_fee_medians()");
|
||
}
|
||
|
||
|
||
initialize_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
|
||
{
|
||
}
|
||
//------------------------------------------------------------------
|
||
void blockchain_storage::initialize_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();
|
||
initialize_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.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 && pos_block && sequence_factor > 20)
|
||
{
|
||
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, 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;
|
||
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::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);
|
||
|
||
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);
|
||
BOOST_FOREACH(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(source_tx) && is_pos_block(source_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();
|
||
|
||
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
|
||
{
|
||
++it;
|
||
}
|
||
}
|
||
|
||
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_coinbase_outs_unlocktime(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 (major_unlock_time < max_unlock_time)
|
||
return false;
|
||
}
|
||
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_MAC_ACTUAL_TIMESTAMP_TO_MINED) ||
|
||
(actual_ts < b.timestamp && b.timestamp - actual_ts > POS_MAC_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_MAC_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_coinbase_outs_unlocktime(b.miner_tx, coinstake_in.amount, max_unlock_time);
|
||
CHECK_AND_ASSERT_MES(r, false, "Failed to validate_coinbase_outs_unlocktime() 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 - 1;
|
||
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;
|
||
|
||
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);
|
||
|
||
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 > 20)
|
||
{
|
||
LOG_PRINT_L0("Block with id: " << id
|
||
<< " has too big sequence_factor = " << sequence_factor);
|
||
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
|
||
bei.already_generated_coins = already_generated_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("Failed to prevalidate_block: " << get_block_hash(bl) << 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);
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
// 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;
|
||
}
|
||
|
||
|