2021-08-19 07:57:32 +00:00
//
// Copyright 2021 Vulcanize, Inc.
//
import assert from 'assert' ;
import debug from 'debug' ;
2021-12-13 10:08:34 +00:00
import { In } from 'typeorm' ;
2021-08-19 07:57:32 +00:00
2021-11-01 06:01:54 +00:00
import { JobQueueConfig } from './config' ;
2021-10-12 10:32:56 +00:00
import {
JOB_KIND_INDEX ,
JOB_KIND_PRUNE ,
JOB_KIND_EVENTS ,
JOB_KIND_CONTRACT ,
MAX_REORG_DEPTH ,
QUEUE_BLOCK_PROCESSING ,
2021-10-26 11:40:03 +00:00
QUEUE_EVENT_PROCESSING
2021-10-12 10:32:56 +00:00
} from './constants' ;
2021-08-19 07:57:32 +00:00
import { JobQueue } from './job-queue' ;
2022-05-26 12:30:17 +00:00
import { EventInterface , IndexerInterface , IPLDIndexerInterface , SyncStatusInterface } from './types' ;
2021-12-10 05:14:10 +00:00
import { wait } from './misc' ;
2022-07-22 07:47:56 +00:00
import { createPruningJob , processBatchEvents } from './common' ;
2022-08-03 10:56:51 +00:00
import { lastBlockNumEvents , lastBlockProcessDuration , lastProcessedBlockNumber } from './metrics' ;
2021-08-19 07:57:32 +00:00
const log = debug ( 'vulcanize:job-runner' ) ;
export class JobRunner {
2022-05-26 12:30:17 +00:00
_indexer : IndexerInterface | IPLDIndexerInterface
2021-08-19 07:57:32 +00:00
_jobQueue : JobQueue
2021-08-24 06:25:29 +00:00
_jobQueueConfig : JobQueueConfig
2021-12-17 09:50:33 +00:00
_blockProcessStartTime? : Date
2022-08-03 10:56:51 +00:00
_endBlockProcessTimer ? : ( ) = > void
2022-09-06 13:51:24 +00:00
_shutDown = false
_signalCount = 0
2021-08-19 07:57:32 +00:00
2021-11-01 06:01:54 +00:00
constructor ( jobQueueConfig : JobQueueConfig , indexer : IndexerInterface , jobQueue : JobQueue ) {
2021-08-19 07:57:32 +00:00
this . _indexer = indexer ;
this . _jobQueue = jobQueue ;
2021-10-12 10:32:56 +00:00
this . _jobQueueConfig = jobQueueConfig ;
2021-08-19 07:57:32 +00:00
}
async processBlock ( job : any ) : Promise < void > {
2021-08-30 05:46:54 +00:00
const { data : { kind } } = job ;
2021-08-19 07:57:32 +00:00
2021-12-21 10:16:05 +00:00
const syncStatus = await this . _indexer . getSyncStatus ( ) ;
assert ( syncStatus ) ;
2021-08-30 05:46:54 +00:00
switch ( kind ) {
case JOB_KIND_INDEX :
2021-12-21 10:16:05 +00:00
await this . _indexBlock ( job , syncStatus ) ;
2021-08-30 05:46:54 +00:00
break ;
2021-08-24 06:25:29 +00:00
2021-08-30 05:46:54 +00:00
case JOB_KIND_PRUNE :
2021-12-21 10:16:05 +00:00
await this . _pruneChain ( job , syncStatus ) ;
2021-08-30 05:46:54 +00:00
break ;
2021-08-24 06:25:29 +00:00
2021-08-30 05:46:54 +00:00
default :
log ( ` Invalid Job kind ${ kind } in QUEUE_BLOCK_PROCESSING. ` ) ;
break ;
2021-08-24 06:25:29 +00:00
}
2021-08-19 07:57:32 +00:00
}
2021-12-08 05:41:29 +00:00
async processEvent ( job : any ) : Promise < EventInterface | void > {
const { data : { kind } } = job ;
2021-08-19 07:57:32 +00:00
2021-12-08 05:41:29 +00:00
switch ( kind ) {
2021-12-10 05:14:10 +00:00
case JOB_KIND_EVENTS :
await this . _processEvents ( job ) ;
break ;
2021-08-19 07:57:32 +00:00
2021-12-08 05:41:29 +00:00
case JOB_KIND_CONTRACT :
2021-12-10 05:14:10 +00:00
await this . _updateWatchedContracts ( job ) ;
break ;
2021-08-19 07:57:32 +00:00
2021-12-08 05:41:29 +00:00
default :
log ( ` Invalid Job kind ${ kind } in QUEUE_EVENT_PROCESSING. ` ) ;
break ;
2021-08-19 07:57:32 +00:00
}
2021-12-10 05:14:10 +00:00
await this . _jobQueue . markComplete ( job ) ;
2021-08-19 07:57:32 +00:00
}
2022-09-06 13:51:24 +00:00
handleShutdown ( ) {
process . on ( 'SIGINT' , this . _processShutdown . bind ( this ) ) ;
process . on ( 'SIGTERM' , this . _processShutdown . bind ( this ) ) ;
}
async _processShutdown ( ) {
this . _shutDown = true ;
this . _signalCount ++ ;
if ( this . _signalCount >= 3 || process . env . YARN_CHILD_PROCESS === 'true' ) {
// Forceful exit on receiving signal for the 3rd time or if job-runner is a child process of yarn.
this . _jobQueue . stop ( ) ;
process . exit ( 1 ) ;
}
}
2021-12-21 10:16:05 +00:00
async _pruneChain ( job : any , syncStatus : SyncStatusInterface ) : Promise < void > {
2022-09-01 08:47:43 +00:00
console . time ( 'time:job-runner#_pruneChain' ) ;
2021-08-30 05:46:54 +00:00
const { pruneBlockHeight } = job . data ;
2021-08-19 07:57:32 +00:00
log ( ` Processing chain pruning at ${ pruneBlockHeight } ` ) ;
// Assert we're at a depth where pruning is safe.
assert ( syncStatus . latestIndexedBlockNumber >= ( pruneBlockHeight + MAX_REORG_DEPTH ) ) ;
// Check that we haven't already pruned at this depth.
if ( syncStatus . latestCanonicalBlockNumber >= pruneBlockHeight ) {
log ( ` Already pruned at block height ${ pruneBlockHeight } , latestCanonicalBlockNumber ${ syncStatus . latestCanonicalBlockNumber } ` ) ;
} else {
// Check how many branches there are at the given height/block number.
const blocksAtHeight = await this . _indexer . getBlocksAtHeight ( pruneBlockHeight , false ) ;
// Should be at least 1.
assert ( blocksAtHeight . length ) ;
2021-12-17 04:53:04 +00:00
let newCanonicalBlockHash ;
2021-08-20 06:56:37 +00:00
// We have more than one node at this height, so prune all nodes not reachable from indexed block at max reorg depth from prune height.
2021-08-19 07:57:32 +00:00
// This will lead to orphaned nodes, which will get pruned at the next height.
if ( blocksAtHeight . length > 1 ) {
2021-08-20 06:56:37 +00:00
const [ indexedBlock ] = await this . _indexer . getBlocksAtHeight ( pruneBlockHeight + MAX_REORG_DEPTH , false ) ;
2021-08-23 11:36:35 +00:00
// Get ancestor blockHash from indexed block at prune height.
const ancestorBlockHash = await this . _indexer . getAncestorAtDepth ( indexedBlock . blockHash , MAX_REORG_DEPTH ) ;
2021-12-17 04:53:04 +00:00
newCanonicalBlockHash = ancestorBlockHash ;
2021-08-23 11:36:35 +00:00
const blocksToBePruned = blocksAtHeight . filter ( block = > ancestorBlockHash !== block . blockHash ) ;
if ( blocksToBePruned . length ) {
// Mark blocks pruned which are not the ancestor block.
await this . _indexer . markBlocksAsPruned ( blocksToBePruned ) ;
2021-08-19 07:57:32 +00:00
}
2021-12-17 04:53:04 +00:00
} else {
newCanonicalBlockHash = blocksAtHeight [ 0 ] . blockHash ;
2021-08-19 07:57:32 +00:00
}
2021-12-17 04:53:04 +00:00
// Update the canonical block in the SyncStatus.
await this . _indexer . updateSyncStatusCanonicalBlock ( newCanonicalBlockHash , pruneBlockHeight ) ;
2021-08-19 07:57:32 +00:00
}
2022-09-01 08:47:43 +00:00
console . timeEnd ( 'time:job-runner#_pruneChain' ) ;
2021-08-19 07:57:32 +00:00
}
2021-08-30 05:46:54 +00:00
2021-12-21 10:16:05 +00:00
async _indexBlock ( job : any , syncStatus : SyncStatusInterface ) : Promise < void > {
2021-10-12 10:32:56 +00:00
const { data : { cid , blockHash , blockNumber , parentHash , priority , timestamp } } = job ;
2021-12-17 09:50:33 +00:00
2021-12-17 06:27:09 +00:00
const indexBlockStartTime = new Date ( ) ;
2021-12-17 09:50:33 +00:00
// Log time taken to complete processing of previous block.
if ( this . _blockProcessStartTime ) {
const blockProcessDuration = indexBlockStartTime . getTime ( ) - this . _blockProcessStartTime . getTime ( ) ;
log ( ` time:job-runner#_indexBlock-process-block- ${ blockNumber - 1 } : ${ blockProcessDuration } ms ` ) ;
log ( ` Total block process time ( ${ blockNumber - 1 } ): ${ blockProcessDuration } ms ` ) ;
}
this . _blockProcessStartTime = indexBlockStartTime ;
2021-08-30 05:46:54 +00:00
log ( ` Processing block number ${ blockNumber } hash ${ blockHash } ` ) ;
// Check if chain pruning is caught up.
if ( ( syncStatus . latestIndexedBlockNumber - syncStatus . latestCanonicalBlockNumber ) > MAX_REORG_DEPTH ) {
await createPruningJob ( this . _jobQueue , syncStatus . latestCanonicalBlockNumber , priority ) ;
const message = ` Chain pruning not caught up yet, latest canonical block number ${ syncStatus . latestCanonicalBlockNumber } and latest indexed block number ${ syncStatus . latestIndexedBlockNumber } ` ;
log ( message ) ;
throw new Error ( message ) ;
}
2022-09-01 08:47:43 +00:00
console . time ( 'time:job-runner#_indexBlock-get-block-progress-entities' ) ;
2021-12-13 10:08:34 +00:00
let [ parentBlock , blockProgress ] = await this . _indexer . getBlockProgressEntities (
{
blockHash : In ( [ parentHash , blockHash ] )
} ,
{
order : {
blockNumber : 'ASC'
}
}
) ;
2022-09-01 08:47:43 +00:00
console . timeEnd ( 'time:job-runner#_indexBlock-get-block-progress-entities' ) ;
2021-12-13 10:08:34 +00:00
2021-08-30 05:46:54 +00:00
// Check if parent block has been processed yet, if not, push a high priority job to process that first and abort.
// However, don't go beyond the `latestCanonicalBlockHash` from SyncStatus as we have to assume the reorg can't be that deep.
if ( blockHash !== syncStatus . latestCanonicalBlockHash ) {
2021-12-17 06:27:09 +00:00
// Create a higher priority job to index parent block and then abort.
// We don't have to worry about aborting as this job will get retried later.
const newPriority = ( priority || 0 ) + 1 ;
2021-12-13 10:08:34 +00:00
if ( ! parentBlock || parentBlock . blockHash !== parentHash ) {
2021-12-03 10:53:11 +00:00
const blocks = await this . _indexer . getBlocks ( { blockHash : parentHash } ) ;
if ( ! blocks . length ) {
const message = ` No blocks at parentHash ${ parentHash } , aborting ` ;
log ( message ) ;
throw new Error ( message ) ;
}
2021-10-12 10:32:56 +00:00
const [ { cid : parentCid , blockNumber : parentBlockNumber , parentHash : grandparentHash , timestamp : parentTimestamp } ] = blocks ;
2021-08-30 05:46:54 +00:00
await this . _jobQueue . pushJob ( QUEUE_BLOCK_PROCESSING , {
kind : JOB_KIND_INDEX ,
2021-10-12 10:32:56 +00:00
cid : parentCid ,
2021-08-30 05:46:54 +00:00
blockHash : parentHash ,
blockNumber : parentBlockNumber ,
2021-12-03 10:53:11 +00:00
parentHash : grandparentHash ,
2021-08-30 05:46:54 +00:00
timestamp : parentTimestamp ,
priority : newPriority
} , { priority : newPriority } ) ;
const message = ` Parent block number ${ parentBlockNumber } hash ${ parentHash } of block number ${ blockNumber } hash ${ blockHash } not fetched yet, aborting ` ;
log ( message ) ;
2022-07-15 13:38:33 +00:00
throw new Error ( message ) ;
2021-08-30 05:46:54 +00:00
}
2021-12-13 10:08:34 +00:00
if ( ! parentBlock . isComplete ) {
2021-08-30 05:46:54 +00:00
// Parent block indexing needs to finish before this block can be indexed.
2021-12-13 10:08:34 +00:00
const message = ` Indexing incomplete for parent block number ${ parentBlock . blockNumber } hash ${ parentHash } of block number ${ blockNumber } hash ${ blockHash } , aborting ` ;
2021-08-30 05:46:54 +00:00
log ( message ) ;
2021-12-17 06:27:09 +00:00
await this . _jobQueue . pushJob ( QUEUE_BLOCK_PROCESSING , {
kind : JOB_KIND_INDEX ,
2021-10-12 10:32:56 +00:00
cid : parentBlock.cid ,
2021-12-17 06:27:09 +00:00
blockHash : parentHash ,
blockNumber : parentBlock.blockNumber ,
parentHash : parentBlock.parentHash ,
timestamp : parentBlock.blockTimestamp ,
priority : newPriority
} , { priority : newPriority } ) ;
2021-08-30 05:46:54 +00:00
throw new Error ( message ) ;
2021-12-20 11:30:14 +00:00
} else {
// Remove the unknown events of the parent block if it is marked complete.
2022-09-01 08:47:43 +00:00
console . time ( 'time:job-runner#_indexBlock-remove-unknown-events' ) ;
2021-12-20 11:30:14 +00:00
await this . _indexer . removeUnknownEvents ( parentBlock ) ;
2022-09-01 08:47:43 +00:00
console . timeEnd ( 'time:job-runner#_indexBlock-remove-unknown-events' ) ;
2021-08-30 05:46:54 +00:00
}
2021-12-16 11:46:48 +00:00
} else {
blockProgress = parentBlock ;
2021-08-30 05:46:54 +00:00
}
if ( ! blockProgress ) {
const { jobDelayInMilliSecs = 0 } = this . _jobQueueConfig ;
// Delay required to process block.
await wait ( jobDelayInMilliSecs ) ;
2022-09-01 08:47:43 +00:00
console . time ( 'time:job-runner#_indexBlock-fetch-block-events' ) ;
2021-10-12 10:32:56 +00:00
blockProgress = await this . _indexer . fetchBlockEvents ( { cid , blockHash , blockNumber , parentHash , blockTimestamp : timestamp } ) ;
2022-09-01 08:47:43 +00:00
console . timeEnd ( 'time:job-runner#_indexBlock-fetch-block-events' ) ;
2021-12-16 11:46:48 +00:00
}
2021-12-10 05:14:10 +00:00
2021-12-29 07:51:39 +00:00
if ( this . _indexer . processBlock ) {
await this . _indexer . processBlock ( blockHash , blockNumber ) ;
}
2022-07-11 05:59:33 +00:00
// Push job to event processing queue.
// Block with all events processed or no events will not be processed again due to check in _processEvents.
await this . _jobQueue . pushJob ( QUEUE_EVENT_PROCESSING , { kind : JOB_KIND_EVENTS , blockHash : blockProgress.blockHash , publish : true } ) ;
2021-12-16 11:46:48 +00:00
2021-12-17 06:27:09 +00:00
const indexBlockDuration = new Date ( ) . getTime ( ) - indexBlockStartTime . getTime ( ) ;
log ( ` time:job-runner#_indexBlock: ${ indexBlockDuration } ms ` ) ;
2021-08-30 05:46:54 +00:00
}
2021-12-08 05:41:29 +00:00
2021-12-10 05:14:10 +00:00
async _processEvents ( job : any ) : Promise < void > {
const { blockHash } = job . data ;
2021-12-17 09:50:33 +00:00
console . time ( 'time:job-runner#_processEvents-get-block-progress' ) ;
2022-07-22 07:47:56 +00:00
const block = await this . _indexer . getBlockProgress ( blockHash ) ;
2021-12-17 09:50:33 +00:00
console . timeEnd ( 'time:job-runner#_processEvents-get-block-progress' ) ;
2021-12-10 05:14:10 +00:00
assert ( block ) ;
2021-12-14 09:18:29 +00:00
console . time ( 'time:job-runner#_processEvents-events' ) ;
2022-07-22 07:47:56 +00:00
await processBatchEvents ( this . _indexer , block , this . _jobQueueConfig . eventsInBatch ) ;
2021-12-14 09:18:29 +00:00
console . timeEnd ( 'time:job-runner#_processEvents-events' ) ;
2022-08-03 10:56:51 +00:00
// Update metrics
lastProcessedBlockNumber . set ( block . blockNumber ) ;
lastBlockNumEvents . set ( block . numEvents ) ;
if ( this . _endBlockProcessTimer ) {
this . _endBlockProcessTimer ( ) ;
}
this . _endBlockProcessTimer = lastBlockProcessDuration . startTimer ( ) ;
2022-09-06 13:51:24 +00:00
if ( this . _shutDown ) {
log ( ` Graceful shutdown after processing block ${ block . blockNumber } ` ) ;
this . _jobQueue . stop ( ) ;
process . exit ( 0 ) ;
}
2021-12-08 05:41:29 +00:00
}
async _updateWatchedContracts ( job : any ) : Promise < void > {
const { data : { contract } } = job ;
assert ( this . _indexer . cacheContract ) ;
this . _indexer . cacheContract ( contract ) ;
2022-05-26 12:30:17 +00:00
const ipldIndexer = this . _indexer as IPLDIndexerInterface ;
if ( ipldIndexer . updateIPLDStatusMap ) {
ipldIndexer . updateIPLDStatusMap ( contract . address , { } ) ;
}
2021-12-08 05:41:29 +00:00
}
2021-08-19 07:57:32 +00:00
}