diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index ca6e2c1cc..c3e0f1a42 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -25,4 +25,26 @@ export class Common { arr.push(transactions[lastindex].feePerVsize); return arr; } + + static findRbfTransactions(added: TransactionExtended[], deleted: TransactionExtended[]): { [txid: string]: TransactionExtended } { + const matches: { [txid: string]: TransactionExtended } = {}; + deleted + // The replaced tx must have at least one input with nSequence < maxint-1 (That’s the opt-in) + .filter((tx) => tx.vin.some((vin) => vin.sequence < 0xfffffffe)) + .forEach((deletedTx) => { + const foundMatches = added.find((addedTx) => { + // The new tx must, absolutely speaking, pay at least as much fee as the replaced tx. + return addedTx.fee > deletedTx.fee + // The new transaction must pay more fee per kB than the replaced tx. + && addedTx.feePerVsize > deletedTx.feePerVsize + // Spends one or more of the same inputs + && deletedTx.vin.some((deletedVin) => + addedTx.vin.some((vin) => vin.txid === deletedVin.txid)); + }); + if (foundMatches) { + matches[deletedTx.txid] = foundMatches; + } + }); + return matches; + } } diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 3381bf237..353c11b1d 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -49,9 +49,9 @@ class MempoolBlocks { transactions.push(tx); } else { mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockVSize, mempoolBlocks.length)); - blockVSize = 0; - blockSize = 0; - transactions = []; + blockVSize = tx.vsize; + blockSize = tx.size; + transactions = [tx]; } }); if (transactions.length) { diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index abc6a5c46..7e083e56c 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -123,31 +123,30 @@ class Mempool { } } - // Replace mempool to clear already confirmed transactions + // Replace mempool to clear deleted transactions const newMempool = {}; - transactions.forEach((tx) => { - if (this.mempoolCache[tx]) { + const deletedTransactions: Transaction[] = []; + for (const tx in this.mempoolCache) { + if (transactions.indexOf(tx) > -1) { newMempool[tx] = this.mempoolCache[tx]; } else { - hasChange = true; + deletedTransactions.push(this.mempoolCache[tx]); } - }); + } if (!this.inSync && transactions.length === Object.keys(newMempool).length) { this.inSync = true; console.log('The mempool is now in sync!'); } - console.log(`New mempool size: ${Object.keys(newMempool).length} Change: ${diff}`); - - this.mempoolCache = newMempool; - - if (hasChange && this.mempoolChangedCallback) { - this.mempoolChangedCallback(this.mempoolCache, newTransactions); + if (this.mempoolChangedCallback && (hasChange || deletedTransactions.length)) { + this.mempoolCache = newMempool; + this.mempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions); } const end = new Date().getTime(); const time = end - start; + console.log(`New mempool size: ${Object.keys(newMempool).length} Change: ${diff}`); console.log('Mempool updated in ' + time / 1000 + ' seconds'); } catch (err) { console.log('getRawMempool error.', err); diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index f29d7a8bb..87e187c9f 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -8,6 +8,7 @@ import backendInfo from './backend-info'; import mempoolBlocks from './mempool-blocks'; import fiatConversion from './fiat-conversion'; import * as os from 'os'; +import { Common } from './common'; class WebsocketHandler { private wss: WebSocket.Server | undefined; @@ -121,7 +122,8 @@ class WebsocketHandler { }); } - handleMempoolChange(newMempool: { [txid: string]: TransactionExtended }, newTransactions: TransactionExtended[]) { + handleMempoolChange(newMempool: { [txid: string]: TransactionExtended }, + newTransactions: TransactionExtended[], deletedTransactions: TransactionExtended[]) { if (!this.wss) { throw new Error('WebSocket.Server is not set'); } @@ -130,6 +132,7 @@ class WebsocketHandler { const mBlocks = mempoolBlocks.getMempoolBlocks(); const mempoolInfo = memPool.getMempoolInfo(); const vBytesPerSecond = memPool.getVBytesPerSecond(); + const rbfTransactions = Common.findRbfTransactions(newTransactions, deletedTransactions); this.wss.clients.forEach((client: WebSocket) => { if (client.readyState !== WebSocket.OPEN) { @@ -204,6 +207,15 @@ class WebsocketHandler { } } + if (client['track-tx'] && rbfTransactions[client['track-tx']]) { + for (const rbfTransaction in rbfTransactions) { + if (client['track-tx'] === rbfTransaction) { + response['rbfTransaction'] = rbfTransactions[rbfTransaction]; + break; + } + } + } + if (Object.keys(response).length) { client.send(JSON.stringify(response)); } @@ -228,7 +240,7 @@ class WebsocketHandler { } } - matchRate = Math.ceil((matches.length / txIds.length) * 100); + matchRate = Math.round((matches.length / (txIds.length - 1)) * 100); if (matchRate > 0) { const currentMemPool = memPool.getMempool(); for (const txId of matches) { diff --git a/backend/src/interfaces.ts b/backend/src/interfaces.ts index 341cee0de..f14561847 100644 --- a/backend/src/interfaces.ts +++ b/backend/src/interfaces.ts @@ -35,9 +35,6 @@ export interface Transaction { } export interface TransactionExtended extends Transaction { - txid: string; - fee: number; - size: number; vsize: number; feePerVsize: number; firstSeen: number; diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index 17846964d..397971e93 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -1,6 +1,14 @@