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