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 @@
+ +

Transaction

diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 7b0bfd2b6..4568e4cee 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -36,6 +36,7 @@ export class TransactionComponent implements OnInit, OnDestroy { potentialP2shGains: 0, }; isRbfTransaction: boolean; + rbfTransaction: undefined | Transaction; constructor( private route: ActivatedRoute, @@ -126,6 +127,9 @@ export class TransactionComponent implements OnInit, OnDestroy { this.audioService.playSound('magic'); this.findBlockAndSetFeeRating(); }); + + this.stateService.txReplaced$ + .subscribe((rbfTransaction) => this.rbfTransaction = rbfTransaction); } handleLoadElectrsTransactionError(error: any): Observable { @@ -198,6 +202,7 @@ export class TransactionComponent implements OnInit, OnDestroy { this.feeRating = undefined; this.waitingForTransaction = false; this.isLoadingTx = true; + this.rbfTransaction = undefined; this.transactionTime = -1; document.body.scrollTo(0, 0); this.leaveTransaction(); diff --git a/frontend/src/app/interfaces/websocket.interface.ts b/frontend/src/app/interfaces/websocket.interface.ts index eabff1583..6c59a4b6b 100644 --- a/frontend/src/app/interfaces/websocket.interface.ts +++ b/frontend/src/app/interfaces/websocket.interface.ts @@ -11,6 +11,7 @@ export interface WebsocketResponse { action?: string; data?: string[]; tx?: Transaction; + rbfTransaction?: Transaction; 'track-tx'?: string; 'track-address'?: string; 'track-asset'?: string; diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index f0c32ee98..4cd6a72ea 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -24,6 +24,7 @@ export class StateService { mempoolStats$ = new ReplaySubject(1); mempoolBlocks$ = new ReplaySubject(1); txConfirmed$ = new Subject(); + txReplaced$ = new Subject(); mempoolTransactions$ = new Subject(); blockTransactions$ = new Subject(); diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index 9be503356..65846df7f 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -89,6 +89,10 @@ export class WebsocketService { this.stateService.conversions$.next(response.conversions); } + if (response.rbfTransaction) { + this.stateService.txReplaced$.next(response.rbfTransaction); + } + if (response['mempool-blocks']) { this.stateService.mempoolBlocks$.next(response['mempool-blocks']); } diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index b53685633..268ce2238 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -410,3 +410,9 @@ h1, h2, h3 { .tooltip-inner { max-width: inherit; } + +.alert-mempool { + color: #ffffff; + background-color: #653b9c; + border-color: #3a1c61; +}