diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index fd04707b5..ac848d4a4 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -162,6 +162,7 @@ class BitcoinRoutes { adjustedVsize: tx.adjustedVsize, acceleration: tx.acceleration, acceleratedBy: tx.acceleratedBy || undefined, + acceleratedAt: tx.acceleratedAt || undefined, }); return; } diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 3c1c434bd..51f9a1618 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -449,12 +449,14 @@ class MempoolBlocks { } mempoolTx.acceleration = true; mempoolTx.acceleratedBy = isAcceleratedBy[txid] || acceleration?.pools; + mempoolTx.acceleratedAt = acceleration?.added; for (const ancestor of mempoolTx.ancestors || []) { if (!mempool[ancestor.txid].acceleration) { mempool[ancestor.txid].cpfpDirty = true; } mempool[ancestor.txid].acceleration = true; mempool[ancestor.txid].acceleratedBy = mempoolTx.acceleratedBy; + mempool[ancestor.txid].acceleratedAt = mempoolTx.acceleratedAt; isAcceleratedBy[ancestor.txid] = mempoolTx.acceleratedBy; } } else { diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 3c8c06ecf..8d1a85994 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -822,6 +822,7 @@ class WebsocketHandler { ...mempoolTx.position, accelerated: mempoolTx.acceleration || undefined, acceleratedBy: mempoolTx.acceleratedBy || undefined, + acceleratedAt: mempoolTx.acceleratedAt || undefined, }, accelerationPositions: memPool.getAccelerationPositions(mempoolTx.txid), }; @@ -862,6 +863,7 @@ class WebsocketHandler { ...mempoolTx.position, accelerated: mempoolTx.acceleration || undefined, acceleratedBy: mempoolTx.acceleratedBy || undefined, + acceleratedAt: mempoolTx.acceleratedAt || undefined, }; if (!mempoolTx.cpfpChecked) { calculateCpfp(mempoolTx, newMempool); @@ -1139,6 +1141,7 @@ class WebsocketHandler { ...mempoolTx.position, accelerated: mempoolTx.acceleration || undefined, acceleratedBy: mempoolTx.acceleratedBy || undefined, + acceleratedAt: mempoolTx.acceleratedAt || undefined, }, accelerationPositions: memPool.getAccelerationPositions(mempoolTx.txid), }); @@ -1160,6 +1163,7 @@ class WebsocketHandler { }, accelerated: mempoolTx.acceleration || undefined, acceleratedBy: mempoolTx.acceleratedBy || undefined, + acceleratedAt: mempoolTx.acceleratedAt || undefined, }; } } diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 2d3c7a7c0..34375604e 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -112,6 +112,7 @@ export interface TransactionExtended extends IEsploraApi.Transaction { }; acceleration?: boolean; acceleratedBy?: number[]; + acceleratedAt?: number; replacement?: boolean; uid?: number; flags?: number; @@ -434,7 +435,7 @@ export interface OptimizedStatistic { export interface TxTrackingInfo { replacedBy?: string, - position?: { block: number, vsize: number, accelerated?: boolean, acceleratedBy?: number[] }, + position?: { block: number, vsize: number, accelerated?: boolean, acceleratedBy?: number[], acceleratedAt?: number }, cpfp?: { ancestors?: Ancestor[], bestDescendant?: Ancestor | null, @@ -446,6 +447,7 @@ export interface TxTrackingInfo { utxoSpent?: { [vout: number]: { vin: number, txid: string } }, accelerated?: boolean, acceleratedBy?: number[], + acceleratedAt?: number, confirmed?: boolean } diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index e79dd2b50..d2cc0789d 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -51,6 +51,7 @@ interface AuditStatus { accelerated?: boolean; conflict?: boolean; coinbase?: boolean; + firstSeen?: number; } @Component({ @@ -325,7 +326,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { const boostCost = acceleration.boostCost || acceleration.bidBoost; acceleration.acceleratedFeeRate = Math.max(acceleration.effectiveFee, acceleration.effectiveFee + boostCost) / acceleration.effectiveVsize; acceleration.boost = boostCost; - + this.tx.acceleratedAt = acceleration.added; this.accelerationInfo = acceleration; this.setIsAccelerated(); } @@ -368,6 +369,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { const isAccelerated = audit.acceleratedTxs.includes(txid); const isConflict = audit.fullrbfTxs.includes(txid); const isExpected = audit.template.some(tx => tx.txid === txid); + const firstSeen = audit.template.find(tx => tx.txid === txid)?.time; return { seen: isExpected || isPrioritized || isAccelerated, expected: isExpected, @@ -375,6 +377,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { prioritized: isPrioritized, conflict: isConflict, accelerated: isAccelerated, + firstSeen, }; }), retry({ count: 3, delay: 2000 }), @@ -388,6 +391,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { }) ).subscribe(auditStatus => { this.auditStatus = auditStatus; + if (this.auditStatus?.firstSeen) { + this.transactionTime = this.auditStatus.firstSeen; + } this.setIsAccelerated(); }); @@ -771,6 +777,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { if (cpfpInfo.acceleration) { this.tx.acceleration = cpfpInfo.acceleration; this.tx.acceleratedBy = cpfpInfo.acceleratedBy; + this.tx.acceleratedAt = cpfpInfo.acceleratedAt; this.setIsAccelerated(firstCpfp); } diff --git a/frontend/src/app/interfaces/electrs.interface.ts b/frontend/src/app/interfaces/electrs.interface.ts index 726649090..942386d8f 100644 --- a/frontend/src/app/interfaces/electrs.interface.ts +++ b/frontend/src/app/interfaces/electrs.interface.ts @@ -21,6 +21,7 @@ export interface Transaction { cpfpChecked?: boolean; acceleration?: boolean; acceleratedBy?: number[]; + acceleratedAt?: number; deleteAfter?: number; _unblinded?: any; _deduced?: boolean; diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index c3c7c9efe..eb5d3ba94 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -30,6 +30,7 @@ export interface CpfpInfo { adjustedVsize?: number; acceleration?: boolean; acceleratedBy?: number[]; + acceleratedAt?: number; } export interface RbfInfo {