diff --git a/backend/src/api/audit.ts b/backend/src/api/audit.ts index 0acffb1c2..2108cccf9 100644 --- a/backend/src/api/audit.ts +++ b/backend/src/api/audit.ts @@ -5,7 +5,7 @@ import { TransactionExtended, MempoolBlockWithTransactions } from '../mempool.in 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 }, accelerations: { [txid: string]: number }) + auditBlock(transactions: TransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: TransactionExtended }) : { censored: string[], added: string[], fresh: string[], sigop: string[], accelerated: string[], score: number, similarity: number } { if (!projectedBlocks?.[0]?.transactionIds || !mempool) { return { censored: [], added: [], fresh: [], sigop: [], accelerated: [], score: 0, similarity: 1 }; @@ -27,7 +27,7 @@ class Audit { const now = Math.round((Date.now() / 1000)); for (const tx of transactions) { inBlock[tx.txid] = tx; - if (accelerations[tx.txid]) { + if (tx.acceleration) { accelerated.push(tx.txid); } } diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index 0a343c376..cd85309f4 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -213,6 +213,7 @@ class BitcoinRoutes { effectiveFeePerVsize: tx.effectiveFeePerVsize || null, sigops: tx.sigops, adjustedVsize: tx.adjustedVsize, + acceleration: tx.acceleration }); return; } diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 37b09a417..7a3dc8a31 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -101,6 +101,7 @@ export class Common { fee: tx.fee, vsize: tx.weight / 4, value: tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0), + acc: tx.acceleration || undefined, rate: tx.effectiveFeePerVsize, }; } diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index d3bbad3f4..c1f2646ab 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -169,7 +169,7 @@ class MempoolBlocks { for (let i = 0; i < Math.max(mempoolBlocks.length, prevBlocks.length); i++) { let added: TransactionStripped[] = []; let removed: string[] = []; - const changed: { txid: string, rate: number | undefined }[] = []; + const changed: { txid: string, rate: number | undefined, acc: number | undefined }[] = []; if (mempoolBlocks[i] && !prevBlocks[i]) { added = mempoolBlocks[i].transactions; } else if (!mempoolBlocks[i] && prevBlocks[i]) { @@ -191,8 +191,8 @@ class MempoolBlocks { mempoolBlocks[i].transactions.forEach(tx => { if (!prevIds[tx.txid]) { added.push(tx); - } else if (tx.rate !== prevIds[tx.txid].rate) { - changed.push({ txid: tx.txid, rate: tx.rate }); + } else if (tx.rate !== prevIds[tx.txid].rate || tx.acc !== prevIds[tx.txid].acc) { + changed.push({ txid: tx.txid, rate: tx.rate, acc: tx.acc }); } }); } @@ -205,7 +205,7 @@ class MempoolBlocks { return mempoolBlockDeltas; } - public async $makeBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, saveResults: boolean = false, accelerations: { [txid: string]: number } = {}): Promise { + public async $makeBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, saveResults: boolean = false): Promise { const start = Date.now(); // reset mempool short ids @@ -221,7 +221,7 @@ class MempoolBlocks { if (entry.uid != null) { strippedMempool.set(entry.uid, { uid: entry.uid, - fee: entry.fee, + fee: entry.fee + (entry.acceleration || 0), weight: (entry.adjustedVsize * 4), sigops: entry.sigops, feePerVsize: entry.adjustedFeePerVsize || entry.feePerVsize, @@ -281,13 +281,14 @@ class MempoolBlocks { for (const tx of Object.values(added)) { this.setUid(tx); } + const removedUids = removed.map(tx => this.getUid(tx)).filter(uid => uid != null) as number[]; // prepare a stripped down version of the mempool with only the minimum necessary data // to reduce the overhead of passing this data to the worker thread const addedStripped: CompactThreadTransaction[] = added.filter(entry => entry.uid != null).map(entry => { return { uid: entry.uid || 0, - fee: entry.fee, + fee: entry.fee + (entry.acceleration || 0), weight: (entry.adjustedVsize * 4), sigops: entry.sigops, feePerVsize: entry.adjustedFeePerVsize || entry.feePerVsize, diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index e3543f4fc..f87924ba2 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -22,6 +22,8 @@ class Mempool { private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[]) => Promise) | undefined; + private accelerations: { [txId: string]: number } = {}; + private txPerSecondArray: number[] = []; private txPerSecond: number = 0; @@ -232,6 +234,17 @@ class Mempool { const newTransactionsStripped = newTransactions.map((tx) => Common.stripTransaction(tx)); this.latestTransactions = newTransactionsStripped.concat(this.latestTransactions).slice(0, 6); + const newAccelerations: { txid: string, delta: number }[] = []; + newTransactions.forEach(tx => { + if (tx.txid.startsWith('00')) { + const delta = Math.floor(Math.random() * 100000) + 100000; + newAccelerations.push({ txid: tx.txid, delta }); + tx.acceleration = delta; + } + }); + this.addAccelerations(newAccelerations); + this.removeAccelerations(deletedTransactions.map(tx => tx.txid)); + if (!this.inSync && transactions.length === Object.keys(this.mempoolCache).length) { this.inSync = true; logger.notice('The mempool is now in sync!'); @@ -256,6 +269,22 @@ class Mempool { this.clearTimer(timer); } + public getAccelerations(): { [txid: string]: number } { + return this.accelerations; + } + + public addAccelerations(newAccelerations: { txid: string, delta: number }[]): void { + for (const acceleration of newAccelerations) { + this.accelerations[acceleration.txid] = acceleration.delta; + } + } + + public removeAccelerations(txids: string[]): void { + for (const txid of txids) { + delete this.accelerations[txid]; + } + } + private startTimer() { const state: any = { start: Date.now(), diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 2afda7a61..27f141d21 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -21,6 +21,7 @@ import Audit from './audit'; import { deepClone } from '../utils/clone'; import priceUpdater from '../tasks/price-updater'; import { ApiPrice } from '../repositories/PricesRepository'; +import mempool from './mempool'; class WebsocketHandler { private wss: WebSocket.Server | undefined; @@ -559,7 +560,7 @@ class WebsocketHandler { } if (Common.indexingEnabled() && memPool.isInSync()) { - const { censored, added, fresh, sigop, accelerated, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool, accelerations); + const { censored, added, fresh, sigop, accelerated, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool); const matchRate = Math.round(score * 100 * 100) / 100; const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions.map((tx) => { @@ -624,6 +625,8 @@ class WebsocketHandler { const da = difficultyAdjustment.getDifficultyAdjustment(); const fees = feeApi.getRecommendedFee(); + memPool.removeAccelerations(txIds); + // update init data this.updateInitData(); diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 25e5ae2aa..ce482a010 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -86,6 +86,7 @@ export interface TransactionExtended extends IEsploraApi.Transaction { block: number, vsize: number, }; + acceleration?: number; uid?: number; } @@ -174,6 +175,7 @@ export interface TransactionStripped { fee: number; vsize: number; value: number; + acc?: number; rate?: number; // effective fee rate } diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index 15e41f1a7..2f6efabbc 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -133,7 +133,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On } } - update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined }[], direction: string = 'left', resetLayout: boolean = false): void { + update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined, acc: number | undefined }[], direction: string = 'left', resetLayout: boolean = false): void { if (this.scene) { this.scene.update(add, remove, change, direction, resetLayout); this.start(); diff --git a/frontend/src/app/components/block-overview-graph/block-scene.ts b/frontend/src/app/components/block-overview-graph/block-scene.ts index 0cd5c9391..043037d51 100644 --- a/frontend/src/app/components/block-overview-graph/block-scene.ts +++ b/frontend/src/app/components/block-overview-graph/block-scene.ts @@ -156,7 +156,7 @@ export default class BlockScene { this.updateAll(startTime, 200, direction); } - update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined }[], direction: string = 'left', resetLayout: boolean = false): void { + update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined, acc: number | undefined }[], direction: string = 'left', resetLayout: boolean = false): void { const startTime = performance.now(); const removed = this.removeBatch(remove, startTime, direction); @@ -181,6 +181,7 @@ export default class BlockScene { // update effective rates change.forEach(tx => { if (this.txs[tx.txid]) { + this.txs[tx.txid].acc = tx.acc; this.txs[tx.txid].feerate = tx.rate || (this.txs[tx.txid].fee / this.txs[tx.txid].vsize); this.txs[tx.txid].rate = tx.rate; this.txs[tx.txid].dirty = true; 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 e72af769a..ee15b53a2 100644 --- a/frontend/src/app/components/block-overview-graph/tx-view.ts +++ b/frontend/src/app/components/block-overview-graph/tx-view.ts @@ -37,6 +37,7 @@ export default class TxView implements TransactionStripped { vsize: number; value: number; feerate: number; + acc?: number; rate?: number; status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'accelerated'; context?: 'projected' | 'actual'; @@ -61,6 +62,7 @@ export default class TxView implements TransactionStripped { this.vsize = tx.vsize; this.value = tx.value; this.feerate = tx.rate || (tx.fee / tx.vsize); // sort by effective fee rate where available + this.acc = tx.acc; this.rate = tx.rate; this.status = tx.status; this.initialised = false; @@ -165,6 +167,11 @@ export default class TxView implements TransactionStripped { const feeLevelColor = feeColors[feeLevelIndex] || feeColors[mempoolFeeColors.length - 1]; // Normal mode if (!this.scene?.highlightingEnabled) { + if (this.acc) { + return auditColors.accelerated; + } else { + return feeLevelColor; + } return feeLevelColor; } // Block audit @@ -189,7 +196,11 @@ export default class TxView implements TransactionStripped { return feeLevelColor; } default: - return feeLevelColor; + if (this.acc) { + return auditColors.accelerated; + } else { + return feeLevelColor; + } } } } 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 d7a8265f3..9c1133200 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 @@ -29,7 +29,8 @@ - Effective fee rate + Effective fee rate + Accelerated fee rate {{ effectiveRate | feeRounding }} sat/vB diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts index 61c294263..65d0f984c 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts @@ -21,6 +21,7 @@ export class BlockOverviewTooltipComponent implements OnChanges { vsize = 1; feeRate = 0; effectiveRate; + acceleration; tooltipPosition: Position = { x: 0, y: 0 }; @@ -53,6 +54,7 @@ export class BlockOverviewTooltipComponent implements OnChanges { this.vsize = tx.vsize || 1; this.feeRate = this.fee / this.vsize; this.effectiveRate = tx.rate; + this.acceleration = tx.acc; } } } diff --git a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts index 540046e13..17212d3a3 100644 --- a/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts +++ b/frontend/src/app/components/mempool-block-overview/mempool-block-overview.component.ts @@ -95,7 +95,6 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang updateBlock(delta: MempoolBlockDelta): void { const blockMined = (this.stateService.latestBlockHeight > this.lastBlockHeight); - if (this.blockIndex !== this.index) { const direction = (this.blockIndex == null || this.index < this.blockIndex) ? this.poolDirection : this.chainDirection; this.blockGraph.replace(delta.added, direction); diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index 64eeeaecd..582da76ea 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -478,7 +478,8 @@ - Effective fee rate + Accelerated fee rate + Effective fee rate
{{ tx.effectiveFeePerVsize | feeRounding }} sat/vB diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index dd32b05e8..9376b6c56 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -178,6 +178,9 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { } else { this.tx.effectiveFeePerVsize = cpfpInfo.effectiveFeePerVsize; } + if (cpfpInfo.acceleration) { + this.tx.acceleration = cpfpInfo.acceleration; + } this.cpfpInfo = cpfpInfo; this.hasEffectiveFeeRate = hasRelatives || (this.tx.effectiveFeePerVsize && (Math.abs(this.tx.effectiveFeePerVsize - this.tx.feePerVsize) > 0.01)); diff --git a/frontend/src/app/interfaces/electrs.interface.ts b/frontend/src/app/interfaces/electrs.interface.ts index dcccfb67c..bc9e55fbf 100644 --- a/frontend/src/app/interfaces/electrs.interface.ts +++ b/frontend/src/app/interfaces/electrs.interface.ts @@ -19,6 +19,7 @@ export interface Transaction { ancestors?: Ancestor[]; bestDescendant?: BestDescendant | null; cpfpChecked?: boolean; + acceleration?: 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 58189e7ef..894a85fdf 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -27,6 +27,7 @@ export interface CpfpInfo { effectiveFeePerVsize?: number; sigops?: number; adjustedVsize?: number; + acceleration?: number; } export interface RbfInfo { diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index d9de023d6..8e2a42c48 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -57,7 +57,7 @@ export interface MempoolBlockWithTransactions extends MempoolBlock { export interface MempoolBlockDelta { added: TransactionStripped[], removed: string[], - changed?: { txid: string, rate: number | undefined }[]; + changed?: { txid: string, rate: number | undefined, acc: number | undefined }[]; } export interface MempoolInfo { @@ -75,6 +75,7 @@ export interface TransactionStripped { fee: number; vsize: number; value: number; + acc?: number; // acceleration delta rate?: number; // effective fee rate status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'accelerated'; context?: 'projected' | 'actual';