2022-07-09 16:53:29 +02:00
import { BlockExtended , BlockPrice } 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-16 17:00:06 +01:00
import { prepareBlock } from '../utils/blocks-utils' ;
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' ;
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 ) {
2022-01-05 15:41:14 +09:00
try {
const query = ` INSERT INTO blocks(
2022-03-09 19:18:51 +01:00
height , hash , blockTimestamp , size ,
weight , tx_count , coinbase_raw , difficulty ,
pool_id , fees , fee_span , median_fee ,
reward , version , bits , nonce ,
2022-03-10 14:21:11 +01:00
merkle_root , previous_block_hash , avg_fee , avg_fee_rate
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
? , ? , ? , ? ,
2022-03-10 14:21:11 +01:00
? , ? , ? , ?
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 ,
2022-02-16 15:19:16 +09:00
block . extras . pool ? . id , // Should always be set to something
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 ,
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
2022-02-05 15:50:57 +09:00
logger . debug ( ` $ saveBlockInDatabase() - Block ${ block . height } has already been indexed, ignoring ` ) ;
} else {
2022-04-13 16:29:52 +09:00
logger . err ( 'Cannot save indexed block into db. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
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
}
}
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 ;
}
}
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
* /
2022-04-12 15:15:57 +09:00
public async $getBlocksByPool ( slug : string , startHeight? : number ) : Promise < object [ ] > {
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 [ ] = [ ] ;
2022-05-27 20:43:14 +02:00
let query = ` SELECT
2022-06-06 10:14:40 +02:00
blocks . height ,
2022-05-27 20:43:14 +02:00
hash as id ,
UNIX_TIMESTAMP ( blocks . blockTimestamp ) as blockTimestamp ,
size ,
weight ,
tx_count ,
coinbase_raw ,
difficulty ,
fees ,
fee_span ,
median_fee ,
reward ,
version ,
bits ,
nonce ,
merkle_root ,
previous_block_hash as previousblockhash ,
avg_fee ,
2022-07-11 23:16:48 +02:00
avg_fee_rate
2022-02-08 18:28:53 +09:00
FROM blocks
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 {
2022-04-12 15:15:57 +09:00
const [ rows ] = await DB . query ( query , params ) ;
2022-02-08 18:28:53 +09:00
2022-03-16 17:00:06 +01:00
const blocks : BlockExtended [ ] = [ ] ;
2022-04-12 15:15:57 +09:00
for ( const block of < object [ ] > rows ) {
2022-03-16 17:00:06 +01:00
blocks . push ( prepareBlock ( 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
* /
2022-03-09 19:18:51 +01:00
public async $getBlockByHeight ( height : number ) : Promise < object | null > {
2022-03-06 16:44:09 +01:00
try {
2022-05-27 20:43:14 +02:00
const [ rows ] : any [ ] = await DB . query ( ` SELECT
2022-06-06 10:14:40 +02:00
blocks . height ,
2022-06-01 10:06:18 +02:00
hash ,
2022-05-27 20:43:14 +02:00
hash as id ,
UNIX_TIMESTAMP ( blocks . blockTimestamp ) as blockTimestamp ,
size ,
weight ,
tx_count ,
coinbase_raw ,
difficulty ,
pools . id as pool_id ,
pools . name as pool_name ,
pools . link as pool_link ,
pools . slug as pool_slug ,
pools . addresses as pool_addresses ,
pools . regexes as pool_regexes ,
fees ,
fee_span ,
median_fee ,
reward ,
version ,
bits ,
nonce ,
merkle_root ,
previous_block_hash as previousblockhash ,
avg_fee ,
2022-07-09 16:53:29 +02:00
avg_fee_rate
2022-03-06 16:44:09 +01:00
FROM blocks
JOIN pools ON blocks . pool_id = pools . id
2022-07-09 16:53:29 +02: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
2022-04-20 13:12:32 +09:00
rows [ 0 ] . fee_span = JSON . parse ( rows [ 0 ] . fee_span ) ;
2022-03-06 16:44:09 +01:00
return rows [ 0 ] ;
} 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
2022-04-20 13:12:32 +09:00
/ * *
* Get one block by hash
* /
public async $getBlockByHash ( hash : string ) : Promise < object | null > {
try {
const query = `
2022-06-06 10:14:40 +02:00
SELECT * , blocks . height , UNIX_TIMESTAMP ( blocks . blockTimestamp ) as blockTimestamp , hash as id ,
2022-04-20 13:12:32 +09:00
pools . id as pool_id , pools . name as pool_name , pools . link as pool_link , pools . slug as pool_slug ,
pools . addresses as pool_addresses , pools . regexes as pool_regexes ,
2022-07-11 23:16:48 +02:00
previous_block_hash as previousblockhash
2022-04-20 13:12:32 +09:00
FROM blocks
JOIN pools ON blocks . pool_id = pools . id
2022-07-12 19:10:57 +02:00
WHERE hash = ? ;
2022-04-20 13:12:32 +09:00
` ;
2022-07-11 23:16:48 +02:00
const [ rows ] : any [ ] = await DB . query ( query , [ hash ] ) ;
2022-04-20 13:12:32 +09:00
if ( rows . length <= 0 ) {
return null ;
}
rows [ 0 ] . fee_span = JSON . parse ( rows [ 0 ] . fee_span ) ;
return rows [ 0 ] ;
} catch ( e ) {
logger . err ( ` Cannot get indexed block ${ hash } . Reason: ` + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
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 ( ) ;
const [ blocks ] : any [ ] = await DB . query ( ` SELECT height, hash, previous_block_hash,
UNIX_TIMESTAMP ( blockTimestamp ) as timestamp FROM blocks ORDER BY height ` );
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 ) ;
2022-06-20 16:35:10 +02:00
await BlocksSummariesRepository . $deleteBlocksFrom ( blocks [ idx - 1 ] . height ) ;
2022-04-30 15:23:53 +09:00
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 ) {
logger . info ( ` Delete newer blocks from height ${ blockHeight } from the database ` ) ;
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 ,
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 ,
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 {
const [ rows ] : any = await DB . query ( ` SELECT height, hash FROM blocks WHERE cpfp_indexed = 0 ORDER BY height DESC ` ) ;
return rows ;
} catch ( e ) {
logger . err ( 'Cannot fetch CPFP unindexed blocks. Reason: ' + ( e instanceof Error ? e.message : e ) ) ;
throw e ;
}
}
public async $setCPFPIndexed ( hash : string ) : Promise < void > {
await DB . query ( ` UPDATE blocks SET cpfp_indexed = 1 WHERE hash = ? ` , [ hash ] ) ;
}
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 ) {
query += ` ( ${ price . height } , ${ price . priceId } ), `
}
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 ;
}
}
}
2022-01-05 15:41:14 +09:00
}
2022-02-08 18:28:53 +09:00
export default new BlocksRepository ( ) ;