From 80b3b91a8236b90503cecd7d7e51d6c06a7e613d Mon Sep 17 00:00:00 2001 From: nymkappa Date: Mon, 6 Jun 2022 10:14:40 +0200 Subject: [PATCH 1/5] Add USD serie in block fee/reward charts --- backend/src/api/blocks.ts | 4 + backend/src/api/mining/mining.ts | 21 ++- backend/src/mempool.interfaces.ts | 1 + backend/src/repositories/BlocksRepository.ts | 36 +++-- backend/src/utils/blocks-utils.ts | 1 + frontend/src/app/app.module.ts | 2 + .../block-fee-rates-graph.component.ts | 4 +- .../block-fees-graph.component.ts | 116 ++++++++++++---- .../block-rewards-graph.component.ts | 127 ++++++++++++++---- .../hashrate-chart.component.ts | 1 + .../app/shared/pipes/fiat-shortener.pipe.ts | 37 +++++ 11 files changed, 275 insertions(+), 75 deletions(-) create mode 100644 frontend/src/app/shared/pipes/fiat-shortener.pipe.ts diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index af317af14..e89060422 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -17,6 +17,9 @@ import { prepareBlock } from '../utils/blocks-utils'; import BlocksRepository from '../repositories/BlocksRepository'; import HashratesRepository from '../repositories/HashratesRepository'; import indexer from '../indexer'; +import fiatConversion from './fiat-conversion'; +import RatesRepository from '../repositories/RatesRepository'; +import database from '../database'; import poolsParser from './pools-parser'; import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository'; import mining from './mining/mining'; @@ -150,6 +153,7 @@ class Blocks { blockExtended.extras.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0); blockExtended.extras.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]); blockExtended.extras.coinbaseRaw = blockExtended.extras.coinbaseTx.vin[0].scriptsig; + blockExtended.extras.usd = fiatConversion.getConversionRates().USD; if (block.height === 0) { blockExtended.extras.medianFee = 0; // 50th percentiles diff --git a/backend/src/api/mining/mining.ts b/backend/src/api/mining/mining.ts index 0ee8bcf00..433e0cae9 100644 --- a/backend/src/api/mining/mining.ts +++ b/backend/src/api/mining/mining.ts @@ -31,7 +31,7 @@ class Mining { */ public async $getHistoricalBlockFees(interval: string | null = null): Promise { return await BlocksRepository.$getHistoricalBlockFees( - this.getTimeRange(interval), + this.getTimeRangeForAmounts(interval), Common.getSqlInterval(interval) ); } @@ -41,7 +41,7 @@ class Mining { */ public async $getHistoricalBlockRewards(interval: string | null = null): Promise { return await BlocksRepository.$getHistoricalBlockRewards( - this.getTimeRange(interval), + this.getTimeRangeForAmounts(interval), Common.getSqlInterval(interval) ); } @@ -462,6 +462,21 @@ class Mining { return date; } + private getTimeRangeForAmounts(interval: string | null): number { + switch (interval) { + case '3y': return 1296000; + case '2y': return 864000; + case '1y': return 432000; + case '6m': return 216000; + case '3m': return 108000; + case '1m': return 36000; + case '1w': return 8400; + case '3d': return 3600; + case '24h': return 1200; + default: return 3888000; + } + } + private getTimeRange(interval: string | null): number { switch (interval) { case '3y': return 43200; // 12h @@ -473,7 +488,7 @@ class Mining { case '1w': return 300; // 5min case '3d': return 1; case '24h': return 1; - default: return 86400; // 24h + default: return 86400; } } } diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 477c6a920..a7f8095c5 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -109,6 +109,7 @@ export interface BlockExtension { avgFee?: number; avgFeeRate?: number; coinbaseRaw?: string; + usd?: number | null; } export interface BlockExtended extends IEsploraApi.Block { diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 83f368248..36a41597c 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -256,7 +256,7 @@ class BlocksRepository { const params: any[] = []; let query = ` SELECT - height, + blocks.height, hash as id, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, size, @@ -274,8 +274,10 @@ class BlocksRepository { merkle_root, previous_block_hash as previousblockhash, avg_fee, - avg_fee_rate + avg_fee_rate, + IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd FROM blocks + LEFT JOIN rates on rates.height = blocks.height WHERE pool_id = ?`; params.push(pool.id); @@ -308,7 +310,7 @@ class BlocksRepository { public async $getBlockByHeight(height: number): Promise { try { const [rows]: any[] = await DB.query(`SELECT - height, + blocks.height, hash, hash as id, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, @@ -333,10 +335,12 @@ class BlocksRepository { merkle_root, previous_block_hash as previousblockhash, avg_fee, - avg_fee_rate + avg_fee_rate, + IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd FROM blocks JOIN pools ON blocks.pool_id = pools.id - WHERE height = ${height}; + LEFT JOIN rates on rates.height = blocks.height + WHERE blocks.height = ${height}; `); if (rows.length <= 0) { @@ -357,12 +361,14 @@ class BlocksRepository { public async $getBlockByHash(hash: string): Promise { try { const query = ` - SELECT *, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, hash as id, + SELECT *, blocks.height, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, hash as id, pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, pools.slug as pool_slug, pools.addresses as pool_addresses, pools.regexes as pool_regexes, - previous_block_hash as previousblockhash + previous_block_hash as previousblockhash, + IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd FROM blocks JOIN pools ON blocks.pool_id = pools.id + LEFT JOIN rates on rates.height = blocks.height WHERE hash = '${hash}'; `; const [rows]: any[] = await DB.query(query); @@ -473,10 +479,12 @@ class BlocksRepository { public async $getHistoricalBlockFees(div: number, interval: string | null): Promise { try { let query = `SELECT - CAST(AVG(height) as INT) as avgHeight, + CAST(AVG(blocks.height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(fees) as INT) as avgFees - FROM blocks`; + CAST(AVG(fees) as INT) as avgFees, + IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd + FROM blocks + LEFT JOIN rates on rates.height = blocks.height`; if (interval !== null) { query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; @@ -498,10 +506,12 @@ class BlocksRepository { public async $getHistoricalBlockRewards(div: number, interval: string | null): Promise { try { let query = `SELECT - CAST(AVG(height) as INT) as avgHeight, + CAST(AVG(blocks.height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(reward) as INT) as avgRewards - FROM blocks`; + CAST(AVG(reward) as INT) as avgRewards, + IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd + FROM blocks + LEFT JOIN rates on rates.height = blocks.height`; if (interval !== null) { query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; diff --git a/backend/src/utils/blocks-utils.ts b/backend/src/utils/blocks-utils.ts index 0f282bdeb..b933d6ae7 100644 --- a/backend/src/utils/blocks-utils.ts +++ b/backend/src/utils/blocks-utils.ts @@ -27,6 +27,7 @@ export function prepareBlock(block: any): BlockExtended { name: block.pool_name, slug: block.pool_slug, } : undefined), + usd: block?.extras?.usd ?? block.usd ?? null, } }; } diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 8845f4255..8f95920f3 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -13,6 +13,7 @@ import { SharedModule } from './shared/shared.module'; import { StorageService } from './services/storage.service'; import { HttpCacheInterceptor } from './services/http-cache.interceptor'; import { LanguageService } from './services/language.service'; +import { FiatShortenerPipe } from './shared/pipes/fiat-shortener.pipe'; import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe'; import { CapAddressPipe } from './shared/pipes/cap-address-pipe/cap-address-pipe'; @@ -37,6 +38,7 @@ import { CapAddressPipe } from './shared/pipes/cap-address-pipe/cap-address-pipe StorageService, LanguageService, ShortenStringPipe, + FiatShortenerPipe, CapAddressPipe, { provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true } ], diff --git a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts index 37243bd4a..949024d4c 100644 --- a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts +++ b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts @@ -180,8 +180,8 @@ export class BlockFeeRatesGraphComponent implements OnInit { } let tooltip = `${formatterXAxis(this.locale, this.timespan, parseInt(data[0].axisValue, 10))}
`; - for (const pool of data.reverse()) { - tooltip += `${pool.marker} ${pool.seriesName}: ${pool.data[1]} sats/vByte
`; + for (const rate of data.reverse()) { + tooltip += `${rate.marker} ${rate.seriesName}: ${rate.data[1]} sats/vByte
`; } if (['24h', '3d'].includes(this.timespan)) { diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts index 1a74416e7..6d6e88122 100644 --- a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts @@ -4,12 +4,13 @@ import { Observable } from 'rxjs'; import { map, share, startWith, switchMap, tap } from 'rxjs/operators'; import { ApiService } from 'src/app/services/api.service'; import { SeoService } from 'src/app/services/seo.service'; -import { formatNumber } from '@angular/common'; +import { formatCurrency, formatNumber, getCurrencySymbol } from '@angular/common'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { download, formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils'; +import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from 'src/app/shared/graphs.utils'; import { StorageService } from 'src/app/services/storage.service'; import { MiningService } from 'src/app/services/mining.service'; import { ActivatedRoute } from '@angular/router'; +import { FiatShortenerPipe } from 'src/app/shared/pipes/fiat-shortener.pipe'; @Component({ selector: 'app-block-fees-graph', @@ -51,6 +52,7 @@ export class BlockFeesGraphComponent implements OnInit { private storageService: StorageService, private miningService: MiningService, private route: ActivatedRoute, + private fiatShortenerPipe: FiatShortenerPipe, ) { this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' }); this.radioGroupForm.controls.dateSpan.setValue('1y'); @@ -82,6 +84,7 @@ export class BlockFeesGraphComponent implements OnInit { tap((response) => { this.prepareChartOptions({ blockFees: response.body.map(val => [val.timestamp * 1000, val.avgFees / 100000000, val.avgHeight]), + blockFeesUSD: response.body.filter(val => val.usd > 0).map(val => [val.timestamp * 1000, val.avgFees / 100000000 * val.usd, val.avgHeight]), }); this.isLoading = false; }), @@ -98,16 +101,17 @@ export class BlockFeesGraphComponent implements OnInit { prepareChartOptions(data) { this.chartOptions = { - animation: false, color: [ - new graphic.LinearGradient(0, 0, 0, 0.65, [ - { offset: 0, color: '#F4511E' }, - { offset: 0.25, color: '#FB8C00' }, - { offset: 0.5, color: '#FFB300' }, - { offset: 0.75, color: '#FDD835' }, - { offset: 1, color: '#7CB342' } + new graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: '#00ACC1' }, + { offset: 1, color: '#0D47A1' }, + ]), + new graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: '#FDD835' }, + { offset: 1, color: '#FB8C00' }, ]), ], + animation: false, grid: { top: 30, bottom: 80, @@ -128,28 +132,52 @@ export class BlockFeesGraphComponent implements OnInit { align: 'left', }, borderColor: '#000', - formatter: (ticks) => { - let tooltip = `${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10))}
`; - tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1], this.locale, '1.3-3')} BTC`; - tooltip += `
`; + formatter: function (data) { + if (data.length <= 0) { + return ''; + } + let tooltip = ` + ${formatterXAxis(this.locale, this.timespan, parseInt(data[0].axisValue, 10))}
`; - if (['24h', '3d'].includes(this.timespan)) { - tooltip += `` + $localize`At block: ${ticks[0].data[2]}` + ``; - } else { - tooltip += `` + $localize`Around block: ${ticks[0].data[2]}` + ``; + for (const tick of data) { + if (tick.seriesIndex === 0) { + tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.3-3')} BTC
`; + } else if (tick.seriesIndex === 1) { + tooltip += `${tick.marker} ${tick.seriesName}: ${formatCurrency(tick.data[1], this.locale, getCurrencySymbol('USD', 'narrow'), 'USD', '1.0-0')}
`; + } } + tooltip += `* On average around block ${data[0].data[2]}`; return tooltip; - } + }.bind(this) }, - xAxis: { - name: formatterXAxisLabel(this.locale, this.timespan), - nameLocation: 'middle', - nameTextStyle: { - padding: [10, 0, 0, 0], - }, + xAxis: data.blockFees.length === 0 ? undefined : + { type: 'time', splitNumber: this.isMobile() ? 5 : 10, + axisLabel: { + hideOverlap: true, + } + }, + legend: { + data: [ + { + name: 'Fees BTC', + inactiveColor: 'rgb(110, 112, 121)', + textStyle: { + color: 'white', + }, + icon: 'roundRect', + }, + { + name: 'Fees USD', + inactiveColor: 'rgb(110, 112, 121)', + textStyle: { + color: 'white', + }, + icon: 'roundRect', + }, + ], }, yAxis: [ { @@ -160,6 +188,9 @@ export class BlockFeesGraphComponent implements OnInit { return `${val} BTC`; } }, + max: (value) => { + return Math.floor(value.max * 2 * 10) / 10; + }, splitLine: { lineStyle: { type: 'dotted', @@ -168,18 +199,47 @@ export class BlockFeesGraphComponent implements OnInit { } }, }, + { + type: 'value', + position: 'right', + axisLabel: { + color: 'rgb(110, 112, 121)', + formatter: function(val) { + return this.fiatShortenerPipe.transform(val); + }.bind(this) + }, + splitLine: { + show: false, + }, + }, ], series: [ { + legendHoverLink: false, zlevel: 0, - name: $localize`:@@c20172223f84462032664d717d739297e5a9e2fe:Fees`, - showSymbol: false, - symbol: 'none', + yAxisIndex: 0, + name: 'Fees BTC', data: data.blockFees, type: 'line', + smooth: 0.25, + symbol: 'none', + areaStyle: { + opacity: 0.25, + }, + }, + { + legendHoverLink: false, + zlevel: 1, + yAxisIndex: 1, + name: 'Fees USD', + data: data.blockFeesUSD, + type: 'line', + smooth: 0.25, + symbol: 'none', lineStyle: { width: 2, - }, + opacity: 0.75, + } }, ], dataZoom: [{ diff --git a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts index 995fb31fb..b3d60c0a5 100644 --- a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts +++ b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts @@ -4,12 +4,13 @@ import { Observable } from 'rxjs'; import { map, share, startWith, switchMap, tap } from 'rxjs/operators'; import { ApiService } from 'src/app/services/api.service'; import { SeoService } from 'src/app/services/seo.service'; -import { formatNumber } from '@angular/common'; +import { formatCurrency, formatNumber, getCurrencySymbol } from '@angular/common'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { download, formatterXAxis, formatterXAxisLabel } from 'src/app/shared/graphs.utils'; +import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from 'src/app/shared/graphs.utils'; import { MiningService } from 'src/app/services/mining.service'; import { StorageService } from 'src/app/services/storage.service'; import { ActivatedRoute } from '@angular/router'; +import { FiatShortenerPipe } from 'src/app/shared/pipes/fiat-shortener.pipe'; @Component({ selector: 'app-block-rewards-graph', @@ -51,6 +52,7 @@ export class BlockRewardsGraphComponent implements OnInit { private miningService: MiningService, private storageService: StorageService, private route: ActivatedRoute, + private fiatShortenerPipe: FiatShortenerPipe, ) { } @@ -80,6 +82,7 @@ export class BlockRewardsGraphComponent implements OnInit { tap((response) => { this.prepareChartOptions({ blockRewards: response.body.map(val => [val.timestamp * 1000, val.avgRewards / 100000000, val.avgHeight]), + blockRewardsUSD: response.body.filter(val => val.usd > 0).map(val => [val.timestamp * 1000, val.avgRewards / 100000000 * val.usd, val.avgHeight]), }); this.isLoading = false; }), @@ -95,15 +98,18 @@ export class BlockRewardsGraphComponent implements OnInit { } prepareChartOptions(data) { + const scaleFactor = 0.1; + this.chartOptions = { animation: false, color: [ - new graphic.LinearGradient(0, 0, 0, 0.65, [ - { offset: 0, color: '#F4511E' }, - { offset: 0.25, color: '#FB8C00' }, - { offset: 0.5, color: '#FFB300' }, - { offset: 0.75, color: '#FDD835' }, - { offset: 1, color: '#7CB342' } + new graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: '#00ACC1' }, + { offset: 1, color: '#0D47A1' }, + ]), + new graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: '#FDD835' }, + { offset: 1, color: '#FB8C00' }, ]), ], grid: { @@ -126,33 +132,55 @@ export class BlockRewardsGraphComponent implements OnInit { align: 'left', }, borderColor: '#000', - formatter: (ticks) => { - let tooltip = `${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10))}
`; - tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1], this.locale, '1.3-3')} BTC`; - tooltip += `
`; + formatter: function (data) { + if (data.length <= 0) { + return ''; + } + let tooltip = ` + ${formatterXAxis(this.locale, this.timespan, parseInt(data[0].axisValue, 10))}
`; - if (['24h', '3d'].includes(this.timespan)) { - tooltip += `` + $localize`At block: ${ticks[0].data[2]}` + ``; - } else { - tooltip += `` + $localize`Around block: ${ticks[0].data[2]}` + ``; + for (const tick of data) { + if (tick.seriesIndex === 0) { + tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.3-3')} BTC
`; + } else if (tick.seriesIndex === 1) { + tooltip += `${tick.marker} ${tick.seriesName}: ${formatCurrency(tick.data[1], this.locale, getCurrencySymbol('USD', 'narrow'), 'USD', '1.0-0')}
`; + } } + tooltip += `* On average around block ${data[0].data[2]}`; return tooltip; - } + }.bind(this) }, - xAxis: { - name: formatterXAxisLabel(this.locale, this.timespan), - nameLocation: 'middle', - nameTextStyle: { - padding: [10, 0, 0, 0], - }, + xAxis: data.blockRewards.length === 0 ? undefined : + { type: 'time', splitNumber: this.isMobile() ? 5 : 10, + axisLabel: { + hideOverlap: true, + } + }, + legend: { + data: [ + { + name: 'Rewards BTC', + inactiveColor: 'rgb(110, 112, 121)', + textStyle: { + color: 'white', + }, + icon: 'roundRect', + }, + { + name: 'Rewards USD', + inactiveColor: 'rgb(110, 112, 121)', + textStyle: { + color: 'white', + }, + icon: 'roundRect', + }, + ], }, yAxis: [ { - min: value => Math.round(10 * value.min * 0.99) / 10, - max: value => Math.round(10 * value.max * 1.01) / 10, type: 'value', axisLabel: { color: 'rgb(110, 112, 121)', @@ -160,6 +188,12 @@ export class BlockRewardsGraphComponent implements OnInit { return `${val} BTC`; } }, + min: (value) => { + return Math.round(value.min * (1.0 - scaleFactor) * 10) / 10; + }, + max: (value) => { + return Math.round(value.max * (1.0 + scaleFactor) * 10) / 10; + }, splitLine: { lineStyle: { type: 'dotted', @@ -168,18 +202,53 @@ export class BlockRewardsGraphComponent implements OnInit { } }, }, + { + min: (value) => { + return Math.round(value.min * (1.0 - scaleFactor) * 10) / 10; + }, + max: (value) => { + return Math.round(value.max * (1.0 + scaleFactor) * 10) / 10; + }, + type: 'value', + position: 'right', + axisLabel: { + color: 'rgb(110, 112, 121)', + formatter: function(val) { + return this.fiatShortenerPipe.transform(val); + }.bind(this) + }, + splitLine: { + show: false, + }, + }, ], series: [ { + legendHoverLink: false, zlevel: 0, - name: $localize`:@@12f86e6747a5ad39e62d3480ddc472b1aeab5b76:Reward`, - showSymbol: false, - symbol: 'none', + yAxisIndex: 0, + name: 'Rewards BTC', data: data.blockRewards, type: 'line', + smooth: 0.25, + symbol: 'none', + areaStyle: { + opacity: 0.25, + }, + }, + { + legendHoverLink: false, + zlevel: 1, + yAxisIndex: 1, + name: 'Rewards USD', + data: data.blockRewardsUSD, + type: 'line', + smooth: 0.25, + symbol: 'none', lineStyle: { width: 2, - }, + opacity: 0.75, + } }, ], dataZoom: [{ diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts index 385be0669..cacbe4198 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -351,6 +351,7 @@ export class HashrateChartComponent implements OnInit { series: data.hashrates.length === 0 ? [] : [ { zlevel: 0, + yAxisIndex: 0, name: $localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`, showSymbol: false, symbol: 'none', diff --git a/frontend/src/app/shared/pipes/fiat-shortener.pipe.ts b/frontend/src/app/shared/pipes/fiat-shortener.pipe.ts new file mode 100644 index 000000000..8c534f93f --- /dev/null +++ b/frontend/src/app/shared/pipes/fiat-shortener.pipe.ts @@ -0,0 +1,37 @@ +import { formatCurrency, getCurrencySymbol } from '@angular/common'; +import { Inject, LOCALE_ID, Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'fiatShortener' +}) +export class FiatShortenerPipe implements PipeTransform { + constructor( + @Inject(LOCALE_ID) public locale: string + ) {} + + transform(num: number, ...args: any[]): unknown { + const digits = args[0] || 1; + const unit = args[1] || undefined; + + if (num < 1000) { + return num.toFixed(digits); + } + + const lookup = [ + { value: 1, symbol: '' }, + { value: 1e3, symbol: 'k' }, + { value: 1e6, symbol: 'M' }, + { value: 1e9, symbol: 'G' }, + { value: 1e12, symbol: 'T' }, + { value: 1e15, symbol: 'P' }, + { value: 1e18, symbol: 'E' } + ]; + const rx = /\.0+$|(\.[0-9]*[1-9])0+$/; + const item = lookup.slice().reverse().find((item) => num >= item.value); + + let result = item ? (num / item.value).toFixed(digits).replace(rx, '$1') : '0'; + result = formatCurrency(parseInt(result, 10), this.locale, getCurrencySymbol('USD', 'narrow'), 'USD', '1.0-0'); + + return result + item.symbol; + } +} \ No newline at end of file From 40634a0eb85be423b0a519840d4f01036f195160 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Sat, 9 Jul 2022 16:53:29 +0200 Subject: [PATCH 2/5] [Indexing] Link blocks to their closest known price --- backend/src/api/blocks.ts | 3 - backend/src/api/database-migration.ts | 363 +++++++++---------- backend/src/api/mining/mining.ts | 67 ++++ backend/src/index.ts | 4 +- backend/src/indexer.ts | 2 +- backend/src/mempool.interfaces.ts | 5 + backend/src/repositories/BlocksRepository.ts | 93 ++++- backend/src/repositories/PricesRepository.ts | 7 +- 8 files changed, 338 insertions(+), 206 deletions(-) diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index e89060422..c8c6f4a98 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -18,13 +18,10 @@ import BlocksRepository from '../repositories/BlocksRepository'; import HashratesRepository from '../repositories/HashratesRepository'; import indexer from '../indexer'; import fiatConversion from './fiat-conversion'; -import RatesRepository from '../repositories/RatesRepository'; -import database from '../database'; import poolsParser from './pools-parser'; import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository'; import mining from './mining/mining'; import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository'; -import difficultyAdjustment from './difficulty-adjustment'; class Blocks { private blocks: BlockExtended[] = []; diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index dbc7ce925..a4075d28b 100644 --- a/backend/src/api/database-migration.ts +++ b/backend/src/api/database-migration.ts @@ -4,7 +4,7 @@ import logger from '../logger'; import { Common } from './common'; class DatabaseMigration { - private static currentVersion = 29; + private static currentVersion = 30; private queryTimeout = 120000; private statisticsAddedIndexed = false; private uniqueLogs: string[] = []; @@ -12,8 +12,6 @@ class DatabaseMigration { private blocksTruncatedMessage = `'blocks' table has been truncated. Re-indexing from scratch.`; private hashratesTruncatedMessage = `'hashrates' table has been truncated. Re-indexing from scratch.`; - constructor() { } - /** * Avoid printing multiple time the same message */ @@ -104,195 +102,187 @@ class DatabaseMigration { await this.$setStatisticsAddedIndexedFlag(databaseSchemaVersion); const isBitcoin = ['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK); - try { - await this.$executeQuery(this.getCreateElementsTableQuery(), await this.$checkIfTableExists('elements_pegs')); - await this.$executeQuery(this.getCreateStatisticsQuery(), await this.$checkIfTableExists('statistics')); - if (databaseSchemaVersion < 2 && this.statisticsAddedIndexed === false) { - await this.$executeQuery(`CREATE INDEX added ON statistics (added);`); - } - if (databaseSchemaVersion < 3) { - await this.$executeQuery(this.getCreatePoolsTableQuery(), await this.$checkIfTableExists('pools')); - } - if (databaseSchemaVersion < 4) { - await this.$executeQuery('DROP table IF EXISTS blocks;'); - await this.$executeQuery(this.getCreateBlocksTableQuery(), await this.$checkIfTableExists('blocks')); - } - if (databaseSchemaVersion < 5 && isBitcoin === true) { - this.uniqueLog(logger.notice, this.blocksTruncatedMessage); - await this.$executeQuery('TRUNCATE blocks;'); // Need to re-index - await this.$executeQuery('ALTER TABLE blocks ADD `reward` double unsigned NOT NULL DEFAULT "0"'); - } - if (databaseSchemaVersion < 6 && isBitcoin === true) { - this.uniqueLog(logger.notice, this.blocksTruncatedMessage); - await this.$executeQuery('TRUNCATE blocks;'); // Need to re-index - // Cleanup original blocks fields type - await this.$executeQuery('ALTER TABLE blocks MODIFY `height` integer unsigned NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE blocks MODIFY `tx_count` smallint unsigned NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE blocks MODIFY `size` integer unsigned NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE blocks MODIFY `weight` integer unsigned NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE blocks MODIFY `difficulty` double NOT NULL DEFAULT "0"'); - // We also fix the pools.id type so we need to drop/re-create the foreign key - await this.$executeQuery('ALTER TABLE blocks DROP FOREIGN KEY IF EXISTS `blocks_ibfk_1`'); - await this.$executeQuery('ALTER TABLE pools MODIFY `id` smallint unsigned AUTO_INCREMENT'); - await this.$executeQuery('ALTER TABLE blocks MODIFY `pool_id` smallint unsigned NULL'); - await this.$executeQuery('ALTER TABLE blocks ADD FOREIGN KEY (`pool_id`) REFERENCES `pools` (`id`)'); - // Add new block indexing fields - await this.$executeQuery('ALTER TABLE blocks ADD `version` integer unsigned NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE blocks ADD `bits` integer unsigned NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE blocks ADD `nonce` bigint unsigned NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE blocks ADD `merkle_root` varchar(65) NOT NULL DEFAULT ""'); - await this.$executeQuery('ALTER TABLE blocks ADD `previous_block_hash` varchar(65) NULL'); - } + await this.$executeQuery(this.getCreateElementsTableQuery(), await this.$checkIfTableExists('elements_pegs')); + await this.$executeQuery(this.getCreateStatisticsQuery(), await this.$checkIfTableExists('statistics')); + if (databaseSchemaVersion < 2 && this.statisticsAddedIndexed === false) { + await this.$executeQuery(`CREATE INDEX added ON statistics (added);`); + } + if (databaseSchemaVersion < 3) { + await this.$executeQuery(this.getCreatePoolsTableQuery(), await this.$checkIfTableExists('pools')); + } + if (databaseSchemaVersion < 4) { + await this.$executeQuery('DROP table IF EXISTS blocks;'); + await this.$executeQuery(this.getCreateBlocksTableQuery(), await this.$checkIfTableExists('blocks')); + } + if (databaseSchemaVersion < 5 && isBitcoin === true) { + this.uniqueLog(logger.notice, this.blocksTruncatedMessage); + await this.$executeQuery('TRUNCATE blocks;'); // Need to re-index + await this.$executeQuery('ALTER TABLE blocks ADD `reward` double unsigned NOT NULL DEFAULT "0"'); + } - if (databaseSchemaVersion < 7 && isBitcoin === true) { - await this.$executeQuery('DROP table IF EXISTS hashrates;'); - await this.$executeQuery(this.getCreateDailyStatsTableQuery(), await this.$checkIfTableExists('hashrates')); - } + if (databaseSchemaVersion < 6 && isBitcoin === true) { + this.uniqueLog(logger.notice, this.blocksTruncatedMessage); + await this.$executeQuery('TRUNCATE blocks;'); // Need to re-index + // Cleanup original blocks fields type + await this.$executeQuery('ALTER TABLE blocks MODIFY `height` integer unsigned NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE blocks MODIFY `tx_count` smallint unsigned NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE blocks MODIFY `size` integer unsigned NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE blocks MODIFY `weight` integer unsigned NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE blocks MODIFY `difficulty` double NOT NULL DEFAULT "0"'); + // We also fix the pools.id type so we need to drop/re-create the foreign key + await this.$executeQuery('ALTER TABLE blocks DROP FOREIGN KEY IF EXISTS `blocks_ibfk_1`'); + await this.$executeQuery('ALTER TABLE pools MODIFY `id` smallint unsigned AUTO_INCREMENT'); + await this.$executeQuery('ALTER TABLE blocks MODIFY `pool_id` smallint unsigned NULL'); + await this.$executeQuery('ALTER TABLE blocks ADD FOREIGN KEY (`pool_id`) REFERENCES `pools` (`id`)'); + // Add new block indexing fields + await this.$executeQuery('ALTER TABLE blocks ADD `version` integer unsigned NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE blocks ADD `bits` integer unsigned NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE blocks ADD `nonce` bigint unsigned NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE blocks ADD `merkle_root` varchar(65) NOT NULL DEFAULT ""'); + await this.$executeQuery('ALTER TABLE blocks ADD `previous_block_hash` varchar(65) NULL'); + } - if (databaseSchemaVersion < 8 && isBitcoin === true) { - this.uniqueLog(logger.notice, this.blocksTruncatedMessage); - await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index - await this.$executeQuery('ALTER TABLE `hashrates` DROP INDEX `PRIMARY`'); - await this.$executeQuery('ALTER TABLE `hashrates` ADD `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST'); - await this.$executeQuery('ALTER TABLE `hashrates` ADD `share` float NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE `hashrates` ADD `type` enum("daily", "weekly") DEFAULT "daily"'); - } + if (databaseSchemaVersion < 7 && isBitcoin === true) { + await this.$executeQuery('DROP table IF EXISTS hashrates;'); + await this.$executeQuery(this.getCreateDailyStatsTableQuery(), await this.$checkIfTableExists('hashrates')); + } - if (databaseSchemaVersion < 9 && isBitcoin === true) { - this.uniqueLog(logger.notice, this.hashratesTruncatedMessage); - await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index - await this.$executeQuery('ALTER TABLE `state` CHANGE `name` `name` varchar(100)'); - await this.$executeQuery('ALTER TABLE `hashrates` ADD UNIQUE `hashrate_timestamp_pool_id` (`hashrate_timestamp`, `pool_id`)'); - } + if (databaseSchemaVersion < 8 && isBitcoin === true) { + this.uniqueLog(logger.notice, this.blocksTruncatedMessage); + await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index + await this.$executeQuery('ALTER TABLE `hashrates` DROP INDEX `PRIMARY`'); + await this.$executeQuery('ALTER TABLE `hashrates` ADD `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST'); + await this.$executeQuery('ALTER TABLE `hashrates` ADD `share` float NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE `hashrates` ADD `type` enum("daily", "weekly") DEFAULT "daily"'); + } - if (databaseSchemaVersion < 10 && isBitcoin === true) { - await this.$executeQuery('ALTER TABLE `blocks` ADD INDEX `blockTimestamp` (`blockTimestamp`)'); - } + if (databaseSchemaVersion < 9 && isBitcoin === true) { + this.uniqueLog(logger.notice, this.hashratesTruncatedMessage); + await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index + await this.$executeQuery('ALTER TABLE `state` CHANGE `name` `name` varchar(100)'); + await this.$executeQuery('ALTER TABLE `hashrates` ADD UNIQUE `hashrate_timestamp_pool_id` (`hashrate_timestamp`, `pool_id`)'); + } - if (databaseSchemaVersion < 11 && isBitcoin === true) { - this.uniqueLog(logger.notice, this.blocksTruncatedMessage); - await this.$executeQuery('TRUNCATE blocks;'); // Need to re-index - await this.$executeQuery(`ALTER TABLE blocks - ADD avg_fee INT UNSIGNED NULL, - ADD avg_fee_rate INT UNSIGNED NULL - `); - await this.$executeQuery('ALTER TABLE blocks MODIFY `reward` BIGINT UNSIGNED NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE blocks MODIFY `median_fee` INT UNSIGNED NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE blocks MODIFY `fees` INT UNSIGNED NOT NULL DEFAULT "0"'); - } + if (databaseSchemaVersion < 10 && isBitcoin === true) { + await this.$executeQuery('ALTER TABLE `blocks` ADD INDEX `blockTimestamp` (`blockTimestamp`)'); + } - if (databaseSchemaVersion < 12 && isBitcoin === true) { - // No need to re-index because the new data type can contain larger values - await this.$executeQuery('ALTER TABLE blocks MODIFY `fees` BIGINT UNSIGNED NOT NULL DEFAULT "0"'); - } + if (databaseSchemaVersion < 11 && isBitcoin === true) { + this.uniqueLog(logger.notice, this.blocksTruncatedMessage); + await this.$executeQuery('TRUNCATE blocks;'); // Need to re-index + await this.$executeQuery(`ALTER TABLE blocks + ADD avg_fee INT UNSIGNED NULL, + ADD avg_fee_rate INT UNSIGNED NULL + `); + await this.$executeQuery('ALTER TABLE blocks MODIFY `reward` BIGINT UNSIGNED NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE blocks MODIFY `median_fee` INT UNSIGNED NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE blocks MODIFY `fees` INT UNSIGNED NOT NULL DEFAULT "0"'); + } - if (databaseSchemaVersion < 13 && isBitcoin === true) { - await this.$executeQuery('ALTER TABLE blocks MODIFY `difficulty` DOUBLE UNSIGNED NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE blocks MODIFY `median_fee` BIGINT UNSIGNED NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE blocks MODIFY `avg_fee` BIGINT UNSIGNED NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE blocks MODIFY `avg_fee_rate` BIGINT UNSIGNED NOT NULL DEFAULT "0"'); - } + if (databaseSchemaVersion < 12 && isBitcoin === true) { + // No need to re-index because the new data type can contain larger values + await this.$executeQuery('ALTER TABLE blocks MODIFY `fees` BIGINT UNSIGNED NOT NULL DEFAULT "0"'); + } - if (databaseSchemaVersion < 14 && isBitcoin === true) { - this.uniqueLog(logger.notice, this.hashratesTruncatedMessage); - await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index - await this.$executeQuery('ALTER TABLE `hashrates` DROP FOREIGN KEY `hashrates_ibfk_1`'); - await this.$executeQuery('ALTER TABLE `hashrates` MODIFY `pool_id` SMALLINT UNSIGNED NOT NULL DEFAULT "0"'); - } + if (databaseSchemaVersion < 13 && isBitcoin === true) { + await this.$executeQuery('ALTER TABLE blocks MODIFY `difficulty` DOUBLE UNSIGNED NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE blocks MODIFY `median_fee` BIGINT UNSIGNED NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE blocks MODIFY `avg_fee` BIGINT UNSIGNED NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE blocks MODIFY `avg_fee_rate` BIGINT UNSIGNED NOT NULL DEFAULT "0"'); + } - if (databaseSchemaVersion < 16 && isBitcoin === true) { - this.uniqueLog(logger.notice, this.hashratesTruncatedMessage); - await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index because we changed timestamps - } + if (databaseSchemaVersion < 14 && isBitcoin === true) { + this.uniqueLog(logger.notice, this.hashratesTruncatedMessage); + await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index + await this.$executeQuery('ALTER TABLE `hashrates` DROP FOREIGN KEY `hashrates_ibfk_1`'); + await this.$executeQuery('ALTER TABLE `hashrates` MODIFY `pool_id` SMALLINT UNSIGNED NOT NULL DEFAULT "0"'); + } - if (databaseSchemaVersion < 17 && isBitcoin === true) { - await this.$executeQuery('ALTER TABLE `pools` ADD `slug` CHAR(50) NULL'); - } + if (databaseSchemaVersion < 16 && isBitcoin === true) { + this.uniqueLog(logger.notice, this.hashratesTruncatedMessage); + await this.$executeQuery('TRUNCATE hashrates;'); // Need to re-index because we changed timestamps + } - if (databaseSchemaVersion < 18 && isBitcoin === true) { - await this.$executeQuery('ALTER TABLE `blocks` ADD INDEX `hash` (`hash`);'); - } + if (databaseSchemaVersion < 17 && isBitcoin === true) { + await this.$executeQuery('ALTER TABLE `pools` ADD `slug` CHAR(50) NULL'); + } - if (databaseSchemaVersion < 19) { - await this.$executeQuery(this.getCreateRatesTableQuery(), await this.$checkIfTableExists('rates')); - } + if (databaseSchemaVersion < 18 && isBitcoin === true) { + await this.$executeQuery('ALTER TABLE `blocks` ADD INDEX `hash` (`hash`);'); + } - if (databaseSchemaVersion < 20 && isBitcoin === true) { - await this.$executeQuery(this.getCreateBlocksSummariesTableQuery(), await this.$checkIfTableExists('blocks_summaries')); - } + if (databaseSchemaVersion < 19) { + await this.$executeQuery(this.getCreateRatesTableQuery(), await this.$checkIfTableExists('rates')); + } - if (databaseSchemaVersion < 21) { - await this.$executeQuery('DROP TABLE IF EXISTS `rates`'); - await this.$executeQuery(this.getCreatePricesTableQuery(), await this.$checkIfTableExists('prices')); - } + if (databaseSchemaVersion < 20 && isBitcoin === true) { + await this.$executeQuery(this.getCreateBlocksSummariesTableQuery(), await this.$checkIfTableExists('blocks_summaries')); + } - if (databaseSchemaVersion < 22 && isBitcoin === true) { - await this.$executeQuery('DROP TABLE IF EXISTS `difficulty_adjustments`'); - await this.$executeQuery(this.getCreateDifficultyAdjustmentsTableQuery(), await this.$checkIfTableExists('difficulty_adjustments')); - } + if (databaseSchemaVersion < 21) { + await this.$executeQuery('DROP TABLE IF EXISTS `rates`'); + await this.$executeQuery(this.getCreatePricesTableQuery(), await this.$checkIfTableExists('prices')); + } - if (databaseSchemaVersion < 23) { - await this.$executeQuery('TRUNCATE `prices`'); - await this.$executeQuery('ALTER TABLE `prices` DROP `avg_prices`'); - await this.$executeQuery('ALTER TABLE `prices` ADD `USD` float DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE `prices` ADD `EUR` float DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE `prices` ADD `GBP` float DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE `prices` ADD `CAD` float DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE `prices` ADD `CHF` float DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE `prices` ADD `AUD` float DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE `prices` ADD `JPY` float DEFAULT "0"'); - } + if (databaseSchemaVersion < 22 && isBitcoin === true) { + await this.$executeQuery('DROP TABLE IF EXISTS `difficulty_adjustments`'); + await this.$executeQuery(this.getCreateDifficultyAdjustmentsTableQuery(), await this.$checkIfTableExists('difficulty_adjustments')); + } - if (databaseSchemaVersion < 24 && isBitcoin == true) { - await this.$executeQuery('DROP TABLE IF EXISTS `blocks_audits`'); - await this.$executeQuery(this.getCreateBlocksAuditsTableQuery(), await this.$checkIfTableExists('blocks_audits')); - } + if (databaseSchemaVersion < 23) { + await this.$executeQuery('TRUNCATE `prices`'); + await this.$executeQuery('ALTER TABLE `prices` DROP `avg_prices`'); + await this.$executeQuery('ALTER TABLE `prices` ADD `USD` float DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE `prices` ADD `EUR` float DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE `prices` ADD `GBP` float DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE `prices` ADD `CAD` float DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE `prices` ADD `CHF` float DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE `prices` ADD `AUD` float DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE `prices` ADD `JPY` float DEFAULT "0"'); + } - if (databaseSchemaVersion < 25 && isBitcoin === true) { - await this.$executeQuery(`INSERT INTO state VALUES('last_node_stats', 0, '1970-01-01');`); - await this.$executeQuery(this.getCreateLightningStatisticsQuery(), await this.$checkIfTableExists('lightning_stats')); - await this.$executeQuery(this.getCreateNodesQuery(), await this.$checkIfTableExists('nodes')); - await this.$executeQuery(this.getCreateChannelsQuery(), await this.$checkIfTableExists('channels')); - await this.$executeQuery(this.getCreateNodesStatsQuery(), await this.$checkIfTableExists('node_stats')); - } + if (databaseSchemaVersion < 24 && isBitcoin == true) { + await this.$executeQuery('DROP TABLE IF EXISTS `blocks_audits`'); + await this.$executeQuery(this.getCreateBlocksAuditsTableQuery(), await this.$checkIfTableExists('blocks_audits')); + } - if (databaseSchemaVersion < 26 && isBitcoin === true) { - this.uniqueLog(logger.notice, `'lightning_stats' table has been truncated. Will re-generate historical data from scratch.`); - await this.$executeQuery(`TRUNCATE lightning_stats`); - await this.$executeQuery('ALTER TABLE `lightning_stats` ADD tor_nodes int(11) NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE `lightning_stats` ADD clearnet_nodes int(11) NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE `lightning_stats` ADD unannounced_nodes int(11) NOT NULL DEFAULT "0"'); - } + if (databaseSchemaVersion < 25 && isBitcoin === true) { + await this.$executeQuery(`INSERT INTO state VALUES('last_node_stats', 0, '1970-01-01');`); + await this.$executeQuery(this.getCreateLightningStatisticsQuery(), await this.$checkIfTableExists('lightning_stats')); + await this.$executeQuery(this.getCreateNodesQuery(), await this.$checkIfTableExists('nodes')); + await this.$executeQuery(this.getCreateChannelsQuery(), await this.$checkIfTableExists('channels')); + await this.$executeQuery(this.getCreateNodesStatsQuery(), await this.$checkIfTableExists('node_stats')); + } - if (databaseSchemaVersion < 27 && isBitcoin === true) { - await this.$executeQuery('ALTER TABLE `lightning_stats` ADD avg_capacity bigint(20) unsigned NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE `lightning_stats` ADD avg_fee_rate int(11) unsigned NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE `lightning_stats` ADD avg_base_fee_mtokens bigint(20) unsigned NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_capacity bigint(20) unsigned NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_fee_rate int(11) unsigned NOT NULL DEFAULT "0"'); - await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_base_fee_mtokens bigint(20) unsigned NOT NULL DEFAULT "0"'); - } + if (databaseSchemaVersion < 26 && isBitcoin === true) { + this.uniqueLog(logger.notice, `'lightning_stats' table has been truncated. Will re-generate historical data from scratch.`); + await this.$executeQuery(`TRUNCATE lightning_stats`); + await this.$executeQuery('ALTER TABLE `lightning_stats` ADD tor_nodes int(11) NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE `lightning_stats` ADD clearnet_nodes int(11) NOT NULL DEFAULT "0"'); + await this.$executeQuery('ALTER TABLE `lightning_stats` ADD unannounced_nodes int(11) NOT NULL DEFAULT "0"'); + } - if (databaseSchemaVersion < 28 && isBitcoin === true) { - await this.$executeQuery(`TRUNCATE lightning_stats`); - await this.$executeQuery(`TRUNCATE node_stats`); - await this.$executeQuery(`ALTER TABLE lightning_stats MODIFY added DATE`); - } + if (databaseSchemaVersion < 28 && isBitcoin === true) { + await this.$executeQuery(`TRUNCATE lightning_stats`); + await this.$executeQuery(`TRUNCATE node_stats`); + await this.$executeQuery(`ALTER TABLE lightning_stats MODIFY added DATE`); + } - if (databaseSchemaVersion < 29 && isBitcoin === true) { - await this.$executeQuery(this.getCreateGeoNamesTableQuery(), await this.$checkIfTableExists('geo_names')); - await this.$executeQuery('ALTER TABLE `nodes` ADD as_number int(11) unsigned NULL DEFAULT NULL'); - await this.$executeQuery('ALTER TABLE `nodes` ADD city_id int(11) unsigned NULL DEFAULT NULL'); - await this.$executeQuery('ALTER TABLE `nodes` ADD country_id int(11) unsigned NULL DEFAULT NULL'); - await this.$executeQuery('ALTER TABLE `nodes` ADD accuracy_radius int(11) unsigned NULL DEFAULT NULL'); - await this.$executeQuery('ALTER TABLE `nodes` ADD subdivision_id int(11) unsigned NULL DEFAULT NULL'); - await this.$executeQuery('ALTER TABLE `nodes` ADD longitude double NULL DEFAULT NULL'); - await this.$executeQuery('ALTER TABLE `nodes` ADD latitude double NULL DEFAULT NULL'); - } - - } catch (e) { - throw e; + if (databaseSchemaVersion < 29 && isBitcoin === true) { + await this.$executeQuery(this.getCreateGeoNamesTableQuery(), await this.$checkIfTableExists('geo_names')); + await this.$executeQuery('ALTER TABLE `nodes` ADD as_number int(11) unsigned NULL DEFAULT NULL'); + await this.$executeQuery('ALTER TABLE `nodes` ADD city_id int(11) unsigned NULL DEFAULT NULL'); + await this.$executeQuery('ALTER TABLE `nodes` ADD country_id int(11) unsigned NULL DEFAULT NULL'); + await this.$executeQuery('ALTER TABLE `nodes` ADD accuracy_radius int(11) unsigned NULL DEFAULT NULL'); + await this.$executeQuery('ALTER TABLE `nodes` ADD subdivision_id int(11) unsigned NULL DEFAULT NULL'); + await this.$executeQuery('ALTER TABLE `nodes` ADD longitude double NULL DEFAULT NULL'); + await this.$executeQuery('ALTER TABLE `nodes` ADD latitude double NULL DEFAULT NULL'); + } + if (databaseSchemaVersion < 25 && isBitcoin == true) { // Link blocks to prices + await this.$executeQuery('ALTER TABLE `prices` ADD `id` int NULL AUTO_INCREMENT UNIQUE'); + await this.$executeQuery('DROP TABLE IF EXISTS `blocks_prices`'); + await this.$executeQuery(this.getCreateBlocksPricesTableQuery(), await this.$checkIfTableExists('blocks_prices')); } } @@ -331,7 +321,7 @@ class DatabaseMigration { /** * Small query execution wrapper to log all executed queries */ - private async $executeQuery(query: string, silent: boolean = false): Promise { + private async $executeQuery(query: string, silent = false): Promise { if (!silent) { logger.debug('MIGRATIONS: Execute query:\n' + query); } @@ -360,21 +350,17 @@ class DatabaseMigration { * Create the `state` table */ private async $createMigrationStateTable(): Promise { - try { - const query = `CREATE TABLE IF NOT EXISTS state ( - name varchar(25) NOT NULL, - number int(11) NULL, - string varchar(100) NULL, - CONSTRAINT name_unique UNIQUE (name) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`; - await this.$executeQuery(query); + const query = `CREATE TABLE IF NOT EXISTS state ( + name varchar(25) NOT NULL, + number int(11) NULL, + string varchar(100) NULL, + CONSTRAINT name_unique UNIQUE (name) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`; + await this.$executeQuery(query); - // Set initial values - await this.$executeQuery(`INSERT INTO state VALUES('schema_version', 0, NULL);`); - await this.$executeQuery(`INSERT INTO state VALUES('last_elements_block', 0, NULL);`); - } catch (e) { - throw e; - } + // Set initial values + await this.$executeQuery(`INSERT INTO state VALUES('schema_version', 0, NULL);`); + await this.$executeQuery(`INSERT INTO state VALUES('last_elements_block', 0, NULL);`); } /** @@ -714,6 +700,15 @@ class DatabaseMigration { ) ENGINE=InnoDB DEFAULT CHARSET=utf8;` } + private getCreateBlocksPricesTableQuery(): string { + return `CREATE TABLE IF NOT EXISTS blocks_prices ( + height int(10) unsigned NOT NULL, + price_id int(10) unsigned NOT NULL, + PRIMARY KEY (height), + INDEX (price_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`; + } + public async $truncateIndexedData(tables: string[]) { const allowedTables = ['blocks', 'hashrates', 'prices']; diff --git a/backend/src/api/mining/mining.ts b/backend/src/api/mining/mining.ts index 433e0cae9..4e2216628 100644 --- a/backend/src/api/mining/mining.ts +++ b/backend/src/api/mining/mining.ts @@ -11,8 +11,11 @@ import indexer from '../../indexer'; import DifficultyAdjustmentsRepository from '../../repositories/DifficultyAdjustmentsRepository'; import config from '../../config'; import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository'; +import PricesRepository from '../repositories/PricesRepository'; class Mining { + blocksPriceIndexingRunning = false; + constructor() { } @@ -453,6 +456,70 @@ class Mining { } } + /** + * Create a link between blocks and the latest price at when they were mined + */ + public async $indexBlockPrices() { + if (this.blocksPriceIndexingRunning === true) { + return; + } + this.blocksPriceIndexingRunning = true; + + try { + const prices: any[] = await PricesRepository.$getPricesTimesAndId(); + const blocksWithoutPrices: any[] = await BlocksRepository.$getBlocksWithoutPrice(); + + let totalInserted = 0; + const blocksPrices: BlockPrice[] = []; + + for (const block of blocksWithoutPrices) { + // Quick optimisation, out mtgox feed only goes back to 2010-07-19 02:00:00, so skip the first 68951 blocks + if (block.height < 68951) { + blocksPrices.push({ + height: block.height, + priceId: prices[0].id, + }); + continue; + } + for (const price of prices) { + if (block.timestamp < price.time) { + blocksPrices.push({ + height: block.height, + priceId: price.id, + }); + break; + }; + } + + if (blocksPrices.length >= 100000) { + totalInserted += blocksPrices.length; + if (blocksWithoutPrices.length > 200000) { + logger.debug(`Linking ${blocksPrices.length} newly indexed blocks to their closest price | Progress ${Math.round(totalInserted / blocksWithoutPrices.length * 100)}%`); + } else { + logger.debug(`Linking ${blocksPrices.length} newly indexed blocks to their closest price`); + } + await BlocksRepository.$saveBlockPrices(blocksPrices); + blocksPrices.length = 0; + } + } + + if (blocksPrices.length > 0) { + totalInserted += blocksPrices.length; + if (blocksWithoutPrices.length > 200000) { + logger.debug(`Linking ${blocksPrices.length} newly indexed blocks to their closest price | Progress ${Math.round(totalInserted / blocksWithoutPrices.length * 100)}%`); + } else { + logger.debug(`Linking ${blocksPrices.length} newly indexed blocks to their closest price`); + } + await BlocksRepository.$saveBlockPrices(blocksPrices); + } + } catch (e) { + this.blocksPriceIndexingRunning = false; + throw e; + } + + this.blocksPriceIndexingRunning = false; + } + private getDateMidnight(date: Date): Date { date.setUTCHours(0); date.setUTCMinutes(0); diff --git a/backend/src/index.ts b/backend/src/index.ts index c5053bf12..18cb62d5b 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -35,6 +35,8 @@ import miningRoutes from "./api/mining/mining-routes"; import bisqRoutes from "./api/bisq/bisq.routes"; import liquidRoutes from "./api/liquid/liquid.routes"; import bitcoinRoutes from "./api/bitcoin/bitcoin.routes"; +import BlocksAuditsRepository from './repositories/BlocksAuditsRepository'; +import mining from "./api/mining"; class Server { private wss: WebSocket.Server | undefined; @@ -166,7 +168,7 @@ class Server { await blocks.$updateBlocks(); await memPool.$updateMempool(); indexer.$run(); - priceUpdater.$run(); + priceUpdater.$run().then(mining.$indexBlockPrices.bind(this)); setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS); this.currentBackendRetryInterval = 5; diff --git a/backend/src/indexer.ts b/backend/src/indexer.ts index 7523af94e..ae67b9f64 100644 --- a/backend/src/indexer.ts +++ b/backend/src/indexer.ts @@ -48,7 +48,7 @@ class Indexer { } await mining.$indexDifficultyAdjustments(); - await this.$resetHashratesIndexingState(); + await this.$resetHashratesIndexingState(); // TODO - Remove this as it's not efficient await mining.$generateNetworkHashrateHistory(); await mining.$generatePoolHashrateHistory(); await blocks.$generateBlocksSummariesDatabase(); diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index a7f8095c5..c2d2ee747 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -121,6 +121,11 @@ export interface BlockSummary { transactions: TransactionStripped[]; } +export interface BlockPrice { + height: number; + priceId: number; +} + export interface TransactionMinerInfo { vin: VinStrippedToScriptsig[]; vout: VoutStrippedToScriptPubkey[]; diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 36a41597c..22c15b00f 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -1,4 +1,4 @@ -import { BlockExtended } from '../mempool.interfaces'; +import { BlockExtended, BlockPrice } from '../mempool.interfaces'; import DB from '../database'; import logger from '../logger'; import { Common } from '../api/common'; @@ -275,9 +275,7 @@ class BlocksRepository { previous_block_hash as previousblockhash, avg_fee, avg_fee_rate, - IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd FROM blocks - LEFT JOIN rates on rates.height = blocks.height WHERE pool_id = ?`; params.push(pool.id); @@ -335,12 +333,10 @@ class BlocksRepository { merkle_root, previous_block_hash as previousblockhash, avg_fee, - avg_fee_rate, - IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd + avg_fee_rate FROM blocks JOIN pools ON blocks.pool_id = pools.id - LEFT JOIN rates on rates.height = blocks.height - WHERE blocks.height = ${height}; + WHERE blocks.height = ${height} `); if (rows.length <= 0) { @@ -365,10 +361,8 @@ class BlocksRepository { pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, pools.slug as pool_slug, pools.addresses as pool_addresses, pools.regexes as pool_regexes, previous_block_hash as previousblockhash, - IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd FROM blocks JOIN pools ON blocks.pool_id = pools.id - LEFT JOIN rates on rates.height = blocks.height WHERE hash = '${hash}'; `; const [rows]: any[] = await DB.query(query); @@ -393,7 +387,20 @@ class BlocksRepository { const [rows]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(blockTimestamp) as time, height, difficulty FROM blocks`); return rows; } catch (e) { - logger.err('Cannot generate difficulty history. Reason: ' + (e instanceof Error ? e.message : e)); + logger.err('Cannot get blocks difficulty list from the db. Reason: ' + (e instanceof Error ? e.message : e)); + throw e; + } + } + + /** + * Return blocks height + */ + public async $getBlocksHeightsAndTimestamp(): Promise { + try { + const [rows]: any[] = await DB.query(`SELECT height, blockTimestamp as timestamp FROM blocks`); + return rows; + } catch (e) { + logger.err('Cannot get blocks height and timestamp from the db. Reason: ' + (e instanceof Error ? e.message : e)); throw e; } } @@ -481,10 +488,9 @@ class BlocksRepository { let query = `SELECT CAST(AVG(blocks.height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(fees) as INT) as avgFees, - IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd + CAST(AVG(fees) as INT) as avgFees FROM blocks - LEFT JOIN rates on rates.height = blocks.height`; + `; if (interval !== null) { query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; @@ -508,10 +514,9 @@ class BlocksRepository { let query = `SELECT CAST(AVG(blocks.height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(reward) as INT) as avgRewards, - IFNULL(JSON_EXTRACT(rates.bisq_rates, '$.USD'), null) as usd + CAST(AVG(reward) as INT) as avgRewards FROM blocks - LEFT JOIN rates on rates.height = blocks.height`; + `; if (interval !== null) { query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; @@ -638,6 +643,62 @@ class BlocksRepository { throw e; } } + + /** + * Get all blocks which have not be linked to a price yet + */ + public async $getBlocksWithoutPrice(): Promise { + try { + const [rows]: any[] = await DB.query(` + SELECT UNIX_TIMESTAMP(blocks.blockTimestamp) as timestamp, blocks.height + FROM blocks + LEFT JOIN blocks_prices ON blocks.height = blocks_prices.height + WHERE blocks_prices.height IS NULL + ORDER BY blocks.height + `); + return rows; + } catch (e) { + logger.err('Cannot get blocks height and timestamp from the db. Reason: ' + (e instanceof Error ? e.message : e)); + throw e; + } + } + + /** + * Save block price + */ + public async $saveBlockPrice(blockPrice: BlockPrice): Promise { + try { + await DB.query(`INSERT INTO blocks_prices(height, price_id) VALUE (?, ?)`, [blockPrice.height, blockPrice.priceId]); + } catch (e: any) { + if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart + logger.debug(`Cannot save block price for block ${blockPrice.height} because it has already been indexed, ignoring`); + } else { + logger.err(`Cannot save block price into db. Reason: ` + (e instanceof Error ? e.message : e)); + throw e; + } + } + } + + /** + * Save block price by batch + */ + public async $saveBlockPrices(blockPrices: BlockPrice[]): Promise { + try { + let query = `INSERT INTO blocks_prices(height, price_id) VALUES`; + for (const price of blockPrices) { + query += ` (${price.height}, ${price.priceId}),` + } + query = query.slice(0, -1); + await DB.query(query); + } catch (e: any) { + if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart + logger.debug(`Cannot save blocks prices for blocks [${blockPrices[0].height} to ${blockPrices[blockPrices.length - 1].height}] because it has already been indexed, ignoring`); + } else { + logger.err(`Cannot save blocks prices for blocks [${blockPrices[0].height} to ${blockPrices[blockPrices.length - 1].height}] into db. Reason: ` + (e instanceof Error ? e.message : e)); + throw e; + } + } + } } export default new BlocksRepository(); diff --git a/backend/src/repositories/PricesRepository.ts b/backend/src/repositories/PricesRepository.ts index a3b4f2fb9..92fb4860f 100644 --- a/backend/src/repositories/PricesRepository.ts +++ b/backend/src/repositories/PricesRepository.ts @@ -33,9 +33,14 @@ class PricesRepository { } public async $getPricesTimes(): Promise { - const [times]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices WHERE USD != -1`); + const [times]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices WHERE USD != -1 ORDER BY time`); return times.map(time => time.time); } + + public async $getPricesTimesAndId(): Promise { + const [times]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time, id, USD from prices ORDER BY time`); + return times; + } } export default new PricesRepository(); From a97675c5388e14edddfe87930112e75e7bd7f5c8 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Mon, 11 Jul 2022 23:16:48 +0200 Subject: [PATCH 3/5] Add daily historical price - show USD in block fee reward charts --- backend/src/api/database-migration.ts | 3 +- backend/src/api/mining/mining.ts | 46 ++++++------------- backend/src/index.ts | 4 -- backend/src/indexer.ts | 4 ++ backend/src/repositories/BlocksRepository.ts | 35 ++++++-------- backend/src/tasks/price-feeds/bitfinex-api.ts | 4 +- backend/src/tasks/price-feeds/bitflyer-api.ts | 2 +- backend/src/tasks/price-feeds/coinbase-api.ts | 4 +- backend/src/tasks/price-feeds/ftx-api.ts | 4 +- backend/src/tasks/price-feeds/gemini-api.ts | 4 +- backend/src/tasks/price-feeds/kraken-api.ts | 2 +- backend/src/tasks/price-updater.ts | 15 +++--- .../block-fees-graph.component.html | 9 ---- .../block-fees-graph.component.ts | 26 +++++------ .../block-rewards-graph.component.html | 9 ---- .../block-rewards-graph.component.ts | 20 ++++---- 16 files changed, 74 insertions(+), 117 deletions(-) diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index a4075d28b..48cb32a60 100644 --- a/backend/src/api/database-migration.ts +++ b/backend/src/api/database-migration.ts @@ -279,7 +279,8 @@ class DatabaseMigration { await this.$executeQuery('ALTER TABLE `nodes` ADD longitude double NULL DEFAULT NULL'); await this.$executeQuery('ALTER TABLE `nodes` ADD latitude double NULL DEFAULT NULL'); } - if (databaseSchemaVersion < 25 && isBitcoin == true) { // Link blocks to prices + + if (databaseSchemaVersion < 30 && isBitcoin == true) { // Link blocks to prices await this.$executeQuery('ALTER TABLE `prices` ADD `id` int NULL AUTO_INCREMENT UNIQUE'); await this.$executeQuery('DROP TABLE IF EXISTS `blocks_prices`'); await this.$executeQuery(this.getCreateBlocksPricesTableQuery(), await this.$checkIfTableExists('blocks_prices')); diff --git a/backend/src/api/mining/mining.ts b/backend/src/api/mining/mining.ts index 4e2216628..55e749596 100644 --- a/backend/src/api/mining/mining.ts +++ b/backend/src/api/mining/mining.ts @@ -1,4 +1,4 @@ -import { IndexedDifficultyAdjustment, PoolInfo, PoolStats, RewardStats } from '../../mempool.interfaces'; +import { BlockPrice, PoolInfo, PoolStats, RewardStats } from '../../mempool.interfaces'; import BlocksRepository from '../../repositories/BlocksRepository'; import PoolsRepository from '../../repositories/PoolsRepository'; import HashratesRepository from '../../repositories/HashratesRepository'; @@ -7,11 +7,10 @@ import logger from '../../logger'; import { Common } from '../common'; import loadingIndicators from '../loading-indicators'; import { escape } from 'mysql2'; -import indexer from '../../indexer'; import DifficultyAdjustmentsRepository from '../../repositories/DifficultyAdjustmentsRepository'; import config from '../../config'; import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository'; -import PricesRepository from '../repositories/PricesRepository'; +import PricesRepository from '../../repositories/PricesRepository'; class Mining { blocksPriceIndexingRunning = false; @@ -34,7 +33,7 @@ class Mining { */ public async $getHistoricalBlockFees(interval: string | null = null): Promise { return await BlocksRepository.$getHistoricalBlockFees( - this.getTimeRangeForAmounts(interval), + this.getTimeRange(interval, 5), Common.getSqlInterval(interval) ); } @@ -44,7 +43,7 @@ class Mining { */ public async $getHistoricalBlockRewards(interval: string | null = null): Promise { return await BlocksRepository.$getHistoricalBlockRewards( - this.getTimeRangeForAmounts(interval), + this.getTimeRange(interval), Common.getSqlInterval(interval) ); } @@ -529,33 +528,18 @@ class Mining { return date; } - private getTimeRangeForAmounts(interval: string | null): number { + private getTimeRange(interval: string | null, scale = 1): number { switch (interval) { - case '3y': return 1296000; - case '2y': return 864000; - case '1y': return 432000; - case '6m': return 216000; - case '3m': return 108000; - case '1m': return 36000; - case '1w': return 8400; - case '3d': return 3600; - case '24h': return 1200; - default: return 3888000; - } - } - - private getTimeRange(interval: string | null): number { - switch (interval) { - case '3y': return 43200; // 12h - case '2y': return 28800; // 8h - case '1y': return 28800; // 8h - case '6m': return 10800; // 3h - case '3m': return 7200; // 2h - case '1m': return 1800; // 30min - case '1w': return 300; // 5min - case '3d': return 1; - case '24h': return 1; - default: return 86400; + case '3y': return 43200 * scale; // 12h + case '2y': return 28800 * scale; // 8h + case '1y': return 28800 * scale; // 8h + case '6m': return 10800 * scale; // 3h + case '3m': return 7200 * scale; // 2h + case '1m': return 1800 * scale; // 30min + case '1w': return 300 * scale; // 5min + case '3d': return 1 * scale; + case '24h': return 1 * scale; + default: return 86400 * scale; } } } diff --git a/backend/src/index.ts b/backend/src/index.ts index 18cb62d5b..b7159afaf 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -24,7 +24,6 @@ import icons from './api/liquid/icons'; import { Common } from './api/common'; import poolsUpdater from './tasks/pools-updater'; import indexer from './indexer'; -import priceUpdater from './tasks/price-updater'; import nodesRoutes from './api/explorer/nodes.routes'; import channelsRoutes from './api/explorer/channels.routes'; import generalLightningRoutes from './api/explorer/general.routes'; @@ -35,8 +34,6 @@ import miningRoutes from "./api/mining/mining-routes"; import bisqRoutes from "./api/bisq/bisq.routes"; import liquidRoutes from "./api/liquid/liquid.routes"; import bitcoinRoutes from "./api/bitcoin/bitcoin.routes"; -import BlocksAuditsRepository from './repositories/BlocksAuditsRepository'; -import mining from "./api/mining"; class Server { private wss: WebSocket.Server | undefined; @@ -168,7 +165,6 @@ class Server { await blocks.$updateBlocks(); await memPool.$updateMempool(); indexer.$run(); - priceUpdater.$run().then(mining.$indexBlockPrices.bind(this)); setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS); this.currentBackendRetryInterval = 5; diff --git a/backend/src/indexer.ts b/backend/src/indexer.ts index ae67b9f64..e452a42f4 100644 --- a/backend/src/indexer.ts +++ b/backend/src/indexer.ts @@ -5,6 +5,7 @@ import mining from './api/mining/mining'; import logger from './logger'; import HashratesRepository from './repositories/HashratesRepository'; import bitcoinClient from './api/bitcoin/bitcoin-client'; +import priceUpdater from './tasks/price-updater'; class Indexer { runIndexer = true; @@ -38,6 +39,8 @@ class Indexer { logger.debug(`Running mining indexer`); try { + await priceUpdater.$run(); + const chainValid = await blocks.$generateBlockDatabase(); if (chainValid === false) { // Chain of block hash was invalid, so we need to reindex. Stop here and continue at the next iteration @@ -47,6 +50,7 @@ class Indexer { return; } + await mining.$indexBlockPrices(); await mining.$indexDifficultyAdjustments(); await this.$resetHashratesIndexingState(); // TODO - Remove this as it's not efficient await mining.$generateNetworkHashrateHistory(); diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 22c15b00f..a84cc797b 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -274,7 +274,7 @@ class BlocksRepository { merkle_root, previous_block_hash as previousblockhash, avg_fee, - avg_fee_rate, + avg_fee_rate FROM blocks WHERE pool_id = ?`; params.push(pool.id); @@ -288,6 +288,7 @@ class BlocksRepository { LIMIT 10`; try { + console.log(query, params); const [rows] = await DB.query(query, params); const blocks: BlockExtended[] = []; @@ -360,12 +361,12 @@ class BlocksRepository { SELECT *, blocks.height, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, hash as id, pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, pools.slug as pool_slug, pools.addresses as pool_addresses, pools.regexes as pool_regexes, - previous_block_hash as previousblockhash, + previous_block_hash as previousblockhash FROM blocks JOIN pools ON blocks.pool_id = pools.id - WHERE hash = '${hash}'; + WHERE hash = '?'; `; - const [rows]: any[] = await DB.query(query); + const [rows]: any[] = await DB.query(query, [hash]); if (rows.length <= 0) { return null; @@ -488,8 +489,11 @@ class BlocksRepository { let query = `SELECT CAST(AVG(blocks.height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(fees) as INT) as avgFees + CAST(AVG(fees) as INT) as avgFees, + prices.USD FROM blocks + JOIN blocks_prices on blocks_prices.height = blocks.height + JOIN prices on prices.id = blocks_prices.price_id `; if (interval !== null) { @@ -514,8 +518,11 @@ class BlocksRepository { let query = `SELECT CAST(AVG(blocks.height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(reward) as INT) as avgRewards + CAST(AVG(reward) as INT) as avgRewards, + prices.USD FROM blocks + JOIN blocks_prices on blocks_prices.height = blocks.height + JOIN prices on prices.id = blocks_prices.price_id `; if (interval !== null) { @@ -663,22 +670,6 @@ class BlocksRepository { } } - /** - * Save block price - */ - public async $saveBlockPrice(blockPrice: BlockPrice): Promise { - try { - await DB.query(`INSERT INTO blocks_prices(height, price_id) VALUE (?, ?)`, [blockPrice.height, blockPrice.priceId]); - } catch (e: any) { - if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart - logger.debug(`Cannot save block price for block ${blockPrice.height} because it has already been indexed, ignoring`); - } else { - logger.err(`Cannot save block price into db. Reason: ` + (e instanceof Error ? e.message : e)); - throw e; - } - } - } - /** * Save block price by batch */ diff --git a/backend/src/tasks/price-feeds/bitfinex-api.ts b/backend/src/tasks/price-feeds/bitfinex-api.ts index be3f5617b..04bd47732 100644 --- a/backend/src/tasks/price-feeds/bitfinex-api.ts +++ b/backend/src/tasks/price-feeds/bitfinex-api.ts @@ -16,7 +16,7 @@ class BitfinexApi implements PriceFeed { return response ? parseInt(response['last_price'], 10) : -1; } - public async $fetchRecentHourlyPrice(currencies: string[]): Promise { + public async $fetchRecentPrice(currencies: string[], type: 'hour' | 'day'): Promise { const priceHistory: PriceHistory = {}; for (const currency of currencies) { @@ -24,7 +24,7 @@ class BitfinexApi implements PriceFeed { continue; } - const response = await query(this.urlHist.replace('{GRANULARITY}', '1h').replace('{CURRENCY}', currency)); + const response = await query(this.urlHist.replace('{GRANULARITY}', type === 'hour' ? '1h' : '1D').replace('{CURRENCY}', currency)); const pricesRaw = response ? response : []; for (const price of pricesRaw as any[]) { diff --git a/backend/src/tasks/price-feeds/bitflyer-api.ts b/backend/src/tasks/price-feeds/bitflyer-api.ts index d87661abb..143fbe8d9 100644 --- a/backend/src/tasks/price-feeds/bitflyer-api.ts +++ b/backend/src/tasks/price-feeds/bitflyer-api.ts @@ -16,7 +16,7 @@ class BitflyerApi implements PriceFeed { return response ? parseInt(response['ltp'], 10) : -1; } - public async $fetchRecentHourlyPrice(currencies: string[]): Promise { + public async $fetchRecentPrice(currencies: string[], type: 'hour' | 'day'): Promise { return []; } } diff --git a/backend/src/tasks/price-feeds/coinbase-api.ts b/backend/src/tasks/price-feeds/coinbase-api.ts index b9abf860e..ef28b0d80 100644 --- a/backend/src/tasks/price-feeds/coinbase-api.ts +++ b/backend/src/tasks/price-feeds/coinbase-api.ts @@ -16,7 +16,7 @@ class CoinbaseApi implements PriceFeed { return response ? parseInt(response['data']['amount'], 10) : -1; } - public async $fetchRecentHourlyPrice(currencies: string[]): Promise { + public async $fetchRecentPrice(currencies: string[], type: 'hour' | 'day'): Promise { const priceHistory: PriceHistory = {}; for (const currency of currencies) { @@ -24,7 +24,7 @@ class CoinbaseApi implements PriceFeed { continue; } - const response = await query(this.urlHist.replace('{GRANULARITY}', '3600').replace('{CURRENCY}', currency)); + const response = await query(this.urlHist.replace('{GRANULARITY}', type === 'hour' ? '3600' : '86400').replace('{CURRENCY}', currency)); const pricesRaw = response ? response : []; for (const price of pricesRaw as any[]) { diff --git a/backend/src/tasks/price-feeds/ftx-api.ts b/backend/src/tasks/price-feeds/ftx-api.ts index db58c8800..193d3e881 100644 --- a/backend/src/tasks/price-feeds/ftx-api.ts +++ b/backend/src/tasks/price-feeds/ftx-api.ts @@ -16,7 +16,7 @@ class FtxApi implements PriceFeed { return response ? parseInt(response['result']['last'], 10) : -1; } - public async $fetchRecentHourlyPrice(currencies: string[]): Promise { + public async $fetchRecentPrice(currencies: string[], type: 'hour' | 'day'): Promise { const priceHistory: PriceHistory = {}; for (const currency of currencies) { @@ -24,7 +24,7 @@ class FtxApi implements PriceFeed { continue; } - const response = await query(this.urlHist.replace('{GRANULARITY}', '3600').replace('{CURRENCY}', currency)); + const response = await query(this.urlHist.replace('{GRANULARITY}', type === 'hour' ? '3600' : '86400').replace('{CURRENCY}', currency)); const pricesRaw = response ? response['result'] : []; for (const price of pricesRaw as any[]) { diff --git a/backend/src/tasks/price-feeds/gemini-api.ts b/backend/src/tasks/price-feeds/gemini-api.ts index 6b5742a7a..abd8e0939 100644 --- a/backend/src/tasks/price-feeds/gemini-api.ts +++ b/backend/src/tasks/price-feeds/gemini-api.ts @@ -16,7 +16,7 @@ class GeminiApi implements PriceFeed { return response ? parseInt(response['last'], 10) : -1; } - public async $fetchRecentHourlyPrice(currencies: string[]): Promise { + public async $fetchRecentPrice(currencies: string[], type: 'hour' | 'day'): Promise { const priceHistory: PriceHistory = {}; for (const currency of currencies) { @@ -24,7 +24,7 @@ class GeminiApi implements PriceFeed { continue; } - const response = await query(this.urlHist.replace('{GRANULARITY}', '1hr').replace('{CURRENCY}', currency)); + const response = await query(this.urlHist.replace('{GRANULARITY}', type === 'hour' ? '1hr' : '1day').replace('{CURRENCY}', currency)); const pricesRaw = response ? response : []; for (const price of pricesRaw as any[]) { diff --git a/backend/src/tasks/price-feeds/kraken-api.ts b/backend/src/tasks/price-feeds/kraken-api.ts index 6c3cf93da..ce76d62c2 100644 --- a/backend/src/tasks/price-feeds/kraken-api.ts +++ b/backend/src/tasks/price-feeds/kraken-api.ts @@ -26,7 +26,7 @@ class KrakenApi implements PriceFeed { return response ? parseInt(response['result'][this.getTicker(currency)]['c'][0], 10) : -1; } - public async $fetchRecentHourlyPrice(currencies: string[]): Promise { + public async $fetchRecentPrice(currencies: string[], type: 'hour' | 'day'): Promise { const priceHistory: PriceHistory = {}; for (const currency of currencies) { diff --git a/backend/src/tasks/price-updater.ts b/backend/src/tasks/price-updater.ts index 2d4087d8b..a5901d7f7 100644 --- a/backend/src/tasks/price-updater.ts +++ b/backend/src/tasks/price-updater.ts @@ -16,7 +16,7 @@ export interface PriceFeed { currencies: string[]; $fetchPrice(currency): Promise; - $fetchRecentHourlyPrice(currencies: string[]): Promise; + $fetchRecentPrice(currencies: string[], type: string): Promise; } export interface PriceHistory { @@ -185,7 +185,8 @@ class PriceUpdater { await new KrakenApi().$insertHistoricalPrice(); // Insert missing recent hourly prices - await this.$insertMissingRecentPrices(); + await this.$insertMissingRecentPrices('day'); + await this.$insertMissingRecentPrices('hour'); this.historyInserted = true; this.lastHistoricalRun = new Date().getTime(); @@ -195,17 +196,17 @@ class PriceUpdater { * Find missing hourly prices and insert them in the database * It has a limited backward range and it depends on which API are available */ - private async $insertMissingRecentPrices(): Promise { + private async $insertMissingRecentPrices(type: 'hour' | 'day'): Promise { const existingPriceTimes = await PricesRepository.$getPricesTimes(); - logger.info(`Fetching hourly price history from exchanges and saving missing ones into the database, this may take a while`); + logger.info(`Fetching ${type === 'day' ? 'dai' : 'hour'}ly price history from exchanges and saving missing ones into the database, this may take a while`); const historicalPrices: PriceHistory[] = []; // Fetch all historical hourly prices for (const feed of this.feeds) { try { - historicalPrices.push(await feed.$fetchRecentHourlyPrice(this.currencies)); + historicalPrices.push(await feed.$fetchRecentPrice(this.currencies, type)); } catch (e) { logger.err(`Cannot fetch hourly historical price from ${feed.name}. Ignoring this feed. Reason: ${e instanceof Error ? e.message : e}`); } @@ -252,9 +253,9 @@ class PriceUpdater { } if (totalInserted > 0) { - logger.notice(`Inserted ${totalInserted} hourly historical prices into the db`); + logger.notice(`Inserted ${totalInserted} ${type === 'day' ? 'dai' : 'hour'}ly historical prices into the db`); } else { - logger.debug(`Inserted ${totalInserted} hourly historical prices into the db`); + logger.debug(`Inserted ${totalInserted} ${type === 'day' ? 'dai' : 'hour'}ly historical prices into the db`); } } } diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html index 4700f8c35..4aa4eadd3 100644 --- a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.html @@ -8,15 +8,6 @@
- - - diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts index 6d6e88122..205699505 100644 --- a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts @@ -60,14 +60,14 @@ export class BlockFeesGraphComponent implements OnInit { ngOnInit(): void { this.seoService.setTitle($localize`:@@6c453b11fd7bd159ae30bc381f367bc736d86909:Block Fees`); - this.miningWindowPreference = this.miningService.getDefaultTimespan('24h'); + 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 (['24h', '3d', '1w', '1m', '3m', '6m', '1y', '2y', '3y', 'all'].indexOf(fragment) > -1) { + if (['1m', '3m', '6m', '1y', '2y', '3y', 'all'].indexOf(fragment) > -1) { this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false }); } }); @@ -84,7 +84,7 @@ export class BlockFeesGraphComponent implements OnInit { tap((response) => { this.prepareChartOptions({ blockFees: response.body.map(val => [val.timestamp * 1000, val.avgFees / 100000000, val.avgHeight]), - blockFeesUSD: response.body.filter(val => val.usd > 0).map(val => [val.timestamp * 1000, val.avgFees / 100000000 * val.usd, val.avgHeight]), + blockFeesUSD: response.body.filter(val => val.USD > 0).map(val => [val.timestamp * 1000, val.avgFees / 100000000 * val.USD, val.avgHeight]), }); this.isLoading = false; }), @@ -102,14 +102,14 @@ export class BlockFeesGraphComponent implements OnInit { prepareChartOptions(data) { this.chartOptions = { color: [ - new graphic.LinearGradient(0, 0, 0, 1, [ - { offset: 0, color: '#00ACC1' }, - { offset: 1, color: '#0D47A1' }, - ]), new graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#FDD835' }, { offset: 1, color: '#FB8C00' }, ]), + new graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: '#C0CA33' }, + { offset: 1, color: '#1B5E20' }, + ]), ], animation: false, grid: { @@ -188,9 +188,6 @@ export class BlockFeesGraphComponent implements OnInit { return `${val} BTC`; } }, - max: (value) => { - return Math.floor(value.max * 2 * 10) / 10; - }, splitLine: { lineStyle: { type: 'dotted', @@ -223,9 +220,10 @@ export class BlockFeesGraphComponent implements OnInit { type: 'line', smooth: 0.25, symbol: 'none', - areaStyle: { - opacity: 0.25, - }, + lineStyle: { + width: 1, + opacity: 1, + } }, { legendHoverLink: false, @@ -238,7 +236,7 @@ export class BlockFeesGraphComponent implements OnInit { symbol: 'none', lineStyle: { width: 2, - opacity: 0.75, + opacity: 1, } }, ], diff --git a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.html b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.html index ef9978191..1087a811f 100644 --- a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.html +++ b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.html @@ -9,15 +9,6 @@
- - - diff --git a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts index b3d60c0a5..a5ff99cd9 100644 --- a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts +++ b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts @@ -58,14 +58,14 @@ export class BlockRewardsGraphComponent implements OnInit { ngOnInit(): void { this.seoService.setTitle($localize`:@@8ba8fe810458280a83df7fdf4c614dfc1a826445:Block Rewards`); - this.miningWindowPreference = this.miningService.getDefaultTimespan('24h'); + this.miningWindowPreference = this.miningService.getDefaultTimespan('3m'); this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); this.route .fragment .subscribe((fragment) => { - if (['24h', '3d', '1w', '1m', '3m', '6m', '1y', '2y', '3y', 'all'].indexOf(fragment) > -1) { + if (['3m', '6m', '1y', '2y', '3y', 'all'].indexOf(fragment) > -1) { this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false }); } }); @@ -82,7 +82,7 @@ export class BlockRewardsGraphComponent implements OnInit { tap((response) => { this.prepareChartOptions({ blockRewards: response.body.map(val => [val.timestamp * 1000, val.avgRewards / 100000000, val.avgHeight]), - blockRewardsUSD: response.body.filter(val => val.usd > 0).map(val => [val.timestamp * 1000, val.avgRewards / 100000000 * val.usd, val.avgHeight]), + blockRewardsUSD: response.body.filter(val => val.USD > 0).map(val => [val.timestamp * 1000, val.avgRewards / 100000000 * val.USD, val.avgHeight]), }); this.isLoading = false; }), @@ -103,14 +103,14 @@ export class BlockRewardsGraphComponent implements OnInit { this.chartOptions = { animation: false, color: [ - new graphic.LinearGradient(0, 0, 0, 1, [ - { offset: 0, color: '#00ACC1' }, - { offset: 1, color: '#0D47A1' }, - ]), new graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#FDD835' }, { offset: 1, color: '#FB8C00' }, ]), + new graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: '#C0CA33' }, + { offset: 1, color: '#1B5E20' }, + ]), ], grid: { top: 20, @@ -232,9 +232,6 @@ export class BlockRewardsGraphComponent implements OnInit { type: 'line', smooth: 0.25, symbol: 'none', - areaStyle: { - opacity: 0.25, - }, }, { legendHoverLink: false, @@ -248,6 +245,9 @@ export class BlockRewardsGraphComponent implements OnInit { lineStyle: { width: 2, opacity: 0.75, + }, + areaStyle: { + opacity: 0.05, } }, ], From 5ac9b5674e652eb0e0bf82a0cfbe9d9812504a13 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 12 Jul 2022 08:50:07 +0200 Subject: [PATCH 4/5] Show "indexing in progress" in fee/reward charts during block indexing --- backend/src/repositories/BlocksRepository.ts | 1 - .../block-fees-graph.component.ts | 22 +++++++++++++++---- .../block-rewards-graph.component.ts | 22 +++++++++++++++---- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index a84cc797b..408fdd014 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -288,7 +288,6 @@ class BlocksRepository { LIMIT 10`; try { - console.log(query, params); const [rows] = await DB.query(query, params); const blocks: BlockExtended[] = []; diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts index 205699505..a64c7e36e 100644 --- a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts @@ -100,7 +100,21 @@ export class BlockFeesGraphComponent implements OnInit { } prepareChartOptions(data) { + let title: object; + if (data.blockFees.length === 0) { + title = { + textStyle: { + color: 'grey', + fontSize: 15 + }, + text: $localize`:@@23555386d8af1ff73f297e89dd4af3f4689fb9dd:Indexing blocks`, + left: 'center', + top: 'center' + }; + } + this.chartOptions = { + title: title, color: [ new graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#FDD835' }, @@ -159,7 +173,7 @@ export class BlockFeesGraphComponent implements OnInit { hideOverlap: true, } }, - legend: { + legend: data.blockFees.length === 0 ? undefined : { data: [ { name: 'Fees BTC', @@ -179,7 +193,7 @@ export class BlockFeesGraphComponent implements OnInit { }, ], }, - yAxis: [ + yAxis: data.blockFees.length === 0 ? undefined : [ { type: 'value', axisLabel: { @@ -210,7 +224,7 @@ export class BlockFeesGraphComponent implements OnInit { }, }, ], - series: [ + series: data.blockFees.length === 0 ? undefined : [ { legendHoverLink: false, zlevel: 0, @@ -240,7 +254,7 @@ export class BlockFeesGraphComponent implements OnInit { } }, ], - dataZoom: [{ + dataZoom: data.blockFees.length === 0 ? undefined : [{ type: 'inside', realtime: true, zoomLock: true, diff --git a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts index a5ff99cd9..5cd9feea2 100644 --- a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts +++ b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts @@ -98,9 +98,23 @@ export class BlockRewardsGraphComponent implements OnInit { } prepareChartOptions(data) { + let title: object; + if (data.blockRewards.length === 0) { + title = { + textStyle: { + color: 'grey', + fontSize: 15 + }, + text: $localize`:@@23555386d8af1ff73f297e89dd4af3f4689fb9dd:Indexing blocks`, + left: 'center', + top: 'center' + }; + } + const scaleFactor = 0.1; this.chartOptions = { + title: title, animation: false, color: [ new graphic.LinearGradient(0, 0, 0, 1, [ @@ -159,7 +173,7 @@ export class BlockRewardsGraphComponent implements OnInit { hideOverlap: true, } }, - legend: { + legend: data.blockRewards.length === 0 ? undefined : { data: [ { name: 'Rewards BTC', @@ -179,7 +193,7 @@ export class BlockRewardsGraphComponent implements OnInit { }, ], }, - yAxis: [ + yAxis: data.blockRewards.length === 0 ? undefined : [ { type: 'value', axisLabel: { @@ -222,7 +236,7 @@ export class BlockRewardsGraphComponent implements OnInit { }, }, ], - series: [ + series: data.blockRewards.length === 0 ? undefined : [ { legendHoverLink: false, zlevel: 0, @@ -251,7 +265,7 @@ export class BlockRewardsGraphComponent implements OnInit { } }, ], - dataZoom: [{ + dataZoom: data.blockRewards.length === 0 ? undefined : [{ type: 'inside', realtime: true, zoomLock: true, From 908635b3dda3653f699869439b1362df8b9ea7e7 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 12 Jul 2022 19:10:57 +0200 Subject: [PATCH 5/5] Fix mysql syntax error --- backend/src/repositories/BlocksRepository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 408fdd014..40f670833 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -363,7 +363,7 @@ class BlocksRepository { previous_block_hash as previousblockhash FROM blocks JOIN pools ON blocks.pool_id = pools.id - WHERE hash = '?'; + WHERE hash = ?; `; const [rows]: any[] = await DB.query(query, [hash]);