diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 5a1b35547..a36f18200 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -31,6 +31,7 @@ import rbfCache from './rbf-cache'; import { calcBitsDifference } from './difficulty-adjustment'; import AccelerationRepository from '../repositories/AccelerationRepository'; import { calculateFastBlockCpfp, calculateGoodBlockCpfp } from './cpfp'; +import mempool from './mempool'; class Blocks { private blocks: BlockExtended[] = []; @@ -602,13 +603,13 @@ class Blocks { for (let height = currentBlockHeight; height >= 0; height--) { try { - let txs: TransactionExtended[] | null = null; + let txs: MempoolTransactionExtended[] | null = null; if (unclassifiedBlocks[height]) { const blockHash = unclassifiedBlocks[height]; // fetch transactions - txs = (await bitcoinApi.$getTxsForBlock(blockHash)).map(tx => transactionUtils.extendTransaction(tx)) || []; + txs = (await bitcoinApi.$getTxsForBlock(blockHash)).map(tx => transactionUtils.extendMempoolTransaction(tx)) || []; // add CPFP - const cpfpSummary = calculateFastBlockCpfp(height, txs, true); + const cpfpSummary = calculateGoodBlockCpfp(height, txs, []); // classify const { transactions: classifiedTxs } = this.summarizeBlockTransactions(blockHash, cpfpSummary.transactions); await BlocksSummariesRepository.$saveTransactions(height, blockHash, classifiedTxs, 1); @@ -637,7 +638,7 @@ class Blocks { } templateTxs.push(tx || templateTx); } - const cpfpSummary = calculateFastBlockCpfp(height, templateTxs?.filter(tx => tx['effectiveFeePerVsize'] != null) as TransactionExtended[], true); + const cpfpSummary = calculateGoodBlockCpfp(height, templateTxs?.filter(tx => tx['effectiveFeePerVsize'] != null) as MempoolTransactionExtended[], []); // classify const { transactions: classifiedTxs } = this.summarizeBlockTransactions(blockHash, cpfpSummary.transactions); const classifiedTxMap: { [txid: string]: TransactionClassified } = {}; @@ -891,7 +892,7 @@ class Blocks { } } - const cpfpSummary: CpfpSummary = calculateGoodBlockCpfp(block.height, transactions); + const cpfpSummary: CpfpSummary = calculateGoodBlockCpfp(block.height, transactions, Object.values(mempool.getAccelerations()).map(a => ({ txid: a.txid, max_bid: a.feeDelta }))); const blockExtended: BlockExtended = await this.$getBlockExtended(block, cpfpSummary.transactions); const blockSummary: BlockSummary = this.summarizeBlockTransactions(block.id, cpfpSummary.transactions); this.updateTimerProgress(timer, `got block data for ${this.currentBlockHeight}`); diff --git a/backend/src/api/cpfp.ts b/backend/src/api/cpfp.ts index 1b0873b7a..9bf311ff4 100644 --- a/backend/src/api/cpfp.ts +++ b/backend/src/api/cpfp.ts @@ -1,6 +1,7 @@ -import { CpfpCluster, CpfpInfo, CpfpSummary, MempoolTransactionExtended, TransactionExtended } from '../mempool.interfaces'; -import { GraphTx, convertToGraphTx, expandRelativesGraph, initializeRelatives, mempoolComparator, removeAncestors, setAncestorScores } from './mini-miner'; +import { Ancestor, CpfpCluster, CpfpInfo, CpfpSummary, MempoolTransactionExtended, TransactionExtended } from '../mempool.interfaces'; +import { GraphTx, convertToGraphTx, expandRelativesGraph, initializeRelatives, makeBlockTemplate, mempoolComparator, removeAncestors, setAncestorScores } from './mini-miner'; import memPool from './mempool'; +import { Acceleration } from './acceleration'; const CPFP_UPDATE_INTERVAL = 60_000; // update CPFP info at most once per 60s per transaction const MAX_CLUSTER_ITERATIONS = 100; @@ -95,8 +96,70 @@ export function calculateFastBlockCpfp(height: number, transactions: Transaction }; } -export function calculateGoodBlockCpfp(height: number, transactions: TransactionExtended[]): CpfpSummary { - return calculateFastBlockCpfp(height, transactions, true); +export function calculateGoodBlockCpfp(height: number, transactions: MempoolTransactionExtended[], accelerations: Acceleration[]): CpfpSummary { + const txMap: { [txid: string]: MempoolTransactionExtended } = {}; + for (const tx of transactions) { + txMap[tx.txid] = tx; + } + const template = makeBlockTemplate(transactions, accelerations, 1, Infinity, Infinity); + const clusters = new Map(); + for (const tx of template) { + const cluster = tx.cluster || []; + const root = cluster.length ? cluster[cluster.length - 1] : null; + if (cluster.length > 1 && root && !clusters.has(root)) { + clusters.set(root, cluster); + } + txMap[tx.txid].effectiveFeePerVsize = tx.effectiveFeePerVsize; + } + + const clusterArray: CpfpCluster[] = []; + + for (const cluster of clusters.values()) { + for (const txid of cluster) { + const mempoolTx = txMap[txid]; + if (mempoolTx) { + const ancestors: Ancestor[] = []; + const descendants: Ancestor[] = []; + let matched = false; + cluster.forEach(relativeTxid => { + if (relativeTxid === txid) { + matched = true; + } else { + const relative = { + txid: relativeTxid, + fee: txMap[relativeTxid].fee, + weight: (txMap[relativeTxid].adjustedVsize * 4) || txMap[relativeTxid].weight, + }; + if (matched) { + descendants.push(relative); + } else { + ancestors.push(relative); + } + } + }); + if (mempoolTx.ancestors?.length !== ancestors.length || mempoolTx.descendants?.length !== descendants.length) { + mempoolTx.cpfpDirty = true; + } + Object.assign(mempoolTx, { ancestors, descendants, bestDescendant: null, cpfpChecked: true }); + } + } + const root = cluster[cluster.length - 1]; + clusterArray.push({ + root: root, + height, + txs: cluster.reverse().map(txid => ({ + txid, + fee: txMap[txid].fee, + weight: (txMap[txid].adjustedVsize * 4) || txMap[txid].weight, + })), + effectiveFeePerVsize: txMap[root].effectiveFeePerVsize, + }); + } + + return { + transactions: transactions.map(tx => txMap[tx.txid]), + clusters: clusterArray, + }; } /**