diff --git a/backend/src/api/audit.ts b/backend/src/api/audit.ts index e79196a7a..f7aecfca8 100644 --- a/backend/src/api/audit.ts +++ b/backend/src/api/audit.ts @@ -1,12 +1,12 @@ import config from '../config'; import logger from '../logger'; -import { TransactionExtended, MempoolBlockWithTransactions } from '../mempool.interfaces'; +import { MempoolTransactionExtended, MempoolBlockWithTransactions } from '../mempool.interfaces'; import rbfCache from './rbf-cache'; const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first seen after which it is assumed to have propagated to all miners class Audit { - auditBlock(transactions: TransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: TransactionExtended }) + auditBlock(transactions: MempoolTransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: MempoolTransactionExtended }) : { censored: string[], added: string[], fresh: string[], sigop: string[], fullrbf: string[], score: number, similarity: number } { if (!projectedBlocks?.[0]?.transactionIds || !mempool) { return { censored: [], added: [], fresh: [], sigop: [], fullrbf: [], score: 0, similarity: 1 }; @@ -14,7 +14,7 @@ class Audit { const matches: string[] = []; // present in both mined block and template const added: string[] = []; // present in mined block, not in template - const fresh: string[] = []; // missing, but firstSeen within PROPAGATION_MARGIN + const fresh: string[] = []; // missing, but firstSeen or lastBoosted within PROPAGATION_MARGIN const fullrbf: string[] = []; // either missing or present, and part of a fullrbf replacement const isCensored = {}; // missing, without excuse const isDisplaced = {}; @@ -36,10 +36,13 @@ class Audit { // look for transactions that were expected in the template, but missing from the mined block for (const txid of projectedBlocks[0].transactionIds) { if (!inBlock[txid]) { - // tx is recent, may have reached the miner too late for inclusion if (rbfCache.isFullRbf(txid)) { fullrbf.push(txid); } else if (mempool[txid]?.firstSeen != null && (now - (mempool[txid]?.firstSeen || 0)) <= PROPAGATION_MARGIN) { + // tx is recent, may have reached the miner too late for inclusion + fresh.push(txid); + } else if (mempool[txid]?.lastBoosted != null && (now - (mempool[txid]?.lastBoosted || 0)) <= PROPAGATION_MARGIN) { + // tx was recently cpfp'd, miner may not have the latest effective rate fresh.push(txid); } else { isCensored[txid] = true; diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index d5538854a..08508310d 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -457,6 +457,7 @@ class MempoolBlocks { }; if (matched) { descendants.push(relative); + mempoolTx.lastBoosted = Math.max(mempoolTx.lastBoosted || 0, mempool[txid].firstSeen || 0); } else { ancestors.push(relative); } diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index a051eea4f..3dad451ac 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -100,6 +100,7 @@ export interface MempoolTransactionExtended extends TransactionExtended { adjustedVsize: number; adjustedFeePerVsize: number; inputs?: number[]; + lastBoosted?: number; } export interface AuditTransaction { diff --git a/frontend/src/app/components/block-overview-graph/tx-view.ts b/frontend/src/app/components/block-overview-graph/tx-view.ts index 77f5a182a..452bb38f5 100644 --- a/frontend/src/app/components/block-overview-graph/tx-view.ts +++ b/frontend/src/app/components/block-overview-graph/tx-view.ts @@ -38,7 +38,7 @@ export default class TxView implements TransactionStripped { value: number; feerate: number; rate?: number; - status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'fullrbf'; + status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'fullrbf'; context?: 'projected' | 'actual'; scene?: BlockScene; @@ -210,6 +210,7 @@ export default class TxView implements TransactionStripped { case 'fullrbf': return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1]; case 'fresh': + case 'freshcpfp': return auditColors.missing; case 'added': return auditColors.added; diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html index 5ebd8fceb..59450326b 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html @@ -50,6 +50,7 @@