From 8aa51c4e802f8069e285bd496455e07b0472b455 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 16 Aug 2023 02:25:48 +0900 Subject: [PATCH] Batch esplora outspends requests --- backend/src/api/bitcoin/bitcoin.routes.ts | 21 +++++++++++++++++++ backend/src/api/bitcoin/esplora-api.ts | 16 +++++++------- .../transactions-list.component.ts | 2 +- .../tx-bowtie-graph.component.ts | 5 +++-- frontend/src/app/services/api.service.ts | 8 ------- .../src/app/services/electrs-api.service.ts | 6 ++++++ 6 files changed, 38 insertions(+), 20 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index 240fb07ce..cc912877c 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -112,6 +112,7 @@ class BitcoinRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/hex', this.getRawTransaction) .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/status', this.getTransactionStatus) .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/outspends', this.getTransactionOutspends) + .get(config.MEMPOOL.API_URL_PREFIX + 'txs/outspends', this.$getOutspends) .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/header', this.getBlockHeader) .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/tip/hash', this.getBlockTipHash) .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/raw', this.getRawBlock) @@ -198,6 +199,26 @@ class BitcoinRoutes { } } + private async $getOutspends(req: Request, res: Response) { + const txids_csv = req.query.txids; + if (!txids_csv || typeof txids_csv !== 'string') { + res.status(500).send('Invalid txids format'); + return; + } + const txids = txids_csv.split(','); + if (txids.length > 50) { + res.status(400).send('Too many txids requested'); + return; + } + + try { + const batchedOutspends = await bitcoinApi.$getBatchedOutspends(txids); + res.json(batchedOutspends); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + private async $getCpfpInfo(req: Request, res: Response) { if (!/^[a-fA-F0-9]{64}$/.test(req.params.txId)) { res.status(501).send(`Invalid transaction ID.`); diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index 8f47921c2..3ccb5693d 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -174,6 +174,9 @@ class FailoverRouter { axiosConfig = { timeout: config.ESPLORA.REQUEST_TIMEOUT, responseType }; url = host.host + path; } + if (data?.params) { + axiosConfig.params = data.params; + } return (method === 'post' ? this.requestConnection.post(url, data, axiosConfig) : this.requestConnection.get(url, axiosConfig) @@ -193,8 +196,8 @@ class FailoverRouter { }); } - public async $get(path, responseType = 'json'): Promise { - return this.$query('get', path, null, responseType); + public async $get(path, responseType = 'json', params: any = null): Promise { + return this.$query('get', path, params ? { params } : null, responseType); } public async $post(path, data: any, responseType = 'json'): Promise { @@ -294,13 +297,8 @@ class ElectrsApi implements AbstractBitcoinApi { return this.failoverRouter.$get('/tx/' + txId + '/outspends'); } - async $getBatchedOutspends(txId: string[]): Promise { - const outspends: IEsploraApi.Outspend[][] = []; - for (const tx of txId) { - const outspend = await this.$getOutspends(tx); - outspends.push(outspend); - } - return outspends; + async $getBatchedOutspends(txids: string[]): Promise { + return this.failoverRouter.$get('/txs/outspends', 'json', { txids: txids.join(',') }); } public startHealthChecks(): void { 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 05d74a75d..19eb2a6d7 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -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 => { return this.apiService.cachedRequest(this.apiService.getOutspendsBatched$, 250, batch); })); + return forkJoin(batches.map(batch => this.electrsApiService.getOutspendsBatched$(batch))); } else { return of([]); } 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 3bc352a35..d22f5705b 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 @@ -8,6 +8,7 @@ import { ApiService } from '../../services/api.service'; import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; import { AssetsService } from '../../services/assets.service'; import { environment } from '../../../environments/environment'; +import { ElectrsApiService } from '../../services/electrs-api.service'; interface SvgLine { path: string; @@ -100,7 +101,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { private router: Router, private relativeUrlPipe: RelativeUrlPipe, private stateService: StateService, - private apiService: ApiService, + private electrsApiService: ElectrsApiService, private assetsService: AssetsService, @Inject(LOCALE_ID) private locale: string, ) { @@ -123,7 +124,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { .pipe( switchMap((txid) => { if (!this.cached) { - return this.apiService.cachedRequest(this.apiService.getOutspendsBatched$, 250, [txid]); + return this.electrsApiService.getOutspendsBatched$([txid]); } else { return of(null); } diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 046b27812..cd5dd1ae6 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -138,14 +138,6 @@ export class ApiService { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/transaction-times', { params }); } - getOutspendsBatched$(txIds: string[]): Observable { - let params = new HttpParams(); - txIds.forEach((txId: string) => { - params = params.append('txId[]', txId); - }); - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/outspends', { params }); - } - getAboutPageProfiles$(): Observable { return this.httpClient.get(this.apiBaseUrl + '/api/v1/services/sponsors'); } diff --git a/frontend/src/app/services/electrs-api.service.ts b/frontend/src/app/services/electrs-api.service.ts index d63d49f68..c3efcbf46 100644 --- a/frontend/src/app/services/electrs-api.service.ts +++ b/frontend/src/app/services/electrs-api.service.ts @@ -54,6 +54,12 @@ export class ElectrsApiService { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/tx/' + hash + '/outspends'); } + getOutspendsBatched$(txids: string[]): Observable { + let params = new HttpParams(); + params = params.append('txids', txids.join(',')); + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/txs/outspends', { params }); + } + getBlockTransactions$(hash: string, index: number = 0): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/block/' + hash + '/txs/' + index); }