From 83a382a0cbb0371eda202ba14ccb348513c08197 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 22 Feb 2022 15:50:14 +0900 Subject: [PATCH 01/10] Merge hashrate and difficulty into one chart --- backend/src/api/mining.ts | 20 +- backend/src/repositories/BlocksRepository.ts | 3 +- backend/src/routes.ts | 11 +- .../difficulty-chart.component.ts | 2 +- .../hashrate-chart.component.ts | 184 +++++++++++++----- frontend/src/app/services/mining.service.ts | 2 +- 6 files changed, 148 insertions(+), 74 deletions(-) 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 { From cfbf863a44c64e8ccc87895ca4db50791959e283 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 22 Feb 2022 16:06:27 +0900 Subject: [PATCH 02/10] Move difficulty adjustment table in the merged hashrate component --- .../hashrate-chart.component.html | 24 ++++++++++++++ .../hashrate-chart.component.ts | 31 +++++++++++-------- 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html index 263df95b2..509e15574 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html @@ -30,4 +30,28 @@
+
+ + + + + + + + + + + + + + + + + + + + +
BlockTimestampAdjustedDifficultyChange
{{ diffChange.height }}‎{{ diffChange.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}{{ formatNumber(diffChange.difficulty, locale, '1.2-2') }}{{ diffChange.difficultyShorten }}{{ formatNumber(diffChange.change, locale, '1.2-2') }}%
+
+ 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 a41e19b0a..e478c5c17 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -57,16 +57,11 @@ export class HashrateChartComponent implements OnInit { switchMap((timespan) => { return this.apiService.getHistoricalHashrate$(timespan) .pipe( - map((data: any) => { + tap((data: any) => { + // We generate duplicated data point so the tooltip works nicely const diffFixed = []; - diffFixed.push({ - timestamp: data.hashrates[0].timestamp, - difficulty: data.difficulty[0].difficulty - }); - - let diffIndex = 1; + let diffIndex = 0; let hashIndex = 0; - while (hashIndex < data.hashrates.length) { if (diffIndex >= data.difficulty.length) { while (hashIndex < data.hashrates.length) { @@ -89,13 +84,9 @@ export class HashrateChartComponent implements OnInit { ++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]) + difficulty: diffFixed.map(val => [val.timestamp * 1000, val.difficulty]) }); this.isLoading = false; }), @@ -103,8 +94,22 @@ export class HashrateChartComponent implements OnInit { const availableTimespanDay = ( (new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp) ) / 3600 / 24; + + const tableData = []; + for (let i = data.difficulty.length - 1; i > 0; --i) { + const selectedPowerOfTen: any = selectPowerOfTen(data.difficulty[i].difficulty); + const change = (data.difficulty[i].difficulty / data.difficulty[i - 1].difficulty - 1) * 100; + + tableData.push(Object.assign(data.difficulty[i], { + change: change, + difficultyShorten: formatNumber( + data.difficulty[i].difficulty / selectedPowerOfTen.divider, + this.locale, '1.2-2') + selectedPowerOfTen.unit + })); + } return { availableTimespanDay: availableTimespanDay, + difficulty: tableData }; }), ); From dcd84680fc1114d862dd7657218e16b6b17a6d1d Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 22 Feb 2022 16:13:09 +0900 Subject: [PATCH 03/10] Remove difficulty component --- frontend/src/app/app-routing.module.ts | 13 -- frontend/src/app/app.module.ts | 2 - .../difficulty-chart.component.html | 53 ----- .../difficulty-chart.component.scss | 27 --- .../difficulty-chart.component.ts | 183 ------------------ .../mining-dashboard.component.html | 12 -- 6 files changed, 290 deletions(-) delete mode 100644 frontend/src/app/components/difficulty-chart/difficulty-chart.component.html delete mode 100644 frontend/src/app/components/difficulty-chart/difficulty-chart.component.scss delete mode 100644 frontend/src/app/components/difficulty-chart/difficulty-chart.component.ts diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index c8a1d98e6..bd7d2d516 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -28,7 +28,6 @@ import { AssetsFeaturedComponent } from './components/assets/assets-featured/ass import { AssetsComponent } from './components/assets/assets.component'; import { PoolComponent } from './components/pool/pool.component'; import { MiningDashboardComponent } from './components/mining-dashboard/mining-dashboard.component'; -import { DifficultyChartComponent } from './components/difficulty-chart/difficulty-chart.component'; import { HashrateChartComponent } from './components/hashrate-chart/hashrate-chart.component'; import { MiningStartComponent } from './components/mining-start/mining-start.component'; @@ -75,10 +74,6 @@ let routes: Routes = [ path: 'mining', component: MiningStartComponent, children: [ - { - path: 'difficulty', - component: DifficultyChartComponent, - }, { path: 'hashrate', component: HashrateChartComponent, @@ -194,10 +189,6 @@ let routes: Routes = [ path: 'mining', component: MiningStartComponent, children: [ - { - path: 'difficulty', - component: DifficultyChartComponent, - }, { path: 'hashrate', component: HashrateChartComponent, @@ -307,10 +298,6 @@ let routes: Routes = [ path: 'mining', component: MiningStartComponent, children: [ - { - path: 'difficulty', - component: DifficultyChartComponent, - }, { path: 'hashrate', component: HashrateChartComponent, diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index e7fe30c1d..c161e24dc 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -70,7 +70,6 @@ import { AssetsFeaturedComponent } from './components/assets/assets-featured/ass import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component'; import { AssetCirculationComponent } from './components/asset-circulation/asset-circulation.component'; import { MiningDashboardComponent } from './components/mining-dashboard/mining-dashboard.component'; -import { DifficultyChartComponent } from './components/difficulty-chart/difficulty-chart.component'; import { HashrateChartComponent } from './components/hashrate-chart/hashrate-chart.component'; import { MiningStartComponent } from './components/mining-start/mining-start.component'; import { AmountShortenerPipe } from './shared/pipes/amount-shortener.pipe'; @@ -126,7 +125,6 @@ import { AmountShortenerPipe } from './shared/pipes/amount-shortener.pipe'; AssetGroupComponent, AssetCirculationComponent, MiningDashboardComponent, - DifficultyChartComponent, HashrateChartComponent, MiningStartComponent, AmountShortenerPipe, diff --git a/frontend/src/app/components/difficulty-chart/difficulty-chart.component.html b/frontend/src/app/components/difficulty-chart/difficulty-chart.component.html deleted file mode 100644 index eb34d4075..000000000 --- a/frontend/src/app/components/difficulty-chart/difficulty-chart.component.html +++ /dev/null @@ -1,53 +0,0 @@ -
- -
-
-
- - - - - - -
-
-
- -
-
-
-
- - - - - - - - - - - - - - - - - - - -
BlockTimestampDifficultyChange
{{ diffChange.height }}‎{{ diffChange.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}{{ formatNumber(diffChange.difficulty, locale, '1.2-2') }}{{ diffChange.difficultyShorten }}{{ formatNumber(diffChange.change, locale, '1.2-2') }}%
- -
diff --git a/frontend/src/app/components/difficulty-chart/difficulty-chart.component.scss b/frontend/src/app/components/difficulty-chart/difficulty-chart.component.scss deleted file mode 100644 index 4205c9db7..000000000 --- a/frontend/src/app/components/difficulty-chart/difficulty-chart.component.scss +++ /dev/null @@ -1,27 +0,0 @@ -.main-title { - position: relative; - color: #ffffff91; - margin-top: -13px; - font-size: 10px; - text-transform: uppercase; - font-weight: 500; - text-align: center; - padding-bottom: 3px; -} - -.formRadioGroup { - margin-top: 6px; - display: flex; - flex-direction: column; - @media (min-width: 830px) { - flex-direction: row; - float: right; - margin-top: 0px; - } - .btn-sm { - font-size: 9px; - @media (min-width: 830px) { - font-size: 14px; - } - } -} diff --git a/frontend/src/app/components/difficulty-chart/difficulty-chart.component.ts b/frontend/src/app/components/difficulty-chart/difficulty-chart.component.ts deleted file mode 100644 index 193805d7a..000000000 --- a/frontend/src/app/components/difficulty-chart/difficulty-chart.component.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core'; -import { EChartsOption, graphic } from 'echarts'; -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 { FormBuilder, FormGroup } from '@angular/forms'; -import { selectPowerOfTen } from 'src/app/bitcoin.utils'; - -@Component({ - selector: 'app-difficulty-chart', - templateUrl: './difficulty-chart.component.html', - styleUrls: ['./difficulty-chart.component.scss'], - styles: [` - .loadingGraphs { - position: absolute; - top: 38%; - left: calc(50% - 15px); - z-index: 100; - } - `], -}) -export class DifficultyChartComponent implements OnInit { - @Input() widget: boolean = false; - - radioGroupForm: FormGroup; - - chartOptions: EChartsOption = {}; - chartInitOptions = { - renderer: 'svg' - }; - - difficultyObservable$: Observable; - isLoading = true; - formatNumber = formatNumber; - - constructor( - @Inject(LOCALE_ID) public locale: string, - private seoService: SeoService, - private apiService: ApiService, - private formBuilder: FormBuilder, - ) { - this.seoService.setTitle($localize`:@@mining.difficulty:Difficulty`); - this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' }); - this.radioGroupForm.controls.dateSpan.setValue('1y'); - } - - ngOnInit(): void { - this.difficultyObservable$ = this.radioGroupForm.get('dateSpan').valueChanges - .pipe( - startWith('1y'), - switchMap((timespan) => { - return this.apiService.getHistoricalDifficulty$(timespan) - .pipe( - tap(data => { - this.prepareChartOptions(data.adjustments.map(val => [val.timestamp * 1000, val.difficulty])); - this.isLoading = false; - }), - map(data => { - const availableTimespanDay = ( - (new Date().getTime() / 1000) - (data.oldestIndexedBlockTimestamp) - ) / 3600 / 24; - - const tableData = []; - for (let i = data.adjustments.length - 1; i > 0; --i) { - const selectedPowerOfTen: any = selectPowerOfTen(data.adjustments[i].difficulty); - const change = (data.adjustments[i].difficulty / data.adjustments[i - 1].difficulty - 1) * 100; - - tableData.push(Object.assign(data.adjustments[i], { - change: change, - difficultyShorten: formatNumber( - data.adjustments[i].difficulty / selectedPowerOfTen.divider, - this.locale, '1.2-2') + selectedPowerOfTen.unit - })); - } - return { - availableTimespanDay: availableTimespanDay, - data: tableData - }; - }), - ); - }), - share() - ); - } - - prepareChartOptions(data) { - this.chartOptions = { - color: new graphic.LinearGradient(0, 0, 0, 0.65, [ - { offset: 0, color: '#D81B60' }, - { offset: 0.25, color: '#8E24AA' }, - { offset: 0.5, color: '#5E35B1' }, - { offset: 0.75, color: '#3949AB' }, - { offset: 1, color: '#1E88E5' } - ]), - title: { - text: this.widget? '' : $localize`:@@mining.difficulty:Difficulty`, - left: 'center', - textStyle: { - color: '#FFF', - }, - }, - tooltip: { - show: true, - trigger: 'axis', - backgroundColor: 'rgba(17, 19, 31, 1)', - borderRadius: 4, - shadowColor: 'rgba(0, 0, 0, 0.5)', - textStyle: { - color: '#b1b1b1', - }, - borderColor: '#000', - formatter: params => { - return `${params[0].axisValueLabel}
- ${params[0].marker} ${formatNumber(params[0].value[1], this.locale, '1.0-0')}` - } - }, - axisPointer: { - type: 'line', - }, - xAxis: { - type: 'time', - splitNumber: this.isMobile() ? 5 : 10, - }, - yAxis: { - type: 'value', - axisLabel: { - formatter: (val) => { - const selectedPowerOfTen: any = selectPowerOfTen(val); - const diff = val / selectedPowerOfTen.divider; - return `${diff} ${selectedPowerOfTen.unit}`; - } - }, - splitLine: { - lineStyle: { - type: 'dotted', - color: '#ffffff66', - opacity: 0.25, - } - } - }, - series: { - showSymbol: false, - data: data, - type: 'line', - smooth: false, - lineStyle: { - width: 2, - }, - }, - dataZoom: this.widget ? null : [{ - type: 'inside', - realtime: true, - zoomLock: true, - zoomOnMouseWheel: true, - moveOnMouseMove: true, - maxSpan: 100, - minSpan: 10, - }, { - showDetail: false, - show: true, - type: 'slider', - brushSelect: false, - realtime: true, - bottom: 0, - selectedDataBackground: { - lineStyle: { - color: '#fff', - opacity: 0.45, - }, - areaStyle: { - opacity: 0, - } - }, - }], - }; - } - - isMobile() { - return (window.innerWidth <= 767.98); - } -} diff --git a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html index 43c5c378c..78c8760bf 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html @@ -26,17 +26,5 @@ - -
-
-
-
Difficulty (1y)
- - -
-
-
- \ No newline at end of file From 3f0bf8172660b5f4d74998210ed9c0f41bacc0b9 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 22 Feb 2022 20:15:15 +0900 Subject: [PATCH 04/10] Improve hashrate chart and mining dashboard design --- .../hashrate-chart.component.html | 7 ++-- .../hashrate-chart.component.scss | 5 +++ .../hashrate-chart.component.ts | 34 ++++++++++++++++--- .../mining-dashboard.component.html | 18 ++++++---- .../mining-dashboard.component.scss | 21 +++++------- .../pool-ranking/pool-ranking.component.html | 5 +-- .../pool-ranking/pool-ranking.component.scss | 12 ++++--- .../pool-ranking/pool-ranking.component.ts | 16 ++++++--- 8 files changed, 81 insertions(+), 37 deletions(-) diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html index 509e15574..745fe491f 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html @@ -25,13 +25,14 @@ -
+
-
- +
+
diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.scss b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.scss index 62eac44f5..316f0fc47 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.scss +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.scss @@ -26,6 +26,11 @@ padding-bottom: 20px; padding-right: 20px; } +.chart-widget { + width: 100%; + height: 100%; + max-height: 275px; +} .formRadioGroup { margin-top: 6px; 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 e478c5c17..fde49b9b4 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -69,7 +69,7 @@ export class HashrateChartComponent implements OnInit { timestamp: data.hashrates[hashIndex].timestamp, difficulty: data.difficulty[data.difficulty.length - 1].difficulty }); - ++hashIndex; + ++hashIndex; } break; } @@ -121,7 +121,7 @@ export class HashrateChartComponent implements OnInit { prepareChartOptions(data) { this.chartOptions = { color: [ - new graphic.LinearGradient(0, 0, 0, 0.65, [ + new graphic.LinearGradient(0, 0, 0, 0.65, [ { offset: 0, color: '#F4511E' }, { offset: 0.25, color: '#FB8C00' }, { offset: 0.5, color: '#FFB300' }, @@ -133,6 +133,7 @@ export class HashrateChartComponent implements OnInit { grid: { right: this.right, left: this.left, + bottom: 30, }, tooltip: { trigger: 'axis', @@ -146,6 +147,25 @@ export class HashrateChartComponent implements OnInit { color: '#b1b1b1', }, borderColor: '#000', + formatter: function (data) { + let hashratePowerOfTen: any = selectPowerOfTen(1); + let hashrate = data[0].data[1]; + let difficultyPowerOfTen = hashratePowerOfTen; + let difficulty = data[1].data[1]; + + if (this.isMobile()) { + hashratePowerOfTen = selectPowerOfTen(data[0].data[1]); + hashrate = Math.round(data[0].data[1] / hashratePowerOfTen.divider); + difficultyPowerOfTen = selectPowerOfTen(data[1].data[1]); + difficulty = Math.round(data[1].data[1] / difficultyPowerOfTen.divider); + } + + return ` + ${data[0].axisValueLabel}
+ ${data[0].marker} ${data[0].seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s
+ ${data[1].marker} ${data[1].seriesName}: ${formatNumber(difficulty, this.locale, '1.0-0')} ${difficultyPowerOfTen.unit} + `; + }.bind(this) }, xAxis: { type: 'time', @@ -179,13 +199,16 @@ export class HashrateChartComponent implements OnInit { }, yAxis: [ { + min: function (value) { + return value.min * 0.9; + }, type: 'value', name: 'Hashrate', axisLabel: { color: 'rgb(110, 112, 121)', formatter: (val) => { const selectedPowerOfTen: any = selectPowerOfTen(val); - const newVal = val / selectedPowerOfTen.divider; + const newVal = Math.round(val / selectedPowerOfTen.divider); return `${newVal} ${selectedPowerOfTen.unit}H/s` } }, @@ -194,6 +217,9 @@ export class HashrateChartComponent implements OnInit { } }, { + min: function (value) { + return value.min * 0.9; + }, type: 'value', name: 'Difficulty', position: 'right', @@ -201,7 +227,7 @@ export class HashrateChartComponent implements OnInit { color: 'rgb(110, 112, 121)', formatter: (val) => { const selectedPowerOfTen: any = selectPowerOfTen(val); - const newVal = val / selectedPowerOfTen.divider; + const newVal = Math.round(val / selectedPowerOfTen.divider); return `${newVal} ${selectedPowerOfTen.unit}` } }, diff --git a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html index 78c8760bf..aa7affc1e 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html @@ -5,11 +5,13 @@
-
-
Mining Pools Share (1w)
+
@@ -18,10 +20,12 @@
-
Hashrate (1y)
+
+ + Hashrate (1y) + +
-
diff --git a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.scss b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.scss index c2fb37e8c..e575a405b 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.scss +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.scss @@ -16,22 +16,17 @@ } .card-title { - color: #4a68b9; font-size: 1rem; } +.card-title > a { + color: #4a68b9; +} -.card-wrapper { - .card { - height: auto !important; - } - .card-body { - display: flex; - flex: inherit; - text-align: center; - flex-direction: column; - justify-content: space-around; - padding: 22px 20px; - } +.card-body { + padding: 1.25rem 1rem 0.75rem 1rem; +} +.card-body.pool-ranking { + padding: 1.25rem 0.25rem 0.75rem 0.25rem; } #blockchain-container { diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.html b/frontend/src/app/components/pool-ranking/pool-ranking.component.html index a687e41c7..5e6068866 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.html +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.html @@ -1,11 +1,12 @@
-
+
-
+
- +
diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html index 2eff96bca..112b35df2 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html @@ -13,7 +13,7 @@ {{ projectedBlock.feeRange[0] | number:feeRounding }} - {{ projectedBlock.feeRange[projectedBlock.feeRange.length - 1] | number:feeRounding }} sat/vB
- +
diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts index 6fe2a155c..05735c0be 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts @@ -34,7 +34,6 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { network = ''; now = new Date().getTime(); showMiningInfo = false; - blockSubsidy = 50; blockWidth = 125; blockPadding = 30; @@ -111,7 +110,6 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { if (this.stateService.network === '') { block.blink = specialBlocks[block.height] ? true : false; } - this.setBlockSubsidy(block.height); }); const stringifiedBlocks = JSON.stringify(mempoolBlocks); @@ -212,18 +210,6 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy { return block.index; } - setBlockSubsidy(blockHeight) { - if (!['', 'testnet', 'signet'].includes(this.stateService.network)) { - return; - } - this.blockSubsidy = 50; - let halvenings = Math.floor(blockHeight / 210000); - while (halvenings > 0) { - this.blockSubsidy = this.blockSubsidy / 2; - halvenings--; - } - } - reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] { const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2; const blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding))); From 78c0fe0e04eabca843277cb24e9e4be9f0297662 Mon Sep 17 00:00:00 2001 From: softsimon Date: Tue, 22 Feb 2022 16:32:54 +0400 Subject: [PATCH 07/10] Handle missing asset registry assets fixes #1246 --- .../transactions-list/transactions-list.component.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html index 8e81cc3e7..87565bc17 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -172,9 +172,12 @@
Block -
+
+ + {{ vout.value }} {{ vout.asset | slice : 0 : 7 }} + From 8aa1fe48dcfee12099512b12d546ec0305192e06 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 22 Feb 2022 22:04:52 +0900 Subject: [PATCH 08/10] Remove debug console.log --- backend/src/routes.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/routes.ts b/backend/src/routes.ts index e38da16de..7fba2e66b 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -591,7 +591,6 @@ class Routes { 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()); From 807ef2288aac7541c745f827f44f5e86a3f9a9be Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 22 Feb 2022 22:53:47 +0900 Subject: [PATCH 09/10] Don't assume two difficulty with the same value is impossible --- backend/src/api/mining.ts | 13 +++++++- backend/src/repositories/BlocksRepository.ts | 30 ++++++++++++++++--- .../hashrate-chart.component.ts | 10 ++++--- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index e7876bd60..9c3689011 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -161,7 +161,18 @@ class Mining { ++totalIndexed; } - await HashratesRepository.$saveHashrates(hashrates); + // Add genesis block manually + if (!indexedTimestamp.includes(genesisTimestamp)) { + hashrates.push({ + hashrateTimestamp: genesisTimestamp, + avgHashrate: await bitcoinClient.getNetworkHashPs(1, 1), + poolId: null + }); + } + + if (hashrates.length > 0) { + await HashratesRepository.$saveHashrates(hashrates); + } await HashratesRepository.$setLatestRunTimestamp(); this.hashrateIndexingStarted = false; diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index e8bfb4a62..a2b0b2f95 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -265,15 +265,37 @@ class BlocksRepository { const connection = await DB.pool.getConnection(); - let query = `SELECT MIN(UNIX_TIMESTAMP(blockTimestamp)) as timestamp, difficulty, height - FROM blocks`; + // :D ... Yeah don't ask me about this one https://stackoverflow.com/a/40303162 + // Basically, using temporary user defined fields, we are able to extract all + // difficulty adjustments from the blocks tables. + // This allow use to avoid indexing it in another table. + let query = ` + SELECT + * + FROM + ( + SELECT + UNIX_TIMESTAMP(blockTimestamp) as timestamp, difficulty, height, + IF(@prevStatus = YT.difficulty, @rn := @rn + 1, + IF(@prevStatus := YT.difficulty, @rn := 1, @rn := 1) + ) AS rn + FROM blocks YT + CROSS JOIN + ( + SELECT @prevStatus := -1, @rn := 1 + ) AS var + `; if (interval) { query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; } - query += ` GROUP BY difficulty - ORDER BY blockTimestamp`; + query += ` + ORDER BY YT.height + ) AS t + WHERE t.rn = 1 + ORDER BY t.height + `; const [rows]: any[] = await connection.query(query); connection.release(); 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 ba1e207a0..f6995056e 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -60,7 +60,7 @@ export class HashrateChartComponent implements OnInit { tap((data: any) => { // We generate duplicated data point so the tooltip works nicely const diffFixed = []; - let diffIndex = 0; + let diffIndex = 1; let hashIndex = 0; while (hashIndex < data.hashrates.length) { if (diffIndex >= data.difficulty.length) { @@ -74,7 +74,9 @@ export class HashrateChartComponent implements OnInit { break; } - while (data.hashrates[hashIndex].timestamp < data.difficulty[diffIndex].timestamp) { + while (hashIndex < data.hashrates.length && diffIndex < data.difficulty.length && + data.hashrates[hashIndex].timestamp <= data.difficulty[diffIndex].timestamp + ) { diffFixed.push({ timestamp: data.hashrates[hashIndex].timestamp, difficulty: data.difficulty[diffIndex - 1].difficulty @@ -133,7 +135,7 @@ export class HashrateChartComponent implements OnInit { grid: { right: this.right, left: this.left, - bottom: 30, + bottom: this.widget ? 30 : 60, }, tooltip: { trigger: 'axis', @@ -164,7 +166,7 @@ export class HashrateChartComponent implements OnInit { return ` ${data[0].axisValueLabel}
${data[0].marker} ${data[0].seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s
- ${data[1].marker} ${data[1].seriesName}: ${formatNumber(difficulty, this.locale, '1.0-0')} ${difficultyPowerOfTen.unit} + ${data[1].marker} ${data[1].seriesName}: ${formatNumber(difficulty, this.locale, '1.2-2')} ${difficultyPowerOfTen.unit} `; }.bind(this) }, From c1092adfd9666580d0ef292c5e1a03d01ec043a3 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 22 Feb 2022 23:57:54 +0900 Subject: [PATCH 10/10] Add blocks.extras.totalFees and show it in blockchain blocks component --- backend/src/api/blocks.ts | 3 +++ backend/src/mempool.interfaces.ts | 1 + .../blockchain-blocks/blockchain-blocks.component.html | 2 +- frontend/src/app/interfaces/node-api.interface.ts | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index de461e095..af25b957b 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -116,6 +116,9 @@ class Blocks { Common.median(transactionsTmp.map((tx) => tx.effectiveFeePerVsize)) : 0; blockExtended.extras.feeRange = transactionsTmp.length > 0 ? Common.getFeesInRange(transactionsTmp, 8) : [0, 0]; + blockExtended.extras.totalFees = transactionsTmp.reduce((acc, tx) => { + return acc + tx.fee; + }, 0) if (Common.indexingEnabled()) { let pool: PoolTag; diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 4869561c2..810398cab 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -78,6 +78,7 @@ export interface TransactionStripped { } export interface BlockExtension { + totalFees?: number; medianFee?: number; feeRange?: number[]; reward?: number; diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html index 8ddbd579b..bc0025d2b 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -14,7 +14,7 @@ {{ block?.extras?.feeRange[1] | number:feeRounding }} - {{ block?.extras?.feeRange[block?.extras?.feeRange.length - 1] | number:feeRounding }} sat/vB
- +
diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index 472df0088..d8760d1f0 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -101,6 +101,7 @@ export interface PoolStat { } export interface BlockExtension { + totalFees?: number; medianFee?: number; feeRange?: number[]; reward?: number;