From b269e99cfb7a48d5ccb32e2f177f3fae6656454b Mon Sep 17 00:00:00 2001 From: hunicus <93150691+hunicus@users.noreply.github.com> Date: Thu, 2 Feb 2023 14:23:08 -0500 Subject: [PATCH 1/5] Update cpfp faq for stored relationships --- frontend/src/app/docs/api-docs/api-docs.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index 48e9b48eb..4e99ce469 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -214,8 +214,8 @@

Mempool aims to show you the effective feerate range for blocks—how much would you actually need to pay to get a transaction included in a block.

-

A transaction's effective feerate is not always the same as the feerate explicitly set for it. For example, if you see a 1 s/vb transaction in a block with a displayed feerate range of 5 s/vb to 72 s/vb, chances are that 1 s/vb transaction had a high-feerate child transaction that boosted its effective feerate to 5 s/vb or higher (this is how CPFP fee-bumping works). In such a case, it would be misleading to use 1 s/vb as the lower bound of the block's feerate range because it actually required more than 1 s/vb to confirm that transaction in that block.

-

For unconfirmed CPFP transactions, Mempool will show the effective feerate (along with descendent & ancestor transaction information) on the transaction page. For confirmed transactions, CPFP relationships are not stored, so this additional information is not shown.

+

A transaction's effective feerate is not always the same as the feerate explicitly set for it. For example, if you see a 1 s/vb transaction in a block with a displayed feerate range of 5 s/vb to 72 s/vb, chances are that 1 s/vb transaction had a high-feerate child transaction that boosted its effective feerate to 5 s/vb or higher (this is how CPFP fee-bumping works). In such a case, it would be misleading to use 1 s/vb as the lower bound of the block's feerate range since it actually required more than 1 s/vb to confirm that transaction in that block.

+

You can find a transaction's feerate on its transaction details page. If the transaction has any CPFP relationships, the page will also show the transaction's effective feerate along with links to descendent and/or ancestor transactions.

