mirror of
https://github.com/mempool/mempool.git
synced 2025-01-18 21:32:55 +01:00
Wait for an push transactions that hasn't yet appeared in the mempool
This commit is contained in:
parent
c5796a8062
commit
e2671df4be
@ -133,6 +133,13 @@ class WebsocketHandler {
|
||||
response['mempool-blocks'] = mBlocks;
|
||||
}
|
||||
|
||||
if (client['track-tx']) {
|
||||
const tx = newTransactions.find((t) => t.txid === client['track-tx']);
|
||||
if (tx) {
|
||||
response['tx'] = tx;
|
||||
}
|
||||
}
|
||||
|
||||
// Send all new incoming transactions related to tracked address
|
||||
if (client['track-address']) {
|
||||
const foundTransactions: TransactionExtended[] = [];
|
||||
|
@ -72,8 +72,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
</ng-template>
|
||||
|
||||
<ng-template #unconfirmedTemplate>
|
||||
@ -132,6 +130,8 @@
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<br>
|
||||
|
||||
<h2>Inputs & Outputs</h2>
|
||||
|
||||
<app-transactions-list [transactions]="[tx]" [transactionPage]="true"></app-transactions-list>
|
||||
@ -227,11 +227,18 @@
|
||||
</ng-template>
|
||||
|
||||
<ng-template [ngIf]="error">
|
||||
<div class="text-center">
|
||||
Error loading transaction data.
|
||||
<br>
|
||||
<i>{{ error.error }}</i>
|
||||
|
||||
<div class="text-center" *ngIf="waitingForTransaction">
|
||||
<h3>Transaction not found.</h3>
|
||||
<h5>Waiting for it to appear in the mempool...</h5>
|
||||
<div class="spinner-border text-light mt-2"></div>
|
||||
</div>
|
||||
|
||||
<ng-template #errorTemplate>
|
||||
<div class="text-center">
|
||||
<h3>{{ error.error }}</h3>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
</div>
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||
import { switchMap, filter, take } from 'rxjs/operators';
|
||||
import { switchMap, filter, take, catchError, mergeMap, flatMap, mergeAll, tap, map } from 'rxjs/operators';
|
||||
import { Transaction, Block } from '../../interfaces/electrs.interface';
|
||||
import { of, merge, Subscription } from 'rxjs';
|
||||
import { of, merge, Subscription, Observable, scheduled } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { WebsocketService } from '../../services/websocket.service';
|
||||
import { AudioService } from 'src/app/services/audio.service';
|
||||
@ -26,6 +26,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
||||
txInBlockIndex: number;
|
||||
isLoadingTx = true;
|
||||
error: any = undefined;
|
||||
waitingForTransaction = false;
|
||||
latestBlock: Block;
|
||||
transactionTime = -1;
|
||||
subscription: Subscription;
|
||||
@ -47,35 +48,47 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
||||
switchMap((params: ParamMap) => {
|
||||
this.txId = params.get('id') || '';
|
||||
this.seoService.setTitle('Transaction: ' + this.txId, true);
|
||||
this.error = undefined;
|
||||
this.feeRating = undefined;
|
||||
this.isLoadingTx = true;
|
||||
this.transactionTime = -1;
|
||||
document.body.scrollTo(0, 0);
|
||||
this.leaveTransaction();
|
||||
this.resetTransaction();
|
||||
return merge(
|
||||
of(true),
|
||||
this.stateService.connectionState$
|
||||
.pipe(filter((state) => state === 2 && this.tx && !this.tx.status.confirmed) ),
|
||||
)
|
||||
.pipe(
|
||||
switchMap(() => {
|
||||
if (history.state.data) {
|
||||
return of(history.state.data);
|
||||
}
|
||||
return this.electrsApiService.getTransaction$(this.txId);
|
||||
})
|
||||
this.stateService.connectionState$.pipe(
|
||||
filter((state) => state === 2 && this.tx && !this.tx.status.confirmed)
|
||||
),
|
||||
);
|
||||
}),
|
||||
flatMap(() => {
|
||||
let transactionObservable$: Observable<Transaction>;
|
||||
if (history.state.data) {
|
||||
transactionObservable$ = of(history.state.data);
|
||||
} else {
|
||||
transactionObservable$ = this.electrsApiService.getTransaction$(this.txId).pipe(
|
||||
catchError(this.handleLoadElectrsTransactionError.bind(this))
|
||||
);
|
||||
}
|
||||
return merge(
|
||||
transactionObservable$,
|
||||
this.stateService.mempoolTransactions$
|
||||
);
|
||||
})
|
||||
)
|
||||
.subscribe((tx: Transaction) => {
|
||||
if (!tx) {
|
||||
return;
|
||||
}
|
||||
this.tx = tx;
|
||||
this.isLoadingTx = false;
|
||||
this.error = undefined;
|
||||
this.waitingForTransaction = false;
|
||||
this.setMempoolBlocksSubscription();
|
||||
|
||||
if (!tx.status.confirmed) {
|
||||
this.websocketService.startTrackTransaction(tx.txid);
|
||||
this.getTransactionTime();
|
||||
|
||||
if (tx.firstSeen) {
|
||||
this.transactionTime = tx.firstSeen;
|
||||
} else {
|
||||
this.getTransactionTime();
|
||||
}
|
||||
} else {
|
||||
this.findBlockAndSetFeeRating();
|
||||
}
|
||||
@ -107,6 +120,16 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
handleLoadElectrsTransactionError(error: any): Observable<any> {
|
||||
if (error.status === 404 && /^[a-fA-F0-9]{64}$/.test(this.txId)) {
|
||||
this.websocketService.startTrackTransaction(this.txId);
|
||||
this.waitingForTransaction = true;
|
||||
}
|
||||
this.error = error;
|
||||
this.isLoadingTx = false;
|
||||
return of(false);
|
||||
}
|
||||
|
||||
setMempoolBlocksSubscription() {
|
||||
this.stateService.mempoolBlocks$
|
||||
.subscribe((mempoolBlocks) => {
|
||||
@ -161,8 +184,14 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscription.unsubscribe();
|
||||
resetTransaction() {
|
||||
this.error = undefined;
|
||||
this.tx = null;
|
||||
this.feeRating = undefined;
|
||||
this.waitingForTransaction = false;
|
||||
this.isLoadingTx = true;
|
||||
this.transactionTime = -1;
|
||||
document.body.scrollTo(0, 0);
|
||||
this.leaveTransaction();
|
||||
}
|
||||
|
||||
@ -170,4 +199,9 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
||||
this.websocketService.stopTrackingTransaction();
|
||||
this.stateService.markBlock$.next({});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscription.unsubscribe();
|
||||
this.leaveTransaction();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Block } from './electrs.interface';
|
||||
import { Block, Transaction } from './electrs.interface';
|
||||
|
||||
export interface WebsocketResponse {
|
||||
block?: Block;
|
||||
@ -10,6 +10,7 @@ export interface WebsocketResponse {
|
||||
vBytesPerSecond?: number;
|
||||
action?: string;
|
||||
data?: string[];
|
||||
tx?: Transaction;
|
||||
'track-tx'?: string;
|
||||
'track-address'?: string;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
||||
import { WebsocketResponse, MempoolBlock } from '../interfaces/websocket.interface';
|
||||
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
||||
import { StateService } from './state.service';
|
||||
import { Block, Transaction } from '../interfaces/electrs.interface';
|
||||
import { Subscription } from 'rxjs';
|
||||
@ -8,6 +8,10 @@ import { Subscription } from 'rxjs';
|
||||
const WEB_SOCKET_PROTOCOL = (document.location.protocol === 'https:') ? 'wss:' : 'ws:';
|
||||
const WEB_SOCKET_URL = WEB_SOCKET_PROTOCOL + '//' + document.location.hostname + ':' + document.location.port + '/ws';
|
||||
|
||||
const OFFLINE_RETRY_AFTER_MS = 10000;
|
||||
const OFFLINE_PING_CHECK_AFTER_MS = 30000;
|
||||
const EXPECT_PING_RESPONSE_AFTER_MS = 1000;
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
@ -44,6 +48,10 @@ export class WebsocketService {
|
||||
});
|
||||
}
|
||||
|
||||
if (response.tx) {
|
||||
this.stateService.mempoolTransactions$.next(response.tx);
|
||||
}
|
||||
|
||||
if (response.block) {
|
||||
if (response.block.height > this.stateService.latestBlockHeight) {
|
||||
this.stateService.latestBlockHeight = response.block.height;
|
||||
@ -115,7 +123,7 @@ export class WebsocketService {
|
||||
},
|
||||
(err: Error) => {
|
||||
console.log(err);
|
||||
console.log('WebSocket error, trying to reconnect in 10 seconds');
|
||||
console.log(`WebSocket error, trying to reconnect in ${OFFLINE_RETRY_AFTER_MS} seconds`);
|
||||
this.goOffline();
|
||||
});
|
||||
}
|
||||
@ -155,7 +163,7 @@ export class WebsocketService {
|
||||
this.stateService.connectionState$.next(0);
|
||||
window.setTimeout(() => {
|
||||
this.startSubscription(true);
|
||||
}, 10000);
|
||||
}, OFFLINE_RETRY_AFTER_MS);
|
||||
}
|
||||
|
||||
startOnlineCheck() {
|
||||
@ -171,7 +179,7 @@ export class WebsocketService {
|
||||
this.subscription.unsubscribe();
|
||||
this.goOffline();
|
||||
}
|
||||
}, 1000);
|
||||
}, 30000);
|
||||
}, EXPECT_PING_RESPONSE_AFTER_MS);
|
||||
}, OFFLINE_PING_CHECK_AFTER_MS);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user