From d7b874ac49a1416a71b23cb547a0d3153215a84f Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 25 Jul 2023 14:00:17 +0900 Subject: [PATCH] Exclude all conflicting transactions from audit score --- backend/src/api/audit.ts | 13 +++++++------ backend/src/api/rbf-cache.ts | 18 ++++++++++++++++++ .../components/block-overview-graph/tx-view.ts | 4 ++-- .../block-overview-tooltip.component.html | 2 +- .../app/components/block/block.component.ts | 12 ++++++------ .../src/app/interfaces/node-api.interface.ts | 2 +- .../src/app/interfaces/websocket.interface.ts | 2 +- 7 files changed, 36 insertions(+), 17 deletions(-) diff --git a/backend/src/api/audit.ts b/backend/src/api/audit.ts index f7aecfca8..a909fc2b6 100644 --- a/backend/src/api/audit.ts +++ b/backend/src/api/audit.ts @@ -15,7 +15,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 or lastBoosted within PROPAGATION_MARGIN - const fullrbf: string[] = []; // either missing or present, and part of a fullrbf replacement + const rbf: string[] = []; // either missing or present, and either part of a full-rbf replacement, or a conflict with the mined block const isCensored = {}; // missing, without excuse const isDisplaced = {}; let displacedWeight = 0; @@ -36,8 +36,9 @@ 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]) { - if (rbfCache.isFullRbf(txid)) { - fullrbf.push(txid); + // allow missing transactions which either belong to a full rbf tree, or conflict with any transaction in the mined block + if (rbfCache.has(txid) && (rbfCache.isFullRbf(txid) || rbfCache.anyInSameTree(txid, (tx) => inBlock[tx.txid]))) { + rbf.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); @@ -98,8 +99,8 @@ class Audit { if (inTemplate[tx.txid]) { matches.push(tx.txid); } else { - if (rbfCache.isFullRbf(tx.txid)) { - fullrbf.push(tx.txid); + if (rbfCache.has(tx.txid)) { + rbf.push(tx.txid); } else if (!isDisplaced[tx.txid]) { added.push(tx.txid); } @@ -147,7 +148,7 @@ class Audit { added, fresh, sigop: [], - fullrbf, + fullrbf: rbf, score, similarity, }; diff --git a/backend/src/api/rbf-cache.ts b/backend/src/api/rbf-cache.ts index 367ba1c0e..f28dd0de3 100644 --- a/backend/src/api/rbf-cache.ts +++ b/backend/src/api/rbf-cache.ts @@ -100,6 +100,24 @@ class RbfCache { this.dirtyTrees.add(treeId); } + public has(txId: string): boolean { + return this.txs.has(txId); + } + + public anyInSameTree(txId: string, predicate: (tx: RbfTransaction) => boolean): boolean { + const tree = this.getRbfTree(txId); + if (!tree) { + return false; + } + const txs = this.getTransactionsInTree(tree); + for (const tx of txs) { + if (predicate(tx)) { + return true; + } + } + return false; + } + public getReplacedBy(txId: string): string | undefined { return this.replacedBy.get(txId); } 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 452bb38f5..1b8c88704 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' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'fullrbf'; + status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf'; context?: 'projected' | 'actual'; scene?: BlockScene; @@ -207,7 +207,7 @@ export default class TxView implements TransactionStripped { return auditColors.censored; case 'missing': case 'sigop': - case 'fullrbf': + case 'rbf': return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1]; case 'fresh': case 'freshcpfp': 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 59450326b..c62779b69 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 @@ -53,7 +53,7 @@ Recently CPFP'd Added Marginal fee rate - Full RBF + Conflicting diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index ce3317255..ec9a49504 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -339,7 +339,7 @@ export class BlockComponent implements OnInit, OnDestroy { const isSelected = {}; const isFresh = {}; const isSigop = {}; - const isFullRbf = {}; + const isRbf = {}; this.numMissing = 0; this.numUnexpected = 0; @@ -363,7 +363,7 @@ export class BlockComponent implements OnInit, OnDestroy { isSigop[txid] = true; } for (const txid of blockAudit.fullrbfTxs || []) { - isFullRbf[txid] = true; + isRbf[txid] = true; } // set transaction statuses for (const tx of blockAudit.template) { @@ -381,8 +381,8 @@ export class BlockComponent implements OnInit, OnDestroy { } } else if (isSigop[tx.txid]) { tx.status = 'sigop'; - } else if (isFullRbf[tx.txid]) { - tx.status = 'fullrbf'; + } else if (isRbf[tx.txid]) { + tx.status = 'rbf'; } else { tx.status = 'missing'; } @@ -398,8 +398,8 @@ export class BlockComponent implements OnInit, OnDestroy { tx.status = 'added'; } else if (inTemplate[tx.txid]) { tx.status = 'found'; - } else if (isFullRbf[tx.txid]) { - tx.status = 'fullrbf'; + } else if (isRbf[tx.txid]) { + tx.status = 'rbf'; } else { tx.status = 'selected'; isSelected[tx.txid] = true; diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 2b434c44d..4249fd9db 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -174,7 +174,7 @@ export interface TransactionStripped { vsize: number; value: number; rate?: number; // effective fee rate - status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'fullrbf'; + status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf'; context?: 'projected' | 'actual'; } diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index 15d97fa8d..e0ecdfeda 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -89,7 +89,7 @@ export interface TransactionStripped { vsize: number; value: number; rate?: number; // effective fee rate - status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'fullrbf'; + status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf'; context?: 'projected' | 'actual'; }