2022-05-19 16:40:38 +02:00
import { Common } from './api/common' ;
import blocks from './api/blocks' ;
import mempool from './api/mempool' ;
2022-07-11 19:15:28 +02:00
import mining from './api/mining/mining' ;
2022-05-19 16:40:38 +02:00
import logger from './logger' ;
2022-06-14 10:58:17 +02:00
import bitcoinClient from './api/bitcoin/bitcoin-client' ;
2022-07-11 23:16:48 +02:00
import priceUpdater from './tasks/price-updater' ;
2022-07-16 09:22:45 +02:00
import PricesRepository from './repositories/PricesRepository' ;
2023-07-11 16:03:44 +09:00
import config from './config' ;
2023-06-20 16:21:54 -04:00
import auditReplicator from './replication/AuditReplication' ;
2024-05-06 20:52:44 +02:00
import statisticsReplicator from './replication/StatisticsReplication' ;
2024-03-11 21:19:03 +00:00
import AccelerationRepository from './repositories/AccelerationRepository' ;
2024-07-20 11:54:09 +00:00
import BlocksAuditsRepository from './repositories/BlocksAuditsRepository' ;
2022-05-19 16:40:38 +02:00
2023-02-17 21:21:21 +09:00
export interface CoreIndex {
name : string ;
synced : boolean ;
best_block_height : number ;
}
2023-09-21 17:50:53 +01:00
type TaskName = 'blocksPrices' | 'coinStatsIndex' ;
2022-05-19 16:40:38 +02:00
class Indexer {
2023-09-21 17:50:53 +01:00
private runIndexer = true ;
private indexerRunning = false ;
private tasksRunning : { [ key in TaskName ] ? : boolean ; } = { } ;
private tasksScheduled : { [ key in TaskName ] ? : NodeJS . Timeout ; } = { } ;
private coreIndexes : CoreIndex [ ] = [ ] ;
public indexerIsRunning ( ) : boolean {
return this . indexerRunning ;
}
2023-02-17 21:21:21 +09:00
/ * *
* Check which core index is available for indexing
* /
public async checkAvailableCoreIndexes ( ) : Promise < void > {
const updatedCoreIndexes : CoreIndex [ ] = [ ] ;
const indexes : any = await bitcoinClient . getIndexInfo ( ) ;
for ( const indexName in indexes ) {
const newState = {
name : indexName ,
synced : indexes [ indexName ] . synced ,
best_block_height : indexes [ indexName ] . best_block_height ,
} ;
logger . info ( ` Core index ' ${ indexName } ' is ${ indexes [ indexName ] . synced ? 'synced' : 'not synced' } . Best block height is ${ indexes [ indexName ] . best_block_height } ` ) ;
updatedCoreIndexes . push ( newState ) ;
if ( indexName === 'coinstatsindex' && newState . synced === true ) {
const previousState = this . isCoreIndexReady ( 'coinstatsindex' ) ;
// if (!previousState || previousState.synced === false) {
this . runSingleTask ( 'coinStatsIndex' ) ;
// }
}
}
this . coreIndexes = updatedCoreIndexes ;
}
2022-05-19 16:40:38 +02:00
2023-02-17 21:21:21 +09:00
/ * *
* Return the best block height if a core index is available , or 0 if not
*
* @param name
* @returns
* /
public isCoreIndexReady ( name : string ) : CoreIndex | null {
for ( const index of this . coreIndexes ) {
if ( index . name === name && index . synced === true ) {
return index ;
}
}
return null ;
}
public reindex ( ) : void {
2022-06-07 11:28:39 +02:00
if ( Common . indexingEnabled ( ) ) {
this . runIndexer = true ;
}
2022-05-19 16:40:38 +02:00
}
2023-09-21 17:50:53 +01:00
/ * *
* schedules a single task to run in ` timeout ` ms
* only one task of each type may be scheduled
*
* @param { TaskName } task - the type of task
* @param { number } timeout - delay in ms
* @param { boolean } replace - ` true ` replaces any already scheduled task ( works like a debounce ) , ` false ` ignores subsequent requests ( works like a throttle )
* /
public scheduleSingleTask ( task : TaskName , timeout : number = 10000 , replace = false ) : void {
if ( this . tasksScheduled [ task ] ) {
if ( ! replace ) { //throttle
return ;
} else { // debounce
clearTimeout ( this . tasksScheduled [ task ] ) ;
}
2022-07-16 09:22:45 +02:00
}
2023-09-21 17:50:53 +01:00
this . tasksScheduled [ task ] = setTimeout ( async ( ) = > {
2023-11-14 05:00:05 +00:00
try {
2023-09-21 17:50:53 +01:00
await this . runSingleTask ( task ) ;
2023-11-14 05:00:05 +00:00
} catch ( e ) {
2023-09-21 17:50:53 +01:00
logger . err ( ` Unexpected error in scheduled task ${ task } : ` + ( e instanceof Error ? e.message : e ) ) ;
} finally {
clearTimeout ( this . tasksScheduled [ task ] ) ;
2022-07-16 09:22:45 +02:00
}
2023-09-21 17:50:53 +01:00
} , timeout ) ;
}
/ * *
* Runs a single task immediately
*
* ( use ` scheduleSingleTask ` instead to queue a task to run after some timeout )
* /
public async runSingleTask ( task : TaskName ) : Promise < void > {
if ( ! Common . indexingEnabled ( ) || this . tasksRunning [ task ] ) {
return ;
2022-07-16 09:22:45 +02:00
}
2023-09-21 17:50:53 +01:00
this . tasksRunning [ task ] = true ;
2023-02-17 21:21:21 +09:00
2023-09-21 17:50:53 +01:00
switch ( task ) {
case 'blocksPrices' : {
2024-03-10 17:12:19 +01:00
if ( ! [ 'testnet' , 'signet' ] . includes ( config . MEMPOOL . NETWORK ) && config . FIAT_PRICE . ENABLED ) {
2023-09-21 17:50:53 +01:00
let lastestPriceId ;
try {
lastestPriceId = await PricesRepository . $getLatestPriceId ( ) ;
} catch ( e ) {
logger . debug ( 'failed to fetch latest price id from db: ' + ( e instanceof Error ? e.message : e ) ) ;
} if ( priceUpdater . historyInserted === false || lastestPriceId === null ) {
logger . debug ( ` Blocks prices indexer is waiting for the price updater to complete ` , logger . tags . mining ) ;
this . scheduleSingleTask ( task , 10000 ) ;
} else {
logger . debug ( ` Blocks prices indexer will run now ` , logger . tags . mining ) ;
await mining . $indexBlockPrices ( ) ;
}
}
} break ;
case 'coinStatsIndex' : {
logger . debug ( ` Indexing coinStatsIndex now ` ) ;
await mining . $indexCoinStatsIndex ( ) ;
} break ;
2023-02-17 21:21:21 +09:00
}
2023-09-21 17:50:53 +01:00
this . tasksRunning [ task ] = false ;
2022-07-16 09:22:45 +02:00
}
2023-02-17 21:21:21 +09:00
public async $run ( ) : Promise < void > {
2022-05-19 16:40:38 +02:00
if ( ! Common . indexingEnabled ( ) || this . runIndexer === false ||
this . indexerRunning === true || mempool . hasPriority ( )
) {
return ;
}
2024-03-10 17:12:19 +01:00
if ( config . FIAT_PRICE . ENABLED ) {
try {
await priceUpdater . $run ( ) ;
} catch ( e ) {
logger . err ( ` Running priceUpdater failed. Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
}
2023-07-29 19:27:19 +09:00
}
2022-06-14 10:58:17 +02:00
// Do not attempt to index anything unless Bitcoin Core is fully synced
const blockchainInfo = await bitcoinClient . getBlockchainInfo ( ) ;
if ( blockchainInfo . blocks !== blockchainInfo . headers ) {
return ;
}
2022-05-19 16:40:38 +02:00
this . runIndexer = false ;
this . indexerRunning = true ;
2023-02-25 11:46:52 +09:00
logger . debug ( ` Running mining indexer ` ) ;
2023-02-17 21:21:21 +09:00
await this . checkAvailableCoreIndexes ( ) ;
2022-07-09 18:25:16 +02:00
2022-05-19 16:40:38 +02:00
try {
2022-06-20 16:35:10 +02:00
const chainValid = await blocks . $generateBlockDatabase ( ) ;
if ( chainValid === false ) {
// Chain of block hash was invalid, so we need to reindex. Stop here and continue at the next iteration
2023-02-25 11:46:52 +09:00
logger . warn ( ` The chain of block hash is invalid, re-indexing invalid data in 10 seconds. ` , logger . tags . mining ) ;
2022-06-25 19:50:39 +02:00
setTimeout ( ( ) = > this . reindex ( ) , 10000 ) ;
2022-06-20 16:35:10 +02:00
this . indexerRunning = false ;
return ;
}
2022-07-16 09:22:45 +02:00
this . runSingleTask ( 'blocksPrices' ) ;
2024-06-24 06:15:01 +00:00
await blocks . $indexCoinbaseAddresses ( ) ;
2022-06-25 12:14:32 +02:00
await mining . $indexDifficultyAdjustments ( ) ;
2022-05-19 16:40:38 +02:00
await mining . $generateNetworkHashrateHistory ( ) ;
await mining . $generatePoolHashrateHistory ( ) ;
2022-06-18 16:48:02 +02:00
await blocks . $generateBlocksSummariesDatabase ( ) ;
2022-11-27 13:46:23 +09:00
await blocks . $generateCPFPDatabase ( ) ;
2023-06-09 13:46:43 -04:00
await blocks . $generateAuditStats ( ) ;
2023-06-20 16:21:54 -04:00
await auditReplicator . $sync ( ) ;
2024-05-06 20:52:44 +02:00
await statisticsReplicator . $sync ( ) ;
2024-03-11 21:19:03 +00:00
await AccelerationRepository . $indexPastAccelerations ( ) ;
2024-07-20 11:54:09 +00:00
await BlocksAuditsRepository . $migrateAuditsV0toV1 ( ) ;
2024-02-19 02:43:19 +00:00
// do not wait for classify blocks to finish
blocks . $classifyBlocks ( ) ;
2022-05-19 16:40:38 +02:00
} catch ( e ) {
2022-06-25 19:50:39 +02:00
this . indexerRunning = false ;
logger . err ( ` Indexer failed, trying again in 10 seconds. Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
setTimeout ( ( ) = > this . reindex ( ) , 10000 ) ;
2022-07-09 18:25:16 +02:00
this . indexerRunning = false ;
return ;
2022-05-19 16:40:38 +02:00
}
this . indexerRunning = false ;
2022-07-09 18:25:16 +02:00
const runEvery = 1000 * 3600 ; // 1 hour
logger . debug ( ` Indexing completed. Next run planned at ${ new Date ( new Date ( ) . getTime ( ) + runEvery ) . toUTCString ( ) } ` ) ;
setTimeout ( ( ) = > this . reindex ( ) , runEvery ) ;
2022-05-19 16:40:38 +02:00
}
}
export default new Indexer ( ) ;