diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json index 3cb79b909..3c2fccfb7 100644 --- a/backend/mempool-config.sample.json +++ b/backend/mempool-config.sample.json @@ -16,6 +16,7 @@ "MEMPOOL_BLOCKS_AMOUNT": 8, "INDEXING_BLOCKS_AMOUNT": 11000, "BLOCKS_SUMMARIES_INDEXING": false, + "GOGGLES_INDEXING": false, "USE_SECOND_NODE_FOR_MINFEE": false, "EXTERNAL_ASSETS": [], "EXTERNAL_MAX_RETRY": 1, diff --git a/backend/src/__fixtures__/mempool-config.template.json b/backend/src/__fixtures__/mempool-config.template.json index 9445fc25d..9ee2bd0bc 100644 --- a/backend/src/__fixtures__/mempool-config.template.json +++ b/backend/src/__fixtures__/mempool-config.template.json @@ -4,6 +4,7 @@ "NETWORK": "__MEMPOOL_NETWORK__", "BACKEND": "__MEMPOOL_BACKEND__", "BLOCKS_SUMMARIES_INDEXING": true, + "GOGGLES_INDEXING": false, "HTTP_PORT": 1, "SPAWN_CLUSTER_PROCS": 2, "API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__", diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts index 97c218370..6af0ce32f 100644 --- a/backend/src/__tests__/config.test.ts +++ b/backend/src/__tests__/config.test.ts @@ -17,6 +17,7 @@ describe('Mempool Backend Config', () => { NETWORK: 'mainnet', BACKEND: 'none', BLOCKS_SUMMARIES_INDEXING: false, + GOGGLES_INDEXING: false, HTTP_PORT: 8999, SPAWN_CLUSTER_PROCS: 0, API_URL_PREFIX: '/api/v1/', diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 21818dc62..2cd043fe2 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -566,7 +566,7 @@ class Blocks { */ public async $classifyBlocks(): Promise { // classification requires an esplora backend - if (!Common.blocksSummariesIndexingEnabled() || config.MEMPOOL.BACKEND !== 'esplora') { + if (!Common.gogglesIndexingEnabled() || config.MEMPOOL.BACKEND !== 'esplora') { return; } @@ -617,6 +617,7 @@ class Blocks { // classify const { transactions: classifiedTxs } = this.summarizeBlockTransactions(blockHash, cpfpSummary.transactions); await BlocksSummariesRepository.$saveTransactions(height, blockHash, classifiedTxs, 1); + await Common.sleep$(250); } if (unclassifiedTemplates[height]) { // classify template @@ -656,6 +657,7 @@ class Blocks { }); } await BlocksSummariesRepository.$saveTemplate({ height, template: { id: blockHash, transactions: classifiedTemplate }, version: 1 }); + await Common.sleep$(250); } } catch (e) { logger.warn(`Failed to classify template or block summary at ${height}`, logger.tags.goggles); diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 52fa9042b..208c67d70 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -508,6 +508,13 @@ export class Common { ); } + static gogglesIndexingEnabled(): boolean { + return ( + Common.blocksSummariesIndexingEnabled() && + config.MEMPOOL.GOGGLES_INDEXING === true + ); + } + static cpfpIndexingEnabled(): boolean { return ( Common.indexingEnabled() && diff --git a/backend/src/config.ts b/backend/src/config.ts index df1022a67..32a7af3df 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -20,6 +20,7 @@ interface IConfig { MEMPOOL_BLOCKS_AMOUNT: number; INDEXING_BLOCKS_AMOUNT: number; BLOCKS_SUMMARIES_INDEXING: boolean; + GOGGLES_INDEXING: boolean; USE_SECOND_NODE_FOR_MINFEE: boolean; EXTERNAL_ASSETS: string[]; EXTERNAL_MAX_RETRY: number; @@ -175,6 +176,7 @@ const defaults: IConfig = { 'MEMPOOL_BLOCKS_AMOUNT': 8, 'INDEXING_BLOCKS_AMOUNT': 11000, // 0 = disable indexing, -1 = index all blocks 'BLOCKS_SUMMARIES_INDEXING': false, + 'GOGGLES_INDEXING': false, 'USE_SECOND_NODE_FOR_MINFEE': false, 'EXTERNAL_ASSETS': [], 'EXTERNAL_MAX_RETRY': 1, diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json index c68e37baa..aefb095cf 100644 --- a/docker/backend/mempool-config.json +++ b/docker/backend/mempool-config.json @@ -22,6 +22,7 @@ "STDOUT_LOG_MIN_PRIORITY": "__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__", "INDEXING_BLOCKS_AMOUNT": __MEMPOOL_INDEXING_BLOCKS_AMOUNT__, "BLOCKS_SUMMARIES_INDEXING": __MEMPOOL_BLOCKS_SUMMARIES_INDEXING__, + "GOGGLES_INDEXING": __MEMPOOL_GOGGLES_INDEXING__, "AUTOMATIC_BLOCK_REINDEXING": __MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__, "AUDIT": __MEMPOOL_AUDIT__, "ADVANCED_GBT_AUDIT": __MEMPOOL_ADVANCED_GBT_AUDIT__, diff --git a/docker/backend/start.sh b/docker/backend/start.sh index d73ea83fb..ce8f72368 100755 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -17,6 +17,7 @@ __MEMPOOL_INITIAL_BLOCKS_AMOUNT__=${MEMPOOL_INITIAL_BLOCKS_AMOUNT:=8} __MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8} __MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=11000} __MEMPOOL_BLOCKS_SUMMARIES_INDEXING__=${MEMPOOL_BLOCKS_SUMMARIES_INDEXING:=false} +__MEMPOOL_GOGGLES_INDEXING__=${MEMPOOL_GOGGLES_INDEXING:=false} __MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__=${MEMPOOL_USE_SECOND_NODE_FOR_MINFEE:=false} __MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[]} __MEMPOOL_EXTERNAL_MAX_RETRY__=${MEMPOOL_EXTERNAL_MAX_RETRY:=1} @@ -170,6 +171,7 @@ sed -i "s!__MEMPOOL_INITIAL_BLOCKS_AMOUNT__!${__MEMPOOL_INITIAL_BLOCKS_AMOUNT__} sed -i "s!__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__!${__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__}!g" mempool-config.json sed -i "s!__MEMPOOL_INDEXING_BLOCKS_AMOUNT__!${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}!g" mempool-config.json sed -i "s!__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__!${__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__}!g" mempool-config.json +sed -i "s!__MEMPOOL_GOGGLES_INDEXING__!${__MEMPOOL_GOGGLES_INDEXING__}!g" mempool-config.json sed -i "s!__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__!${__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__}!g" mempool-config.json sed -i "s!__MEMPOOL_EXTERNAL_ASSETS__!${__MEMPOOL_EXTERNAL_ASSETS__}!g" mempool-config.json sed -i "s!__MEMPOOL_EXTERNAL_MAX_RETRY__!${__MEMPOOL_EXTERNAL_MAX_RETRY__}!g" mempool-config.json diff --git a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts index d348449fe..c28ff3f2f 100644 --- a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts +++ b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts @@ -68,7 +68,6 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy { } ngOnInit(): void { - this.seoService.setTitle($localize`:@@bcf34abc2d9ed8f45a2f65dd464c46694e9a181e:Acceleration Fees`); this.isLoading = true; if (this.widget) { this.miningWindowPreference = '1m'; @@ -88,6 +87,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy { }), ); } else { + this.seoService.setTitle($localize`:@@bcf34abc2d9ed8f45a2f65dd464c46694e9a181e:Acceleration Fees`); this.miningWindowPreference = this.miningService.getDefaultTimespan('1w'); this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference }); this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference); diff --git a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.scss b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.scss index 4182abb68..110ff033c 100644 --- a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.scss +++ b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.scss @@ -63,66 +63,82 @@ tr, td, th { } .txid { - width: 25%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-width: 30%; - @media (max-width: 1060px) and (min-width: 768px) { - display: none; - } @media (max-width: 500px) { display: none; } } -.fee-rate { - width: 20%; - @media (max-width: 1060px) and (min-width: 768px) { - text-align: start !important; - } - @media (max-width: 500px) { - text-align: start !important; - } - @media (max-width: 840px) and (min-width: 768px) { - display: none; - } - @media (max-width: 410px) { - display: none; +.fee, .block, .status { + width: 15%; + + @media (max-width: 720px) { + width: 20%; } } -.bid { - width: 30%; - min-width: 150px; - @media (max-width: 840px) and (min-width: 768px) { - text-align: start !important; +.widget { + .txid { + width: 30%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 30%; + @media (max-width: 1060px) and (min-width: 768px) { + display: none; + } + @media (max-width: 500px) { + display: none; + } } - @media (max-width: 410px) { - text-align: start !important; + + .fee-rate { + width: 20%; + @media (max-width: 1060px) and (min-width: 768px) { + text-align: start !important; + } + @media (max-width: 500px) { + text-align: start !important; + } + @media (max-width: 840px) and (min-width: 768px) { + display: none; + } + @media (max-width: 410px) { + display: none; + } } -} -.time { - width: 25%; -} - -.fee { - width: 35%; - @media (max-width: 1060px) and (min-width: 768px) { - text-align: start !important; + .bid { + width: 30%; + min-width: 150px; + @media (max-width: 840px) and (min-width: 768px) { + text-align: start !important; + } + @media (max-width: 410px) { + text-align: start !important; + } } - @media (max-width: 500px) { - text-align: start !important; + + .time { + width: 25%; } -} -.block { - width: 20%; -} + .fee { + width: 30%; + @media (max-width: 1060px) and (min-width: 768px) { + text-align: start !important; + } + @media (max-width: 500px) { + text-align: start !important; + } + } -.status { - width: 20% + .block { + width: 20%; + } + + .status { + width: 20% + } } /* Tooltip text */ diff --git a/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html b/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html index 243a48939..19d01e726 100644 --- a/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html +++ b/frontend/src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html @@ -80,7 +80,7 @@
-
Next Halving
-
- - {{ i }} blocks - {{ i }} block +
Next Halving
+
+ {{ timeUntilHalving | date }} +
+ +
+ +
+ +
+
+
-
-
+ + + {{ i }} blocks + {{ i }} block + +
diff --git a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts index c23d7d4b9..c1283b8b1 100644 --- a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts +++ b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; -import { combineLatest, Observable, timer } from 'rxjs'; -import { map, switchMap } from 'rxjs/operators'; +import { combineLatest, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; import { StateService } from '../../services/state.service'; interface EpochProgress { @@ -15,6 +15,7 @@ interface EpochProgress { previousRetarget: number; blocksUntilHalving: number; timeUntilHalving: number; + timeAvg: number; } @Component({ @@ -26,6 +27,9 @@ interface EpochProgress { export class DifficultyMiningComponent implements OnInit { isLoadingWebSocket$: Observable; difficultyEpoch$: Observable; + blocksUntilHalving: number | null = null; + timeUntilHalving = 0; + now = new Date().getTime(); @Input() showProgress = true; @Input() showHalving = false; @@ -64,8 +68,9 @@ export class DifficultyMiningComponent implements OnInit { colorPreviousAdjustments = '#ffffff66'; } - const blocksUntilHalving = 210000 - (maxHeight % 210000); - const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000); + this.blocksUntilHalving = 210000 - (maxHeight % 210000); + this.timeUntilHalving = new Date().getTime() + (this.blocksUntilHalving * 600000); + this.now = new Date().getTime(); const data = { base: `${da.progressPercent.toFixed(2)}%`, @@ -77,8 +82,9 @@ export class DifficultyMiningComponent implements OnInit { newDifficultyHeight: da.nextRetargetHeight, estimatedRetargetDate: da.estimatedRetargetDate, previousRetarget: da.previousRetarget, - blocksUntilHalving, - timeUntilHalving, + blocksUntilHalving: this.blocksUntilHalving, + timeUntilHalving: this.timeUntilHalving, + timeAvg: da.timeAvg, }; return data; }) diff --git a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts index b0557ca7c..3f0e9258f 100644 --- a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts +++ b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts @@ -11,6 +11,13 @@ import { MiningService } from '../../services/mining.service'; import { download } from '../../shared/graphs.utils'; import { ActivatedRoute } from '@angular/router'; +interface Hashrate { + timestamp: number; + avgHashRate: number; + share: number; + poolName: string; +} + @Component({ selector: 'app-hashrate-chart-pools', templateUrl: './hashrate-chart-pools.component.html', @@ -32,6 +39,7 @@ export class HashrateChartPoolsComponent implements OnInit { miningWindowPreference: string; radioGroupForm: UntypedFormGroup; + hashrates: Hashrate[]; chartOptions: EChartsOption = {}; chartInitOptions = { renderer: 'svg', @@ -87,56 +95,9 @@ export class HashrateChartPoolsComponent implements OnInit { return this.apiService.getHistoricalPoolsHashrate$(timespan) .pipe( tap((response) => { - const hashrates = response.body; + this.hashrates = response.body; // Prepare series (group all hashrates data point by pool) - const grouped = {}; - for (const hashrate of hashrates) { - if (!grouped.hasOwnProperty(hashrate.poolName)) { - grouped[hashrate.poolName] = []; - } - grouped[hashrate.poolName].push(hashrate); - } - - const series = []; - const legends = []; - for (const name in grouped) { - series.push({ - zlevel: 0, - stack: 'Total', - name: name, - showSymbol: false, - symbol: 'none', - data: grouped[name].map((val) => [val.timestamp * 1000, val.share * 100]), - type: 'line', - lineStyle: { width: 0 }, - areaStyle: { opacity: 1 }, - smooth: true, - color: poolsColor[name.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()], - emphasis: { - disabled: true, - scale: false, - }, - }); - - legends.push({ - name: name, - inactiveColor: 'rgb(110, 112, 121)', - textStyle: { - color: 'white', - }, - icon: 'roundRect', - itemStyle: { - color: poolsColor[name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()], - }, - }); - } - - this.prepareChartOptions({ - legends: legends, - series: series, - }); - this.isLoading = false; - + const series = this.applyHashrates(); if (series.length === 0) { this.cd.markForCheck(); throw new Error(); @@ -156,6 +117,77 @@ export class HashrateChartPoolsComponent implements OnInit { ); } + applyHashrates(): any[] { + const times: { [time: number]: { hashrates: { [pool: string]: Hashrate } } } = {}; + const pools = {}; + for (const hashrate of this.hashrates) { + if (!times[hashrate.timestamp]) { + times[hashrate.timestamp] = { hashrates: {} }; + } + times[hashrate.timestamp].hashrates[hashrate.poolName] = hashrate; + if (!pools[hashrate.poolName]) { + pools[hashrate.poolName] = true; + } + } + + const sortedTimes = Object.keys(times).sort((a,b) => parseInt(a) - parseInt(b)).map(time => ({ time: parseInt(time), hashrates: times[time].hashrates })); + const lastHashrates = sortedTimes[sortedTimes.length - 1].hashrates; + const sortedPools = Object.keys(pools).sort((a,b) => { + if (lastHashrates[b]?.share ?? lastHashrates[a]?.share ?? false) { + // sort by descending share of hashrate in latest period + return (lastHashrates[b]?.share || 0) - (lastHashrates[a]?.share || 0); + } else { + // tiebreak by pool name + b < a; + } + }); + + const series = []; + const legends = []; + for (const name of sortedPools) { + const data = sortedTimes.map(({ time, hashrates }) => { + return [time * 1000, (hashrates[name]?.share || 0) * 100]; + }); + series.push({ + zlevel: 0, + stack: 'Total', + name: name, + showSymbol: false, + symbol: 'none', + data, + type: 'line', + lineStyle: { width: 0 }, + areaStyle: { opacity: 1 }, + smooth: true, + color: poolsColor[name.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()], + emphasis: { + disabled: true, + scale: false, + }, + }); + + legends.push({ + name: name, + inactiveColor: 'rgb(110, 112, 121)', + textStyle: { + color: 'white', + }, + icon: 'roundRect', + itemStyle: { + color: poolsColor[name.replace(/[^a-zA-Z0-9]/g, "").toLowerCase()], + }, + }); + } + + this.prepareChartOptions({ + legends: legends, + series: series, + }); + this.isLoading = false; + + return series; + } + prepareChartOptions(data) { let title: object; if (data.series.length === 0) { @@ -256,6 +288,7 @@ export class HashrateChartPoolsComponent implements OnInit { }, }], }; + this.cd.markForCheck(); } onChartInit(ec) { diff --git a/frontend/src/app/components/master-page/master-page.component.html b/frontend/src/app/components/master-page/master-page.component.html index 7d8b37d00..66d8627a8 100644 --- a/frontend/src/app/components/master-page/master-page.component.html +++ b/frontend/src/app/components/master-page/master-page.component.html @@ -53,7 +53,10 @@