diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index 1d5142080..e7876bd60 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -42,9 +42,7 @@ class Mining { }); poolsStatistics['pools'] = poolsStats; - - const oldestBlock = new Date(await BlocksRepository.$oldestBlockTimestamp()); - poolsStatistics['oldestIndexedBlockTimestamp'] = oldestBlock.getTime(); + poolsStatistics['oldestIndexedBlockTimestamp'] = await BlocksRepository.$oldestBlockTimestamp(); const blockCount: number = await BlocksRepository.$blockCount(null, interval); poolsStatistics['blockCount'] = blockCount; @@ -79,26 +77,14 @@ class Mining { * Return the historical difficulty adjustments and oldest indexed block timestamp */ public async $getHistoricalDifficulty(interval: string | null): Promise { - const difficultyAdjustments = await BlocksRepository.$getBlocksDifficulty(interval); - const oldestBlock = new Date(await BlocksRepository.$oldestBlockTimestamp()); - - return { - adjustments: difficultyAdjustments, - oldestIndexedBlockTimestamp: oldestBlock.getTime(), - }; + return await BlocksRepository.$getBlocksDifficulty(interval); } /** * Return the historical hashrates and oldest indexed block timestamp */ public async $getHistoricalHashrates(interval: string | null): Promise { - const hashrates = await HashratesRepository.$get(interval); - const oldestBlock = new Date(await BlocksRepository.$oldestBlockTimestamp()); - - return { - hashrates: hashrates, - oldestIndexedBlockTimestamp: oldestBlock.getTime(), - }; + return await HashratesRepository.$get(interval); } /** diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 235dc9ebd..e8bfb4a62 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -187,12 +187,11 @@ class BlocksRepository { * Get the oldest indexed block */ public async $oldestBlockTimestamp(): Promise { - const query = `SELECT blockTimestamp + const query = `SELECT UNIX_TIMESTAMP(blockTimestamp) as blockTimestamp FROM blocks ORDER BY height LIMIT 1;`; - // logger.debug(query); const connection = await DB.pool.getConnection(); const [rows]: any[] = await connection.query(query); diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 1bf1c3434..e38da16de 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -588,11 +588,18 @@ class Routes { public async $getHistoricalHashrate(req: Request, res: Response) { try { - const stats = await mining.$getHistoricalHashrates(req.params.interval ?? null); + const hashrates = await mining.$getHistoricalHashrates(req.params.interval ?? null); + const difficulty = await mining.$getHistoricalDifficulty(req.params.interval ?? null); + const oldestIndexedBlockTimestamp = await BlocksRepository.$oldestBlockTimestamp(); + console.log(oldestIndexedBlockTimestamp); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); - res.json(stats); + res.json({ + oldestIndexedBlockTimestamp: oldestIndexedBlockTimestamp, + hashrates: hashrates, + difficulty: difficulty, + }); } catch (e) { res.status(500).send(e instanceof Error ? e.message : e); } diff --git a/frontend/src/app/components/difficulty-chart/difficulty-chart.component.ts b/frontend/src/app/components/difficulty-chart/difficulty-chart.component.ts index 4bbc9520a..193805d7a 100644 --- a/frontend/src/app/components/difficulty-chart/difficulty-chart.component.ts +++ b/frontend/src/app/components/difficulty-chart/difficulty-chart.component.ts @@ -59,7 +59,7 @@ export class DifficultyChartComponent implements OnInit { }), map(data => { const availableTimespanDay = ( - (new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp / 1000) + (new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp) ) / 3600 / 24; const tableData = []; 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 9a202b69a..a41e19b0a 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -23,7 +23,7 @@ import { selectPowerOfTen } from 'src/app/bitcoin.utils'; }) export class HashrateChartComponent implements OnInit { @Input() widget: boolean = false; - @Input() right: number | string = 10; + @Input() right: number | string = 45; @Input() left: number | string = 75; radioGroupForm: FormGroup; @@ -45,7 +45,7 @@ export class HashrateChartComponent implements OnInit { private apiService: ApiService, private formBuilder: FormBuilder, ) { - this.seoService.setTitle($localize`:@@mining.hashrate:hashrate`); + this.seoService.setTitle($localize`:@@mining.hashrate-difficulty:Hashrate and Difficulty`); this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' }); this.radioGroupForm.controls.dateSpan.setValue('1y'); } @@ -57,17 +57,54 @@ export class HashrateChartComponent implements OnInit { switchMap((timespan) => { return this.apiService.getHistoricalHashrate$(timespan) .pipe( - tap(data => { - this.prepareChartOptions(data.hashrates.map(val => [val.timestamp * 1000, val.avgHashrate])); + map((data: any) => { + const diffFixed = []; + diffFixed.push({ + timestamp: data.hashrates[0].timestamp, + difficulty: data.difficulty[0].difficulty + }); + + let diffIndex = 1; + let hashIndex = 0; + + while (hashIndex < data.hashrates.length) { + if (diffIndex >= data.difficulty.length) { + while (hashIndex < data.hashrates.length) { + diffFixed.push({ + timestamp: data.hashrates[hashIndex].timestamp, + difficulty: data.difficulty[data.difficulty.length - 1].difficulty + }); + ++hashIndex; + } + break; + } + + while (data.hashrates[hashIndex].timestamp < data.difficulty[diffIndex].timestamp) { + diffFixed.push({ + timestamp: data.hashrates[hashIndex].timestamp, + difficulty: data.difficulty[diffIndex - 1].difficulty + }); + ++hashIndex; + } + ++diffIndex; + } + + data.difficulty = diffFixed; + return data; + }), + tap((data: any) => { + this.prepareChartOptions({ + hashrates: data.hashrates.map(val => [val.timestamp * 1000, val.avgHashrate]), + difficulty: data.difficulty.map(val => [val.timestamp * 1000, val.difficulty]) + }); this.isLoading = false; }), - map(data => { + map((data: any) => { const availableTimespanDay = ( - (new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp / 1000) + (new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp) ) / 3600 / 24; return { availableTimespanDay: availableTimespanDay, - data: data.hashrates }; }), ); @@ -78,27 +115,25 @@ export class HashrateChartComponent implements OnInit { prepareChartOptions(data) { this.chartOptions = { - 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' } - ]), + 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' } + ]), + '#D81B60', + ], grid: { right: this.right, left: this.left, }, - title: { - text: this.widget ? '' : $localize`:@@mining.hashrate:Hashrate`, - left: 'center', - textStyle: { - color: '#FFF', - }, - }, tooltip: { - show: true, trigger: 'axis', + axisPointer: { + type: 'line' + }, backgroundColor: 'rgba(17, 19, 31, 1)', borderRadius: 4, shadowColor: 'rgba(0, 0, 0, 0.5)', @@ -106,44 +141,91 @@ export class HashrateChartComponent implements OnInit { color: '#b1b1b1', }, borderColor: '#000', - formatter: params => { - return `${params[0].axisValueLabel}
- ${params[0].marker} ${formatNumber(params[0].value[1], this.locale, '1.0-0')} H/s` - } - }, - axisPointer: { - type: 'line', }, xAxis: { type: 'time', splitNumber: this.isMobile() ? 5 : 10, }, - yAxis: { - type: 'value', - axisLabel: { - formatter: (val) => { - const selectedPowerOfTen: any = selectPowerOfTen(val); - const newVal = val / selectedPowerOfTen.divider; - return `${newVal} ${selectedPowerOfTen.unit}H/s` + legend: { + data: [ + { + name: 'Hashrate', + inactiveColor: 'rgb(110, 112, 121)', + textStyle: { + color: 'white', + }, + icon: 'roundRect', + itemStyle: { + color: '#FFB300', + }, + }, + { + name: 'Difficulty', + inactiveColor: 'rgb(110, 112, 121)', + textStyle: { + color: 'white', + }, + icon: 'roundRect', + itemStyle: { + color: '#D81B60', + } + }, + ], + }, + yAxis: [ + { + type: 'value', + name: 'Hashrate', + axisLabel: { + color: 'rgb(110, 112, 121)', + formatter: (val) => { + const selectedPowerOfTen: any = selectPowerOfTen(val); + const newVal = val / selectedPowerOfTen.divider; + return `${newVal} ${selectedPowerOfTen.unit}H/s` + } + }, + splitLine: { + show: false, } }, - splitLine: { + { + type: 'value', + name: 'Difficulty', + position: 'right', + axisLabel: { + color: 'rgb(110, 112, 121)', + formatter: (val) => { + const selectedPowerOfTen: any = selectPowerOfTen(val); + const newVal = val / selectedPowerOfTen.divider; + return `${newVal} ${selectedPowerOfTen.unit}` + } + }, + splitLine: { + show: false, + } + } + ], + series: [ + { + name: 'Hashrate', + showSymbol: false, + data: data.hashrates, + type: 'line', lineStyle: { - type: 'dotted', - color: '#ffffff66', - opacity: 0.25, + width: 2, + }, + }, + { + yAxisIndex: 1, + name: 'Difficulty', + showSymbol: false, + data: data.difficulty, + type: 'line', + lineStyle: { + width: 3, } - }, - }, - series: { - showSymbol: false, - data: data, - type: 'line', - smooth: false, - lineStyle: { - width: 2, - }, - }, + } + ], dataZoom: this.widget ? null : [{ type: 'inside', realtime: true, diff --git a/frontend/src/app/services/mining.service.ts b/frontend/src/app/services/mining.service.ts index 20056354b..c216515b0 100644 --- a/frontend/src/app/services/mining.service.ts +++ b/frontend/src/app/services/mining.service.ts @@ -82,7 +82,7 @@ export class MiningService { }); const availableTimespanDay = ( - (new Date().getTime() / 1000) - (stats.oldestIndexedBlockTimestamp / 1000) + (new Date().getTime() / 1000) - (stats.oldestIndexedBlockTimestamp) ) / 3600 / 24; return {