From 2644f2fb0725c1e2eb97d846be8358bec4baab12 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 22 Mar 2022 12:34:29 +0900 Subject: [PATCH 1/4] Move reward stats to component - Add /api/v1/mining/reward-stats/{blockCount} --- backend/src/api/mining.ts | 9 ++- backend/src/index.ts | 1 + backend/src/mempool.interfaces.ts | 6 ++ backend/src/repositories/BlocksRepository.ts | 26 +++++++++ backend/src/routes.ts | 9 +++ frontend/src/app/app.module.ts | 2 + .../mining-dashboard.component.html | 51 +---------------- .../mining-dashboard.component.scss | 36 ------------ .../mining-dashboard.component.ts | 22 ------- .../reward-stats/reward-stats.component.html | 51 +++++++++++++++++ .../reward-stats/reward-stats.component.scss | 57 +++++++++++++++++++ .../reward-stats/reward-stats.component.ts | 29 ++++++++++ .../src/app/interfaces/node-api.interface.ts | 6 ++ frontend/src/app/services/api.service.ts | 6 +- production/nginx-cache-warmer | 1 + 15 files changed, 202 insertions(+), 110 deletions(-) create mode 100644 frontend/src/app/components/reward-stats/reward-stats.component.html create mode 100644 frontend/src/app/components/reward-stats/reward-stats.component.scss create mode 100644 frontend/src/app/components/reward-stats/reward-stats.component.ts diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index b16a406cb..8b277da57 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -1,4 +1,4 @@ -import { PoolInfo, PoolStats } from '../mempool.interfaces'; +import { PoolInfo, PoolStats, RewardStats } from '../mempool.interfaces'; import BlocksRepository from '../repositories/BlocksRepository'; import PoolsRepository from '../repositories/PoolsRepository'; import HashratesRepository from '../repositories/HashratesRepository'; @@ -70,6 +70,13 @@ class Mining { }; } + /** + * Get miner reward stats + */ + public async $getRewardStats(blockCount: number): Promise { + return await BlocksRepository.$getBlockStats(blockCount); + } + /** * [INDEXING] Generate weekly mining pool hashrate history */ diff --git a/backend/src/index.ts b/backend/src/index.ts index d4b55e078..d5bf0e59e 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -312,6 +312,7 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/pools/:interval', routes.$getPoolsHistoricalHashrate) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate', routes.$getHistoricalHashrate) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/:interval', routes.$getHistoricalHashrate) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/reward-stats/:blockCount', routes.$getRewardStats) ; } diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index b5cbb7e11..15d1ad618 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -209,3 +209,9 @@ export interface IDifficultyAdjustment { timeAvg: number; timeOffset: number; } + +export interface RewardStats { + totalReward: number; + totalFee: number; + totalTx: number; +} diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 40c705bdb..33cb727d9 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -354,6 +354,9 @@ class BlocksRepository { } } + /** + * Return oldest blocks height + */ public async $getOldestIndexedBlockHeight(): Promise { const connection = await DB.getConnection(); try { @@ -367,6 +370,29 @@ class BlocksRepository { throw e; } } + + /** + * Get general block stats + */ + public async $getBlockStats(blockCount: number): Promise { + let connection; + try { + connection = await DB.getConnection(); + + // We need to use a subquery + const query = `SELECT SUM(reward) as totalReward, SUM(fees) as totalFee, SUM(tx_count) as totalTx + FROM (SELECT reward, fees, tx_count FROM blocks ORDER by height DESC LIMIT ${blockCount}) as sub`; + + const [rows]: any = await connection.query(query); + connection.release(); + + return rows[0]; + } catch (e) { + connection.release(); + logger.err('$getBlockStats() error: ' + (e instanceof Error ? e.message : e)); + throw e; + } + } } export default new BlocksRepository(); diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 2f4cdff3a..b14ea6ac4 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -935,6 +935,15 @@ class Routes { res.status(500).end(); } } + + public async $getRewardStats(req: Request, res: Response) { + try { + const response = await mining.$getRewardStats(parseInt(req.params.blockCount)) + res.json(response); + } catch (e) { + res.status(500).end(); + } + } } export default new Routes(); diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index cb59c19dc..807c88ade 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -78,6 +78,7 @@ import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-st import { GraphsComponent } from './components/graphs/graphs.component'; import { DifficultyAdjustmentsTable } from './components/difficulty-adjustments-table/difficulty-adjustments-table.components'; import { BlocksList } from './components/blocks-list/blocks-list.component'; +import { RewardStatsComponent } from './components/reward-stats/reward-stats.component'; import { DataCyDirective } from './data-cy.directive'; @NgModule({ @@ -139,6 +140,7 @@ import { DataCyDirective } from './data-cy.directive'; DifficultyAdjustmentsTable, BlocksList, DataCyDirective, + RewardStatsComponent, ], imports: [ BrowserModule.withServerTransition({ appId: 'serverApp' }), 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 cdcd03319..659299d72 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html @@ -8,60 +8,11 @@
-
-
-
Miners Reward
-
- -
in the last 8 blocks
-
-
-
-
Reward Per Tx
-
- {{ rewardStats.rewardPerTx | amountShortener }} - sats/tx -
in the last 8 blocks
-
-
-
-
Average Fee
-
- {{ rewardStats.feePerTx | amountShortener}} - sats/tx -
in the last 8 blocks
-
-
-
+
- -
-
-
Miners Reward
-
-
-
-
-
-
-
Reward Per Tx
-
-
-
-
-
-
-
Average Fee
-
-
-
-
-
-
-
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 6d87f0a57..d744e285d 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.scss +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.scss @@ -59,42 +59,6 @@ padding-bottom: 3px; } -.reward-container { - display: flex; - flex-direction: row; - justify-content: space-around; - height: 76px; - .shared-block { - color: #ffffff66; - font-size: 12px; - } - .item { - display: table-cell; - padding: 0 5px; - width: 100%; - &:nth-child(1) { - display: none; - @media (min-width: 485px) { - display: table-cell; - } - @media (min-width: 768px) { - display: none; - } - @media (min-width: 992px) { - display: table-cell; - } - } - } - .card-text { - font-size: 22px; - margin-top: -9px; - position: relative; - } - .card-text.skeleton { - margin-top: 0px; - } -} - .more-padding { padding: 18px; } diff --git a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts index ce92ed56c..cfd1eafbd 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts @@ -14,14 +14,8 @@ import { WebsocketService } from 'src/app/services/websocket.service'; export class MiningDashboardComponent implements OnInit { private blocks = []; - public $rewardStats: Observable; - public totalReward = 0; - public rewardPerTx = '~'; - public feePerTx = '~'; - constructor( private seoService: SeoService, - public stateService: StateService, private websocketService: WebsocketService, ) { this.seoService.setTitle($localize`:@@mining.mining-dashboard:Mining Dashboard`); @@ -29,21 +23,5 @@ export class MiningDashboardComponent implements OnInit { ngOnInit(): void { this.websocketService.want(['blocks', 'mempool-blocks']); - - this.$rewardStats = this.stateService.blocks$.pipe( - map(([block]) => { - this.blocks.unshift(block); - this.blocks = this.blocks.slice(0, 8); - const totalTx = this.blocks.reduce((acc, b) => acc + b.tx_count, 0); - const totalFee = this.blocks.reduce((acc, b) => acc + b.extras?.totalFees ?? 0, 0); - const totalReward = this.blocks.reduce((acc, b) => acc + b.extras?.reward ?? 0, 0); - - return { - 'totalReward': totalReward, - 'rewardPerTx': Math.round(totalReward / totalTx), - 'feePerTx': Math.round(totalFee / totalTx), - }; - }) - ); } } diff --git a/frontend/src/app/components/reward-stats/reward-stats.component.html b/frontend/src/app/components/reward-stats/reward-stats.component.html new file mode 100644 index 000000000..3e7df438d --- /dev/null +++ b/frontend/src/app/components/reward-stats/reward-stats.component.html @@ -0,0 +1,51 @@ +
+
+
Miners Reward
+
+ +
in the past 144 blocks
+
+
+
+
Reward Per Tx
+
+ {{ rewardStats.rewardPerTx | amountShortener }} + sats/tx +
in the past 144 blocks
+
+
+
+
Average Fee
+
+ {{ rewardStats.feePerTx | amountShortener}} + sats/tx +
in the past 144 blocks
+
+
+
+ + +
+
+
Miners Reward
+
+
+
+
+
+
+
Reward Per Tx
+
+
+
+
+
+
+
Average Fee
+
+
+
+
+
+
+
diff --git a/frontend/src/app/components/reward-stats/reward-stats.component.scss b/frontend/src/app/components/reward-stats/reward-stats.component.scss new file mode 100644 index 000000000..77d57fa1c --- /dev/null +++ b/frontend/src/app/components/reward-stats/reward-stats.component.scss @@ -0,0 +1,57 @@ +.reward-container { + display: flex; + flex-direction: row; + justify-content: space-around; + height: 76px; + .shared-block { + color: #ffffff66; + font-size: 12px; + } + .item { + display: table-cell; + padding: 0 5px; + width: 100%; + &:nth-child(1) { + display: none; + @media (min-width: 485px) { + display: table-cell; + } + @media (min-width: 768px) { + display: none; + } + @media (min-width: 992px) { + display: table-cell; + } + } + } + .card-text { + font-size: 22px; + margin-top: -9px; + position: relative; + } + .card-text.skeleton { + margin-top: 0px; + } +} + +.card-text { + font-size: 22px; +} + +.card-title { + font-size: 1rem; + color: #4a68b9; +} + +.skeleton-loader { + width: 100%; + display: block; + &:first-child { + max-width: 90px; + margin: 15px auto 3px; + } + &:last-child { + margin: 10px auto 3px; + max-width: 55px; + } +} diff --git a/frontend/src/app/components/reward-stats/reward-stats.component.ts b/frontend/src/app/components/reward-stats/reward-stats.component.ts new file mode 100644 index 000000000..0300a8fe7 --- /dev/null +++ b/frontend/src/app/components/reward-stats/reward-stats.component.ts @@ -0,0 +1,29 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { ApiService } from 'src/app/services/api.service'; + +@Component({ + selector: 'app-reward-stats', + templateUrl: './reward-stats.component.html', + styleUrls: ['./reward-stats.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class RewardStatsComponent implements OnInit { + public $rewardStats: Observable; + + constructor(private apiService: ApiService) { } + + ngOnInit(): void { + this.$rewardStats = this.apiService.getRewardStats$() + .pipe( + map((res) => { + return { + totalReward: res.totalReward, + rewardPerTx: res.totalReward / res.totalTx, + feePerTx: res.totalFee / res.totalTx, + }; + }) + ); + } +} diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index c8118add7..786fd6687 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -115,3 +115,9 @@ export interface BlockExtension { export interface BlockExtended extends Block { extras?: BlockExtension; } + +export interface RewardStats { + totalReward: number; + totalFee: number; + totalTx: number; +} diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index cf510c449..92068c44e 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; -import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, PoolsStats, PoolStat, BlockExtended } from '../interfaces/node-api.interface'; +import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators, PoolsStats, PoolStat, BlockExtended, RewardStats } from '../interfaces/node-api.interface'; import { Observable } from 'rxjs'; import { StateService } from './state.service'; import { WebsocketResponse } from '../interfaces/websocket.interface'; @@ -174,4 +174,8 @@ export class ApiService { (interval !== undefined ? `/${interval}` : '') ); } + + getRewardStats$(blockCount: number = 144): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/reward-stats/${blockCount}`); + } } diff --git a/production/nginx-cache-warmer b/production/nginx-cache-warmer index 38db73215..f9b89b7fa 100755 --- a/production/nginx-cache-warmer +++ b/production/nginx-cache-warmer @@ -33,6 +33,7 @@ do for url in / \ '/api/v1/mining/hashrate/pools/2y' \ '/api/v1/mining/hashrate/pools/3y' \ '/api/v1/mining/hashrate/pools/all' \ + '/api/v1/mining/reward-stats/144' \ do curl -s "https://${hostname}${url}" >/dev/null From 8cc005cb2c958a9231661bf2f11a2539ca562673 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 22 Mar 2022 18:07:26 +0900 Subject: [PATCH 2/4] Refresh reward stats when a new block is mined --- .../reward-stats/reward-stats.component.ts | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/components/reward-stats/reward-stats.component.ts b/frontend/src/app/components/reward-stats/reward-stats.component.ts index 0300a8fe7..dd466985e 100644 --- a/frontend/src/app/components/reward-stats/reward-stats.component.ts +++ b/frontend/src/app/components/reward-stats/reward-stats.component.ts @@ -1,7 +1,8 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { map, skip, switchMap } from 'rxjs/operators'; import { ApiService } from 'src/app/services/api.service'; +import { StateService } from 'src/app/services/state.service'; @Component({ selector: 'app-reward-stats', @@ -12,17 +13,24 @@ import { ApiService } from 'src/app/services/api.service'; export class RewardStatsComponent implements OnInit { public $rewardStats: Observable; - constructor(private apiService: ApiService) { } + constructor(private apiService: ApiService, private stateService: StateService) { } ngOnInit(): void { - this.$rewardStats = this.apiService.getRewardStats$() + this.$rewardStats = this.stateService.blocks$ .pipe( - map((res) => { - return { - totalReward: res.totalReward, - rewardPerTx: res.totalReward / res.totalTx, - feePerTx: res.totalFee / res.totalTx, - }; + // (we always receives some blocks at start so only trigger for the last one) + skip(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT - 1), + switchMap(() => { + return this.apiService.getRewardStats$() + .pipe( + map((stats) => { + return { + totalReward: stats.totalReward, + rewardPerTx: stats.totalReward / stats.totalTx, + feePerTx: stats.totalFee / stats.totalTx, + }; + }) + ); }) ); } From fb7e81af57aa098c5331a9e621e1462df88e0cb5 Mon Sep 17 00:00:00 2001 From: nymkappa Date: Tue, 22 Mar 2022 18:25:46 +0900 Subject: [PATCH 3/4] Add more verbose description for stats + i18n --- .../mining-dashboard/mining-dashboard.component.html | 5 ++++- .../components/reward-stats/reward-stats.component.html | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) 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 659299d72..674d0bc44 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html @@ -4,7 +4,10 @@
-
Reward stats
+
+ Reward stats  + (144 blocks) +
diff --git a/frontend/src/app/components/reward-stats/reward-stats.component.html b/frontend/src/app/components/reward-stats/reward-stats.component.html index 3e7df438d..bd607ae34 100644 --- a/frontend/src/app/components/reward-stats/reward-stats.component.html +++ b/frontend/src/app/components/reward-stats/reward-stats.component.html @@ -3,15 +3,15 @@
Miners Reward
-
in the past 144 blocks
+
were rewarded to miners
Reward Per Tx
{{ rewardStats.rewardPerTx | amountShortener }} - sats/tx -
in the past 144 blocks
+ sats/tx +
miners reward / tx count
@@ -19,7 +19,7 @@
{{ rewardStats.feePerTx | amountShortener}} sats/tx -
in the past 144 blocks
+
were paid per tx
From dcd50802e456b60a71756fc17f9582c45066765e Mon Sep 17 00:00:00 2001 From: nymkappa Date: Wed, 23 Mar 2022 12:15:58 +0900 Subject: [PATCH 4/4] Use fee estimation box layout for reward stats and show USD amounts --- .../reward-stats/reward-stats.component.html | 72 +++++++++- .../reward-stats/reward-stats.component.scss | 128 +++++++++++------- 2 files changed, 148 insertions(+), 52 deletions(-) diff --git a/frontend/src/app/components/reward-stats/reward-stats.component.html b/frontend/src/app/components/reward-stats/reward-stats.component.html index bd607ae34..861921ca6 100644 --- a/frontend/src/app/components/reward-stats/reward-stats.component.html +++ b/frontend/src/app/components/reward-stats/reward-stats.component.html @@ -1,4 +1,72 @@ -
+
+
+
+
Miners Reward
+
+
+ +
+ + + +
+
+
+
Reward Per Tx
+
+
+ {{ rewardStats.rewardPerTx | amountShortener }} + sats/tx +
+ + + +
+
+
+
Average Fee
+
+
{{ rewardStats.feePerTx | amountShortener }} + sats/tx +
+ + + +
+
+
+
+ + +
+
+
Low priority
+
+
+
+
+
+
+
Medium priority
+
+
+
+
+
+
+
High priority
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/frontend/src/app/components/reward-stats/reward-stats.component.scss b/frontend/src/app/components/reward-stats/reward-stats.component.scss index 77d57fa1c..460db5e4b 100644 --- a/frontend/src/app/components/reward-stats/reward-stats.component.scss +++ b/frontend/src/app/components/reward-stats/reward-stats.component.scss @@ -1,57 +1,85 @@ -.reward-container { - display: flex; - flex-direction: row; - justify-content: space-around; - height: 76px; - .shared-block { - color: #ffffff66; - font-size: 12px; - } - .item { - display: table-cell; - padding: 0 5px; - width: 100%; - &:nth-child(1) { - display: none; - @media (min-width: 485px) { - display: table-cell; - } - @media (min-width: 768px) { - display: none; - } - @media (min-width: 992px) { - display: table-cell; - } - } - } - .card-text { - font-size: 22px; - margin-top: -9px; - position: relative; - } - .card-text.skeleton { - margin-top: 0px; - } +.card-title { + color: #4a68b9; + font-size: 10px; + margin-bottom: 4px; + font-size: 1rem; } .card-text { font-size: 22px; -} - -.card-title { - font-size: 1rem; - color: #4a68b9; -} - -.skeleton-loader { - width: 100%; - display: block; - &:first-child { - max-width: 90px; - margin: 15px auto 3px; + span { + font-size: 11px; + position: relative; + top: -2px; + display: inline-flex; } - &:last-child { - margin: 10px auto 3px; - max-width: 55px; + .green-color { + display: block; } } + +.fee-estimation-container { + display: flex; + justify-content: space-between; + @media (min-width: 376px) { + flex-direction: row; + } + .item { + max-width: 150px; + margin: 0; + width: -webkit-fill-available; + @media (min-width: 376px) { + margin: 0 auto 0px; + } + &:first-child{ + display: none; + @media (min-width: 485px) { + display: block; + } + @media (min-width: 768px) { + display: none; + } + @media (min-width: 992px) { + display: block; + } + } + &:last-child { + margin-bottom: 0; + } + .card-text span { + color: #ffffff66; + font-size: 12px; + top: 0px; + } + .fee-text{ + border-bottom: 1px solid #ffffff1c; + width: fit-content; + margin: auto; + line-height: 1.45; + padding: 0px 2px; + } + .fiat { + display: block; + font-size: 14px !important; + } + } +} + +.loading-container{ + min-height: 76px; +} + +.card-text { + .skeleton-loader { + width: 100%; + display: block; + &:first-child { + max-width: 90px; + margin: 15px auto 3px; + } + &:last-child { + margin: 10px auto 3px; + max-width: 55px; + } + } +} \ No newline at end of file