From c5300c950b92b68540ab37106f91817285eb35cf Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 31 Mar 2024 07:55:43 +0000 Subject: [PATCH 1/2] Add track-txs websocket subscription --- backend/src/api/websocket-handler.ts | 121 ++++++++++++++++++++++++++- backend/src/mempool.interfaces.ts | 16 ++++ 2 files changed, 135 insertions(+), 2 deletions(-) diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 63e7f383c..41202e0fa 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -2,7 +2,7 @@ import logger from '../logger'; import * as WebSocket from 'ws'; import { BlockExtended, TransactionExtended, MempoolTransactionExtended, WebsocketResponse, - OptimizedStatistic, ILoadingIndicators, GbtCandidates, + OptimizedStatistic, ILoadingIndicators, GbtCandidates, TxTrackingInfo, } from '../mempool.interfaces'; import blocks from './blocks'; import memPool from './mempool'; @@ -209,6 +209,52 @@ class WebsocketHandler { } } + if (parsedMessage && parsedMessage['track-txs']) { + const txids: string[] = []; + if (Array.isArray(parsedMessage['track-txs'])) { + for (const txid of parsedMessage['track-txs']) { + if (/^[a-fA-F0-9]{64}$/.test(txid)) { + txids.push(txid); + } + } + } + + const txs: { [txid: string]: TxTrackingInfo } = {}; + for (const txid of txids) { + const txInfo: TxTrackingInfo = { + confirmed: true, + }; + const rbfCacheTxid = rbfCache.getReplacedBy(txid); + if (rbfCacheTxid) { + txInfo.replacedBy = rbfCacheTxid; + txInfo.confirmed = false; + } + const tx = memPool.getMempool()[txid]; + if (tx && tx.position) { + txInfo.position = { + ...tx.position + }; + if (tx.acceleration) { + txInfo.accelerated = tx.acceleration; + } + } + if (tx) { + txInfo.confirmed = false; + } + txs[txid] = txInfo; + } + + if (txids.length) { + client['track-txs'] = txids; + } else { + client['track-txs'] = null; + } + + if (Object.keys(txs).length) { + response['tracked-txs'] = JSON.stringify(txs); + } + } + if (parsedMessage && parsedMessage['track-address']) { const validAddress = this.testAddress(parsedMessage['track-address']); if (validAddress) { @@ -517,6 +563,11 @@ class WebsocketHandler { if (client['track-tx']) { trackedTxs.add(client['track-tx']); } + if (client['track-txs']) { + for (const txid of client['track-txs']) { + trackedTxs.add(client['track-tx']); + } + } }); if (trackedTxs.size > 0) { for (const tx of newTransactions) { @@ -713,6 +764,46 @@ class WebsocketHandler { } } + if (client['track-txs']) { + const txids = client['track-txs']; + const txs: { [txid: string]: TxTrackingInfo } = {}; + for (const txid of txids) { + const txInfo: TxTrackingInfo = {}; + const outspends = outspendCache[txid]; + if (outspends && Object.keys(outspends).length) { + txInfo.utxoSpent = outspends; + } + const replacedBy = rbfChanges.map[txid] ? rbfCache.getReplacedBy(txid) : false; + if (replacedBy) { + txInfo.replacedBy = replacedBy; + } + const mempoolTx = newMempool[txid]; + if (mempoolTx && mempoolTx.position) { + txInfo.position = { + ...mempoolTx.position, + accelerated: mempoolTx.acceleration || undefined, + }; + if (!mempoolTx.cpfpChecked) { + calculateCpfp(mempoolTx, newMempool); + } + if (mempoolTx.cpfpDirty) { + txInfo.cpfp = { + ancestors: mempoolTx.ancestors, + bestDescendant: mempoolTx.bestDescendant || null, + descendants: mempoolTx.descendants || null, + effectiveFeePerVsize: mempoolTx.effectiveFeePerVsize || null, + sigops: mempoolTx.sigops, + adjustedVsize: mempoolTx.adjustedVsize, + }; + } + } + txs[txid] = txInfo; + } + if (Object.keys(txs).length) { + response['tracked-txs'] = JSON.stringify(txs); + } + } + if (client['track-mempool-block'] >= 0 && memPool.isInSync()) { const index = client['track-mempool-block']; if (mBlockDeltas[index]) { @@ -931,6 +1022,28 @@ class WebsocketHandler { } } + if (client['track-txs']) { + const txs: { [txid: string]: TxTrackingInfo } = {}; + for (const txid of client['track-txs']) { + if (confirmedTxids[txid]) { + txs[txid] = { confirmed: true }; + } else { + const mempoolTx = _memPool[txid]; + if (mempoolTx && mempoolTx.position) { + txs[txid] = { + position: { + ...mempoolTx.position, + }, + accelerated: mempoolTx.acceleration || undefined, + }; + } + } + } + if (Object.keys(txs).length) { + response['tracked-txs'] = JSON.stringify(txs); + } + } + if (client['track-address']) { const foundTransactions: TransactionExtended[] = Array.from(addressCache[client['track-address']]?.values() || []); @@ -1126,6 +1239,7 @@ class WebsocketHandler { private printLogs(): void { if (this.wss) { let numTxSubs = 0; + let numTxsSubs = 0; let numProjectedSubs = 0; let numRbfSubs = 0; @@ -1133,6 +1247,9 @@ class WebsocketHandler { if (client['track-tx']) { numTxSubs++; } + if (client['track-txs']) { + numTxsSubs++; + } if (client['track-mempool-block'] != null && client['track-mempool-block'] >= 0) { numProjectedSubs++; } @@ -1145,7 +1262,7 @@ class WebsocketHandler { const diff = count - this.numClients; this.numClients = count; logger.debug(`${count} websocket clients | ${this.numConnected} connected | ${this.numDisconnected} disconnected | (${diff >= 0 ? '+' : ''}${diff})`); - logger.debug(`websocket subscriptions: track-tx: ${numTxSubs}, track-mempool-block: ${numProjectedSubs} track-rbf: ${numRbfSubs}`); + logger.debug(`websocket subscriptions: track-tx: ${numTxSubs}, track-txs: ${numTxsSubs}, track-mempool-block: ${numProjectedSubs} track-rbf: ${numRbfSubs}`); this.numConnected = 0; this.numDisconnected = 0; } diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index a12351fe3..5b31f13a6 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -411,6 +411,22 @@ export interface OptimizedStatistic { vsizes: number[]; } +export interface TxTrackingInfo { + replacedBy?: string, + position?: { block: number, vsize: number, accelerated?: boolean }, + cpfp?: { + ancestors?: Ancestor[], + bestDescendant?: Ancestor | null, + descendants?: Ancestor[] | null, + effectiveFeePerVsize?: number | null, + sigops: number, + adjustedVsize: number, + }, + utxoSpent?: { [vout: number]: { vin: number, txid: string } }, + accelerated?: boolean, + confirmed?: boolean +} + export interface WebsocketResponse { action: string; data: string[]; From 7deee5d5f2589ca738086425bfdb450dfef3b0bf Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 31 Mar 2024 09:45:26 +0000 Subject: [PATCH 2/2] Fix outspend tracking typo --- backend/src/api/websocket-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 41202e0fa..a20088128 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -565,7 +565,7 @@ class WebsocketHandler { } if (client['track-txs']) { for (const txid of client['track-txs']) { - trackedTxs.add(client['track-tx']); + trackedTxs.add(txid); } } });