mirror of
https://github.com/mempool/mempool.git
synced 2025-03-03 17:47:01 +01:00
parent
d504d0ecc0
commit
f85771e03f
8 changed files with 98 additions and 30 deletions
|
@ -8,6 +8,7 @@ import { IBitcoinApi } from './bitcoin/bitcoin-api.interface';
|
|||
import loadingIndicators from './loading-indicators';
|
||||
import bitcoinClient from './bitcoin/bitcoin-client';
|
||||
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
|
||||
import rbfCache from './rbf-cache';
|
||||
|
||||
class Mempool {
|
||||
private static WEBSOCKET_REFRESH_RATE_MS = 10000;
|
||||
|
@ -200,6 +201,17 @@ class Mempool {
|
|||
logger.debug('Mempool updated in ' + time / 1000 + ' seconds');
|
||||
}
|
||||
|
||||
public handleRbfTransactions(rbfTransactions: { [txid: string]: TransactionExtended; }) {
|
||||
for (const rbfTransaction in rbfTransactions) {
|
||||
if (this.mempoolCache[rbfTransaction]) {
|
||||
// Store replaced transactions
|
||||
rbfCache.add(rbfTransaction, rbfTransactions[rbfTransaction].txid);
|
||||
// Erase the replaced transactions from the local mempool
|
||||
delete this.mempoolCache[rbfTransaction];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateTxPerSecond() {
|
||||
const nowMinusTimeSpan = new Date().getTime() - (1000 * config.STATISTICS.TX_PER_SECOND_SAMPLE_PERIOD);
|
||||
this.txPerSecondArray = this.txPerSecondArray.filter((unixTime) => unixTime > nowMinusTimeSpan);
|
||||
|
|
34
backend/src/api/rbf-cache.ts
Normal file
34
backend/src/api/rbf-cache.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
export interface CachedRbf {
|
||||
txid: string;
|
||||
expires: Date;
|
||||
}
|
||||
|
||||
class RbfCache {
|
||||
private cache: { [txid: string]: CachedRbf; } = {};
|
||||
|
||||
constructor() {
|
||||
setInterval(this.cleanup.bind(this), 1000 * 60 * 60);
|
||||
}
|
||||
|
||||
public add(replacedTxId: string, newTxId: string): void {
|
||||
this.cache[replacedTxId] = {
|
||||
expires: new Date(Date.now() + 1000 * 604800), // 1 week
|
||||
txid: newTxId,
|
||||
};
|
||||
}
|
||||
|
||||
public get(txId: string): CachedRbf | undefined {
|
||||
return this.cache[txId];
|
||||
}
|
||||
|
||||
private cleanup(): void {
|
||||
const currentDate = new Date();
|
||||
for (const c in this.cache) {
|
||||
if (this.cache[c].expires < currentDate) {
|
||||
delete this.cache[c];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new RbfCache();
|
|
@ -11,6 +11,7 @@ import { Common } from './common';
|
|||
import loadingIndicators from './loading-indicators';
|
||||
import config from '../config';
|
||||
import transactionUtils from './transaction-utils';
|
||||
import rbfCache from './rbf-cache';
|
||||
|
||||
class WebsocketHandler {
|
||||
private wss: WebSocket.Server | undefined;
|
||||
|
@ -48,29 +49,38 @@ class WebsocketHandler {
|
|||
if (parsedMessage && parsedMessage['track-tx']) {
|
||||
if (/^[a-fA-F0-9]{64}$/.test(parsedMessage['track-tx'])) {
|
||||
client['track-tx'] = parsedMessage['track-tx'];
|
||||
// Client is telling the transaction wasn't found but it might have appeared before we had the time to start watching for it
|
||||
// Client is telling the transaction wasn't found
|
||||
if (parsedMessage['watch-mempool']) {
|
||||
const tx = memPool.getMempool()[client['track-tx']];
|
||||
if (tx) {
|
||||
if (config.MEMPOOL.BACKEND === 'esplora') {
|
||||
response['tx'] = tx;
|
||||
const rbfCacheTx = rbfCache.get(client['track-tx']);
|
||||
if (rbfCacheTx) {
|
||||
response['txReplaced'] = {
|
||||
txid: rbfCacheTx.txid,
|
||||
};
|
||||
client['track-tx'] = null;
|
||||
} else {
|
||||
// It might have appeared before we had the time to start watching for it
|
||||
const tx = memPool.getMempool()[client['track-tx']];
|
||||
if (tx) {
|
||||
if (config.MEMPOOL.BACKEND === 'esplora') {
|
||||
response['tx'] = tx;
|
||||
} else {
|
||||
// tx.prevout is missing from transactions when in bitcoind mode
|
||||
try {
|
||||
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
||||
response['tx'] = fullTx;
|
||||
} catch (e) {
|
||||
logger.debug('Error finding transaction: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// tx.prevouts is missing from transactions when in bitcoind mode
|
||||
try {
|
||||
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
||||
const fullTx = await transactionUtils.$getTransactionExtended(client['track-tx'], true);
|
||||
response['tx'] = fullTx;
|
||||
} catch (e) {
|
||||
logger.debug('Error finding transaction: ' + (e instanceof Error ? e.message : e));
|
||||
logger.debug('Error finding transaction. ' + (e instanceof Error ? e.message : e));
|
||||
client['track-mempool-tx'] = parsedMessage['track-tx'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const fullTx = await transactionUtils.$getTransactionExtended(client['track-tx'], true);
|
||||
response['tx'] = fullTx;
|
||||
} catch (e) {
|
||||
logger.debug('Error finding transaction. ' + (e instanceof Error ? e.message : e));
|
||||
client['track-mempool-tx'] = parsedMessage['track-tx'];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -221,14 +231,10 @@ class WebsocketHandler {
|
|||
|
||||
mempoolBlocks.updateMempoolBlocks(newMempool);
|
||||
const mBlocks = mempoolBlocks.getMempoolBlocks();
|
||||
const mempool = memPool.getMempool();
|
||||
const mempoolInfo = memPool.getMempoolInfo();
|
||||
const vBytesPerSecond = memPool.getVBytesPerSecond();
|
||||
const rbfTransactions = Common.findRbfTransactions(newTransactions, deletedTransactions);
|
||||
|
||||
for (const rbfTransaction in rbfTransactions) {
|
||||
delete mempool[rbfTransaction];
|
||||
}
|
||||
memPool.handleRbfTransactions(rbfTransactions);
|
||||
|
||||
this.wss.clients.forEach(async (client: WebSocket) => {
|
||||
if (client.readyState !== WebSocket.OPEN) {
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
<div class="title-block">
|
||||
<div *ngIf="rbfTransaction" class="alert alert-mempool" role="alert">
|
||||
<span i18n="transaction.rbf.replacement|RBF replacement">This transaction has been replaced by:</span>
|
||||
<a class="alert-link" [routerLink]="['/tx/' | relativeUrl, rbfTransaction.txid]" [state]="{ data: rbfTransaction }">
|
||||
<a class="alert-link" [routerLink]="['/tx/' | relativeUrl, rbfTransaction.txid]" [state]="{ data: rbfTransaction.size ? rbfTransaction : null }">
|
||||
<span class="d-inline d-lg-none">{{ rbfTransaction.txid | shortenString : 24 }}</span>
|
||||
<span class="d-none d-lg-inline">{{ rbfTransaction.txid }}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<ng-container>
|
||||
<ng-container *ngIf="!rbfTransaction || rbfTransaction?.size">
|
||||
<h1 i18n="shared.transaction">Transaction</h1>
|
||||
|
||||
<span class="tx-link float-left">
|
||||
|
|
|
@ -37,6 +37,8 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||
transactionTime = -1;
|
||||
subscription: Subscription;
|
||||
fetchCpfpSubscription: Subscription;
|
||||
txReplacedSubscription: Subscription;
|
||||
blocksSubscription: Subscription;
|
||||
rbfTransaction: undefined | Transaction;
|
||||
cpfpInfo: CpfpInfo | null;
|
||||
showCpfpDetails = false;
|
||||
|
@ -217,7 +219,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
);
|
||||
|
||||
this.stateService.blocks$.subscribe(([block, txConfirmed]) => {
|
||||
this.blocksSubscription = this.stateService.blocks$.subscribe(([block, txConfirmed]) => {
|
||||
this.latestBlock = block;
|
||||
|
||||
if (txConfirmed && this.tx) {
|
||||
|
@ -232,9 +234,13 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
});
|
||||
|
||||
this.stateService.txReplaced$.subscribe(
|
||||
(rbfTransaction) => (this.rbfTransaction = rbfTransaction)
|
||||
);
|
||||
this.txReplacedSubscription = this.stateService.txReplaced$.subscribe((rbfTransaction) => {
|
||||
if (!rbfTransaction.size) {
|
||||
this.error = new Error();
|
||||
this.waitingForTransaction = false;
|
||||
}
|
||||
this.rbfTransaction = rbfTransaction;
|
||||
});
|
||||
}
|
||||
|
||||
handleLoadElectrsTransactionError(error: any): Observable<any> {
|
||||
|
@ -302,6 +308,8 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||
ngOnDestroy() {
|
||||
this.subscription.unsubscribe();
|
||||
this.fetchCpfpSubscription.unsubscribe();
|
||||
this.txReplacedSubscription.unsubscribe();
|
||||
this.blocksSubscription.unsubscribe();
|
||||
this.leaveTransaction();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,8 @@ export interface WebsocketResponse {
|
|||
action?: string;
|
||||
data?: string[];
|
||||
tx?: Transaction;
|
||||
rbfTransaction?: Transaction;
|
||||
rbfTransaction?: ReplacedTransaction;
|
||||
txReplaced?: ReplacedTransaction;
|
||||
utxoSpent?: object;
|
||||
transactions?: TransactionStripped[];
|
||||
loadingIndicators?: ILoadingIndicators;
|
||||
|
@ -27,6 +28,9 @@ export interface WebsocketResponse {
|
|||
'track-bisq-market'?: string;
|
||||
}
|
||||
|
||||
export interface ReplacedTransaction extends Transaction {
|
||||
txid: string;
|
||||
}
|
||||
export interface MempoolBlock {
|
||||
blink?: boolean;
|
||||
height?: number;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
|
||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs';
|
||||
import { Transaction } from '../interfaces/electrs.interface';
|
||||
import { IBackendInfo, MempoolBlock, MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
||||
import { IBackendInfo, MempoolBlock, MempoolInfo, ReplacedTransaction, TransactionStripped } from '../interfaces/websocket.interface';
|
||||
import { BlockExtended, OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||
import { Router, NavigationStart } from '@angular/router';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
|
@ -80,7 +80,7 @@ export class StateService {
|
|||
bsqPrice$ = new ReplaySubject<number>(1);
|
||||
mempoolInfo$ = new ReplaySubject<MempoolInfo>(1);
|
||||
mempoolBlocks$ = new ReplaySubject<MempoolBlock[]>(1);
|
||||
txReplaced$ = new Subject<Transaction>();
|
||||
txReplaced$ = new Subject<ReplacedTransaction>();
|
||||
utxoSpent$ = new Subject<object>();
|
||||
mempoolTransactions$ = new Subject<Transaction>();
|
||||
blockTransactions$ = new Subject<Transaction>();
|
||||
|
|
|
@ -239,6 +239,10 @@ export class WebsocketService {
|
|||
this.stateService.txReplaced$.next(response.rbfTransaction);
|
||||
}
|
||||
|
||||
if (response.txReplaced) {
|
||||
this.stateService.txReplaced$.next(response.txReplaced);
|
||||
}
|
||||
|
||||
if (response['mempool-blocks']) {
|
||||
this.stateService.mempoolBlocks$.next(response['mempool-blocks']);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue