2023-03-12 11:09:11 +09:00
import { BlockExtended , BlockExtension , BlockPrice , EffectiveFeeStats } from '../mempool.interfaces' ;
2022-04-13 17:38:42 +04:00
import DB from '../database' ;
2022-01-18 17:37:04 +09:00
import logger from '../logger' ;
2022-02-09 19:41:05 +09:00
import { Common } from '../api/common' ;
2022-03-25 14:22:22 +09:00
import PoolsRepository from './PoolsRepository' ;
2022-04-18 17:49:22 +09:00
import HashratesRepository from './HashratesRepository' ;
2022-05-18 15:01:24 +02:00
import { escape } from 'mysql2' ;
2022-06-20 16:35:10 +02:00
import BlocksSummariesRepository from './BlocksSummariesRepository' ;
2022-06-25 12:14:32 +02:00
import DifficultyAdjustmentsRepository from './DifficultyAdjustmentsRepository' ;
2023-01-05 13:02:53 -06:00
import bitcoinClient from '../api/bitcoin/bitcoin-client' ;
import config from '../config' ;
2023-02-27 18:00:00 +09:00
import chainTips from '../api/chain-tips' ;
import blocks from '../api/blocks' ;
2023-02-27 18:39:02 +09:00
import BlocksAuditsRepository from './BlocksAuditsRepository' ;
2023-02-27 18:00:00 +09:00
const BLOCK_DB_FIELDS = `
blocks . hash AS id ,
blocks . height ,
blocks . version ,
UNIX_TIMESTAMP ( blocks . blockTimestamp ) AS timestamp ,
blocks . bits ,
blocks . nonce ,
blocks . difficulty ,
blocks . merkle_root ,
blocks . tx_count ,
blocks . size ,
blocks . weight ,
blocks . previous_block_hash AS previousblockhash ,
UNIX_TIMESTAMP ( blocks . median_timestamp ) AS mediantime ,
blocks . fees AS totalFees ,
blocks . median_fee AS medianFee ,
blocks . fee_span AS feeRange ,
blocks . reward ,
pools . unique_id AS poolId ,
pools . name AS poolName ,
pools . slug AS poolSlug ,
blocks . avg_fee AS avgFee ,
blocks . avg_fee_rate AS avgFeeRate ,
blocks . coinbase_raw AS coinbaseRaw ,
blocks . coinbase_address AS coinbaseAddress ,
blocks . coinbase_signature AS coinbaseSignature ,
blocks . coinbase_signature_ascii AS coinbaseSignatureAscii ,
blocks . avg_tx_size AS avgTxSize ,
blocks . total_inputs AS totalInputs ,
blocks . total_outputs AS totalOutputs ,
blocks . total_output_amt AS totalOutputAmt ,
blocks . median_fee_amt AS medianFeeAmt ,
blocks . fee_percentiles AS feePercentiles ,
blocks . segwit_total_txs AS segwitTotalTxs ,
blocks . segwit_total_size AS segwitTotalSize ,
blocks . segwit_total_weight AS segwitTotalWeight ,
blocks . header ,
blocks . utxoset_change AS utxoSetChange ,
blocks . utxoset_size AS utxoSetSize ,
blocks . total_input_amt AS totalInputAmts
` ;
2022-01-06 19:59:33 +09:00
2022-01-05 15:41:14 +09:00
class BlocksRepository {
/ * *
* Save indexed block data in the database
* /
2022-02-08 15:47:43 +09:00
public async $saveBlockInDatabase ( block : BlockExtended ) {
2023-02-26 18:24:08 +09:00
const truncatedCoinbaseSignature = block ? . extras ? . coinbaseSignature ? . substring ( 0 , 500 ) ;
const truncatedCoinbaseSignatureAscii = block ? . extras ? . coinbaseSignatureAscii ? . substring ( 0 , 500 ) ;
2022-01-05 15:41:14 +09:00
try {
const query = ` INSERT INTO blocks(
2023-02-17 21:21:21 +09:00
height , hash , blockTimestamp , size ,
weight , tx_count , coinbase_raw , difficulty ,
pool_id , fees , fee_span , median_fee ,
reward , version , bits , nonce ,
merkle_root , previous_block_hash , avg_fee , avg_fee_rate ,
2023-02-24 11:33:36 +09:00
median_timestamp , header , coinbase_address ,
2023-02-17 21:21:21 +09:00
coinbase_signature , utxoset_size , utxoset_change , avg_tx_size ,
total_inputs , total_outputs , total_input_amt , total_output_amt ,
fee_percentiles , segwit_total_txs , segwit_total_size , segwit_total_weight ,
2023-02-23 08:50:30 +09:00
median_fee_amt , coinbase_signature_ascii
2022-01-05 15:41:14 +09:00
) VALUE (
2022-01-06 19:59:33 +09:00
? , ? , FROM_UNIXTIME ( ? ) , ? ,
2022-01-05 15:41:14 +09:00
? , ? , ? , ? ,
2022-02-08 15:47:43 +09:00
? , ? , ? , ? ,
2022-02-16 15:19:16 +09:00
? , ? , ? , ? ,
2023-02-17 21:21:21 +09:00
? , ? , ? , ? ,
2023-02-24 11:33:36 +09:00
FROM_UNIXTIME ( ? ) , ? , ? ,
2023-02-17 21:21:21 +09:00
? , ? , ? , ? ,
? , ? , ? , ? ,
? , ? , ? , ? ,
2023-02-23 08:50:30 +09:00
? , ?
2022-01-05 15:41:14 +09:00
) ` ;
2023-02-27 18:00:00 +09:00
const poolDbId = await PoolsRepository . $getPoolByUniqueId ( block . extras . pool . id ) ;
if ( ! poolDbId ) {
throw Error ( ` Could not find a mining pool with the unique_id = ${ block . extras . pool . id } . This error should never be printed. ` ) ;
}
2022-01-05 15:41:14 +09:00
const params : any [ ] = [
2022-02-04 12:51:45 +09:00
block . height ,
2022-02-08 15:47:43 +09:00
block . id ,
2022-02-04 12:51:45 +09:00
block . timestamp ,
block . size ,
block . weight ,
block . tx_count ,
2022-03-16 12:10:18 +01:00
block . extras . coinbaseRaw ,
2022-02-04 12:51:45 +09:00
block . difficulty ,
2023-02-27 18:00:00 +09:00
poolDbId . id ,
2022-03-09 19:18:51 +01:00
block . extras . totalFees ,
JSON . stringify ( block . extras . feeRange ) ,
block . extras . medianFee ,
block . extras . reward ,
2022-02-16 15:19:16 +09:00
block . version ,
block . bits ,
block . nonce ,
block . merkle_root ,
2022-03-09 19:18:51 +01:00
block . previousblockhash ,
block . extras . avgFee ,
block . extras . avgFeeRate ,
2023-02-27 18:00:00 +09:00
block . mediantime ,
2023-02-17 21:21:21 +09:00
block . extras . header ,
block . extras . coinbaseAddress ,
2023-02-26 18:24:08 +09:00
truncatedCoinbaseSignature ,
2023-02-17 21:21:21 +09:00
block . extras . utxoSetSize ,
block . extras . utxoSetChange ,
block . extras . avgTxSize ,
block . extras . totalInputs ,
block . extras . totalOutputs ,
block . extras . totalInputAmt ,
block . extras . totalOutputAmt ,
2023-02-20 11:59:38 +09:00
block . extras . feePercentiles ? JSON . stringify ( block . extras . feePercentiles ) : null ,
2023-02-17 21:21:21 +09:00
block . extras . segwitTotalTxs ,
block . extras . segwitTotalSize ,
block . extras . segwitTotalWeight ,
block . extras . medianFeeAmt ,
2023-02-26 18:24:08 +09:00
truncatedCoinbaseSignatureAscii ,
2022-01-05 15:41:14 +09:00
] ;
2022-04-12 15:15:57 +09:00
await DB . query ( query , params ) ;
2022-02-05 15:50:57 +09:00
} catch ( e : any ) {
2022-04-13 16:29:52 +09:00
if ( e . errno === 1062 ) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
2023-02-27 18:00:00 +09:00
logger . debug ( ` $ saveBlockInDatabase() - Block ${ block . height } has already been indexed, ignoring ` , logger . tags . mining ) ;
2022-02-05 15:50:57 +09:00
} else {
2023-02-27 18:00:00 +09:00
logger . err ( 'Cannot save indexed block into db. Reason: ' + ( e instanceof Error ? e.message : e ) , logger . tags . mining ) ;
2022-03-06 16:44:09 +01:00
throw e ;
2022-02-05 15:50:57 +09:00
}
2022-01-05 15:41:14 +09:00
}
}
2023-02-17 21:21:21 +09:00
/ * *
* Save newly indexed data from core coinstatsindex
*
* @param utxoSetSize
* @param totalInputAmt
* /
public async $updateCoinStatsIndexData ( blockHash : string , utxoSetSize : number ,
totalInputAmt : number
) : Promise < void > {
try {
const query = `
UPDATE blocks
SET utxoset_size = ? , total_input_amt = ?
WHERE hash = ?
` ;
const params : any [ ] = [
utxoSetSize ,
totalInputAmt ,
blockHash
] ;
await DB . query ( query , params ) ;
} catch ( e : any ) {
logger . err ( 'Cannot update indexed block coinstatsindex. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-01-18 17:37:04 +09:00
/ * *
* Get all block height that have not been indexed between [ startHeight , endHeight ]
* /
public async $getMissingBlocksBetweenHeights ( startHeight : number , endHeight : number ) : Promise < number [ ] > {
2022-01-24 17:43:11 +09:00
if ( startHeight < endHeight ) {
return [ ] ;
}
2022-03-06 16:44:09 +01:00
try {
2022-04-12 15:15:57 +09:00
const [ rows ] : any [ ] = await DB . query ( `
2022-03-06 16:44:09 +01:00
SELECT height
FROM blocks
WHERE height <= ? AND height >= ?
ORDER BY height DESC ;
` , [startHeight, endHeight]);
const indexedBlockHeights : number [ ] = [ ] ;
rows . forEach ( ( row : any ) = > { indexedBlockHeights . push ( row . height ) ; } ) ;
const seekedBlocks : number [ ] = Array . from ( Array ( startHeight - endHeight + 1 ) . keys ( ) , n = > n + endHeight ) . reverse ( ) ;
const missingBlocksHeights = seekedBlocks . filter ( x = > indexedBlockHeights . indexOf ( x ) === - 1 ) ;
return missingBlocksHeights ;
} catch ( e ) {
2022-04-13 16:29:52 +09:00
logger . err ( 'Cannot retrieve blocks list to index. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
2022-03-06 16:44:09 +01:00
throw e ;
}
2022-01-18 17:37:04 +09:00
}
2022-01-06 19:59:33 +09:00
/ * *
2022-02-09 19:41:05 +09:00
* Get empty blocks for one or all pools
2022-01-06 19:59:33 +09:00
* /
2022-03-08 16:55:49 +01:00
public async $countEmptyBlocks ( poolId : number | null , interval : string | null = null ) : Promise < any > {
2022-02-09 19:41:05 +09:00
interval = Common . getSqlInterval ( interval ) ;
const params : any [ ] = [ ] ;
2022-03-08 16:55:49 +01:00
let query = ` SELECT count(height) as count, pools.id as poolId
2022-01-06 19:59:33 +09:00
FROM blocks
2022-03-08 16:55:49 +01:00
JOIN pools on pools . id = blocks . pool_id
2022-02-09 19:41:05 +09:00
WHERE tx_count = 1 ` ;
if ( poolId ) {
query += ` AND pool_id = ? ` ;
params . push ( poolId ) ;
}
if ( interval ) {
query += ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${ interval } ) AND NOW() ` ;
}
2022-01-25 18:33:46 +09:00
2022-03-08 16:55:49 +01:00
query += ` GROUP by pools.id ` ;
2022-03-06 16:44:09 +01:00
try {
2022-04-12 15:15:57 +09:00
const [ rows ] = await DB . query ( query , params ) ;
2022-03-08 16:55:49 +01:00
return rows ;
2022-03-06 16:44:09 +01:00
} catch ( e ) {
2022-04-13 16:29:52 +09:00
logger . err ( 'Cannot count empty blocks. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
2022-03-06 16:44:09 +01:00
throw e ;
}
2022-01-06 19:59:33 +09:00
}
2022-05-24 11:16:01 +02:00
/ * *
* Return most recent block height
* /
public async $mostRecentBlockHeight ( ) : Promise < number > {
try {
const [ row ] = await DB . query ( 'SELECT MAX(height) as maxHeight from blocks' ) ;
return row [ 0 ] [ 'maxHeight' ] ;
} catch ( e ) {
logger . err ( ` Cannot count blocks for this pool (using offset). Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-01-06 19:59:33 +09:00
/ * *
* Get blocks count for a period
* /
2022-03-08 13:54:04 +01:00
public async $blockCount ( poolId : number | null , interval : string | null = null ) : Promise < number > {
2022-02-09 19:41:05 +09:00
interval = Common . getSqlInterval ( interval ) ;
2022-01-25 18:33:46 +09:00
2022-02-09 19:41:05 +09:00
const params : any [ ] = [ ] ;
let query = ` SELECT count(height) as blockCount
FROM blocks ` ;
if ( poolId ) {
query += ` WHERE pool_id = ? ` ;
params . push ( poolId ) ;
}
if ( interval ) {
if ( poolId ) {
query += ` AND ` ;
} else {
query += ` WHERE ` ;
}
query += ` blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${ interval } ) AND NOW() ` ;
}
2022-03-06 16:44:09 +01:00
try {
2022-04-12 15:15:57 +09:00
const [ rows ] = await DB . query ( query , params ) ;
2022-03-06 16:44:09 +01:00
return < number > rows [ 0 ] . blockCount ;
} catch ( e ) {
2022-04-13 16:29:52 +09:00
logger . err ( ` Cannot count blocks for this pool (using offset). Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
2022-03-06 16:44:09 +01:00
throw e ;
}
2022-01-25 18:33:46 +09:00
}
2022-02-19 20:45:02 +09:00
/ * *
* Get blocks count between two dates
2022-03-16 17:00:06 +01:00
* @param poolId
2022-02-19 20:45:02 +09:00
* @param from - The oldest timestamp
* @param to - The newest timestamp
2022-03-16 17:00:06 +01:00
* @returns
2022-02-19 20:45:02 +09:00
* /
public async $blockCountBetweenTimestamp ( poolId : number | null , from : number , to : number ) : Promise < number > {
const params : any [ ] = [ ] ;
let query = ` SELECT
count ( height ) as blockCount ,
max ( height ) as lastBlockHeight
FROM blocks ` ;
if ( poolId ) {
query += ` WHERE pool_id = ? ` ;
params . push ( poolId ) ;
}
if ( poolId ) {
query += ` AND ` ;
} else {
query += ` WHERE ` ;
}
2022-02-24 16:55:18 +09:00
query += ` blockTimestamp BETWEEN FROM_UNIXTIME(' ${ from } ') AND FROM_UNIXTIME(' ${ to } ') ` ;
2022-02-19 20:45:02 +09:00
2022-03-06 16:44:09 +01:00
try {
2022-04-12 15:15:57 +09:00
const [ rows ] = await DB . query ( query , params ) ;
2022-03-06 16:44:09 +01:00
return < number > rows [ 0 ] ;
} catch ( e ) {
2022-04-13 16:29:52 +09:00
logger . err ( ` Cannot count blocks for this pool (using timestamps). Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
2022-03-06 16:44:09 +01:00
throw e ;
}
2022-02-19 20:45:02 +09:00
}
2022-05-02 17:28:58 +09:00
/ * *
* Get blocks count for a period
* /
public async $blockCountBetweenHeight ( startHeight : number , endHeight : number ) : Promise < number > {
const params : any [ ] = [ ] ;
let query = ` SELECT count(height) as blockCount
FROM blocks
WHERE height <= $ { startHeight } AND height >= $ { endHeight } ` ;
try {
const [ rows ] = await DB . query ( query , params ) ;
return < number > rows [ 0 ] . blockCount ;
} catch ( e ) {
logger . err ( ` Cannot count blocks for this pool (using offset). Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2023-03-16 16:13:11 +09:00
/ * *
* Get average block health for all blocks for a single pool
* /
public async $getAvgBlockHealthPerPoolId ( poolId : number ) : Promise < number > {
const params : any [ ] = [ ] ;
const query = `
SELECT AVG ( blocks_audits . match_rate ) AS avg_match_rate
FROM blocks
JOIN blocks_audits ON blocks . height = blocks_audits . height
WHERE blocks . pool_id = ?
` ;
params . push ( poolId ) ;
try {
const [ rows ] = await DB . query ( query , params ) ;
if ( ! rows [ 0 ] || ! rows [ 0 ] . avg_match_rate ) {
return 0 ;
}
return Math . round ( rows [ 0 ] . avg_match_rate * 100 ) / 100 ;
} catch ( e ) {
logger . err ( ` Cannot get average block health for pool id ${ poolId } . Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
/ * *
* Get average block health for all blocks for a single pool
* /
public async $getTotalRewardForPoolId ( poolId : number ) : Promise < number > {
const params : any [ ] = [ ] ;
const query = `
SELECT sum ( reward ) as total_reward
FROM blocks
WHERE blocks . pool_id = ?
` ;
params . push ( poolId ) ;
try {
const [ rows ] = await DB . query ( query , params ) ;
if ( ! rows [ 0 ] || ! rows [ 0 ] . total_reward ) {
return 0 ;
}
return rows [ 0 ] . total_reward ;
} catch ( e ) {
logger . err ( ` Cannot get total reward for pool id ${ poolId } . Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-01-25 18:33:46 +09:00
/ * *
* Get the oldest indexed block
* /
public async $oldestBlockTimestamp ( ) : Promise < number > {
2022-02-22 15:50:14 +09:00
const query = ` SELECT UNIX_TIMESTAMP(blockTimestamp) as blockTimestamp
2022-01-06 19:59:33 +09:00
FROM blocks
2022-01-25 18:33:46 +09:00
ORDER BY height
2022-02-09 19:41:05 +09:00
LIMIT 1 ; ` ;
2022-03-06 16:44:09 +01:00
try {
2022-04-12 15:15:57 +09:00
const [ rows ] : any [ ] = await DB . query ( query ) ;
2022-01-06 19:59:33 +09:00
2022-03-06 16:44:09 +01:00
if ( rows . length <= 0 ) {
return - 1 ;
}
2022-01-25 18:33:46 +09:00
2022-03-06 16:44:09 +01:00
return < number > rows [ 0 ] . blockTimestamp ;
} catch ( e ) {
2022-04-13 16:29:52 +09:00
logger . err ( 'Cannot get oldest indexed block timestamp. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
2022-03-06 16:44:09 +01:00
throw e ;
}
2022-01-06 19:59:33 +09:00
}
2022-02-08 15:47:43 +09:00
2022-02-08 18:28:53 +09:00
/ * *
* Get blocks mined by a specific mining pool
* /
2023-02-27 18:00:00 +09:00
public async $getBlocksByPool ( slug : string , startHeight? : number ) : Promise < BlockExtended [ ] > {
2022-03-25 14:22:22 +09:00
const pool = await PoolsRepository . $getPool ( slug ) ;
if ( ! pool ) {
2022-05-18 15:01:24 +02:00
throw new Error ( 'This mining pool does not exist ' + escape ( slug ) ) ;
2022-03-25 14:22:22 +09:00
}
2022-02-09 19:41:05 +09:00
const params : any [ ] = [ ] ;
2023-02-27 18:00:00 +09:00
let query = `
SELECT $ { BLOCK_DB_FIELDS }
2022-02-08 18:28:53 +09:00
FROM blocks
2023-02-27 18:00:00 +09:00
JOIN pools ON blocks . pool_id = pools . id
2022-02-09 19:41:05 +09:00
WHERE pool_id = ? ` ;
2022-03-25 14:22:22 +09:00
params . push ( pool . id ) ;
2022-02-09 19:41:05 +09:00
2022-03-16 20:19:39 +01:00
if ( startHeight !== undefined ) {
2022-02-09 19:41:05 +09:00
query += ` AND height < ? ` ;
params . push ( startHeight ) ;
}
2022-02-08 18:28:53 +09:00
2022-02-09 19:41:05 +09:00
query += ` ORDER BY height DESC
LIMIT 10 ` ;
2022-03-06 16:44:09 +01:00
try {
2023-02-27 18:00:00 +09:00
const [ rows ] : any [ ] = await DB . query ( query , params ) ;
2022-02-08 18:28:53 +09:00
2022-03-16 17:00:06 +01:00
const blocks : BlockExtended [ ] = [ ] ;
2023-02-27 18:00:00 +09:00
for ( const block of rows ) {
blocks . push ( await this . formatDbBlockIntoExtendedBlock ( block ) ) ;
2022-03-06 16:44:09 +01:00
}
2022-02-09 19:41:05 +09:00
2022-03-16 17:00:06 +01:00
return blocks ;
2022-03-06 16:44:09 +01:00
} catch ( e ) {
2022-04-13 16:29:52 +09:00
logger . err ( 'Cannot get blocks for this pool. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
2022-03-06 16:44:09 +01:00
throw e ;
}
2022-02-08 18:28:53 +09:00
}
2022-02-08 15:47:43 +09:00
/ * *
* Get one block by height
* /
2023-02-27 18:00:00 +09:00
public async $getBlockByHeight ( height : number ) : Promise < BlockExtended | null > {
2022-03-06 16:44:09 +01:00
try {
2023-02-27 18:00:00 +09:00
const [ rows ] : any [ ] = await DB . query ( `
SELECT $ { BLOCK_DB_FIELDS }
2022-03-06 16:44:09 +01:00
FROM blocks
JOIN pools ON blocks . pool_id = pools . id
2023-02-27 18:00:00 +09:00
WHERE blocks . height = ? ` ,
[ height ]
) ;
2022-03-06 16:44:09 +01:00
if ( rows . length <= 0 ) {
return null ;
}
2022-02-08 15:47:43 +09:00
2023-02-27 18:00:00 +09:00
return await this . formatDbBlockIntoExtendedBlock ( rows [ 0 ] ) ;
2022-03-06 16:44:09 +01:00
} catch ( e ) {
2022-04-13 16:29:52 +09:00
logger . err ( ` Cannot get indexed block ${ height } . Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
2022-03-06 16:44:09 +01:00
throw e ;
2022-02-08 15:47:43 +09:00
}
}
2022-02-16 21:20:28 +09:00
/ * *
* Return blocks difficulty
* /
2022-06-25 12:14:32 +02:00
public async $getBlocksDifficulty ( ) : Promise < object [ ] > {
2022-03-06 16:44:09 +01:00
try {
2022-06-25 12:14:32 +02:00
const [ rows ] : any [ ] = await DB . query ( ` SELECT UNIX_TIMESTAMP(blockTimestamp) as time, height, difficulty FROM blocks ` ) ;
2022-03-06 16:44:09 +01:00
return rows ;
} catch ( e ) {
2022-07-09 16:53:29 +02:00
logger . err ( 'Cannot get blocks difficulty list from the db. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-09-18 22:30:09 +09:00
/ * *
* Get the first block at or directly after a given timestamp
* @param timestamp number unix time in seconds
* @returns The height and timestamp of a block ( timestamp might vary from given timestamp )
* /
public async $getBlockHeightFromTimestamp (
timestamp : number ,
2022-09-19 16:44:53 +09:00
) : Promise < { height : number ; hash : string ; timestamp : number } > {
2022-09-18 22:30:09 +09:00
try {
// Get first block at or after the given timestamp
2022-09-19 16:44:53 +09:00
const query = ` SELECT height, hash, blockTimestamp as timestamp FROM blocks
WHERE blockTimestamp <= FROM_UNIXTIME ( ? )
ORDER BY blockTimestamp DESC
2022-09-18 22:30:09 +09:00
LIMIT 1 ` ;
const params = [ timestamp ] ;
const [ rows ] : any [ ] [ ] = await DB . query ( query , params ) ;
if ( rows . length === 0 ) {
2022-09-19 16:44:53 +09:00
throw new Error ( ` No block was found before timestamp ${ timestamp } ` ) ;
2022-09-18 22:30:09 +09:00
}
return rows [ 0 ] ;
} catch ( e ) {
logger . err (
'Cannot get block height from timestamp from the db. Reason: ' +
( e instanceof Error ? e.message : e ) ,
) ;
throw e ;
}
}
2022-07-09 16:53:29 +02:00
/ * *
* Return blocks height
* /
public async $getBlocksHeightsAndTimestamp ( ) : Promise < object [ ] > {
try {
const [ rows ] : any [ ] = await DB . query ( ` SELECT height, blockTimestamp as timestamp FROM blocks ` ) ;
return rows ;
} catch ( e ) {
logger . err ( 'Cannot get blocks height and timestamp from the db. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
2022-03-06 16:44:09 +01:00
throw e ;
}
2022-02-16 21:20:28 +09:00
}
2022-02-21 12:22:20 +09:00
2022-03-22 12:34:29 +09:00
/ * *
* Get general block stats
* /
public async $getBlockStats ( blockCount : number ) : Promise < any > {
try {
// We need to use a subquery
2022-04-01 12:41:25 +09:00
const query = `
SELECT MIN ( height ) as startBlock , MAX ( height ) as endBlock , SUM ( reward ) as totalReward , SUM ( fees ) as totalFee , SUM ( tx_count ) as totalTx
FROM
( SELECT height , reward , fees , tx_count FROM blocks
ORDER by height DESC
LIMIT ? ) as sub ` ;
2022-04-12 15:15:57 +09:00
const [ rows ] : any = await DB . query ( query , [ blockCount ] ) ;
2022-03-22 12:34:29 +09:00
return rows [ 0 ] ;
} catch ( e ) {
2022-04-13 16:29:52 +09:00
logger . err ( 'Cannot generate reward stats. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
2022-03-22 12:34:29 +09:00
throw e ;
}
}
2022-03-15 13:07:06 +01:00
/ * *
2022-04-18 17:49:22 +09:00
* Check if the chain of block hash is valid and delete data from the stale branch if needed
2022-03-15 13:07:06 +01:00
* /
2022-04-18 17:49:22 +09:00
public async $validateChain ( ) : Promise < boolean > {
try {
const start = new Date ( ) . getTime ( ) ;
2023-03-01 13:50:15 +09:00
const [ blocks ] : any [ ] = await DB . query ( `
SELECT
height ,
hash ,
previous_block_hash ,
UNIX_TIMESTAMP ( blockTimestamp ) AS timestamp
FROM blocks
ORDER BY height
` );
2022-04-18 17:49:22 +09:00
2022-04-30 15:23:53 +09:00
let partialMsg = false ;
let idx = 1 ;
while ( idx < blocks . length ) {
if ( blocks [ idx ] . height - 1 !== blocks [ idx - 1 ] . height ) {
if ( partialMsg === false ) {
logger . info ( 'Some blocks are not indexed, skipping missing blocks during chain validation' ) ;
partialMsg = true ;
}
++ idx ;
continue ;
}
if ( blocks [ idx ] . previous_block_hash !== blocks [ idx - 1 ] . hash ) {
2022-07-06 21:03:55 +02:00
logger . warn ( ` Chain divergence detected at block ${ blocks [ idx - 1 ] . height } ` ) ;
2022-04-30 15:23:53 +09:00
await this . $deleteBlocksFrom ( blocks [ idx - 1 ] . height ) ;
await HashratesRepository . $deleteHashratesFromTimestamp ( blocks [ idx - 1 ] . timestamp - 604800 ) ;
2022-06-25 12:14:32 +02:00
await DifficultyAdjustmentsRepository . $deleteAdjustementsFromHeight ( blocks [ idx - 1 ] . height ) ;
2022-04-18 17:49:22 +09:00
return false ;
}
2022-04-30 15:23:53 +09:00
++ idx ;
2022-04-18 17:49:22 +09:00
}
2022-07-11 10:55:25 +02:00
logger . debug ( ` ${ idx } blocks hash validated in ${ new Date ( ) . getTime ( ) - start } ms ` ) ;
2022-04-18 17:49:22 +09:00
return true ;
} catch ( e ) {
logger . err ( 'Cannot validate chain of block hash. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
return true ; // Don't do anything if there is a db error
}
}
/ * *
* Delete blocks from the database from blockHeight
* /
public async $deleteBlocksFrom ( blockHeight : number ) {
2023-03-19 10:46:38 +09:00
logger . info ( ` Delete newer blocks from height ${ blockHeight } from the database ` , logger . tags . mining ) ;
2022-03-15 13:07:06 +01:00
try {
2022-04-18 17:49:22 +09:00
await DB . query ( ` DELETE FROM blocks where height >= ${ blockHeight } ` ) ;
2022-03-15 13:07:06 +01:00
} catch ( e ) {
2022-04-18 17:49:22 +09:00
logger . err ( 'Cannot delete indexed blocks. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
2022-03-15 13:07:06 +01:00
}
}
2022-04-09 01:07:13 +09:00
/ * *
2022-04-13 16:29:52 +09:00
* Get the historical averaged block fees
2022-04-09 01:07:13 +09:00
* /
public async $getHistoricalBlockFees ( div : number , interval : string | null ) : Promise < any > {
try {
2022-04-15 00:21:38 +09:00
let query = ` SELECT
2022-06-06 10:14:40 +02:00
CAST ( AVG ( blocks . height ) as INT ) as avgHeight ,
2022-04-15 00:21:38 +09:00
CAST ( AVG ( UNIX_TIMESTAMP ( blockTimestamp ) ) as INT ) as timestamp ,
2022-07-11 23:16:48 +02:00
CAST ( AVG ( fees ) as INT ) as avgFees ,
2023-02-23 15:06:57 +09:00
prices . USD
2022-06-06 10:14:40 +02:00
FROM blocks
2022-07-11 23:16:48 +02:00
JOIN blocks_prices on blocks_prices . height = blocks . height
JOIN prices on prices . id = blocks_prices . price_id
2022-07-09 16:53:29 +02:00
` ;
2022-04-09 01:07:13 +09:00
if ( interval !== null ) {
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${ interval } ) AND NOW() ` ;
}
query += ` GROUP BY UNIX_TIMESTAMP(blockTimestamp) DIV ${ div } ` ;
2022-04-12 15:15:57 +09:00
const [ rows ] : any = await DB . query ( query ) ;
2022-04-09 01:07:13 +09:00
return rows ;
} catch ( e ) {
2022-04-13 16:29:52 +09:00
logger . err ( 'Cannot generate block fees history. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
2022-04-09 01:07:13 +09:00
throw e ;
}
}
2022-04-11 20:57:13 +09:00
/ * *
* Get the historical averaged block rewards
* /
2022-04-20 13:12:32 +09:00
public async $getHistoricalBlockRewards ( div : number , interval : string | null ) : Promise < any > {
2022-04-11 20:57:13 +09:00
try {
2022-04-15 00:21:38 +09:00
let query = ` SELECT
2022-06-06 10:14:40 +02:00
CAST ( AVG ( blocks . height ) as INT ) as avgHeight ,
2022-04-15 00:21:38 +09:00
CAST ( AVG ( UNIX_TIMESTAMP ( blockTimestamp ) ) as INT ) as timestamp ,
2022-07-11 23:16:48 +02:00
CAST ( AVG ( reward ) as INT ) as avgRewards ,
2023-02-23 15:06:57 +09:00
prices . USD
2022-06-06 10:14:40 +02:00
FROM blocks
2022-07-11 23:16:48 +02:00
JOIN blocks_prices on blocks_prices . height = blocks . height
JOIN prices on prices . id = blocks_prices . price_id
2022-07-09 16:53:29 +02:00
` ;
2022-04-11 20:57:13 +09:00
if ( interval !== null ) {
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${ interval } ) AND NOW() ` ;
}
query += ` GROUP BY UNIX_TIMESTAMP(blockTimestamp) DIV ${ div } ` ;
2022-04-12 15:15:57 +09:00
const [ rows ] : any = await DB . query ( query ) ;
2022-04-11 20:57:13 +09:00
return rows ;
} catch ( e ) {
2022-04-13 16:29:52 +09:00
logger . err ( 'Cannot generate block rewards history. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
2022-04-11 20:57:13 +09:00
throw e ;
}
}
2022-04-14 15:37:03 +09:00
/ * *
* Get the historical averaged block fee rate percentiles
* /
public async $getHistoricalBlockFeeRates ( div : number , interval : string | null ) : Promise < any > {
try {
2022-04-15 00:21:38 +09:00
let query = ` SELECT
2022-05-20 09:44:29 +02:00
CAST ( AVG ( height ) as INT ) as avgHeight ,
2022-04-15 00:21:38 +09:00
CAST ( AVG ( UNIX_TIMESTAMP ( blockTimestamp ) ) as INT ) as timestamp ,
2022-05-20 09:44:29 +02:00
CAST ( AVG ( JSON_EXTRACT ( fee_span , '$[0]' ) ) as INT ) as avgFee_0 ,
CAST ( AVG ( JSON_EXTRACT ( fee_span , '$[1]' ) ) as INT ) as avgFee_10 ,
CAST ( AVG ( JSON_EXTRACT ( fee_span , '$[2]' ) ) as INT ) as avgFee_25 ,
CAST ( AVG ( JSON_EXTRACT ( fee_span , '$[3]' ) ) as INT ) as avgFee_50 ,
CAST ( AVG ( JSON_EXTRACT ( fee_span , '$[4]' ) ) as INT ) as avgFee_75 ,
CAST ( AVG ( JSON_EXTRACT ( fee_span , '$[5]' ) ) as INT ) as avgFee_90 ,
CAST ( AVG ( JSON_EXTRACT ( fee_span , '$[6]' ) ) as INT ) as avgFee_100
2022-04-14 15:37:03 +09:00
FROM blocks ` ;
if ( interval !== null ) {
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${ interval } ) AND NOW() ` ;
}
query += ` GROUP BY UNIX_TIMESTAMP(blockTimestamp) DIV ${ div } ` ;
2022-04-15 18:05:58 +09:00
const [ rows ] : any = await DB . query ( query ) ;
2022-04-14 15:37:03 +09:00
return rows ;
} catch ( e ) {
logger . err ( 'Cannot generate block fee rates history. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-05-03 15:44:01 +09:00
/ * *
* Get the historical averaged block sizes
* /
public async $getHistoricalBlockSizes ( div : number , interval : string | null ) : Promise < any > {
try {
let query = ` SELECT
2022-05-20 09:44:29 +02:00
CAST ( AVG ( height ) as INT ) as avgHeight ,
2022-05-03 15:44:01 +09:00
CAST ( AVG ( UNIX_TIMESTAMP ( blockTimestamp ) ) as INT ) as timestamp ,
2022-05-20 09:44:29 +02:00
CAST ( AVG ( size ) as INT ) as avgSize
2022-05-03 15:44:01 +09:00
FROM blocks ` ;
if ( interval !== null ) {
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${ interval } ) AND NOW() ` ;
}
query += ` GROUP BY UNIX_TIMESTAMP(blockTimestamp) DIV ${ div } ` ;
const [ rows ] : any = await DB . query ( query ) ;
return rows ;
} catch ( e ) {
logger . err ( 'Cannot generate block size and weight history. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
/ * *
* Get the historical averaged block weights
* /
public async $getHistoricalBlockWeights ( div : number , interval : string | null ) : Promise < any > {
try {
let query = ` SELECT
2022-05-20 09:44:29 +02:00
CAST ( AVG ( height ) as INT ) as avgHeight ,
2022-05-03 15:44:01 +09:00
CAST ( AVG ( UNIX_TIMESTAMP ( blockTimestamp ) ) as INT ) as timestamp ,
2022-05-20 09:44:29 +02:00
CAST ( AVG ( weight ) as INT ) as avgWeight
2022-05-03 15:44:01 +09:00
FROM blocks ` ;
if ( interval !== null ) {
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${ interval } ) AND NOW() ` ;
}
query += ` GROUP BY UNIX_TIMESTAMP(blockTimestamp) DIV ${ div } ` ;
const [ rows ] : any = await DB . query ( query ) ;
return rows ;
} catch ( e ) {
logger . err ( 'Cannot generate block size and weight history. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-06-18 16:48:02 +02:00
/ * *
* Get a list of blocks that have been indexed
* /
public async $getIndexedBlocks ( ) : Promise < any [ ] > {
try {
const [ rows ] : any = await DB . query ( ` SELECT height, hash FROM blocks ORDER BY height DESC ` ) ;
return rows ;
} catch ( e ) {
logger . err ( 'Cannot generate block size and weight history. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-07-09 12:05:36 +02:00
2022-11-27 13:46:23 +09:00
/ * *
* Get a list of blocks that have not had CPFP data indexed
* /
public async $getCPFPUnindexedBlocks ( ) : Promise < any [ ] > {
try {
2023-01-05 13:02:53 -06:00
const blockchainInfo = await bitcoinClient . getBlockchainInfo ( ) ;
const currentBlockHeight = blockchainInfo . blocks ;
2023-01-10 17:15:55 -06:00
let indexingBlockAmount = Math . min ( config . MEMPOOL . INDEXING_BLOCKS_AMOUNT , currentBlockHeight ) ;
2023-01-05 13:02:53 -06:00
if ( indexingBlockAmount <= - 1 ) {
indexingBlockAmount = currentBlockHeight + 1 ;
}
2023-01-10 17:15:55 -06:00
const minHeight = Math . max ( 0 , currentBlockHeight - indexingBlockAmount + 1 ) ;
2023-01-05 13:02:53 -06:00
2023-01-10 17:15:55 -06:00
const [ rows ] : any [ ] = await DB . query ( `
SELECT height
FROM compact_cpfp_clusters
WHERE height <= ? AND height >= ?
2023-03-07 21:01:54 -06:00
GROUP BY height
2023-01-10 17:15:55 -06:00
ORDER BY height DESC ;
` , [currentBlockHeight, minHeight]);
const indexedHeights = { } ;
rows . forEach ( ( row ) = > { indexedHeights [ row . height ] = true ; } ) ;
const allHeights : number [ ] = Array . from ( Array ( currentBlockHeight - minHeight + 1 ) . keys ( ) , n = > n + minHeight ) . reverse ( ) ;
const unindexedHeights = allHeights . filter ( x = > ! indexedHeights [ x ] ) ;
return unindexedHeights ;
2022-11-27 13:46:23 +09:00
} catch ( e ) {
logger . err ( 'Cannot fetch CPFP unindexed blocks. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-07-09 12:05:36 +02:00
/ * *
2022-07-09 13:14:09 +02:00
* Return the oldest block from a consecutive chain of block from the most recent one
2022-07-09 12:05:36 +02:00
* /
2022-07-09 13:14:09 +02:00
public async $getOldestConsecutiveBlock ( ) : Promise < any > {
2022-07-09 12:05:36 +02:00
try {
2022-07-09 13:14:09 +02:00
const [ rows ] : any = await DB . query ( ` SELECT height, UNIX_TIMESTAMP(blockTimestamp) as timestamp, difficulty FROM blocks ORDER BY height DESC ` ) ;
2022-07-09 12:05:36 +02:00
for ( let i = 0 ; i < rows . length - 1 ; ++ i ) {
if ( rows [ i ] . height - rows [ i + 1 ] . height > 1 ) {
2022-07-09 13:14:09 +02:00
return rows [ i ] ;
2022-07-09 12:05:36 +02:00
}
}
2022-07-09 13:14:09 +02:00
return rows [ rows . length - 1 ] ;
2022-07-09 12:05:36 +02:00
} catch ( e ) {
logger . err ( 'Cannot generate block size and weight history. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2022-07-09 16:53:29 +02:00
/ * *
* Get all blocks which have not be linked to a price yet
* /
public async $getBlocksWithoutPrice ( ) : Promise < object [ ] > {
try {
const [ rows ] : any [ ] = await DB . query ( `
SELECT UNIX_TIMESTAMP ( blocks . blockTimestamp ) as timestamp , blocks . height
FROM blocks
LEFT JOIN blocks_prices ON blocks . height = blocks_prices . height
WHERE blocks_prices . height IS NULL
ORDER BY blocks . height
` );
return rows ;
} catch ( e ) {
logger . err ( 'Cannot get blocks height and timestamp from the db. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
/ * *
* Save block price by batch
* /
public async $saveBlockPrices ( blockPrices : BlockPrice [ ] ) : Promise < void > {
try {
let query = ` INSERT INTO blocks_prices(height, price_id) VALUES ` ;
for ( const price of blockPrices ) {
2023-02-17 21:21:21 +09:00
query += ` ( ${ price . height } , ${ price . priceId } ), ` ;
2022-07-09 16:53:29 +02:00
}
query = query . slice ( 0 , - 1 ) ;
await DB . query ( query ) ;
} catch ( e : any ) {
if ( e . errno === 1062 ) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
logger . debug ( ` Cannot save blocks prices for blocks [ ${ blockPrices [ 0 ] . height } to ${ blockPrices [ blockPrices . length - 1 ] . height } ] because it has already been indexed, ignoring ` ) ;
} else {
logger . err ( ` Cannot save blocks prices for blocks [ ${ blockPrices [ 0 ] . height } to ${ blockPrices [ blockPrices . length - 1 ] . height } ] into db. Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
}
2023-02-17 21:21:21 +09:00
/ * *
* Get all indexed blocsk with missing coinstatsindex data
* /
public async $getBlocksMissingCoinStatsIndex ( maxHeight : number , minHeight : number ) : Promise < any > {
try {
const [ blocks ] = await DB . query ( `
SELECT height , hash
FROM blocks
WHERE height >= $ { minHeight } AND height <= $ { maxHeight } AND
( utxoset_size IS NULL OR total_input_amt IS NULL )
` );
return blocks ;
} catch ( e ) {
logger . err ( ` Cannot get blocks with missing coinstatsindex. Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2023-02-18 11:26:13 +09:00
/ * *
* Save indexed median fee to avoid recomputing it later
*
* @param id
* @param feePercentiles
* /
public async $saveFeePercentilesForBlockId ( id : string , feePercentiles : number [ ] ) : Promise < void > {
try {
await DB . query ( `
UPDATE blocks SET fee_percentiles = ? , median_fee_amt = ?
WHERE hash = ? ` ,
[ JSON . stringify ( feePercentiles ) , feePercentiles [ 3 ] , id ]
) ;
} catch ( e ) {
logger . err ( ` Cannot update block fee_percentiles. Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2023-02-27 18:00:00 +09:00
2023-03-12 11:09:11 +09:00
/ * *
* Save indexed effective fee statistics
*
* @param id
* @param feeStats
* /
public async $saveEffectiveFeeStats ( id : string , feeStats : EffectiveFeeStats ) : Promise < void > {
try {
await DB . query ( `
UPDATE blocks SET median_fee = ? , fee_span = ?
WHERE hash = ? ` ,
[ feeStats . medianFee , JSON . stringify ( feeStats . feeRange ) , id ]
) ;
} catch ( e ) {
logger . err ( ` Cannot update block fee stats. Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
2023-02-27 18:00:00 +09:00
/ * *
* Convert a mysql row block into a BlockExtended . Note that you
* must provide the correct field into dbBlk object param
*
* @param dbBlk
* /
private async formatDbBlockIntoExtendedBlock ( dbBlk : any ) : Promise < BlockExtended > {
const blk : Partial < BlockExtended > = { } ;
const extras : Partial < BlockExtension > = { } ;
// IEsploraApi.Block
blk . id = dbBlk . id ;
blk . height = dbBlk . height ;
blk . version = dbBlk . version ;
blk . timestamp = dbBlk . timestamp ;
blk . bits = dbBlk . bits ;
blk . nonce = dbBlk . nonce ;
blk . difficulty = dbBlk . difficulty ;
blk . merkle_root = dbBlk . merkle_root ;
blk . tx_count = dbBlk . tx_count ;
blk . size = dbBlk . size ;
blk . weight = dbBlk . weight ;
blk . previousblockhash = dbBlk . previousblockhash ;
blk . mediantime = dbBlk . mediantime ;
// BlockExtension
extras . totalFees = dbBlk . totalFees ;
extras . medianFee = dbBlk . medianFee ;
extras . feeRange = JSON . parse ( dbBlk . feeRange ) ;
extras . reward = dbBlk . reward ;
extras . pool = {
id : dbBlk.poolId ,
name : dbBlk.poolName ,
slug : dbBlk.poolSlug ,
} ;
extras . avgFee = dbBlk . avgFee ;
extras . avgFeeRate = dbBlk . avgFeeRate ;
extras . coinbaseRaw = dbBlk . coinbaseRaw ;
extras . coinbaseAddress = dbBlk . coinbaseAddress ;
extras . coinbaseSignature = dbBlk . coinbaseSignature ;
extras . coinbaseSignatureAscii = dbBlk . coinbaseSignatureAscii ;
extras . avgTxSize = dbBlk . avgTxSize ;
extras . totalInputs = dbBlk . totalInputs ;
extras . totalOutputs = dbBlk . totalOutputs ;
extras . totalOutputAmt = dbBlk . totalOutputAmt ;
extras . medianFeeAmt = dbBlk . medianFeeAmt ;
extras . feePercentiles = JSON . parse ( dbBlk . feePercentiles ) ;
extras . segwitTotalTxs = dbBlk . segwitTotalTxs ;
extras . segwitTotalSize = dbBlk . segwitTotalSize ;
extras . segwitTotalWeight = dbBlk . segwitTotalWeight ;
extras . header = dbBlk . header ,
extras . utxoSetChange = dbBlk . utxoSetChange ;
extras . utxoSetSize = dbBlk . utxoSetSize ;
extras . totalInputAmt = dbBlk . totalInputAmt ;
extras . virtualSize = dbBlk . weight / 4.0 ;
// Re-org can happen after indexing so we need to always get the
// latest state from core
extras . orphans = chainTips . getOrphanedBlocksAtHeight ( dbBlk . height ) ;
2023-02-27 18:39:02 +09:00
// Match rate is not part of the blocks table, but it is part of APIs so we must include it
extras . matchRate = null ;
if ( config . MEMPOOL . AUDIT ) {
const auditScore = await BlocksAuditsRepository . $getBlockAuditScore ( dbBlk . id ) ;
if ( auditScore != null ) {
extras . matchRate = auditScore . matchRate ;
}
}
2023-02-27 18:00:00 +09:00
// If we're missing block summary related field, check if we can populate them on the fly now
2023-03-19 10:46:38 +09:00
// This is for example triggered upon re-org
2023-02-27 18:00:00 +09:00
if ( Common . blocksSummariesIndexingEnabled ( ) &&
( extras . medianFeeAmt === null || extras . feePercentiles === null ) )
{
extras . feePercentiles = await BlocksSummariesRepository . $getFeePercentilesByBlockId ( dbBlk . id ) ;
if ( extras . feePercentiles === null ) {
const block = await bitcoinClient . getBlock ( dbBlk . id , 2 ) ;
const summary = blocks . summarizeBlock ( block ) ;
2023-03-31 12:08:05 +09:00
await BlocksSummariesRepository . $saveTransactions ( dbBlk . height , dbBlk . hash , summary . transactions ) ;
2023-02-27 18:00:00 +09:00
extras . feePercentiles = await BlocksSummariesRepository . $getFeePercentilesByBlockId ( dbBlk . id ) ;
}
if ( extras . feePercentiles !== null ) {
extras . medianFeeAmt = extras . feePercentiles [ 3 ] ;
}
}
blk . extras = < BlockExtension > extras ;
return < BlockExtended > blk ;
}
2022-01-05 15:41:14 +09:00
}
2022-02-08 18:28:53 +09:00
export default new BlocksRepository ( ) ;