2023-08-03 18:46:18 -07:00
import { GbtGenerator , GbtResult , ThreadTransaction as RustThreadTransaction , ThreadAcceleration as RustThreadAcceleration } from 'rust-gbt' ;
2021-03-18 23:47:40 +07:00
import logger from '../logger' ;
2023-02-17 17:54:29 -06:00
import { MempoolBlock , MempoolTransactionExtended , MempoolBlockWithTransactions , MempoolBlockDelta , Ancestor , CompactThreadTransaction , EffectiveFeeStats , TransactionClassified , TransactionCompressed , MempoolDeltaChange , GbtCandidates } from '../mempool.interfaces' ;
2023-05-10 12:59:05 -06:00
import { Common , OnlineFeeStatsCalculator } from './common' ;
2021-03-18 05:52:46 +00:00
import config from '../config' ;
2022-12-06 05:51:44 +09:00
import { Worker } from 'worker_threads' ;
2022-11-16 18:17:07 -06:00
import path from 'path' ;
2023-05-30 19:35:39 -04:00
import mempool from './mempool' ;
2020-02-16 22:15:07 +07:00
2023-06-25 23:42:33 -04:00
const MAX_UINT32 = Math . pow ( 2 , 32 ) - 1 ;
2020-02-16 22:15:07 +07:00
class MempoolBlocks {
2020-06-08 02:08:51 +07:00
private mempoolBlocks : MempoolBlockWithTransactions [ ] = [ ] ;
2022-05-31 21:36:42 +00:00
private mempoolBlockDeltas : MempoolBlockDelta [ ] = [ ] ;
2022-12-06 05:51:44 +09:00
private txSelectionWorker : Worker | null = null ;
2023-06-23 18:37:03 -04:00
private rustInitialized : boolean = false ;
2023-06-24 23:05:43 -07:00
private rustGbtGenerator : GbtGenerator = new GbtGenerator ( ) ;
2020-02-16 22:15:07 +07:00
2023-05-02 17:46:48 -06:00
private nextUid : number = 1 ;
private uidMap : Map < number , string > = new Map ( ) ; // map short numerical uids to full txids
2024-01-08 00:03:05 +00:00
private txidMap : Map < string , number > = new Map ( ) ; // map full txids back to short numerical uids
2023-05-02 17:46:48 -06:00
2020-02-16 22:15:07 +07:00
public getMempoolBlocks ( ) : MempoolBlock [ ] {
2020-06-08 02:08:51 +07:00
return this . mempoolBlocks . map ( ( block ) = > {
return {
blockSize : block.blockSize ,
blockVSize : block.blockVSize ,
nTx : block.nTx ,
totalFees : block.totalFees ,
medianFee : block.medianFee ,
feeRange : block.feeRange ,
} ;
} ) ;
}
public getMempoolBlocksWithTransactions ( ) : MempoolBlockWithTransactions [ ] {
2020-02-16 22:15:07 +07:00
return this . mempoolBlocks ;
}
2022-05-31 21:36:42 +00:00
public getMempoolBlockDeltas ( ) : MempoolBlockDelta [ ] {
2022-06-02 01:29:03 +04:00
return this . mempoolBlockDeltas ;
2022-05-31 21:36:42 +00:00
}
2022-11-16 18:17:07 -06:00
private calculateMempoolDeltas ( prevBlocks : MempoolBlockWithTransactions [ ] , mempoolBlocks : MempoolBlockWithTransactions [ ] ) : MempoolBlockDelta [ ] {
const mempoolBlockDeltas : MempoolBlockDelta [ ] = [ ] ;
2022-05-31 21:36:42 +00:00
for ( let i = 0 ; i < Math . max ( mempoolBlocks . length , prevBlocks . length ) ; i ++ ) {
2023-12-05 06:54:31 +00:00
let added : TransactionClassified [ ] = [ ] ;
2022-06-02 01:29:03 +04:00
let removed : string [ ] = [ ] ;
2024-02-08 22:40:22 +00:00
const changed : TransactionClassified [ ] = [ ] ;
2022-05-31 21:36:42 +00:00
if ( mempoolBlocks [ i ] && ! prevBlocks [ i ] ) {
2022-06-02 01:29:03 +04:00
added = mempoolBlocks [ i ] . transactions ;
2022-05-31 21:36:42 +00:00
} else if ( ! mempoolBlocks [ i ] && prevBlocks [ i ] ) {
2022-06-02 01:29:03 +04:00
removed = prevBlocks [ i ] . transactions . map ( tx = > tx . txid ) ;
2022-05-31 21:36:42 +00:00
} else if ( mempoolBlocks [ i ] && prevBlocks [ i ] ) {
2022-06-02 01:29:03 +04:00
const prevIds = { } ;
const newIds = { } ;
2022-05-31 21:36:42 +00:00
prevBlocks [ i ] . transactions . forEach ( tx = > {
2023-04-05 08:40:43 +09:00
prevIds [ tx . txid ] = tx ;
2022-06-02 01:29:03 +04:00
} ) ;
2022-05-31 21:36:42 +00:00
mempoolBlocks [ i ] . transactions . forEach ( tx = > {
2022-06-02 01:29:03 +04:00
newIds [ tx . txid ] = true ;
} ) ;
2022-05-31 21:36:42 +00:00
prevBlocks [ i ] . transactions . forEach ( tx = > {
2022-06-02 01:29:03 +04:00
if ( ! newIds [ tx . txid ] ) {
removed . push ( tx . txid ) ;
}
} ) ;
2022-05-31 21:36:42 +00:00
mempoolBlocks [ i ] . transactions . forEach ( tx = > {
2022-06-02 01:29:03 +04:00
if ( ! prevIds [ tx . txid ] ) {
added . push ( tx ) ;
2023-05-26 21:10:32 -04:00
} else if ( tx . rate !== prevIds [ tx . txid ] . rate || tx . acc !== prevIds [ tx . txid ] . acc ) {
2024-02-08 22:40:22 +00:00
changed . push ( tx ) ;
2022-06-02 01:29:03 +04:00
}
} ) ;
2022-05-31 21:36:42 +00:00
}
mempoolBlockDeltas . push ( {
2024-02-08 22:40:22 +00:00
added : added.map ( this . compressTx ) ,
2023-04-05 08:40:43 +09:00
removed ,
2024-02-08 22:40:22 +00:00
changed : changed.map ( this . compressDeltaChange ) ,
2022-06-02 01:29:03 +04:00
} ) ;
2022-05-31 21:36:42 +00:00
}
2022-11-16 18:17:07 -06:00
return mempoolBlockDeltas ;
2020-02-16 22:15:07 +07:00
}
2023-02-17 17:54:29 -06:00
public async $makeBlockTemplates ( transactions : string [ ] , newMempool : { [ txid : string ] : MempoolTransactionExtended } , candidates : GbtCandidates | undefined , saveResults : boolean = false , useAccelerations : boolean = false , accelerationPool? : number ) : Promise < MempoolBlockWithTransactions [ ] > {
2023-05-08 19:03:39 -06:00
const start = Date . now ( ) ;
2023-05-02 17:46:48 -06:00
// reset mempool short ids
2023-07-19 11:18:04 +09:00
if ( saveResults ) {
this . resetUids ( ) ;
}
// set missing short ids
2023-02-17 17:54:29 -06:00
for ( const txid of transactions ) {
const tx = newMempool [ txid ] ;
2024-01-07 17:57:36 +00:00
this . setUid ( tx , ! saveResults ) ;
2023-05-02 17:46:48 -06:00
}
2023-06-03 16:54:12 -04:00
const accelerations = useAccelerations ? mempool . getAccelerations ( ) : { } ;
2023-05-30 19:35:39 -04:00
2022-12-06 05:51:44 +09:00
// prepare a stripped down version of the mempool with only the minimum necessary data
// to reduce the overhead of passing this data to the worker thread
2023-05-02 17:46:48 -06:00
const strippedMempool : Map < number , CompactThreadTransaction > = new Map ( ) ;
2023-02-17 17:54:29 -06:00
for ( const txid of transactions ) {
const entry = newMempool [ txid ] ;
2023-06-26 11:15:52 -04:00
if ( entry . uid !== null && entry . uid !== undefined ) {
2023-06-23 16:42:58 -04:00
const stripped = {
2023-05-02 17:46:48 -06:00
uid : entry.uid ,
2023-06-13 17:03:36 -04:00
fee : entry.fee + ( useAccelerations && ( ! accelerationPool || accelerations [ entry . txid ] ? . pools ? . includes ( accelerationPool ) ) ? ( accelerations [ entry . txid ] ? . feeDelta || 0 ) : 0 ) ,
2023-05-29 15:56:29 -04:00
weight : ( entry . adjustedVsize * 4 ) ,
2023-05-29 17:21:02 -04:00
sigops : entry.sigops ,
2023-05-29 15:56:29 -04:00
feePerVsize : entry.adjustedFeePerVsize || entry . feePerVsize ,
effectiveFeePerVsize : entry.effectiveFeePerVsize || entry . adjustedFeePerVsize || entry . feePerVsize ,
2023-06-26 11:15:52 -04:00
inputs : entry.vin.map ( v = > this . getUid ( newMempool [ v . txid ] ) ) . filter ( uid = > ( uid !== null && uid !== undefined ) ) as number [ ] ,
2023-06-23 16:42:58 -04:00
} ;
strippedMempool . set ( entry . uid , stripped ) ;
2023-05-02 17:46:48 -06:00
}
2023-02-17 17:54:29 -06:00
}
2022-10-10 22:13:04 +00:00
2022-12-07 14:51:26 -06:00
// (re)initialize tx selection worker thread
2022-12-06 05:51:44 +09:00
if ( ! this . txSelectionWorker ) {
this . txSelectionWorker = new Worker ( path . resolve ( __dirname , './tx-selection-worker.js' ) ) ;
2022-12-07 14:51:26 -06:00
// if the thread throws an unexpected error, or exits for any other reason,
// reset worker state so that it will be re-initialized on the next run
2022-12-06 05:51:44 +09:00
this . txSelectionWorker . once ( 'error' , ( ) = > {
this . txSelectionWorker = null ;
} ) ;
this . txSelectionWorker . once ( 'exit' , ( ) = > {
this . txSelectionWorker = null ;
} ) ;
}
// run the block construction algorithm in a separate thread, and wait for a result
2022-12-07 14:51:26 -06:00
let threadErrorListener ;
try {
2023-05-08 19:03:39 -06:00
const workerResultPromise = new Promise < { blocks : number [ ] [ ] , rates : Map < number , number > , clusters : Map < number , number [ ] > } > ( ( resolve , reject ) = > {
2022-12-07 14:51:26 -06:00
threadErrorListener = reject ;
this . txSelectionWorker ? . once ( 'message' , ( result ) : void = > {
resolve ( result ) ;
} ) ;
this . txSelectionWorker ? . once ( 'error' , reject ) ;
2022-12-06 05:51:44 +09:00
} ) ;
2022-12-07 14:51:26 -06:00
this . txSelectionWorker . postMessage ( { type : 'set' , mempool : strippedMempool } ) ;
2023-05-08 19:03:39 -06:00
const { blocks , rates , clusters } = this . convertResultTxids ( await workerResultPromise ) ;
2022-10-27 10:21:39 -06:00
2022-12-07 14:51:26 -06:00
// clean up thread error listener
this . txSelectionWorker ? . removeListener ( 'error' , threadErrorListener ) ;
2023-01-30 16:26:37 -06:00
2023-02-17 17:54:29 -06:00
const processed = this . processBlockTemplates ( newMempool , blocks , null , Object . entries ( rates ) , Object . values ( clusters ) , candidates , accelerations , accelerationPool , saveResults ) ;
2023-06-23 16:42:58 -04:00
2023-05-08 19:03:39 -06:00
logger . debug ( ` makeBlockTemplates completed in ${ ( Date . now ( ) - start ) / 1000 } seconds ` ) ;
2023-06-23 16:42:58 -04:00
2023-05-08 19:03:39 -06:00
return processed ;
2022-12-07 14:51:26 -06:00
} catch ( e ) {
logger . err ( 'makeBlockTemplates failed. ' + ( e instanceof Error ? e.message : e ) ) ;
}
2023-01-30 16:26:37 -06:00
return this . mempoolBlocks ;
2022-12-06 05:51:44 +09:00
}
2023-02-17 17:54:29 -06:00
public async $updateBlockTemplates ( transactions : string [ ] , newMempool : { [ txid : string ] : MempoolTransactionExtended } , added : MempoolTransactionExtended [ ] , removed : MempoolTransactionExtended [ ] , candidates : GbtCandidates | undefined , accelerationDelta : string [ ] = [ ] , saveResults : boolean = false , useAccelerations : boolean = false ) : Promise < void > {
2022-12-06 05:51:44 +09:00
if ( ! this . txSelectionWorker ) {
// need to reset the worker
2023-02-17 17:54:29 -06:00
await this . $makeBlockTemplates ( transactions , newMempool , candidates , saveResults , useAccelerations ) ;
2023-01-30 16:26:37 -06:00
return ;
2022-12-06 05:51:44 +09:00
}
2023-05-02 17:46:48 -06:00
2023-05-08 19:03:39 -06:00
const start = Date . now ( ) ;
2023-06-03 16:54:12 -04:00
const accelerations = useAccelerations ? mempool . getAccelerations ( ) : { } ;
const addedAndChanged : MempoolTransactionExtended [ ] = useAccelerations ? accelerationDelta . map ( txid = > newMempool [ txid ] ) . filter ( tx = > tx != null ) . concat ( added ) : added ;
2023-05-30 19:35:39 -04:00
for ( const tx of addedAndChanged ) {
2023-02-17 17:54:29 -06:00
this . setUid ( tx , false ) ;
2023-05-02 17:46:48 -06:00
}
2024-01-07 17:57:36 +00:00
const removedTxs = removed . filter ( tx = > tx . uid != null ) as MempoolTransactionExtended [ ] ;
2023-05-30 19:35:39 -04:00
2022-12-06 05:51:44 +09:00
// prepare a stripped down version of the mempool with only the minimum necessary data
// to reduce the overhead of passing this data to the worker thread
2023-05-30 19:35:39 -04:00
const addedStripped : CompactThreadTransaction [ ] = addedAndChanged . filter ( entry = > entry . uid != null ) . map ( entry = > {
2022-12-06 05:51:44 +09:00
return {
2023-05-02 17:46:48 -06:00
uid : entry.uid || 0 ,
2023-06-13 17:03:36 -04:00
fee : entry.fee + ( useAccelerations ? ( accelerations [ entry . txid ] ? . feeDelta || 0 ) : 0 ) ,
2023-05-29 15:56:29 -04:00
weight : ( entry . adjustedVsize * 4 ) ,
2023-05-29 17:21:02 -04:00
sigops : entry.sigops ,
2023-05-29 15:56:29 -04:00
feePerVsize : entry.adjustedFeePerVsize || entry . feePerVsize ,
effectiveFeePerVsize : entry.effectiveFeePerVsize || entry . adjustedFeePerVsize || entry . feePerVsize ,
2023-06-26 11:15:52 -04:00
inputs : entry.vin.map ( v = > this . getUid ( newMempool [ v . txid ] ) ) . filter ( uid = > ( uid !== null && uid !== undefined ) ) as number [ ] ,
2022-12-06 05:51:44 +09:00
} ;
} ) ;
// run the block construction algorithm in a separate thread, and wait for a result
2022-12-07 14:51:26 -06:00
let threadErrorListener ;
try {
2023-05-08 19:03:39 -06:00
const workerResultPromise = new Promise < { blocks : number [ ] [ ] , rates : Map < number , number > , clusters : Map < number , number [ ] > } > ( ( resolve , reject ) = > {
2022-12-07 14:51:26 -06:00
threadErrorListener = reject ;
this . txSelectionWorker ? . once ( 'message' , ( result ) : void = > {
resolve ( result ) ;
} ) ;
this . txSelectionWorker ? . once ( 'error' , reject ) ;
2022-12-06 05:51:44 +09:00
} ) ;
2024-01-07 17:57:36 +00:00
this . txSelectionWorker . postMessage ( { type : 'update' , added : addedStripped , removed : removedTxs.map ( tx = > tx . uid ) as number [ ] } ) ;
2023-05-08 19:03:39 -06:00
const { blocks , rates , clusters } = this . convertResultTxids ( await workerResultPromise ) ;
2022-12-06 05:51:44 +09:00
2024-01-07 17:57:36 +00:00
this . removeUids ( removedTxs ) ;
2023-05-02 17:46:48 -06:00
2022-12-07 14:51:26 -06:00
// clean up thread error listener
this . txSelectionWorker ? . removeListener ( 'error' , threadErrorListener ) ;
2023-01-30 16:26:37 -06:00
2023-02-17 17:54:29 -06:00
this . processBlockTemplates ( newMempool , blocks , null , Object . entries ( rates ) , Object . values ( clusters ) , candidates , accelerations , null , saveResults ) ;
2023-05-08 19:03:39 -06:00
logger . debug ( ` updateBlockTemplates completed in ${ ( Date . now ( ) - start ) / 1000 } seconds ` ) ;
2022-12-07 14:51:26 -06:00
} catch ( e ) {
logger . err ( 'updateBlockTemplates failed. ' + ( e instanceof Error ? e.message : e ) ) ;
}
2022-12-06 05:51:44 +09:00
}
2023-06-25 20:37:42 -04:00
private resetRustGbt ( ) : void {
this . rustInitialized = false ;
this . rustGbtGenerator = new GbtGenerator ( ) ;
}
2023-02-17 17:54:29 -06:00
public async $rustMakeBlockTemplates ( txids : string [ ] , newMempool : { [ txid : string ] : MempoolTransactionExtended } , candidates : GbtCandidates | undefined , saveResults : boolean = false , useAccelerations : boolean = false , accelerationPool? : number ) : Promise < MempoolBlockWithTransactions [ ] > {
2023-06-23 16:42:58 -04:00
const start = Date . now ( ) ;
// reset mempool short ids
2023-06-25 20:37:42 -04:00
if ( saveResults ) {
this . resetUids ( ) ;
}
2023-02-17 17:54:29 -06:00
const transactions = txids . map ( txid = > newMempool [ txid ] ) . filter ( tx = > tx != null ) ;
2023-06-25 20:37:42 -04:00
// set missing short ids
2023-02-17 17:54:29 -06:00
for ( const tx of transactions ) {
2024-01-07 17:57:36 +00:00
this . setUid ( tx , ! saveResults ) ;
2023-06-23 16:42:58 -04:00
}
2023-06-28 16:31:59 -04:00
// set short ids for transaction inputs
2023-02-17 17:54:29 -06:00
for ( const tx of transactions ) {
2023-06-28 16:31:59 -04:00
tx . inputs = tx . vin . map ( v = > this . getUid ( newMempool [ v . txid ] ) ) . filter ( uid = > ( uid !== null && uid !== undefined ) ) as number [ ] ;
}
2023-06-23 16:42:58 -04:00
2023-07-18 15:05:44 +09:00
const accelerations = useAccelerations ? mempool . getAccelerations ( ) : { } ;
const acceleratedList = accelerationPool ? Object . values ( accelerations ) . filter ( acc = > newMempool [ acc . txid ] && acc . pools . includes ( accelerationPool ) ) : Object . values ( accelerations ) . filter ( acc = > newMempool [ acc . txid ] ) ;
const convertedAccelerations = acceleratedList . map ( acc = > {
2024-01-07 17:57:36 +00:00
this . setUid ( newMempool [ acc . txid ] , true ) ;
2023-07-18 15:05:44 +09:00
return {
uid : this.getUid ( newMempool [ acc . txid ] ) ,
delta : acc.feeDelta ,
} ;
} ) ;
2023-06-23 16:42:58 -04:00
// run the block construction algorithm in a separate thread, and wait for a result
2023-06-25 20:37:42 -04:00
const rustGbt = saveResults ? this . rustGbtGenerator : new GbtGenerator ( ) ;
2023-06-23 16:42:58 -04:00
try {
2024-01-09 17:08:25 +00:00
const { blocks , blockWeights , rates , clusters , overflow } = this . convertNapiResultTxids (
2023-02-17 17:54:29 -06:00
await rustGbt . make ( transactions as RustThreadTransaction [ ] , convertedAccelerations as RustThreadAcceleration [ ] , this . nextUid ) ,
2023-06-24 11:40:25 -07:00
) ;
2023-06-25 20:37:42 -04:00
if ( saveResults ) {
this . rustInitialized = true ;
}
2023-02-17 17:54:29 -06:00
const expectedSize = transactions . length ;
2024-01-09 17:08:25 +00:00
const resultMempoolSize = blocks . reduce ( ( total , block ) = > total + block . length , 0 ) + overflow . length ;
2023-02-17 17:54:29 -06:00
logger . debug ( ` RUST updateBlockTemplates returned ${ resultMempoolSize } txs out of ${ expectedSize } in the mempool, ${ overflow . length } were unmineable ` ) ;
const processed = this . processBlockTemplates ( newMempool , blocks , blockWeights , rates , clusters , candidates , accelerations , accelerationPool , saveResults ) ;
2023-06-23 16:42:58 -04:00
logger . debug ( ` RUST makeBlockTemplates completed in ${ ( Date . now ( ) - start ) / 1000 } seconds ` ) ;
return processed ;
} catch ( e ) {
logger . err ( 'RUST makeBlockTemplates failed. ' + ( e instanceof Error ? e.message : e ) ) ;
2023-06-25 20:37:42 -04:00
if ( saveResults ) {
this . resetRustGbt ( ) ;
}
2023-06-23 16:42:58 -04:00
}
return this . mempoolBlocks ;
}
2023-02-17 17:54:29 -06:00
public async $oneOffRustBlockTemplates ( transactions : string [ ] , newMempool : { [ txid : string ] : MempoolTransactionExtended } , candidates : GbtCandidates | undefined , useAccelerations : boolean , accelerationPool? : number ) : Promise < MempoolBlockWithTransactions [ ] > {
return this . $rustMakeBlockTemplates ( transactions , newMempool , candidates , false , useAccelerations , accelerationPool ) ;
2023-06-25 20:37:42 -04:00
}
2023-02-17 17:54:29 -06:00
public async $rustUpdateBlockTemplates ( transactions : string [ ] , newMempool : { [ txid : string ] : MempoolTransactionExtended } , added : MempoolTransactionExtended [ ] , removed : MempoolTransactionExtended [ ] , candidates : GbtCandidates | undefined , useAccelerations : boolean , accelerationPool? : number ) : Promise < MempoolBlockWithTransactions [ ] > {
2023-07-03 22:01:54 -04:00
// GBT optimization requires that uids never get too sparse
// as a sanity check, we should also explicitly prevent uint32 uid overflow
2023-02-17 17:54:29 -06:00
if ( this . nextUid + added . length >= Math . min ( Math . max ( 262144 , 2 * transactions . length ) , MAX_UINT32 ) ) {
2023-06-25 20:38:18 -04:00
this . resetRustGbt ( ) ;
}
2023-07-18 15:05:44 +09:00
2023-06-23 18:37:03 -04:00
if ( ! this . rustInitialized ) {
// need to reset the worker
2023-02-17 17:54:29 -06:00
return this . $rustMakeBlockTemplates ( transactions , newMempool , candidates , true , useAccelerations , accelerationPool ) ;
2023-06-23 18:37:03 -04:00
}
const start = Date . now ( ) ;
2023-06-28 16:31:59 -04:00
// set missing short ids
for ( const tx of added ) {
2023-02-17 17:54:29 -06:00
this . setUid ( tx , false ) ;
2023-06-23 18:37:03 -04:00
}
2023-06-28 16:31:59 -04:00
// set short ids for transaction inputs
for ( const tx of added ) {
tx . inputs = tx . vin . map ( v = > this . getUid ( newMempool [ v . txid ] ) ) . filter ( uid = > ( uid !== null && uid !== undefined ) ) as number [ ] ;
}
2024-01-07 17:57:36 +00:00
const removedTxs = removed . filter ( tx = > tx . uid != null ) as MempoolTransactionExtended [ ] ;
2023-06-23 18:37:03 -04:00
2023-07-18 15:05:44 +09:00
const accelerations = useAccelerations ? mempool . getAccelerations ( ) : { } ;
const acceleratedList = accelerationPool ? Object . values ( accelerations ) . filter ( acc = > newMempool [ acc . txid ] && acc . pools . includes ( accelerationPool ) ) : Object . values ( accelerations ) . filter ( acc = > newMempool [ acc . txid ] ) ;
const convertedAccelerations = acceleratedList . map ( acc = > {
2024-01-07 17:57:36 +00:00
this . setUid ( newMempool [ acc . txid ] , true ) ;
2023-07-18 15:05:44 +09:00
return {
uid : this.getUid ( newMempool [ acc . txid ] ) ,
delta : acc.feeDelta ,
} ;
} ) ;
2023-06-23 18:37:03 -04:00
// run the block construction algorithm in a separate thread, and wait for a result
try {
2024-01-09 17:08:25 +00:00
const { blocks , blockWeights , rates , clusters , overflow } = this . convertNapiResultTxids (
2023-06-24 23:05:43 -07:00
await this . rustGbtGenerator . update (
2023-06-28 16:31:59 -04:00
added as RustThreadTransaction [ ] ,
2024-01-07 17:57:36 +00:00
removedTxs . map ( tx = > tx . uid ) as number [ ] ,
2023-07-18 15:05:44 +09:00
convertedAccelerations as RustThreadAcceleration [ ] ,
2023-07-03 22:01:54 -04:00
this . nextUid ,
2023-06-24 19:28:19 -07:00
) ,
2023-06-24 11:40:25 -07:00
) ;
2024-01-09 17:08:25 +00:00
const resultMempoolSize = blocks . reduce ( ( total , block ) = > total + block . length , 0 ) + overflow . length ;
2023-02-17 17:54:29 -06:00
logger . debug ( ` RUST updateBlockTemplates returned ${ resultMempoolSize } txs out of ${ transactions . length } candidates, ${ overflow . length } were unmineable ` ) ;
if ( transactions . length !== resultMempoolSize ) {
throw new Error ( ` GBT returned wrong number of transactions ${ transactions . length } vs ${ resultMempoolSize } , cache is probably out of sync ` ) ;
2024-01-09 17:08:25 +00:00
} else {
2023-02-17 17:54:29 -06:00
const processed = this . processBlockTemplates ( newMempool , blocks , blockWeights , rates , clusters , candidates , accelerations , accelerationPool , true ) ;
2024-01-07 17:57:36 +00:00
this . removeUids ( removedTxs ) ;
2023-07-18 17:30:51 +09:00
logger . debug ( ` RUST updateBlockTemplates completed in ${ ( Date . now ( ) - start ) / 1000 } seconds ` ) ;
return processed ;
2024-01-09 17:08:25 +00:00
}
2023-06-23 18:37:03 -04:00
} catch ( e ) {
logger . err ( 'RUST updateBlockTemplates failed. ' + ( e instanceof Error ? e.message : e ) ) ;
2023-06-25 20:37:42 -04:00
this . resetRustGbt ( ) ;
2023-07-18 17:30:51 +09:00
return this . mempoolBlocks ;
2023-06-23 18:37:03 -04:00
}
2023-06-23 16:42:58 -04:00
}
2024-01-05 22:25:07 +00:00
private processBlockTemplates ( mempool : { [ txid : string ] : MempoolTransactionExtended } , blocks : string [ ] [ ] , blockWeights : number [ ] | null , rates : [ string , number ] [ ] , clusters : string [ ] [ ] , candidates : GbtCandidates | undefined , accelerations , accelerationPool , saveResults ) : MempoolBlockWithTransactions [ ] {
2024-03-31 08:06:01 +00:00
for ( const txid of Object . keys ( candidates ? . txs ? ? mempool ) ) {
if ( txid in mempool ) {
mempool [ txid ] . cpfpDirty = false ;
}
}
2023-06-30 14:46:44 -04:00
for ( const [ txid , rate ] of rates ) {
2023-05-08 19:03:39 -06:00
if ( txid in mempool ) {
2023-08-27 00:30:33 +09:00
mempool [ txid ] . cpfpDirty = ( rate !== mempool [ txid ] . effectiveFeePerVsize ) ;
2023-06-30 14:46:44 -04:00
mempool [ txid ] . effectiveFeePerVsize = rate ;
2024-04-06 03:22:56 +00:00
mempool [ txid ] . cpfpChecked = true ;
2023-05-08 19:03:39 -06:00
}
}
2023-06-28 19:51:52 -04:00
const lastBlockIndex = blocks . length - 1 ;
2023-05-10 12:59:05 -06:00
let hasBlockStack = blocks . length >= 8 ;
let stackWeight ;
let feeStatsCalculator : OnlineFeeStatsCalculator | void ;
if ( hasBlockStack ) {
2023-06-26 14:22:10 -04:00
if ( blockWeights && blockWeights [ 7 ] !== null ) {
stackWeight = blockWeights [ 7 ] ;
} else {
2023-06-28 19:51:52 -04:00
stackWeight = blocks [ lastBlockIndex ] . reduce ( ( total , tx ) = > total + ( mempool [ tx ] ? . weight || 0 ) , 0 ) ;
2023-06-26 14:22:10 -04:00
}
2023-05-10 12:59:05 -06:00
hasBlockStack = stackWeight > config . MEMPOOL . BLOCK_WEIGHT_UNITS ;
2023-06-07 13:22:27 -04:00
feeStatsCalculator = new OnlineFeeStatsCalculator ( stackWeight , 0.5 , [ 10 , 20 , 30 , 40 , 50 , 60 , 70 , 80 , 90 ] ) ;
2023-05-10 12:59:05 -06:00
}
2023-06-30 14:46:44 -04:00
for ( const cluster of clusters ) {
2023-06-26 14:22:10 -04:00
for ( const memberTxid of cluster ) {
2023-06-28 19:51:52 -04:00
const mempoolTx = mempool [ memberTxid ] ;
if ( mempoolTx ) {
2023-06-26 14:22:10 -04:00
const ancestors : Ancestor [ ] = [ ] ;
const descendants : Ancestor [ ] = [ ] ;
let matched = false ;
cluster . forEach ( txid = > {
if ( txid === memberTxid ) {
matched = true ;
} else {
2023-02-17 17:54:29 -06:00
if ( ! mempool [ txid ] ) {
console . log ( 'txid missing from mempool! ' , txid , candidates ? . txs [ txid ] ) ;
}
2023-06-26 14:22:10 -04:00
const relative = {
txid : txid ,
fee : mempool [ txid ] . fee ,
weight : ( mempool [ txid ] . adjustedVsize * 4 ) ,
} ;
if ( matched ) {
descendants . push ( relative ) ;
2023-07-16 13:49:33 +09:00
mempoolTx . lastBoosted = Math . max ( mempoolTx . lastBoosted || 0 , mempool [ txid ] . firstSeen || 0 ) ;
2023-06-26 14:22:10 -04:00
} else {
ancestors . push ( relative ) ;
}
}
} ) ;
2023-08-27 00:30:33 +09:00
if ( mempoolTx . ancestors ? . length !== ancestors . length || mempoolTx . descendants ? . length !== descendants . length ) {
mempoolTx . cpfpDirty = true ;
}
2023-06-28 19:51:52 -04:00
Object . assign ( mempoolTx , { ancestors , descendants , bestDescendant : null , cpfpChecked : true } ) ;
2023-06-26 14:22:10 -04:00
}
}
}
2023-07-19 11:18:04 +09:00
const isAccelerated : { [ txid : string ] : boolean } = { } ;
2023-05-08 19:03:39 -06:00
const sizeLimit = ( config . MEMPOOL . BLOCK_WEIGHT_UNITS / 4 ) * 1.2 ;
2022-12-06 05:51:44 +09:00
// update this thread's mempool with the results
2023-06-30 14:46:44 -04:00
let mempoolTx : MempoolTransactionExtended ;
const mempoolBlocks : MempoolBlockWithTransactions [ ] = blocks . map ( ( block , blockIndex ) = > {
2023-05-08 19:03:39 -06:00
let totalSize = 0 ;
let totalVsize = 0 ;
let totalWeight = 0 ;
let totalFees = 0 ;
2023-05-29 15:56:29 -04:00
const transactions : MempoolTransactionExtended [ ] = [ ] ;
2023-02-17 17:54:29 -06:00
// backfill purged transactions
if ( candidates ? . txs && blockIndex === blocks . length - 1 ) {
for ( const txid of Object . keys ( mempool ) ) {
if ( ! candidates . txs [ txid ] ) {
block . push ( txid ) ;
}
}
}
2023-06-26 14:22:10 -04:00
for ( const txid of block ) {
2023-05-08 19:03:39 -06:00
if ( txid ) {
mempoolTx = mempool [ txid ] ;
2023-04-21 08:40:21 +09:00
// save position in projected blocks
2023-05-08 19:03:39 -06:00
mempoolTx . position = {
2023-04-21 08:40:21 +09:00
block : blockIndex ,
2023-05-08 19:03:39 -06:00
vsize : totalVsize + ( mempoolTx . vsize / 2 ) ,
2023-04-21 08:40:21 +09:00
} ;
2023-05-08 19:03:39 -06:00
2023-07-18 15:05:44 +09:00
const acceleration = accelerations [ txid ] ;
2023-07-19 11:18:04 +09:00
if ( isAccelerated [ txid ] || ( acceleration && ( ! accelerationPool || acceleration . pools . includes ( accelerationPool ) ) ) ) {
2023-08-27 00:30:33 +09:00
if ( ! mempoolTx . acceleration ) {
mempoolTx . cpfpDirty = true ;
}
2023-07-18 15:05:44 +09:00
mempoolTx . acceleration = true ;
for ( const ancestor of mempoolTx . ancestors || [ ] ) {
2023-08-27 00:30:33 +09:00
if ( ! mempool [ ancestor . txid ] . acceleration ) {
mempool [ ancestor . txid ] . cpfpDirty = true ;
}
2023-07-18 15:05:44 +09:00
mempool [ ancestor . txid ] . acceleration = true ;
2023-07-19 11:18:04 +09:00
isAccelerated [ ancestor . txid ] = true ;
2023-07-18 15:05:44 +09:00
}
} else {
2023-08-27 00:30:33 +09:00
if ( mempoolTx . acceleration ) {
mempoolTx . cpfpDirty = true ;
}
2023-07-18 15:05:44 +09:00
delete mempoolTx . acceleration ;
}
2023-06-03 16:54:12 -04:00
2023-05-10 12:59:05 -06:00
// online calculation of stack-of-blocks fee stats
2023-06-28 19:51:52 -04:00
if ( hasBlockStack && blockIndex === lastBlockIndex && feeStatsCalculator ) {
2023-05-10 12:59:05 -06:00
feeStatsCalculator . processNext ( mempoolTx ) ;
}
2023-05-08 19:03:39 -06:00
totalSize += mempoolTx . size ;
totalVsize += mempoolTx . vsize ;
totalWeight += mempoolTx . weight ;
totalFees += mempoolTx . fee ;
2023-04-21 08:40:21 +09:00
2023-05-08 19:03:39 -06:00
if ( totalVsize <= sizeLimit ) {
transactions . push ( mempoolTx ) ;
2022-12-06 05:51:44 +09:00
}
2023-05-08 19:03:39 -06:00
}
}
2023-06-30 14:46:44 -04:00
return this . dataToMempoolBlocks (
block ,
2023-05-08 19:03:39 -06:00
transactions ,
totalSize ,
totalWeight ,
2023-05-10 12:59:05 -06:00
totalFees ,
2023-06-30 14:46:44 -04:00
( hasBlockStack && blockIndex === lastBlockIndex && feeStatsCalculator ) ? feeStatsCalculator . getRawFeeStats ( ) : undefined ,
) ;
2023-05-10 12:59:05 -06:00
} ) ;
2022-12-06 05:51:44 +09:00
2023-01-30 16:26:37 -06:00
if ( saveResults ) {
const deltas = this . calculateMempoolDeltas ( this . mempoolBlocks , mempoolBlocks ) ;
this . mempoolBlocks = mempoolBlocks ;
this . mempoolBlockDeltas = deltas ;
2023-12-05 06:54:31 +00:00
2023-01-30 16:26:37 -06:00
}
2022-12-06 05:51:44 +09:00
2023-01-30 16:26:37 -06:00
return mempoolBlocks ;
2022-10-10 22:13:04 +00:00
}
2023-05-29 15:56:29 -04:00
private dataToMempoolBlocks ( transactionIds : string [ ] , transactions : MempoolTransactionExtended [ ] , totalSize : number , totalWeight : number , totalFees : number , feeStats? : EffectiveFeeStats ) : MempoolBlockWithTransactions {
2023-05-10 12:59:05 -06:00
if ( ! feeStats ) {
2024-03-31 05:40:51 +00:00
feeStats = Common . calcEffectiveFeeStatistics ( transactions ) ;
2023-05-10 12:59:05 -06:00
}
2020-02-16 22:15:07 +07:00
return {
2022-12-06 05:51:44 +09:00
blockSize : totalSize ,
2023-05-08 19:03:39 -06:00
blockVSize : ( totalWeight / 4 ) , // fractional vsize to avoid rounding errors
nTx : transactionIds.length ,
totalFees : totalFees ,
2023-03-12 14:51:06 +09:00
medianFee : feeStats.medianFee , // Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
feeRange : feeStats.feeRange , //Common.getFeesInRange(transactions, rangeLength),
2023-05-08 19:03:39 -06:00
transactionIds : transactionIds ,
2023-12-05 06:54:31 +00:00
transactions : transactions.map ( ( tx ) = > Common . classifyTransaction ( tx ) ) ,
2020-02-16 22:15:07 +07:00
} ;
}
2023-05-02 17:46:48 -06:00
private resetUids ( ) : void {
this . uidMap . clear ( ) ;
2024-01-08 00:03:05 +00:00
this . txidMap . clear ( ) ;
2023-05-02 17:46:48 -06:00
this . nextUid = 1 ;
}
2023-06-23 18:37:03 -04:00
private setUid ( tx : MempoolTransactionExtended , skipSet = false ) : number {
2024-01-08 00:03:05 +00:00
if ( ! this . txidMap . has ( tx . txid ) || ! skipSet ) {
2023-06-23 18:37:03 -04:00
const uid = this . nextUid ;
this . nextUid ++ ;
this . uidMap . set ( uid , tx . txid ) ;
2024-01-08 00:03:05 +00:00
this . txidMap . set ( tx . txid , uid ) ;
2023-06-23 18:37:03 -04:00
tx . uid = uid ;
return uid ;
} else {
2024-01-08 00:03:05 +00:00
tx . uid = this . txidMap . get ( tx . txid ) as number ;
2023-06-23 18:37:03 -04:00
return tx . uid ;
}
2023-05-02 17:46:48 -06:00
}
2023-05-29 15:56:29 -04:00
private getUid ( tx : MempoolTransactionExtended ) : number | void {
2024-01-08 00:03:05 +00:00
if ( tx ) {
return this . txidMap . get ( tx . txid ) ;
2023-05-02 17:46:48 -06:00
}
}
2024-01-07 17:57:36 +00:00
private removeUids ( txs : MempoolTransactionExtended [ ] ) : void {
for ( const tx of txs ) {
2024-01-08 00:03:05 +00:00
const uid = this . txidMap . get ( tx . txid ) ;
if ( uid != null ) {
this . uidMap . delete ( uid ) ;
this . txidMap . delete ( tx . txid ) ;
2024-01-07 17:57:36 +00:00
}
2024-01-08 00:03:05 +00:00
tx . uid = undefined ;
2023-05-02 17:46:48 -06:00
}
}
2023-05-08 19:03:39 -06:00
private convertResultTxids ( { blocks , rates , clusters } : { blocks : number [ ] [ ] , rates : Map < number , number > , clusters : Map < number , number [ ] > } )
: { blocks : string [ ] [ ] , rates : { [ root : string ] : number } , clusters : { [ root : string ] : string [ ] } } {
const convertedBlocks : string [ ] [ ] = blocks . map ( block = > block . map ( uid = > {
return this . uidMap . get ( uid ) || '' ;
} ) ) ;
const convertedRates = { } ;
for ( const rateUid of rates . keys ( ) ) {
const rateTxid = this . uidMap . get ( rateUid ) ;
if ( rateTxid ) {
convertedRates [ rateTxid ] = rates . get ( rateUid ) ;
2023-05-02 17:46:48 -06:00
}
}
const convertedClusters = { } ;
for ( const rootUid of clusters . keys ( ) ) {
const rootTxid = this . uidMap . get ( rootUid ) ;
if ( rootTxid ) {
const members = clusters . get ( rootUid ) ? . map ( uid = > {
return this . uidMap . get ( uid ) ;
} ) ;
convertedClusters [ rootTxid ] = members ;
}
}
2023-05-08 19:03:39 -06:00
return { blocks : convertedBlocks , rates : convertedRates , clusters : convertedClusters } as { blocks : string [ ] [ ] , rates : { [ root : string ] : number } , clusters : { [ root : string ] : string [ ] } } ;
2023-05-02 17:46:48 -06:00
}
2023-06-23 16:42:58 -04:00
2024-01-09 17:08:25 +00:00
private convertNapiResultTxids ( { blocks , blockWeights , rates , clusters , overflow } : GbtResult )
: { blocks : string [ ] [ ] , blockWeights : number [ ] , rates : [ string , number ] [ ] , clusters : string [ ] [ ] , overflow : string [ ] } {
2023-06-23 16:42:58 -04:00
const convertedBlocks : string [ ] [ ] = blocks . map ( block = > block . map ( uid = > {
2023-06-25 21:23:30 -04:00
const txid = this . uidMap . get ( uid ) ;
if ( txid !== undefined ) {
return txid ;
} else {
throw new Error ( 'GBT returned a block containing a transaction with unknown uid' ) ;
}
2023-06-23 16:42:58 -04:00
} ) ) ;
2023-06-30 14:46:44 -04:00
const convertedRates : [ string , number ] [ ] = [ ] ;
for ( const [ rateUid , rate ] of rates ) {
const rateTxid = this . uidMap . get ( rateUid ) as string ;
convertedRates . push ( [ rateTxid , rate ] ) ;
2023-06-23 16:42:58 -04:00
}
2023-06-30 14:46:44 -04:00
const convertedClusters : string [ ] [ ] = [ ] ;
for ( const cluster of clusters ) {
convertedClusters . push ( cluster . map ( uid = > this . uidMap . get ( uid ) ) as string [ ] ) ;
2023-06-23 16:42:58 -04:00
}
2024-01-09 17:08:25 +00:00
const convertedOverflow : string [ ] = overflow . map ( uid = > {
const txid = this . uidMap . get ( uid ) ;
if ( txid !== undefined ) {
return txid ;
} else {
throw new Error ( 'GBT returned an unmineable transaction with unknown uid' ) ;
}
} ) ;
return { blocks : convertedBlocks , blockWeights , rates : convertedRates , clusters : convertedClusters , overflow : convertedOverflow } ;
2023-06-23 16:42:58 -04:00
}
2024-02-08 22:40:22 +00:00
public compressTx ( tx : TransactionClassified ) : TransactionCompressed {
if ( tx . acc ) {
return [
tx . txid ,
tx . fee ,
tx . vsize ,
tx . value ,
Math . round ( ( tx . rate || ( tx . fee / tx . vsize ) ) * 100 ) / 100 ,
tx . flags ,
2024-03-31 03:45:48 +00:00
tx . time || 0 ,
1 ,
2024-02-08 22:40:22 +00:00
] ;
} else {
return [
tx . txid ,
tx . fee ,
tx . vsize ,
tx . value ,
Math . round ( ( tx . rate || ( tx . fee / tx . vsize ) ) * 100 ) / 100 ,
tx . flags ,
2024-03-31 03:45:48 +00:00
tx . time || 0 ,
2024-02-08 22:40:22 +00:00
] ;
}
}
public compressDeltaChange ( tx : TransactionClassified ) : MempoolDeltaChange {
return [
tx . txid ,
Math . round ( ( tx . rate || ( tx . fee / tx . vsize ) ) * 100 ) / 100 ,
tx . flags ,
tx . acc ? 1 : 0 ,
] ;
}
2020-02-16 22:15:07 +07:00
}
export default new MempoolBlocks ( ) ;