2020-04-16 13:14:08 -04:00
// Copyright (c) 2017-2020 The Bitcoin Core developers
2018-05-15 15:57:48 -07:00
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
# include <chainparams.h>
# include <index/base.h>
2021-04-02 20:42:05 +02:00
# include <node/blockstorage.h>
2020-06-19 18:14:17 -04:00
# include <node/ui_interface.h>
2018-05-16 19:17:40 +00:00
# include <shutdown.h>
2018-05-15 15:57:48 -07:00
# include <tinyformat.h>
2021-04-13 20:44:46 +03:00
# include <util/thread.h>
2020-04-11 18:47:17 +03:00
# include <util/translation.h>
2021-04-02 20:42:05 +02:00
# include <validation.h> // For g_chainman
2018-05-15 15:57:48 -07:00
# include <warnings.h>
2018-05-15 17:20:17 -07:00
constexpr char DB_BEST_BLOCK = ' B ' ;
2018-05-15 15:57:48 -07:00
constexpr int64_t SYNC_LOG_INTERVAL = 30 ; // seconds
constexpr int64_t SYNC_LOCATOR_WRITE_INTERVAL = 30 ; // seconds
2020-06-16 11:02:46 -04:00
template < typename . . . Args >
2018-05-15 15:57:48 -07:00
static void FatalError ( const char * fmt , const Args & . . . args )
{
std : : string strMessage = tfm : : format ( fmt , args . . . ) ;
2020-06-10 12:14:32 +03:00
SetMiscWarning ( Untranslated ( strMessage ) ) ;
2018-05-15 15:57:48 -07:00
LogPrintf ( " *** %s \n " , strMessage ) ;
2020-06-16 11:02:46 -04:00
AbortError ( _ ( " A fatal internal error occurred, see debug.log for details " ) ) ;
2018-05-15 15:57:48 -07:00
StartShutdown ( ) ;
}
2018-05-15 17:20:17 -07:00
BaseIndex : : DB : : DB ( const fs : : path & path , size_t n_cache_size , bool f_memory , bool f_wipe , bool f_obfuscate ) :
CDBWrapper ( path , n_cache_size , f_memory , f_wipe , f_obfuscate )
{ }
bool BaseIndex : : DB : : ReadBestBlock ( CBlockLocator & locator ) const
{
bool success = Read ( DB_BEST_BLOCK , locator ) ;
if ( ! success ) {
locator . SetNull ( ) ;
}
return success ;
}
2019-03-02 18:35:55 -08:00
void BaseIndex : : DB : : WriteBestBlock ( CDBBatch & batch , const CBlockLocator & locator )
2018-05-15 17:20:17 -07:00
{
2019-03-02 18:35:55 -08:00
batch . Write ( DB_BEST_BLOCK , locator ) ;
2018-05-15 17:20:17 -07:00
}
2018-05-15 15:57:48 -07:00
BaseIndex : : ~ BaseIndex ( )
{
Interrupt ( ) ;
Stop ( ) ;
}
bool BaseIndex : : Init ( )
{
CBlockLocator locator ;
if ( ! GetDB ( ) . ReadBestBlock ( locator ) ) {
locator . SetNull ( ) ;
}
LOCK ( cs_main ) ;
2018-08-27 12:28:35 -07:00
if ( locator . IsNull ( ) ) {
m_best_block_index = nullptr ;
} else {
2020-08-25 16:40:21 -04:00
m_best_block_index = g_chainman . m_blockman . FindForkInGlobalIndex ( : : ChainActive ( ) , locator ) ;
2018-08-27 12:28:35 -07:00
}
2019-03-27 11:14:25 -04:00
m_synced = m_best_block_index . load ( ) = = : : ChainActive ( ) . Tip ( ) ;
2019-05-02 20:41:01 +02:00
if ( ! m_synced ) {
bool prune_violation = false ;
if ( ! m_best_block_index ) {
// index is not built yet
// make sure we have all block data back to the genesis
const CBlockIndex * block = : : ChainActive ( ) . Tip ( ) ;
while ( block - > pprev & & ( block - > pprev - > nStatus & BLOCK_HAVE_DATA ) ) {
block = block - > pprev ;
}
prune_violation = block ! = : : ChainActive ( ) . Genesis ( ) ;
}
// in case the index has a best block set and is not fully synced
// check if we have the required blocks to continue building the index
else {
const CBlockIndex * block_to_test = m_best_block_index . load ( ) ;
if ( ! ChainActive ( ) . Contains ( block_to_test ) ) {
// if the bestblock is not part of the mainchain, find the fork
// and make sure we have all data down to the fork
block_to_test = : : ChainActive ( ) . FindFork ( block_to_test ) ;
}
const CBlockIndex * block = : : ChainActive ( ) . Tip ( ) ;
prune_violation = true ;
// check backwards from the tip if we have all block data until we reach the indexes bestblock
while ( block_to_test & & block - > pprev & & ( block - > pprev - > nStatus & BLOCK_HAVE_DATA ) ) {
if ( block_to_test = = block ) {
prune_violation = false ;
break ;
}
block = block - > pprev ;
}
}
if ( prune_violation ) {
// throw error and graceful shutdown if we can't build the index
FatalError ( " %s: %s best block of the index goes beyond pruned data. Please disable the index or reindex (which will download the whole blockchain again) " , __func__ , GetName ( ) ) ;
return false ;
}
}
2018-05-15 15:57:48 -07:00
return true ;
}
2018-10-10 11:28:43 +02:00
static const CBlockIndex * NextSyncBlock ( const CBlockIndex * pindex_prev ) EXCLUSIVE_LOCKS_REQUIRED ( cs_main )
2018-05-15 15:57:48 -07:00
{
AssertLockHeld ( cs_main ) ;
if ( ! pindex_prev ) {
2019-03-27 11:14:25 -04:00
return : : ChainActive ( ) . Genesis ( ) ;
2018-05-15 15:57:48 -07:00
}
2019-03-27 11:14:25 -04:00
const CBlockIndex * pindex = : : ChainActive ( ) . Next ( pindex_prev ) ;
2018-05-15 15:57:48 -07:00
if ( pindex ) {
return pindex ;
}
2019-03-27 11:14:25 -04:00
return : : ChainActive ( ) . Next ( : : ChainActive ( ) . FindFork ( pindex_prev ) ) ;
2018-05-15 15:57:48 -07:00
}
void BaseIndex : : ThreadSync ( )
{
const CBlockIndex * pindex = m_best_block_index . load ( ) ;
if ( ! m_synced ) {
auto & consensus_params = Params ( ) . GetConsensus ( ) ;
int64_t last_log_time = 0 ;
int64_t last_locator_write_time = 0 ;
while ( true ) {
if ( m_interrupt ) {
2019-03-02 18:35:55 -08:00
m_best_block_index = pindex ;
// No need to handle errors in Commit. If it fails, the error will be already be
// logged. The best way to recover is to continue, as index cannot be corrupted by
// a missed commit to disk for an advanced index state.
Commit ( ) ;
2018-05-15 15:57:48 -07:00
return ;
}
{
LOCK ( cs_main ) ;
const CBlockIndex * pindex_next = NextSyncBlock ( pindex ) ;
if ( ! pindex_next ) {
m_best_block_index = pindex ;
m_synced = true ;
2019-03-02 18:35:55 -08:00
// No need to handle errors in Commit. See rationale above.
Commit ( ) ;
2018-05-15 15:57:48 -07:00
break ;
}
2018-08-27 15:26:29 -07:00
if ( pindex_next - > pprev ! = pindex & & ! Rewind ( pindex , pindex_next - > pprev ) ) {
FatalError ( " %s: Failed to rewind index %s to a previous chain tip " ,
__func__ , GetName ( ) ) ;
return ;
}
2018-05-15 15:57:48 -07:00
pindex = pindex_next ;
}
int64_t current_time = GetTime ( ) ;
if ( last_log_time + SYNC_LOG_INTERVAL < current_time ) {
LogPrintf ( " Syncing %s with block chain from height %d \n " ,
GetName ( ) , pindex - > nHeight ) ;
last_log_time = current_time ;
}
if ( last_locator_write_time + SYNC_LOCATOR_WRITE_INTERVAL < current_time ) {
2019-03-02 18:35:55 -08:00
m_best_block_index = pindex ;
2018-05-15 15:57:48 -07:00
last_locator_write_time = current_time ;
2019-03-02 18:35:55 -08:00
// No need to handle errors in Commit. See rationale above.
Commit ( ) ;
2018-05-15 15:57:48 -07:00
}
CBlock block ;
if ( ! ReadBlockFromDisk ( block , pindex , consensus_params ) ) {
FatalError ( " %s: Failed to read block %s from disk " ,
__func__ , pindex - > GetBlockHash ( ) . ToString ( ) ) ;
return ;
}
if ( ! WriteBlock ( block , pindex ) ) {
FatalError ( " %s: Failed to write block %s to index database " ,
__func__ , pindex - > GetBlockHash ( ) . ToString ( ) ) ;
return ;
}
}
}
if ( pindex ) {
LogPrintf ( " %s is enabled at height %d \n " , GetName ( ) , pindex - > nHeight ) ;
} else {
LogPrintf ( " %s is enabled \n " , GetName ( ) ) ;
}
}
2019-03-02 18:35:55 -08:00
bool BaseIndex : : Commit ( )
2018-05-15 15:57:48 -07:00
{
2019-03-02 18:35:55 -08:00
CDBBatch batch ( GetDB ( ) ) ;
if ( ! CommitInternal ( batch ) | | ! GetDB ( ) . WriteBatch ( batch ) ) {
return error ( " %s: Failed to commit latest %s state " , __func__ , GetName ( ) ) ;
2018-05-15 15:57:48 -07:00
}
return true ;
}
2019-03-02 18:35:55 -08:00
bool BaseIndex : : CommitInternal ( CDBBatch & batch )
{
LOCK ( cs_main ) ;
2019-03-27 11:14:25 -04:00
GetDB ( ) . WriteBestBlock ( batch , : : ChainActive ( ) . GetLocator ( m_best_block_index ) ) ;
2019-03-02 18:35:55 -08:00
return true ;
}
2018-08-27 15:26:29 -07:00
bool BaseIndex : : Rewind ( const CBlockIndex * current_tip , const CBlockIndex * new_tip )
{
assert ( current_tip = = m_best_block_index ) ;
assert ( current_tip - > GetAncestor ( new_tip - > nHeight ) = = new_tip ) ;
// In the case of a reorg, ensure persisted block locator is not stale.
2019-05-02 20:41:01 +02:00
// Pruning has a minimum of 288 blocks-to-keep and getting the index
// out of sync may be possible but a users fault.
// In case we reorg beyond the pruned depth, ReadBlockFromDisk would
// throw and lead to a graceful shutdown
2018-08-27 15:26:29 -07:00
m_best_block_index = new_tip ;
if ( ! Commit ( ) ) {
// If commit fails, revert the best block index to avoid corruption.
m_best_block_index = current_tip ;
return false ;
}
return true ;
}
2019-11-11 10:43:34 -05:00
void BaseIndex : : BlockConnected ( const std : : shared_ptr < const CBlock > & block , const CBlockIndex * pindex )
2018-05-15 15:57:48 -07:00
{
if ( ! m_synced ) {
return ;
}
const CBlockIndex * best_block_index = m_best_block_index . load ( ) ;
if ( ! best_block_index ) {
if ( pindex - > nHeight ! = 0 ) {
FatalError ( " %s: First block connected is not the genesis block (height=%d) " ,
__func__ , pindex - > nHeight ) ;
return ;
}
} else {
// Ensure block connects to an ancestor of the current best block. This should be the case
// most of the time, but may not be immediately after the sync thread catches up and sets
// m_synced. Consider the case where there is a reorg and the blocks on the stale branch are
// in the ValidationInterface queue backlog even after the sync thread has caught up to the
// new chain tip. In this unlikely event, log a warning and let the queue clear.
if ( best_block_index - > GetAncestor ( pindex - > nHeight - 1 ) ! = pindex - > pprev ) {
LogPrintf ( " %s: WARNING: Block %s does not connect to an ancestor of " /* Continued */
" known best chain (tip=%s); not updating index \n " ,
__func__ , pindex - > GetBlockHash ( ) . ToString ( ) ,
best_block_index - > GetBlockHash ( ) . ToString ( ) ) ;
return ;
}
2018-08-27 15:26:29 -07:00
if ( best_block_index ! = pindex - > pprev & & ! Rewind ( best_block_index , pindex - > pprev ) ) {
FatalError ( " %s: Failed to rewind index %s to a previous chain tip " ,
__func__ , GetName ( ) ) ;
return ;
}
2018-05-15 15:57:48 -07:00
}
if ( WriteBlock ( * block , pindex ) ) {
m_best_block_index = pindex ;
} else {
FatalError ( " %s: Failed to write block %s to index " ,
__func__ , pindex - > GetBlockHash ( ) . ToString ( ) ) ;
return ;
}
}
void BaseIndex : : ChainStateFlushed ( const CBlockLocator & locator )
{
if ( ! m_synced ) {
return ;
}
const uint256 & locator_tip_hash = locator . vHave . front ( ) ;
const CBlockIndex * locator_tip_index ;
{
LOCK ( cs_main ) ;
2020-08-25 17:39:57 -04:00
locator_tip_index = g_chainman . m_blockman . LookupBlockIndex ( locator_tip_hash ) ;
2018-05-15 15:57:48 -07:00
}
if ( ! locator_tip_index ) {
FatalError ( " %s: First block (hash=%s) in locator was not found " ,
__func__ , locator_tip_hash . ToString ( ) ) ;
return ;
}
// This checks that ChainStateFlushed callbacks are received after BlockConnected. The check may fail
// immediately after the sync thread catches up and sets m_synced. Consider the case where
// there is a reorg and the blocks on the stale branch are in the ValidationInterface queue
// backlog even after the sync thread has caught up to the new chain tip. In this unlikely
// event, log a warning and let the queue clear.
const CBlockIndex * best_block_index = m_best_block_index . load ( ) ;
if ( best_block_index - > GetAncestor ( locator_tip_index - > nHeight ) ! = locator_tip_index ) {
LogPrintf ( " %s: WARNING: Locator contains block (hash=%s) not on known best " /* Continued */
" chain (tip=%s); not writing index locator \n " ,
__func__ , locator_tip_hash . ToString ( ) ,
best_block_index - > GetBlockHash ( ) . ToString ( ) ) ;
return ;
}
2019-03-02 18:35:55 -08:00
// No need to handle errors in Commit. If it fails, the error will be already be logged. The
// best way to recover is to continue, as index cannot be corrupted by a missed commit to disk
// for an advanced index state.
Commit ( ) ;
2018-05-15 15:57:48 -07:00
}
2020-03-02 15:57:25 +09:00
bool BaseIndex : : BlockUntilSyncedToCurrentChain ( ) const
2018-05-15 15:57:48 -07:00
{
AssertLockNotHeld ( cs_main ) ;
if ( ! m_synced ) {
return false ;
}
{
// Skip the queue-draining stuff if we know we're caught up with
2019-03-27 11:14:25 -04:00
// ::ChainActive().Tip().
2018-05-15 15:57:48 -07:00
LOCK ( cs_main ) ;
2019-03-27 11:14:25 -04:00
const CBlockIndex * chain_tip = : : ChainActive ( ) . Tip ( ) ;
2018-05-15 15:57:48 -07:00
const CBlockIndex * best_block_index = m_best_block_index . load ( ) ;
if ( best_block_index - > GetAncestor ( chain_tip - > nHeight ) = = chain_tip ) {
return true ;
}
}
LogPrintf ( " %s: %s is catching up on block notifications \n " , __func__ , GetName ( ) ) ;
SyncWithValidationInterfaceQueue ( ) ;
return true ;
}
void BaseIndex : : Interrupt ( )
{
m_interrupt ( ) ;
}
void BaseIndex : : Start ( )
{
// Need to register this ValidationInterface before running Init(), so that
// callbacks are not missed if Init sets m_synced to true.
RegisterValidationInterface ( this ) ;
if ( ! Init ( ) ) {
FatalError ( " %s: %s failed to initialize " , __func__ , GetName ( ) ) ;
return ;
}
2021-04-13 20:44:46 +03:00
m_thread_sync = std : : thread ( & util : : TraceThread , GetName ( ) ,
2018-05-15 15:57:48 -07:00
std : : bind ( & BaseIndex : : ThreadSync , this ) ) ;
}
void BaseIndex : : Stop ( )
{
UnregisterValidationInterface ( this ) ;
if ( m_thread_sync . joinable ( ) ) {
m_thread_sync . join ( ) ;
}
}
2020-07-31 14:01:31 +02:00
IndexSummary BaseIndex : : GetSummary ( ) const
{
IndexSummary summary { } ;
summary . name = GetName ( ) ;
summary . synced = m_synced ;
2020-12-10 13:36:47 +01:00
summary . best_block_height = m_best_block_index ? m_best_block_index . load ( ) - > nHeight : 0 ;
2020-07-31 14:01:31 +02:00
return summary ;
}