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;
|
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
|
// Send all new incoming transactions related to tracked address
|
||||||
if (client['track-address']) {
|
if (client['track-address']) {
|
||||||
const foundTransactions: TransactionExtended[] = [];
|
const foundTransactions: TransactionExtended[] = [];
|
||||||
|
@ -72,8 +72,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #unconfirmedTemplate>
|
<ng-template #unconfirmedTemplate>
|
||||||
@ -132,6 +130,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
<h2>Inputs & Outputs</h2>
|
<h2>Inputs & Outputs</h2>
|
||||||
|
|
||||||
<app-transactions-list [transactions]="[tx]" [transactionPage]="true"></app-transactions-list>
|
<app-transactions-list [transactions]="[tx]" [transactionPage]="true"></app-transactions-list>
|
||||||
@ -227,11 +227,18 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template [ngIf]="error">
|
<ng-template [ngIf]="error">
|
||||||
<div class="text-center">
|
|
||||||
Error loading transaction data.
|
<div class="text-center" *ngIf="waitingForTransaction">
|
||||||
<br>
|
<h3>Transaction not found.</h3>
|
||||||
<i>{{ error.error }}</i>
|
<h5>Waiting for it to appear in the mempool...</h5>
|
||||||
|
<div class="spinner-border text-light mt-2"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ng-template #errorTemplate>
|
||||||
|
<div class="text-center">
|
||||||
|
<h3>{{ error.error }}</h3>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
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 { 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 { StateService } from '../../services/state.service';
|
||||||
import { WebsocketService } from '../../services/websocket.service';
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
import { AudioService } from 'src/app/services/audio.service';
|
import { AudioService } from 'src/app/services/audio.service';
|
||||||
@ -26,6 +26,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||||||
txInBlockIndex: number;
|
txInBlockIndex: number;
|
||||||
isLoadingTx = true;
|
isLoadingTx = true;
|
||||||
error: any = undefined;
|
error: any = undefined;
|
||||||
|
waitingForTransaction = false;
|
||||||
latestBlock: Block;
|
latestBlock: Block;
|
||||||
transactionTime = -1;
|
transactionTime = -1;
|
||||||
subscription: Subscription;
|
subscription: Subscription;
|
||||||
@ -47,35 +48,47 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||||||
switchMap((params: ParamMap) => {
|
switchMap((params: ParamMap) => {
|
||||||
this.txId = params.get('id') || '';
|
this.txId = params.get('id') || '';
|
||||||
this.seoService.setTitle('Transaction: ' + this.txId, true);
|
this.seoService.setTitle('Transaction: ' + this.txId, true);
|
||||||
this.error = undefined;
|
this.resetTransaction();
|
||||||
this.feeRating = undefined;
|
|
||||||
this.isLoadingTx = true;
|
|
||||||
this.transactionTime = -1;
|
|
||||||
document.body.scrollTo(0, 0);
|
|
||||||
this.leaveTransaction();
|
|
||||||
return merge(
|
return merge(
|
||||||
of(true),
|
of(true),
|
||||||
this.stateService.connectionState$
|
this.stateService.connectionState$.pipe(
|
||||||
.pipe(filter((state) => state === 2 && this.tx && !this.tx.status.confirmed) ),
|
filter((state) => state === 2 && this.tx && !this.tx.status.confirmed)
|
||||||
)
|
),
|
||||||
.pipe(
|
);
|
||||||
switchMap(() => {
|
}),
|
||||||
if (history.state.data) {
|
flatMap(() => {
|
||||||
return of(history.state.data);
|
let transactionObservable$: Observable<Transaction>;
|
||||||
}
|
if (history.state.data) {
|
||||||
return this.electrsApiService.getTransaction$(this.txId);
|
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) => {
|
.subscribe((tx: Transaction) => {
|
||||||
|
if (!tx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.tx = tx;
|
this.tx = tx;
|
||||||
this.isLoadingTx = false;
|
this.isLoadingTx = false;
|
||||||
|
this.error = undefined;
|
||||||
|
this.waitingForTransaction = false;
|
||||||
this.setMempoolBlocksSubscription();
|
this.setMempoolBlocksSubscription();
|
||||||
|
|
||||||
if (!tx.status.confirmed) {
|
if (!tx.status.confirmed) {
|
||||||
this.websocketService.startTrackTransaction(tx.txid);
|
this.websocketService.startTrackTransaction(tx.txid);
|
||||||
this.getTransactionTime();
|
|
||||||
|
if (tx.firstSeen) {
|
||||||
|
this.transactionTime = tx.firstSeen;
|
||||||
|
} else {
|
||||||
|
this.getTransactionTime();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.findBlockAndSetFeeRating();
|
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() {
|
setMempoolBlocksSubscription() {
|
||||||
this.stateService.mempoolBlocks$
|
this.stateService.mempoolBlocks$
|
||||||
.subscribe((mempoolBlocks) => {
|
.subscribe((mempoolBlocks) => {
|
||||||
@ -161,8 +184,14 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
resetTransaction() {
|
||||||
this.subscription.unsubscribe();
|
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();
|
this.leaveTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,4 +199,9 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
|||||||
this.websocketService.stopTrackingTransaction();
|
this.websocketService.stopTrackingTransaction();
|
||||||
this.stateService.markBlock$.next({});
|
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 {
|
export interface WebsocketResponse {
|
||||||
block?: Block;
|
block?: Block;
|
||||||
@ -10,6 +10,7 @@ export interface WebsocketResponse {
|
|||||||
vBytesPerSecond?: number;
|
vBytesPerSecond?: number;
|
||||||
action?: string;
|
action?: string;
|
||||||
data?: string[];
|
data?: string[];
|
||||||
|
tx?: Transaction;
|
||||||
'track-tx'?: string;
|
'track-tx'?: string;
|
||||||
'track-address'?: string;
|
'track-address'?: string;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
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 { StateService } from './state.service';
|
||||||
import { Block, Transaction } from '../interfaces/electrs.interface';
|
import { Block, Transaction } from '../interfaces/electrs.interface';
|
||||||
import { Subscription } from 'rxjs';
|
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_PROTOCOL = (document.location.protocol === 'https:') ? 'wss:' : 'ws:';
|
||||||
const WEB_SOCKET_URL = WEB_SOCKET_PROTOCOL + '//' + document.location.hostname + ':' + document.location.port + '/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({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
@ -44,6 +48,10 @@ export class WebsocketService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response.tx) {
|
||||||
|
this.stateService.mempoolTransactions$.next(response.tx);
|
||||||
|
}
|
||||||
|
|
||||||
if (response.block) {
|
if (response.block) {
|
||||||
if (response.block.height > this.stateService.latestBlockHeight) {
|
if (response.block.height > this.stateService.latestBlockHeight) {
|
||||||
this.stateService.latestBlockHeight = response.block.height;
|
this.stateService.latestBlockHeight = response.block.height;
|
||||||
@ -115,7 +123,7 @@ export class WebsocketService {
|
|||||||
},
|
},
|
||||||
(err: Error) => {
|
(err: Error) => {
|
||||||
console.log(err);
|
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();
|
this.goOffline();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -155,7 +163,7 @@ export class WebsocketService {
|
|||||||
this.stateService.connectionState$.next(0);
|
this.stateService.connectionState$.next(0);
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
this.startSubscription(true);
|
this.startSubscription(true);
|
||||||
}, 10000);
|
}, OFFLINE_RETRY_AFTER_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
startOnlineCheck() {
|
startOnlineCheck() {
|
||||||
@ -171,7 +179,7 @@ export class WebsocketService {
|
|||||||
this.subscription.unsubscribe();
|
this.subscription.unsubscribe();
|
||||||
this.goOffline();
|
this.goOffline();
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, EXPECT_PING_RESPONSE_AFTER_MS);
|
||||||
}, 30000);
|
}, OFFLINE_PING_CHECK_AFTER_MS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user