From 3f0c3c1952176cf98ea866666637d937aee41d12 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 11 Nov 2023 07:16:59 +0000 Subject: [PATCH 1/2] Add apiService cachedRequest function, apply to outspends requests --- .../transactions-list.component.ts | 10 ++++- .../tx-bowtie-graph.component.ts | 2 +- frontend/src/app/services/api.service.ts | 44 ++++++++++++++++++- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts index c49ff0e3c..9541864fc 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -6,7 +6,7 @@ import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.inter import { ElectrsApiService } from '../../services/electrs-api.service'; import { environment } from '../../../environments/environment'; import { AssetsService } from '../../services/assets.service'; -import { filter, map, tap, switchMap, shareReplay } from 'rxjs/operators'; +import { filter, map, tap, switchMap, shareReplay, catchError } from 'rxjs/operators'; import { BlockExtended } from '../../interfaces/node-api.interface'; import { ApiService } from '../../services/api.service'; import { PriceService } from '../../services/price.service'; @@ -53,6 +53,7 @@ export class TransactionsListComponent implements OnInit, OnChanges { private assetsService: AssetsService, private ref: ChangeDetectorRef, private priceService: PriceService, + private cd: ChangeDetectorRef, ) { } ngOnInit(): void { @@ -75,7 +76,7 @@ export class TransactionsListComponent implements OnInit, OnChanges { for (let i = 0; i < txIds.length; i += 50) { batches.push(txIds.slice(i, i + 50)); } - return forkJoin(batches.map(batch => this.apiService.getOutspendsBatched$(batch))); + return forkJoin(batches.map(batch => { return this.apiService.cachedRequest(this.apiService.getOutspendsBatched$, 5000, batch); })); } else { return of([]); } @@ -90,6 +91,7 @@ export class TransactionsListComponent implements OnInit, OnChanges { outspends.forEach((outspend, i) => { transactions[i]._outspends = outspend; }); + this.cd.markForCheck(); }), ), this.stateService.utxoSpent$ @@ -108,6 +110,10 @@ export class TransactionsListComponent implements OnInit, OnChanges { .pipe( filter(() => this.stateService.env.LIGHTNING), switchMap((txIds) => this.apiService.getChannelByTxIds$(txIds)), + catchError((error) => { + // handle 404 + return of([]); + }), tap((channels) => { if (!this.transactions) { return; diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts index 97e74957e..1dafb327f 100644 --- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts +++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts @@ -123,7 +123,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { .pipe( switchMap((txid) => { if (!this.cached) { - return this.apiService.getOutspendsBatched$([txid]); + return this.apiService.cachedRequest(this.apiService.getOutspendsBatched$, 5000, [txid]); } else { return of(null); } diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 744474f9d..046b27812 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights, RbfTree, BlockAudit } from '../interfaces/node-api.interface'; -import { Observable, of } from 'rxjs'; +import { BehaviorSubject, Observable, catchError, filter, of, shareReplay, take, tap } from 'rxjs'; import { StateService } from './state.service'; import { IBackendInfo, WebsocketResponse } from '../interfaces/websocket.interface'; import { Outspend, Transaction } from '../interfaces/electrs.interface'; @@ -20,6 +20,8 @@ export class ApiService { private apiBaseUrl: string; // base URL is protocol, hostname, and port private apiBasePath: string; // network path is /testnet, etc. or '' for mainnet + private requestCache = new Map, expiry: number }>; + constructor( private httpClient: HttpClient, private stateService: StateService, @@ -44,6 +46,46 @@ export class ApiService { } } + private generateCacheKey(functionName: string, params: any[]): string { + return functionName + JSON.stringify(params); + } + + // delete expired cache entries + private cleanExpiredCache(): void { + this.requestCache.forEach((value, key) => { + if (value.expiry < Date.now()) { + this.requestCache.delete(key); + } + }); + } + + cachedRequest Observable>( + apiFunction: F, + expireAfter: number, // in ms + ...params: Parameters + ): Observable { + this.cleanExpiredCache(); + + const cacheKey = this.generateCacheKey(apiFunction.name, params); + if (!this.requestCache.has(cacheKey)) { + const subject = new BehaviorSubject(null); + this.requestCache.set(cacheKey, { subject, expiry: Date.now() + expireAfter }); + + apiFunction.bind(this)(...params).pipe( + tap(data => { + subject.next(data as T); + }), + catchError((error) => { + subject.error(error); + return of(null); + }), + shareReplay(1), + ).subscribe(); + } + + return this.requestCache.get(cacheKey).subject.asObservable().pipe(filter(val => val !== null), take(1)); + } + list2HStatistics$(): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/2h'); } From 6740ab61f3509ed0d64d11bea5d3a83f3475b8b4 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 11 Nov 2023 07:46:25 +0000 Subject: [PATCH 2/2] Reduce outspend API cache expiry to 250ms --- .../transactions-list/transactions-list.component.ts | 5 ++--- .../components/tx-bowtie-graph/tx-bowtie-graph.component.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts index 9541864fc..05d74a75d 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -53,7 +53,6 @@ export class TransactionsListComponent implements OnInit, OnChanges { private assetsService: AssetsService, private ref: ChangeDetectorRef, private priceService: PriceService, - private cd: ChangeDetectorRef, ) { } ngOnInit(): void { @@ -76,7 +75,7 @@ export class TransactionsListComponent implements OnInit, OnChanges { for (let i = 0; i < txIds.length; i += 50) { batches.push(txIds.slice(i, i + 50)); } - return forkJoin(batches.map(batch => { return this.apiService.cachedRequest(this.apiService.getOutspendsBatched$, 5000, batch); })); + return forkJoin(batches.map(batch => { return this.apiService.cachedRequest(this.apiService.getOutspendsBatched$, 250, batch); })); } else { return of([]); } @@ -91,7 +90,7 @@ export class TransactionsListComponent implements OnInit, OnChanges { outspends.forEach((outspend, i) => { transactions[i]._outspends = outspend; }); - this.cd.markForCheck(); + this.ref.markForCheck(); }), ), this.stateService.utxoSpent$ diff --git a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts index 1dafb327f..3bc352a35 100644 --- a/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts +++ b/frontend/src/app/components/tx-bowtie-graph/tx-bowtie-graph.component.ts @@ -123,7 +123,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { .pipe( switchMap((txid) => { if (!this.cached) { - return this.apiService.cachedRequest(this.apiService.getOutspendsBatched$, 5000, [txid]); + return this.apiService.cachedRequest(this.apiService.getOutspendsBatched$, 250, [txid]); } else { return of(null); }