diff --git a/backend/src/api/mining/mining-routes.ts b/backend/src/api/mining/mining-routes.ts index 7fa59e219..3492114b5 100644 --- a/backend/src/api/mining/mining-routes.ts +++ b/backend/src/api/mining/mining-routes.ts @@ -24,8 +24,6 @@ class MiningRoutes { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments', this.$getDifficultyAdjustments) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/reward-stats/:blockCount', this.$getRewardStats) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fees/:interval', this.$getHistoricalBlockFees) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/exact-fees/:height', this.$getHistoricalExactBlockFees) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/exact-fees', this.$getHistoricalExactBlockFees) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/rewards/:interval', this.$getHistoricalBlockRewards) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fee-rates/:interval', this.$getHistoricalBlockFeeRates) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/sizes-weights/:interval', this.$getHistoricalBlockSizeAndWeight) @@ -219,25 +217,6 @@ class MiningRoutes { } } - private async $getHistoricalExactBlockFees(req: Request, res: Response) { - try { - const heightRegex = /^(0|[1-9]\d{0,9})$/; - if (req.params.height !== undefined && !heightRegex.test(req.params.height)) { - throw new Error('Invalid height parameter'); - } - - const blockFees = await mining.$getHistoricalExactBlockFees(req.params.height); - const blockCount = await BlocksRepository.$blockCount(null, null); - res.header('Pragma', 'public'); - res.header('Cache-control', 'public'); - res.header('X-total-count', blockCount.toString()); - res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); - res.json(blockFees); - } catch (e) { - res.status(500).send(e instanceof Error ? e.message : e); - } - } - private async $getHistoricalBlockRewards(req: Request, res: Response) { try { const blockRewards = await mining.$getHistoricalBlockRewards(req.params.interval); diff --git a/backend/src/api/mining/mining.ts b/backend/src/api/mining/mining.ts index 845e571f6..85554db2d 100644 --- a/backend/src/api/mining/mining.ts +++ b/backend/src/api/mining/mining.ts @@ -50,13 +50,6 @@ class Mining { ); } - /** - * Get historical (not averaged) block total fee - */ - public async $getHistoricalExactBlockFees(height: string | null = null): Promise { - return await BlocksRepository.$getHistoricalExactBlockFees(height); - } - /** * Get historical block rewards */ diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 8a667c351..e6e92d60f 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -689,52 +689,6 @@ class BlocksRepository { } } - /** - * Get the historical block fees - */ - public async $getHistoricalExactBlockFees(height: string | null): Promise { - try { - let query = `SELECT - blocks.height, - fees, - prices.USD - FROM blocks - JOIN blocks_prices on blocks_prices.height = blocks.height - JOIN prices on prices.id = blocks_prices.price_id - `; - - if (height !== null) { - query += ` WHERE blocks.height <= ${height}`; - } - query += ` ORDER BY blocks.height DESC LIMIT 10000`; - - const [rows]: any = await DB.query(query); - - // Add accelerations data if available - if (config.MEMPOOL_SERVICES.ACCELERATIONS && rows.length > 0) { - - let query = ` - SELECT height, boost_cost FROM accelerations - WHERE height >= ${rows[rows.length - 1].height} AND height <= ${rows[0].height} - `; - const [accelerations]: any = await DB.query(query); - - for (let i = 0; i < accelerations.length; ++i) { - const idx = rows.findIndex((block) => block.height === accelerations[i].height); - if (idx !== -1) { - if (rows[idx].accelerations === undefined) rows[idx].accelerations = 0; - rows[idx].accelerations += accelerations[i].boost_cost; - } - } - } - - return rows; - } catch (e) { - logger.err('Cannot generate exact block fees history. Reason: ' + (e instanceof Error ? e.message : e)); - throw e; - } - } - /** * Get the historical averaged block rewards */ diff --git a/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.html b/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.html index e9e3e9ea9..8e44cbf51 100644 --- a/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.html +++ b/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.html @@ -9,18 +9,31 @@ - -
- -
- -
+
+
+ + + + + + +
- +
; isLoading = true; formatNumber = formatNumber; - endBlock = ''; - indexedBlocksInterval = { start: 0, end: 0 }; - lowerBound = 0; - upperBound = 0; + timespan = ''; chartInstance: any = undefined; showFiat = false; - dropdownOptions = []; - step = 10000; - includeAccelerations = false; constructor( @Inject(LOCALE_ID) public locale: string, @@ -56,40 +53,43 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { private apiService: ApiService, private formBuilder: UntypedFormBuilder, public stateService: StateService, + private storageService: StorageService, + private miningService: MiningService, private route: ActivatedRoute, - private router: Router, private fiatShortenerPipe: FiatShortenerPipe, private fiatCurrencyPipe: FiatCurrencyPipe, ) { - this.radioGroupForm = this.formBuilder.group({ endBlock: '' }); - this.radioGroupForm.controls.endBlock.setValue(''); + this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' }); + this.radioGroupForm.controls.dateSpan.setValue('1y'); } ngOnInit(): void { this.seoService.setTitle($localize`:@@mining.block-fees-subsidy:Block Fees Vs Subsidy`); this.seoService.setDescription($localize`:@@meta.description.bitcoin.graphs.block-fees-subsidy:See the mining fees earned per Bitcoin block compared to the Bitcoin block subsidy, visualized in BTC and USD over time.`); - this.route.queryParams.subscribe((params) => { - if (/^(0|[1-9]\d{0,9})$/.test(params['height'])) { - this.radioGroupForm.controls.endBlock.setValue(params['height'], { emitEvent: false }); + this.miningWindowPreference = this.miningService.getDefaultTimespan('1m'); + this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); + this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); + + this.route + .fragment + .subscribe((fragment) => { + if (['1m', '3m', '6m', '1y', '2y', '3y', 'all'].indexOf(fragment) > -1) { + this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false }); } }); - this.includeAccelerations = this.stateService.env.ACCELERATOR; - - this.statsObservable$ = this.radioGroupForm.get('endBlock').valueChanges + this.statsObservable$ = this.radioGroupForm.get('dateSpan').valueChanges .pipe( - startWith(this.radioGroupForm.controls.endBlock.value), - switchMap((endBlock) => { + startWith(this.radioGroupForm.controls.dateSpan.value), + switchMap((timespan) => { this.isLoading = true; - this.endBlock = endBlock; - return this.apiService.getHistoricalExactBlockFees$(endBlock === '' ? undefined : endBlock) + this.storageService.setValue('miningWindowPreference', timespan); + this.timespan = timespan; + this.isLoading = true; + return this.apiService.getHistoricalBlockFees$(timespan) .pipe( tap((response) => { - if (response.body.length === 0) { - this.isLoading = false; - return; - } let blockReward = 50 * 100_000_000; const subsidies = {}; for (let i = 0; i <= 33; i++) { @@ -97,55 +97,21 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { blockReward = Math.floor(blockReward / 2); } - const existingHeights = new Set(response.body.map(val => val.height)); - - for (let i = response.body[0].height; i <= response.body[response.body.length - 1].height; i++) { - if (!existingHeights.has(i)) { - response.body.push({ height: i, fees: 0, missing: true }); - } - } - - response.body.sort((a, b) => a.height - b.height); - const data = { - blockHeight: response.body.map(val => val.height), - blockSubsidy: response.body.map(val => val?.missing ? 0 : subsidies[Math.floor(Math.min(val.height / 210000, 33))] / 100_000_000), - blockSubsidyFiat: response.body.map(val => val?.missing ? 0 : subsidies[Math.floor(Math.min(val.height / 210000, 33))] / 100_000_000 * val.USD), - blockFees: response.body.map(val => val.fees / 100_000_000), - blockFeesFiat: response.body.map(val => val.fees / 100_000_000 * val.USD), - } - - let accelerationData = {}; - if (this.includeAccelerations) { - accelerationData = { - blockAccelerations: response.body.map(val => val?.accelerations ? val.accelerations / 100_000_000 : 0), - blockAccelerationsFiat: response.body.map(val => val?.accelerations ? val.accelerations / 100_000_000 * val.USD : 0), - } - } - - this.prepareChartOptions(data, accelerationData); + timestamp: response.body.map(val => val.timestamp * 1000), + blockHeight: response.body.map(val => val.avgHeight), + blockFees: response.body.map(val => val.avgFees / 100_000_000), + blockFeesFiat: response.body.filter(val => val['USD'] > 0).map(val => val.avgFees / 100_000_000 * val['USD']), + blockSubsidy: response.body.map(val => subsidies[Math.floor(Math.min(val.avgHeight / 210000, 33))] / 100_000_000), + blockSubsidyFiat: response.body.filter(val => val['USD'] > 0).map(val => subsidies[Math.floor(Math.min(val.avgHeight / 210000, 33))] / 100_000_000 * val['USD']), + }; + + this.prepareChartOptions(data); this.isLoading = false; }), map((response) => { - const blockCount = parseInt(response.headers.get('x-total-count'), 10); - const chainTip = this.stateService.latestBlockHeight; - - this.indexedBlocksInterval = { - start: chainTip - blockCount, - end: chainTip, - }; - - if (this.radioGroupForm.controls.endBlock.value === '') this.radioGroupForm.controls.endBlock.setValue((this.indexedBlocksInterval.end).toString(), { emitEvent: false }); - this.dropdownOptions = [(this.indexedBlocksInterval.end).toString()]; - if (this.indexedBlocksInterval.end - this.step > this.indexedBlocksInterval.start) { - let newEndBlock = this.indexedBlocksInterval.end - this.step; - while (newEndBlock > this.indexedBlocksInterval.start) { - this.dropdownOptions.push(newEndBlock.toString()); - newEndBlock -= this.step; - } - } return { - indexedBlocksInterval: this.indexedBlocksInterval, + blockCount: parseInt(response.headers.get('x-total-count'), 10), }; }), ); @@ -154,7 +120,7 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { ); } - prepareChartOptions(data, accelerationData) { + prepareChartOptions(data) { let title: object; if (data.blockFees.length === 0) { title = { @@ -168,15 +134,11 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { }; } - this.lowerBound = data.blockHeight[0]; - this.upperBound = data.blockHeight[data.blockHeight.length - 1]; - this.chartOptions = { title: title, color: [ 'var(--orange)', 'var(--success)', - 'var(--tertiary)' ], animation: false, grid: { @@ -204,10 +166,9 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { return ''; } - let tooltip = `Block ${data[0].axisValue}
`; + let tooltip = `Around block ${data[0].axisValue}
`; for (let i = data.length - 1; i >= 0; i--) { const tick = data[i]; - if (tick.seriesName.includes('Accelerations') && tick.data === 0) continue; if (!this.showFiat) tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data, this.locale, '1.0-3')} BTC
`; else tooltip += `${tick.marker} ${tick.seriesName}: ${this.fiatCurrencyPipe.transform(tick.data, null, 'USD') }
`; } @@ -216,15 +177,37 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { return tooltip; }.bind(this) }, - xAxis: data.blockFees.length === 0 ? undefined : - { - type: 'category', - data: data.blockHeight, - axisLabel: { - hideOverlap: true, - color: 'var(--grey)', + xAxis: data.blockFees.length === 0 ? undefined : [ + { + type: 'category', + data: data.blockHeight, + show: false, + axisLabel: { + hideOverlap: true, + } + }, + { + type: 'category', + data: data.timestamp, + show: true, + position: 'bottom', + axisLabel: { + color: 'var(--grey)', + formatter: (val) => { + return formatterXAxis(this.locale, this.timespan, parseInt(val, 10)); + } + }, + axisTick: { + show: false, + }, + axisLine: { + show: false, + }, + splitLine: { + show: false, + }, } - }, + ], legend: data.blockFees.length === 0 ? undefined : { data: [ { @@ -243,14 +226,6 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { }, icon: 'roundRect', }, - this.includeAccelerations ? { - name: 'Accelerations', - inactiveColor: 'var(--grey)', - textStyle: { - color: 'white', - }, - icon: 'roundRect', - } : null, { name: 'Subsidy (USD)', inactiveColor: 'var(--grey)', @@ -267,22 +242,12 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { }, icon: 'roundRect', }, - this.includeAccelerations ? { - name: 'Accelerations (USD)', - inactiveColor: 'var(--grey)', - textStyle: { - color: 'white', - }, - icon: 'roundRect', - } : null - ].filter(legend => legend !== null), + ], selected: { 'Subsidy (USD)': this.showFiat, 'Fees (USD)': this.showFiat, - 'Accelerations (USD)': this.showFiat, 'Subsidy': !this.showFiat, 'Fees': !this.showFiat, - 'Accelerations': !this.showFiat, }, }, yAxis: data.blockFees.length === 0 ? undefined : [ @@ -332,13 +297,6 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { stack: 'total', data: data.blockFees, }, - { - name: 'Accelerations', - yAxisIndex: 0, - type: 'bar', - stack: 'total', - data: accelerationData.blockAccelerations, - }, { name: 'Subsidy (USD)', yAxisIndex: 1, @@ -353,13 +311,6 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { stack: 'total', data: data.blockFeesFiat, }, - { - name: 'Accelerations (USD)', - yAxisIndex: 1, - type: 'bar', - stack: 'total', - data: accelerationData.blockAccelerationsFiat, - }, ], dataZoom: data.blockFees.length === 0 ? undefined : [{ type: 'inside', @@ -398,18 +349,14 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { this.showFiat = true; this.chartInstance.dispatchAction({ type: 'legendUnSelect', name: 'Subsidy' }); this.chartInstance.dispatchAction({ type: 'legendUnSelect', name: 'Fees' }); - this.chartInstance.dispatchAction({ type: 'legendUnSelect', name: 'Accelerations' }); this.chartInstance.dispatchAction({ type: 'legendSelect', name: 'Subsidy (USD)' }); this.chartInstance.dispatchAction({ type: 'legendSelect', name: 'Fees (USD)' }); - this.chartInstance.dispatchAction({ type: 'legendSelect', name: 'Accelerations (USD)' }); } else { this.showFiat = false; this.chartInstance.dispatchAction({ type: 'legendSelect', name: 'Subsidy' }); this.chartInstance.dispatchAction({ type: 'legendSelect', name: 'Fees' }); - this.chartInstance.dispatchAction({ type: 'legendSelect', name: 'Accelerations' }); this.chartInstance.dispatchAction({ type: 'legendUnSelect', name: 'Subsidy (USD)' }); this.chartInstance.dispatchAction({ type: 'legendUnSelect', name: 'Fees (USD)' }); - this.chartInstance.dispatchAction({ type: 'legendUnSelect', name: 'Accelerations (USD)' }); } }); } @@ -429,23 +376,11 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { download(this.chartInstance.getDataURL({ pixelRatio: 2, excludeComponents: ['dataZoom'], - }), `block-fees-subsidy-${this.endBlock}-${Math.round(now.getTime() / 1000)}.svg`); + }), `block-fees-subsidy-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`); // @ts-ignore this.chartOptions.grid.bottom = prevBottom; this.chartOptions.backgroundColor = 'none'; this.chartInstance.setOption(this.chartOptions); } - selectBlockSpan(value: string) { - this.radioGroupForm.controls.endBlock.setValue(value); - this.router.navigate([], { queryParams: { height: value }, queryParamsHandling: 'merge' }); - } - - endBlockToSelector(value: string): string { - let upperBound = Math.min(this.indexedBlocksInterval.end, parseInt(value, 10)); - let lowerBound = Math.max(0, parseInt(value, 10) - this.step); - if (lowerBound < this.indexedBlocksInterval.start) lowerBound = this.indexedBlocksInterval.start + 1; - if (lowerBound > upperBound) lowerBound = upperBound; - return `Blocks ${lowerBound} - ${upperBound}`; - } } diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 1d755baa5..0531916a0 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -333,13 +333,6 @@ export class ApiService { ); } - getHistoricalExactBlockFees$(height: string | undefined) : Observable { - return this.httpClient.get( - this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/exact-fees` + - (height !== undefined ? `/${height}` : ''), { observe: 'response' } - ); - } - getHistoricalBlockRewards$(interval: string | undefined) : Observable { return this.httpClient.get( this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/rewards` +