2020-12-20 22:36:36 +07:00
import config from '../config' ;
import bitcoinApi from './bitcoin/bitcoin-api-factory' ;
2020-10-13 15:27:52 +07:00
import logger from '../logger' ;
2020-02-23 19:16:50 +07:00
import memPool from './mempool' ;
2020-12-30 01:47:07 +07:00
import { BlockExtended , TransactionExtended } 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-08-01 15:49:26 +03:00
import bitcoinBaseApi from './bitcoin/bitcoin-base.api' ;
2019-07-21 17:59:47 +03:00
class Blocks {
2020-12-28 04:47:22 +07:00
private blocks : BlockExtended [ ] = [ ] ;
2019-08-29 01:17:31 +02:00
private currentBlockHeight = 0 ;
2021-07-23 14:35:04 +03:00
private currentDifficulty = 0 ;
2020-09-21 19:41:12 +07:00
private lastDifficultyAdjustmentTime = 0 ;
2021-07-23 14:35:04 +03:00
private previousDifficultyRetarget = 0 ;
2020-12-28 04:47:22 +07:00
private newBlockCallbacks : ( ( block : BlockExtended , txIds : string [ ] , transactions : TransactionExtended [ ] ) = > void ) [ ] = [ ] ;
2019-08-29 01:17:31 +02:00
2020-02-16 22:15:07 +07:00
constructor ( ) { }
2019-07-21 17:59:47 +03:00
2020-12-28 04:47:22 +07:00
public getBlocks ( ) : BlockExtended [ ] {
2019-07-21 17:59:47 +03:00
return this . blocks ;
}
2020-12-28 04:47:22 +07:00
public setBlocks ( blocks : BlockExtended [ ] ) {
2020-02-29 21:52:04 +07:00
this . blocks = blocks ;
}
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
}
2020-10-18 21:47:47 +07:00
public async $updateBlocks() {
2020-12-21 23:08:34 +07:00
const blockHeightTip = await bitcoinApi . $getBlockHeightTip ( ) ;
2019-07-21 17:59:47 +03:00
2020-10-18 21:47:47 +07:00
if ( this . blocks . length === 0 ) {
2021-07-31 17:56:10 +03:00
this . currentBlockHeight = blockHeightTip - config . MEMPOOL . INITIAL_BLOCKS_AMOUNT ;
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 ;
2020-10-18 21:47:47 +07:00
}
if ( ! this . lastDifficultyAdjustmentTime ) {
2021-08-01 15:49:26 +03:00
const blockchainInfo = await bitcoinBaseApi . $getBlockchainInfo ( ) ;
if ( blockchainInfo . blocks === blockchainInfo . headers ) {
const heightDiff = blockHeightTip % 2016 ;
const blockHash = await bitcoinApi . $getBlockHash ( blockHeightTip - heightDiff ) ;
const block = await bitcoinApi . $getBlock ( blockHash ) ;
this . lastDifficultyAdjustmentTime = block . timestamp ;
this . currentDifficulty = block . difficulty ;
const previousPeriodBlockHash = await bitcoinApi . $getBlockHash ( blockHeightTip - heightDiff - 2016 ) ;
const previousPeriodBlock = await bitcoinApi . $getBlock ( previousPeriodBlockHash ) ;
this . previousDifficultyRetarget = ( block . difficulty - previousPeriodBlock . difficulty ) / previousPeriodBlock . difficulty * 100 ;
logger . debug ( ` Initial difficulty adjustment data set. ` ) ;
} 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 ) {
if ( this . currentBlockHeight === 0 ) {
this . currentBlockHeight = blockHeightTip ;
2019-07-21 17:59:47 +03:00
} else {
2020-10-18 21:47:47 +07:00
this . currentBlockHeight ++ ;
logger . debug ( ` New block found (# ${ this . currentBlockHeight } )! ` ) ;
2019-07-21 17:59:47 +03:00
}
2020-12-21 23:08:34 +07:00
const transactions : TransactionExtended [ ] = [ ] ;
2020-12-20 22:36:36 +07:00
2020-12-21 23:08:34 +07:00
const blockHash = await bitcoinApi . $getBlockHash ( this . currentBlockHeight ) ;
const block = await bitcoinApi . $getBlock ( blockHash ) ;
const txIds : string [ ] = await bitcoinApi . $getTxIdsForBlock ( blockHash ) ;
2020-03-03 15:11:14 +07:00
2020-10-18 21:47:47 +07:00
const mempool = memPool . getMempool ( ) ;
2020-12-28 20:17:32 +07:00
let transactionsFound = 0 ;
2019-07-21 17:59:47 +03:00
2020-10-18 21:47:47 +07:00
for ( let i = 0 ; i < txIds . length ; i ++ ) {
if ( mempool [ txIds [ i ] ] ) {
transactions . push ( mempool [ txIds [ i ] ] ) ;
2020-12-28 20:17:32 +07:00
transactionsFound ++ ;
2021-01-24 23:56:51 +07:00
} else if ( config . MEMPOOL . BACKEND === 'esplora' || memPool . isInSync ( ) || i === 0 ) {
2020-12-22 06:04:31 +07:00
logger . debug ( ` Fetching block tx ${ i } of ${ txIds . length } ` ) ;
2021-01-24 02:51:22 +07:00
try {
const tx = await transactionUtils . $getTransactionExtended ( txIds [ i ] ) ;
2020-12-22 06:04:31 +07:00
transactions . push ( tx ) ;
2021-01-24 02:51:22 +07:00
} catch ( e ) {
logger . debug ( 'Error fetching block tx: ' + e . message || e ) ;
2021-01-24 23:56:51 +07:00
if ( i === 0 ) {
throw new Error ( 'Failed to fetch Coinbase transaction: ' + txIds [ i ] ) ;
}
2020-02-23 19:16:50 +07:00
}
}
2020-10-18 21:47:47 +07:00
}
2020-02-23 19:16:50 +07:00
2021-03-18 23:47:40 +07:00
transactions . forEach ( ( tx ) = > {
if ( ! tx . cpfpChecked ) {
Common . setRelativesAndGetCpfpInfo ( tx , mempool ) ;
}
} ) ;
2020-12-28 20:17:32 +07:00
logger . debug ( ` ${ transactionsFound } of ${ txIds . length } found in mempool. ${ txIds . length - transactionsFound } not found. ` ) ;
2020-02-16 22:15:07 +07:00
2020-12-28 04:47:22 +07:00
const blockExtended : BlockExtended = Object . assign ( { } , block ) ;
blockExtended . reward = transactions [ 0 ] . vout . reduce ( ( acc , curr ) = > acc + curr . value , 0 ) ;
2020-12-28 20:17:32 +07:00
blockExtended . coinbaseTx = transactionUtils . stripCoinbaseTransaction ( transactions [ 0 ] ) ;
2021-03-18 23:47:40 +07:00
transactions . shift ( ) ;
transactions . sort ( ( a , b ) = > b . effectiveFeePerVsize - a . effectiveFeePerVsize ) ;
blockExtended . medianFee = transactions . length > 1 ? Common . median ( transactions . map ( ( tx ) = > tx . effectiveFeePerVsize ) ) : 0 ;
blockExtended . feeRange = transactions . length > 1 ? Common . getFeesInRange ( transactions , 8 ) : [ 0 , 0 ] ;
2020-09-21 19:41:12 +07:00
2020-10-18 21:47:47 +07:00
if ( block . height % 2016 === 0 ) {
2021-07-23 14:35:04 +03:00
this . previousDifficultyRetarget = ( block . difficulty - this . currentDifficulty ) / this . currentDifficulty * 100 ;
2020-10-18 21:47:47 +07:00
this . lastDifficultyAdjustmentTime = block . timestamp ;
2021-07-23 14:35:04 +03:00
this . currentDifficulty = block . difficulty ;
2020-10-18 21:47:47 +07:00
}
2019-07-21 17:59:47 +03:00
2020-12-28 04:47:22 +07:00
this . blocks . push ( blockExtended ) ;
2021-07-31 17:56:10 +03:00
if ( this . blocks . length > config . MEMPOOL . INITIAL_BLOCKS_AMOUNT * 4 ) {
this . blocks = this . blocks . slice ( - config . MEMPOOL . INITIAL_BLOCKS_AMOUNT * 4 ) ;
2019-07-21 17:59:47 +03:00
}
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
}
2021-01-22 23:20:39 +07:00
if ( memPool . isInSync ( ) ) {
diskCache . $saveCacheToDisk ( ) ;
}
2019-08-29 01:17:31 +02:00
}
}
2020-05-24 22:45:45 +07:00
2020-09-21 19:41:12 +07:00
public getLastDifficultyAdjustmentTime ( ) : number {
return this . lastDifficultyAdjustmentTime ;
}
2021-07-23 14:35:04 +03:00
public getPreviousDifficultyRetarget ( ) : number {
return this . previousDifficultyRetarget ;
}
2020-12-21 23:08:34 +07:00
public getCurrentBlockHeight ( ) : number {
return this . currentBlockHeight ;
}
2019-07-21 17:59:47 +03:00
}
export default new Blocks ( ) ;