calculate & index ancestor-dependent effective rates
This commit is contained in:
		
							parent
							
								
									804640216f
								
							
						
					
					
						commit
						ca9b48283d
					
				@ -1,4 +1,4 @@
 | 
			
		||||
import { Ancestor, CpfpInfo, CpfpSummary, EffectiveFeeStats, MempoolBlockWithTransactions, TransactionExtended, MempoolTransactionExtended, TransactionStripped, WorkingEffectiveFeeStats } from '../mempool.interfaces';
 | 
			
		||||
import { Ancestor, CpfpInfo, CpfpSummary, CpfpCluster, EffectiveFeeStats, MempoolBlockWithTransactions, TransactionExtended, MempoolTransactionExtended, TransactionStripped, WorkingEffectiveFeeStats } from '../mempool.interfaces';
 | 
			
		||||
import config from '../config';
 | 
			
		||||
import { NodeSocket } from '../repositories/NodesSocketsRepository';
 | 
			
		||||
import { isIP } from 'net';
 | 
			
		||||
@ -366,40 +366,80 @@ export class Common {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static calculateCpfp(height: number, transactions: TransactionExtended[]): CpfpSummary {
 | 
			
		||||
    const clusters: { root: string, height: number, txs: Ancestor[], effectiveFeePerVsize: number }[] = [];
 | 
			
		||||
    let cluster: TransactionExtended[] = [];
 | 
			
		||||
    let ancestors: { [txid: string]: boolean } = {};
 | 
			
		||||
    const clusters: CpfpCluster[] = []; // list of all cpfp clusters in this block
 | 
			
		||||
    const clusterMap: { [txid: string]: CpfpCluster } = {}; // map transactions to their cpfp cluster
 | 
			
		||||
    let clusterTxs: TransactionExtended[] = []; // working list of elements of the current cluster
 | 
			
		||||
    let ancestors: { [txid: string]: boolean } = {}; // working set of ancestors of the current cluster root
 | 
			
		||||
    const txMap = {};
 | 
			
		||||
    // initialize the txMap
 | 
			
		||||
    for (const tx of transactions) {
 | 
			
		||||
      txMap[tx.txid] = tx;
 | 
			
		||||
    }
 | 
			
		||||
    // reverse pass to identify CPFP clusters
 | 
			
		||||
    for (let i = transactions.length - 1; i >= 0; i--) {
 | 
			
		||||
      const tx = transactions[i];
 | 
			
		||||
      txMap[tx.txid] = tx;
 | 
			
		||||
      if (!ancestors[tx.txid]) {
 | 
			
		||||
        let totalFee = 0;
 | 
			
		||||
        let totalVSize = 0;
 | 
			
		||||
        cluster.forEach(tx => {
 | 
			
		||||
        clusterTxs.forEach(tx => {
 | 
			
		||||
          totalFee += tx?.fee || 0;
 | 
			
		||||
          totalVSize += (tx.weight / 4);
 | 
			
		||||
        });
 | 
			
		||||
        const effectiveFeePerVsize = totalFee / totalVSize;
 | 
			
		||||
        if (cluster.length > 1) {
 | 
			
		||||
          clusters.push({
 | 
			
		||||
            root: cluster[0].txid,
 | 
			
		||||
        let cluster: CpfpCluster;
 | 
			
		||||
        if (clusterTxs.length > 1) {
 | 
			
		||||
          cluster = {
 | 
			
		||||
            root: clusterTxs[0].txid,
 | 
			
		||||
            height,
 | 
			
		||||
            txs: cluster.map(tx => { return { txid: tx.txid, weight: tx.weight, fee: tx.fee || 0 }; }),
 | 
			
		||||
            txs: clusterTxs.map(tx => { return { txid: tx.txid, weight: tx.weight, fee: tx.fee || 0 }; }),
 | 
			
		||||
            effectiveFeePerVsize,
 | 
			
		||||
          });
 | 
			
		||||
          };
 | 
			
		||||
          clusters.push(cluster);
 | 
			
		||||
        }
 | 
			
		||||
        cluster.forEach(tx => {
 | 
			
		||||
        clusterTxs.forEach(tx => {
 | 
			
		||||
          txMap[tx.txid].effectiveFeePerVsize = effectiveFeePerVsize;
 | 
			
		||||
          if (cluster) {
 | 
			
		||||
            clusterMap[tx.txid] = cluster;
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
        cluster = [];
 | 
			
		||||
        // reset working vars
 | 
			
		||||
        clusterTxs = [];
 | 
			
		||||
        ancestors = {};
 | 
			
		||||
      }
 | 
			
		||||
      cluster.push(tx);
 | 
			
		||||
      clusterTxs.push(tx);
 | 
			
		||||
      tx.vin.forEach(vin => {
 | 
			
		||||
        ancestors[vin.txid] = true;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
    // forward pass to enforce ancestor rate caps
 | 
			
		||||
    for (const tx of transactions) {
 | 
			
		||||
      let minAncestorRate = tx.effectiveFeePerVsize;
 | 
			
		||||
      for (const vin of tx.vin) {
 | 
			
		||||
        if (txMap[vin.txid]?.effectiveFeePerVsize) {
 | 
			
		||||
          minAncestorRate = Math.min(minAncestorRate, txMap[vin.txid].effectiveFeePerVsize);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      // check rounded values to skip cases with almost identical fees
 | 
			
		||||
      const roundedMinAncestorRate = Math.ceil(minAncestorRate);
 | 
			
		||||
      const roundedEffectiveFeeRate = Math.floor(tx.effectiveFeePerVsize);
 | 
			
		||||
      if (roundedMinAncestorRate < roundedEffectiveFeeRate) {
 | 
			
		||||
        tx.effectiveFeePerVsize = minAncestorRate;
 | 
			
		||||
        if (!clusterMap[tx.txid]) {
 | 
			
		||||
          // add a single-tx cluster to record the dependent rate
 | 
			
		||||
          const cluster = {
 | 
			
		||||
            root: tx.txid,
 | 
			
		||||
            height,
 | 
			
		||||
            txs: [{ txid: tx.txid, weight: tx.weight, fee: tx.fee || 0 }],
 | 
			
		||||
            effectiveFeePerVsize: minAncestorRate,
 | 
			
		||||
          };
 | 
			
		||||
          clusterMap[tx.txid] = cluster;
 | 
			
		||||
          clusters.push(cluster);
 | 
			
		||||
        } else {
 | 
			
		||||
          // update the existing cluster with the dependent rate
 | 
			
		||||
          clusterMap[tx.txid].effectiveFeePerVsize = minAncestorRate;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return {
 | 
			
		||||
      transactions,
 | 
			
		||||
      clusters,
 | 
			
		||||
 | 
			
		||||
@ -253,9 +253,16 @@ export interface WorkingEffectiveFeeStats extends EffectiveFeeStats {
 | 
			
		||||
  maxFee: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CpfpCluster {
 | 
			
		||||
  root: string,
 | 
			
		||||
  height: number,
 | 
			
		||||
  txs: Ancestor[],
 | 
			
		||||
  effectiveFeePerVsize: number,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CpfpSummary {
 | 
			
		||||
  transactions: TransactionExtended[];
 | 
			
		||||
  clusters: { root: string, height: number, txs: Ancestor[], effectiveFeePerVsize: number }[];
 | 
			
		||||
  clusters: CpfpCluster[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Statistic {
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,7 @@
 | 
			
		||||
import cluster, { Cluster } from 'cluster';
 | 
			
		||||
import { RowDataPacket } from 'mysql2';
 | 
			
		||||
import DB from '../database';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
import { Ancestor } from '../mempool.interfaces';
 | 
			
		||||
import { Ancestor, CpfpCluster } from '../mempool.interfaces';
 | 
			
		||||
import transactionRepository from '../repositories/TransactionRepository';
 | 
			
		||||
 | 
			
		||||
class CpfpRepository {
 | 
			
		||||
@ -12,7 +11,7 @@ class CpfpRepository {
 | 
			
		||||
    }
 | 
			
		||||
    // skip clusters of transactions with the same fees
 | 
			
		||||
    const roundedEffectiveFee = Math.round(effectiveFeePerVsize * 100) / 100;
 | 
			
		||||
    const equalFee = txs.reduce((acc, tx) => {
 | 
			
		||||
    const equalFee = txs.length > 1 && txs.reduce((acc, tx) => {
 | 
			
		||||
      return (acc && Math.round(((tx.fee || 0) / (tx.weight / 4)) * 100) / 100 === roundedEffectiveFee);
 | 
			
		||||
    }, true);
 | 
			
		||||
    if (equalFee) {
 | 
			
		||||
@ -54,9 +53,9 @@ class CpfpRepository {
 | 
			
		||||
      const txs: any[] = [];
 | 
			
		||||
 | 
			
		||||
      for (const cluster of clusters) {
 | 
			
		||||
        if (cluster.txs?.length > 1) {
 | 
			
		||||
        if (cluster.txs?.length) {
 | 
			
		||||
          const roundedEffectiveFee = Math.round(cluster.effectiveFeePerVsize * 100) / 100;
 | 
			
		||||
          const equalFee = cluster.txs.reduce((acc, tx) => {
 | 
			
		||||
          const equalFee = cluster.txs.length > 1 && cluster.txs.reduce((acc, tx) => {
 | 
			
		||||
            return (acc && Math.round(((tx.fee || 0) / (tx.weight / 4)) * 100) / 100 === roundedEffectiveFee);
 | 
			
		||||
          }, true);
 | 
			
		||||
          if (!equalFee) {
 | 
			
		||||
@ -111,7 +110,7 @@ class CpfpRepository {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getCluster(clusterRoot: string): Promise<Cluster | void> {
 | 
			
		||||
  public async $getCluster(clusterRoot: string): Promise<CpfpCluster | void> {
 | 
			
		||||
    const [clusterRows]: any = await DB.query(
 | 
			
		||||
      `
 | 
			
		||||
        SELECT *
 | 
			
		||||
@ -121,6 +120,7 @@ class CpfpRepository {
 | 
			
		||||
      [clusterRoot]
 | 
			
		||||
    );
 | 
			
		||||
    const cluster = clusterRows[0];
 | 
			
		||||
    cluster.effectiveFeePerVsize = cluster.fee_rate;
 | 
			
		||||
    if (cluster?.txs) {
 | 
			
		||||
      cluster.txs = this.unpack(cluster.txs);
 | 
			
		||||
      return cluster;
 | 
			
		||||
 | 
			
		||||
@ -105,6 +105,7 @@ class TransactionRepository {
 | 
			
		||||
    return {
 | 
			
		||||
      descendants,
 | 
			
		||||
      ancestors,
 | 
			
		||||
      effectiveFeePerVsize: cluster.effectiveFeePerVsize,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user