From 7270b1ccacdcbdfbbdaebff914d6d15331b2c965 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 16 Feb 2022 21:20:28 +0900 Subject: [PATCH] Create difficulty chart component --- backend/src/api/blocks.ts | 10 +- backend/src/index.ts | 9 +- backend/src/repositories/BlocksRepository.ts | 24 +++++ backend/src/routes.ts | 12 +++ frontend/src/app/app-routing.module.ts | 13 +++ frontend/src/app/app.module.ts | 2 + .../difficulty-chart.component.html | 8 ++ .../difficulty-chart.component.scss | 0 .../difficulty-chart.component.ts | 102 ++++++++++++++++++ .../pool-ranking/pool-ranking.component.ts | 7 +- frontend/src/app/services/api.service.ts | 21 +++- 11 files changed, 192 insertions(+), 16 deletions(-) create mode 100644 frontend/src/app/components/difficulty-chart/difficulty-chart.component.html create mode 100644 frontend/src/app/components/difficulty-chart/difficulty-chart.component.scss create mode 100644 frontend/src/app/components/difficulty-chart/difficulty-chart.component.ts diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 7513f259e..c406ae803 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -199,6 +199,7 @@ class Blocks { const chunkSize = 10000; let totaIndexed = 0; + let indexedThisRun = 0; while (currentBlockHeight >= lastBlockToIndex) { const endBlock = Math.max(0, lastBlockToIndex, currentBlockHeight - chunkSize + 1); @@ -207,9 +208,11 @@ class Blocks { if (missingBlockHeights.length <= 0) { logger.debug(`No missing blocks between #${currentBlockHeight} to #${endBlock}`); currentBlockHeight -= chunkSize; + totaIndexed += chunkSize; continue; } + totaIndexed += chunkSize - missingBlockHeights.length; logger.debug(`Indexing ${missingBlockHeights.length} blocks from #${currentBlockHeight} to #${endBlock}`); for (const blockHeight of missingBlockHeights) { @@ -219,8 +222,10 @@ class Blocks { try { if (totaIndexed % 100 === 0 || blockHeight === lastBlockToIndex) { const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt)); - const blockPerSeconds = Math.round(totaIndexed / elapsedSeconds); - logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds} blocks/sec | total: ${totaIndexed} | elapsed: ${elapsedSeconds} seconds`); + const blockPerSeconds = Math.max(1, Math.round(indexedThisRun / elapsedSeconds)); + const progress = Math.round(totaIndexed / indexingBlockAmount * 100); + const timeLeft = Math.round((indexingBlockAmount - totaIndexed) / blockPerSeconds); + logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds} blocks/sec | total: ${totaIndexed}/${indexingBlockAmount} (${progress}%) | elapsed: ${elapsedSeconds} seconds | left: ~${timeLeft} seconds`); } const blockHash = await bitcoinApi.$getBlockHash(blockHeight); const block = await bitcoinApi.$getBlock(blockHash); @@ -228,6 +233,7 @@ class Blocks { const blockExtended = await this.$getBlockExtended(block, transactions); await blocksRepository.$saveBlockInDatabase(blockExtended); ++totaIndexed; + ++indexedThisRun; } catch (e) { logger.err(`Something went wrong while indexing blocks.` + e); } diff --git a/backend/src/index.ts b/backend/src/index.ts index 1f8575294..23c70f59d 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -259,10 +259,7 @@ class Server { ; } - const indexingAvailable = - ['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) && - config.DATABASE.ENABLED === true; - if (indexingAvailable) { + if (Common.indexingEnabled()) { this.app .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/24h', routes.$getPools.bind(routes, '24h')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3d', routes.$getPools.bind(routes, '3d')) @@ -277,7 +274,9 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks', routes.$getPoolBlocks) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/blocks/:height', routes.$getPoolBlocks) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId', routes.$getPool) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/:interval', routes.$getPool); + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/:interval', routes.$getPool) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty', routes.$getHistoricalDifficulty) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty/:interval', routes.$getHistoricalDifficulty); } if (config.BISQ.ENABLED) { diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index adc3a1f31..d57bc8eb0 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -223,6 +223,30 @@ class BlocksRepository { return rows[0]; } + + /** + * Return blocks difficulty + */ + public async $getBlocksDifficulty(interval: string | null): Promise { + interval = Common.getSqlInterval(interval); + + const connection = await DB.pool.getConnection(); + + let query = `SELECT MIN(blockTimestamp) as timestamp, difficulty + FROM blocks`; + + if (interval) { + query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; + } + + query += ` GROUP BY difficulty + ORDER BY blockTimestamp DESC`; + + const [rows]: any[] = await connection.query(query); + connection.release(); + + return rows; + } } export default new BlocksRepository(); diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 66ecebd31..2159c0721 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -575,6 +575,18 @@ class Routes { } } + public async $getHistoricalDifficulty(req: Request, res: Response) { + try { + const stats = await BlocksRepository.$getBlocksDifficulty(req.params.interval ?? null); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); + res.json(stats); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + public async getBlock(req: Request, res: Response) { try { const result = await bitcoinApi.$getBlock(req.params.hash); diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 4018ed64e..6ce39b1d2 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -27,6 +27,7 @@ import { AssetGroupComponent } from './components/assets/asset-group/asset-group import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component'; import { AssetsComponent } from './components/assets/assets.component'; import { PoolComponent } from './components/pool/pool.component'; +import { DifficultyChartComponent } from './components/difficulty-chart/difficulty-chart.component'; let routes: Routes = [ { @@ -63,6 +64,10 @@ let routes: Routes = [ path: 'blocks', component: LatestBlocksComponent, }, + { + path: 'mining/difficulty', + component: DifficultyChartComponent, + }, { path: 'mining/pools', component: PoolRankingComponent, @@ -155,6 +160,10 @@ let routes: Routes = [ path: 'blocks', component: LatestBlocksComponent, }, + { + path: 'mining/difficulty', + component: DifficultyChartComponent, + }, { path: 'mining/pools', component: PoolRankingComponent, @@ -241,6 +250,10 @@ let routes: Routes = [ path: 'blocks', component: LatestBlocksComponent, }, + { + path: 'mining/difficulty', + component: DifficultyChartComponent, + }, { path: 'mining/pools', component: PoolRankingComponent, diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 20eb2ea03..15bebd033 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -68,6 +68,7 @@ import { PushTransactionComponent } from './components/push-transaction/push-tra import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component'; import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component'; +import { DifficultyChartComponent } from './components/difficulty-chart/difficulty-chart.component'; @NgModule({ declarations: [ @@ -118,6 +119,7 @@ import { AssetGroupComponent } from './components/assets/asset-group/asset-group AssetsNavComponent, AssetsFeaturedComponent, AssetGroupComponent, + DifficultyChartComponent, ], imports: [ BrowserModule.withServerTransition({ appId: 'serverApp' }), diff --git a/frontend/src/app/components/difficulty-chart/difficulty-chart.component.html b/frontend/src/app/components/difficulty-chart/difficulty-chart.component.html new file mode 100644 index 000000000..0a9a60620 --- /dev/null +++ b/frontend/src/app/components/difficulty-chart/difficulty-chart.component.html @@ -0,0 +1,8 @@ +
+ +
+
+
+
+ +
diff --git a/frontend/src/app/components/difficulty-chart/difficulty-chart.component.scss b/frontend/src/app/components/difficulty-chart/difficulty-chart.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/components/difficulty-chart/difficulty-chart.component.ts b/frontend/src/app/components/difficulty-chart/difficulty-chart.component.ts new file mode 100644 index 000000000..635a8a6d9 --- /dev/null +++ b/frontend/src/app/components/difficulty-chart/difficulty-chart.component.ts @@ -0,0 +1,102 @@ +import { Component, OnInit } from '@angular/core'; +import { EChartsOption } from 'echarts'; +import { Observable } from 'rxjs'; +import { map, tap } from 'rxjs/operators'; +import { ApiService } from 'src/app/services/api.service'; +import { SeoService } from 'src/app/services/seo.service'; + +@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 { + chartOptions: EChartsOption = {}; + chartInitOptions = { + renderer: 'svg' + }; + + difficultyObservable$: Observable; + isLoading = true; + + constructor( + private seoService: SeoService, + private apiService: ApiService, + ) { + this.seoService.setTitle($localize`:@@mining.difficulty:Difficulty`); + } + + ngOnInit(): void { + this.difficultyObservable$ = this.apiService.getHistoricalDifficulty$(undefined) + .pipe( + map(data => { + return data.map(val => [val.timestamp, val.difficulty]) + }), + tap(data => { + this.prepareChartOptions(data); + this.isLoading = false; + }) + ) + } + + prepareChartOptions(data) { + this.chartOptions = { + title: { + text: $localize`:@@mining.difficulty:Difficulty`, + left: 'center', + textStyle: { + color: '#FFF', + }, + }, + tooltip: { + show: true, + trigger: 'axis', + }, + axisPointer: { + type: 'line', + }, + xAxis: [ + { + type: 'time', + } + ], + yAxis: { + type: 'value', + axisLabel: { + fontSize: 11, + formatter: function(val) { + const diff = val / Math.pow(10, 12); // terra + return diff.toString() + 'T'; + } + }, + splitLine: { + lineStyle: { + type: 'dotted', + color: '#ffffff66', + opacity: 0.25, + } + } + }, + series: [ + { + data: data, + type: 'line', + smooth: false, + lineStyle: { + width: 3, + }, + areaStyle: {} + }, + ], + }; + } + +} diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts index d1b64f190..9a7a33fc0 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { Router } from '@angular/router'; import { EChartsOption, PieSeriesOption } from 'echarts'; @@ -23,7 +23,7 @@ import { StateService } from '../../services/state.service'; } `], }) -export class PoolRankingComponent implements OnInit, OnDestroy { +export class PoolRankingComponent implements OnInit { poolsWindowPreference: string; radioGroupForm: FormGroup; @@ -90,9 +90,6 @@ export class PoolRankingComponent implements OnInit, OnDestroy { ); } - ngOnDestroy(): void { - } - formatPoolUI(pool: SinglePoolStats) { pool['blockText'] = pool.blockCount.toString() + ` (${pool.share}%)`; return pool; diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index bf468c467..950a99fa7 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -129,12 +129,18 @@ export class ApiService { return this.httpClient.post(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'}); } - listPools$(interval: string | null) : Observable { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pools/${interval}`); + listPools$(interval: string | undefined) : Observable { + return this.httpClient.get( + this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pools` + + (interval !== undefined ? `/${interval}` : '') + ); } - getPoolStats$(poolId: number, interval: string | null): Observable { - return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}/${interval}`); + getPoolStats$(poolId: number, interval: string | undefined): Observable { + return this.httpClient.get( + this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${poolId}` + + (interval !== undefined ? `/${interval}` : '') + ); } getPoolBlocks$(poolId: number, fromHeight: number): Observable { @@ -143,4 +149,11 @@ export class ApiService { (fromHeight !== undefined ? `/${fromHeight}` : '') ); } + + getHistoricalDifficulty$(interval: string | undefined): Observable { + return this.httpClient.get( + this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty` + + (interval !== undefined ? `/${interval}` : '') + ); + } }