2020-12-20 22:36:36 +07:00
import config from '../config' ;
2023-03-03 13:59:17 +09:00
import bitcoinApi , { bitcoinCoreApi } 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' ;
2023-05-29 15:56:29 -04:00
import { BlockExtended , BlockExtension , BlockSummary , PoolTag , TransactionExtended , TransactionStripped , TransactionMinerInfo , CpfpSummary , MempoolTransactionExtended } 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-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-10-28 10:31:55 -06:00
import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository' ;
2022-11-27 13:46:23 +09:00
import cpfpRepository from '../repositories/CpfpRepository' ;
2022-07-11 19:15:28 +02:00
import mining from './mining/mining' ;
2022-06-25 12:14:32 +02:00
import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository' ;
2022-07-16 09:22:45 +02:00
import PricesRepository from '../repositories/PricesRepository' ;
import priceUpdater from '../tasks/price-updater' ;
2023-02-18 14:10:07 +09:00
import chainTips from './chain-tips' ;
2023-07-08 01:07:06 -04:00
import websocketHandler from './websocket-handler' ;
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 ) [ ] = [ ] ;
2023-06-25 20:37:42 -04:00
private newAsyncBlockCallbacks : ( ( block : BlockExtended , txIds : string [ ] , transactions : MempoolTransactionExtended [ ] ) = > Promise < void > ) [ ] = [ ] ;
2019-08-29 01:17:31 +02:00
2023-04-27 10:19:12 +09:00
private mainLoopTimeout : number = 120000 ;
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
}
2023-06-25 20:37:42 -04:00
public setNewAsyncBlockCallback ( fn : ( block : BlockExtended , txIds : string [ ] , transactions : MempoolTransactionExtended [ ] ) = > Promise < void > ) {
2022-11-16 18:18:59 -06:00
this . newAsyncBlockCallbacks . push ( fn ) ;
}
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 ,
2023-07-16 17:12:26 +09:00
txIds : string [ ] | null = null ,
2022-02-10 23:02:12 +09:00
quiet : boolean = false ,
2023-05-29 15:56:29 -04:00
addMempoolData : boolean = false ,
2022-02-10 23:02:12 +09:00
) : Promise < TransactionExtended [ ] > {
2022-01-05 15:41:14 +09:00
const transactions : TransactionExtended [ ] = [ ] ;
2023-07-16 17:12:26 +09:00
if ( ! txIds ) {
txIds = await bitcoinApi . $getTxIdsForBlock ( blockHash ) ;
}
2022-01-05 15:41:14 +09:00
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 {
2023-05-29 15:56:29 -04:00
const tx = await transactionUtils . $getTransactionExtended ( txIds [ i ] , false , false , false , addMempoolData ) ;
2022-01-05 15:41:14 +09:00
transactions . push ( tx ) ;
transactionsFetched ++ ;
} catch ( e ) {
2023-01-07 11:17:30 +01:00
try {
if ( config . MEMPOOL . BACKEND === 'esplora' ) {
// Try again with core
2023-05-29 15:56:29 -04:00
const tx = await transactionUtils . $getTransactionExtended ( txIds [ i ] , false , false , true , addMempoolData ) ;
2023-01-07 11:17:30 +01:00
transactions . push ( tx ) ;
transactionsFetched ++ ;
} else {
throw e ;
}
} catch ( e ) {
if ( i === 0 ) {
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
}
}
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
* /
2023-02-27 18:00:00 +09:00
public summarizeBlock ( block : IBitcoinApi.VerboseBlock ) : BlockSummary {
2023-02-21 22:23:36 -06:00
if ( Common . isLiquid ( ) ) {
block = this . convertLiquidFees ( block ) ;
}
const stripped = block . tx . map ( ( tx : IBitcoinApi.VerboseTransaction ) = > {
2022-06-15 19:53:37 +00:00
return {
txid : tx.txid ,
2022-11-04 10:37:14 -06:00
vsize : tx.weight / 4 ,
2022-06-15 19:53:37 +00:00
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
} ;
}
2023-06-29 19:24:19 -04:00
public summarizeBlockTransactions ( hash : string , transactions : TransactionExtended [ ] ) : BlockSummary {
return {
id : hash ,
transactions : Common.stripTransactions ( transactions ) ,
} ;
}
2023-02-21 22:23:36 -06:00
private convertLiquidFees ( block : IBitcoinApi.VerboseBlock ) : IBitcoinApi . VerboseBlock {
block . tx . forEach ( tx = > {
tx . fee = Object . values ( tx . fee || { } ) . reduce ( ( total , output ) = > total + output , 0 ) ;
} ) ;
return block ;
}
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 > {
2023-02-27 18:00:00 +09:00
const coinbaseTx = transactionUtils . stripCoinbaseTransaction ( transactions [ 0 ] ) ;
const blk : Partial < BlockExtended > = Object . assign ( { } , block ) ;
const extras : Partial < BlockExtension > = { } ;
extras . reward = transactions [ 0 ] . vout . reduce ( ( acc , curr ) = > acc + curr . value , 0 ) ;
extras . coinbaseRaw = coinbaseTx . vin [ 0 ] . scriptsig ;
extras . orphans = chainTips . getOrphanedBlocksAtHeight ( blk . height ) ;
2022-03-11 14:54:34 +01:00
if ( block . height === 0 ) {
2023-02-27 18:00:00 +09:00
extras . medianFee = 0 ; // 50th percentiles
extras . feeRange = [ 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ;
extras . totalFees = 0 ;
extras . avgFee = 0 ;
extras . avgFeeRate = 0 ;
extras . utxoSetChange = 0 ;
extras . avgTxSize = 0 ;
extras . totalInputs = 0 ;
extras . totalOutputs = 1 ;
extras . totalOutputAmt = 0 ;
extras . segwitTotalTxs = 0 ;
extras . segwitTotalSize = 0 ;
extras . segwitTotalWeight = 0 ;
2022-03-13 11:38:45 +01:00
} else {
2023-02-27 18:00:00 +09:00
const stats : IBitcoinApi.BlockStats = await bitcoinClient . getBlockStats ( block . id ) ;
2023-03-12 11:09:11 +09:00
let feeStats = {
medianFee : stats.feerate_percentiles [ 2 ] , // 50th percentiles
feeRange : [ stats . minfeerate , stats . feerate_percentiles , stats . maxfeerate ] . flat ( ) ,
} ;
if ( transactions ? . length > 1 ) {
2023-03-12 14:36:36 +09:00
feeStats = Common . calcEffectiveFeeStatistics ( transactions ) ;
2023-03-12 11:09:11 +09:00
}
extras . medianFee = feeStats . medianFee ;
extras . feeRange = feeStats . feeRange ;
2023-02-27 18:00:00 +09:00
extras . totalFees = stats . totalfee ;
extras . avgFee = stats . avgfee ;
extras . avgFeeRate = stats . avgfeerate ;
extras . utxoSetChange = stats . utxo_increase ;
extras . avgTxSize = Math . round ( stats . total_size / stats . txs * 100 ) * 0.01 ;
extras . totalInputs = stats . ins ;
extras . totalOutputs = stats . outs ;
extras . totalOutputAmt = stats . total_out ;
extras . segwitTotalTxs = stats . swtxs ;
extras . segwitTotalSize = stats . swtotal_size ;
extras . segwitTotalWeight = stats . swtotal_weight ;
2023-02-17 21:21:21 +09:00
}
2023-02-24 11:33:36 +09:00
if ( Common . blocksSummariesIndexingEnabled ( ) ) {
2023-02-27 18:00:00 +09:00
extras . feePercentiles = await BlocksSummariesRepository . $getFeePercentilesByBlockId ( block . id ) ;
if ( extras . feePercentiles !== null ) {
extras . medianFeeAmt = extras . feePercentiles [ 3 ] ;
2023-02-19 19:17:51 +09:00
}
2023-02-18 11:26:13 +09:00
}
2023-02-17 21:21:21 +09:00
2023-02-27 18:00:00 +09:00
extras . virtualSize = block . weight / 4.0 ;
if ( coinbaseTx ? . vout . length > 0 ) {
extras . coinbaseAddress = coinbaseTx . vout [ 0 ] . scriptpubkey_address ? ? null ;
extras . coinbaseSignature = coinbaseTx . vout [ 0 ] . scriptpubkey_asm ? ? null ;
extras . coinbaseSignatureAscii = transactionUtils . hex2ascii ( coinbaseTx . vin [ 0 ] . scriptsig ) ? ? null ;
2023-02-17 21:21:21 +09:00
} else {
2023-02-27 18:00:00 +09:00
extras . coinbaseAddress = null ;
extras . coinbaseSignature = null ;
extras . coinbaseSignatureAscii = null ;
2023-02-17 21:21:21 +09:00
}
const header = await bitcoinClient . getBlockHeader ( block . id , false ) ;
2023-02-27 18:00:00 +09:00
extras . header = header ;
2023-02-17 21:21:21 +09:00
const coinStatsIndex = indexer . isCoreIndexReady ( 'coinstatsindex' ) ;
if ( coinStatsIndex !== null && coinStatsIndex . best_block_height >= block . height ) {
const txoutset = await bitcoinClient . getTxoutSetinfo ( 'none' , block . height ) ;
2023-02-27 18:00:00 +09:00
extras . utxoSetSize = txoutset . txouts ,
extras . totalInputAmt = Math . round ( txoutset . block_info . prevout_spent * 100000000 ) ;
2023-02-17 21:21:21 +09:00
} else {
2023-02-27 18:00:00 +09:00
extras . utxoSetSize = null ;
extras . totalInputAmt = null ;
2022-03-13 11:38:45 +01:00
}
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 ;
2023-02-27 18:00:00 +09:00
if ( coinbaseTx !== undefined ) {
pool = await this . $findBlockMiner ( coinbaseTx ) ;
2022-04-25 15:50:26 +09:00
} 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
2023-02-17 21:21:21 +09:00
logger . warn ( ` Cannot assign pool to block ${ blk . height } and 'unknown' pool does not exist. ` +
2022-04-25 15:50:26 +09:00
` Check your "pools" table entries ` ) ;
2022-10-28 10:31:55 -06:00
} else {
2023-02-27 18:00:00 +09:00
extras . pool = {
id : pool.uniqueId ,
2022-10-28 10:31:55 -06:00
name : pool.name ,
slug : pool.slug ,
} ;
2022-04-25 15:50:26 +09:00
}
2022-02-08 15:47:43 +09:00
2023-02-27 18:00:00 +09:00
extras . matchRate = null ;
2023-06-06 08:52:29 +02:00
extras . expectedFees = null ;
2023-06-10 12:09:06 -04:00
extras . expectedWeight = null ;
2023-02-12 21:43:12 -06:00
if ( config . MEMPOOL . AUDIT ) {
const auditScore = await BlocksAuditsRepository . $getBlockAuditScore ( block . id ) ;
if ( auditScore != null ) {
2023-02-27 18:00:00 +09:00
extras . matchRate = auditScore . matchRate ;
2023-06-06 08:52:29 +02:00
extras . expectedFees = auditScore . expectedFees ;
2023-06-10 12:09:06 -04:00
extras . expectedWeight = auditScore . expectedWeight ;
2023-02-12 21:43:12 -06:00
}
2022-10-28 10:31:55 -06:00
}
2022-04-25 15:50:26 +09:00
}
2022-04-20 13:12:32 +09:00
2023-02-27 18:00:00 +09:00
blk . extras = < BlockExtension > extras ;
return < BlockExtended > blk ;
2022-01-05 15:41:14 +09:00
}
/ * *
* 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 ) ;
2023-05-24 01:22:01 +04:00
const addresses = txMinerInfo . vout . map ( ( vout ) = > vout . scriptpubkey_address ) . filter ( ( address ) = > address ) ;
2022-01-05 15:41:14 +09:00
2022-06-07 11:28:39 +02:00
let pools : PoolTag [ ] = [ ] ;
if ( config . DATABASE . ENABLED === true ) {
pools = await poolsRepository . $getPools ( ) ;
} else {
pools = poolsParser . miningPools ;
}
2023-02-27 18:00:00 +09:00
2022-01-05 15:41:14 +09:00
for ( let i = 0 ; i < pools . length ; ++ i ) {
2023-05-24 01:22:01 +04:00
if ( addresses . length ) {
const poolAddresses : string [ ] = typeof pools [ i ] . addresses === 'string' ?
2023-02-27 18:00:00 +09:00
JSON . parse ( pools [ i ] . addresses ) : pools [ i ] . addresses ;
2023-05-24 01:22:01 +04:00
for ( let y = 0 ; y < poolAddresses . length ; y ++ ) {
if ( addresses . indexOf ( poolAddresses [ y ] ) !== - 1 ) {
return pools [ i ] ;
}
2022-01-05 15:41:14 +09:00
}
}
2023-02-27 18:00:00 +09:00
const regexes : string [ ] = typeof pools [ i ] . regexes === 'string' ?
JSON . parse ( pools [ i ] . regexes ) : 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
* /
2022-11-27 13:46:23 +09:00
public async $generateBlocksSummariesDatabase ( ) : Promise < void > {
2022-06-18 16:48:02 +02:00
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 ;
2022-12-01 15:52:06 +01:00
logger . debug ( ` Indexing block summary for # ${ block . height } | ~ ${ blockPerSeconds . toFixed ( 2 ) } blocks/sec | total: ${ totalIndexed } / ${ indexedBlocks . length } ( ${ progress } %) | elapsed: ${ runningFor } seconds ` , logger . tags . mining ) ;
2022-06-18 16:48:02 +02:00
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-07-11 10:55:25 +02:00
if ( newlyIndexed > 0 ) {
2022-12-01 15:52:06 +01:00
logger . notice ( ` Blocks summaries indexing completed: indexed ${ newlyIndexed } blocks ` , logger . tags . mining ) ;
2022-07-11 10:55:25 +02:00
} else {
2022-12-01 15:52:06 +01:00
logger . debug ( ` Blocks summaries indexing completed: indexed ${ newlyIndexed } blocks ` , logger . tags . mining ) ;
2022-07-11 10:55:25 +02:00
}
2022-06-18 16:48:02 +02:00
} catch ( e ) {
2022-12-01 15:52:06 +01:00
logger . err ( ` Blocks summaries indexing failed. Trying again in 10 seconds. Reason: ${ ( e instanceof Error ? e.message : e ) } ` , logger . tags . mining ) ;
2022-06-25 19:50:39 +02:00
throw e ;
2022-06-18 16:48:02 +02:00
}
}
2022-11-27 13:46:23 +09:00
/ * *
* [ INDEXING ] Index transaction CPFP data for all blocks
* /
public async $generateCPFPDatabase ( ) : Promise < void > {
if ( Common . cpfpIndexingEnabled ( ) === false ) {
return ;
}
try {
// Get all indexed block hash
2023-01-10 17:15:55 -06:00
const unindexedBlockHeights = await blocksRepository . $getCPFPUnindexedBlocks ( ) ;
2022-11-27 13:46:23 +09:00
2023-01-10 17:15:55 -06:00
if ( ! unindexedBlockHeights ? . length ) {
2022-11-27 13:46:23 +09:00
return ;
}
2023-03-14 13:53:52 +09:00
logger . info ( ` Indexing cpfp data for ${ unindexedBlockHeights . length } blocks ` ) ;
2022-11-27 13:46:23 +09:00
// Logging
let count = 0 ;
let countThisRun = 0 ;
let timer = new Date ( ) . getTime ( ) / 1000 ;
const startedAt = new Date ( ) . getTime ( ) / 1000 ;
2023-01-10 17:15:55 -06:00
for ( const height of unindexedBlockHeights ) {
2022-11-27 13:46:23 +09:00
// Logging
2023-01-10 17:15:55 -06:00
const hash = await bitcoinApi . $getBlockHash ( height ) ;
2022-11-28 17:56:50 +09:00
const elapsedSeconds = Math . max ( 1 , new Date ( ) . getTime ( ) / 1000 - timer ) ;
2022-11-27 13:46:23 +09:00
if ( elapsedSeconds > 5 ) {
const runningFor = Math . max ( 1 , Math . round ( ( new Date ( ) . getTime ( ) / 1000 ) - startedAt ) ) ;
2023-01-09 08:34:33 -06:00
const blockPerSeconds = ( countThisRun / elapsedSeconds ) ;
2023-01-10 17:15:55 -06:00
const progress = Math . round ( count / unindexedBlockHeights . length * 10000 ) / 100 ;
logger . debug ( ` Indexing cpfp clusters for # ${ height } | ~ ${ blockPerSeconds . toFixed ( 2 ) } blocks/sec | total: ${ count } / ${ unindexedBlockHeights . length } ( ${ progress } %) | elapsed: ${ runningFor } seconds ` ) ;
2022-11-27 13:46:23 +09:00
timer = new Date ( ) . getTime ( ) / 1000 ;
countThisRun = 0 ;
}
2023-01-10 17:15:55 -06:00
await this . $indexCPFP ( hash , height ) ; // Calculate and save CPFP data for transactions in this block
2022-11-27 13:46:23 +09:00
// Logging
count ++ ;
2022-11-28 17:56:50 +09:00
countThisRun ++ ;
2022-11-27 13:46:23 +09:00
}
2023-01-10 17:15:55 -06:00
logger . notice ( ` CPFP indexing completed: indexed ${ count } blocks ` ) ;
2022-11-27 13:46:23 +09:00
} catch ( e ) {
logger . err ( ` CPFP indexing failed. Trying again in 10 seconds. Reason: ${ ( e instanceof Error ? e.message : e ) } ` ) ;
throw e ;
}
}
2023-06-09 13:46:43 -04:00
/ * *
* [ INDEXING ] Index expected fees & weight for all audited blocks
* /
public async $generateAuditStats ( ) : Promise < void > {
const blockIds = await BlocksAuditsRepository . $getBlocksWithoutSummaries ( ) ;
if ( ! blockIds ? . length ) {
return ;
}
let timer = Date . now ( ) ;
let indexedThisRun = 0 ;
let indexedTotal = 0 ;
logger . debug ( ` Indexing ${ blockIds . length } block audit details ` ) ;
for ( const hash of blockIds ) {
const summary = await BlocksSummariesRepository . $getTemplate ( hash ) ;
let totalFees = 0 ;
let totalWeight = 0 ;
for ( const tx of summary ? . transactions || [ ] ) {
totalFees += tx . fee ;
totalWeight += ( tx . vsize * 4 ) ;
}
await BlocksAuditsRepository . $setSummary ( hash , totalFees , totalWeight ) ;
2023-06-14 16:15:33 -04:00
const cachedBlock = this . blocks . find ( block = > block . id === hash ) ;
if ( cachedBlock ) {
cachedBlock . extras . expectedFees = totalFees ;
cachedBlock . extras . expectedWeight = totalWeight ;
}
2023-06-09 13:46:43 -04:00
indexedThisRun ++ ;
indexedTotal ++ ;
const elapsedSeconds = ( Date . now ( ) - timer ) / 1000 ;
if ( elapsedSeconds > 5 ) {
const blockPerSeconds = indexedThisRun / elapsedSeconds ;
logger . debug ( ` Indexed ${ indexedTotal } / ${ blockIds . length } block audit details ( ${ blockPerSeconds . toFixed ( 1 ) } /s) ` ) ;
timer = Date . now ( ) ;
indexedThisRun = 0 ;
}
}
logger . debug ( ` Indexing block audit details completed ` ) ;
}
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-12-01 15:52:06 +01:00
logger . debug ( ` Indexing blocks from # ${ currentBlockHeight } to # ${ lastBlockToIndex } ` , logger . tags . mining ) ;
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-12-01 15:52:06 +01:00
logger . info ( ` Indexing ${ missingBlockHeights . length } blocks from # ${ currentBlockHeight } to # ${ endBlock } ` , logger . tags . mining ) ;
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-11-28 17:56:50 +09:00
const elapsedSeconds = Math . max ( 1 , new Date ( ) . getTime ( ) / 1000 - timer ) ;
2022-03-10 14:21:11 +01:00
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 ;
2022-12-01 15:52:06 +01:00
logger . debug ( ` Indexing block # ${ blockHeight } | ~ ${ blockPerSeconds . toFixed ( 2 ) } blocks/sec | total: ${ totalIndexed } / ${ indexingBlockAmount } ( ${ progress } %) | elapsed: ${ runningFor } seconds ` , logger . tags . mining ) ;
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 ) ;
2023-03-03 13:59:17 +09:00
const block : IEsploraApi.Block = await bitcoinCoreApi . $getBlock ( blockHash ) ;
2023-07-16 17:12:26 +09:00
const transactions = await this . $getTransactionsExtended ( blockHash , block . height , true , null , true ) ;
2022-03-05 15:50:48 +01:00
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-07-11 10:55:25 +02:00
if ( newlyIndexed > 0 ) {
2022-12-01 15:52:06 +01:00
logger . notice ( ` Block indexing completed: indexed ${ newlyIndexed } blocks ` , logger . tags . mining ) ;
2022-07-11 10:55:25 +02:00
} else {
2022-12-01 15:52:06 +01:00
logger . debug ( ` Block indexing completed: indexed ${ newlyIndexed } blocks ` , logger . tags . mining ) ;
2022-07-11 10:55:25 +02:00
}
2022-05-02 17:28:58 +09:00
loadingIndicators . setProgress ( 'block-indexing' , 100 ) ;
2022-01-24 15:36:30 +09:00
} catch ( e ) {
2022-12-01 15:52:06 +01:00
logger . err ( 'Block indexing failed. Trying again in 10 seconds. Reason: ' + ( e instanceof Error ? e.message : e ) , logger . tags . mining ) ;
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
}
2023-04-07 09:41:25 +09:00
public async $updateBlocks ( ) : Promise < number > {
2023-04-27 10:19:12 +09:00
// warn if this run stalls the main loop for more than 2 minutes
const timer = this . startTimer ( ) ;
2023-05-01 14:30:30 -06:00
diskCache . lock ( ) ;
2022-04-18 17:49:22 +09:00
let fastForwarded = false ;
2023-04-07 09:41:25 +09:00
let handledBlocks = 0 ;
2023-07-16 17:12:26 +09:00
const blockHeightTip = await bitcoinCoreApi . $getBlockHeightTip ( ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , 'got block height tip' ) ;
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 ( ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , 'got blockchain info for initial difficulty adjustment' ) ;
2021-08-01 15:49:26 +03:00
if ( blockchainInfo . blocks === blockchainInfo . headers ) {
const heightDiff = blockHeightTip % 2016 ;
const blockHash = await bitcoinApi . $getBlockHash ( blockHeightTip - heightDiff ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , 'got block hash for initial difficulty adjustment' ) ;
2023-03-03 13:59:17 +09:00
const block : IEsploraApi.Block = await bitcoinCoreApi . $getBlock ( blockHash ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , 'got block for initial difficulty adjustment' ) ;
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 ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , 'got previous block hash for initial difficulty adjustment' ) ;
2023-03-03 13:59:17 +09:00
const previousPeriodBlock : IEsploraApi.Block = await bitcoinCoreApi . $getBlock ( previousPeriodBlockHash ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , 'got previous block for initial difficulty adjustment' ) ;
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 ) {
2023-03-26 17:54:24 +09:00
if ( this . currentBlockHeight === 0 ) {
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 } )! ` ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , ` getting orphaned blocks for ${ this . currentBlockHeight } ` ) ;
2023-02-18 14:10:07 +09:00
await chainTips . updateOrphanedBlocks ( ) ;
2019-07-21 17:59:47 +03:00
}
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , ` getting block data for ${ this . currentBlockHeight } ` ) ;
2023-07-16 17:12:26 +09:00
const blockHash = await bitcoinCoreApi . $getBlockHash ( this . currentBlockHeight ) ;
2022-06-15 19:53:37 +00:00
const verboseBlock = await bitcoinClient . getBlock ( blockHash , 2 ) ;
const block = BitcoinApi . convertBlock ( verboseBlock ) ;
2023-07-16 17:12:26 +09:00
const txIds : string [ ] = verboseBlock . tx . map ( tx = > tx . txid ) ;
const transactions = await this . $getTransactionsExtended ( blockHash , block . height , false , txIds , false , true ) as MempoolTransactionExtended [ ] ;
2023-05-26 19:12:12 -04:00
if ( config . MEMPOOL . BACKEND !== 'esplora' ) {
// fill in missing transaction fee data from verboseBlock
for ( let i = 0 ; i < transactions . length ; i ++ ) {
if ( ! transactions [ i ] . fee && transactions [ i ] . txid === verboseBlock . tx [ i ] . txid ) {
transactions [ i ] . fee = verboseBlock . tx [ i ] . fee * 100 _000_000 ;
}
}
}
2023-03-12 14:36:36 +09:00
const cpfpSummary : CpfpSummary = Common . calculateCpfp ( block . height , transactions ) ;
2023-03-12 11:09:11 +09:00
const blockExtended : BlockExtended = await this . $getBlockExtended ( block , cpfpSummary . transactions ) ;
2023-06-29 19:24:19 -04:00
const blockSummary : BlockSummary = this . summarizeBlockTransactions ( block . id , cpfpSummary . transactions ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , ` got block data for ${ this . currentBlockHeight } ` ) ;
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 ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , ` got block by height for ${ this . currentBlockHeight } ` ) ;
2023-03-01 13:50:15 +09:00
if ( lastBlock !== null && blockExtended . previousblockhash !== lastBlock . id ) {
2023-03-19 10:46:38 +09:00
logger . warn ( ` Chain divergence detected at block ${ lastBlock . height } , re-indexing most recent data ` , logger . tags . mining ) ;
2022-04-19 15:45:06 +09:00
// We assume there won't be a reorg with more than 10 block depth
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , ` rolling back diverged chain from ${ this . currentBlockHeight } ` ) ;
2023-03-01 13:50:15 +09:00
await BlocksRepository . $deleteBlocksFrom ( lastBlock . height - 10 ) ;
2022-04-18 17:49:22 +09:00
await HashratesRepository . $deleteLastEntries ( ) ;
2023-03-01 13:50:15 +09:00
await cpfpRepository . $deleteClustersFrom ( lastBlock . height - 10 ) ;
2023-07-08 00:33:14 -04:00
this . blocks = this . blocks . slice ( 0 , - 10 ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , ` rolled back chain divergence from ${ this . currentBlockHeight } ` ) ;
2022-04-19 15:45:06 +09:00
for ( let i = 10 ; i >= 0 ; -- i ) {
2023-03-01 13:50:15 +09:00
const newBlock = await this . $indexBlock ( lastBlock . height - i ) ;
2023-07-08 00:33:14 -04:00
this . blocks . push ( newBlock ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , ` reindexed block ` ) ;
2023-06-29 19:24:19 -04:00
let cpfpSummary ;
2022-12-28 05:28:37 -06:00
if ( config . MEMPOOL . CPFP_INDEXING ) {
2023-06-29 19:24:19 -04:00
cpfpSummary = await this . $indexCPFP ( newBlock . id , lastBlock . height - i ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , ` reindexed block cpfp ` ) ;
2022-11-27 13:46:23 +09:00
}
2023-06-29 19:24:19 -04:00
await this . $getStrippedBlockTransactions ( newBlock . id , true , true , cpfpSummary , newBlock . height ) ;
this . updateTimerProgress ( timer , ` reindexed block summary ` ) ;
2022-04-19 15:45:06 +09:00
}
2022-06-25 12:14:32 +02:00
await mining . $indexDifficultyAdjustments ( ) ;
await DifficultyAdjustmentsRepository . $deleteLastAdjustment ( ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , ` reindexed difficulty adjustments ` ) ;
2023-03-19 10:46:38 +09:00
logger . info ( ` Re-indexed 10 blocks and summaries. Also re-indexed the last difficulty adjustments. Will re-index latest hashrates in a few seconds. ` , logger . tags . mining ) ;
2022-06-25 12:14:32 +02:00
indexer . reindex ( ) ;
2023-07-08 01:07:06 -04:00
websocketHandler . handleReorg ( ) ;
2022-04-18 17:49:22 +09:00
}
2023-06-07 11:59:31 -04:00
}
2022-06-18 16:48:02 +02:00
2023-06-07 11:59:31 -04:00
await blocksRepository . $saveBlockInDatabase ( blockExtended ) ;
this . updateTimerProgress ( timer , ` saved ${ this . currentBlockHeight } to database ` ) ;
if ( ! fastForwarded ) {
2022-07-16 09:22:45 +02:00
const lastestPriceId = await PricesRepository . $getLatestPriceId ( ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , ` got latest price id ${ this . currentBlockHeight } ` ) ;
2022-07-16 09:22:45 +02:00
if ( priceUpdater . historyInserted === true && lastestPriceId !== null ) {
await blocksRepository . $saveBlockPrices ( [ {
height : blockExtended.height ,
priceId : lastestPriceId ,
} ] ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , ` saved prices for ${ this . currentBlockHeight } ` ) ;
2022-07-16 09:22:45 +02:00
} else {
2023-03-14 13:53:52 +09:00
logger . debug ( ` Cannot save block price for ${ blockExtended . height } because the price updater hasnt completed yet. Trying again in 10 seconds. ` , logger . tags . mining ) ;
2022-07-16 09:22:45 +02:00
setTimeout ( ( ) = > {
indexer . runSingleTask ( 'blocksPrices' ) ;
} , 10000 ) ;
}
2022-06-18 16:48:02 +02:00
// Save blocks summary for visualization if it's enabled
if ( Common . blocksSummariesIndexingEnabled ( ) === true ) {
2023-06-29 19:24:19 -04:00
await this . $getStrippedBlockTransactions ( blockExtended . id , true , false , cpfpSummary , blockExtended . height ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , ` saved block summary for ${ this . currentBlockHeight } ` ) ;
2022-06-18 16:48:02 +02:00
}
2022-12-28 05:28:37 -06:00
if ( config . MEMPOOL . CPFP_INDEXING ) {
2023-03-12 11:09:11 +09:00
this . $saveCpfp ( blockExtended . id , this . currentBlockHeight , cpfpSummary ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , ` saved cpfp for ${ this . currentBlockHeight } ` ) ;
2022-11-27 13:46:23 +09:00
}
2022-03-15 13:07:06 +01:00
}
2022-01-21 00:08:51 +09:00
}
2020-09-21 19:41:12 +07:00
2023-07-08 00:33:14 -04:00
// start async callbacks
this . updateTimerProgress ( timer , ` starting async callbacks for ${ this . currentBlockHeight } ` ) ;
const callbackPromises = this . newAsyncBlockCallbacks . map ( ( cb ) = > cb ( blockExtended , txIds , transactions ) ) ;
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
} ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , ` saved difficulty adjustment for ${ this . currentBlockHeight } ` ) ;
2022-06-25 12:14:32 +02:00
}
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
2023-06-12 15:31:47 -04:00
// wait for pending async callbacks to finish
this . updateTimerProgress ( timer , ` waiting for async callbacks to complete for ${ this . currentBlockHeight } ` ) ;
await Promise . all ( callbackPromises ) ;
this . updateTimerProgress ( timer , ` async callbacks completed for ${ this . currentBlockHeight } ` ) ;
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
}
2023-03-20 15:23:02 +09:00
if ( ! memPool . hasPriority ( ) && ( block . height % config . MEMPOOL . DISK_CACHE_BLOCK_INTERVAL === 0 ) ) {
2021-01-22 23:20:39 +07:00
diskCache . $saveCacheToDisk ( ) ;
}
2022-11-16 18:18:59 -06:00
2023-04-07 09:41:25 +09:00
handledBlocks ++ ;
2023-04-27 10:19:12 +09:00
}
2023-05-01 14:30:30 -06:00
diskCache . unlock ( ) ;
2023-04-27 10:19:12 +09:00
this . clearTimer ( timer ) ;
2023-04-07 09:41:25 +09:00
return handledBlocks ;
2023-04-27 10:19:12 +09:00
}
private startTimer() {
const state : any = {
start : Date.now ( ) ,
progress : 'begin $updateBlocks' ,
timer : null ,
} ;
state . timer = setTimeout ( ( ) = > {
2023-05-01 00:16:23 +04:00
logger . err ( ` $ updateBlocks stalled at " ${ state . progress } " ` ) ;
2023-04-27 10:19:12 +09:00
} , this . mainLoopTimeout ) ;
return state ;
}
private updateTimerProgress ( state , msg ) {
state . progress = msg ;
}
private clearTimer ( state ) {
if ( state . timer ) {
clearTimeout ( state . timer ) ;
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 > {
2023-02-19 19:08:29 +09:00
if ( Common . indexingEnabled ( ) ) {
const dbBlock = await blocksRepository . $getBlockByHeight ( height ) ;
if ( dbBlock !== null ) {
2023-02-27 18:00:00 +09:00
return dbBlock ;
2023-02-19 19:08:29 +09:00
}
2022-02-08 15:47:43 +09:00
}
const blockHash = await bitcoinApi . $getBlockHash ( height ) ;
2023-03-03 13:59:17 +09:00
const block : IEsploraApi.Block = await bitcoinCoreApi . $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 ) ;
2023-02-14 22:12:43 +09:00
if ( Common . indexingEnabled ( ) ) {
await blocksRepository . $saveBlockInDatabase ( blockExtended ) ;
}
2022-02-08 15:47:43 +09:00
2023-02-27 18:00:00 +09:00
return blockExtended ;
2022-02-08 15:47:43 +09:00
}
2023-07-08 00:33:14 -04:00
public async $indexStaleBlock ( hash : string ) : Promise < BlockExtended > {
const block : IEsploraApi.Block = await bitcoinCoreApi . $getBlock ( hash ) ;
const transactions = await this . $getTransactionsExtended ( hash , block . height , true ) ;
const blockExtended = await this . $getBlockExtended ( block , transactions ) ;
blockExtended . canonical = await bitcoinApi . $getBlockHash ( block . height ) ;
return blockExtended ;
}
2022-04-20 13:12:32 +09:00
/ * *
2023-02-27 18:00:00 +09:00
* Get one block by its hash
2022-04-20 13:12:32 +09:00
* /
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 ;
}
2023-02-27 18:00:00 +09:00
// Not Bitcoin network, return the block as it from the bitcoin backend
2022-04-20 13:12:32 +09:00
if ( [ 'mainnet' , 'testnet' , 'signet' ] . includes ( config . MEMPOOL . NETWORK ) === false ) {
2023-03-03 13:59:17 +09:00
return await bitcoinCoreApi . $getBlock ( hash ) ;
2022-04-20 13:12:32 +09:00
}
// Bitcoin network, add our custom data on top
2023-03-03 13:59:17 +09:00
const block : IEsploraApi.Block = await bitcoinCoreApi . $getBlock ( hash ) ;
2023-07-08 00:33:14 -04:00
if ( block . stale ) {
return await this . $indexStaleBlock ( hash ) ;
} else {
return await this . $indexBlock ( block . height ) ;
}
2022-04-20 13:12:32 +09:00
}
2022-07-11 08:41:28 +02:00
public async $getStrippedBlockTransactions ( hash : string , skipMemoryCache = false ,
2023-06-29 19:24:19 -04:00
skipDBLookup = false , cpfpSummary? : CpfpSummary , blockHeight? : number ) : 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 ) ;
2022-11-25 10:16:58 +09:00
if ( cachedSummary ? . transactions ? . length ) {
2022-06-18 16:48:02 +02:00
return cachedSummary . transactions ;
}
}
// Check if it's indexed in db
if ( skipDBLookup === false && Common . blocksSummariesIndexingEnabled ( ) === true ) {
const indexedSummary = await BlocksSummariesRepository . $getByBlockId ( hash ) ;
2022-11-25 10:16:58 +09:00
if ( indexedSummary !== undefined && indexedSummary ? . transactions ? . length ) {
2022-06-18 16:48:02 +02:00
return indexedSummary . transactions ;
}
2022-06-15 19:53:37 +00:00
}
2022-06-18 16:48:02 +02:00
2023-06-29 19:24:19 -04:00
let height = blockHeight ;
let summary : BlockSummary ;
if ( cpfpSummary ) {
summary = {
id : hash ,
transactions : cpfpSummary.transactions.map ( tx = > {
return {
txid : tx.txid ,
fee : tx.fee ,
vsize : tx.vsize ,
value : Math.round ( tx . vout . reduce ( ( acc , vout ) = > acc + ( vout . value ? vout.value : 0 ) , 0 ) ) ,
rate : tx.effectiveFeePerVsize
} ;
} ) ,
} ;
} else {
// Call Core RPC
const block = await bitcoinClient . getBlock ( hash , 2 ) ;
summary = this . summarizeBlock ( block ) ;
height = block . height ;
}
if ( height == null ) {
const block = await bitcoinApi . $getBlock ( hash ) ;
height = block . height ;
}
2022-06-18 16:48:02 +02:00
// Index the response if needed
if ( Common . blocksSummariesIndexingEnabled ( ) === true ) {
2023-06-29 19:24:19 -04:00
await BlocksSummariesRepository . $saveTransactions ( height , hash , summary . transactions ) ;
2022-06-18 16:48:02 +02:00
}
2022-06-15 19:53:37 +00:00
return summary . transactions ;
}
2023-02-27 18:00:00 +09:00
/ * *
* Get 15 blocks
*
* Internally this function uses two methods to get the blocks , and
* the method is automatically selected :
* - Using previous block hash links
* - Using block height
*
* @param fromHeight
* @param limit
* @returns
* /
2022-05-20 18:11:02 +02:00
public async $getBlocks ( fromHeight? : number , limit : number = 15 ) : Promise < BlockExtended [ ] > {
2022-12-16 17:43:37 +01:00
let currentHeight = fromHeight !== undefined ? fromHeight : this.currentBlockHeight ;
2022-12-27 05:28:57 -06:00
if ( currentHeight > this . currentBlockHeight ) {
limit -= currentHeight - this . currentBlockHeight ;
currentHeight = this . currentBlockHeight ;
}
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
for ( let i = 0 ; i < limit && currentHeight >= 0 ; i ++ ) {
let block = this . getBlocks ( ) . find ( ( b ) = > b . height === currentHeight ) ;
if ( block ) {
2023-02-27 18:00:00 +09:00
// Using the memory cache (find by height)
2022-07-08 16:34:00 +02:00
returnBlocks . push ( block ) ;
2023-03-02 10:08:40 +09:00
} else {
2023-02-27 18:00:00 +09:00
// Using indexing (find by height, index on the fly, save in database)
2022-07-08 16:34:00 +02:00
block = await this . $indexBlock ( currentHeight ) ;
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
}
2023-02-17 21:21:21 +09:00
/ * *
* Used for bulk block data query
*
* @param fromHeight
* @param toHeight
* /
public async $getBlocksBetweenHeight ( fromHeight : number , toHeight : number ) : Promise < any > {
if ( ! Common . indexingEnabled ( ) ) {
return [ ] ;
}
2023-02-16 15:36:16 +09:00
const blocks : any [ ] = [ ] ;
2023-02-17 21:21:21 +09:00
while ( fromHeight <= toHeight ) {
2023-02-27 18:00:00 +09:00
let block : BlockExtended | null = await blocksRepository . $getBlockByHeight ( fromHeight ) ;
2023-02-17 21:21:21 +09:00
if ( ! block ) {
2023-02-18 08:42:38 +09:00
await this . $indexBlock ( fromHeight ) ;
block = await blocksRepository . $getBlockByHeight ( fromHeight ) ;
if ( ! block ) {
continue ;
}
2023-02-17 21:21:21 +09:00
}
2023-02-24 11:33:36 +09:00
// Cleanup fields before sending the response
const cleanBlock : any = {
height : block.height ? ? null ,
hash : block.id ? ? null ,
2023-02-27 18:00:00 +09:00
timestamp : block.timestamp ? ? null ,
median_timestamp : block.mediantime ? ? null ,
2023-02-24 11:43:38 +09:00
previous_block_hash : block.previousblockhash ? ? null ,
2023-02-24 11:33:36 +09:00
difficulty : block.difficulty ? ? null ,
2023-02-27 18:00:00 +09:00
header : block.extras.header ? ? null ,
2023-02-24 11:33:36 +09:00
version : block.version ? ? null ,
bits : block.bits ? ? null ,
nonce : block.nonce ? ? null ,
size : block.size ? ? null ,
weight : block.weight ? ? null ,
tx_count : block.tx_count ? ? null ,
merkle_root : block.merkle_root ? ? null ,
2023-02-27 18:00:00 +09:00
reward : block.extras.reward ? ? null ,
total_fee_amt : block.extras.totalFees ? ? null ,
avg_fee_amt : block.extras.avgFee ? ? null ,
median_fee_amt : block.extras.medianFeeAmt ? ? null ,
fee_amt_percentiles : block.extras.feePercentiles ? ? null ,
avg_fee_rate : block.extras.avgFeeRate ? ? null ,
median_fee_rate : block.extras.medianFee ? ? null ,
fee_rate_percentiles : block.extras.feeRange ? ? null ,
total_inputs : block.extras.totalInputs ? ? null ,
total_input_amt : block.extras.totalInputAmt ? ? null ,
total_outputs : block.extras.totalOutputs ? ? null ,
total_output_amt : block.extras.totalOutputAmt ? ? null ,
segwit_total_txs : block.extras.segwitTotalTxs ? ? null ,
segwit_total_size : block.extras.segwitTotalSize ? ? null ,
segwit_total_weight : block.extras.segwitTotalWeight ? ? null ,
avg_tx_size : block.extras.avgTxSize ? ? null ,
utxoset_change : block.extras.utxoSetChange ? ? null ,
utxoset_size : block.extras.utxoSetSize ? ? null ,
coinbase_raw : block.extras.coinbaseRaw ? ? null ,
coinbase_address : block.extras.coinbaseAddress ? ? null ,
coinbase_signature : block.extras.coinbaseSignature ? ? null ,
coinbase_signature_ascii : block.extras.coinbaseSignatureAscii ? ? null ,
pool_slug : block.extras.pool.slug ? ? null ,
pool_id : block.extras.pool.id ? ? null ,
2023-02-24 11:33:36 +09:00
} ;
if ( Common . blocksSummariesIndexingEnabled ( ) && cleanBlock . fee_amt_percentiles === null ) {
cleanBlock . fee_amt_percentiles = await BlocksSummariesRepository . $getFeePercentilesByBlockId ( cleanBlock . hash ) ;
if ( cleanBlock . fee_amt_percentiles === null ) {
const block = await bitcoinClient . getBlock ( cleanBlock . hash , 2 ) ;
const summary = this . summarizeBlock ( block ) ;
2023-03-31 12:08:05 +09:00
await BlocksSummariesRepository . $saveTransactions ( cleanBlock . height , cleanBlock . hash , summary . transactions ) ;
2023-02-24 11:33:36 +09:00
cleanBlock . fee_amt_percentiles = await BlocksSummariesRepository . $getFeePercentilesByBlockId ( cleanBlock . hash ) ;
}
if ( cleanBlock . fee_amt_percentiles !== null ) {
cleanBlock . median_fee_amt = cleanBlock . fee_amt_percentiles [ 3 ] ;
2023-04-06 11:55:17 +09:00
await blocksRepository . $updateFeeAmounts ( cleanBlock . hash , cleanBlock . fee_amt_percentiles , cleanBlock . median_fee_amt ) ;
2023-02-18 11:26:13 +09:00
}
}
2023-02-24 12:48:55 +09:00
cleanBlock . fee_amt_percentiles = {
'min' : cleanBlock . fee_amt_percentiles [ 0 ] ,
'perc_10' : cleanBlock . fee_amt_percentiles [ 1 ] ,
'perc_25' : cleanBlock . fee_amt_percentiles [ 2 ] ,
'perc_50' : cleanBlock . fee_amt_percentiles [ 3 ] ,
'perc_75' : cleanBlock . fee_amt_percentiles [ 4 ] ,
'perc_90' : cleanBlock . fee_amt_percentiles [ 5 ] ,
'max' : cleanBlock . fee_amt_percentiles [ 6 ] ,
} ;
cleanBlock . fee_rate_percentiles = {
'min' : cleanBlock . fee_rate_percentiles [ 0 ] ,
'perc_10' : cleanBlock . fee_rate_percentiles [ 1 ] ,
'perc_25' : cleanBlock . fee_rate_percentiles [ 2 ] ,
'perc_50' : cleanBlock . fee_rate_percentiles [ 3 ] ,
'perc_75' : cleanBlock . fee_rate_percentiles [ 4 ] ,
'perc_90' : cleanBlock . fee_rate_percentiles [ 5 ] ,
'max' : cleanBlock . fee_rate_percentiles [ 6 ] ,
} ;
2023-02-18 14:10:07 +09:00
// Re-org can happen after indexing so we need to always get the
// latest state from core
2023-02-24 11:33:36 +09:00
cleanBlock . orphans = chainTips . getOrphanedBlocksAtHeight ( cleanBlock . height ) ;
2023-02-18 14:10:07 +09:00
2023-02-24 11:33:36 +09:00
blocks . push ( cleanBlock ) ;
2023-02-17 21:21:21 +09:00
fromHeight ++ ;
2023-02-16 15:36:16 +09:00
}
2023-02-17 21:21:21 +09:00
2023-02-16 15:36:16 +09:00
return blocks ;
}
2022-11-25 19:32:50 +09:00
public async $getBlockAuditSummary ( hash : string ) : Promise < any > {
2022-11-28 14:26:28 +09:00
if ( [ 'mainnet' , 'testnet' , 'signet' ] . includes ( config . MEMPOOL . NETWORK ) ) {
2023-06-20 14:54:25 -04:00
return BlocksAuditsRepository . $getBlockAudit ( hash ) ;
} else {
return null ;
2022-11-25 19:32:50 +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 ;
}
2022-11-27 13:46:23 +09:00
2022-11-28 17:56:50 +09:00
public async $indexCPFP ( hash : string , height : number ) : Promise < void > {
2023-01-07 15:51:31 -06:00
const block = await bitcoinClient . getBlock ( hash , 2 ) ;
2023-01-09 10:24:12 -06:00
const transactions = block . tx . map ( tx = > {
2023-01-07 15:51:31 -06:00
tx . fee *= 100 _000_000 ;
return tx ;
} ) ;
2023-03-12 14:36:36 +09:00
const summary = Common . calculateCpfp ( height , transactions ) ;
2023-03-12 11:09:11 +09:00
await this . $saveCpfp ( hash , height , summary ) ;
2023-03-12 14:36:36 +09:00
const effectiveFeeStats = Common . calcEffectiveFeeStatistics ( summary . transactions ) ;
2023-03-12 11:09:11 +09:00
await blocksRepository . $saveEffectiveFeeStats ( hash , effectiveFeeStats ) ;
}
2023-01-10 17:13:11 -06:00
2023-03-12 11:09:11 +09:00
public async $saveCpfp ( hash : string , height : number , cpfpSummary : CpfpSummary ) : Promise < void > {
2023-06-22 11:52:43 -04:00
try {
const result = await cpfpRepository . $batchSaveClusters ( cpfpSummary . clusters ) ;
if ( ! result ) {
await cpfpRepository . $insertProgressMarker ( height ) ;
}
} catch ( e ) {
// not a fatal error, we'll try again next time the indexer runs
2023-01-10 17:15:55 -06:00
}
2022-11-27 13:46:23 +09:00
}
2019-07-21 17:59:47 +03:00
}
export default new Blocks ( ) ;