Wait for an push transactions that hasn't yet appeared in the mempool
This commit is contained in:
		
							parent
							
								
									36d88fec9c
								
							
						
					
					
						commit
						d432b3ce4a
					
				@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user