Merge branch 'master' into mononaut/clocktower
This commit is contained in:
		
						commit
						34565dc675
					
				@ -1,5 +1,5 @@
 | 
				
			|||||||
import logger from '../logger';
 | 
					import logger from '../logger';
 | 
				
			||||||
import { MempoolBlock, TransactionExtended, ThreadTransaction, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction } from '../mempool.interfaces';
 | 
					import { MempoolBlock, TransactionExtended, 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';
 | 
				
			||||||
@ -104,8 +104,12 @@ class MempoolBlocks {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private calculateMempoolBlocks(transactionsSorted: TransactionExtended[]): MempoolBlockWithTransactions[] {
 | 
					  private calculateMempoolBlocks(transactionsSorted: TransactionExtended[]): MempoolBlockWithTransactions[] {
 | 
				
			||||||
    const mempoolBlocks: MempoolBlockWithTransactions[] = [];
 | 
					    const mempoolBlocks: MempoolBlockWithTransactions[] = [];
 | 
				
			||||||
 | 
					    let blockSize = 0;
 | 
				
			||||||
    let blockWeight = 0;
 | 
					    let blockWeight = 0;
 | 
				
			||||||
    let blockVsize = 0;
 | 
					    let blockVsize = 0;
 | 
				
			||||||
 | 
					    let blockFees = 0;
 | 
				
			||||||
 | 
					    const sizeLimit = (config.MEMPOOL.BLOCK_WEIGHT_UNITS / 4) * 1.2;
 | 
				
			||||||
 | 
					    let transactionIds: string[] = [];
 | 
				
			||||||
    let transactions: TransactionExtended[] = [];
 | 
					    let transactions: TransactionExtended[] = [];
 | 
				
			||||||
    transactionsSorted.forEach((tx) => {
 | 
					    transactionsSorted.forEach((tx) => {
 | 
				
			||||||
      if (blockWeight + tx.weight <= config.MEMPOOL.BLOCK_WEIGHT_UNITS
 | 
					      if (blockWeight + tx.weight <= config.MEMPOOL.BLOCK_WEIGHT_UNITS
 | 
				
			||||||
@ -116,9 +120,14 @@ class MempoolBlocks {
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
        blockWeight += tx.weight;
 | 
					        blockWeight += tx.weight;
 | 
				
			||||||
        blockVsize += tx.vsize;
 | 
					        blockVsize += tx.vsize;
 | 
				
			||||||
        transactions.push(tx);
 | 
					        blockSize += tx.size;
 | 
				
			||||||
 | 
					        blockFees += tx.fee;
 | 
				
			||||||
 | 
					        if (blockVsize <= sizeLimit) {
 | 
				
			||||||
 | 
					          transactions.push(tx);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        transactionIds.push(tx.txid);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        mempoolBlocks.push(this.dataToMempoolBlocks(transactions));
 | 
					        mempoolBlocks.push(this.dataToMempoolBlocks(transactionIds, transactions, blockSize, blockWeight, blockFees));
 | 
				
			||||||
        blockVsize = 0;
 | 
					        blockVsize = 0;
 | 
				
			||||||
        tx.position = {
 | 
					        tx.position = {
 | 
				
			||||||
          block: mempoolBlocks.length,
 | 
					          block: mempoolBlocks.length,
 | 
				
			||||||
@ -126,11 +135,14 @@ class MempoolBlocks {
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
        blockVsize += tx.vsize;
 | 
					        blockVsize += tx.vsize;
 | 
				
			||||||
        blockWeight = tx.weight;
 | 
					        blockWeight = tx.weight;
 | 
				
			||||||
 | 
					        blockSize = tx.size;
 | 
				
			||||||
 | 
					        blockFees = tx.fee;
 | 
				
			||||||
 | 
					        transactionIds = [tx.txid];
 | 
				
			||||||
        transactions = [tx];
 | 
					        transactions = [tx];
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    if (transactions.length) {
 | 
					    if (transactions.length) {
 | 
				
			||||||
      mempoolBlocks.push(this.dataToMempoolBlocks(transactions));
 | 
					      mempoolBlocks.push(this.dataToMempoolBlocks(transactionIds, transactions, blockSize, blockWeight, blockFees));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return mempoolBlocks;
 | 
					    return mempoolBlocks;
 | 
				
			||||||
@ -178,6 +190,8 @@ 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[]> {
 | 
				
			||||||
 | 
					    const start = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // reset mempool short ids
 | 
					    // reset mempool short ids
 | 
				
			||||||
    this.resetUids();
 | 
					    this.resetUids();
 | 
				
			||||||
    for (const tx of Object.values(newMempool)) {
 | 
					    for (const tx of Object.values(newMempool)) {
 | 
				
			||||||
@ -194,7 +208,7 @@ class MempoolBlocks {
 | 
				
			|||||||
          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.effectiveFeePerVsize || (entry.fee / (entry.weight / 4)),
 | 
				
			||||||
          inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => uid != null) as number[],
 | 
					          inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => uid != null) as number[],
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -216,7 +230,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: CompactThreadTransaction[][], clusters: Map<number, number[]> }>((resolve, reject) => {
 | 
					      const workerResultPromise = new Promise<{ blocks: number[][], rates: Map<number, number>, 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);
 | 
				
			||||||
@ -224,19 +238,14 @@ 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 } = this.convertResultTxids(await workerResultPromise);
 | 
					      const { blocks, rates, clusters } = this.convertResultTxids(await workerResultPromise);
 | 
				
			||||||
      // filter out stale transactions
 | 
					 | 
				
			||||||
      const unfilteredCount = blocks.reduce((total, block) => { return total + block.length; }, 0);
 | 
					 | 
				
			||||||
      blocks = blocks.map(block => block.filter(tx => (tx.txid && tx.txid in newMempool)));
 | 
					 | 
				
			||||||
      const filteredCount = blocks.reduce((total, block) => { return total + block.length; }, 0);
 | 
					 | 
				
			||||||
      if (filteredCount < unfilteredCount) {
 | 
					 | 
				
			||||||
        logger.warn(`tx selection worker thread returned ${unfilteredCount - filteredCount} stale transactions from makeBlockTemplates`);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // clean up thread error listener
 | 
					      // clean up thread error listener
 | 
				
			||||||
      this.txSelectionWorker?.removeListener('error', threadErrorListener);
 | 
					      this.txSelectionWorker?.removeListener('error', threadErrorListener);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return this.processBlockTemplates(newMempool, blocks, clusters, saveResults);
 | 
					      const processed = this.processBlockTemplates(newMempool, blocks, rates, clusters, saveResults);
 | 
				
			||||||
 | 
					      logger.debug(`makeBlockTemplates completed in ${(Date.now() - start)/1000} seconds`);
 | 
				
			||||||
 | 
					      return processed;
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      logger.err('makeBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
 | 
					      logger.err('makeBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -250,6 +259,8 @@ class MempoolBlocks {
 | 
				
			|||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const start = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const tx of Object.values(added)) {
 | 
					    for (const tx of Object.values(added)) {
 | 
				
			||||||
      this.setUid(tx);
 | 
					      this.setUid(tx);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -262,7 +273,7 @@ class MempoolBlocks {
 | 
				
			|||||||
        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.effectiveFeePerVsize || (entry.fee / (entry.weight / 4)),
 | 
				
			||||||
        inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => uid != null) as number[],
 | 
					        inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => uid != null) as number[],
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@ -270,7 +281,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: CompactThreadTransaction[][], clusters: Map<number, number[]> }>((resolve, reject) => {
 | 
					      const workerResultPromise = new Promise<{ blocks: number[][], rates: Map<number, number>, 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);
 | 
				
			||||||
@ -278,84 +289,100 @@ class MempoolBlocks {
 | 
				
			|||||||
        this.txSelectionWorker?.once('error', reject);
 | 
					        this.txSelectionWorker?.once('error', reject);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
      this.txSelectionWorker.postMessage({ type: 'update', added: addedStripped, removed: removedUids });
 | 
					      this.txSelectionWorker.postMessage({ type: 'update', added: addedStripped, removed: removedUids });
 | 
				
			||||||
      let { blocks, clusters } = this.convertResultTxids(await workerResultPromise);
 | 
					      const { blocks, rates, clusters } = this.convertResultTxids(await workerResultPromise);
 | 
				
			||||||
      // filter out stale transactions
 | 
					 | 
				
			||||||
      const unfilteredCount = blocks.reduce((total, block) => { return total + block.length; }, 0);
 | 
					 | 
				
			||||||
      blocks = blocks.map(block => block.filter(tx => (tx.txid && tx.txid in newMempool)));
 | 
					 | 
				
			||||||
      const filteredCount = blocks.reduce((total, block) => { return total + block.length; }, 0);
 | 
					 | 
				
			||||||
      if (filteredCount < unfilteredCount) {
 | 
					 | 
				
			||||||
        logger.warn(`tx selection worker thread returned ${unfilteredCount - filteredCount} stale transactions from updateBlockTemplates`);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.removeUids(removedUids);
 | 
					      this.removeUids(removedUids);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // clean up thread error listener
 | 
					      // clean up thread error listener
 | 
				
			||||||
      this.txSelectionWorker?.removeListener('error', threadErrorListener);
 | 
					      this.txSelectionWorker?.removeListener('error', threadErrorListener);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.processBlockTemplates(newMempool, blocks, clusters, saveResults);
 | 
					      this.processBlockTemplates(newMempool, blocks, rates, clusters, saveResults);
 | 
				
			||||||
 | 
					      logger.debug(`updateBlockTemplates completed in ${(Date.now() - start) / 1000} seconds`);
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      logger.err('updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
 | 
					      logger.err('updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private processBlockTemplates(mempool, blocks: ThreadTransaction[][], clusters, saveResults): MempoolBlockWithTransactions[] {
 | 
					  private processBlockTemplates(mempool, blocks: string[][], rates: { [root: string]: number }, clusters: { [root: string]: string[] }, saveResults): MempoolBlockWithTransactions[] {
 | 
				
			||||||
 | 
					    for (const txid of Object.keys(rates)) {
 | 
				
			||||||
 | 
					      if (txid in mempool) {
 | 
				
			||||||
 | 
					        mempool[txid].effectiveFeePerVsize = rates[txid];
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const readyBlocks: { transactionIds, transactions, totalSize, totalWeight, totalFees }[] = [];
 | 
				
			||||||
 | 
					    const sizeLimit = (config.MEMPOOL.BLOCK_WEIGHT_UNITS / 4) * 1.2;
 | 
				
			||||||
    // update this thread's mempool with the results
 | 
					    // update this thread's mempool with the results
 | 
				
			||||||
    blocks.forEach((block, blockIndex) => {
 | 
					    for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
 | 
				
			||||||
      let runningVsize = 0;
 | 
					      const block: string[] = blocks[blockIndex];
 | 
				
			||||||
      block.forEach(tx => {
 | 
					      let txid: string;
 | 
				
			||||||
        if (tx.txid && tx.txid in mempool) {
 | 
					      let mempoolTx: TransactionExtended;
 | 
				
			||||||
 | 
					      let totalSize = 0;
 | 
				
			||||||
 | 
					      let totalVsize = 0;
 | 
				
			||||||
 | 
					      let totalWeight = 0;
 | 
				
			||||||
 | 
					      let totalFees = 0;
 | 
				
			||||||
 | 
					      const transactions: TransactionExtended[] = [];
 | 
				
			||||||
 | 
					      for (let txIndex = 0; txIndex < block.length; txIndex++) {
 | 
				
			||||||
 | 
					        txid = block[txIndex];
 | 
				
			||||||
 | 
					        if (txid) {
 | 
				
			||||||
 | 
					          mempoolTx = mempool[txid];
 | 
				
			||||||
          // save position in projected blocks
 | 
					          // save position in projected blocks
 | 
				
			||||||
          mempool[tx.txid].position = {
 | 
					          mempoolTx.position = {
 | 
				
			||||||
            block: blockIndex,
 | 
					            block: blockIndex,
 | 
				
			||||||
            vsize: runningVsize + (mempool[tx.txid].vsize / 2),
 | 
					            vsize: totalVsize + (mempoolTx.vsize / 2),
 | 
				
			||||||
          };
 | 
					          };
 | 
				
			||||||
          runningVsize += mempool[tx.txid].vsize;
 | 
					          mempoolTx.cpfpChecked = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          if (tx.effectiveFeePerVsize != null) {
 | 
					          totalSize += mempoolTx.size;
 | 
				
			||||||
            mempool[tx.txid].effectiveFeePerVsize = tx.effectiveFeePerVsize;
 | 
					          totalVsize += mempoolTx.vsize;
 | 
				
			||||||
 | 
					          totalWeight += mempoolTx.weight;
 | 
				
			||||||
 | 
					          totalFees += mempoolTx.fee;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (totalVsize <= sizeLimit) {
 | 
				
			||||||
 | 
					            transactions.push(mempoolTx);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          if (tx.cpfpRoot && tx.cpfpRoot in clusters) {
 | 
					 | 
				
			||||||
            const ancestors: Ancestor[] = [];
 | 
					 | 
				
			||||||
            const descendants: Ancestor[] = [];
 | 
					 | 
				
			||||||
            const cluster = clusters[tx.cpfpRoot];
 | 
					 | 
				
			||||||
            let matched = false;
 | 
					 | 
				
			||||||
            cluster.forEach(txid => {
 | 
					 | 
				
			||||||
              if (!txid || !mempool[txid]) {
 | 
					 | 
				
			||||||
                logger.warn('projected transaction ancestor missing from mempool cache');
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
              if (txid === tx.txid) {
 | 
					 | 
				
			||||||
                matched = true;
 | 
					 | 
				
			||||||
              } else {
 | 
					 | 
				
			||||||
                const relative = {
 | 
					 | 
				
			||||||
                  txid: txid,
 | 
					 | 
				
			||||||
                  fee: mempool[txid].fee,
 | 
					 | 
				
			||||||
                  weight: mempool[txid].weight,
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
                if (matched) {
 | 
					 | 
				
			||||||
                  descendants.push(relative);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                  ancestors.push(relative);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            mempool[tx.txid].ancestors = ancestors;
 | 
					 | 
				
			||||||
            mempool[tx.txid].descendants = descendants;
 | 
					 | 
				
			||||||
            mempool[tx.txid].bestDescendant = null;
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          mempool[tx.txid].cpfpChecked = tx.cpfpChecked;
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          logger.warn('projected transaction missing from mempool cache');
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      readyBlocks.push({
 | 
				
			||||||
 | 
					        transactionIds: block,
 | 
				
			||||||
 | 
					        transactions,
 | 
				
			||||||
 | 
					        totalSize,
 | 
				
			||||||
 | 
					        totalWeight,
 | 
				
			||||||
 | 
					        totalFees
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // unpack the condensed blocks into proper mempool blocks
 | 
					    for (const cluster of Object.values(clusters)) {
 | 
				
			||||||
    const mempoolBlocks = blocks.map((transactions) => {
 | 
					      for (const memberTxid of cluster) {
 | 
				
			||||||
      return this.dataToMempoolBlocks(transactions.map(tx => {
 | 
					        if (memberTxid in mempool) {
 | 
				
			||||||
        return mempool[tx.txid] || null;
 | 
					          const mempoolTx = mempool[memberTxid];
 | 
				
			||||||
      }).filter(tx => !!tx));
 | 
					          const ancestors: Ancestor[] = [];
 | 
				
			||||||
    });
 | 
					          const descendants: Ancestor[] = [];
 | 
				
			||||||
 | 
					          let matched = false;
 | 
				
			||||||
 | 
					          cluster.forEach(txid => {
 | 
				
			||||||
 | 
					            if (txid === memberTxid) {
 | 
				
			||||||
 | 
					              matched = true;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              const relative = {
 | 
				
			||||||
 | 
					                txid: txid,
 | 
				
			||||||
 | 
					                fee: mempool[txid].fee,
 | 
				
			||||||
 | 
					                weight: mempool[txid].weight,
 | 
				
			||||||
 | 
					              };
 | 
				
			||||||
 | 
					              if (matched) {
 | 
				
			||||||
 | 
					                descendants.push(relative);
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                ancestors.push(relative);
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					          mempoolTx.ancestors = ancestors;
 | 
				
			||||||
 | 
					          mempoolTx.descendants = descendants;
 | 
				
			||||||
 | 
					          mempoolTx.bestDescendant = null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const mempoolBlocks = readyBlocks.map(b => this.dataToMempoolBlocks(b.transactionIds, b.transactions, b.totalSize, b.totalWeight, b.totalFees));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (saveResults) {
 | 
					    if (saveResults) {
 | 
				
			||||||
      const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks);
 | 
					      const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks);
 | 
				
			||||||
@ -366,27 +393,17 @@ class MempoolBlocks {
 | 
				
			|||||||
    return mempoolBlocks;
 | 
					    return mempoolBlocks;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private dataToMempoolBlocks(transactions: TransactionExtended[]): MempoolBlockWithTransactions {
 | 
					  private dataToMempoolBlocks(transactionIds: string[], transactions: TransactionExtended[], totalSize: number, totalWeight: number, totalFees: number): MempoolBlockWithTransactions {
 | 
				
			||||||
    let totalSize = 0;
 | 
					 | 
				
			||||||
    let totalWeight = 0;
 | 
					 | 
				
			||||||
    const fitTransactions: TransactionExtended[] = [];
 | 
					 | 
				
			||||||
    transactions.forEach(tx => {
 | 
					 | 
				
			||||||
      totalSize += tx.size;
 | 
					 | 
				
			||||||
      totalWeight += tx.weight;
 | 
					 | 
				
			||||||
      if ((totalWeight + tx.weight) <= config.MEMPOOL.BLOCK_WEIGHT_UNITS * 1.2) {
 | 
					 | 
				
			||||||
        fitTransactions.push(tx);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    const feeStats = Common.calcEffectiveFeeStatistics(transactions);
 | 
					    const feeStats = Common.calcEffectiveFeeStatistics(transactions);
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      blockSize: totalSize,
 | 
					      blockSize: totalSize,
 | 
				
			||||||
      blockVSize: totalWeight / 4,
 | 
					      blockVSize: (totalWeight / 4), // fractional vsize to avoid rounding errors
 | 
				
			||||||
      nTx: transactions.length,
 | 
					      nTx: transactionIds.length,
 | 
				
			||||||
      totalFees: transactions.reduce((acc, cur) => acc + cur.fee, 0),
 | 
					      totalFees: totalFees,
 | 
				
			||||||
      medianFee: feeStats.medianFee, // Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
 | 
					      medianFee: feeStats.medianFee, // Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
 | 
				
			||||||
      feeRange: feeStats.feeRange, //Common.getFeesInRange(transactions, rangeLength),
 | 
					      feeRange: feeStats.feeRange, //Common.getFeesInRange(transactions, rangeLength),
 | 
				
			||||||
      transactionIds: transactions.map((tx) => tx.txid),
 | 
					      transactionIds: transactionIds,
 | 
				
			||||||
      transactions: fitTransactions.map((tx) => Common.stripTransaction(tx)),
 | 
					      transactions: transactions.map((tx) => Common.stripTransaction(tx)),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -415,14 +432,16 @@ class MempoolBlocks {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private convertResultTxids({ blocks, clusters }: { blocks: any[][], clusters: Map<number, number[]>})
 | 
					  private convertResultTxids({ blocks, rates, clusters }: { blocks: number[][], rates: Map<number, number>, clusters: Map<number, number[]>})
 | 
				
			||||||
    : { blocks: ThreadTransaction[][], clusters: { [root: string]: string[] }} {
 | 
					    : { blocks: string[][], rates: { [root: string]: number }, clusters: { [root: string]: string[] }} {
 | 
				
			||||||
    for (const block of blocks) {
 | 
					    const convertedBlocks: string[][] = blocks.map(block => block.map(uid => {
 | 
				
			||||||
      for (const tx of block) {
 | 
					      return this.uidMap.get(uid) || '';
 | 
				
			||||||
        tx.txid = this.uidMap.get(tx.uid);
 | 
					    }));
 | 
				
			||||||
        if (tx.cpfpRoot) {
 | 
					    const convertedRates = {};
 | 
				
			||||||
          tx.cpfpRoot = this.uidMap.get(tx.cpfpRoot);
 | 
					    for (const rateUid of rates.keys()) {
 | 
				
			||||||
        }
 | 
					      const rateTxid = this.uidMap.get(rateUid);
 | 
				
			||||||
 | 
					      if (rateTxid) {
 | 
				
			||||||
 | 
					        convertedRates[rateTxid] = rates.get(rateUid);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const convertedClusters = {};
 | 
					    const convertedClusters = {};
 | 
				
			||||||
@ -435,7 +454,7 @@ class MempoolBlocks {
 | 
				
			|||||||
        convertedClusters[rootTxid] = members;
 | 
					        convertedClusters[rootTxid] = members;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return { blocks, clusters: convertedClusters } as { blocks: ThreadTransaction[][], clusters: { [root: string]: string[] }};
 | 
					    return { blocks: convertedBlocks, rates: convertedRates, clusters: convertedClusters } as { blocks: string[][], rates: { [root: string]: number }, clusters: { [root: string]: string[] }};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import config from '../config';
 | 
					import config from '../config';
 | 
				
			||||||
import logger from '../logger';
 | 
					import logger from '../logger';
 | 
				
			||||||
import { CompactThreadTransaction, MempoolBlockWithTransactions, AuditTransaction } from '../mempool.interfaces';
 | 
					import { CompactThreadTransaction, 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';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -19,11 +19,11 @@ if (parentPort) {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    const { blocks, clusters } = makeBlockTemplates(mempool);
 | 
					    const { blocks, rates, clusters } = makeBlockTemplates(mempool);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // return the result to main thread.
 | 
					    // return the result to main thread.
 | 
				
			||||||
    if (parentPort) {
 | 
					    if (parentPort) {
 | 
				
			||||||
      parentPort.postMessage({ blocks, clusters });
 | 
					      parentPort.postMessage({ blocks, rates, clusters });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -33,14 +33,14 @@ if (parentPort) {
 | 
				
			|||||||
* (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: Map<number, CompactThreadTransaction>)
 | 
					function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
 | 
				
			||||||
  : { blocks: CompactThreadTransaction[][], clusters: Map<number, number[]> } {
 | 
					  : { blocks: number[][], rates: Map<number, number>, clusters: Map<number, number[]> } {
 | 
				
			||||||
  const start = Date.now();
 | 
					  const start = Date.now();
 | 
				
			||||||
  const auditPool: Map<number, AuditTransaction> = new Map();
 | 
					  const auditPool: Map<number, AuditTransaction> = new Map();
 | 
				
			||||||
  const mempoolArray: AuditTransaction[] = [];
 | 
					  const mempoolArray: AuditTransaction[] = [];
 | 
				
			||||||
  const restOfArray: CompactThreadTransaction[] = [];
 | 
					 | 
				
			||||||
  const cpfpClusters: Map<number, number[]> = new Map();
 | 
					  const cpfpClusters: Map<number, number[]> = new Map();
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
  mempool.forEach(tx => {
 | 
					  mempool.forEach(tx => {
 | 
				
			||||||
 | 
					    tx.dirty = false;
 | 
				
			||||||
    // initializing everything up front helps V8 optimize property access later
 | 
					    // initializing everything up front helps V8 optimize property access later
 | 
				
			||||||
    auditPool.set(tx.uid, {
 | 
					    auditPool.set(tx.uid, {
 | 
				
			||||||
      uid: tx.uid,
 | 
					      uid: tx.uid,
 | 
				
			||||||
@ -81,9 +81,8 @@ function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // 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: CompactThreadTransaction[][] = [];
 | 
					  const blocks: number[][] = [];
 | 
				
			||||||
  let blockWeight = 4000;
 | 
					  let blockWeight = 4000;
 | 
				
			||||||
  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) {
 | 
				
			||||||
@ -139,13 +138,16 @@ function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
 | 
				
			|||||||
          ancestor.used = true;
 | 
					          ancestor.used = true;
 | 
				
			||||||
          ancestor.usedBy = nextTx.uid;
 | 
					          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;
 | 
					          if (mempoolTx.effectiveFeePerVsize !== effectiveFeeRate) {
 | 
				
			||||||
          if (isCluster) {
 | 
					            mempoolTx.effectiveFeePerVsize = effectiveFeeRate;
 | 
				
			||||||
            mempoolTx.cpfpRoot = nextTx.uid;
 | 
					            mempoolTx.dirty = true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (mempoolTx.cpfpRoot !== nextTx.uid) {
 | 
				
			||||||
 | 
					            mempoolTx.cpfpRoot = isCluster ? nextTx.uid : null;
 | 
				
			||||||
 | 
					            mempoolTx.dirty;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          mempoolTx.cpfpChecked = true;
 | 
					          mempoolTx.cpfpChecked = true;
 | 
				
			||||||
          transactions.push(ancestor);
 | 
					          transactions.push(ancestor);
 | 
				
			||||||
          blockSize += ancestor.size;
 | 
					 | 
				
			||||||
          blockWeight += ancestor.weight;
 | 
					          blockWeight += ancestor.weight;
 | 
				
			||||||
          used.push(ancestor);
 | 
					          used.push(ancestor);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -171,11 +173,10 @@ function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
 | 
				
			|||||||
    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.get(t.uid) as CompactThreadTransaction));
 | 
					        blocks.push(transactions.map(t => t.uid));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // reset for the next block
 | 
					      // reset for the next block
 | 
				
			||||||
      transactions = [];
 | 
					      transactions = [];
 | 
				
			||||||
      blockSize = 0;
 | 
					 | 
				
			||||||
      blockWeight = 4000;
 | 
					      blockWeight = 4000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // 'overflow' packages didn't fit in this block, but are valid candidates for the next
 | 
					      // 'overflow' packages didn't fit in this block, but are valid candidates for the next
 | 
				
			||||||
@ -196,14 +197,22 @@ function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  // 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.get(t.uid) as CompactThreadTransaction));
 | 
					    blocks.push(transactions.map(t => t.uid));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // get map of dirty transactions
 | 
				
			||||||
 | 
					  const rates = new Map<number, number>();
 | 
				
			||||||
 | 
					  for (const tx of mempool.values()) {
 | 
				
			||||||
 | 
					    if (tx?.dirty) {
 | 
				
			||||||
 | 
					      rates.set(tx.uid, tx.effectiveFeePerVsize || tx.feePerVsize);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const end = Date.now();
 | 
					  const end = Date.now();
 | 
				
			||||||
  const time = end - start;
 | 
					  const time = end - start;
 | 
				
			||||||
  logger.debug('Mempool templates calculated in ' + time / 1000 + ' seconds');
 | 
					  logger.debug('Mempool templates calculated in ' + time / 1000 + ' seconds');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return { blocks, clusters: cpfpClusters };
 | 
					  return { blocks, rates, clusters: cpfpClusters };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// traverse in-mempool ancestors
 | 
					// traverse in-mempool ancestors
 | 
				
			||||||
 | 
				
			|||||||
@ -114,6 +114,7 @@ export interface CompactThreadTransaction {
 | 
				
			|||||||
  inputs: number[];
 | 
					  inputs: number[];
 | 
				
			||||||
  cpfpRoot?: string;
 | 
					  cpfpRoot?: string;
 | 
				
			||||||
  cpfpChecked?: boolean;
 | 
					  cpfpChecked?: boolean;
 | 
				
			||||||
 | 
					  dirty?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ThreadTransaction {
 | 
					export interface ThreadTransaction {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user