mirror of
https://github.com/mempool/mempool.git
synced 2025-02-24 14:50:52 +01:00
Merge pull request #4361 from mempool/mononaut/fix-double-outspend-request
ApiService caching layer
This commit is contained in:
commit
9845567c2f
3 changed files with 51 additions and 4 deletions
|
@ -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';
|
||||
|
@ -75,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 => this.apiService.getOutspendsBatched$(batch)));
|
||||
return forkJoin(batches.map(batch => { return this.apiService.cachedRequest(this.apiService.getOutspendsBatched$, 250, batch); }));
|
||||
} else {
|
||||
return of([]);
|
||||
}
|
||||
|
@ -90,6 +90,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||
outspends.forEach((outspend, i) => {
|
||||
transactions[i]._outspends = outspend;
|
||||
});
|
||||
this.ref.markForCheck();
|
||||
}),
|
||||
),
|
||||
this.stateService.utxoSpent$
|
||||
|
@ -108,6 +109,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;
|
||||
|
|
|
@ -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$, 250, [txid]);
|
||||
} else {
|
||||
return of(null);
|
||||
}
|
||||
|
|
|
@ -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<string, { subject: BehaviorSubject<any>, 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<T, F extends (...args: any[]) => Observable<T>>(
|
||||
apiFunction: F,
|
||||
expireAfter: number, // in ms
|
||||
...params: Parameters<F>
|
||||
): Observable<T> {
|
||||
this.cleanExpiredCache();
|
||||
|
||||
const cacheKey = this.generateCacheKey(apiFunction.name, params);
|
||||
if (!this.requestCache.has(cacheKey)) {
|
||||
const subject = new BehaviorSubject<T | null>(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<OptimizedMempoolStats[]> {
|
||||
return this.httpClient.get<OptimizedMempoolStats[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/statistics/2h');
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue