2020-12-20 22:36:36 +07:00
import config from '../config' ;
import bitcoinApi from './bitcoin/bitcoin-api-factory' ;
2020-10-13 15:27:52 +07:00
import logger from '../logger' ;
2020-02-23 19:16:50 +07:00
import memPool from './mempool' ;
2022-06-15 19:53:37 +00:00
import { BlockExtended , BlockSummary , PoolTag , TransactionExtended , TransactionStripped , TransactionMinerInfo } from '../mempool.interfaces' ;
2020-05-24 16:29:30 +07:00
import { Common } from './common' ;
2020-10-27 00:05:06 +07:00
import diskCache from './disk-cache' ;
2020-12-21 23:08:34 +07:00
import transactionUtils from './transaction-utils' ;
2021-09-15 01:47:24 +04:00
import bitcoinClient from './bitcoin/bitcoin-client' ;
2022-06-15 19:53:37 +00:00
import { IBitcoinApi } from './bitcoin/bitcoin-api.interface' ;
2022-01-05 15:41:14 +09:00
import { IEsploraApi } from './bitcoin/esplora-api.interface' ;
import poolsRepository from '../repositories/PoolsRepository' ;
import blocksRepository from '../repositories/BlocksRepository' ;
2022-02-08 15:47:43 +09:00
import loadingIndicators from './loading-indicators' ;
2022-03-13 11:38:45 +01:00
import BitcoinApi from './bitcoin/bitcoin-api' ;
2022-03-16 17:00:06 +01:00
import { prepareBlock } from '../utils/blocks-utils' ;
2022-04-18 17:49:22 +09:00
import BlocksRepository from '../repositories/BlocksRepository' ;
import HashratesRepository from '../repositories/HashratesRepository' ;
2022-05-19 16:40:38 +02:00
import indexer from '../indexer' ;
2022-06-07 11:28:39 +02:00
import poolsParser from './pools-parser' ;
2022-06-18 16:48:02 +02:00
import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository' ;
2022-06-25 12:14:32 +02:00
import mining from './mining' ;
import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository' ;
import difficultyAdjustment from './difficulty-adjustment' ;
2019-07-21 17:59:47 +03:00
class Blocks {
2020-12-28 04:47:22 +07:00
private blocks : BlockExtended [ ] = [ ] ;
2022-06-15 19:53:37 +00:00
private blockSummaries : BlockSummary [ ] = [ ] ;
2019-08-29 01:17:31 +02:00
private currentBlockHeight = 0 ;
2021-07-23 14:35:04 +03:00
private currentDifficulty = 0 ;
2020-09-21 19:41:12 +07:00
private lastDifficultyAdjustmentTime = 0 ;
2021-07-23 14:35:04 +03:00
private previousDifficultyRetarget = 0 ;
2020-12-28 04:47:22 +07:00
private newBlockCallbacks : ( ( block : BlockExtended , txIds : string [ ] , transactions : TransactionExtended [ ] ) = > void ) [ ] = [ ] ;
2019-08-29 01:17:31 +02:00
2020-02-16 22:15:07 +07:00
constructor ( ) { }
2019-07-21 17:59:47 +03:00
2020-12-28 04:47:22 +07:00
public getBlocks ( ) : BlockExtended [ ] {
2019-07-21 17:59:47 +03:00
return this . blocks ;
}
2020-12-28 04:47:22 +07:00
public setBlocks ( blocks : BlockExtended [ ] ) {
2020-02-29 21:52:04 +07:00
this . blocks = blocks ;
}
2022-06-15 19:53:37 +00:00
public getBlockSummaries ( ) : BlockSummary [ ] {
return this . blockSummaries ;
}
public setBlockSummaries ( blockSummaries : BlockSummary [ ] ) {
this . blockSummaries = blockSummaries ;
}
2020-12-28 04:47:22 +07:00
public setNewBlockCallback ( fn : ( block : BlockExtended , txIds : string [ ] , transactions : TransactionExtended [ ] ) = > void ) {
2020-09-27 17:21:18 +07:00
this . newBlockCallbacks . push ( fn ) ;
2019-07-21 17:59:47 +03:00
}
2022-01-05 15:41:14 +09:00
/ * *
* Return the list of transaction for a block
* @param blockHash
* @param blockHeight
* @param onlyCoinbase - Set to true if you only need the coinbase transaction
* @returns Promise < TransactionExtended [ ] >
* /
2022-02-10 23:02:12 +09:00
private async $getTransactionsExtended (
blockHash : string ,
blockHeight : number ,
onlyCoinbase : boolean ,
quiet : boolean = false ,
) : Promise < TransactionExtended [ ] > {
2022-01-05 15:41:14 +09:00
const transactions : TransactionExtended [ ] = [ ] ;
const txIds : string [ ] = await bitcoinApi . $getTxIdsForBlock ( blockHash ) ;
const mempool = memPool . getMempool ( ) ;
let transactionsFound = 0 ;
let transactionsFetched = 0 ;
for ( let i = 0 ; i < txIds . length ; i ++ ) {
if ( mempool [ txIds [ i ] ] ) {
// We update blocks before the mempool (index.ts), therefore we can
// optimize here by directly fetching txs in the "outdated" mempool
transactions . push ( mempool [ txIds [ i ] ] ) ;
transactionsFound ++ ;
2022-02-11 21:25:58 +09:00
} else if ( config . MEMPOOL . BACKEND === 'esplora' || ! memPool . hasPriority ( ) || i === 0 ) {
2022-01-05 15:41:14 +09:00
// Otherwise we fetch the tx data through backend services (esplora, electrum, core rpc...)
2022-02-10 23:02:12 +09:00
if ( ! quiet && ( i % ( Math . round ( ( txIds . length ) / 10 ) ) === 0 || i + 1 === txIds . length ) ) { // Avoid log spam
2022-01-05 15:41:14 +09:00
logger . debug ( ` Indexing tx ${ i + 1 } of ${ txIds . length } in block # ${ blockHeight } ` ) ;
}
try {
const tx = await transactionUtils . $getTransactionExtended ( txIds [ i ] ) ;
transactions . push ( tx ) ;
transactionsFetched ++ ;
} catch ( e ) {
if ( i === 0 ) {
2022-04-13 16:29:52 +09:00
const msg = ` Cannot fetch coinbase tx ${ txIds [ i ] } . Reason: ` + ( e instanceof Error ? e.message : e ) ;
logger . err ( msg ) ;
throw new Error ( msg ) ;
} else {
logger . err ( ` Cannot fetch tx ${ txIds [ i ] } . Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
2022-01-05 15:41:14 +09:00
}
}
}
if ( onlyCoinbase === true ) {
break ; // Fetch the first transaction and exit
}
}
transactions . forEach ( ( tx ) = > {
if ( ! tx . cpfpChecked ) {
Common . setRelativesAndGetCpfpInfo ( tx , mempool ) ; // Child Pay For Parent
}
} ) ;
2022-02-10 23:02:12 +09:00
if ( ! quiet ) {
logger . debug ( ` ${ transactionsFound } of ${ txIds . length } found in mempool. ${ transactionsFetched } fetched through backend service. ` ) ;
}
2022-01-05 15:41:14 +09:00
return transactions ;
}
2022-06-15 19:53:37 +00:00
/ * *
* Return a block summary ( list of stripped transactions )
* @param block
* @returns BlockSummary
* /
private summarizeBlock ( block : IBitcoinApi.VerboseBlock ) : BlockSummary {
const stripped = block . tx . map ( ( tx ) = > {
return {
txid : tx.txid ,
vsize : tx.vsize ,
fee : tx.fee ? Math . round ( tx . fee * 100000000 ) : 0 ,
value : Math.round ( tx . vout . reduce ( ( acc , vout ) = > acc + ( vout . value ? vout.value : 0 ) , 0 ) * 100000000 )
} ;
} ) ;
return {
id : block.hash ,
transactions : stripped
} ;
}
2022-01-05 15:41:14 +09:00
/ * *
* Return a block with additional data ( reward , coinbase , fees . . . )
* @param block
* @param transactions
* @returns BlockExtended
* /
2022-03-13 11:38:45 +01:00
private async $getBlockExtended ( block : IEsploraApi.Block , transactions : TransactionExtended [ ] ) : Promise < BlockExtended > {
const blockExtended : BlockExtended = Object . assign ( { extras : { } } , block ) ;
2022-02-08 15:47:43 +09:00
blockExtended . extras . reward = transactions [ 0 ] . vout . reduce ( ( acc , curr ) = > acc + curr . value , 0 ) ;
blockExtended . extras . coinbaseTx = transactionUtils . stripCoinbaseTransaction ( transactions [ 0 ] ) ;
2022-03-16 12:10:18 +01:00
blockExtended . extras . coinbaseRaw = blockExtended . extras . coinbaseTx . vin [ 0 ] . scriptsig ;
2022-03-11 14:54:34 +01:00
if ( block . height === 0 ) {
2022-03-13 11:38:45 +01:00
blockExtended . extras . medianFee = 0 ; // 50th percentiles
blockExtended . extras . feeRange = [ 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ;
blockExtended . extras . totalFees = 0 ;
blockExtended . extras . avgFee = 0 ;
blockExtended . extras . avgFeeRate = 0 ;
} else {
2022-03-15 20:57:27 +01:00
const stats = await bitcoinClient . getBlockStats ( block . id , [
'feerate_percentiles' , 'minfeerate' , 'maxfeerate' , 'totalfee' , 'avgfee' , 'avgfeerate'
] ) ;
2022-03-11 14:54:34 +01:00
blockExtended . extras . medianFee = stats . feerate_percentiles [ 2 ] ; // 50th percentiles
2022-03-13 11:38:45 +01:00
blockExtended . extras . feeRange = [ stats . minfeerate , stats . feerate_percentiles , stats . maxfeerate ] . flat ( ) ;
blockExtended . extras . totalFees = stats . totalfee ;
blockExtended . extras . avgFee = stats . avgfee ;
blockExtended . extras . avgFeeRate = stats . avgfeerate ;
}
2022-01-05 15:41:14 +09:00
2022-07-08 16:34:00 +02:00
if ( [ 'mainnet' , 'testnet' , 'signet' ] . includes ( config . MEMPOOL . NETWORK ) ) {
2022-04-25 15:50:26 +09:00
let pool : PoolTag ;
if ( blockExtended . extras ? . coinbaseTx !== undefined ) {
pool = await this . $findBlockMiner ( blockExtended . extras ? . coinbaseTx ) ;
} else {
2022-06-07 11:28:39 +02:00
if ( config . DATABASE . ENABLED === true ) {
pool = await poolsRepository . $getUnknownPool ( ) ;
} else {
pool = poolsParser . unknownPool ;
}
2022-04-25 15:50:26 +09:00
}
2022-04-07 18:14:28 +09:00
2022-04-25 15:50:26 +09:00
if ( ! pool ) { // We should never have this situation in practise
logger . warn ( ` Cannot assign pool to block ${ blockExtended . height } and 'unknown' pool does not exist. ` +
` Check your "pools" table entries ` ) ;
return blockExtended ;
}
2022-02-08 15:47:43 +09:00
2022-04-25 15:50:26 +09:00
blockExtended . extras . pool = {
id : pool.id ,
name : pool.name ,
slug : pool.slug ,
} ;
}
2022-04-20 13:12:32 +09:00
2022-01-05 15:41:14 +09:00
return blockExtended ;
}
/ * *
* Try to find which miner found the block
* @param txMinerInfo
* @returns
* /
2022-01-20 17:20:02 +09:00
private async $findBlockMiner ( txMinerInfo : TransactionMinerInfo | undefined ) : Promise < PoolTag > {
2022-01-25 18:33:46 +09:00
if ( txMinerInfo === undefined || txMinerInfo . vout . length < 1 ) {
2022-06-07 11:28:39 +02:00
if ( config . DATABASE . ENABLED === true ) {
return await poolsRepository . $getUnknownPool ( ) ;
} else {
return poolsParser . unknownPool ;
}
2022-01-05 15:41:14 +09:00
}
const asciiScriptSig = transactionUtils . hex2ascii ( txMinerInfo . vin [ 0 ] . scriptsig ) ;
const address = txMinerInfo . vout [ 0 ] . scriptpubkey_address ;
2022-06-07 11:28:39 +02:00
let pools : PoolTag [ ] = [ ] ;
if ( config . DATABASE . ENABLED === true ) {
pools = await poolsRepository . $getPools ( ) ;
} else {
pools = poolsParser . miningPools ;
}
2022-01-05 15:41:14 +09:00
for ( let i = 0 ; i < pools . length ; ++ i ) {
if ( address !== undefined ) {
2022-01-20 17:20:02 +09:00
const addresses : string [ ] = JSON . parse ( pools [ i ] . addresses ) ;
2022-01-05 15:41:14 +09:00
if ( addresses . indexOf ( address ) !== - 1 ) {
return pools [ i ] ;
}
}
2022-01-20 17:20:02 +09:00
const regexes : string [ ] = JSON . parse ( pools [ i ] . regexes ) ;
2022-01-05 15:41:14 +09:00
for ( let y = 0 ; y < regexes . length ; ++ y ) {
2022-04-22 03:25:34 -04:00
const regex = new RegExp ( regexes [ y ] , 'i' ) ;
const match = asciiScriptSig . match ( regex ) ;
2022-01-05 15:41:14 +09:00
if ( match !== null ) {
return pools [ i ] ;
}
}
}
2022-06-07 11:28:39 +02:00
if ( config . DATABASE . ENABLED === true ) {
return await poolsRepository . $getUnknownPool ( ) ;
} else {
return poolsParser . unknownPool ;
}
2022-01-05 15:41:14 +09:00
}
2022-06-18 16:48:02 +02:00
/ * *
* [ INDEXING ] Index all blocks summaries for the block txs visualization
* /
public async $generateBlocksSummariesDatabase() {
if ( Common . blocksSummariesIndexingEnabled ( ) === false ) {
return ;
}
try {
// Get all indexed block hash
const indexedBlocks = await blocksRepository . $getIndexedBlocks ( ) ;
2022-06-20 16:35:10 +02:00
const indexedBlockSummariesHashesArray = await BlocksSummariesRepository . $getIndexedSummariesId ( ) ;
const indexedBlockSummariesHashes = { } ; // Use a map for faster seek during the indexing loop
for ( const hash of indexedBlockSummariesHashesArray ) {
indexedBlockSummariesHashes [ hash ] = true ;
}
2022-06-18 16:48:02 +02:00
// Logging
2022-06-20 16:35:10 +02:00
let newlyIndexed = 0 ;
let totalIndexed = indexedBlockSummariesHashesArray . length ;
2022-06-18 16:48:02 +02:00
let indexedThisRun = 0 ;
let timer = new Date ( ) . getTime ( ) / 1000 ;
const startedAt = new Date ( ) . getTime ( ) / 1000 ;
for ( const block of indexedBlocks ) {
2022-06-20 16:35:10 +02:00
if ( indexedBlockSummariesHashes [ block . hash ] === true ) {
2022-06-18 16:48:02 +02:00
continue ;
}
// Logging
const elapsedSeconds = Math . max ( 1 , Math . round ( ( new Date ( ) . getTime ( ) / 1000 ) - timer ) ) ;
if ( elapsedSeconds > 5 ) {
const runningFor = Math . max ( 1 , Math . round ( ( new Date ( ) . getTime ( ) / 1000 ) - startedAt ) ) ;
const blockPerSeconds = Math . max ( 1 , indexedThisRun / elapsedSeconds ) ;
const progress = Math . round ( totalIndexed / indexedBlocks . length * 10000 ) / 100 ;
const timeLeft = Math . round ( ( indexedBlocks . length - totalIndexed ) / blockPerSeconds ) ;
logger . debug ( ` Indexing block summary for # ${ block . height } | ~ ${ blockPerSeconds . toFixed ( 2 ) } blocks/sec | total: ${ totalIndexed } / ${ indexedBlocks . length } ( ${ progress } %) | elapsed: ${ runningFor } seconds | left: ~ ${ timeLeft } seconds ` ) ;
timer = new Date ( ) . getTime ( ) / 1000 ;
indexedThisRun = 0 ;
}
await this . $getStrippedBlockTransactions ( block . hash , true , true ) ; // This will index the block summary
// Logging
indexedThisRun ++ ;
totalIndexed ++ ;
2022-06-20 16:35:10 +02:00
newlyIndexed ++ ;
2022-06-18 16:48:02 +02:00
}
2022-06-20 16:35:10 +02:00
logger . notice ( ` Blocks summaries indexing completed: indexed ${ newlyIndexed } blocks ` ) ;
2022-06-18 16:48:02 +02:00
} catch ( e ) {
2022-06-25 19:50:39 +02:00
logger . err ( ` Blocks summaries indexing failed. Trying again in 10 seconds. Reason: ${ ( e instanceof Error ? e.message : e ) } ` ) ;
throw e ;
2022-06-18 16:48:02 +02:00
}
}
2022-01-05 15:41:14 +09:00
/ * *
2022-03-06 12:47:16 +01:00
* [ INDEXING ] Index all blocks metadata for the mining dashboard
2022-01-05 15:41:14 +09:00
* /
2022-06-20 16:35:10 +02:00
public async $generateBlockDatabase ( ) : Promise < boolean > {
2022-01-24 15:36:30 +09:00
try {
2022-06-14 10:58:17 +02:00
const blockchainInfo = await bitcoinClient . getBlockchainInfo ( ) ;
2022-01-24 19:57:54 +09:00
let currentBlockHeight = blockchainInfo . blocks ;
2022-01-25 18:33:46 +09:00
2022-05-19 16:40:38 +02:00
let indexingBlockAmount = Math . min ( config . MEMPOOL . INDEXING_BLOCKS_AMOUNT , blockchainInfo . blocks ) ;
2022-01-25 18:33:46 +09:00
if ( indexingBlockAmount <= - 1 ) {
indexingBlockAmount = currentBlockHeight + 1 ;
}
const lastBlockToIndex = Math . max ( 0 , currentBlockHeight - indexingBlockAmount + 1 ) ;
2022-01-24 15:36:30 +09:00
2022-04-13 16:29:52 +09:00
logger . debug ( ` Indexing blocks from # ${ currentBlockHeight } to # ${ lastBlockToIndex } ` ) ;
2022-05-02 17:28:58 +09:00
loadingIndicators . setProgress ( 'block-indexing' , 0 ) ;
2022-01-24 15:36:30 +09:00
const chunkSize = 10000 ;
2022-05-10 14:52:37 +02:00
let totalIndexed = await blocksRepository . $blockCountBetweenHeight ( currentBlockHeight , lastBlockToIndex ) ;
2022-02-16 21:20:28 +09:00
let indexedThisRun = 0 ;
2022-04-13 16:29:52 +09:00
let newlyIndexed = 0 ;
2022-03-10 14:21:11 +01:00
const startedAt = new Date ( ) . getTime ( ) / 1000 ;
let timer = new Date ( ) . getTime ( ) / 1000 ;
2022-01-24 17:43:11 +09:00
while ( currentBlockHeight >= lastBlockToIndex ) {
const endBlock = Math . max ( 0 , lastBlockToIndex , currentBlockHeight - chunkSize + 1 ) ;
2022-01-24 15:36:30 +09:00
const missingBlockHeights : number [ ] = await blocksRepository . $getMissingBlocksBetweenHeights (
currentBlockHeight , endBlock ) ;
if ( missingBlockHeights . length <= 0 ) {
currentBlockHeight -= chunkSize ;
continue ;
}
2022-01-18 17:37:04 +09:00
2022-04-13 16:29:52 +09:00
logger . info ( ` Indexing ${ missingBlockHeights . length } blocks from # ${ currentBlockHeight } to # ${ endBlock } ` ) ;
2022-01-24 15:36:30 +09:00
for ( const blockHeight of missingBlockHeights ) {
2022-01-24 17:43:11 +09:00
if ( blockHeight < lastBlockToIndex ) {
break ;
}
2022-03-05 15:50:48 +01:00
++ indexedThisRun ;
2022-05-10 14:52:37 +02:00
++ totalIndexed ;
2022-03-10 14:21:11 +01:00
const elapsedSeconds = Math . max ( 1 , Math . round ( ( new Date ( ) . getTime ( ) / 1000 ) - timer ) ) ;
if ( elapsedSeconds > 5 || blockHeight === lastBlockToIndex ) {
const runningFor = Math . max ( 1 , Math . round ( ( new Date ( ) . getTime ( ) / 1000 ) - startedAt ) ) ;
2022-06-18 16:48:02 +02:00
const blockPerSeconds = Math . max ( 1 , indexedThisRun / elapsedSeconds ) ;
2022-05-10 14:52:37 +02:00
const progress = Math . round ( totalIndexed / indexingBlockAmount * 10000 ) / 100 ;
const timeLeft = Math . round ( ( indexingBlockAmount - totalIndexed ) / blockPerSeconds ) ;
logger . debug ( ` Indexing block # ${ blockHeight } | ~ ${ blockPerSeconds . toFixed ( 2 ) } blocks/sec | total: ${ totalIndexed } / ${ indexingBlockAmount } ( ${ progress } %) | elapsed: ${ runningFor } seconds | left: ~ ${ timeLeft } seconds ` ) ;
2022-03-10 14:21:11 +01:00
timer = new Date ( ) . getTime ( ) / 1000 ;
indexedThisRun = 0 ;
2022-05-10 14:52:37 +02:00
loadingIndicators . setProgress ( 'block-indexing' , progress , false ) ;
2022-03-10 14:23:29 +01:00
}
2022-03-05 15:50:48 +01:00
const blockHash = await bitcoinApi . $getBlockHash ( blockHeight ) ;
2022-03-13 11:38:45 +01:00
const block = BitcoinApi . convertBlock ( await bitcoinClient . getBlock ( blockHash ) ) ;
2022-03-05 15:50:48 +01:00
const transactions = await this . $getTransactionsExtended ( blockHash , block . height , true , true ) ;
const blockExtended = await this . $getBlockExtended ( block , transactions ) ;
2022-04-13 16:29:52 +09:00
newlyIndexed ++ ;
2022-03-05 15:50:48 +01:00
await blocksRepository . $saveBlockInDatabase ( blockExtended ) ;
2022-01-18 17:37:04 +09:00
}
2022-01-24 15:36:30 +09:00
currentBlockHeight -= chunkSize ;
}
2022-06-13 10:12:27 +02:00
logger . notice ( ` Block indexing completed: indexed ${ newlyIndexed } blocks ` ) ;
2022-05-02 17:28:58 +09:00
loadingIndicators . setProgress ( 'block-indexing' , 100 ) ;
2022-01-24 15:36:30 +09:00
} catch ( e ) {
2022-06-25 19:50:39 +02:00
logger . err ( 'Block indexing failed. Trying again in 10 seconds. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
2022-05-02 17:28:58 +09:00
loadingIndicators . setProgress ( 'block-indexing' , 100 ) ;
2022-06-25 19:50:39 +02:00
throw e ;
2022-01-05 15:41:14 +09:00
}
2022-02-21 17:34:07 +09:00
2022-06-25 19:50:39 +02:00
return await BlocksRepository . $validateChain ( ) ;
2022-01-05 15:41:14 +09:00
}
2020-10-18 21:47:47 +07:00
public async $updateBlocks() {
2022-04-18 17:49:22 +09:00
let fastForwarded = false ;
2020-12-21 23:08:34 +07:00
const blockHeightTip = await bitcoinApi . $getBlockHeightTip ( ) ;
2019-07-21 17:59:47 +03:00
2020-10-18 21:47:47 +07:00
if ( this . blocks . length === 0 ) {
2022-01-19 16:58:56 +01:00
this . currentBlockHeight = Math . max ( blockHeightTip - config . MEMPOOL . INITIAL_BLOCKS_AMOUNT , - 1 ) ;
2020-10-18 21:47:47 +07:00
} else {
this . currentBlockHeight = this . blocks [ this . blocks . length - 1 ] . height ;
}
2021-07-31 17:56:10 +03:00
if ( blockHeightTip - this . currentBlockHeight > config . MEMPOOL . INITIAL_BLOCKS_AMOUNT * 2 ) {
logger . info ( ` ${ blockHeightTip - this . currentBlockHeight } blocks since tip. Fast forwarding to the ${ config . MEMPOOL . INITIAL_BLOCKS_AMOUNT } recent blocks ` ) ;
this . currentBlockHeight = blockHeightTip - config . MEMPOOL . INITIAL_BLOCKS_AMOUNT ;
2022-04-18 17:49:22 +09:00
fastForwarded = true ;
2022-05-19 16:40:38 +02:00
logger . info ( ` Re-indexing skipped blocks and corresponding hashrates data ` ) ;
indexer . reindex ( ) ; // Make sure to index the skipped blocks #1619
2020-10-18 21:47:47 +07:00
}
if ( ! this . lastDifficultyAdjustmentTime ) {
2021-09-15 01:47:24 +04:00
const blockchainInfo = await bitcoinClient . getBlockchainInfo ( ) ;
2021-08-01 15:49:26 +03:00
if ( blockchainInfo . blocks === blockchainInfo . headers ) {
const heightDiff = blockHeightTip % 2016 ;
const blockHash = await bitcoinApi . $getBlockHash ( blockHeightTip - heightDiff ) ;
2022-03-13 11:38:45 +01:00
const block = BitcoinApi . convertBlock ( await bitcoinClient . getBlock ( blockHash ) ) ;
2021-08-01 15:49:26 +03:00
this . lastDifficultyAdjustmentTime = block . timestamp ;
this . currentDifficulty = block . difficulty ;
2022-03-09 11:51:36 +01:00
if ( blockHeightTip >= 2016 ) {
2022-01-19 16:58:56 +01:00
const previousPeriodBlockHash = await bitcoinApi . $getBlockHash ( blockHeightTip - heightDiff - 2016 ) ;
2022-07-08 16:34:00 +02:00
const previousPeriodBlock = await bitcoinClient . getBlock ( previousPeriodBlockHash )
2022-01-19 16:58:56 +01:00
this . previousDifficultyRetarget = ( block . difficulty - previousPeriodBlock . difficulty ) / previousPeriodBlock . difficulty * 100 ;
logger . debug ( ` Initial difficulty adjustment data set. ` ) ;
}
2021-08-01 15:49:26 +03:00
} else {
logger . debug ( ` Blockchain headers ( ${ blockchainInfo . headers } ) and blocks ( ${ blockchainInfo . blocks } ) not in sync. Waiting... ` ) ;
}
2020-10-18 21:47:47 +07:00
}
while ( this . currentBlockHeight < blockHeightTip ) {
2022-01-19 16:58:56 +01:00
if ( this . currentBlockHeight < blockHeightTip - config . MEMPOOL . INITIAL_BLOCKS_AMOUNT ) {
2020-10-18 21:47:47 +07:00
this . currentBlockHeight = blockHeightTip ;
2019-07-21 17:59:47 +03:00
} else {
2020-10-18 21:47:47 +07:00
this . currentBlockHeight ++ ;
logger . debug ( ` New block found (# ${ this . currentBlockHeight } )! ` ) ;
2019-07-21 17:59:47 +03:00
}
2020-12-21 23:08:34 +07:00
const blockHash = await bitcoinApi . $getBlockHash ( this . currentBlockHeight ) ;
2022-06-15 19:53:37 +00:00
const verboseBlock = await bitcoinClient . getBlock ( blockHash , 2 ) ;
const block = BitcoinApi . convertBlock ( verboseBlock ) ;
2020-12-21 23:08:34 +07:00
const txIds : string [ ] = await bitcoinApi . $getTxIdsForBlock ( blockHash ) ;
2022-01-05 15:41:14 +09:00
const transactions = await this . $getTransactionsExtended ( blockHash , block . height , false ) ;
2022-02-08 15:47:43 +09:00
const blockExtended : BlockExtended = await this . $getBlockExtended ( block , transactions ) ;
2022-06-15 19:53:37 +00:00
const blockSummary : BlockSummary = this . summarizeBlock ( verboseBlock ) ;
2022-02-08 15:47:43 +09:00
2022-02-15 16:02:30 +09:00
if ( Common . indexingEnabled ( ) ) {
2022-04-18 17:49:22 +09:00
if ( ! fastForwarded ) {
const lastBlock = await blocksRepository . $getBlockByHeight ( blockExtended . height - 1 ) ;
2022-04-19 15:45:06 +09:00
if ( lastBlock !== null && blockExtended . previousblockhash !== lastBlock [ 'hash' ] ) {
2022-04-18 17:49:22 +09:00
logger . warn ( ` Chain divergence detected at block ${ lastBlock [ 'height' ] } , re-indexing most recent data ` ) ;
2022-04-19 15:45:06 +09:00
// We assume there won't be a reorg with more than 10 block depth
await BlocksRepository . $deleteBlocksFrom ( lastBlock [ 'height' ] - 10 ) ;
2022-04-18 17:49:22 +09:00
await HashratesRepository . $deleteLastEntries ( ) ;
2022-06-20 16:35:10 +02:00
await BlocksSummariesRepository . $deleteBlocksFrom ( lastBlock [ 'height' ] - 10 ) ;
2022-04-19 15:45:06 +09:00
for ( let i = 10 ; i >= 0 ; -- i ) {
2022-06-20 16:35:10 +02:00
const newBlock = await this . $indexBlock ( lastBlock [ 'height' ] - i ) ;
await this . $getStrippedBlockTransactions ( newBlock . id , true , true ) ;
2022-04-19 15:45:06 +09:00
}
2022-06-25 12:14:32 +02:00
await mining . $indexDifficultyAdjustments ( ) ;
await DifficultyAdjustmentsRepository . $deleteLastAdjustment ( ) ;
logger . info ( ` Re-indexed 10 blocks and summaries. Also re-indexed the last difficulty adjustments. Will re-index latest hashrates in a few seconds. ` ) ;
indexer . reindex ( ) ;
2022-04-18 17:49:22 +09:00
}
2022-04-19 15:45:06 +09:00
await blocksRepository . $saveBlockInDatabase ( blockExtended ) ;
2022-06-18 16:48:02 +02:00
// Save blocks summary for visualization if it's enabled
if ( Common . blocksSummariesIndexingEnabled ( ) === true ) {
await this . $getStrippedBlockTransactions ( blockExtended . id , true ) ;
}
2022-03-15 13:07:06 +01:00
}
2022-01-21 00:08:51 +09:00
}
2020-09-21 19:41:12 +07:00
2020-10-18 21:47:47 +07:00
if ( block . height % 2016 === 0 ) {
2022-06-25 12:14:32 +02:00
if ( Common . indexingEnabled ( ) ) {
await DifficultyAdjustmentsRepository . $saveAdjustments ( {
time : block.timestamp ,
height : block.height ,
difficulty : block.difficulty ,
adjustment : Math.round ( ( block . difficulty / this . currentDifficulty ) * 1000000 ) / 1000000 , // Remove float point noise
} ) ;
}
2021-07-23 14:35:04 +03:00
this . previousDifficultyRetarget = ( block . difficulty - this . currentDifficulty ) / this . currentDifficulty * 100 ;
2020-10-18 21:47:47 +07:00
this . lastDifficultyAdjustmentTime = block . timestamp ;
2021-07-23 14:35:04 +03:00
this . currentDifficulty = block . difficulty ;
2020-10-18 21:47:47 +07:00
}
2019-07-21 17:59:47 +03:00
2020-12-28 04:47:22 +07:00
this . blocks . push ( blockExtended ) ;
2021-07-31 17:56:10 +03:00
if ( this . blocks . length > config . MEMPOOL . INITIAL_BLOCKS_AMOUNT * 4 ) {
this . blocks = this . blocks . slice ( - config . MEMPOOL . INITIAL_BLOCKS_AMOUNT * 4 ) ;
2019-07-21 17:59:47 +03:00
}
2022-06-15 19:53:37 +00:00
this . blockSummaries . push ( blockSummary ) ;
if ( this . blockSummaries . length > config . MEMPOOL . INITIAL_BLOCKS_AMOUNT * 4 ) {
this . blockSummaries = this . blockSummaries . slice ( - config . MEMPOOL . INITIAL_BLOCKS_AMOUNT * 4 ) ;
}
2019-07-21 17:59:47 +03:00
2020-10-18 21:47:47 +07:00
if ( this . newBlockCallbacks . length ) {
2020-12-28 04:47:22 +07:00
this . newBlockCallbacks . forEach ( ( cb ) = > cb ( blockExtended , txIds , transactions ) ) ;
2020-10-18 21:47:47 +07:00
}
2022-02-11 21:25:58 +09:00
if ( ! memPool . hasPriority ( ) ) {
2021-01-22 23:20:39 +07:00
diskCache . $saveCacheToDisk ( ) ;
}
2019-08-29 01:17:31 +02:00
}
}
2020-05-24 22:45:45 +07:00
2022-02-08 15:47:43 +09:00
/ * *
* Index a block if it ' s missing from the database . Returns the block after indexing
* /
2022-03-13 11:38:45 +01:00
public async $indexBlock ( height : number ) : Promise < BlockExtended > {
2022-02-08 15:47:43 +09:00
const dbBlock = await blocksRepository . $getBlockByHeight ( height ) ;
if ( dbBlock != null ) {
2022-03-16 17:00:06 +01:00
return prepareBlock ( dbBlock ) ;
2022-02-08 15:47:43 +09:00
}
const blockHash = await bitcoinApi . $getBlockHash ( height ) ;
2022-03-13 11:38:45 +01:00
const block = BitcoinApi . convertBlock ( await bitcoinClient . getBlock ( blockHash ) ) ;
2022-02-08 15:47:43 +09:00
const transactions = await this . $getTransactionsExtended ( blockHash , block . height , true ) ;
const blockExtended = await this . $getBlockExtended ( block , transactions ) ;
await blocksRepository . $saveBlockInDatabase ( blockExtended ) ;
2022-03-16 17:00:06 +01:00
return prepareBlock ( blockExtended ) ;
2022-02-08 15:47:43 +09:00
}
2022-04-20 13:12:32 +09:00
/ * *
* Index a block by hash if it ' s missing from the database . Returns the block after indexing
* /
public async $getBlock ( hash : string ) : Promise < BlockExtended | IEsploraApi.Block > {
2022-04-20 13:40:10 +09:00
// Check the memory cache
const blockByHash = this . getBlocks ( ) . find ( ( b ) = > b . id === hash ) ;
if ( blockByHash ) {
return blockByHash ;
}
2022-04-20 13:12:32 +09:00
// Block has already been indexed
if ( Common . indexingEnabled ( ) ) {
const dbBlock = await blocksRepository . $getBlockByHash ( hash ) ;
if ( dbBlock != null ) {
return prepareBlock ( dbBlock ) ;
}
}
// Not Bitcoin network, return the block as it
if ( [ 'mainnet' , 'testnet' , 'signet' ] . includes ( config . MEMPOOL . NETWORK ) === false ) {
2022-07-11 08:41:28 +02:00
return await bitcoinApi . $getBlock ( hash ) ;
2022-04-20 13:12:32 +09:00
}
2022-07-11 08:41:28 +02:00
let block = await bitcoinClient . getBlock ( hash ) ;
2022-07-08 16:34:00 +02:00
block = prepareBlock ( block ) ;
2022-04-20 13:12:32 +09:00
// Bitcoin network, add our custom data on top
const transactions = await this . $getTransactionsExtended ( hash , block . height , true ) ;
const blockExtended = await this . $getBlockExtended ( block , transactions ) ;
if ( Common . indexingEnabled ( ) ) {
delete ( blockExtended [ 'coinbaseTx' ] ) ;
await blocksRepository . $saveBlockInDatabase ( blockExtended ) ;
}
return blockExtended ;
}
2022-07-11 08:41:28 +02:00
public async $getStrippedBlockTransactions ( hash : string , skipMemoryCache = false ,
skipDBLookup = false ) : Promise < TransactionStripped [ ] >
2022-06-20 16:35:10 +02:00
{
2022-06-18 16:48:02 +02:00
if ( skipMemoryCache === false ) {
// Check the memory cache
const cachedSummary = this . getBlockSummaries ( ) . find ( ( b ) = > b . id === hash ) ;
if ( cachedSummary ) {
return cachedSummary . transactions ;
}
}
// Check if it's indexed in db
if ( skipDBLookup === false && Common . blocksSummariesIndexingEnabled ( ) === true ) {
const indexedSummary = await BlocksSummariesRepository . $getByBlockId ( hash ) ;
if ( indexedSummary !== undefined ) {
return indexedSummary . transactions ;
}
2022-06-15 19:53:37 +00:00
}
2022-06-18 16:48:02 +02:00
// Call Core RPC
2022-06-15 19:53:37 +00:00
const block = await bitcoinClient . getBlock ( hash , 2 ) ;
const summary = this . summarizeBlock ( block ) ;
2022-06-18 16:48:02 +02:00
// Index the response if needed
if ( Common . blocksSummariesIndexingEnabled ( ) === true ) {
await BlocksSummariesRepository . $saveSummary ( block . height , summary ) ;
}
2022-06-15 19:53:37 +00:00
return summary . transactions ;
}
2022-05-20 18:11:02 +02:00
public async $getBlocks ( fromHeight? : number , limit : number = 15 ) : Promise < BlockExtended [ ] > {
2022-07-09 16:02:43 +02:00
let currentHeight = fromHeight !== undefined ? fromHeight : await blocksRepository . $mostRecentBlockHeight ( ) ;
2022-07-08 16:34:00 +02:00
const returnBlocks : BlockExtended [ ] = [ ] ;
2022-02-08 15:47:43 +09:00
2022-07-08 16:34:00 +02:00
if ( currentHeight < 0 ) {
return returnBlocks ;
}
2022-02-08 15:47:43 +09:00
2022-07-08 16:34:00 +02:00
// Check if block height exist in local cache to skip the hash lookup
const blockByHeight = this . getBlocks ( ) . find ( ( b ) = > b . height === currentHeight ) ;
let startFromHash : string | null = null ;
if ( blockByHeight ) {
startFromHash = blockByHeight . id ;
} else if ( ! Common . indexingEnabled ( ) ) {
startFromHash = await bitcoinApi . $getBlockHash ( currentHeight ) ;
}
2022-02-08 15:47:43 +09:00
2022-07-08 16:34:00 +02:00
let nextHash = startFromHash ;
for ( let i = 0 ; i < limit && currentHeight >= 0 ; i ++ ) {
let block = this . getBlocks ( ) . find ( ( b ) = > b . height === currentHeight ) ;
if ( block ) {
returnBlocks . push ( block ) ;
} else if ( Common . indexingEnabled ( ) ) {
block = await this . $indexBlock ( currentHeight ) ;
returnBlocks . push ( block ) ;
} else if ( nextHash != null ) {
block = prepareBlock ( await bitcoinClient . getBlock ( nextHash ) ) ;
nextHash = block . previousblockhash ;
returnBlocks . push ( block ) ;
2022-02-08 15:47:43 +09:00
}
2022-07-08 16:34:00 +02:00
currentHeight -- ;
2022-02-08 15:47:43 +09:00
}
2022-07-08 16:34:00 +02:00
return returnBlocks ;
2022-02-08 15:47:43 +09:00
}
2020-09-21 19:41:12 +07:00
public getLastDifficultyAdjustmentTime ( ) : number {
return this . lastDifficultyAdjustmentTime ;
}
2021-07-23 14:35:04 +03:00
public getPreviousDifficultyRetarget ( ) : number {
return this . previousDifficultyRetarget ;
}
2020-12-21 23:08:34 +07:00
public getCurrentBlockHeight ( ) : number {
return this . currentBlockHeight ;
}
2019-07-21 17:59:47 +03:00
}
export default new Blocks ( ) ;