From a897aebbc1de9148e1bff71eefa26d0b826ce639 Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Thu, 16 Feb 2023 15:39:09 +0100 Subject: [PATCH 2/5] add size per weight graph and ts type for `getHistoricalBlockSizesAndWeights` --- .../block-sizes-weights-graph.component.ts | 25 +++++++++++++++++++ .../src/app/interfaces/node-api.interface.ts | 13 ++++++++++ frontend/src/app/services/api.service.ts | 8 +++--- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts index bc3c642db..7ad147b28 100644 --- a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts +++ b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts @@ -90,6 +90,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit { this.prepareChartOptions({ sizes: data.sizes.map(val => [val.timestamp * 1000, val.avgSize / 1000000, val.avgHeight]), weights: data.weights.map(val => [val.timestamp * 1000, val.avgWeight / 1000000, val.avgHeight]), + sizePerWeight: data.weights.map((val, i) => [val.timestamp * 1000, data.sizes[i].avgSize / (val.avgWeight / 4), val.avgHeight]), }); this.isLoading = false; }), @@ -124,6 +125,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit { color: [ '#FDD835', '#D81B60', + '#14EDF5', ], grid: { top: 30, @@ -153,6 +155,8 @@ export class BlockSizesWeightsGraphComponent implements OnInit { tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.2-2')} MB`; } else if (tick.seriesIndex === 1) { // Weight tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.2-2')} MWU`; + } else if (tick.seriesIndex === 2) { // Size per weight + tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.2-2')} B/vB`; } tooltip += `
`; } @@ -192,10 +196,19 @@ export class BlockSizesWeightsGraphComponent implements OnInit { }, icon: 'roundRect', }, + { + name: $localize`Size per weight`, + inactiveColor: 'rgb(110, 112, 121)', + textStyle: { + color: 'white', + }, + icon: 'roundRect', + }, ], selected: JSON.parse(this.storageService.getValue('sizes_weights_legend')) ?? { 'Size': true, 'Weight': true, + 'Size per weight': true, } }, yAxis: data.sizes.length === 0 ? undefined : [ @@ -262,6 +275,18 @@ export class BlockSizesWeightsGraphComponent implements OnInit { lineStyle: { width: 2, } + }, + { + zlevel: 1, + yAxisIndex: 0, + name: $localize`Size per weight`, + showSymbol: false, + symbol: 'none', + data: data.sizePerWeight, + type: 'line', + lineStyle: { + width: 2, + } } ], dataZoom: [{ diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index c35eb8098..c11cb5828 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -151,6 +151,19 @@ export interface RewardStats { totalTx: number; } +export interface BlockSizesAndWeights { + sizes: { + timestamp: number; + avgHeight: number; + avgSize: number; + }[]; + weights: { + timestamp: number; + avgHeight: number; + avgWeight: number; + }[]; +} + export interface AuditScore { hash: string; matchRate?: number; diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 6eff41f61..04b2b72e2 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; -import { HttpClient, HttpParams } from '@angular/common/http'; +import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, - PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore } from '../interfaces/node-api.interface'; + PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights } from '../interfaces/node-api.interface'; import { Observable } from 'rxjs'; import { StateService } from './state.service'; import { WebsocketResponse } from '../interfaces/websocket.interface'; @@ -222,8 +222,8 @@ export class ApiService { ); } - getHistoricalBlockSizesAndWeights$(interval: string | undefined) : Observable { - return this.httpClient.get( + getHistoricalBlockSizesAndWeights$(interval: string | undefined) : Observable> { + return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/sizes-weights` + (interval !== undefined ? `/${interval}` : ''), { observe: 'response' } ); From 565e572b88311b413f1a5ad072e3585e3db78784 Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <49868160+antonilol@users.noreply.github.com> Date: Fri, 17 Feb 2023 15:45:15 +0100 Subject: [PATCH 3/5] Update frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts change size per weight graph color Co-authored-by: nymkappa <9780671+nymkappa@users.noreply.github.com> --- .../block-sizes-weights-graph.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts index 7ad147b28..016dd79f6 100644 --- a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts +++ b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts @@ -125,7 +125,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit { color: [ '#FDD835', '#D81B60', - '#14EDF5', + '#039BE5, ], grid: { top: 30, From 04d8926ef5ffe4c07987f670ce8d911bcedd3e30 Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Fri, 17 Feb 2023 15:47:32 +0100 Subject: [PATCH 4/5] fix missing quote --- .../block-sizes-weights-graph.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts index 016dd79f6..8477af588 100644 --- a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts +++ b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts @@ -125,7 +125,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit { color: [ '#FDD835', '#D81B60', - '#039BE5, + '#039BE5', ], grid: { top: 30, From 7f54e30a26df7b7b35bb26385f4f051d4e70060c Mon Sep 17 00:00:00 2001 From: Mononaut Date: Tue, 7 Feb 2023 20:56:33 -0600 Subject: [PATCH 5/5] Reuse HTTP connections to esplora backend --- backend/src/api/bitcoin/esplora-api.ts | 29 +++++++++++++++----------- backend/src/index.ts | 7 +++++-- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index 37f6e5892..0366695d1 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -1,8 +1,13 @@ import config from '../../config'; import axios, { AxiosRequestConfig } from 'axios'; +import http from 'http'; import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory'; import { IEsploraApi } from './esplora-api.interface'; +const axiosConnection = axios.create({ + httpAgent: new http.Agent({ keepAlive: true }) +}); + class ElectrsApi implements AbstractBitcoinApi { axiosConfig: AxiosRequestConfig = { timeout: 10000, @@ -11,52 +16,52 @@ class ElectrsApi implements AbstractBitcoinApi { constructor() { } $getRawMempool(): Promise { - return axios.get(config.ESPLORA.REST_API_URL + '/mempool/txids', this.axiosConfig) + return axiosConnection.get(config.ESPLORA.REST_API_URL + '/mempool/txids', this.axiosConfig) .then((response) => response.data); } $getRawTransaction(txId: string): Promise { - return axios.get(config.ESPLORA.REST_API_URL + '/tx/' + txId, this.axiosConfig) + return axiosConnection.get(config.ESPLORA.REST_API_URL + '/tx/' + txId, this.axiosConfig) .then((response) => response.data); } $getTransactionHex(txId: string): Promise { - return axios.get(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/hex', this.axiosConfig) + return axiosConnection.get(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/hex', this.axiosConfig) .then((response) => response.data); } $getBlockHeightTip(): Promise { - return axios.get(config.ESPLORA.REST_API_URL + '/blocks/tip/height', this.axiosConfig) + return axiosConnection.get(config.ESPLORA.REST_API_URL + '/blocks/tip/height', this.axiosConfig) .then((response) => response.data); } $getBlockHashTip(): Promise { - return axios.get(config.ESPLORA.REST_API_URL + '/blocks/tip/hash', this.axiosConfig) + return axiosConnection.get(config.ESPLORA.REST_API_URL + '/blocks/tip/hash', this.axiosConfig) .then((response) => response.data); } $getTxIdsForBlock(hash: string): Promise { - return axios.get(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids', this.axiosConfig) + return axiosConnection.get(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids', this.axiosConfig) .then((response) => response.data); } $getBlockHash(height: number): Promise { - return axios.get(config.ESPLORA.REST_API_URL + '/block-height/' + height, this.axiosConfig) + return axiosConnection.get(config.ESPLORA.REST_API_URL + '/block-height/' + height, this.axiosConfig) .then((response) => response.data); } $getBlockHeader(hash: string): Promise { - return axios.get(config.ESPLORA.REST_API_URL + '/block/' + hash + '/header', this.axiosConfig) + return axiosConnection.get(config.ESPLORA.REST_API_URL + '/block/' + hash + '/header', this.axiosConfig) .then((response) => response.data); } $getBlock(hash: string): Promise { - return axios.get(config.ESPLORA.REST_API_URL + '/block/' + hash, this.axiosConfig) + return axiosConnection.get(config.ESPLORA.REST_API_URL + '/block/' + hash, this.axiosConfig) .then((response) => response.data); } $getRawBlock(hash: string): Promise { - return axios.get(config.ESPLORA.REST_API_URL + '/block/' + hash + "/raw", { ...this.axiosConfig, responseType: 'arraybuffer' }) + return axiosConnection.get(config.ESPLORA.REST_API_URL + '/block/' + hash + "/raw", { ...this.axiosConfig, responseType: 'arraybuffer' }) .then((response) => { return Buffer.from(response.data); }); } @@ -77,12 +82,12 @@ class ElectrsApi implements AbstractBitcoinApi { } $getOutspend(txId: string, vout: number): Promise { - return axios.get(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspend/' + vout, this.axiosConfig) + return axiosConnection.get(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspend/' + vout, this.axiosConfig) .then((response) => response.data); } $getOutspends(txId: string): Promise { - return axios.get(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspends', this.axiosConfig) + return axiosConnection.get(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspends', this.axiosConfig) .then((response) => response.data); } diff --git a/backend/src/index.ts b/backend/src/index.ts index a81275066..919c039c3 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -36,6 +36,7 @@ import bitcoinRoutes from './api/bitcoin/bitcoin.routes'; import fundingTxFetcher from './tasks/lightning/sync-tasks/funding-tx-fetcher'; import forensicsService from './tasks/lightning/forensics.service'; import priceUpdater from './tasks/price-updater'; +import { AxiosError } from 'axios'; class Server { private wss: WebSocket.Server | undefined; @@ -178,7 +179,7 @@ class Server { setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS); this.currentBackendRetryInterval = 5; - } catch (e) { + } catch (e: any) { const loggerMsg = `runMainLoop error: ${(e instanceof Error ? e.message : e)}. Retrying in ${this.currentBackendRetryInterval} sec.`; if (this.currentBackendRetryInterval > 5) { logger.warn(loggerMsg); @@ -186,7 +187,9 @@ class Server { } else { logger.debug(loggerMsg); } - logger.debug(JSON.stringify(e)); + if (e instanceof AxiosError) { + logger.debug(`AxiosError: ${e?.message}`); + } setTimeout(this.runMainUpdateLoop.bind(this), 1000 * this.currentBackendRetryInterval); this.currentBackendRetryInterval *= 2; this.currentBackendRetryInterval = Math.min(this.currentBackendRetryInterval, 60);