2020-10-19 11:57:02 +07:00
import config from '../config' ;
2023-05-30 19:35:39 -04:00
import bitcoinApi from './bitcoin/bitcoin-api-factory' ;
2023-02-17 17:54:29 -06:00
import { MempoolTransactionExtended , TransactionExtended , VbytesPerSecond , GbtCandidates } from '../mempool.interfaces' ;
2020-10-13 15:27:52 +07:00
import logger from '../logger' ;
2020-09-26 02:11:30 +07:00
import { Common } from './common' ;
2020-12-21 23:08:34 +07:00
import transactionUtils from './transaction-utils' ;
2020-12-28 04:47:22 +07:00
import { IBitcoinApi } from './bitcoin/bitcoin-api.interface' ;
2021-01-05 18:57:06 +07:00
import loadingIndicators from './loading-indicators' ;
2021-09-15 01:47:24 +04:00
import bitcoinClient from './bitcoin/bitcoin-client' ;
import bitcoinSecondClient from './bitcoin/bitcoin-second-client' ;
2022-03-08 14:49:25 +01:00
import rbfCache from './rbf-cache' ;
2023-12-06 12:09:28 +00:00
import { Acceleration } from './services/acceleration' ;
2023-05-12 16:29:45 -06:00
import redisCache from './redis-cache' ;
2023-02-17 17:54:29 -06:00
import blocks from './blocks' ;
2019-07-21 17:59:47 +03:00
class Mempool {
2020-04-01 20:06:44 +07:00
private inSync : boolean = false ;
2022-02-11 21:25:58 +09:00
private mempoolCacheDelta : number = - 1 ;
2023-05-29 15:56:29 -04:00
private mempoolCache : { [ txId : string ] : MempoolTransactionExtended } = { } ;
2023-02-17 17:54:29 -06:00
private mempoolCandidates : { [ txid : string ] : boolean } = { } ;
2023-05-29 15:56:29 -04:00
private spendMap = new Map < string , MempoolTransactionExtended > ( ) ;
2024-08-26 21:51:49 +00:00
private recentlyDeleted : MempoolTransactionExtended [ ] [ ] = [ ] ; // buffer of transactions deleted in recent mempool updates
2022-02-13 13:52:04 +01:00
private mempoolInfo : IBitcoinApi.MempoolInfo = { loaded : false , size : 0 , bytes : 0 , usage : 0 , total_fee : 0 ,
2023-12-01 18:12:59 +09:00
maxmempool : 300000000 , mempoolminfee : Common.isLiquid ( ) ? 0.00000100 : 0.00001000 , minrelaytxfee : Common.isLiquid ( ) ? 0.00000100 : 0.00001000 } ;
2023-05-29 15:56:29 -04:00
private mempoolChangedCallback : ( ( newMempool : { [ txId : string ] : MempoolTransactionExtended ; } , newTransactions : MempoolTransactionExtended [ ] ,
2024-08-26 21:51:49 +00:00
deletedTransactions : MempoolTransactionExtended [ ] [ ] , accelerationDelta : string [ ] ) = > void ) | undefined ;
2023-06-28 19:51:52 -04:00
private $asyncMempoolChangedCallback : ( ( newMempool : { [ txId : string ] : MempoolTransactionExtended ; } , mempoolSize : number , newTransactions : MempoolTransactionExtended [ ] ,
2024-08-26 21:51:49 +00:00
deletedTransactions : MempoolTransactionExtended [ ] [ ] , accelerationDelta : string [ ] , candidates? : GbtCandidates ) = > Promise < void > ) | undefined ;
2019-07-21 17:59:47 +03:00
2023-06-13 17:03:36 -04:00
private accelerations : { [ txId : string ] : Acceleration } = { } ;
2024-02-02 02:25:49 +00:00
private accelerationPositions : { [ txid : string ] : { poolId : number , pool : string , block : number , vsize : number } [ ] } = { } ;
2023-05-26 21:10:32 -04:00
2019-07-21 17:59:47 +03:00
private txPerSecondArray : number [ ] = [ ] ;
private txPerSecond : number = 0 ;
2020-06-10 23:52:14 +07:00
private vBytesPerSecondArray : VbytesPerSecond [ ] = [ ] ;
2019-07-21 17:59:47 +03:00
private vBytesPerSecond : number = 0 ;
2020-06-18 13:54:54 +07:00
private mempoolProtection = 0 ;
2020-09-26 02:11:30 +07:00
private latestTransactions : any [ ] = [ ] ;
2019-07-21 17:59:47 +03:00
2023-03-09 17:45:08 +09:00
private ESPLORA_MISSING_TX_WARNING_THRESHOLD = 100 ;
private SAMPLE_TIME = 10000 ; // In ms
private timer = new Date ( ) . getTime ( ) ;
private missingTxCount = 0 ;
2023-04-27 10:19:12 +09:00
private mainLoopTimeout : number = 120000 ;
2023-02-17 17:54:29 -06:00
public limitGBT = config . MEMPOOL . USE_SECOND_NODE_FOR_MINFEE && config . MEMPOOL . LIMIT_GBT ;
2019-07-21 17:59:47 +03:00
constructor ( ) {
setInterval ( this . updateTxPerSecond . bind ( this ) , 1000 ) ;
}
2022-02-11 21:25:58 +09:00
/ * *
* Return true if we should leave resources available for mempool tx caching
* /
public hasPriority ( ) : boolean {
if ( this . inSync ) {
return false ;
} else {
return this . mempoolCacheDelta == - 1 || this . mempoolCacheDelta > 25 ;
}
}
2021-01-20 17:16:43 +07:00
public isInSync ( ) : boolean {
2020-04-01 20:06:44 +07:00
return this . inSync ;
}
2021-01-20 17:16:43 +07:00
public setOutOfSync ( ) : void {
this . inSync = false ;
loadingIndicators . setProgress ( 'mempool' , 99 ) ;
}
2020-09-26 02:11:30 +07:00
public getLatestTransactions() {
return this . latestTransactions ;
}
2023-05-29 15:56:29 -04:00
public setMempoolChangedCallback ( fn : ( newMempool : { [ txId : string ] : MempoolTransactionExtended ; } ,
2024-08-26 21:51:49 +00:00
newTransactions : MempoolTransactionExtended [ ] , deletedTransactions : MempoolTransactionExtended [ ] [ ] , accelerationDelta : string [ ] ) = > void ) : void {
2019-07-21 17:59:47 +03:00
this . mempoolChangedCallback = fn ;
}
2023-06-28 19:51:52 -04:00
public setAsyncMempoolChangedCallback ( fn : ( newMempool : { [ txId : string ] : MempoolTransactionExtended ; } , mempoolSize : number ,
2024-08-26 21:51:49 +00:00
newTransactions : MempoolTransactionExtended [ ] , deletedTransactions : MempoolTransactionExtended [ ] [ ] , accelerationDelta : string [ ] ,
2023-02-17 17:54:29 -06:00
candidates? : GbtCandidates ) = > Promise < void > ) : void {
2023-04-30 15:28:34 -06:00
this . $asyncMempoolChangedCallback = fn ;
2022-11-16 18:18:59 -06:00
}
2023-05-29 15:56:29 -04:00
public getMempool ( ) : { [ txid : string ] : MempoolTransactionExtended } {
2020-02-16 22:15:07 +07:00
return this . mempoolCache ;
2019-07-21 17:59:47 +03:00
}
2023-05-29 15:56:29 -04:00
public getSpendMap ( ) : Map < string , MempoolTransactionExtended > {
2023-05-18 09:51:41 -04:00
return this . spendMap ;
}
2024-01-08 00:56:48 +00:00
public getFromSpendMap ( txid , index ) : MempoolTransactionExtended | void {
return this . spendMap . get ( ` ${ txid } : ${ index } ` ) ;
}
2023-05-29 15:56:29 -04:00
public async $setMempool ( mempoolData : { [ txId : string ] : MempoolTransactionExtended } ) {
2020-02-16 22:15:07 +07:00
this . mempoolCache = mempoolData ;
2023-06-28 19:51:52 -04:00
let count = 0 ;
2023-06-16 18:56:34 -04:00
const redisTimer = Date . now ( ) ;
if ( config . MEMPOOL . CACHE_ENABLED && config . REDIS . ENABLED ) {
logger . debug ( ` Migrating ${ Object . keys ( this . mempoolCache ) . length } transactions from disk cache to Redis cache ` ) ;
}
2023-05-29 15:56:29 -04:00
for ( const txid of Object . keys ( this . mempoolCache ) ) {
2023-09-16 13:02:47 +00:00
if ( ! this . mempoolCache [ txid ] . adjustedVsize || this . mempoolCache [ txid ] . sigops == null || this . mempoolCache [ txid ] . effectiveFeePerVsize == null ) {
2023-05-29 15:56:29 -04:00
this . mempoolCache [ txid ] = transactionUtils . extendMempoolTransaction ( this . mempoolCache [ txid ] ) ;
}
2023-07-02 20:05:30 -04:00
if ( this . mempoolCache [ txid ] . order == null ) {
this . mempoolCache [ txid ] . order = transactionUtils . txidToOrdering ( txid ) ;
}
2023-12-05 06:54:31 +00:00
for ( const vin of this . mempoolCache [ txid ] . vin ) {
transactionUtils . addInnerScriptsToVin ( vin ) ;
}
2023-06-28 19:51:52 -04:00
count ++ ;
2023-06-16 18:56:34 -04:00
if ( config . MEMPOOL . CACHE_ENABLED && config . REDIS . ENABLED ) {
await redisCache . $addTransaction ( this . mempoolCache [ txid ] ) ;
}
2024-03-08 00:48:36 +00:00
this . mempoolCache [ txid ] . flags = Common . getTransactionFlags ( this . mempoolCache [ txid ] ) ;
2024-01-08 00:56:48 +00:00
this . mempoolCache [ txid ] . cpfpChecked = false ;
this . mempoolCache [ txid ] . cpfpDirty = true ;
this . mempoolCache [ txid ] . cpfpUpdated = undefined ;
2023-06-16 18:56:34 -04:00
}
if ( config . MEMPOOL . CACHE_ENABLED && config . REDIS . ENABLED ) {
await redisCache . $flushTransactions ( ) ;
logger . debug ( ` Finished migrating cache transactions in ${ ( ( Date . now ( ) - redisTimer ) / 1000 ) . toFixed ( 2 ) } seconds ` ) ;
2023-05-29 15:56:29 -04:00
}
2020-07-03 23:45:19 +07:00
if ( this . mempoolChangedCallback ) {
2023-05-30 19:35:39 -04:00
this . mempoolChangedCallback ( this . mempoolCache , [ ] , [ ] , [ ] ) ;
2020-07-03 23:45:19 +07:00
}
2023-04-30 15:28:34 -06:00
if ( this . $asyncMempoolChangedCallback ) {
2023-02-17 17:54:29 -06:00
await this . $asyncMempoolChangedCallback ( this . mempoolCache , count , [ ] , [ ] , [ ] , this . limitGBT ? { txs : { } , added : [ ] , removed : [ ] } : undefined ) ;
2022-11-16 18:18:59 -06:00
}
2023-05-18 09:51:41 -04:00
this . addToSpendMap ( Object . values ( this . mempoolCache ) ) ;
2019-07-21 17:59:47 +03:00
}
2023-07-24 17:22:38 +09:00
public async $reloadMempool ( expectedCount : number ) : Promise < MempoolTransactionExtended [ ] > {
2023-07-24 14:59:51 +09:00
let count = 0 ;
let done = false ;
let last_txid ;
2023-07-24 17:22:38 +09:00
const newTransactions : MempoolTransactionExtended [ ] = [ ] ;
2023-07-24 14:59:51 +09:00
loadingIndicators . setProgress ( 'mempool' , count / expectedCount * 100 ) ;
while ( ! done ) {
try {
2023-11-15 06:57:31 +00:00
const result = await bitcoinApi . $getAllMempoolTransactions ( last_txid , config . ESPLORA . BATCH_QUERY_BASE_SIZE ) ;
2023-07-24 14:59:51 +09:00
if ( result ) {
for ( const tx of result ) {
const extendedTransaction = transactionUtils . extendMempoolTransaction ( tx ) ;
2023-07-24 17:22:38 +09:00
if ( ! this . mempoolCache [ extendedTransaction . txid ] ) {
newTransactions . push ( extendedTransaction ) ;
this . mempoolCache [ extendedTransaction . txid ] = extendedTransaction ;
}
2023-07-24 14:59:51 +09:00
count ++ ;
}
logger . info ( ` Fetched ${ count } of ${ expectedCount } mempool transactions from esplora ` ) ;
if ( result . length > 0 ) {
last_txid = result [ result . length - 1 ] . txid ;
} else {
done = true ;
}
2023-07-24 17:49:34 +09:00
if ( Math . floor ( ( count / expectedCount ) * 100 ) < 100 ) {
2023-07-24 14:59:51 +09:00
loadingIndicators . setProgress ( 'mempool' , count / expectedCount * 100 ) ;
}
} else {
done = true ;
}
} catch ( err ) {
logger . err ( 'failed to fetch bulk mempool transactions from esplora' ) ;
}
2023-07-22 14:09:11 +09:00
}
logger . info ( ` Done inserting loaded mempool transactions into local cache ` ) ;
2023-07-25 16:35:21 +09:00
return newTransactions ;
2023-07-22 14:09:11 +09:00
}
2023-02-17 17:54:29 -06:00
public getMempoolCandidates ( ) : { [ txid : string ] : boolean } {
return this . mempoolCandidates ;
}
2020-10-18 21:47:47 +07:00
public async $updateMemPoolInfo() {
2021-09-15 01:47:24 +04:00
this . mempoolInfo = await this . $getMempoolInfo ( ) ;
2020-02-17 20:39:20 +07:00
}
2021-02-24 12:26:55 +07:00
public getMempoolInfo ( ) : IBitcoinApi . MempoolInfo {
2019-07-21 17:59:47 +03:00
return this . mempoolInfo ;
}
public getTxPerSecond ( ) : number {
return this . txPerSecond ;
}
public getVBytesPerSecond ( ) : number {
return this . vBytesPerSecond ;
}
2020-02-28 01:09:07 +07:00
public getFirstSeenForTransactions ( txIds : string [ ] ) : number [ ] {
const txTimes : number [ ] = [ ] ;
txIds . forEach ( ( txId : string ) = > {
2020-12-28 04:47:22 +07:00
const tx = this . mempoolCache [ txId ] ;
if ( tx && tx . firstSeen ) {
txTimes . push ( tx . firstSeen ) ;
2020-02-28 01:09:07 +07:00
} else {
txTimes . push ( 0 ) ;
}
} ) ;
return txTimes ;
}
2023-02-17 17:54:29 -06:00
public async $updateMempool ( transactions : string [ ] , accelerations : Acceleration [ ] | null , minFeeMempool : string [ ] , minFeeTip : number , pollRate : number ) : Promise < void > {
2022-08-19 17:54:52 +04:00
logger . debug ( ` Updating mempool... ` ) ;
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 ( ) ;
2019-07-21 17:59:47 +03:00
const start = new Date ( ) . getTime ( ) ;
let hasChange : boolean = false ;
2020-06-18 13:54:54 +07:00
const currentMempoolSize = Object . keys ( this . mempoolCache ) . length ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , 'got raw mempool' ) ;
2020-10-18 21:47:47 +07:00
const diff = transactions . length - currentMempoolSize ;
2023-07-24 17:22:38 +09:00
let newTransactions : MempoolTransactionExtended [ ] = [ ] ;
2020-10-18 21:47:47 +07:00
2022-02-11 21:25:58 +09:00
this . mempoolCacheDelta = Math . abs ( diff ) ;
2021-01-05 18:57:06 +07:00
if ( ! this . inSync ) {
2023-05-07 18:47:56 +04:00
loadingIndicators . setProgress ( 'mempool' , currentMempoolSize / transactions . length * 100 ) ;
2021-01-05 18:57:06 +07:00
}
2023-03-09 17:45:08 +09:00
// https://github.com/mempool/mempool/issues/3283
const logEsplora404 = ( missingTxCount , threshold , time ) = > {
const log = ` In the past ${ time / 1000 } seconds, esplora tx API replied ${ missingTxCount } times with a 404 error code while updating nodejs backend mempool ` ;
if ( missingTxCount >= threshold ) {
logger . warn ( log ) ;
} else if ( missingTxCount > 0 ) {
logger . debug ( log ) ;
}
} ;
2023-07-02 18:10:02 +02:00
let intervalTimer = Date . now ( ) ;
2023-07-22 14:09:11 +09:00
let loaded = false ;
if ( config . MEMPOOL . BACKEND === 'esplora' && currentMempoolSize < transactions . length * 0.5 && transactions . length > 20 _000 ) {
2023-07-24 16:19:19 +09:00
this . inSync = false ;
2023-07-22 14:09:11 +09:00
logger . info ( ` Missing ${ transactions . length - currentMempoolSize } mempool transactions, attempting to reload in bulk from esplora ` ) ;
try {
2023-07-24 17:22:38 +09:00
newTransactions = await this . $reloadMempool ( transactions . length ) ;
2023-07-25 16:35:21 +09:00
if ( config . REDIS . ENABLED ) {
for ( const tx of newTransactions ) {
await redisCache . $addTransaction ( tx ) ;
}
}
2023-07-22 14:09:11 +09:00
loaded = true ;
} catch ( e ) {
logger . err ( 'failed to load mempool in bulk from esplora, falling back to fetching individual transactions' ) ;
}
}
if ( ! loaded ) {
2023-08-02 13:24:56 +09:00
const remainingTxids = transactions . filter ( txid = > ! this . mempoolCache [ txid ] ) ;
2023-11-15 06:10:00 +00:00
const sliceLength = config . ESPLORA . BATCH_QUERY_BASE_SIZE ;
2023-08-02 13:24:56 +09:00
for ( let i = 0 ; i < Math . ceil ( remainingTxids . length / sliceLength ) ; i ++ ) {
const slice = remainingTxids . slice ( i * sliceLength , ( i + 1 ) * sliceLength ) ;
const txs = await transactionUtils . $getMempoolTransactionsExtended ( slice , false , false , false ) ;
logger . debug ( ` fetched ${ txs . length } transactions ` ) ;
this . updateTimerProgress ( timer , 'fetched new transactions' ) ;
2023-06-16 18:56:34 -04:00
2023-08-02 13:24:56 +09:00
for ( const transaction of txs ) {
this . mempoolCache [ transaction . txid ] = transaction ;
if ( this . inSync ) {
this . txPerSecondArray . push ( new Date ( ) . getTime ( ) ) ;
this . vBytesPerSecondArray . push ( {
unixTime : new Date ( ) . getTime ( ) ,
vSize : transaction.vsize ,
} ) ;
}
hasChange = true ;
newTransactions . push ( transaction ) ;
2023-06-16 18:56:34 -04:00
2023-08-02 13:24:56 +09:00
if ( config . REDIS . ENABLED ) {
await redisCache . $addTransaction ( transaction ) ;
}
}
if ( txs . length < slice . length ) {
const missing = slice . length - txs . length ;
if ( config . MEMPOOL . BACKEND === 'esplora' ) {
this . missingTxCount += missing ;
2023-03-09 17:45:08 +09:00
}
2023-08-02 13:24:56 +09:00
logger . debug ( ` Error finding ${ missing } transactions in the mempool: ` ) ;
2019-11-15 17:27:12 +08:00
}
2023-06-27 18:46:28 +02:00
2023-08-01 15:55:03 +09:00
if ( Date . now ( ) - intervalTimer > Math . max ( pollRate * 2 , 5 _000 ) ) {
2023-07-22 14:09:11 +09:00
if ( this . inSync ) {
// Break and restart mempool loop if we spend too much time processing
// new transactions that may lead to falling behind on block height
logger . debug ( 'Breaking mempool loop because the 5s time limit exceeded.' ) ;
break ;
} else {
const progress = ( currentMempoolSize + newTransactions . length ) / transactions . length * 100 ;
logger . debug ( ` Mempool is synchronizing. Processed ${ newTransactions . length } / ${ diff } txs ( ${ Math . round ( progress ) } %) ` ) ;
2023-07-24 17:49:34 +09:00
if ( Math . floor ( progress ) < 100 ) {
loadingIndicators . setProgress ( 'mempool' , progress ) ;
}
2023-08-01 15:55:03 +09:00
intervalTimer = Date . now ( ) ;
2023-07-22 14:09:11 +09:00
}
2023-06-27 18:46:28 +02:00
}
2023-06-16 20:42:31 +02:00
}
2020-10-18 21:47:47 +07:00
}
2020-06-09 02:32:24 +07:00
2023-03-09 17:45:08 +09:00
// Reset esplora 404 counter and log a warning if needed
const elapsedTime = new Date ( ) . getTime ( ) - this . timer ;
if ( elapsedTime > this . SAMPLE_TIME ) {
logEsplora404 ( this . missingTxCount , this . ESPLORA_MISSING_TX_WARNING_THRESHOLD , elapsedTime ) ;
this . timer = new Date ( ) . getTime ( ) ;
this . missingTxCount = 0 ;
}
2020-10-18 21:47:47 +07:00
// Prevent mempool from clear on bitcoind restart by delaying the deletion
2020-10-19 17:30:47 +07:00
if ( this . mempoolProtection === 0
&& currentMempoolSize > 20000
&& transactions . length / currentMempoolSize <= 0.80
) {
2020-10-18 21:47:47 +07:00
this . mempoolProtection = 1 ;
this . inSync = false ;
logger . warn ( ` Mempool clear protection triggered because transactions.length: ${ transactions . length } and currentMempoolSize: ${ currentMempoolSize } . ` ) ;
setTimeout ( ( ) = > {
this . mempoolProtection = 2 ;
2023-07-28 23:39:33 +09:00
logger . warn ( 'Mempool clear protection ended, normal operation resumed.' ) ;
2021-02-14 20:32:00 +07:00
} , 1000 * 60 * config . MEMPOOL . CLEAR_PROTECTION_MINUTES ) ;
2020-10-18 21:47:47 +07:00
}
2020-06-18 13:54:54 +07:00
2023-05-29 15:56:29 -04:00
const deletedTransactions : MempoolTransactionExtended [ ] = [ ] ;
2020-10-18 21:47:47 +07:00
if ( this . mempoolProtection !== 1 ) {
this . mempoolProtection = 0 ;
// Index object for faster search
const transactionsObject = { } ;
transactions . forEach ( ( txId ) = > transactionsObject [ txId ] = true ) ;
2023-04-07 09:41:25 +09:00
// Delete evicted transactions from mempool
2020-10-18 21:47:47 +07:00
for ( const tx in this . mempoolCache ) {
2023-04-07 09:41:25 +09:00
if ( ! transactionsObject [ tx ] ) {
2020-10-18 21:47:47 +07:00
deletedTransactions . push ( this . mempoolCache [ tx ] ) ;
2019-07-21 17:59:47 +03:00
}
2020-06-08 18:55:53 +07:00
}
2023-04-07 09:41:25 +09:00
for ( const tx of deletedTransactions ) {
delete this . mempoolCache [ tx . txid ] ;
}
2020-10-18 21:47:47 +07:00
}
2019-07-21 17:59:47 +03:00
2024-01-07 17:57:36 +00:00
const candidates = await this . getNextCandidates ( minFeeMempool , minFeeTip , deletedTransactions ) ;
2023-06-28 19:51:52 -04:00
const newMempoolSize = currentMempoolSize + newTransactions . length - deletedTransactions . length ;
2020-10-18 21:47:47 +07:00
const newTransactionsStripped = newTransactions . map ( ( tx ) = > Common . stripTransaction ( tx ) ) ;
this . latestTransactions = newTransactionsStripped . concat ( this . latestTransactions ) . slice ( 0 , 6 ) ;
2020-09-26 02:11:30 +07:00
2023-12-06 12:09:28 +00:00
const accelerationDelta = accelerations != null ? await this . $updateAccelerations ( accelerations ) : [ ] ;
2023-05-30 19:35:39 -04:00
if ( accelerationDelta . length ) {
hasChange = true ;
}
2023-05-26 21:10:32 -04:00
2023-06-28 19:51:52 -04:00
this . mempoolCacheDelta = Math . abs ( transactions . length - newMempoolSize ) ;
2022-02-11 21:25:58 +09:00
2023-02-17 17:54:29 -06:00
const candidatesChanged = candidates ? . added ? . length || candidates ? . removed ? . length ;
2024-08-26 21:51:49 +00:00
this . recentlyDeleted . unshift ( deletedTransactions ) ;
this . recentlyDeleted . length = Math . min ( this . recentlyDeleted . length , 10 ) ; // truncate to the last 10 mempool updates
if ( this . mempoolChangedCallback && ( hasChange || newTransactions . length || deletedTransactions . length ) ) {
this . mempoolChangedCallback ( this . mempoolCache , newTransactions , this . recentlyDeleted , accelerationDelta ) ;
2019-07-21 17:59:47 +03:00
}
2024-08-26 21:51:49 +00:00
if ( this . $asyncMempoolChangedCallback && ( hasChange || newTransactions . length || deletedTransactions . length || candidatesChanged ) ) {
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , 'running async mempool callback' ) ;
2024-08-26 21:51:49 +00:00
await this . $asyncMempoolChangedCallback ( this . mempoolCache , newMempoolSize , newTransactions , this . recentlyDeleted , accelerationDelta , candidates ) ;
2023-04-27 10:19:12 +09:00
this . updateTimerProgress ( timer , 'completed async mempool callback' ) ;
2022-11-16 18:18:59 -06:00
}
2020-10-18 21:47:47 +07:00
2023-07-24 16:19:19 +09:00
if ( ! this . inSync && transactions . length === newMempoolSize ) {
this . inSync = true ;
logger . notice ( 'The mempool is now in sync!' ) ;
loadingIndicators . setProgress ( 'mempool' , 100 ) ;
}
2023-05-12 16:29:45 -06:00
// Update Redis cache
if ( config . REDIS . ENABLED ) {
2023-06-16 18:56:34 -04:00
await redisCache . $flushTransactions ( ) ;
2023-05-12 16:29:45 -06:00
await redisCache . $removeTransactions ( deletedTransactions . map ( tx = > tx . txid ) ) ;
2023-05-12 16:31:01 -06:00
await rbfCache . updateCache ( ) ;
2023-05-12 16:29:45 -06:00
}
2020-10-18 21:47:47 +07:00
const end = new Date ( ) . getTime ( ) ;
const time = end - start ;
2022-08-19 17:54:52 +04:00
logger . debug ( ` Mempool updated in ${ time / 1000 } seconds. New size: ${ Object . keys ( this . mempoolCache ) . length } ( ${ diff > 0 ? '+' + diff : diff } ) ` ) ;
2023-04-27 10:19:12 +09:00
this . clearTimer ( timer ) ;
}
2023-06-13 17:03:36 -04:00
public getAccelerations ( ) : { [ txid : string ] : Acceleration } {
2023-05-26 21:10:32 -04:00
return this . accelerations ;
}
2023-12-06 12:09:28 +00:00
public $updateAccelerations ( newAccelerations : Acceleration [ ] ) : string [ ] {
2023-05-30 19:35:39 -04:00
try {
const changed : string [ ] = [ ] ;
2023-06-13 17:03:36 -04:00
const newAccelerationMap : { [ txid : string ] : Acceleration } = { } ;
2023-05-30 19:35:39 -04:00
for ( const acceleration of newAccelerations ) {
2024-05-04 00:18:33 +00:00
// skip transactions we don't know about
if ( ! this . mempoolCache [ acceleration . txid ] ) {
continue ;
}
2023-06-13 17:03:36 -04:00
newAccelerationMap [ acceleration . txid ] = acceleration ;
2023-05-30 19:35:39 -04:00
if ( this . accelerations [ acceleration . txid ] == null ) {
// new acceleration
changed . push ( acceleration . txid ) ;
2023-06-13 17:03:36 -04:00
} else {
if ( this . accelerations [ acceleration . txid ] . feeDelta !== acceleration . feeDelta ) {
// feeDelta changed
changed . push ( acceleration . txid ) ;
} else if ( this . accelerations [ acceleration . txid ] . pools ? . length ) {
let poolsChanged = false ;
const pools = new Set ( ) ;
this . accelerations [ acceleration . txid ] . pools . forEach ( pool = > {
pools . add ( pool ) ;
} ) ;
acceleration . pools . forEach ( pool = > {
if ( ! pools . has ( pool ) ) {
poolsChanged = true ;
} else {
pools . delete ( pool ) ;
}
} ) ;
if ( pools . size > 0 ) {
poolsChanged = true ;
}
if ( poolsChanged ) {
// pools changed
changed . push ( acceleration . txid ) ;
}
}
2023-05-30 19:35:39 -04:00
}
}
for ( const oldTxid of Object . keys ( this . accelerations ) ) {
if ( ! newAccelerationMap [ oldTxid ] ) {
// removed
changed . push ( oldTxid ) ;
}
}
this . accelerations = newAccelerationMap ;
2023-05-26 21:10:32 -04:00
2023-05-30 19:35:39 -04:00
return changed ;
} catch ( e : any ) {
logger . debug ( ` Failed to update accelerations: ` + ( e instanceof Error ? e.message : e ) ) ;
return [ ] ;
2023-05-26 21:10:32 -04:00
}
}
2024-01-07 17:57:36 +00:00
public async getNextCandidates ( minFeeTransactions : string [ ] , blockHeight : number , deletedTransactions : MempoolTransactionExtended [ ] ) : Promise < GbtCandidates | undefined > {
2023-02-17 17:54:29 -06:00
if ( this . limitGBT ) {
2024-01-07 17:57:36 +00:00
const deletedTxsMap = { } ;
for ( const tx of deletedTransactions ) {
deletedTxsMap [ tx . txid ] = tx ;
}
2023-02-17 17:54:29 -06:00
const newCandidateTxMap = { } ;
for ( const txid of minFeeTransactions ) {
if ( this . mempoolCache [ txid ] ) {
newCandidateTxMap [ txid ] = true ;
}
}
2024-01-08 00:03:05 +00:00
const accelerations = this . getAccelerations ( ) ;
for ( const txid of Object . keys ( accelerations ) ) {
if ( this . mempoolCache [ txid ] ) {
newCandidateTxMap [ txid ] = true ;
}
}
2023-02-17 17:54:29 -06:00
const removed : MempoolTransactionExtended [ ] = [ ] ;
const added : MempoolTransactionExtended [ ] = [ ] ;
// don't prematurely remove txs included in a new block
if ( blockHeight > blocks . getCurrentBlockHeight ( ) ) {
for ( const txid of Object . keys ( this . mempoolCandidates ) ) {
newCandidateTxMap [ txid ] = true ;
}
} else {
for ( const txid of Object . keys ( this . mempoolCandidates ) ) {
if ( ! newCandidateTxMap [ txid ] ) {
2024-01-05 22:25:07 +00:00
if ( this . mempoolCache [ txid ] ) {
2024-01-07 17:57:36 +00:00
removed . push ( this . mempoolCache [ txid ] ) ;
2024-01-05 22:25:07 +00:00
this . mempoolCache [ txid ] . effectiveFeePerVsize = this . mempoolCache [ txid ] . adjustedFeePerVsize ;
this . mempoolCache [ txid ] . ancestors = [ ] ;
this . mempoolCache [ txid ] . descendants = [ ] ;
this . mempoolCache [ txid ] . bestDescendant = null ;
this . mempoolCache [ txid ] . cpfpChecked = false ;
this . mempoolCache [ txid ] . cpfpUpdated = undefined ;
2024-01-07 17:57:36 +00:00
} else if ( deletedTxsMap [ txid ] ) {
removed . push ( deletedTxsMap [ txid ] ) ;
2024-01-05 22:25:07 +00:00
}
2023-02-17 17:54:29 -06:00
}
}
}
for ( const txid of Object . keys ( newCandidateTxMap ) ) {
if ( ! this . mempoolCandidates [ txid ] ) {
2024-01-05 22:25:07 +00:00
added . push ( this . mempoolCache [ txid ] ) ;
2023-02-17 17:54:29 -06:00
}
}
this . mempoolCandidates = newCandidateTxMap ;
return {
txs : this.mempoolCandidates ,
added ,
removed
} ;
}
}
2024-02-02 02:25:49 +00:00
setAccelerationPositions ( positions : { [ txid : string ] : { poolId : number , pool : string , block : number , vsize : number } [ ] } ) : void {
this . accelerationPositions = positions ;
}
getAccelerationPositions ( txid : string ) : { [ pool : number ] : { poolId : number , pool : string , block : number , vsize : number } } | undefined {
return this . accelerationPositions [ txid ] ;
}
2023-04-27 10:19:12 +09:00
private startTimer() {
const state : any = {
start : Date.now ( ) ,
progress : 'begin $updateMempool' ,
timer : null ,
} ;
state . timer = setTimeout ( ( ) = > {
2023-05-01 00:16:23 +04:00
logger . err ( ` $ updateMempool 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-07-21 17:59:47 +03:00
}
2024-08-26 21:51:49 +00:00
public handleRbfTransactions ( rbfTransactions : { [ txid : string ] : { replaced : MempoolTransactionExtended [ ] , replacedBy : TransactionExtended } } ) : void {
2023-05-18 09:51:41 -04:00
for ( const rbfTransaction in rbfTransactions ) {
if ( rbfTransactions [ rbfTransaction ] . replacedBy && rbfTransactions [ rbfTransaction ] ? . replaced ? . length ) {
// Store replaced transactions
2023-05-31 11:37:13 -04:00
rbfCache . add ( rbfTransactions [ rbfTransaction ] . replaced , transactionUtils . extendMempoolTransaction ( rbfTransactions [ rbfTransaction ] . replacedBy ) ) ;
2023-05-18 09:51:41 -04:00
}
}
}
2023-05-29 15:56:29 -04:00
public addToSpendMap ( transactions : MempoolTransactionExtended [ ] ) : void {
2023-05-18 09:51:41 -04:00
for ( const tx of transactions ) {
for ( const vin of tx . vin ) {
this . spendMap . set ( ` ${ vin . txid } : ${ vin . vout } ` , tx ) ;
}
}
}
2023-05-31 11:37:13 -04:00
public removeFromSpendMap ( transactions : TransactionExtended [ ] ) : void {
2023-05-18 09:51:41 -04:00
for ( const tx of transactions ) {
for ( const vin of tx . vin ) {
const key = ` ${ vin . txid } : ${ vin . vout } ` ;
if ( this . spendMap . get ( key ) ? . txid === tx . txid ) {
this . spendMap . delete ( key ) ;
}
}
}
}
2019-07-21 17:59:47 +03:00
private updateTxPerSecond() {
2020-10-19 11:57:02 +07:00
const nowMinusTimeSpan = new Date ( ) . getTime ( ) - ( 1000 * config . STATISTICS . TX_PER_SECOND_SAMPLE_PERIOD ) ;
2019-07-21 17:59:47 +03:00
this . txPerSecondArray = this . txPerSecondArray . filter ( ( unixTime ) = > unixTime > nowMinusTimeSpan ) ;
2020-10-19 11:57:02 +07:00
this . txPerSecond = this . txPerSecondArray . length / config . STATISTICS . TX_PER_SECOND_SAMPLE_PERIOD || 0 ;
2019-07-21 17:59:47 +03:00
this . vBytesPerSecondArray = this . vBytesPerSecondArray . filter ( ( data ) = > data . unixTime > nowMinusTimeSpan ) ;
if ( this . vBytesPerSecondArray . length ) {
this . vBytesPerSecond = Math . round (
2020-10-19 11:57:02 +07:00
this . vBytesPerSecondArray . map ( ( data ) = > data . vSize ) . reduce ( ( a , b ) = > a + b ) / config . STATISTICS . TX_PER_SECOND_SAMPLE_PERIOD
2019-07-21 17:59:47 +03:00
) ;
}
}
2021-03-21 06:06:03 +07:00
2021-09-15 01:47:24 +04:00
private $getMempoolInfo() {
2021-09-19 02:40:16 +04:00
if ( config . MEMPOOL . USE_SECOND_NODE_FOR_MINFEE ) {
2021-09-15 01:47:24 +04:00
return Promise . all ( [
bitcoinClient . getMempoolInfo ( ) ,
bitcoinSecondClient . getMempoolInfo ( )
] ) . then ( ( [ mempoolInfo , secondMempoolInfo ] ) = > {
mempoolInfo . maxmempool = secondMempoolInfo . maxmempool ;
mempoolInfo . mempoolminfee = secondMempoolInfo . mempoolminfee ;
mempoolInfo . minrelaytxfee = secondMempoolInfo . minrelaytxfee ;
return mempoolInfo ;
} ) ;
}
return bitcoinClient . getMempoolInfo ( ) ;
}
2019-07-21 17:59:47 +03:00
}
export default new Mempool ( ) ;