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 config from '../config';
 | 
				
			||||||
import { NodeSocket } from '../repositories/NodesSocketsRepository';
 | 
					import { NodeSocket } from '../repositories/NodesSocketsRepository';
 | 
				
			||||||
import { isIP } from 'net';
 | 
					import { isIP } from 'net';
 | 
				
			||||||
@ -366,40 +366,80 @@ export class Common {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static calculateCpfp(height: number, transactions: TransactionExtended[]): CpfpSummary {
 | 
					  static calculateCpfp(height: number, transactions: TransactionExtended[]): CpfpSummary {
 | 
				
			||||||
    const clusters: { root: string, height: number, txs: Ancestor[], effectiveFeePerVsize: number }[] = [];
 | 
					    const clusters: CpfpCluster[] = []; // list of all cpfp clusters in this block
 | 
				
			||||||
    let cluster: TransactionExtended[] = [];
 | 
					    const clusterMap: { [txid: string]: CpfpCluster } = {}; // map transactions to their cpfp cluster
 | 
				
			||||||
    let ancestors: { [txid: string]: boolean } = {};
 | 
					    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 = {};
 | 
					    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--) {
 | 
					    for (let i = transactions.length - 1; i >= 0; i--) {
 | 
				
			||||||
      const tx = transactions[i];
 | 
					      const tx = transactions[i];
 | 
				
			||||||
      txMap[tx.txid] = tx;
 | 
					 | 
				
			||||||
      if (!ancestors[tx.txid]) {
 | 
					      if (!ancestors[tx.txid]) {
 | 
				
			||||||
        let totalFee = 0;
 | 
					        let totalFee = 0;
 | 
				
			||||||
        let totalVSize = 0;
 | 
					        let totalVSize = 0;
 | 
				
			||||||
        cluster.forEach(tx => {
 | 
					        clusterTxs.forEach(tx => {
 | 
				
			||||||
          totalFee += tx?.fee || 0;
 | 
					          totalFee += tx?.fee || 0;
 | 
				
			||||||
          totalVSize += (tx.weight / 4);
 | 
					          totalVSize += (tx.weight / 4);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        const effectiveFeePerVsize = totalFee / totalVSize;
 | 
					        const effectiveFeePerVsize = totalFee / totalVSize;
 | 
				
			||||||
        if (cluster.length > 1) {
 | 
					        let cluster: CpfpCluster;
 | 
				
			||||||
          clusters.push({
 | 
					        if (clusterTxs.length > 1) {
 | 
				
			||||||
            root: cluster[0].txid,
 | 
					          cluster = {
 | 
				
			||||||
 | 
					            root: clusterTxs[0].txid,
 | 
				
			||||||
            height,
 | 
					            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,
 | 
					            effectiveFeePerVsize,
 | 
				
			||||||
          });
 | 
					          };
 | 
				
			||||||
 | 
					          clusters.push(cluster);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        cluster.forEach(tx => {
 | 
					        clusterTxs.forEach(tx => {
 | 
				
			||||||
          txMap[tx.txid].effectiveFeePerVsize = effectiveFeePerVsize;
 | 
					          txMap[tx.txid].effectiveFeePerVsize = effectiveFeePerVsize;
 | 
				
			||||||
 | 
					          if (cluster) {
 | 
				
			||||||
 | 
					            clusterMap[tx.txid] = cluster;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
        cluster = [];
 | 
					        // reset working vars
 | 
				
			||||||
 | 
					        clusterTxs = [];
 | 
				
			||||||
        ancestors = {};
 | 
					        ancestors = {};
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      cluster.push(tx);
 | 
					      clusterTxs.push(tx);
 | 
				
			||||||
      tx.vin.forEach(vin => {
 | 
					      tx.vin.forEach(vin => {
 | 
				
			||||||
        ancestors[vin.txid] = true;
 | 
					        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 {
 | 
					    return {
 | 
				
			||||||
      transactions,
 | 
					      transactions,
 | 
				
			||||||
      clusters,
 | 
					      clusters,
 | 
				
			||||||
 | 
				
			|||||||
@ -253,9 +253,16 @@ export interface WorkingEffectiveFeeStats extends EffectiveFeeStats {
 | 
				
			|||||||
  maxFee: number;
 | 
					  maxFee: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface CpfpCluster {
 | 
				
			||||||
 | 
					  root: string,
 | 
				
			||||||
 | 
					  height: number,
 | 
				
			||||||
 | 
					  txs: Ancestor[],
 | 
				
			||||||
 | 
					  effectiveFeePerVsize: number,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface CpfpSummary {
 | 
					export interface CpfpSummary {
 | 
				
			||||||
  transactions: TransactionExtended[];
 | 
					  transactions: TransactionExtended[];
 | 
				
			||||||
  clusters: { root: string, height: number, txs: Ancestor[], effectiveFeePerVsize: number }[];
 | 
					  clusters: CpfpCluster[];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface Statistic {
 | 
					export interface Statistic {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,7 @@
 | 
				
			|||||||
import cluster, { Cluster } from 'cluster';
 | 
					 | 
				
			||||||
import { RowDataPacket } from 'mysql2';
 | 
					import { RowDataPacket } from 'mysql2';
 | 
				
			||||||
import DB from '../database';
 | 
					import DB from '../database';
 | 
				
			||||||
import logger from '../logger';
 | 
					import logger from '../logger';
 | 
				
			||||||
import { Ancestor } from '../mempool.interfaces';
 | 
					import { Ancestor, CpfpCluster } from '../mempool.interfaces';
 | 
				
			||||||
import transactionRepository from '../repositories/TransactionRepository';
 | 
					import transactionRepository from '../repositories/TransactionRepository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CpfpRepository {
 | 
					class CpfpRepository {
 | 
				
			||||||
@ -12,7 +11,7 @@ class CpfpRepository {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
    // skip clusters of transactions with the same fees
 | 
					    // skip clusters of transactions with the same fees
 | 
				
			||||||
    const roundedEffectiveFee = Math.round(effectiveFeePerVsize * 100) / 100;
 | 
					    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);
 | 
					      return (acc && Math.round(((tx.fee || 0) / (tx.weight / 4)) * 100) / 100 === roundedEffectiveFee);
 | 
				
			||||||
    }, true);
 | 
					    }, true);
 | 
				
			||||||
    if (equalFee) {
 | 
					    if (equalFee) {
 | 
				
			||||||
@ -54,9 +53,9 @@ class CpfpRepository {
 | 
				
			|||||||
      const txs: any[] = [];
 | 
					      const txs: any[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      for (const cluster of clusters) {
 | 
					      for (const cluster of clusters) {
 | 
				
			||||||
        if (cluster.txs?.length > 1) {
 | 
					        if (cluster.txs?.length) {
 | 
				
			||||||
          const roundedEffectiveFee = Math.round(cluster.effectiveFeePerVsize * 100) / 100;
 | 
					          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);
 | 
					            return (acc && Math.round(((tx.fee || 0) / (tx.weight / 4)) * 100) / 100 === roundedEffectiveFee);
 | 
				
			||||||
          }, true);
 | 
					          }, true);
 | 
				
			||||||
          if (!equalFee) {
 | 
					          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(
 | 
					    const [clusterRows]: any = await DB.query(
 | 
				
			||||||
      `
 | 
					      `
 | 
				
			||||||
        SELECT *
 | 
					        SELECT *
 | 
				
			||||||
@ -121,6 +120,7 @@ class CpfpRepository {
 | 
				
			|||||||
      [clusterRoot]
 | 
					      [clusterRoot]
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    const cluster = clusterRows[0];
 | 
					    const cluster = clusterRows[0];
 | 
				
			||||||
 | 
					    cluster.effectiveFeePerVsize = cluster.fee_rate;
 | 
				
			||||||
    if (cluster?.txs) {
 | 
					    if (cluster?.txs) {
 | 
				
			||||||
      cluster.txs = this.unpack(cluster.txs);
 | 
					      cluster.txs = this.unpack(cluster.txs);
 | 
				
			||||||
      return cluster;
 | 
					      return cluster;
 | 
				
			||||||
 | 
				
			|||||||
@ -105,6 +105,7 @@ class TransactionRepository {
 | 
				
			|||||||
    return {
 | 
					    return {
 | 
				
			||||||
      descendants,
 | 
					      descendants,
 | 
				
			||||||
      ancestors,
 | 
					      ancestors,
 | 
				
			||||||
 | 
					      effectiveFeePerVsize: cluster.effectiveFeePerVsize,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user