From 823f06451c9a9ec00c91d6bac0023d3929d400ab Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 11 Sep 2023 11:04:01 +0900 Subject: [PATCH 1/8] Send correct tx conf status in websocket msgs --- backend/src/api/blocks.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 73b010b91..64dc1d5ba 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -81,6 +81,7 @@ class Blocks { private async $getTransactionsExtended( blockHash: string, blockHeight: number, + blockTime: number, onlyCoinbase: boolean, txIds: string[] | null = null, quiet: boolean = false, @@ -101,6 +102,12 @@ class Blocks { if (!onlyCoinbase) { for (const txid of txIds) { if (mempool[txid]) { + mempool[txid].status = { + confirmed: true, + block_height: blockHeight, + block_hash: blockHash, + block_time: blockTime, + }; transactionMap[txid] = mempool[txid]; foundInMempool++; totalFound++; @@ -608,7 +615,7 @@ class Blocks { } const blockHash = await bitcoinApi.$getBlockHash(blockHeight); const block: IEsploraApi.Block = await bitcoinApi.$getBlock(blockHash); - const transactions = await this.$getTransactionsExtended(blockHash, block.height, true, null, true); + const transactions = await this.$getTransactionsExtended(blockHash, block.height, block.timestamp, true, null, true); const blockExtended = await this.$getBlockExtended(block, transactions); newlyIndexed++; @@ -701,7 +708,7 @@ class Blocks { const verboseBlock = await bitcoinClient.getBlock(blockHash, 2); const block = BitcoinApi.convertBlock(verboseBlock); const txIds: string[] = verboseBlock.tx.map(tx => tx.txid); - const transactions = await this.$getTransactionsExtended(blockHash, block.height, false, txIds, false, true) as MempoolTransactionExtended[]; + const transactions = await this.$getTransactionsExtended(blockHash, block.height, block.timestamp, false, txIds, false, true) as MempoolTransactionExtended[]; // fill in missing transaction fee data from verboseBlock for (let i = 0; i < transactions.length; i++) { @@ -890,7 +897,7 @@ class Blocks { const blockHash = await bitcoinApi.$getBlockHash(height); const block: IEsploraApi.Block = await bitcoinApi.$getBlock(blockHash); - const transactions = await this.$getTransactionsExtended(blockHash, block.height, true); + const transactions = await this.$getTransactionsExtended(blockHash, block.height, block.timestamp, true); const blockExtended = await this.$getBlockExtended(block, transactions); if (Common.indexingEnabled()) { @@ -902,7 +909,7 @@ class Blocks { public async $indexStaleBlock(hash: string): Promise { const block: IEsploraApi.Block = await bitcoinApi.$getBlock(hash); - const transactions = await this.$getTransactionsExtended(hash, block.height, true); + const transactions = await this.$getTransactionsExtended(hash, block.height, block.timestamp, true); const blockExtended = await this.$getBlockExtended(block, transactions); blockExtended.canonical = await bitcoinApi.$getBlockHash(block.height); From 8aa51c4e802f8069e285bd496455e07b0472b455 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 16 Aug 2023 02:25:48 +0900 Subject: [PATCH 2/8] 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); } From 09f208484a19512d5d8188f45c382e44dedda2c8 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 12 Nov 2023 06:38:18 +0000 Subject: [PATCH 3/8] Add electrsApiService cachedRequest function, switch outspends --- .../transactions-list.component.ts | 2 +- .../tx-bowtie-graph.component.ts | 2 +- .../src/app/services/electrs-api.service.ts | 44 ++++++++++++++++++- 3 files changed, 45 insertions(+), 3 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 19eb2a6d7..d066d3583 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 => this.electrsApiService.getOutspendsBatched$(batch))); + return forkJoin(batches.map(batch => { return this.electrsApiService.cachedRequest(this.electrsApiService.getOutspendsBatched$, 250, 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 d22f5705b..043c9ea3b 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 @@ -124,7 +124,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges { .pipe( switchMap((txid) => { if (!this.cached) { - return this.electrsApiService.getOutspendsBatched$([txid]); + return this.electrsApiService.cachedRequest(this.electrsApiService.getOutspendsBatched$, 250, [txid]); } else { return of(null); } diff --git a/frontend/src/app/services/electrs-api.service.ts b/frontend/src/app/services/electrs-api.service.ts index c3efcbf46..eaa1ab52d 100644 --- a/frontend/src/app/services/electrs-api.service.ts +++ b/frontend/src/app/services/electrs-api.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; -import { Observable, from, of, switchMap } from 'rxjs'; +import { BehaviorSubject, Observable, catchError, filter, from, of, shareReplay, switchMap, take, tap } from 'rxjs'; import { Transaction, Address, Outspend, Recent, Asset, ScriptHash } from '../interfaces/electrs.interface'; import { StateService } from './state.service'; import { BlockExtended } from '../interfaces/node-api.interface'; @@ -13,6 +13,8 @@ export class ElectrsApiService { 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, @@ -30,6 +32,46 @@ export class ElectrsApiService { }); } + 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)); + } + getBlock$(hash: string): Observable { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/block/' + hash); } From 7ec7ae7b955efdf5250b9da942614be41a50693a Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 12 Nov 2023 06:51:48 +0000 Subject: [PATCH 4/8] Remove old batched outspends API --- backend/src/api/bitcoin/bitcoin.routes.ts | 29 ++--------------------- backend/src/api/bitcoin/esplora-api.ts | 2 +- 2 files changed, 3 insertions(+), 28 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index cc912877c..d62f3da72 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -24,7 +24,6 @@ class BitcoinRoutes { public initRoutes(app: Application) { app .get(config.MEMPOOL.API_URL_PREFIX + 'transaction-times', this.getTransactionTimes) - .get(config.MEMPOOL.API_URL_PREFIX + 'outspends', this.$getBatchedOutspends) .get(config.MEMPOOL.API_URL_PREFIX + 'cpfp/:txId', this.$getCpfpInfo) .get(config.MEMPOOL.API_URL_PREFIX + 'difficulty-adjustment', this.getDifficultyChange) .get(config.MEMPOOL.API_URL_PREFIX + 'fees/recommended', this.getRecommendedFees) @@ -112,7 +111,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 + 'txs/outspends', this.$getBatchedOutspends) .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) @@ -175,31 +174,7 @@ class BitcoinRoutes { res.json(times); } - private async $getBatchedOutspends(req: Request, res: Response) { - if (!Array.isArray(req.query.txId)) { - res.status(500).send('Not an array'); - return; - } - if (req.query.txId.length > 50) { - res.status(400).send('Too many txids requested'); - return; - } - const txIds: string[] = []; - for (const _txId in req.query.txId) { - if (typeof req.query.txId[_txId] === 'string') { - txIds.push(req.query.txId[_txId].toString()); - } - } - - try { - const batchedOutspends = await bitcoinApi.$getBatchedOutspends(txIds); - res.json(batchedOutspends); - } catch (e) { - res.status(500).send(e instanceof Error ? e.message : e); - } - } - - private async $getOutspends(req: Request, res: Response) { + private async $getBatchedOutspends(req: Request, res: Response): Promise { const txids_csv = req.query.txids; if (!txids_csv || typeof txids_csv !== 'string') { res.status(500).send('Invalid txids format'); diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index 3ccb5693d..d400dad7a 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -298,7 +298,7 @@ class ElectrsApi implements AbstractBitcoinApi { } async $getBatchedOutspends(txids: string[]): Promise { - return this.failoverRouter.$get('/txs/outspends', 'json', { txids: txids.join(',') }); + throw new Error('Method not implemented.'); } public startHealthChecks(): void { From 5bee54a2bf9f5dfdd56ddf3cc7799329319b369e Mon Sep 17 00:00:00 2001 From: Mononaut Date: Thu, 17 Aug 2023 02:42:59 +0900 Subject: [PATCH 5/8] Use new bulk endpoints to speed up forensics --- .../bitcoin/bitcoin-api-abstract-factory.ts | 1 + backend/src/api/bitcoin/bitcoin-api.ts | 17 +- backend/src/api/bitcoin/esplora-api.ts | 13 ++ .../src/tasks/lightning/forensics.service.ts | 193 ++++++++++++------ 4 files changed, 155 insertions(+), 69 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts index a76b93e8d..6f20dad92 100644 --- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts +++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts @@ -24,6 +24,7 @@ export interface AbstractBitcoinApi { $getOutspend(txId: string, vout: number): Promise; $getOutspends(txId: string): Promise; $getBatchedOutspends(txId: string[]): Promise; + $getBatchedOutspendsInternal(txId: string[]): Promise; startHealthChecks(): void; } diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index 1be7993b8..9e4cbdd8b 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -60,8 +60,17 @@ class BitcoinApi implements AbstractBitcoinApi { }); } - $getRawTransactions(txids: string[]): Promise { - throw new Error('Method getRawTransactions not supported by the Bitcoin RPC API.'); + async $getRawTransactions(txids: string[]): Promise { + const txs: IEsploraApi.Transaction[] = []; + for (const txid of txids) { + try { + const tx = await this.$getRawTransaction(txid, false, true); + txs.push(tx); + } catch (err) { + // skip failures + } + } + return txs; } $getMempoolTransactions(txids: string[]): Promise { @@ -202,6 +211,10 @@ class BitcoinApi implements AbstractBitcoinApi { return outspends; } + async $getBatchedOutspendsInternal(txId: string[]): Promise { + return this.$getBatchedOutspends(txId); + } + $getEstimatedHashrate(blockHeight: number): Promise { // 120 is the default block span in Core return this.bitcoindClient.getNetworkHashPs(120, blockHeight); diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index d400dad7a..574113ae6 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -301,6 +301,19 @@ class ElectrsApi implements AbstractBitcoinApi { throw new Error('Method not implemented.'); } + async $getBatchedOutspendsInternal(txids: string[]): Promise { + const allOutspends: IEsploraApi.Outspend[][] = []; + const sliceLength = 50; + for (let i = 0; i < Math.ceil(txids.length / sliceLength); i++) { + const slice = txids.slice(i * sliceLength, (i + 1) * sliceLength); + const sliceOutspends = await this.failoverRouter.$get('/txs/outspends', 'json', { txids: slice.join(',') }); + for (const outspends of sliceOutspends) { + allOutspends.push(outspends); + } + } + return allOutspends; + } + public startHealthChecks(): void { this.failoverRouter.startHealthChecks(); } diff --git a/backend/src/tasks/lightning/forensics.service.ts b/backend/src/tasks/lightning/forensics.service.ts index 65ea61dc1..8a9bb825f 100644 --- a/backend/src/tasks/lightning/forensics.service.ts +++ b/backend/src/tasks/lightning/forensics.service.ts @@ -15,8 +15,6 @@ class ForensicsService { txCache: { [txid: string]: IEsploraApi.Transaction } = {}; tempCached: string[] = []; - constructor() {} - public async $startService(): Promise { logger.info('Starting lightning network forensics service'); @@ -66,93 +64,154 @@ class ForensicsService { */ public async $runClosedChannelsForensics(onlyNewChannels: boolean = false): Promise { + // Only Esplora backend can retrieve spent transaction outputs if (config.MEMPOOL.BACKEND !== 'esplora') { return; } - let progress = 0; - try { logger.debug(`Started running closed channel forensics...`); - let channels; + let remainingChannels; if (onlyNewChannels) { - channels = await channelsApi.$getClosedChannelsWithoutReason(); + remainingChannels = await channelsApi.$getClosedChannelsWithoutReason(); } else { - channels = await channelsApi.$getUnresolvedClosedChannels(); + remainingChannels = await channelsApi.$getUnresolvedClosedChannels(); } - for (const channel of channels) { - let reason = 0; - let resolvedForceClose = false; - // Only Esplora backend can retrieve spent transaction outputs - const cached: string[] = []; + let progress = 0; + const sliceLength = 1000; + // process batches of 1000 channels + for (let i = 0; i < Math.ceil(remainingChannels.length / sliceLength); i++) { + const channels = remainingChannels.slice(i * sliceLength, (i + 1) * sliceLength); + + let allOutspends: IEsploraApi.Outspend[][] = []; + const forceClosedChannels: { channel: any, cachedSpends: string[] }[] = []; + + // fetch outspends in bulk try { - let outspends: IEsploraApi.Outspend[] | undefined; - try { - outspends = await bitcoinApi.$getOutspends(channel.closing_transaction_id); - await Common.sleep$(config.LIGHTNING.FORENSICS_RATE_LIMIT); - } catch (e) { - logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + channel.closing_transaction_id + '/outspends'}. Reason ${e instanceof Error ? e.message : e}`); - continue; - } - const lightningScriptReasons: number[] = []; - for (const outspend of outspends) { - if (outspend.spent && outspend.txid) { - let spendingTx = await this.fetchTransaction(outspend.txid); - if (!spendingTx) { - continue; + const outspendTxids = channels.map(channel => channel.closing_transaction_id); + allOutspends = await bitcoinApi.$getBatchedOutspendsInternal(outspendTxids); + logger.info(`Fetched outspends for ${allOutspends.length} txs from esplora for lightning forensics`); + await Common.sleep$(config.LIGHTNING.FORENSICS_RATE_LIMIT); + } catch (e) { + logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/txs/outspends'}. Reason ${e instanceof Error ? e.message : e}`); + } + // fetch spending transactions in bulk and load into txCache + try { + const newSpendingTxids: { [txid: string]: boolean } = {}; + for (const outspends of allOutspends) { + for (const outspend of outspends) { + if (outspend.spent && outspend.txid) { + if (!this.txCache[outspend.txid]) { + newSpendingTxids[outspend.txid] = true; + } } - cached.push(spendingTx.txid); - const lightningScript = this.findLightningScript(spendingTx.vin[outspend.vin || 0]); - lightningScriptReasons.push(lightningScript); } } - const filteredReasons = lightningScriptReasons.filter((r) => r !== 1); - if (filteredReasons.length) { - if (filteredReasons.some((r) => r === 2 || r === 4)) { - reason = 3; - } else { - reason = 2; - resolvedForceClose = true; - } - } else { - /* - We can detect a commitment transaction (force close) by reading Sequence and Locktime - https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction - */ - let closingTx = await this.fetchTransaction(channel.closing_transaction_id, true); - if (!closingTx) { + const allOutspendTxs = await bitcoinApi.$getRawTransactions(Object.keys(newSpendingTxids)); + logger.info(`Fetched ${allOutspendTxs.length} out-spending txs from esplora for lightning forensics`); + for (const tx of allOutspendTxs) { + this.txCache[tx.txid] = tx; + } + } catch (e) { + logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/txs'}. Reason ${e instanceof Error ? e.message : e}`); + } + + // process each outspend + for (const [index, channel] of channels.entries()) { + let reason = 0; + const cached: string[] = []; + try { + const outspends = allOutspends[index]; + if (!outspends || !outspends.length) { + // outspends are missing continue; } - cached.push(closingTx.txid); - const sequenceHex: string = closingTx.vin[0].sequence.toString(16); - const locktimeHex: string = closingTx.locktime.toString(16); - if (sequenceHex.substring(0, 2) === '80' && locktimeHex.substring(0, 2) === '20') { - reason = 2; // Here we can't be sure if it's a penalty or not - } else { - reason = 1; + const lightningScriptReasons: number[] = []; + for (const outspend of outspends) { + if (outspend.spent && outspend.txid) { + const spendingTx = this.txCache[outspend.txid]; + if (!spendingTx) { + continue; + } + cached.push(spendingTx.txid); + const lightningScript = this.findLightningScript(spendingTx.vin[outspend.vin || 0]); + lightningScriptReasons.push(lightningScript); + } } - } - if (reason) { - logger.debug('Setting closing reason ' + reason + ' for channel: ' + channel.id + '.'); - await DB.query(`UPDATE channels SET closing_reason = ? WHERE id = ?`, [reason, channel.id]); - if (reason === 2 && resolvedForceClose) { - await DB.query(`UPDATE channels SET closing_resolved = ? WHERE id = ?`, [true, channel.id]); - } - if (reason !== 2 || resolvedForceClose) { + const filteredReasons = lightningScriptReasons.filter((r) => r !== 1); + if (filteredReasons.length) { + if (filteredReasons.some((r) => r === 2 || r === 4)) { + // Force closed with penalty + reason = 3; + } else { + // Force closed without penalty + reason = 2; + await DB.query(`UPDATE channels SET closing_resolved = ? WHERE id = ?`, [true, channel.id]); + } + await DB.query(`UPDATE channels SET closing_reason = ? WHERE id = ?`, [reason, channel.id]); + // clean up cached transactions cached.forEach(txid => { delete this.txCache[txid]; }); + } else { + forceClosedChannels.push({ channel, cachedSpends: cached }); } + } catch (e) { + logger.err(`$runClosedChannelsForensics() failed for channel ${channel.short_id}. Reason: ${e instanceof Error ? e.message : e}`); } - } catch (e) { - logger.err(`$runClosedChannelsForensics() failed for channel ${channel.short_id}. Reason: ${e instanceof Error ? e.message : e}`); } - ++progress; + // fetch force-closing transactions in bulk + try { + const newClosingTxids: { [txid: string]: boolean } = {}; + for (const { channel } of forceClosedChannels) { + if (!this.txCache[channel.closing_transaction_id]) { + newClosingTxids[channel.closing_transaction_id] = true; + } + } + const closingTxs = await bitcoinApi.$getRawTransactions(Object.keys(newClosingTxids)); + logger.info(`Fetched ${closingTxs.length} closing txs from esplora for lightning forensics`); + for (const tx of closingTxs) { + this.txCache[tx.txid] = tx; + } + } catch (e) { + logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/txs'}. Reason ${e instanceof Error ? e.message : e}`); + } + + // process channels with no lightning script reasons + for (const { channel, cachedSpends } of forceClosedChannels) { + const closingTx = this.txCache[channel.closing_transaction_id]; + if (!closingTx) { + // no channel close transaction found yet + continue; + } + /* + We can detect a commitment transaction (force close) by reading Sequence and Locktime + https://github.com/lightning/bolts/blob/master/03-transactions.md#commitment-transaction + */ + const sequenceHex: string = closingTx.vin[0].sequence.toString(16); + const locktimeHex: string = closingTx.locktime.toString(16); + let reason; + if (sequenceHex.substring(0, 2) === '80' && locktimeHex.substring(0, 2) === '20') { + // Force closed, but we can't be sure if it's a penalty or not + reason = 2; + } else { + // Mutually closed + reason = 1; + // clean up cached transactions + delete this.txCache[closingTx.txid]; + for (const txid of cachedSpends) { + delete this.txCache[txid]; + } + } + await DB.query(`UPDATE channels SET closing_reason = ? WHERE id = ?`, [reason, channel.id]); + } + + progress += channels.length; const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer); if (elapsedSeconds > 10) { - logger.debug(`Updating channel closed channel forensics ${progress}/${channels.length}`); + logger.debug(`Updating channel closed channel forensics ${progress}/${remainingChannels.length}`); this.loggerTimer = new Date().getTime() / 1000; } } @@ -221,7 +280,7 @@ class ForensicsService { const channels = await channelsApi.$getChannelsWithoutSourceChecked(); for (const openChannel of channels) { - let openTx = await this.fetchTransaction(openChannel.transaction_id, true); + const openTx = await this.fetchTransaction(openChannel.transaction_id, true); if (!openTx) { continue; } @@ -276,7 +335,7 @@ class ForensicsService { // Check if a channel open tx input spends the result of a swept channel close output private async $attributeSweptChannelCloses(openChannel: ILightningApi.Channel, input: IEsploraApi.Vin): Promise { - let sweepTx = await this.fetchTransaction(input.txid, true); + const sweepTx = await this.fetchTransaction(input.txid, true); if (!sweepTx) { logger.err(`couldn't find input transaction for channel forensics ${openChannel.channel_id} ${input.txid}`); return; @@ -335,7 +394,7 @@ class ForensicsService { if (matched && !ambiguous) { // fetch closing channel transaction and perform forensics on the outputs - let prevChannelTx = await this.fetchTransaction(input.txid, true); + const prevChannelTx = await this.fetchTransaction(input.txid, true); let outspends: IEsploraApi.Outspend[] | undefined; try { outspends = await bitcoinApi.$getOutspends(input.txid); @@ -430,7 +489,7 @@ class ForensicsService { } await Common.sleep$(config.LIGHTNING.FORENSICS_RATE_LIMIT); } catch (e) { - logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + txid + '/outspends'}. Reason ${e instanceof Error ? e.message : e}`); + logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + txid}. Reason ${e instanceof Error ? e.message : e}`); return null; } } From 995acb238df795d2f041787ddac8a5c8fa809e77 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 18 Aug 2023 00:19:02 +0900 Subject: [PATCH 6/8] Refactor forensics batching, speed up opened channel forensics --- .../src/tasks/lightning/forensics.service.ts | 102 ++++++++++-------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/backend/src/tasks/lightning/forensics.service.ts b/backend/src/tasks/lightning/forensics.service.ts index 8a9bb825f..c83f1720c 100644 --- a/backend/src/tasks/lightning/forensics.service.ts +++ b/backend/src/tasks/lightning/forensics.service.ts @@ -71,18 +71,18 @@ class ForensicsService { try { logger.debug(`Started running closed channel forensics...`); - let remainingChannels; + let allChannels; if (onlyNewChannels) { - remainingChannels = await channelsApi.$getClosedChannelsWithoutReason(); + allChannels = await channelsApi.$getClosedChannelsWithoutReason(); } else { - remainingChannels = await channelsApi.$getUnresolvedClosedChannels(); + allChannels = await channelsApi.$getUnresolvedClosedChannels(); } let progress = 0; const sliceLength = 1000; // process batches of 1000 channels - for (let i = 0; i < Math.ceil(remainingChannels.length / sliceLength); i++) { - const channels = remainingChannels.slice(i * sliceLength, (i + 1) * sliceLength); + for (let i = 0; i < Math.ceil(allChannels.length / sliceLength); i++) { + const channels = allChannels.slice(i * sliceLength, (i + 1) * sliceLength); let allOutspends: IEsploraApi.Outspend[][] = []; const forceClosedChannels: { channel: any, cachedSpends: string[] }[] = []; @@ -91,31 +91,28 @@ class ForensicsService { try { const outspendTxids = channels.map(channel => channel.closing_transaction_id); allOutspends = await bitcoinApi.$getBatchedOutspendsInternal(outspendTxids); - logger.info(`Fetched outspends for ${allOutspends.length} txs from esplora for lightning forensics`); + logger.info(`Fetched outspends for ${allOutspends.length} txs from esplora for LN forensics`); await Common.sleep$(config.LIGHTNING.FORENSICS_RATE_LIMIT); } catch (e) { logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/txs/outspends'}. Reason ${e instanceof Error ? e.message : e}`); } // fetch spending transactions in bulk and load into txCache - try { - const newSpendingTxids: { [txid: string]: boolean } = {}; - for (const outspends of allOutspends) { - for (const outspend of outspends) { - if (outspend.spent && outspend.txid) { - if (!this.txCache[outspend.txid]) { - newSpendingTxids[outspend.txid] = true; - } - } + const newSpendingTxids: { [txid: string]: boolean } = {}; + for (const outspends of allOutspends) { + for (const outspend of outspends) { + if (outspend.spent && outspend.txid) { + newSpendingTxids[outspend.txid] = true; } } - const allOutspendTxs = await bitcoinApi.$getRawTransactions(Object.keys(newSpendingTxids)); - logger.info(`Fetched ${allOutspendTxs.length} out-spending txs from esplora for lightning forensics`); - for (const tx of allOutspendTxs) { - this.txCache[tx.txid] = tx; - } - } catch (e) { - logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/txs'}. Reason ${e instanceof Error ? e.message : e}`); } + const allOutspendTxs = await this.fetchTransactions( + allOutspends.flatMap(outspends => + outspends + .filter(outspend => outspend.spent && outspend.txid) + .map(outspend => outspend.txid) + ) + ); + logger.info(`Fetched ${allOutspendTxs.length} out-spending txs from esplora for LN forensics`); // process each outspend for (const [index, channel] of channels.entries()) { @@ -163,21 +160,8 @@ class ForensicsService { } // fetch force-closing transactions in bulk - try { - const newClosingTxids: { [txid: string]: boolean } = {}; - for (const { channel } of forceClosedChannels) { - if (!this.txCache[channel.closing_transaction_id]) { - newClosingTxids[channel.closing_transaction_id] = true; - } - } - const closingTxs = await bitcoinApi.$getRawTransactions(Object.keys(newClosingTxids)); - logger.info(`Fetched ${closingTxs.length} closing txs from esplora for lightning forensics`); - for (const tx of closingTxs) { - this.txCache[tx.txid] = tx; - } - } catch (e) { - logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/txs'}. Reason ${e instanceof Error ? e.message : e}`); - } + const closingTxs = await this.fetchTransactions(forceClosedChannels.map(x => x.channel.closing_transaction_id)); + logger.info(`Fetched ${closingTxs.length} closing txs from esplora for LN forensics`); // process channels with no lightning script reasons for (const { channel, cachedSpends } of forceClosedChannels) { @@ -211,7 +195,7 @@ class ForensicsService { progress += channels.length; const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer); if (elapsedSeconds > 10) { - logger.debug(`Updating channel closed channel forensics ${progress}/${remainingChannels.length}`); + logger.debug(`Updating channel closed channel forensics ${progress}/${allChannels.length}`); this.loggerTimer = new Date().getTime() / 1000; } } @@ -279,8 +263,11 @@ class ForensicsService { logger.debug(`Started running open channel forensics...`); const channels = await channelsApi.$getChannelsWithoutSourceChecked(); + // preload open channel transactions + await this.fetchTransactions(channels.map(channel => channel.transaction_id), true); + for (const openChannel of channels) { - const openTx = await this.fetchTransaction(openChannel.transaction_id, true); + const openTx = this.txCache[openChannel.transaction_id]; if (!openTx) { continue; } @@ -414,17 +401,17 @@ class ForensicsService { }; }); } + + // preload outspend transactions + await this.fetchTransactions(outspends.filter(o => o.spent && o.txid).map(o => o.txid), true); + for (let i = 0; i < outspends?.length; i++) { const outspend = outspends[i]; const output = prevChannel.outputs[i]; if (outspend.spent && outspend.txid) { - try { - const spendingTx = await this.fetchTransaction(outspend.txid, true); - if (spendingTx) { - output.type = this.findLightningScript(spendingTx.vin[outspend.vin || 0]); - } - } catch (e) { - logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/tx/' + outspend.txid}. Reason ${e instanceof Error ? e.message : e}`); + const spendingTx = this.txCache[outspend.txid]; + if (spendingTx) { + output.type = this.findLightningScript(spendingTx.vin[outspend.vin || 0]); } } else { output.type = 0; @@ -496,6 +483,29 @@ class ForensicsService { return tx; } + // fetches a batch of transactions and adds them to the txCache + // the returned list of txs does *not* preserve ordering or number + async fetchTransactions(txids, temp: boolean = false): Promise<(IEsploraApi.Transaction | null)[]> { + // deduplicate txids + const uniqueTxids = [...new Set(txids)]; + // filter out any transactions we already have in the cache + const needToFetch: string[] = uniqueTxids.filter(txid => !this.txCache[txid]); + try { + const txs = await bitcoinApi.$getRawTransactions(needToFetch); + for (const tx of txs) { + this.txCache[tx.txid] = tx; + if (temp) { + this.tempCached.push(tx.txid); + } + } + await Common.sleep$(config.LIGHTNING.FORENSICS_RATE_LIMIT); + } catch (e) { + logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/txs'}. Reason ${e instanceof Error ? e.message : e}`); + return []; + } + return txids.map(txid => this.txCache[txid]); + } + clearTempCache(): void { for (const txid of this.tempCached) { delete this.txCache[txid]; From 70badaf4615e3d258a6cd15d4b12a09f085cbd6c Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 18 Aug 2023 02:47:32 +0900 Subject: [PATCH 7/8] Speed up $scanForClosedChannels, use internal outspends apis --- .../bitcoin/bitcoin-api-abstract-factory.ts | 1 + backend/src/api/bitcoin/bitcoin-api.ts | 9 ++++++ backend/src/api/bitcoin/esplora-api.ts | 15 +++------ .../src/tasks/lightning/forensics.service.ts | 2 +- .../tasks/lightning/network-sync.service.ts | 32 ++++++++++++------- 5 files changed, 37 insertions(+), 22 deletions(-) diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts index 6f20dad92..f008e5ed8 100644 --- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts +++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts @@ -25,6 +25,7 @@ export interface AbstractBitcoinApi { $getOutspends(txId: string): Promise; $getBatchedOutspends(txId: string[]): Promise; $getBatchedOutspendsInternal(txId: string[]): Promise; + $getOutSpendsByOutpoint(outpoints: { txid: string, vout: number }[]): Promise; startHealthChecks(): void; } diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index 9e4cbdd8b..1722334df 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -215,6 +215,15 @@ class BitcoinApi implements AbstractBitcoinApi { return this.$getBatchedOutspends(txId); } + async $getOutSpendsByOutpoint(outpoints: { txid: string, vout: number }[]): Promise { + const outspends: IEsploraApi.Outspend[] = []; + for (const outpoint of outpoints) { + const outspend = await this.$getOutspend(outpoint.txid, outpoint.vout); + outspends.push(outspend); + } + return outspends; + } + $getEstimatedHashrate(blockHeight: number): Promise { // 120 is the default block span in Core return this.bitcoindClient.getNetworkHashPs(120, blockHeight); diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index 574113ae6..2beebe270 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -302,16 +302,11 @@ class ElectrsApi implements AbstractBitcoinApi { } async $getBatchedOutspendsInternal(txids: string[]): Promise { - const allOutspends: IEsploraApi.Outspend[][] = []; - const sliceLength = 50; - for (let i = 0; i < Math.ceil(txids.length / sliceLength); i++) { - const slice = txids.slice(i * sliceLength, (i + 1) * sliceLength); - const sliceOutspends = await this.failoverRouter.$get('/txs/outspends', 'json', { txids: slice.join(',') }); - for (const outspends of sliceOutspends) { - allOutspends.push(outspends); - } - } - return allOutspends; + return this.failoverRouter.$post('/internal-api/txs/outspends/by-txid', txids, 'json'); + } + + async $getOutSpendsByOutpoint(outpoints: { txid: string, vout: number }[]): Promise { + return this.failoverRouter.$post('/internal-api/txs/outspends/by-outpoint', outpoints.map(out => `${out.txid}:${out.vout}`), 'json'); } public startHealthChecks(): void { diff --git a/backend/src/tasks/lightning/forensics.service.ts b/backend/src/tasks/lightning/forensics.service.ts index c83f1720c..1cbf7d647 100644 --- a/backend/src/tasks/lightning/forensics.service.ts +++ b/backend/src/tasks/lightning/forensics.service.ts @@ -94,7 +94,7 @@ class ForensicsService { logger.info(`Fetched outspends for ${allOutspends.length} txs from esplora for LN forensics`); await Common.sleep$(config.LIGHTNING.FORENSICS_RATE_LIMIT); } catch (e) { - logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/txs/outspends'}. Reason ${e instanceof Error ? e.message : e}`); + logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/internal-api/txs/outspends/by-txid'}. Reason ${e instanceof Error ? e.message : e}`); } // fetch spending transactions in bulk and load into txCache const newSpendingTxids: { [txid: string]: boolean } = {}; diff --git a/backend/src/tasks/lightning/network-sync.service.ts b/backend/src/tasks/lightning/network-sync.service.ts index 963b9e8c2..dc0d609fa 100644 --- a/backend/src/tasks/lightning/network-sync.service.ts +++ b/backend/src/tasks/lightning/network-sync.service.ts @@ -288,22 +288,32 @@ class NetworkSyncService { } logger.debug(`${log}`, logger.tags.ln); - const channels = await channelsApi.$getChannelsByStatus([0, 1]); - for (const channel of channels) { - const spendingTx = await bitcoinApi.$getOutspend(channel.transaction_id, channel.transaction_vout); - if (spendingTx.spent === true && spendingTx.status?.confirmed === true) { - logger.debug(`Marking channel: ${channel.id} as closed.`, logger.tags.ln); - await DB.query(`UPDATE channels SET status = 2, closing_date = FROM_UNIXTIME(?) WHERE id = ?`, - [spendingTx.status.block_time, channel.id]); - if (spendingTx.txid && !channel.closing_transaction_id) { - await DB.query(`UPDATE channels SET closing_transaction_id = ? WHERE id = ?`, [spendingTx.txid, channel.id]); + const allChannels = await channelsApi.$getChannelsByStatus([0, 1]); + + const sliceLength = 5000; + // process batches of 5000 channels + for (let i = 0; i < Math.ceil(allChannels.length / sliceLength); i++) { + const channels = allChannels.slice(i * sliceLength, (i + 1) * sliceLength); + const outspends = await bitcoinApi.$getOutSpendsByOutpoint(channels.map(channel => { + return { txid: channel.transaction_id, vout: channel.transaction_vout }; + })); + + for (const [index, channel] of channels.entries()) { + const spendingTx = outspends[index]; + if (spendingTx.spent === true && spendingTx.status?.confirmed === true) { + // logger.debug(`Marking channel: ${channel.id} as closed.`, logger.tags.ln); + await DB.query(`UPDATE channels SET status = 2, closing_date = FROM_UNIXTIME(?) WHERE id = ?`, + [spendingTx.status.block_time, channel.id]); + if (spendingTx.txid && !channel.closing_transaction_id) { + await DB.query(`UPDATE channels SET closing_transaction_id = ? WHERE id = ?`, [spendingTx.txid, channel.id]); + } } } - ++progress; + progress += channels.length; const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer); if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) { - logger.debug(`Checking if channel has been closed ${progress}/${channels.length}`, logger.tags.ln); + logger.debug(`Checking if channel has been closed ${progress}/${allChannels.length}`, logger.tags.ln); this.loggerTimer = new Date().getTime() / 1000; } } From b9217da45301f3c4d752d9d5214b30fb018cd4cd Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 28 Aug 2023 16:52:28 +0900 Subject: [PATCH 8/8] Change internal API prefix to "internal" --- backend/src/api/bitcoin/esplora-api.ts | 4 ++-- backend/src/tasks/lightning/forensics.service.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index 2beebe270..dda584ec4 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -302,11 +302,11 @@ class ElectrsApi implements AbstractBitcoinApi { } async $getBatchedOutspendsInternal(txids: string[]): Promise { - return this.failoverRouter.$post('/internal-api/txs/outspends/by-txid', txids, 'json'); + return this.failoverRouter.$post('/internal/txs/outspends/by-txid', txids, 'json'); } async $getOutSpendsByOutpoint(outpoints: { txid: string, vout: number }[]): Promise { - return this.failoverRouter.$post('/internal-api/txs/outspends/by-outpoint', outpoints.map(out => `${out.txid}:${out.vout}`), 'json'); + return this.failoverRouter.$post('/internal/txs/outspends/by-outpoint', outpoints.map(out => `${out.txid}:${out.vout}`), 'json'); } public startHealthChecks(): void { diff --git a/backend/src/tasks/lightning/forensics.service.ts b/backend/src/tasks/lightning/forensics.service.ts index 1cbf7d647..584dd3c79 100644 --- a/backend/src/tasks/lightning/forensics.service.ts +++ b/backend/src/tasks/lightning/forensics.service.ts @@ -94,7 +94,7 @@ class ForensicsService { logger.info(`Fetched outspends for ${allOutspends.length} txs from esplora for LN forensics`); await Common.sleep$(config.LIGHTNING.FORENSICS_RATE_LIMIT); } catch (e) { - logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/internal-api/txs/outspends/by-txid'}. Reason ${e instanceof Error ? e.message : e}`); + logger.err(`Failed to call ${config.ESPLORA.REST_API_URL + '/internal/txs/outspends/by-txid'}. Reason ${e instanceof Error ? e.message : e}`); } // fetch spending transactions in bulk and load into txCache const newSpendingTxids: { [txid: string]: boolean } = {};