optimize data structures for advanced GBT algorithm
This commit is contained in:
		
							parent
							
								
									07d9315bbe
								
							
						
					
					
						commit
						428d4fc6ab
					
				@ -1,5 +1,5 @@
 | 
				
			|||||||
import logger from '../logger';
 | 
					import logger from '../logger';
 | 
				
			||||||
import { MempoolBlock, TransactionExtended, ThreadTransaction, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor } from '../mempool.interfaces';
 | 
					import { MempoolBlock, TransactionExtended, ThreadTransaction, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction } from '../mempool.interfaces';
 | 
				
			||||||
import { Common } from './common';
 | 
					import { Common } from './common';
 | 
				
			||||||
import config from '../config';
 | 
					import config from '../config';
 | 
				
			||||||
import { Worker } from 'worker_threads';
 | 
					import { Worker } from 'worker_threads';
 | 
				
			||||||
@ -10,6 +10,9 @@ class MempoolBlocks {
 | 
				
			|||||||
  private mempoolBlockDeltas: MempoolBlockDelta[] = [];
 | 
					  private mempoolBlockDeltas: MempoolBlockDelta[] = [];
 | 
				
			||||||
  private txSelectionWorker: Worker | null = null;
 | 
					  private txSelectionWorker: Worker | null = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private nextUid: number = 1;
 | 
				
			||||||
 | 
					  private uidMap: Map<number, string> = new Map(); // map short numerical uids to full txids
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor() {}
 | 
					  constructor() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public getMempoolBlocks(): MempoolBlock[] {
 | 
					  public getMempoolBlocks(): MempoolBlock[] {
 | 
				
			||||||
@ -175,18 +178,26 @@ class MempoolBlocks {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public async $makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, saveResults: boolean = false): Promise<MempoolBlockWithTransactions[]> {
 | 
					  public async $makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, saveResults: boolean = false): Promise<MempoolBlockWithTransactions[]> {
 | 
				
			||||||
 | 
					    // reset mempool short ids
 | 
				
			||||||
 | 
					    this.resetUids();
 | 
				
			||||||
 | 
					    for (const tx of Object.values(newMempool)) {
 | 
				
			||||||
 | 
					      this.setUid(tx);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // prepare a stripped down version of the mempool with only the minimum necessary data
 | 
					    // 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
 | 
					    // to reduce the overhead of passing this data to the worker thread
 | 
				
			||||||
    const strippedMempool: { [txid: string]: ThreadTransaction } = {};
 | 
					    const strippedMempool: Map<number, CompactThreadTransaction> = new Map();
 | 
				
			||||||
    Object.values(newMempool).forEach(entry => {
 | 
					    Object.values(newMempool).forEach(entry => {
 | 
				
			||||||
      strippedMempool[entry.txid] = {
 | 
					      if (entry.uid != null) {
 | 
				
			||||||
        txid: entry.txid,
 | 
					        strippedMempool.set(entry.uid, {
 | 
				
			||||||
        fee: entry.fee,
 | 
					          uid: entry.uid,
 | 
				
			||||||
        weight: entry.weight,
 | 
					          fee: entry.fee,
 | 
				
			||||||
        feePerVsize: entry.fee / (entry.weight / 4),
 | 
					          weight: entry.weight,
 | 
				
			||||||
        effectiveFeePerVsize: entry.fee / (entry.weight / 4),
 | 
					          feePerVsize: entry.fee / (entry.weight / 4),
 | 
				
			||||||
        vin: entry.vin.map(v => v.txid),
 | 
					          effectiveFeePerVsize: entry.fee / (entry.weight / 4),
 | 
				
			||||||
      };
 | 
					          inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => uid != null) as number[],
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // (re)initialize tx selection worker thread
 | 
					    // (re)initialize tx selection worker thread
 | 
				
			||||||
@ -205,7 +216,7 @@ class MempoolBlocks {
 | 
				
			|||||||
    // run the block construction algorithm in a separate thread, and wait for a result
 | 
					    // run the block construction algorithm in a separate thread, and wait for a result
 | 
				
			||||||
    let threadErrorListener;
 | 
					    let threadErrorListener;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const workerResultPromise = new Promise<{ blocks: ThreadTransaction[][], clusters: { [root: string]: string[] } }>((resolve, reject) => {
 | 
					      const workerResultPromise = new Promise<{ blocks: CompactThreadTransaction[][], clusters: Map<number, number[]> }>((resolve, reject) => {
 | 
				
			||||||
        threadErrorListener = reject;
 | 
					        threadErrorListener = reject;
 | 
				
			||||||
        this.txSelectionWorker?.once('message', (result): void => {
 | 
					        this.txSelectionWorker?.once('message', (result): void => {
 | 
				
			||||||
          resolve(result);
 | 
					          resolve(result);
 | 
				
			||||||
@ -213,7 +224,7 @@ class MempoolBlocks {
 | 
				
			|||||||
        this.txSelectionWorker?.once('error', reject);
 | 
					        this.txSelectionWorker?.once('error', reject);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      this.txSelectionWorker.postMessage({ type: 'set', mempool: strippedMempool });
 | 
					      this.txSelectionWorker.postMessage({ type: 'set', mempool: strippedMempool });
 | 
				
			||||||
      let { blocks, clusters } = await workerResultPromise;
 | 
					      let { blocks, clusters } = this.convertResultTxids(await workerResultPromise);
 | 
				
			||||||
      // filter out stale transactions
 | 
					      // filter out stale transactions
 | 
				
			||||||
      const unfilteredCount = blocks.reduce((total, block) => { return total + block.length; }, 0);
 | 
					      const unfilteredCount = blocks.reduce((total, block) => { return total + block.length; }, 0);
 | 
				
			||||||
      blocks = blocks.map(block => block.filter(tx => (tx.txid && tx.txid in newMempool)));
 | 
					      blocks = blocks.map(block => block.filter(tx => (tx.txid && tx.txid in newMempool)));
 | 
				
			||||||
@ -232,37 +243,42 @@ class MempoolBlocks {
 | 
				
			|||||||
    return this.mempoolBlocks;
 | 
					    return this.mempoolBlocks;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public async $updateBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, added: TransactionExtended[], removed: string[], saveResults: boolean = false): Promise<void> {
 | 
					  public async $updateBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, added: TransactionExtended[], removed: TransactionExtended[], saveResults: boolean = false): Promise<void> {
 | 
				
			||||||
    if (!this.txSelectionWorker) {
 | 
					    if (!this.txSelectionWorker) {
 | 
				
			||||||
      // need to reset the worker
 | 
					      // need to reset the worker
 | 
				
			||||||
      await this.$makeBlockTemplates(newMempool, saveResults);
 | 
					      await this.$makeBlockTemplates(newMempool, saveResults);
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const tx of Object.values(added)) {
 | 
				
			||||||
 | 
					      this.setUid(tx);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const removedUids = removed.map(tx => this.getUid(tx)).filter(uid => uid != null) as number[];
 | 
				
			||||||
    // prepare a stripped down version of the mempool with only the minimum necessary data
 | 
					    // 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
 | 
					    // to reduce the overhead of passing this data to the worker thread
 | 
				
			||||||
    const addedStripped: ThreadTransaction[] = added.map(entry => {
 | 
					    const addedStripped: CompactThreadTransaction[] = added.filter(entry => entry.uid != null).map(entry => {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        txid: entry.txid,
 | 
					        uid: entry.uid || 0,
 | 
				
			||||||
        fee: entry.fee,
 | 
					        fee: entry.fee,
 | 
				
			||||||
        weight: entry.weight,
 | 
					        weight: entry.weight,
 | 
				
			||||||
        feePerVsize: entry.fee / (entry.weight / 4),
 | 
					        feePerVsize: entry.fee / (entry.weight / 4),
 | 
				
			||||||
        effectiveFeePerVsize: entry.fee / (entry.weight / 4),
 | 
					        effectiveFeePerVsize: entry.fee / (entry.weight / 4),
 | 
				
			||||||
        vin: entry.vin.map(v => v.txid),
 | 
					        inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => uid != null) as number[],
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // run the block construction algorithm in a separate thread, and wait for a result
 | 
					    // run the block construction algorithm in a separate thread, and wait for a result
 | 
				
			||||||
    let threadErrorListener;
 | 
					    let threadErrorListener;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const workerResultPromise = new Promise<{ blocks: ThreadTransaction[][], clusters: { [root: string]: string[] } }>((resolve, reject) => {
 | 
					      const workerResultPromise = new Promise<{ blocks: CompactThreadTransaction[][], clusters: Map<number, number[]> }>((resolve, reject) => {
 | 
				
			||||||
        threadErrorListener = reject;
 | 
					        threadErrorListener = reject;
 | 
				
			||||||
        this.txSelectionWorker?.once('message', (result): void => {
 | 
					        this.txSelectionWorker?.once('message', (result): void => {
 | 
				
			||||||
          resolve(result);
 | 
					          resolve(result);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        this.txSelectionWorker?.once('error', reject);
 | 
					        this.txSelectionWorker?.once('error', reject);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      this.txSelectionWorker.postMessage({ type: 'update', added: addedStripped, removed });
 | 
					      this.txSelectionWorker.postMessage({ type: 'update', added: addedStripped, removed: removedUids });
 | 
				
			||||||
      let { blocks, clusters } = await workerResultPromise;
 | 
					      let { blocks, clusters } = this.convertResultTxids(await workerResultPromise);
 | 
				
			||||||
      // filter out stale transactions
 | 
					      // filter out stale transactions
 | 
				
			||||||
      const unfilteredCount = blocks.reduce((total, block) => { return total + block.length; }, 0);
 | 
					      const unfilteredCount = blocks.reduce((total, block) => { return total + block.length; }, 0);
 | 
				
			||||||
      blocks = blocks.map(block => block.filter(tx => (tx.txid && tx.txid in newMempool)));
 | 
					      blocks = blocks.map(block => block.filter(tx => (tx.txid && tx.txid in newMempool)));
 | 
				
			||||||
@ -271,6 +287,8 @@ class MempoolBlocks {
 | 
				
			|||||||
        logger.warn(`tx selection worker thread returned ${unfilteredCount - filteredCount} stale transactions from updateBlockTemplates`);
 | 
					        logger.warn(`tx selection worker thread returned ${unfilteredCount - filteredCount} stale transactions from updateBlockTemplates`);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.removeUids(removedUids);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // clean up thread error listener
 | 
					      // clean up thread error listener
 | 
				
			||||||
      this.txSelectionWorker?.removeListener('error', threadErrorListener);
 | 
					      this.txSelectionWorker?.removeListener('error', threadErrorListener);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -280,7 +298,7 @@ class MempoolBlocks {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private processBlockTemplates(mempool, blocks, clusters, saveResults): MempoolBlockWithTransactions[] {
 | 
					  private processBlockTemplates(mempool, blocks: ThreadTransaction[][], clusters, saveResults): MempoolBlockWithTransactions[] {
 | 
				
			||||||
    // update this thread's mempool with the results
 | 
					    // update this thread's mempool with the results
 | 
				
			||||||
    blocks.forEach((block, blockIndex) => {
 | 
					    blocks.forEach((block, blockIndex) => {
 | 
				
			||||||
      let runningVsize = 0;
 | 
					      let runningVsize = 0;
 | 
				
			||||||
@ -371,6 +389,54 @@ class MempoolBlocks {
 | 
				
			|||||||
      transactions: fitTransactions.map((tx) => Common.stripTransaction(tx)),
 | 
					      transactions: fitTransactions.map((tx) => Common.stripTransaction(tx)),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private resetUids(): void {
 | 
				
			||||||
 | 
					    this.uidMap.clear();
 | 
				
			||||||
 | 
					    this.nextUid = 1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private setUid(tx: TransactionExtended): number {
 | 
				
			||||||
 | 
					    const uid = this.nextUid;
 | 
				
			||||||
 | 
					    this.nextUid++;
 | 
				
			||||||
 | 
					    this.uidMap.set(uid, tx.txid);
 | 
				
			||||||
 | 
					    tx.uid = uid;
 | 
				
			||||||
 | 
					    return uid;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private getUid(tx: TransactionExtended): number | void {
 | 
				
			||||||
 | 
					    if (tx?.uid != null && this.uidMap.has(tx.uid)) {
 | 
				
			||||||
 | 
					      return tx.uid;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private removeUids(uids: number[]): void {
 | 
				
			||||||
 | 
					    for (const uid of uids) {
 | 
				
			||||||
 | 
					      this.uidMap.delete(uid);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private convertResultTxids({ blocks, clusters }: { blocks: any[][], clusters: Map<number, number[]>})
 | 
				
			||||||
 | 
					    : { blocks: ThreadTransaction[][], clusters: { [root: string]: string[] }} {
 | 
				
			||||||
 | 
					    for (const block of blocks) {
 | 
				
			||||||
 | 
					      for (const tx of block) {
 | 
				
			||||||
 | 
					        tx.txid = this.uidMap.get(tx.uid);
 | 
				
			||||||
 | 
					        if (tx.cpfpRoot) {
 | 
				
			||||||
 | 
					          tx.cpfpRoot = this.uidMap.get(tx.cpfpRoot);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    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;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return { blocks, clusters: convertedClusters } as { blocks: ThreadTransaction[][], clusters: { [root: string]: string[] }};
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default new MempoolBlocks();
 | 
					export default new MempoolBlocks();
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,10 @@
 | 
				
			|||||||
import config from '../config';
 | 
					import config from '../config';
 | 
				
			||||||
import logger from '../logger';
 | 
					import logger from '../logger';
 | 
				
			||||||
import { ThreadTransaction, MempoolBlockWithTransactions, AuditTransaction } from '../mempool.interfaces';
 | 
					import { CompactThreadTransaction, MempoolBlockWithTransactions, AuditTransaction } from '../mempool.interfaces';
 | 
				
			||||||
import { PairingHeap } from '../utils/pairing-heap';
 | 
					import { PairingHeap } from '../utils/pairing-heap';
 | 
				
			||||||
import { parentPort } from 'worker_threads';
 | 
					import { parentPort } from 'worker_threads';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let mempool: { [txid: string]: ThreadTransaction } = {};
 | 
					let mempool: Map<number, CompactThreadTransaction> = new Map();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (parentPort) {
 | 
					if (parentPort) {
 | 
				
			||||||
  parentPort.on('message', (params) => {
 | 
					  parentPort.on('message', (params) => {
 | 
				
			||||||
@ -12,10 +12,10 @@ if (parentPort) {
 | 
				
			|||||||
      mempool = params.mempool;
 | 
					      mempool = params.mempool;
 | 
				
			||||||
    } else if (params.type === 'update') {
 | 
					    } else if (params.type === 'update') {
 | 
				
			||||||
      params.added.forEach(tx => {
 | 
					      params.added.forEach(tx => {
 | 
				
			||||||
        mempool[tx.txid] = tx;
 | 
					        mempool.set(tx.uid, tx);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      params.removed.forEach(txid => {
 | 
					      params.removed.forEach(uid => {
 | 
				
			||||||
        delete mempool[txid];
 | 
					        mempool.delete(uid);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
@ -23,7 +23,7 @@ if (parentPort) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // return the result to main thread.
 | 
					    // return the result to main thread.
 | 
				
			||||||
    if (parentPort) {
 | 
					    if (parentPort) {
 | 
				
			||||||
     parentPort.postMessage({ blocks, clusters });
 | 
					      parentPort.postMessage({ blocks, clusters });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -32,26 +32,25 @@ if (parentPort) {
 | 
				
			|||||||
* Build projected mempool blocks using an approximation of the transaction selection algorithm from Bitcoin Core
 | 
					* Build projected mempool blocks using an approximation of the transaction selection algorithm from Bitcoin Core
 | 
				
			||||||
* (see BlockAssembler in https://github.com/bitcoin/bitcoin/blob/master/src/node/miner.cpp)
 | 
					* (see BlockAssembler in https://github.com/bitcoin/bitcoin/blob/master/src/node/miner.cpp)
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
 | 
					function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
 | 
				
			||||||
  : { blocks: ThreadTransaction[][], clusters: { [root: string]: string[] } } {
 | 
					  : { blocks: CompactThreadTransaction[][], clusters: Map<number, number[]> } {
 | 
				
			||||||
  const start = Date.now();
 | 
					  const start = Date.now();
 | 
				
			||||||
  const auditPool: { [txid: string]: AuditTransaction } = {};
 | 
					  const auditPool: Map<number, AuditTransaction> = new Map();
 | 
				
			||||||
  const mempoolArray: AuditTransaction[] = [];
 | 
					  const mempoolArray: AuditTransaction[] = [];
 | 
				
			||||||
  const restOfArray: ThreadTransaction[] = [];
 | 
					  const restOfArray: CompactThreadTransaction[] = [];
 | 
				
			||||||
  const cpfpClusters: { [root: string]: string[] } = {};
 | 
					  const cpfpClusters: Map<number, number[]> = new Map();
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  // grab the top feerate txs up to maxWeight
 | 
					  mempool.forEach(tx => {
 | 
				
			||||||
  Object.values(mempool).sort((a, b) => b.feePerVsize - a.feePerVsize).forEach(tx => {
 | 
					 | 
				
			||||||
    // initializing everything up front helps V8 optimize property access later
 | 
					    // initializing everything up front helps V8 optimize property access later
 | 
				
			||||||
    auditPool[tx.txid] = {
 | 
					    auditPool.set(tx.uid, {
 | 
				
			||||||
      txid: tx.txid,
 | 
					      uid: tx.uid,
 | 
				
			||||||
      fee: tx.fee,
 | 
					      fee: tx.fee,
 | 
				
			||||||
      weight: tx.weight,
 | 
					      weight: tx.weight,
 | 
				
			||||||
      feePerVsize: tx.feePerVsize,
 | 
					      feePerVsize: tx.feePerVsize,
 | 
				
			||||||
      effectiveFeePerVsize: tx.feePerVsize,
 | 
					      effectiveFeePerVsize: tx.feePerVsize,
 | 
				
			||||||
      vin: tx.vin,
 | 
					      inputs: tx.inputs || [],
 | 
				
			||||||
      relativesSet: false,
 | 
					      relativesSet: false,
 | 
				
			||||||
      ancestorMap: new Map<string, AuditTransaction>(),
 | 
					      ancestorMap: new Map<number, AuditTransaction>(),
 | 
				
			||||||
      children: new Set<AuditTransaction>(),
 | 
					      children: new Set<AuditTransaction>(),
 | 
				
			||||||
      ancestorFee: 0,
 | 
					      ancestorFee: 0,
 | 
				
			||||||
      ancestorWeight: 0,
 | 
					      ancestorWeight: 0,
 | 
				
			||||||
@ -59,8 +58,8 @@ function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
 | 
				
			|||||||
      used: false,
 | 
					      used: false,
 | 
				
			||||||
      modified: false,
 | 
					      modified: false,
 | 
				
			||||||
      modifiedNode: null,
 | 
					      modifiedNode: null,
 | 
				
			||||||
    };
 | 
					    });
 | 
				
			||||||
    mempoolArray.push(auditPool[tx.txid]);
 | 
					    mempoolArray.push(auditPool.get(tx.uid) as AuditTransaction);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Build relatives graph & calculate ancestor scores
 | 
					  // Build relatives graph & calculate ancestor scores
 | 
				
			||||||
@ -73,8 +72,8 @@ function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
 | 
				
			|||||||
  // Sort by descending ancestor score
 | 
					  // Sort by descending ancestor score
 | 
				
			||||||
  mempoolArray.sort((a, b) => {
 | 
					  mempoolArray.sort((a, b) => {
 | 
				
			||||||
    if (b.score === a.score) {
 | 
					    if (b.score === a.score) {
 | 
				
			||||||
      // tie-break by lexicographic txid order for stability
 | 
					      // tie-break by uid for stability
 | 
				
			||||||
      return a.txid < b.txid ? -1 : 1;
 | 
					      return a.uid < b.uid ? -1 : 1;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return (b.score || 0) - (a.score || 0);
 | 
					      return (b.score || 0) - (a.score || 0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -82,14 +81,14 @@ function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // Build blocks by greedily choosing the highest feerate package
 | 
					  // Build blocks by greedily choosing the highest feerate package
 | 
				
			||||||
  // (i.e. the package rooted in the transaction with the best ancestor score)
 | 
					  // (i.e. the package rooted in the transaction with the best ancestor score)
 | 
				
			||||||
  const blocks: ThreadTransaction[][] = [];
 | 
					  const blocks: CompactThreadTransaction[][] = [];
 | 
				
			||||||
  let blockWeight = 4000;
 | 
					  let blockWeight = 4000;
 | 
				
			||||||
  let blockSize = 0;
 | 
					  let blockSize = 0;
 | 
				
			||||||
  let transactions: AuditTransaction[] = [];
 | 
					  let transactions: AuditTransaction[] = [];
 | 
				
			||||||
  const modified: PairingHeap<AuditTransaction> = new PairingHeap((a, b): boolean => {
 | 
					  const modified: PairingHeap<AuditTransaction> = new PairingHeap((a, b): boolean => {
 | 
				
			||||||
    if (a.score === b.score) {
 | 
					    if (a.score === b.score) {
 | 
				
			||||||
      // tie-break by lexicographic txid order for stability
 | 
					      // tie-break by uid for stability
 | 
				
			||||||
      return a.txid > b.txid;
 | 
					      return a.uid > b.uid;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return (a.score || 0) > (b.score || 0);
 | 
					      return (a.score || 0) > (b.score || 0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -126,20 +125,23 @@ function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
 | 
				
			|||||||
        const sortedTxSet = [...ancestors.sort((a, b) => { return (a.ancestorMap.size || 0) - (b.ancestorMap.size || 0); }), nextTx];
 | 
					        const sortedTxSet = [...ancestors.sort((a, b) => { return (a.ancestorMap.size || 0) - (b.ancestorMap.size || 0); }), nextTx];
 | 
				
			||||||
        let isCluster = false;
 | 
					        let isCluster = false;
 | 
				
			||||||
        if (sortedTxSet.length > 1) {
 | 
					        if (sortedTxSet.length > 1) {
 | 
				
			||||||
          cpfpClusters[nextTx.txid] = sortedTxSet.map(tx => tx.txid);
 | 
					          cpfpClusters.set(nextTx.uid, sortedTxSet.map(tx => tx.uid));
 | 
				
			||||||
          isCluster = true;
 | 
					          isCluster = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        const effectiveFeeRate = nextTx.ancestorFee / (nextTx.ancestorWeight / 4);
 | 
					        const effectiveFeeRate = nextTx.ancestorFee / (nextTx.ancestorWeight / 4);
 | 
				
			||||||
        const used: AuditTransaction[] = [];
 | 
					        const used: AuditTransaction[] = [];
 | 
				
			||||||
        while (sortedTxSet.length) {
 | 
					        while (sortedTxSet.length) {
 | 
				
			||||||
          const ancestor = sortedTxSet.pop();
 | 
					          const ancestor = sortedTxSet.pop();
 | 
				
			||||||
          const mempoolTx = mempool[ancestor.txid];
 | 
					          const mempoolTx = mempool.get(ancestor.uid);
 | 
				
			||||||
 | 
					          if (!mempoolTx) {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
          ancestor.used = true;
 | 
					          ancestor.used = true;
 | 
				
			||||||
          ancestor.usedBy = nextTx.txid;
 | 
					          ancestor.usedBy = nextTx.uid;
 | 
				
			||||||
          // update original copy of this tx with effective fee rate & relatives data
 | 
					          // update original copy of this tx with effective fee rate & relatives data
 | 
				
			||||||
          mempoolTx.effectiveFeePerVsize = effectiveFeeRate;
 | 
					          mempoolTx.effectiveFeePerVsize = effectiveFeeRate;
 | 
				
			||||||
          if (isCluster) {
 | 
					          if (isCluster) {
 | 
				
			||||||
            mempoolTx.cpfpRoot = nextTx.txid;
 | 
					            mempoolTx.cpfpRoot = nextTx.uid;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          mempoolTx.cpfpChecked = true;
 | 
					          mempoolTx.cpfpChecked = true;
 | 
				
			||||||
          transactions.push(ancestor);
 | 
					          transactions.push(ancestor);
 | 
				
			||||||
@ -169,7 +171,7 @@ function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
 | 
				
			|||||||
    if ((exceededPackageTries || queueEmpty) && blocks.length < 7) {
 | 
					    if ((exceededPackageTries || queueEmpty) && blocks.length < 7) {
 | 
				
			||||||
      // construct this block
 | 
					      // construct this block
 | 
				
			||||||
      if (transactions.length) {
 | 
					      if (transactions.length) {
 | 
				
			||||||
        blocks.push(transactions.map(t => mempool[t.txid]));
 | 
					        blocks.push(transactions.map(t => mempool.get(t.uid) as CompactThreadTransaction));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // reset for the next block
 | 
					      // reset for the next block
 | 
				
			||||||
      transactions = [];
 | 
					      transactions = [];
 | 
				
			||||||
@ -194,7 +196,7 @@ function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  // add the final unbounded block if it contains any transactions
 | 
					  // add the final unbounded block if it contains any transactions
 | 
				
			||||||
  if (transactions.length > 0) {
 | 
					  if (transactions.length > 0) {
 | 
				
			||||||
    blocks.push(transactions.map(t => mempool[t.txid]));
 | 
					    blocks.push(transactions.map(t => mempool.get(t.uid) as CompactThreadTransaction));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const end = Date.now();
 | 
					  const end = Date.now();
 | 
				
			||||||
@ -208,10 +210,10 @@ function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
 | 
				
			|||||||
// recursion unavoidable, but should be limited to depth < 25 by mempool policy
 | 
					// recursion unavoidable, but should be limited to depth < 25 by mempool policy
 | 
				
			||||||
function setRelatives(
 | 
					function setRelatives(
 | 
				
			||||||
  tx: AuditTransaction,
 | 
					  tx: AuditTransaction,
 | 
				
			||||||
  mempool: { [txid: string]: AuditTransaction },
 | 
					  mempool: Map<number, AuditTransaction>,
 | 
				
			||||||
): void {
 | 
					): void {
 | 
				
			||||||
  for (const parent of tx.vin) {
 | 
					  for (const parent of tx.inputs) {
 | 
				
			||||||
    const parentTx = mempool[parent];
 | 
					    const parentTx = mempool.get(parent);
 | 
				
			||||||
    if (parentTx && !tx.ancestorMap?.has(parent)) {
 | 
					    if (parentTx && !tx.ancestorMap?.has(parent)) {
 | 
				
			||||||
      tx.ancestorMap.set(parent, parentTx);
 | 
					      tx.ancestorMap.set(parent, parentTx);
 | 
				
			||||||
      parentTx.children.add(tx);
 | 
					      parentTx.children.add(tx);
 | 
				
			||||||
@ -220,7 +222,7 @@ function setRelatives(
 | 
				
			|||||||
        setRelatives(parentTx, mempool);
 | 
					        setRelatives(parentTx, mempool);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      parentTx.ancestorMap.forEach((ancestor) => {
 | 
					      parentTx.ancestorMap.forEach((ancestor) => {
 | 
				
			||||||
        tx.ancestorMap.set(ancestor.txid, ancestor);
 | 
					        tx.ancestorMap.set(ancestor.uid, ancestor);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@ -238,7 +240,7 @@ function setRelatives(
 | 
				
			|||||||
// avoids recursion to limit call stack depth
 | 
					// avoids recursion to limit call stack depth
 | 
				
			||||||
function updateDescendants(
 | 
					function updateDescendants(
 | 
				
			||||||
  rootTx: AuditTransaction,
 | 
					  rootTx: AuditTransaction,
 | 
				
			||||||
  mempool: { [txid: string]: AuditTransaction },
 | 
					  mempool: Map<number, AuditTransaction>,
 | 
				
			||||||
  modified: PairingHeap<AuditTransaction>,
 | 
					  modified: PairingHeap<AuditTransaction>,
 | 
				
			||||||
): void {
 | 
					): void {
 | 
				
			||||||
  const descendantSet: Set<AuditTransaction> = new Set();
 | 
					  const descendantSet: Set<AuditTransaction> = new Set();
 | 
				
			||||||
@ -254,9 +256,9 @@ function updateDescendants(
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
  while (descendants.length) {
 | 
					  while (descendants.length) {
 | 
				
			||||||
    descendantTx = descendants.pop();
 | 
					    descendantTx = descendants.pop();
 | 
				
			||||||
    if (descendantTx && descendantTx.ancestorMap && descendantTx.ancestorMap.has(rootTx.txid)) {
 | 
					    if (descendantTx && descendantTx.ancestorMap && descendantTx.ancestorMap.has(rootTx.uid)) {
 | 
				
			||||||
      // remove tx as ancestor
 | 
					      // remove tx as ancestor
 | 
				
			||||||
      descendantTx.ancestorMap.delete(rootTx.txid);
 | 
					      descendantTx.ancestorMap.delete(rootTx.uid);
 | 
				
			||||||
      descendantTx.ancestorFee -= rootTx.fee;
 | 
					      descendantTx.ancestorFee -= rootTx.fee;
 | 
				
			||||||
      descendantTx.ancestorWeight -= rootTx.weight;
 | 
					      descendantTx.ancestorWeight -= rootTx.weight;
 | 
				
			||||||
      tmpScore = descendantTx.score;
 | 
					      tmpScore = descendantTx.score;
 | 
				
			||||||
 | 
				
			|||||||
@ -282,7 +282,7 @@ class WebsocketHandler {
 | 
				
			|||||||
    this.printLogs();
 | 
					    this.printLogs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
 | 
					    if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
 | 
				
			||||||
      await mempoolBlocks.$updateBlockTemplates(newMempool, newTransactions, deletedTransactions.map(tx => tx.txid), true);
 | 
					      await mempoolBlocks.$updateBlockTemplates(newMempool, newTransactions, deletedTransactions, true);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      mempoolBlocks.updateMempoolBlocks(newMempool, true);
 | 
					      mempoolBlocks.updateMempoolBlocks(newMempool, true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -84,17 +84,18 @@ export interface TransactionExtended extends IEsploraApi.Transaction {
 | 
				
			|||||||
    block: number,
 | 
					    block: number,
 | 
				
			||||||
    vsize: number,
 | 
					    vsize: number,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					  uid?: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface AuditTransaction {
 | 
					export interface AuditTransaction {
 | 
				
			||||||
  txid: string;
 | 
					  uid: number;
 | 
				
			||||||
  fee: number;
 | 
					  fee: number;
 | 
				
			||||||
  weight: number;
 | 
					  weight: number;
 | 
				
			||||||
  feePerVsize: number;
 | 
					  feePerVsize: number;
 | 
				
			||||||
  effectiveFeePerVsize: number;
 | 
					  effectiveFeePerVsize: number;
 | 
				
			||||||
  vin: string[];
 | 
					  inputs: number[];
 | 
				
			||||||
  relativesSet: boolean;
 | 
					  relativesSet: boolean;
 | 
				
			||||||
  ancestorMap: Map<string, AuditTransaction>;
 | 
					  ancestorMap: Map<number, AuditTransaction>;
 | 
				
			||||||
  children: Set<AuditTransaction>;
 | 
					  children: Set<AuditTransaction>;
 | 
				
			||||||
  ancestorFee: number;
 | 
					  ancestorFee: number;
 | 
				
			||||||
  ancestorWeight: number;
 | 
					  ancestorWeight: number;
 | 
				
			||||||
@ -104,13 +105,24 @@ export interface AuditTransaction {
 | 
				
			|||||||
  modifiedNode: HeapNode<AuditTransaction>;
 | 
					  modifiedNode: HeapNode<AuditTransaction>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface CompactThreadTransaction {
 | 
				
			||||||
 | 
					  uid: number;
 | 
				
			||||||
 | 
					  fee: number;
 | 
				
			||||||
 | 
					  weight: number;
 | 
				
			||||||
 | 
					  feePerVsize: number;
 | 
				
			||||||
 | 
					  effectiveFeePerVsize?: number;
 | 
				
			||||||
 | 
					  inputs: number[];
 | 
				
			||||||
 | 
					  cpfpRoot?: string;
 | 
				
			||||||
 | 
					  cpfpChecked?: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ThreadTransaction {
 | 
					export interface ThreadTransaction {
 | 
				
			||||||
  txid: string;
 | 
					  txid: string;
 | 
				
			||||||
  fee: number;
 | 
					  fee: number;
 | 
				
			||||||
  weight: number;
 | 
					  weight: number;
 | 
				
			||||||
  feePerVsize: number;
 | 
					  feePerVsize: number;
 | 
				
			||||||
  effectiveFeePerVsize?: number;
 | 
					  effectiveFeePerVsize?: number;
 | 
				
			||||||
  vin: string[];
 | 
					  inputs: number[];
 | 
				
			||||||
  cpfpRoot?: string;
 | 
					  cpfpRoot?: string;
 | 
				
			||||||
  cpfpChecked?: boolean;
 | 
					  cpfpChecked?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user