import { Validator } from "../validator/validator"; import { filter, map, tap, switchMap, mergeMap, first, share, catchError } from "rxjs/operators"; import { Subscription, of, timer, interval, merge, Observable } from "rxjs"; import { TransferRequest } from "../transfer-request"; import { INetworkListener, NrtTransaction } from "../interfaces/network-listener.i"; import { ITransferRequestStore } from "../interfaces/transfer-request-store.i"; import { logger } from "../logger/logger"; const DAY = 24 * 60 * 60 * 1000; const TEN_SECOND = 10000; export class LightValidatorNode { private inProgress = new Set(); private transferRequestInProgress = new Map(); private previousTxs = this.store.retrieve().pipe(switchMap((previousTxs) => merge(...previousTxs.map(tx => of({ transaction: tx }))))); constructor( protected store: ITransferRequestStore, protected validator: Validator, protected networkListener: INetworkListener) { } private checkIfIsAlreadyWatching = filter(({ transactionID }) => !this.inProgress.has(transactionID)) private markTransactionInProgress = tap(({ transactionID }) => this.inProgress.add(transactionID)); private addTransactionToStore = tap((tx) => this.store.put(tx)); private removeTransactionFromStore = tap(({ id }) => this.store.remove(id)); private updateTransferRequestInProgress = tap((transferRequest: TransferRequest) => this.transferRequestInProgress.set(transferRequest.id, transferRequest)); private startValidation = mergeMap(({ transactionID, amount, sourceAddress, sourceNetwork, destinationAddress, destinationNetwork }) => { return this.validator.validate({ id: transactionID, amount: Number(amount), ticker: sourceNetwork, source: { address: sourceAddress, nbConf: 1, network: sourceNetwork, }, destination: { address: destinationAddress, nbConf: 1, network: destinationNetwork, } }) }) private filterValidationCompleted = filter((transferRequest: TransferRequest) => { return transferRequest.isCompleted; }); private filterValidationSuccess = filter((transferRequest: TransferRequest) => { return transferRequest.isValid; }); private filterValidationError = filter((transferRequest: TransferRequest) => { return !transferRequest.isValid; }); private broadcastValidationSuccess = switchMap((transferRequest: TransferRequest) => { return this.networkListener.broadcastVerificationMessage({ success: true, ...transferRequest.toJSON(), }).pipe(switchMap(() => of(transferRequest))); }); private broadcastValidationError = switchMap((transferRequest: TransferRequest) => { return of(transferRequest); }); private createUnmarkObs(transferRequest: TransferRequest) { return timer(TEN_SECOND).pipe(first(), tap(() => { this.inProgress.delete(transferRequest.id) this.transferRequestInProgress.delete(transferRequest.id) })) } private unmarkTransactionInProgress = tap((transferRequest: TransferRequest) => { this.createUnmarkObs(transferRequest).subscribe(); }) private subscriptions: Subscription[] = []; private removeStaleTransferRequestEach(time: number) { this.subscriptions.push( interval(time).pipe( switchMap(() => { const obs = []; for (let transferRequest of this.transferRequestInProgress.values()) { const date = new Date; date.setHours(date.getHours() - 24); if (transferRequest.timestamp < date) { obs.push(this.createUnmarkObs(transferRequest)); } } return merge(...obs); }) ).subscribe() ); } public validationObservable = merge( this.previousTxs, this.networkListener.incommingTxObservable ).pipe( tap(console.log), map(({ transaction }) => transaction), this.checkIfIsAlreadyWatching, this.addTransactionToStore, this.markTransactionInProgress, this.startValidation, tap(console.log), catchError((err) => { logger.error(err); return of(null); }), filter((result) => !!result), map((tx) => tx as TransferRequest), this.updateTransferRequestInProgress, share(), ) as Observable; public completedValidationObservable = this.validationObservable.pipe( this.filterValidationCompleted, this.unmarkTransactionInProgress, this.removeTransactionFromStore, share() ) public successValidationObservable = this.completedValidationObservable.pipe( this.filterValidationSuccess, this.broadcastValidationSuccess, share() ) public failureValidationObservable = this.completedValidationObservable.pipe( this.filterValidationError, this.broadcastValidationError, share() ) public init() { this.removeStaleTransferRequestEach(DAY); this.subscriptions.push(...[ this.successValidationObservable.subscribe(), this.failureValidationObservable.subscribe() ]) } public stop() { if (this.subscriptions) { this.subscriptions.forEach((s) => s.unsubscribe()); this.inProgress = new Set(); this.transferRequestInProgress = new Map(); } } public get pendingTransferRequests(): Iterator { return this.transferRequestInProgress.values(); } public get transferRequestCount() { return this.transferRequestInProgress.size; } }