2014-10-06 23:46:25 +00:00
// Copyright (c) 2014, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
# include <algorithm>
# include <cstdio>
# include <boost/archive/binary_oarchive.hpp>
# include <boost/archive/binary_iarchive.hpp>
2014-10-28 03:44:45 +00:00
# include <boost/filesystem.hpp>
2014-10-06 23:46:25 +00:00
# include "include_base_utils.h"
# include "cryptonote_basic_impl.h"
2014-10-28 03:44:45 +00:00
# include "tx_pool.h"
2014-10-06 23:46:25 +00:00
# include "blockchain.h"
2014-10-28 03:44:45 +00:00
# include "cryptonote_core/blockchain_db.h"
# include "cryptonote_core/BlockchainDB_impl/db_lmdb.h"
2014-10-06 23:46:25 +00:00
# include "cryptonote_format_utils.h"
# include "cryptonote_boost_serialization.h"
# include "cryptonote_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 "serialization/json_archive.h"
/* TODO:
* Clean up code :
* Possibly change how outputs are referred to / indexed in blockchain and wallets
*
*/
using namespace cryptonote ;
DISABLE_VS_WARNINGS ( 4267 )
//------------------------------------------------------------------
// TODO: initialize m_db with a concrete implementation of BlockchainDB
Blockchain : : Blockchain ( tx_memory_pool & tx_pool ) : m_db ( ) , m_tx_pool ( tx_pool ) , m_current_block_cumul_sz_limit ( 0 ) , m_is_in_checkpoint_zone ( false ) , m_is_blockchain_storing ( false )
{
}
//------------------------------------------------------------------
//TODO: is this still needed? I don't think so - tewinget
template < class archive_t >
void Blockchain : : serialize ( archive_t & ar , const unsigned int version )
{
if ( version < 11 )
return ;
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
ar & m_blocks ;
ar & m_blocks_index ;
ar & m_transactions ;
ar & m_spent_keys ;
ar & m_alternative_chains ;
ar & m_outputs ;
ar & m_invalid_blocks ;
ar & m_current_block_cumul_sz_limit ;
/*serialization bug workaround*/
if ( version > 11 )
{
uint64_t total_check_count = m_db - > height ( ) + m_blocks_index . size ( ) + m_transactions . size ( ) + m_spent_keys . size ( ) + m_alternative_chains . size ( ) + m_outputs . size ( ) + m_invalid_blocks . size ( ) + m_current_block_cumul_sz_limit ;
if ( archive_t : : is_saving : : value )
{
ar & total_check_count ;
} else
{
uint64_t total_check_count_loaded = 0 ;
ar & total_check_count_loaded ;
if ( total_check_count ! = total_check_count_loaded )
{
LOG_ERROR ( " Blockchain storage data corruption detected. total_count loaded from file = " < < total_check_count_loaded < < " , expected = " < < total_check_count ) ;
LOG_PRINT_L0 ( " Blockchain storage: " < < std : : endl < <
" m_blocks: " < < m_db - > height ( ) < < std : : endl < <
" m_blocks_index: " < < m_blocks_index . size ( ) < < std : : endl < <
" m_transactions: " < < m_transactions . size ( ) < < std : : endl < <
" m_spent_keys: " < < m_spent_keys . size ( ) < < std : : endl < <
" m_alternative_chains: " < < m_alternative_chains . size ( ) < < std : : endl < <
" m_outputs: " < < m_outputs . size ( ) < < std : : endl < <
" m_invalid_blocks: " < < m_invalid_blocks . size ( ) < < std : : endl < <
" m_current_block_cumul_sz_limit: " < < m_current_block_cumul_sz_limit ) ;
throw std : : runtime_error ( " Blockchain data corruption " ) ;
}
}
}
LOG_PRINT_L2 ( " Blockchain storage: " < < std : : endl < <
" m_blocks: " < < m_db - > height ( ) < < std : : endl < <
" m_blocks_index: " < < m_blocks_index . size ( ) < < std : : endl < <
" m_transactions: " < < m_transactions . size ( ) < < std : : endl < <
" m_spent_keys: " < < m_spent_keys . size ( ) < < std : : endl < <
" m_alternative_chains: " < < m_alternative_chains . size ( ) < < std : : endl < <
" m_outputs: " < < m_outputs . size ( ) < < std : : endl < <
" m_invalid_blocks: " < < m_invalid_blocks . size ( ) < < std : : endl < <
" m_current_block_cumul_sz_limit: " < < m_current_block_cumul_sz_limit ) ;
}
//------------------------------------------------------------------
bool Blockchain : : have_tx ( const crypto : : hash & id )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
return m_db - > tx_exists ( id ) ;
}
//------------------------------------------------------------------
bool Blockchain : : have_tx_keyimg_as_spent ( const crypto : : key_image & key_im )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
return m_db - > has_key_image ( key_im ) ;
}
//------------------------------------------------------------------
// This function makes sure that each "input" in an input (mixins) exists
// and collects the public key for each from the transaction it was included in
// via the visitor passed to it.
template < class visitor_t >
bool Blockchain : : scan_outputkeys_for_indexes ( const txin_to_key & tx_in_to_key , visitor_t & vis , uint64_t * pmax_related_block_height )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
// verify that the input has key offsets (that it exists properly, really)
if ( ! tx_in_to_key . key_offsets . size ( ) )
return false ;
// cryptonote_format_utils uses relative offsets for indexing to the global
// outputs list. that is to say that absolute offset #2 is absolute offset
// #1 plus relative offset #2.
// TODO: Investigate if this is necessary / why this is done.
std : : vector < uint64_t > absolute_offsets = relative_output_offsets_to_absolute ( tx_in_to_key . key_offsets ) ;
//std::vector<std::pair<crypto::hash, size_t> >& amount_outs_vec = it->second;
size_t count = 0 ;
for ( const uint64_t & i : absolute_offsets )
{
try
{
// get tx hash and output index for output
auto output_index = m_db - > get_output_tx_and_index ( tx_in_to_key . amount , i ) ;
// get tx that output is from
auto tx = m_db - > get_tx ( output_index . first ) ;
// make sure output index is within range for the given transaction
if ( output_index . second > = tx . vout . size ( ) )
{
LOG_PRINT_L0 ( " Output does not exist. tx = " < < output_index . first < < " , index = " < < output_index . second ) ;
return false ;
}
// call to the passed boost visitor to grab the public key for the output
if ( ! vis . handle_output ( tx , tx . vout [ output_index . second ] ) )
{
LOG_PRINT_L0 ( " Failed to handle_output for output no = " < < count < < " , with absolute offset " < < i ) ;
return false ;
}
// if on last output and pmax_related_block_height not null pointer
if ( + + count = = absolute_offsets . size ( ) & & pmax_related_block_height )
{
// set *pmax_related_block_height to tx block height for this output
auto h = m_db - > get_tx_block_height ( output_index . first ) ;
if ( * pmax_related_block_height < h )
{
* pmax_related_block_height = h ;
}
}
}
catch ( const OUTPUT_DNE & e )
{
LOG_PRINT_L0 ( " Output does not exist: " < < e . what ( ) ) ;
return false ;
}
catch ( const TX_DNE & e )
{
LOG_PRINT_L0 ( " Transaction does not exist: " < < e . what ( ) ) ;
return false ;
}
}
return true ;
}
//------------------------------------------------------------------
uint64_t Blockchain : : get_current_blockchain_height ( )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
return m_db - > height ( ) ;
}
//------------------------------------------------------------------
//FIXME: possibly move this into the constructor, to avoid accidentally
// dereferencing a null BlockchainDB pointer
2014-10-13 04:31:21 +00:00
bool Blockchain : : init ( const std : : string & config_folder , bool testnet )
2014-10-06 23:46:25 +00:00
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
2014-10-28 03:44:45 +00:00
m_db = new BlockchainLMDB ( ) ;
2014-10-06 23:46:25 +00:00
m_config_folder = config_folder ;
2014-10-28 03:44:45 +00:00
m_testnet = testnet ;
boost : : filesystem : : path folder ( m_config_folder ) ;
// append "testnet" directory as needed
if ( testnet )
{
folder / = " testnet " ;
}
2014-10-06 23:46:25 +00:00
LOG_PRINT_L0 ( " Loading blockchain... " ) ;
//FIXME: update filename for BlockchainDB
2014-10-28 03:44:45 +00:00
const std : : string filename = folder . string ( ) ;
2014-10-06 23:46:25 +00:00
try
{
m_db - > open ( filename ) ;
}
catch ( const DB_OPEN_FAILURE & e )
{
LOG_PRINT_L0 ( " No blockchain file found, attempting to create one. " ) ;
try
{
m_db - > create ( filename ) ;
}
catch ( const DB_CREATE_FAILURE & db_create_error )
{
LOG_PRINT_L0 ( " Unable to create BlockchainDB! This is not good... " ) ;
//TODO: make sure whatever calls this handles the return value properly
return false ;
}
}
// if the blockchain is new, add the genesis block
// this feels kinda kludgy to do it this way, but can be looked at later.
2014-10-13 04:31:21 +00:00
// TODO: add function to create and store genesis block,
// taking testnet into account
2014-10-06 23:46:25 +00:00
if ( ! m_db - > height ( ) )
{
LOG_PRINT_L0 ( " Blockchain not loaded, generating genesis block. " ) ;
block bl = boost : : value_initialized < block > ( ) ;
block_verification_context bvc = boost : : value_initialized < block_verification_context > ( ) ;
2014-10-13 04:31:21 +00:00
if ( testnet )
{
generate_genesis_block ( bl , config : : testnet : : GENESIS_TX , config : : testnet : : GENESIS_NONCE ) ;
}
else
{
generate_genesis_block ( bl , config : : GENESIS_TX , config : : GENESIS_NONCE ) ;
}
2014-10-06 23:46:25 +00:00
add_new_block ( bl , bvc ) ;
2014-10-06 23:56:31 +00:00
CHECK_AND_ASSERT_MES ( ! bvc . m_verifivation_failed , false , " Failed to add genesis block to blockchain " ) ;
2014-10-06 23:46:25 +00:00
}
2014-10-13 04:31:21 +00:00
// TODO: if blockchain load successful, verify blockchain against both
// hard-coded and runtime-loaded (and enforced) checkpoints.
else
{
}
2014-10-06 23:46:25 +00:00
// check how far behind we are
uint64_t top_block_timestamp = m_db - > get_top_block_timestamp ( ) ;
uint64_t timestamp_diff = time ( NULL ) - top_block_timestamp ;
// genesis block has no timestamp, could probably change it to have timestamp of 1341378000...
if ( ! top_block_timestamp )
timestamp_diff = time ( NULL ) - 1341378000 ;
LOG_PRINT_GREEN ( " Blockchain initialized. last block: " < < m_db - > height ( ) - 1 < < " , " < < epee : : misc_utils : : get_time_interval_string ( timestamp_diff ) < < " time ago, current difficulty: " < < get_difficulty_for_next_block ( ) , LOG_LEVEL_0 ) ;
return true ;
}
//------------------------------------------------------------------
bool Blockchain : : store_blockchain ( )
{
// TODO: make sure if this throws that it is not simply ignored higher
// up the call stack
try
{
m_db - > sync ( ) ;
}
catch ( const std : : exception & e )
{
LOG_PRINT_L0 ( std : : string ( " Error syncing blockchain db: " ) + e . what ( ) + " -- shutting down now to prevent issues! " ) ;
throw ;
}
catch ( . . . )
{
LOG_PRINT_L0 ( " There was an issue storing the blockchain, shutting down now to prevent issues! " ) ;
throw ;
}
LOG_PRINT_L0 ( " Blockchain stored OK. " ) ;
return true ;
}
//------------------------------------------------------------------
bool Blockchain : : deinit ( )
{
// as this should be called if handling a SIGSEGV, need to check
// if m_db is a NULL pointer (and thus may have caused the illegal
// memory operation), otherwise we may cause a loop.
if ( m_db = = NULL )
{
throw new DB_ERROR ( " The db pointer is null in Blockchain, the blockchain may be corrupt! " ) ;
}
try
{
m_db - > close ( ) ;
}
catch ( const std : : exception & e )
{
LOG_PRINT_L0 ( std : : string ( " Error closing blockchain db: " ) + e . what ( ) ) ;
}
catch ( . . . )
{
LOG_PRINT_L0 ( " There was an issue closing/storing the blockchain, shutting down now to prevent issues! " ) ;
}
2014-10-28 03:44:45 +00:00
delete m_db ;
2014-10-06 23:46:25 +00:00
return true ;
}
//------------------------------------------------------------------
// This function tells BlockchainDB to remove the top block from the
// blockchain and then returns all transactions (except the miner tx, of course)
// from it to the tx_pool
block Blockchain : : pop_block_from_blockchain ( )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
block popped_block ;
std : : vector < transaction > popped_txs ;
try
{
m_db - > pop_block ( popped_block , popped_txs ) ;
}
// anything that could cause this to throw is likely catastrophic,
// so we re-throw
catch ( const std : : exception & e )
{
LOG_ERROR ( " Error popping block from blockchain: " < < e . what ( ) ) ;
throw ;
}
catch ( . . . )
{
LOG_ERROR ( " Error popping block from blockchain, throwing! " ) ;
throw ;
}
// return transactions from popped block to the tx_pool
for ( transaction & tx : popped_txs )
{
if ( ! is_coinbase ( tx ) )
{
cryptonote : : tx_verification_context tvc = AUTO_VAL_INIT ( tvc ) ;
bool r = m_tx_pool . add_tx ( tx , tvc , true ) ;
if ( ! r )
{
LOG_ERROR ( " Error returning transaction to tx_pool " ) ;
}
}
}
return popped_block ;
}
//------------------------------------------------------------------
bool Blockchain : : reset_and_set_genesis_block ( const block & b )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
m_transactions . clear ( ) ;
m_spent_keys . clear ( ) ;
m_blocks . clear ( ) ;
m_blocks_index . clear ( ) ;
m_alternative_chains . clear ( ) ;
m_outputs . clear ( ) ;
m_db - > reset ( ) ;
block_verification_context bvc = boost : : value_initialized < block_verification_context > ( ) ;
add_new_block ( b , bvc ) ;
2014-10-06 23:56:31 +00:00
return bvc . m_added_to_main_chain & & ! bvc . m_verifivation_failed ;
2014-10-06 23:46:25 +00:00
}
//------------------------------------------------------------------
//TODO: move to BlockchainDB subclass
bool Blockchain : : purge_transaction_keyimages_from_blockchain ( const transaction & tx , bool strict_check )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
struct purge_transaction_visitor : public boost : : static_visitor < bool >
{
key_images_container & m_spent_keys ;
bool m_strict_check ;
purge_transaction_visitor ( key_images_container & spent_keys , bool strict_check ) : m_spent_keys ( spent_keys ) , m_strict_check ( strict_check ) { }
bool operator ( ) ( const txin_to_key & inp ) const
{
//const crypto::key_image& ki = inp.k_image;
auto r = m_spent_keys . find ( inp . k_image ) ;
if ( r ! = m_spent_keys . end ( ) )
{
m_spent_keys . erase ( r ) ;
} else
{
CHECK_AND_ASSERT_MES ( ! m_strict_check , false , " purge_block_data_from_blockchain: key image in transaction not found " ) ;
}
return true ;
}
bool operator ( ) ( const txin_gen & inp ) const
{
return true ;
}
bool operator ( ) ( const txin_to_script & tx ) const
{
return false ;
}
bool operator ( ) ( const txin_to_scripthash & tx ) const
{
return false ;
}
} ;
BOOST_FOREACH ( const txin_v & in , tx . vin )
{
bool r = boost : : apply_visitor ( purge_transaction_visitor ( m_spent_keys , strict_check ) , in ) ;
CHECK_AND_ASSERT_MES ( ! strict_check | | r , false , " failed to process purge_transaction_visitor " ) ;
}
return true ;
}
//------------------------------------------------------------------
crypto : : hash Blockchain : : get_tail_id ( uint64_t & height )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
height = m_db - > height ( ) ;
return get_tail_id ( ) ;
}
//------------------------------------------------------------------
crypto : : hash Blockchain : : get_tail_id ( )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
return m_db - > top_block_hash ( ) ;
}
//------------------------------------------------------------------
/*TODO: this function was...poorly written. As such, I'm not entirely
* certain on what it was supposed to be doing . Need to look into this ,
* but it doesn ' t seem terribly important just yet .
*
* puts into list < ids > a list of hashes representing certain blocks
* from the blockchain in reverse chronological order
*
* the blocks chosen , at the time of this writing , are :
* the most recent 11
* powers of 2 less recent from there , so 13 , 17 , 25 , etc . . .
*
*/
bool Blockchain : : get_short_chain_history ( std : : list < crypto : : hash > & ids )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
uint64_t i = 0 ;
uint64_t current_multiplier = 1 ;
uint64_t sz = m_db - > height ( ) ;
if ( ! sz )
return true ;
uint64_t current_back_offset = 0 ;
while ( current_back_offset < sz )
{
ids . push_back ( m_db - > get_block_hash_from_height ( sz - current_back_offset ) ) ;
if ( i < 10 )
{
+ + current_back_offset ;
}
else
{
current_multiplier * = 2 ;
current_back_offset + = current_multiplier ;
}
+ + i ;
}
return true ;
}
//------------------------------------------------------------------
crypto : : hash Blockchain : : get_block_id_by_height ( uint64_t height )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
try
{
return m_db - > get_block_hash_from_height ( height ) ;
}
catch ( const BLOCK_DNE & e )
{
}
catch ( const std : : exception & e )
{
LOG_PRINT_L0 ( std : : string ( " Something went wrong fetching block hash by height: " ) + e . what ( ) ) ;
throw ;
}
catch ( . . . )
{
LOG_PRINT_L0 ( std : : string ( " Something went wrong fetching block hash by height " ) ) ;
throw ;
}
return null_hash ;
}
//------------------------------------------------------------------
bool Blockchain : : get_block_by_hash ( const crypto : : hash & h , block & blk )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
// try to find block in main chain
try
{
blk = m_db - > get_block ( h ) ;
return true ;
}
// try to find block in alternative chain
catch ( const BLOCK_DNE & e )
{
blocks_ext_by_hash : : const_iterator it_alt = m_alternative_chains . find ( h ) ;
if ( m_alternative_chains . end ( ) ! = it_alt ) {
blk = it_alt - > second . bl ;
return true ;
}
}
catch ( const std : : exception & e )
{
LOG_PRINT_L0 ( std : : string ( " Something went wrong fetching block by hash: " ) + e . what ( ) ) ;
throw ;
}
catch ( . . . )
{
LOG_PRINT_L0 ( std : : string ( " Something went wrong fetching block hash by hash " ) ) ;
throw ;
}
return false ;
}
//------------------------------------------------------------------
2014-10-15 22:33:53 +00:00
//FIXME: this function does not seem to be called from anywhere, but
// if it ever is, should probably change std::list for std::vector
2014-10-06 23:46:25 +00:00
void Blockchain : : get_all_known_block_ids ( std : : list < crypto : : hash > & main , std : : list < crypto : : hash > & alt , std : : list < crypto : : hash > & invalid )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
2014-10-15 22:33:53 +00:00
for ( auto & a : m_db - > get_hashes_range ( 0 , m_db - > height ( ) ) )
{
main . push_back ( a ) ;
}
2014-10-06 23:46:25 +00:00
BOOST_FOREACH ( blocks_ext_by_hash : : value_type & v , m_alternative_chains )
alt . push_back ( v . first ) ;
BOOST_FOREACH ( blocks_ext_by_hash : : value_type & v , m_invalid_blocks )
invalid . push_back ( v . first ) ;
}
//------------------------------------------------------------------
// This function aggregates the cumulative difficulties and timestamps of the
// last DIFFICULTY_BLOCKS_COUNT blocks and passes them to next_difficulty,
// returning the result of that call. Ignores the genesis block, and can use
// less blocks than desired if there aren't enough.
difficulty_type Blockchain : : get_difficulty_for_next_block ( )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
std : : vector < uint64_t > timestamps ;
std : : vector < difficulty_type > cumulative_difficulties ;
auto h = m_db - > height ( ) ;
size_t offset = h - std : : min ( h , static_cast < size_t > ( DIFFICULTY_BLOCKS_COUNT ) ) ;
// because BlockchainDB::height() returns the index of the top block, the
// first index we need to get needs to be one
// higher than height() - DIFFICULTY_BLOCKS_COUNT. This also conveniently
// makes sure we don't use the genesis block.
+ + offset ;
for ( ; offset < = h ; offset + + )
{
timestamps . push_back ( m_db - > get_block_timestamp ( offset ) ) ;
cumulative_difficulties . push_back ( m_db - > get_block_cumulative_difficulty ( offset ) ) ;
}
return next_difficulty ( timestamps , cumulative_difficulties ) ;
}
//------------------------------------------------------------------
// This function removes blocks from the blockchain until it gets to the
// position where the blockchain switch started and then re-adds the blocks
// that had been removed.
2014-10-13 04:31:21 +00:00
bool Blockchain : : rollback_blockchain_switching ( std : : list < block > & original_chain , uint64_t rollback_height )
2014-10-06 23:46:25 +00:00
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
2014-10-13 04:31:21 +00:00
// remove blocks from blockchain until we get back to where we should be.
while ( m_db - > height ( ) ! = rollback_height )
2014-10-06 23:46:25 +00:00
{
pop_block_from_blockchain ( ) ;
}
//return back original chain
for ( 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! " ) ;
}
2014-10-13 04:31:21 +00:00
LOG_PRINT_L1 ( " Rollback to height " < < rollback_height < < " was successful. " ) ;
if ( original_chain . size ( ) )
{
LOG_PRINT_L1 ( " Restoration to previous blockchain successful as well. " ) ;
}
2014-10-06 23:46:25 +00:00
return true ;
}
//------------------------------------------------------------------
// This function attempts to switch to an alternate chain, returning
// boolean based on success therein.
bool Blockchain : : switch_to_alternative_blockchain ( std : : list < blocks_ext_by_hash : : iterator > & alt_chain , bool discard_disconnected_chain )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
// if empty alt chain passed (not sure how that could happen), return false
CHECK_AND_ASSERT_MES ( alt_chain . size ( ) , false , " switch_to_alternative_blockchain: empty chain passed " ) ;
// verify that main chain has front of alt chain's parent block
if ( ! m_db - > block_exists ( alt_chain . front ( ) - > second . bl . prev_id ) )
{
LOG_ERROR ( " Attempting to move to an alternate chain, but it doesn't appear to connect to the main chain! " ) ;
return false ;
}
// pop blocks from the blockchain until the top block is the parent
// of the front block of the alt chain.
std : : list < block > disconnected_chain ;
while ( m_db - > top_block_hash ( ) ! = alt_chain . front ( ) - > second . bl . prev_id )
{
block b = pop_block_from_blockchain ( ) ;
disconnected_chain . push_front ( b ) ;
}
auto split_height = m_db - > height ( ) ;
//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 > ( ) ;
// add block to main chain
bool r = handle_block_to_main_chain ( ch_ent - > second . bl , bvc ) ;
// if adding block to main chain failed, rollback to previous state and
// return false
if ( ! r | | ! bvc . m_added_to_main_chain )
{
LOG_PRINT_L0 ( " Failed to switch to alternative blockchain " ) ;
2014-10-13 04:31:21 +00:00
// rollback_blockchain_switching should be moved to two different
// functions: rollback and apply_chain, but for now we pretend it is
// just the latter (because the rollback was done above).
rollback_blockchain_switching ( disconnected_chain , m_db - > height ( ) ) ;
2014-10-06 23:46:25 +00:00
// FIXME: Why do we keep invalid blocks around? Possibly in case we hear
// about them again so we can immediately dismiss them, but needs some
// looking into.
add_block_as_invalid ( ch_ent - > second , get_block_hash ( ch_ent - > second . bl ) ) ;
LOG_PRINT_L0 ( " The block was inserted as invalid while connecting new alternative chain, block_id: " < < get_block_hash ( ch_ent - > second . bl ) ) ;
m_alternative_chains . erase ( ch_ent ) ;
for ( auto alt_ch_to_orph_iter = + + alt_ch_iter ; alt_ch_to_orph_iter ! = alt_chain . end ( ) ; alt_ch_to_orph_iter + + )
{
add_block_as_invalid ( ( * alt_ch_iter ) - > second , ( * alt_ch_iter ) - > first ) ;
m_alternative_chains . erase ( * alt_ch_to_orph_iter ) ;
}
return false ;
}
}
// if we're to keep the disconnected blocks, add them as alternates
if ( ! discard_disconnected_chain )
{
//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 " ) ;
// previously this would fail the blockchain switching, but I don't
// think this is bad enough to warrant that.
}
}
}
//removing alt_chain entries from alternative chain
BOOST_FOREACH ( auto ch_ent , alt_chain )
{
m_alternative_chains . erase ( ch_ent ) ;
}
LOG_PRINT_GREEN ( " REORGANIZE SUCCESS! on height: " < < split_height < < " , new blockchain size: " < < m_db - > height ( ) , LOG_LEVEL_0 ) ;
return true ;
}
//------------------------------------------------------------------
// This function calculates the difficulty target for the block being added to
// an alternate chain.
difficulty_type Blockchain : : get_next_difficulty_for_alternative_chain ( const std : : list < blocks_ext_by_hash : : iterator > & alt_chain , block_extended_info & bei )
{
std : : vector < uint64_t > timestamps ;
std : : vector < difficulty_type > cumulative_difficulties ;
// if the alt chain isn't long enough to calculate the difficulty target
// based on its blocks alone, need to get more blocks from the main chain
if ( alt_chain . size ( ) < DIFFICULTY_BLOCKS_COUNT )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
// Figure out start and stop offsets for main chain blocks
size_t main_chain_stop_offset = alt_chain . size ( ) ? alt_chain . front ( ) - > second . height : bei . height ;
size_t main_chain_count = DIFFICULTY_BLOCKS_COUNT - std : : min ( static_cast < size_t > ( DIFFICULTY_BLOCKS_COUNT ) , alt_chain . size ( ) ) ;
main_chain_count = std : : min ( main_chain_count , main_chain_stop_offset ) ;
size_t main_chain_start_offset = main_chain_stop_offset - main_chain_count ;
if ( ! main_chain_start_offset )
+ + main_chain_start_offset ; //skip genesis block
// get difficulties and timestamps from relevant main chain blocks
for ( ; main_chain_start_offset < main_chain_stop_offset ; + + main_chain_start_offset )
{
timestamps . push_back ( m_db - > get_block_timestamp ( main_chain_start_offset ) ) ;
cumulative_difficulties . push_back ( m_db - > get_block_cumulative_difficulty ( main_chain_start_offset ) ) ;
}
// make sure we haven't accidentally grabbed too many blocks...maybe don't need this check?
CHECK_AND_ASSERT_MES ( ( alt_chain . size ( ) + timestamps . size ( ) ) < = DIFFICULTY_BLOCKS_COUNT , false ,
" Internal error, alt_chain.size()[ " < < alt_chain . size ( )
< < " ] + vtimestampsec.size()[ " < < timestamps . size ( )
< < " ] NOT <= DIFFICULTY_WINDOW[] " < < DIFFICULTY_BLOCKS_COUNT
) ;
for ( auto it : alt_chain )
{
timestamps . push_back ( it - > second . bl . timestamp ) ;
cumulative_difficulties . push_back ( it - > second . cumulative_difficulty ) ;
}
}
// if the alt chain is long enough for the difficulty calc, grab difficulties
// and timestamps from it alone
else
{
timestamps . resize ( static_cast < size_t > ( DIFFICULTY_BLOCKS_COUNT ) ) ;
cumulative_difficulties . resize ( static_cast < size_t > ( DIFFICULTY_BLOCKS_COUNT ) ) ;
size_t count = 0 ;
size_t max_i = timestamps . size ( ) - 1 ;
// get difficulties and timestamps from most recent blocks in alt chain
BOOST_REVERSE_FOREACH ( auto it , alt_chain )
{
timestamps [ max_i - count ] = it - > second . bl . timestamp ;
cumulative_difficulties [ max_i - count ] = it - > second . cumulative_difficulty ;
count + + ;
if ( count > = DIFFICULTY_BLOCKS_COUNT )
break ;
}
}
// calculate the difficulty target for the block and return it
return next_difficulty ( timestamps , cumulative_difficulties ) ;
}
//------------------------------------------------------------------
// This function does a sanity check on basic things that all miner
// transactions have in common, such as:
// one input, of type txin_gen, with height set to the block's height
// correct miner tx unlock time
// a non-overflowing tx amount (dubious necessity on this check)
bool Blockchain : : prevalidate_miner_transaction ( const block & b , uint64_t height )
{
CHECK_AND_ASSERT_MES ( 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 ;
}
CHECK_AND_ASSERT_MES ( b . miner_tx . unlock_time = = height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW ,
false ,
" coinbase transaction transaction have wrong unlock time= " < < b . miner_tx . unlock_time < < " , expected " < < height + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW ) ;
//check outs overflow
//NOTE: not entirely sure this is necessary, given that this function is
// designed simply to make sure the total amount for a transaction
// does not overflow a uint64_t, and this transaction *is* a uint64_t...
if ( ! check_outs_overflow ( b . miner_tx ) )
{
LOG_PRINT_RED_L0 ( " miner transaction have money overflow in block " < < get_block_hash ( b ) ) ;
return false ;
}
return true ;
}
//------------------------------------------------------------------
// This function validates the miner transaction reward
bool Blockchain : : validate_miner_transaction ( const block & b , size_t cumulative_block_size , uint64_t fee , uint64_t & base_reward , uint64_t already_generated_coins )
{
//validate reward
uint64_t money_in_use = 0 ;
BOOST_FOREACH ( auto & o , b . miner_tx . vout )
money_in_use + = o . amount ;
std : : vector < size_t > last_blocks_sizes ;
get_last_n_blocks_sizes ( last_blocks_sizes , CRYPTONOTE_REWARD_BLOCKS_WINDOW ) ;
if ( ! get_block_reward ( epee : : misc_utils : : median ( last_blocks_sizes ) , cumulative_block_size , already_generated_coins , base_reward ) ) {
LOG_PRINT_L0 ( " block size " < < cumulative_block_size < < " is bigger than allowed for this blockchain " ) ;
return false ;
}
if ( base_reward + fee < money_in_use )
{
LOG_ERROR ( " coinbase transaction spend too much money ( " < < print_money ( money_in_use ) < < " ). Block reward is " < < print_money ( base_reward + fee ) < < " ( " < < print_money ( base_reward ) < < " + " < < print_money ( fee ) < < " ) " ) ;
return false ;
}
if ( base_reward + fee ! = money_in_use )
{
LOG_ERROR ( " coinbase transaction doesn't use full amount of block reward: spent: "
< < print_money ( money_in_use ) < < " , block reward " < < print_money ( base_reward + fee ) < < " ( " < < print_money ( base_reward ) < < " + " < < print_money ( fee ) < < " ) " ) ;
return false ;
}
return true ;
}
//------------------------------------------------------------------
// get the block sizes of the last <count> blocks, starting at <from_height>
// and return by reference <sz>.
void Blockchain : : get_last_n_blocks_sizes ( std : : vector < size_t > & sz , size_t count )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
auto h = m_db - > height ( ) ;
// this function is meaningless for an empty blockchain...granted it should never be empty
if ( h = = 0 )
return ;
// add size of last <count> blocks to vector <sz> (or less, if blockchain size < count)
size_t start_offset = ( h + 1 ) - std : : min ( ( h + 1 ) , count ) ;
for ( size_t i = start_offset ; i < = h ; i + + )
{
sz . push_back ( m_db - > get_block_size ( i ) ) ;
}
}
//------------------------------------------------------------------
uint64_t Blockchain : : get_current_cumulative_blocksize_limit ( )
{
return m_current_block_cumul_sz_limit ;
}
//------------------------------------------------------------------
//TODO: This function only needed minor modification to work with BlockchainDB,
// and *works*. As such, to reduce the number of things that might break
// in moving to BlockchainDB, this function will remain otherwise
// unchanged for the time being.
//
// This function makes a new block for a miner to mine the hash for
//
// FIXME: this codebase references #if defined(DEBUG_CREATE_BLOCK_TEMPLATE)
// in a lot of places. That flag is not referenced in any of the code
// nor any of the makefiles, howeve. Need to look into whether or not it's
// necessary at all.
bool Blockchain : : create_block_template ( block & b , const account_public_address & miner_address , difficulty_type & diffic , uint64_t & height , const blobdata & ex_nonce )
{
size_t median_size ;
uint64_t already_generated_coins ;
CRITICAL_REGION_BEGIN ( m_blockchain_lock ) ;
b . major_version = CURRENT_BLOCK_MAJOR_VERSION ;
b . minor_version = CURRENT_BLOCK_MINOR_VERSION ;
b . prev_id = get_tail_id ( ) ;
b . timestamp = time ( NULL ) ;
auto old_height = m_db - > height ( ) ;
height = old_height + 1 ;
diffic = get_difficulty_for_next_block ( ) ;
CHECK_AND_ASSERT_MES ( diffic , false , " difficulty owverhead. " ) ;
median_size = m_current_block_cumul_sz_limit / 2 ;
already_generated_coins = m_db - > get_block_already_generated_coins ( old_height ) ;
CRITICAL_REGION_END ( ) ;
size_t txs_size ;
uint64_t fee ;
if ( ! m_tx_pool . fill_block_template ( b , median_size , already_generated_coins , txs_size , fee ) ) {
return false ;
}
# if defined(DEBUG_CREATE_BLOCK_TEMPLATE)
size_t real_txs_size = 0 ;
uint64_t real_fee = 0 ;
CRITICAL_REGION_BEGIN ( m_tx_pool . m_transactions_lock ) ;
BOOST_FOREACH ( crypto : : hash & cur_hash , b . tx_hashes ) {
auto cur_res = m_tx_pool . m_transactions . find ( cur_hash ) ;
if ( cur_res = = m_tx_pool . m_transactions . end ( ) ) {
LOG_ERROR ( " Creating block template: error: transaction not found " ) ;
continue ;
}
tx_memory_pool : : tx_details & cur_tx = cur_res - > second ;
real_txs_size + = cur_tx . blob_size ;
real_fee + = cur_tx . fee ;
if ( cur_tx . blob_size ! = get_object_blobsize ( cur_tx . tx ) ) {
LOG_ERROR ( " Creating block template: error: invalid transaction size " ) ;
}
uint64_t inputs_amount ;
if ( ! get_inputs_money_amount ( cur_tx . tx , inputs_amount ) ) {
LOG_ERROR ( " Creating block template: error: cannot get inputs amount " ) ;
} else if ( cur_tx . fee ! = inputs_amount - get_outs_money_amount ( cur_tx . tx ) ) {
LOG_ERROR ( " Creating block template: error: invalid fee " ) ;
}
}
if ( txs_size ! = real_txs_size ) {
LOG_ERROR ( " Creating block template: error: wrongly calculated transaction size " ) ;
}
if ( fee ! = real_fee ) {
LOG_ERROR ( " Creating block template: error: wrongly calculated fee " ) ;
}
CRITICAL_REGION_END ( ) ;
LOG_PRINT_L1 ( " Creating block template: height " < < height < <
" , median size " < < median_size < <
" , already generated coins " < < already_generated_coins < <
" , transaction size " < < txs_size < <
" , fee " < < fee ) ;
# endif
/*
two - phase miner transaction generation : we don ' t know exact block size until we prepare block , but we don ' t know reward until we know
block size , so first miner transaction generated with fake amount of money , and with phase we know think we know expected block size
*/
//make blocks coin-base tx looks close to real coinbase tx to get truthful blob size
bool r = construct_miner_tx ( height , median_size , already_generated_coins , txs_size , fee , miner_address , b . miner_tx , ex_nonce , 11 ) ;
CHECK_AND_ASSERT_MES ( r , false , " Failed to construc miner tx, first chance " ) ;
size_t cumulative_size = txs_size + get_object_blobsize ( b . miner_tx ) ;
# if defined(DEBUG_CREATE_BLOCK_TEMPLATE)
LOG_PRINT_L1 ( " Creating block template: miner tx size " < < get_object_blobsize ( b . miner_tx ) < <
" , cumulative size " < < cumulative_size ) ;
# endif
for ( size_t try_count = 0 ; try_count ! = 10 ; + + try_count ) {
r = construct_miner_tx ( height , median_size , already_generated_coins , cumulative_size , fee , miner_address , b . miner_tx , ex_nonce , 11 ) ;
CHECK_AND_ASSERT_MES ( r , false , " Failed to construc miner tx, second chance " ) ;
size_t coinbase_blob_size = get_object_blobsize ( b . miner_tx ) ;
if ( coinbase_blob_size > cumulative_size - txs_size ) {
cumulative_size = txs_size + coinbase_blob_size ;
# if defined(DEBUG_CREATE_BLOCK_TEMPLATE)
LOG_PRINT_L1 ( " Creating block template: miner tx size " < < coinbase_blob_size < <
" , cumulative size " < < cumulative_size < < " is greater then before " ) ;
# endif
continue ;
}
if ( coinbase_blob_size < cumulative_size - txs_size ) {
size_t delta = cumulative_size - txs_size - coinbase_blob_size ;
# if defined(DEBUG_CREATE_BLOCK_TEMPLATE)
LOG_PRINT_L1 ( " Creating block template: miner tx size " < < coinbase_blob_size < <
" , cumulative size " < < txs_size + coinbase_blob_size < <
" is less then before, adding " < < delta < < " zero bytes " ) ;
# endif
b . miner_tx . extra . insert ( b . miner_tx . extra . end ( ) , delta , 0 ) ;
//here could be 1 byte difference, because of extra field counter is varint, and it can become from 1-byte len to 2-bytes len.
if ( cumulative_size ! = txs_size + get_object_blobsize ( b . miner_tx ) ) {
CHECK_AND_ASSERT_MES ( cumulative_size + 1 = = txs_size + get_object_blobsize ( b . miner_tx ) , false , " unexpected case: cumulative_size= " < < cumulative_size < < " + 1 is not equal txs_cumulative_size= " < < txs_size < < " + get_object_blobsize(b.miner_tx)= " < < get_object_blobsize ( b . miner_tx ) ) ;
b . miner_tx . extra . resize ( b . miner_tx . extra . size ( ) - 1 ) ;
if ( cumulative_size ! = txs_size + get_object_blobsize ( b . miner_tx ) ) {
//fuck, not lucky, -1 makes varint-counter size smaller, in that case we continue to grow with cumulative_size
LOG_PRINT_RED ( " Miner tx creation have no luck with delta_extra size = " < < delta < < " and " < < delta - 1 , LOG_LEVEL_2 ) ;
cumulative_size + = delta - 1 ;
continue ;
}
LOG_PRINT_GREEN ( " Setting extra for block: " < < b . miner_tx . extra . size ( ) < < " , try_count= " < < try_count , LOG_LEVEL_1 ) ;
}
}
CHECK_AND_ASSERT_MES ( cumulative_size = = txs_size + get_object_blobsize ( b . miner_tx ) , false , " unexpected case: cumulative_size= " < < cumulative_size < < " is not equal txs_cumulative_size= " < < txs_size < < " + get_object_blobsize(b.miner_tx)= " < < get_object_blobsize ( b . miner_tx ) ) ;
# if defined(DEBUG_CREATE_BLOCK_TEMPLATE)
LOG_PRINT_L1 ( " Creating block template: miner tx size " < < coinbase_blob_size < <
" , cumulative size " < < cumulative_size < < " is now good " ) ;
# endif
return true ;
}
LOG_ERROR ( " Failed to create_block_template with " < < 10 < < " tries " ) ;
return false ;
}
//------------------------------------------------------------------
// for an alternate chain, get the timestamps from the main chain to complete
// the needed number of timestamps for the BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW.
bool Blockchain : : 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_blockchain_lock ) ;
size_t need_elements = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW - timestamps . size ( ) ;
CHECK_AND_ASSERT_MES ( start_top_height < = m_db - > height ( ) , false , " internal error: passed start_height > " < < " m_db->height() -- " < < start_top_height < < " > " < < m_db - > height ( ) ) ;
size_t stop_offset = start_top_height > need_elements ? start_top_height - need_elements : 0 ;
while ( start_top_height ! = stop_offset ) ;
{
timestamps . push_back ( m_db - > get_block_timestamp ( start_top_height ) ) ;
- - start_top_height ;
}
return true ;
}
//------------------------------------------------------------------
// If a block is to be added and its parent block is not the current
// main chain top block, then we need to see if we know about its parent block.
// If its parent block is part of a known forked chain, then we need to see
// if that chain is long enough to become the main chain and re-org accordingly
// if so. If not, we need to hang on to the block in case it becomes part of
// a long forked chain eventually.
bool Blockchain : : handle_alternative_block ( const block & b , const crypto : : hash & id , block_verification_context & bvc )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
uint64_t block_height = get_block_height ( b ) ;
if ( 0 = = block_height )
{
LOG_ERROR ( " Block with id: " < < epee : : string_tools : : pod_to_hex ( id ) < < " (as alternative) have wrong miner transaction " ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
return false ;
}
// TODO: this basically says if the blockchain is smaller than the first
// checkpoint then alternate blocks are allowed...this seems backwards, but
// I'm not sure. Needs further investigating.
if ( ! m_checkpoints . is_alternative_block_allowed ( get_current_blockchain_height ( ) , block_height ) )
{
LOG_PRINT_RED_L0 ( " Block with id: " < < id
< < std : : endl < < " can't be accepted for alternative chain, block height: " < < block_height
< < std : : endl < < " blockchain height: " < < get_current_blockchain_height ( ) ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
return false ;
}
//block is not related with head of main chain
//first of all - look in alternative chains container
auto it_prev = m_alternative_chains . find ( b . prev_id ) ;
bool parent_in_main = m_db - > block_exists ( b . prev_id ) ;
if ( it_prev ! = m_alternative_chains . end ( ) | | parent_in_main )
{
//we have new block in alternative chain
//build alternative subchain, front -> mainchain, back -> alternative head
blocks_ext_by_hash : : iterator alt_it = it_prev ; //m_alternative_chains.find()
std : : list < blocks_ext_by_hash : : iterator > alt_chain ;
std : : vector < uint64_t > timestamps ;
while ( alt_it ! = m_alternative_chains . end ( ) )
{
alt_chain . push_front ( alt_it ) ;
timestamps . push_back ( alt_it - > second . bl . timestamp ) ;
alt_it = m_alternative_chains . find ( alt_it - > second . bl . prev_id ) ;
}
// if block to be added connects to known blocks that aren't part of the
// main chain -- that is, if we're adding on to an alternate chain
if ( alt_chain . size ( ) )
{
// make sure alt chain doesn't somehow start past the end of the main chain
CHECK_AND_ASSERT_MES ( m_db - > height ( ) > alt_chain . front ( ) - > second . height , false , " main blockchain wrong height " ) ;
// make sure that the blockchain contains the block that should connect
// this alternate chain with it.
if ( ! m_db - > block_exists ( alt_chain . front ( ) - > second . bl . prev_id ) )
{
LOG_PRINT_L1 ( " alternate chain does not appear to connect to main chain... " ) ;
return false ;
}
// make sure block connects correctly to the main chain
auto h = m_db - > get_block_hash_from_height ( alt_chain . front ( ) - > second . height - 1 ) ;
CHECK_AND_ASSERT_MES ( h = = alt_chain . front ( ) - > second . bl . prev_id , false , " alternative chain have wrong connection to main chain " ) ;
complete_timestamps_vector ( m_db - > get_block_height ( alt_chain . front ( ) - > second . bl . prev_id ) , timestamps ) ;
}
// if block not associated with known alternate chain
else
{
// if block parent is not part of main chain or an alternate chain,
// we ignore it
CHECK_AND_ASSERT_MES ( parent_in_main , false , " internal error: broken imperative condition it_main_prev != m_blocks_index.end() " ) ;
complete_timestamps_vector ( m_db - > get_block_height ( b . prev_id ) , timestamps ) ;
}
// verify that the block's timestamp is within the acceptable range
// (not earlier than the median of the last X blocks)
if ( ! check_block_timestamp ( timestamps , b ) )
{
LOG_PRINT_RED_L0 ( " Block with id: " < < id
< < std : : endl < < " for alternative chain, have invalid timestamp: " < < b . timestamp ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
return false ;
}
// FIXME: consider moving away from block_extended_info at some point
block_extended_info bei = boost : : value_initialized < block_extended_info > ( ) ;
bei . bl = b ;
bei . height = alt_chain . size ( ) ? it_prev - > second . height + 1 : m_db - > get_block_height ( b . prev_id ) + 1 ;
bool is_a_checkpoint ;
if ( ! m_checkpoints . check_block ( bei . height , id , is_a_checkpoint ) )
{
LOG_ERROR ( " CHECKPOINT VALIDATION FAILED " ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
return false ;
}
// Check the block's hash against the difficulty target for its alt chain
m_is_in_checkpoint_zone = false ;
difficulty_type current_diff = get_next_difficulty_for_alternative_chain ( alt_chain , bei ) ;
CHECK_AND_ASSERT_MES ( current_diff , false , " !!!!!!! DIFFICULTY OVERHEAD !!!!!!! " ) ;
crypto : : hash proof_of_work = null_hash ;
get_block_longhash ( bei . bl , proof_of_work , bei . height ) ;
if ( ! check_hash ( proof_of_work , current_diff ) )
{
LOG_PRINT_RED_L0 ( " Block with id: " < < id
< < std : : endl < < " for alternative chain, have not enough proof of work: " < < proof_of_work
< < std : : endl < < " expected difficulty: " < < current_diff ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
return false ;
}
if ( ! prevalidate_miner_transaction ( b , bei . height ) )
{
LOG_PRINT_RED_L0 ( " Block with id: " < < epee : : string_tools : : pod_to_hex ( id )
< < " (as alternative) have wrong miner transaction. " ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
return false ;
}
// FIXME:
// this brings up an interesting point: consider allowing to get block
// difficulty both by height OR by hash, not just height.
bei . cumulative_difficulty = alt_chain . size ( ) ? it_prev - > second . cumulative_difficulty : m_db - > get_block_cumulative_difficulty ( m_db - > get_block_height ( b . prev_id ) ) ;
bei . cumulative_difficulty + = current_diff ;
// add block to alternate blocks storage,
// as well as the current "alt chain" container
auto i_res = m_alternative_chains . insert ( blocks_ext_by_hash : : value_type ( id , bei ) ) ;
CHECK_AND_ASSERT_MES ( i_res . second , false , " insertion of new alternative block returned as it already exist " ) ;
alt_chain . push_back ( i_res . first ) ;
// FIXME: is it even possible for a checkpoint to show up not on the main chain?
if ( is_a_checkpoint )
{
//do reorganize!
LOG_PRINT_GREEN ( " ###### REORGANIZE on height: " < < alt_chain . front ( ) - > second . height < < " of " < < m_db - > height ( ) - 1 < <
" , checkpoint is found in alternative chain on height " < < bei . height , LOG_LEVEL_0 ) ;
bool r = switch_to_alternative_blockchain ( alt_chain , true ) ;
bvc . m_added_to_main_chain = r ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = ! r ;
2014-10-06 23:46:25 +00:00
return r ;
}
else if ( m_blocks . back ( ) . cumulative_difficulty < bei . cumulative_difficulty ) //check if difficulty bigger then in main chain
{
//do reorganize!
LOG_PRINT_GREEN ( " ###### REORGANIZE on height: "
< < alt_chain . front ( ) - > second . height < < " of " < < m_db - > height ( )
< < " with cum_difficulty " < < m_db - > get_block_cumulative_difficulty ( m_db - > height ( ) )
< < std : : endl < < " alternative blockchain size: " < < alt_chain . size ( )
< < " with cum_difficulty " < < bei . cumulative_difficulty , LOG_LEVEL_0
) ;
bool r = switch_to_alternative_blockchain ( alt_chain , false ) ;
if ( r ) bvc . m_added_to_main_chain = true ;
2014-10-06 23:56:31 +00:00
else bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
return r ;
}
else
{
LOG_PRINT_BLUE ( " ----- BLOCK ADDED AS ALTERNATIVE ON HEIGHT " < < bei . height
< < std : : endl < < " id: \t " < < id
< < std : : endl < < " PoW: \t " < < proof_of_work
< < std : : endl < < " difficulty: \t " < < current_diff , LOG_LEVEL_0 ) ;
return true ;
}
}
else
{
//block orphaned
bvc . m_marked_as_orphaned = true ;
LOG_PRINT_RED_L0 ( " Block recognized as orphaned and rejected, id = " < < id ) ;
}
return true ;
}
//------------------------------------------------------------------
bool Blockchain : : get_blocks ( uint64_t start_offset , size_t count , std : : list < block > & blocks , std : : list < transaction > & txs )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
if ( start_offset > m_db - > height ( ) )
return false ;
if ( ! get_blocks ( start_offset , count , blocks ) )
{
return false ;
}
for ( const block & blk : blocks )
{
std : : list < crypto : : hash > missed_ids ;
get_transactions ( blk . 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 : : get_blocks ( uint64_t start_offset , size_t count , std : : list < block > & blocks )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
if ( start_offset > m_db - > height ( ) )
return false ;
for ( size_t i = start_offset ; i < start_offset + count & & i < = m_db - > height ( ) ; i + + )
{
blocks . push_back ( m_db - > get_block_from_height ( i ) ) ;
}
return true ;
}
//------------------------------------------------------------------
//TODO: This function *looks* like it won't need to be rewritten
// to use BlockchainDB, as it calls other functions that were,
// but it warrants some looking into later.
bool Blockchain : : handle_get_objects ( NOTIFY_REQUEST_GET_OBJECTS : : request & arg , NOTIFY_RESPONSE_GET_OBJECTS : : request & rsp )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
rsp . current_blockchain_height = get_current_blockchain_height ( ) ;
std : : list < block > blocks ;
get_blocks ( arg . blocks , blocks , rsp . missed_ids ) ;
BOOST_FOREACH ( const auto & bl , blocks )
{
std : : list < crypto : : hash > missed_tx_id ;
std : : list < transaction > txs ;
get_transactions ( bl . tx_hashes , txs , rsp . missed_ids ) ;
CHECK_AND_ASSERT_MES ( ! missed_tx_id . size ( ) , false , " Internal error: have missed missed_tx_id.size()= " < < missed_tx_id . size ( )
< < std : : 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 : : get_alternative_blocks ( std : : list < block > & blocks )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
BOOST_FOREACH ( const auto & alt_bl , m_alternative_chains )
{
blocks . push_back ( alt_bl . second . bl ) ;
}
return true ;
}
//------------------------------------------------------------------
size_t Blockchain : : get_alternative_blocks_count ( )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
return m_alternative_chains . size ( ) ;
}
//------------------------------------------------------------------
// This function adds the output specified by <amount, i> to the result_outs container
// unlocked and other such checks should be done by here.
void Blockchain : : add_out_to_get_random_outs ( COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS : : outs_for_amount & result_outs , uint64_t amount , size_t i )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
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 = m_db - > get_output_key ( amount , i ) ;
}
//------------------------------------------------------------------
// This function takes an RPC request for mixins and creates an RPC response
// with the requested mixins.
// TODO: figure out why this returns boolean / if we should be returning false
// in some cases
bool Blockchain : : get_random_outs_for_amounts ( const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS : : request & req , COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS : : response & res )
{
srand ( static_cast < unsigned int > ( time ( NULL ) ) ) ;
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
// for each amount that we need to get mixins for, get <n> random outputs
// from BlockchainDB where <n> is req.outs_count (number of mixins).
for ( uint64_t amount : req . amounts )
{
// create outs_for_amount struct and populate amount field
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 ;
std : : unordered_set < uint64_t > seen_indices ;
// if there aren't enough outputs to mix with (or just enough),
// use all of them. Eventually this should become impossible.
if ( m_db - > get_num_outputs ( amount ) < = req . outs_count )
{
for ( uint64_t i = 0 ; i < m_db - > get_num_outputs ( amount ) ; i + + )
{
// get tx_hash, tx_out_index from DB
tx_out_index toi = m_db - > get_output_tx_and_index ( amount , i ) ;
// if tx is unlocked, add output to result_outs
if ( is_tx_spendtime_unlocked ( m_db - > get_tx_unlock_time ( toi . first ) ) )
{
add_out_to_get_random_outs ( result_outs , amount , i ) ;
}
}
}
else
{
// while we still need more mixins
auto num_outs = m_db - > get_num_outputs ( amount ) ;
while ( result_outs . outs . size ( ) < req . outs_count )
{
// if we've gone through every possible output, we've gotten all we can
if ( seen_indices . size ( ) = = num_outs )
{
break ;
}
// get a random output index from the DB. If we've already seen it,
// return to the top of the loop and try again, otherwise add it to the
// list of output indices we've seen.
uint64_t i = m_db - > get_random_output ( amount ) ;
if ( seen_indices . count ( i ) )
{
continue ;
}
seen_indices . emplace ( i ) ;
// get tx_hash, tx_out_index from DB
tx_out_index toi = m_db - > get_output_tx_and_index ( amount , i ) ;
// if the output's transaction is unlocked, add the output's index to
// our list.
if ( is_tx_spendtime_unlocked ( m_db - > get_tx_unlock_time ( toi . first ) ) )
{
add_out_to_get_random_outs ( result_outs , amount , i ) ;
}
}
}
}
return true ;
}
//------------------------------------------------------------------
// This function takes a list of block hashes from another node
// on the network to find where the split point is between us and them.
// This is used to see what to send another node that needs to sync.
bool Blockchain : : find_blockchain_supplement ( const std : : list < crypto : : hash > & qblock_ids , uint64_t & starter_offset )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
// make sure the request includes at least the genesis block, otherwise
// how can we expect to sync from the client that the block list came from?
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 ;
}
// make sure that the last block in the request's block list matches
// the genesis block
auto gen_hash = m_db - > get_block_hash_from_height ( 0 ) ;
if ( qblock_ids . back ( ) ! = gen_hash )
{
LOG_ERROR ( " Client sent wrong NOTIFY_REQUEST_CHAIN: genesis block missmatch: " < < std : : endl < < " id: "
< < qblock_ids . back ( ) < < " , " < < std : : endl < < " expected: " < < gen_hash
< < " , " < < std : : endl < < " dropping connection " ) ;
return false ;
}
// Find the first block the foreign chain has that we also have.
// Assume qblock_ids is in reverse-chronological order.
auto bl_it = qblock_ids . begin ( ) ;
uint64_t split_height = 0 ;
for ( ; bl_it ! = qblock_ids . end ( ) ; bl_it + + )
{
try
{
split_height = m_db - > get_block_height ( * bl_it ) ;
break ;
}
catch ( const BLOCK_DNE & e )
{
continue ;
}
catch ( const std : : exception & e )
{
LOG_PRINT_L1 ( " Non-critical error trying to find block by hash in BlockchainDB, hash: " < < * bl_it ) ;
return false ;
}
}
// this should be impossible, as we checked that we share the genesis block,
// but just in case...
if ( bl_it = = qblock_ids . end ( ) )
{
LOG_ERROR ( " Internal error handling connection, can't find split point " ) ;
return false ;
}
// if split_height remains 0, we didn't have any but the genesis block in common
if ( split_height = = 0 )
{
LOG_ERROR ( " Ours and foreign blockchain have only genesis block in common... o.O " ) ;
return false ;
}
//we start to put block ids INCLUDING last known id, just to make other side be sure
starter_offset = split_height ;
return true ;
}
//------------------------------------------------------------------
uint64_t Blockchain : : block_difficulty ( uint64_t i )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
try
{
return m_db - > get_block_difficulty ( i ) ;
}
catch ( const BLOCK_DNE & e )
{
LOG_PRINT_L0 ( " Attempted to get block difficulty for height above blockchain height " ) ;
}
return 0 ;
}
//------------------------------------------------------------------
template < class t_ids_container , class t_blocks_container , class t_missed_container >
2014-10-28 03:44:45 +00:00
bool Blockchain : : get_blocks ( const t_ids_container & block_ids , t_blocks_container & blocks , t_missed_container & missed_bs )
2014-10-06 23:46:25 +00:00
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
for ( const auto & block_hash : block_ids )
{
try
{
blocks . push_back ( m_db - > get_block ( block_hash ) ) ;
}
catch ( const BLOCK_DNE & e )
{
missed_bs . push_back ( block_hash ) ;
}
2014-10-28 03:44:45 +00:00
catch ( const std : : exception & e )
{
return false ;
}
2014-10-06 23:46:25 +00:00
}
2014-10-28 03:44:45 +00:00
return true ;
2014-10-06 23:46:25 +00:00
}
//------------------------------------------------------------------
template < class t_ids_container , class t_tx_container , class t_missed_container >
2014-10-28 03:44:45 +00:00
bool Blockchain : : get_transactions ( const t_ids_container & txs_ids , t_tx_container & txs , t_missed_container & missed_txs )
2014-10-06 23:46:25 +00:00
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
for ( const auto & tx_hash : txs_ids )
{
try
{
txs . push_back ( m_db - > get_tx ( tx_hash ) ) ;
}
catch ( const TX_DNE & e )
{
missed_txs . push_back ( tx_hash ) ;
}
2014-10-28 03:44:45 +00:00
//FIXME: is this the correct way to handle this?
catch ( const std : : exception & e )
{
return false ;
}
2014-10-06 23:46:25 +00:00
}
2014-10-28 03:44:45 +00:00
return true ;
2014-10-06 23:46:25 +00:00
}
//------------------------------------------------------------------
void Blockchain : : print_blockchain ( uint64_t start_index , uint64_t end_index )
{
std : : stringstream ss ;
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
auto h = m_db - > height ( ) ;
if ( start_index > h )
{
LOG_PRINT_L0 ( " Wrong starter index set: " < < start_index < < " , expected max index " < < h ) ;
return ;
}
for ( size_t i = start_index ; i < = h & & i ! = end_index ; i + + )
{
ss < < " height " < < i
< < " , timestamp " < < m_db - > get_block_timestamp ( i )
< < " , cumul_dif " < < m_db - > get_block_cumulative_difficulty ( i )
< < " , size " < < m_db - > get_block_size ( i )
< < " \n id \t \t " < < m_db - > get_block_hash_from_height ( i )
< < " \n difficulty \t \t " < < m_db - > get_block_difficulty ( i )
< < " , nonce " < < m_db - > get_block_from_height ( i ) . nonce
< < " , tx_count " < < m_db - > get_block_from_height ( i ) . tx_hashes . size ( )
< < std : : endl ;
}
LOG_PRINT_L1 ( " Current blockchain: " < < std : : endl < < ss . str ( ) ) ;
LOG_PRINT_L0 ( " Blockchain printed with log level 1 " ) ;
}
//------------------------------------------------------------------
void Blockchain : : print_blockchain_index ( )
{
std : : stringstream ss ;
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
2014-10-28 17:43:50 +00:00
auto height = m_db - > height ( ) ;
if ( height ! = 0 )
2014-10-06 23:46:25 +00:00
{
2014-10-28 17:43:50 +00:00
for ( uint64_t i = 0 ; i < = height ; i + + )
{
ss < < " height: " < < i < < " , hash: " < < m_db - > get_block_hash_from_height ( i ) ;
}
2014-10-06 23:46:25 +00:00
}
LOG_PRINT_L0 ( " Current blockchain index: " < < std : : endl
< < ss . str ( )
) ;
}
//------------------------------------------------------------------
//TODO: remove this function and references to it
void Blockchain : : print_blockchain_outs ( const std : : string & file )
{
return ;
}
//------------------------------------------------------------------
// Find the split point between us and foreign blockchain and return
// (by reference) the most recent common block hash along with up to
// BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT additional (more recent) hashes.
bool Blockchain : : find_blockchain_supplement ( const std : : list < crypto : : hash > & qblock_ids , NOTIFY_RESPONSE_CHAIN_ENTRY : : request & resp )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
// if we can't find the split point, return false
if ( ! find_blockchain_supplement ( qblock_ids , resp . start_height ) )
{
return false ;
}
resp . total_height = get_current_blockchain_height ( ) ;
size_t count = 0 ;
for ( size_t i = resp . start_height ; i < = m_db - > height ( ) & & count < BLOCKS_IDS_SYNCHRONIZING_DEFAULT_COUNT ; i + + , count + + )
{
resp . m_block_ids . push_back ( m_db - > get_block_hash_from_height ( i ) ) ;
}
return true ;
}
//------------------------------------------------------------------
//FIXME: change argument to std::vector, low priority
// find split point between ours and foreign blockchain (or start at
// blockchain height <req_start_block>), and return up to max_count FULL
// blocks by reference.
bool Blockchain : : find_blockchain_supplement ( const uint64_t req_start_block , 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 )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
// if a specific start height has been requested
if ( req_start_block > 0 )
{
// if requested height is higher than our chain, return false -- we can't help
if ( req_start_block > m_db - > height ( ) )
{
return false ;
}
start_height = req_start_block ;
}
else
{
if ( ! find_blockchain_supplement ( qblock_ids , start_height ) )
{
return false ;
}
}
total_height = get_current_blockchain_height ( ) ;
size_t count = 0 ;
for ( size_t i = start_height ; i < = m_db - > height ( ) & & count < max_count ; i + + , count + + )
{
blocks . resize ( blocks . size ( ) + 1 ) ;
blocks . back ( ) . first = m_db - > get_block_from_height ( i ) ;
std : : list < crypto : : hash > mis ;
get_transactions ( m_blocks [ i ] . bl . tx_hashes , blocks . back ( ) . second , mis ) ;
CHECK_AND_ASSERT_MES ( ! mis . size ( ) , false , " internal error, transaction from block not found " ) ;
}
return true ;
}
//------------------------------------------------------------------
bool Blockchain : : 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 : : add_block_as_invalid ( const block_extended_info & bei , const crypto : : hash & h )
{
CRITICAL_REGION_LOCAL ( m_blockchain_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 < < std : : endl < < " , prev_id= " < < bei . bl . prev_id < < " , m_invalid_blocks count= " < < m_invalid_blocks . size ( ) ) ;
return true ;
}
//------------------------------------------------------------------
bool Blockchain : : have_block ( const crypto : : hash & id )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
if ( m_db - > block_exists ( id ) )
return true ;
if ( m_alternative_chains . count ( id ) )
return true ;
if ( m_invalid_blocks . count ( id ) )
return true ;
return false ;
}
//------------------------------------------------------------------
bool Blockchain : : 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 ) ;
}
//------------------------------------------------------------------
size_t Blockchain : : get_total_transactions ( )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
return m_db - > get_tx_count ( ) ;
}
//------------------------------------------------------------------
// This function checks each input in the transaction <tx> to make sure it
// has not been used already, and adds its key to the container <keys_this_block>.
//
// This container should be managed by the code that validates blocks so we don't
// have to store the used keys in a given block in the permanent storage only to
// remove them later if the block fails validation.
bool Blockchain : : check_for_double_spend ( const transaction & tx , key_images_container & keys_this_block )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
struct add_transaction_input_visitor : public boost : : static_visitor < bool >
{
key_images_container & m_spent_keys ;
BlockchainDB * m_db ;
add_transaction_input_visitor ( key_images_container & spent_keys , BlockchainDB * db ) : m_spent_keys ( spent_keys ) , m_db ( db )
{ }
bool operator ( ) ( const txin_to_key & in ) const
{
const crypto : : key_image & ki = in . k_image ;
// attempt to insert the newly-spent key into the container of
// keys spent this block. If this fails, the key was spent already
// in this block, return false to flag that a double spend was detected.
//
// if the insert into the block-wide spent keys container succeeds,
// check the blockchain-wide spent keys container and make sure the
// key wasn't used in another block already.
auto r = m_spent_keys . insert ( ki ) ;
if ( ! r . second | | m_db - > has_key_image ( ki ) )
{
//double spend detected
return false ;
}
// if no double-spend detected, return true
return true ;
}
bool operator ( ) ( const txin_gen & tx ) const { return true ; }
bool operator ( ) ( const txin_to_script & tx ) const { return false ; }
bool operator ( ) ( const txin_to_scripthash & tx ) const { return false ; }
} ;
for ( const txin_v & in : tx . vin )
{
if ( ! boost : : apply_visitor ( add_transaction_input_visitor ( keys_this_block , m_db ) , in ) )
{
LOG_ERROR ( " Double spend detected! " ) ;
return false ;
}
}
return true ;
}
//------------------------------------------------------------------
bool Blockchain : : get_tx_outputs_gindexs ( const crypto : : hash & tx_id , std : : vector < uint64_t > & indexs )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
if ( ! m_db - > tx_exists ( tx_id ) )
{
LOG_PRINT_L0 ( " warning: get_tx_outputs_gindexs failed to find transaction with id = " < < tx_id ) ;
return false ;
}
indexs = m_db - > get_tx_output_indices ( tx_id ) ;
return true ;
}
//------------------------------------------------------------------
// This function overloads its sister function with
// an extra value (hash of highest block that holds an output used as input)
// as a return-by-reference.
bool Blockchain : : check_tx_inputs ( const transaction & tx , uint64_t & max_used_block_height , crypto : : hash & max_used_block_id )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
bool res = check_tx_inputs ( tx , & max_used_block_height ) ;
if ( ! res ) return false ;
CHECK_AND_ASSERT_MES ( max_used_block_height < m_db - > height ( ) , false , " internal error: max used block index= " < < max_used_block_height < < " is not less then blockchain size = " < < m_db - > height ( ) ) ;
max_used_block_id = m_db - > get_block_hash_from_height ( max_used_block_height ) ;
return true ;
}
//------------------------------------------------------------------
bool Blockchain : : have_tx_keyimges_as_spent ( const transaction & tx )
{
BOOST_FOREACH ( const txin_v & in , tx . vin )
{
CHECKED_GET_SPECIFIC_VARIANT ( in , const txin_to_key , in_to_key , true ) ;
if ( have_tx_keyimg_as_spent ( in_to_key . k_image ) )
return true ;
}
return false ;
}
//------------------------------------------------------------------
// This function validates transaction inputs and their keys. Previously
// it also performed double spend checking, but that has been moved to its
// own function.
bool Blockchain : : check_tx_inputs ( const transaction & tx , uint64_t * pmax_used_block_height )
{
size_t sig_index = 0 ;
if ( pmax_used_block_height )
* pmax_used_block_height = 0 ;
crypto : : hash tx_prefix_hash = get_transaction_prefix_hash ( tx ) ;
for ( const auto & txin : tx . vin )
{
// make sure output being spent is of type txin_to_key, rather than
// e.g. txin_gen, which is only used for miner transactions
CHECK_AND_ASSERT_MES ( txin . type ( ) = = typeid ( txin_to_key ) , false , " wrong type id in tx input at Blockchain::check_tx_inputs " ) ;
const txin_to_key & in_to_key = boost : : get < txin_to_key > ( txin ) ;
// make sure tx output has key offset(s) (is signed to be used)
CHECK_AND_ASSERT_MES ( in_to_key . key_offsets . size ( ) , false , " empty in_to_key.key_offsets in transaction with id " < < get_transaction_hash ( tx ) ) ;
// basically, make sure number of inputs == number of signatures
CHECK_AND_ASSERT_MES ( sig_index < tx . signatures . size ( ) , false , " wrong transaction: not signature entry for input with index= " < < sig_index ) ;
// make sure that output being spent matches up correctly with the
// signature spending it.
if ( ! check_tx_input ( in_to_key , tx_prefix_hash , tx . signatures [ sig_index ] , pmax_used_block_height ) )
{
LOG_PRINT_L0 ( " Failed to check ring signature for tx " < < get_transaction_hash ( tx ) ) ;
return false ;
}
sig_index + + ;
}
return true ;
}
//------------------------------------------------------------------
// This function checks to see if a tx is unlocked. unlock_time is either
// a block index or a unix time.
bool Blockchain : : is_tx_spendtime_unlocked ( uint64_t unlock_time )
{
if ( unlock_time < CRYPTONOTE_MAX_BLOCK_NUMBER )
{
//interpret as block index
if ( get_current_blockchain_height ( ) + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS > = unlock_time )
return true ;
else
return false ;
} else
{
//interpret as time
uint64_t current_time = static_cast < uint64_t > ( time ( NULL ) ) ;
if ( current_time + CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS > = unlock_time )
return true ;
else
return false ;
}
return false ;
}
//------------------------------------------------------------------
// This function locates all outputs associated with a given input (mixins)
// and validates that they exist and are usable. It also checks the ring
// signature for each input.
bool Blockchain : : check_tx_input ( const txin_to_key & txin , const crypto : : hash & tx_prefix_hash , const std : : vector < crypto : : signature > & sig , uint64_t * pmax_related_block_height )
{
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
struct outputs_visitor
{
std : : vector < const crypto : : public_key * > & m_results_collector ;
Blockchain & m_bch ;
outputs_visitor ( std : : vector < const crypto : : public_key * > & results_collector , Blockchain & bch ) : m_results_collector ( results_collector ) , m_bch ( bch )
{ }
bool handle_output ( const transaction & tx , const tx_out & out )
{
//check tx unlock time
if ( ! m_bch . is_tx_spendtime_unlocked ( tx . unlock_time ) )
{
LOG_PRINT_L0 ( " One of outputs for one of inputs have wrong tx.unlock_time = " < < tx . unlock_time ) ;
return false ;
}
if ( out . target . type ( ) ! = typeid ( txout_to_key ) )
{
LOG_PRINT_L0 ( " Output have wrong type id, which= " < < out . target . which ( ) ) ;
return false ;
}
m_results_collector . push_back ( & boost : : get < txout_to_key > ( out . target ) . key ) ;
return true ;
}
} ;
//check ring signature
std : : vector < const crypto : : public_key * > output_keys ;
outputs_visitor vi ( output_keys , * this ) ;
if ( ! scan_outputkeys_for_indexes ( txin , vi , pmax_related_block_height ) )
{
LOG_PRINT_L0 ( " Failed to get output keys for tx with amount = " < < print_money ( txin . amount ) < < " and count indexes " < < txin . key_offsets . size ( ) ) ;
return false ;
}
if ( txin . key_offsets . size ( ) ! = output_keys . 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 . size ( ) ) ;
return false ;
}
CHECK_AND_ASSERT_MES ( sig . size ( ) = = output_keys . size ( ) , false , " internal error: tx signatures count= " < < sig . size ( ) < < " mismatch with outputs keys count for inputs= " < < output_keys . size ( ) ) ;
if ( m_is_in_checkpoint_zone )
return true ;
return crypto : : check_ring_signature ( tx_prefix_hash , txin . k_image , output_keys , sig . data ( ) ) ;
}
//------------------------------------------------------------------
//TODO: Is this intended to do something else? Need to look into the todo there.
uint64_t Blockchain : : get_adjusted_time ( )
{
//TODO: add collecting median time
return time ( NULL ) ;
}
//------------------------------------------------------------------
2014-10-13 04:31:21 +00:00
//TODO: revisit, has changed a bit on upstream
bool Blockchain : : check_block_timestamp ( std : : vector < uint64_t > & timestamps , const block & b )
2014-10-06 23:46:25 +00:00
{
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 ;
}
//------------------------------------------------------------------
// This function grabs the timestamps from the most recent <n> blocks,
// where n = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW. If there are not those many
// blocks in the blockchain, the timestap is assumed to be valid. If there
// are, this function returns:
// true if the block's timestamp is not less than the timestamp of the
// median of the selected blocks
// false otherwise
bool Blockchain : : check_block_timestamp ( const block & b )
{
if ( b . timestamp > get_adjusted_time ( ) + CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT )
{
LOG_PRINT_L0 ( " Timestamp of block with id: " < < get_block_hash ( b ) < < " , " < < b . timestamp < < " , bigger than adjusted time + 2 hours " ) ;
return false ;
}
// if not enough blocks, no proper median yet, return true
if ( m_db - > height ( ) < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW + 1 )
{
return true ;
}
std : : vector < uint64_t > timestamps ;
auto h = m_db - > height ( ) ;
// need most recent 60 blocks, get index of first of those
// using +1 because BlockchainDB::height() returns the index of the top block,
// not the size of the blockchain (0-indexed)
size_t offset = h - BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW + 1 ;
for ( ; offset < = h ; + + offset )
{
timestamps . push_back ( m_db - > get_block_timestamp ( offset ) ) ;
}
return check_block_timestamp ( timestamps , b ) ;
}
//------------------------------------------------------------------
// Needs to validate the block and acquire each transaction from the
// transaction mem_pool, then pass the block and transactions to
// m_db->add_block()
bool Blockchain : : handle_block_to_main_chain ( const block & bl , const crypto : : hash & id , block_verification_context & bvc )
{
// if we already have the block, return false
if ( have_block ( id ) )
{
LOG_PRINT_L0 ( " Attempting to add block to main chain, but it's already either there or in an alternate chain. hash: " < < id ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
return false ;
}
TIME_MEASURE_START ( block_processing_time ) ;
CRITICAL_REGION_LOCAL ( m_blockchain_lock ) ;
if ( bl . prev_id ! = get_tail_id ( ) )
{
LOG_PRINT_L0 ( " Block with id: " < < id < < std : : endl
< < " have wrong prev_id: " < < bl . prev_id < < std : : endl
< < " expected: " < < get_tail_id ( ) ) ;
return false ;
}
// make sure block timestamp is not less than the median timestamp
// of a set number of the most recent blocks.
if ( ! check_block_timestamp ( bl ) )
{
LOG_PRINT_L0 ( " Block with id: " < < id < < std : : endl
< < " have invalid timestamp: " < < bl . timestamp ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
return false ;
}
//check proof of work
TIME_MEASURE_START ( target_calculating_time ) ;
// get the target difficulty for the block.
// the calculation can overflow, among other failure cases,
// so we need to check the return type.
// FIXME: get_difficulty_for_next_block can also assert, look into
// changing this to throwing exceptions instead so we can clean up.
difficulty_type current_diffic = get_difficulty_for_next_block ( ) ;
CHECK_AND_ASSERT_MES ( current_diffic , false , " !!!!!!!!! difficulty overhead !!!!!!!!! " ) ;
TIME_MEASURE_FINISH ( target_calculating_time ) ;
TIME_MEASURE_START ( longhash_calculating_time ) ;
crypto : : hash proof_of_work = null_hash ;
// Formerly the code below contained an if loop with the following condition
// !m_checkpoints.is_in_checkpoint_zone(get_current_blockchain_height())
// however, this caused the daemon to not bother checking PoW for blocks
// before checkpoints, which is very dangerous behaviour. We moved the PoW
// validation out of the next chunk of code to make sure that we correctly
// check PoW now.
// FIXME: height parameter is not used...should it be used or should it not
// be a parameter?
proof_of_work = get_block_longhash ( bl , m_db - > height ( ) ) ;
// validate proof_of_work versus difficulty target
if ( ! check_hash ( proof_of_work , current_diffic ) )
{
LOG_PRINT_L0 ( " Block with id: " < < id < < std : : endl
< < " have not enough proof of work: " < < proof_of_work < < std : : endl
< < " nexpected difficulty: " < < current_diffic ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
return false ;
}
// If we're at a checkpoint, ensure that our hardcoded checkpoint hash
// is correct.
if ( m_checkpoints . is_in_checkpoint_zone ( get_current_blockchain_height ( ) ) )
{
if ( ! m_checkpoints . check_block ( get_current_blockchain_height ( ) , id ) )
{
LOG_ERROR ( " CHECKPOINT VALIDATION FAILED " ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
return false ;
}
}
TIME_MEASURE_FINISH ( longhash_calculating_time ) ;
// sanity check basic miner tx properties
if ( ! prevalidate_miner_transaction ( bl , m_db - > height ( ) ) )
{
LOG_PRINT_L0 ( " Block with id: " < < id
< < " failed to pass prevalidation " ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
return false ;
}
size_t coinbase_blob_size = get_object_blobsize ( bl . miner_tx ) ;
size_t cumulative_block_size = coinbase_blob_size ;
std : : vector < transaction > txs ;
key_images_container keys ;
// add miner transaction to list of block's transactions.
txs . push_back ( bl . miner_tx ) ;
uint64_t fee_summary = 0 ;
// Iterate over the block's transaction hashes, grabbing each
// from the tx_pool and validating them. Each is then added
// to txs. Keys spent in each are added to <keys> by the double spend check.
for ( const crypto : : hash & tx_id : bl . tx_hashes )
{
transaction tx ;
size_t blob_size = 0 ;
uint64_t fee = 0 ;
if ( m_db - > tx_exists ( tx_id ) )
{
LOG_PRINT_L0 ( " Block with id: " < < id < < " attempting to add transaction already in blockchain with id: " < < tx_id ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
break ;
}
// get transaction with hash <tx_id> from tx_pool
if ( ! m_tx_pool . take_tx ( tx_id , tx , blob_size , fee ) )
{
LOG_PRINT_L0 ( " Block with id: " < < id < < " have at least one unknown transaction with id: " < < tx_id ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
break ;
}
// add the transaction to the temp list of transactions, so we can either
// store the list of transactions all at once or return the ones we've
// taken from the tx_pool back to it if the block fails verification.
txs . push_back ( tx ) ;
// validate that transaction inputs and the keys spending them are correct.
if ( ! check_tx_inputs ( tx ) )
{
LOG_PRINT_L0 ( " Block with id: " < < id < < " have at least one transaction (id: " < < tx_id < < " ) with wrong inputs. " ) ;
//TODO: why is this done? make sure that keeping invalid blocks makes sense.
add_block_as_invalid ( bl , id ) ;
LOG_PRINT_L0 ( " Block with id " < < id < < " added as invalid becouse of wrong inputs in transactions " ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
break ;
}
if ( ! check_for_double_spend ( tx , keys ) )
{
LOG_PRINT_L0 ( " Double spend detected in transaction (id: " < < tx_id ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
break ;
}
fee_summary + = fee ;
cumulative_block_size + = blob_size ;
}
uint64_t base_reward = 0 ;
uint64_t already_generated_coins = m_db - > height ( ) ? m_db - > get_block_already_generated_coins ( m_db - > height ( ) ) : 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 " ) ;
2014-10-06 23:56:31 +00:00
bvc . m_verifivation_failed = true ;
2014-10-06 23:46:25 +00:00
}
block_extended_info bei = boost : : value_initialized < block_extended_info > ( ) ;
size_t block_size ;
difficulty_type cumulative_difficulty ;
// populate various metadata about the block to be stored alongside it.
block_size = cumulative_block_size ;
cumulative_difficulty = current_diffic ;
already_generated_coins = already_generated_coins + base_reward ;
if ( m_db - > height ( ) )
cumulative_difficulty + = m_db - > get_block_cumulative_difficulty ( m_db - > height ( ) ) ;
update_next_cumulative_size_limit ( ) ;
TIME_MEASURE_FINISH ( block_processing_time ) ;
uint64_t new_height = 0 ;
bool add_success = true ;
try
{
new_height = m_db - > add_block ( bl , block_size , cumulative_difficulty , already_generated_coins , txs ) ;
}
catch ( const std : : exception & e )
{
//TODO: figure out the best way to deal with this failure
LOG_ERROR ( " Error adding block with hash: " < < id < < " to blockchain, what = " < < e . what ( ) ) ;
add_success = false ;
}
// if we failed for any reason to verify the block, return taken
// transactions to the tx_pool.
2014-10-06 23:56:31 +00:00
if ( bvc . m_verifivation_failed | | ! add_success )
2014-10-06 23:46:25 +00:00
{
// return taken transactions to transaction pool
for ( auto & tx : txs )
{
cryptonote : : tx_verification_context tvc = AUTO_VAL_INIT ( tvc ) ;
if ( ! m_tx_pool . add_tx ( tx , tvc , true ) )
{
LOG_PRINT_L0 ( " Failed to return taken transaction with hash: " < < get_transaction_hash ( tx ) < < " to tx_pool " ) ;
}
}
return false ;
}
LOG_PRINT_L1 ( " +++++ BLOCK SUCCESSFULLY ADDED " < < std : : endl < < " id: \t " < < id
< < std : : endl < < " PoW: \t " < < proof_of_work
< < std : : endl < < " HEIGHT " < < new_height < < " , difficulty: \t " < < current_diffic
< < std : : endl < < " block reward: " < < print_money ( fee_summary + base_reward ) < < " ( " < < print_money ( base_reward ) < < " + " < < print_money ( fee_summary )
< < " ), coinbase_blob_size: " < < coinbase_blob_size < < " , cumulative size: " < < cumulative_block_size
< < " , " < < block_processing_time < < " ( " < < target_calculating_time < < " / " < < longhash_calculating_time < < " )ms " ) ;
bvc . m_added_to_main_chain = true ;
// appears to be a NOP *and* is called elsewhere. wat?
m_tx_pool . on_blockchain_inc ( new_height , id ) ;
return true ;
}
//------------------------------------------------------------------
bool Blockchain : : update_next_cumulative_size_limit ( )
{
std : : vector < size_t > sz ;
get_last_n_blocks_sizes ( sz , CRYPTONOTE_REWARD_BLOCKS_WINDOW ) ;
uint64_t median = epee : : misc_utils : : median ( sz ) ;
if ( median < = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE )
median = CRYPTONOTE_BLOCK_GRANTED_FULL_REWARD_ZONE ;
m_current_block_cumul_sz_limit = median * 2 ;
return true ;
}
//------------------------------------------------------------------
bool Blockchain : : add_new_block ( const block & bl_ , block_verification_context & bvc )
{
//copy block here to let modify block.target
block bl = bl_ ;
crypto : : hash id = get_block_hash ( bl ) ;
CRITICAL_REGION_LOCAL ( m_tx_pool ) ; //to avoid deadlock lets lock tx_pool for whole add/reorganize process
CRITICAL_REGION_LOCAL1 ( m_blockchain_lock ) ;
if ( have_block ( id ) )
{
LOG_PRINT_L3 ( " block with id = " < < id < < " already exists " ) ;
bvc . m_already_exists = true ;
return false ;
}
//check that block refers to chain tail
if ( ! ( bl . prev_id = = get_tail_id ( ) ) )
{
//chain switching or wrong block
bvc . m_added_to_main_chain = false ;
return handle_alternative_block ( bl , id , bvc ) ;
//never relay alternative blocks
}
return handle_block_to_main_chain ( bl , id , bvc ) ;
}