diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts index 266be5f1e..53c731e1f 100644 --- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts +++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts @@ -2,7 +2,7 @@ import { IEsploraApi } from './esplora-api.interface'; export interface AbstractBitcoinApi { $getRawMempool(): Promise; - $getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean, blockHash?: string): Promise; + $getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean): Promise; $getBlockHeightTip(): Promise; $getTxIdsForBlock(hash: string): Promise; $getBlockHash(height: number): Promise; diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index 51ed99b6c..48368a128 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -14,14 +14,31 @@ class BitcoinApi implements AbstractBitcoinApi { this.bitcoindClient = bitcoinClient; } - $getRawTransaction(txId: string, skipConversion = false, addPrevout = false, blockHash?: string): Promise { + static convertBlock(block: IBitcoinApi.Block): IEsploraApi.Block { + return { + id: block.hash, + height: block.height, + version: block.version, + timestamp: block.time, + bits: parseInt(block.bits, 16), + nonce: block.nonce, + difficulty: block.difficulty, + merkle_root: block.merkleroot, + tx_count: block.nTx, + size: block.size, + weight: block.weight, + previousblockhash: block.previousblockhash, + }; + } + + $getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise { // If the transaction is in the mempool we already converted and fetched the fee. Only prevouts are missing const txInMempool = mempool.getMempool()[txId]; if (txInMempool && addPrevout) { return this.$addPrevouts(txInMempool); } - return this.bitcoindClient.getRawTransaction(txId, true, blockHash) + return this.bitcoindClient.getRawTransaction(txId, true) .then((transaction: IBitcoinApi.Transaction) => { if (skipConversion) { transaction.vout.forEach((vout) => { @@ -174,35 +191,18 @@ class BitcoinApi implements AbstractBitcoinApi { }; } - if (transaction.confirmations) { - esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction, addPrevout); - } else { - esploraTransaction = await this.$appendMempoolFeeData(esploraTransaction); - if (addPrevout) { - esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction, addPrevout); + if (addPrevout) { + if (transaction.confirmations) { + esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction); + } else { + esploraTransaction = await this.$appendMempoolFeeData(esploraTransaction); + esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction); } } return esploraTransaction; } - static convertBlock(block: IBitcoinApi.Block): IEsploraApi.Block { - return { - id: block.hash, - height: block.height, - version: block.version, - timestamp: block.time, - bits: parseInt(block.bits, 16), - nonce: block.nonce, - difficulty: block.difficulty, - merkle_root: block.merkleroot, - tx_count: block.nTx, - size: block.size, - weight: block.weight, - previousblockhash: block.previousblockhash, - }; - } - private translateScriptPubKeyType(outputType: string): string { const map = { 'pubkey': 'p2pk', @@ -245,7 +245,7 @@ class BitcoinApi implements AbstractBitcoinApi { if (vin.prevout) { continue; } - const innerTx = await this.$getRawTransaction(vin.txid, false); + const innerTx = await this.$getRawTransaction(vin.txid, false, false); vin.prevout = innerTx.vout[vin.vout]; this.addInnerScriptsToVin(vin); } @@ -271,18 +271,16 @@ class BitcoinApi implements AbstractBitcoinApi { return this.bitcoindClient.getRawMemPool(true); } - private async $calculateFeeFromInputs(transaction: IEsploraApi.Transaction, addPrevout: boolean): Promise { + private async $calculateFeeFromInputs(transaction: IEsploraApi.Transaction): Promise { if (transaction.vin[0].is_coinbase) { transaction.fee = 0; return transaction; } let totalIn = 0; for (const vin of transaction.vin) { - const innerTx = await this.$getRawTransaction(vin.txid, !addPrevout); - if (addPrevout) { - vin.prevout = innerTx.vout[vin.vout]; - this.addInnerScriptsToVin(vin); - } + const innerTx = await this.$getRawTransaction(vin.txid, false, false); + vin.prevout = innerTx.vout[vin.vout]; + this.addInnerScriptsToVin(vin); totalIn += innerTx.vout[vin.vout].value; } const totalOut = transaction.vout.reduce((p, output) => p + output.value, 0); diff --git a/backend/src/api/difficulty-adjustment.ts b/backend/src/api/difficulty-adjustment.ts index eea0e9b42..1f85fdb80 100644 --- a/backend/src/api/difficulty-adjustment.ts +++ b/backend/src/api/difficulty-adjustment.ts @@ -32,7 +32,7 @@ class DifficultyAdjustmentApi { } } - let timeAvgMins = blocksInEpoch ? diff / blocksInEpoch / 60 : 10; + let timeAvgMins = blocksInEpoch && blocksInEpoch > 146 ? diff / blocksInEpoch / 60 : 10; // Testnet difficulty is set to 1 after 20 minutes of no blocks, // therefore the time between blocks will always be below 20 minutes (1200s). diff --git a/backend/src/index.ts b/backend/src/index.ts index f2658a22a..c6eab6c17 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -300,24 +300,12 @@ class Server { 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')) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/1w', routes.$getPools.bind(routes, '1w')) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/1m', routes.$getPools.bind(routes, '1m')) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3m', routes.$getPools.bind(routes, '3m')) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/6m', routes.$getPools.bind(routes, '6m')) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/1y', routes.$getPools.bind(routes, '1y')) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/2y', routes.$getPools.bind(routes, '2y')) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3y', routes.$getPools.bind(routes, '3y')) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/all', routes.$getPools.bind(routes, 'all')) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/:interval', routes.$getPools) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/hashrate', routes.$getPoolHistoricalHashrate) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/blocks', routes.$getPoolBlocks) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/blocks/:height', routes.$getPoolBlocks) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug', routes.$getPool) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug/:interval', routes.$getPool) - .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/pools', routes.$getPoolsHistoricalHashrate) .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) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fees/:interval', routes.$getHistoricalBlockFees) diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 52f590775..d7fe35b08 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -572,9 +572,9 @@ class Routes { } } - public async $getPools(interval: string, req: Request, res: Response) { + public async $getPools(req: Request, res: Response) { try { - const stats = await miningStats.$getPoolsStats(interval); + const stats = await miningStats.$getPoolsStats(req.params.interval); const blockCount = await BlocksRepository.$blockCount(null, null); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); @@ -588,7 +588,7 @@ class Routes { public async $getPoolsHistoricalHashrate(req: Request, res: Response) { try { - const hashrates = await HashratesRepository.$getPoolsWeeklyHashrate(req.params.interval ?? null); + const hashrates = await HashratesRepository.$getPoolsWeeklyHashrate(req.params.interval); const blockCount = await BlocksRepository.$blockCount(null, null); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); @@ -620,8 +620,8 @@ class Routes { public async $getHistoricalHashrate(req: Request, res: Response) { try { - const hashrates = await HashratesRepository.$getNetworkDailyHashrate(req.params.interval ?? null); - const difficulty = await BlocksRepository.$getBlocksDifficulty(req.params.interval ?? null); + const hashrates = await HashratesRepository.$getNetworkDailyHashrate(req.params.interval); + const difficulty = await BlocksRepository.$getBlocksDifficulty(req.params.interval); const blockCount = await BlocksRepository.$blockCount(null, null); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); @@ -640,7 +640,7 @@ class Routes { public async $getHistoricalBlockFees(req: Request, res: Response) { try { - const blockFees = await mining.$getHistoricalBlockFees(req.params.interval ?? null); + const blockFees = await mining.$getHistoricalBlockFees(req.params.interval); const blockCount = await BlocksRepository.$blockCount(null, null); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); @@ -654,7 +654,7 @@ class Routes { public async $getHistoricalBlockRewards(req: Request, res: Response) { try { - const blockRewards = await mining.$getHistoricalBlockRewards(req.params.interval ?? null); + const blockRewards = await mining.$getHistoricalBlockRewards(req.params.interval); const blockCount = await BlocksRepository.$blockCount(null, null); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); @@ -668,7 +668,7 @@ class Routes { public async $getHistoricalBlockFeeRates(req: Request, res: Response) { try { - const blockFeeRates = await mining.$getHistoricalBlockFeeRates(req.params.interval ?? null); + const blockFeeRates = await mining.$getHistoricalBlockFeeRates(req.params.interval); const oldestIndexedBlockTimestamp = await BlocksRepository.$oldestBlockTimestamp(); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); @@ -684,8 +684,8 @@ class Routes { public async $getHistoricalBlockSizeAndWeight(req: Request, res: Response) { try { - const blockSizes = await mining.$getHistoricalBlockSizes(req.params.interval ?? null); - const blockWeights = await mining.$getHistoricalBlockWeights(req.params.interval ?? null); + const blockSizes = await mining.$getHistoricalBlockSizes(req.params.interval); + const blockWeights = await mining.$getHistoricalBlockWeights(req.params.interval); const blockCount = await BlocksRepository.$blockCount(null, null); res.header('Pragma', 'public'); res.header('Cache-control', 'public'); diff --git a/backend/src/tasks/pools-updater.ts b/backend/src/tasks/pools-updater.ts index aee786ff9..aac301256 100644 --- a/backend/src/tasks/pools-updater.ts +++ b/backend/src/tasks/pools-updater.ts @@ -1,8 +1,10 @@ -const https = require('https'); +import axios from 'axios'; import poolsParser from '../api/pools-parser'; import config from '../config'; import DB from '../database'; import logger from '../logger'; +import { SocksProxyAgent } from 'socks-proxy-agent'; +import * as https from 'https'; /** * Maintain the most recent version of pools.json @@ -28,6 +30,13 @@ class PoolsUpdater { this.lastRun = now; + logger.info('Updating latest mining pools from Github'); + if (config.SOCKS5PROXY.ENABLED) { + logger.info('List of public pools will be queried over the Tor network'); + } else { + logger.info('List of public pools will be queried over clearnet'); + } + try { const dbSha = await this.getShaFromDb(); const githubSha = await this.fetchPoolsSha(); // Fetch pools.json sha from github @@ -41,7 +50,10 @@ class PoolsUpdater { } logger.warn('Pools.json is outdated, fetch latest from github'); - const poolsJson = await this.fetchPools(); + const poolsJson = await this.query('https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json'); + if (poolsJson === undefined) { + return; + } await poolsParser.migratePoolsJson(poolsJson); await this.updateDBSha(githubSha); logger.notice('PoolsUpdater completed'); @@ -52,14 +64,6 @@ class PoolsUpdater { } } - /** - * Fetch pools.json from github repo - */ - private async fetchPools(): Promise { - const response = await this.query('/repos/mempool/mining-pools/contents/pools.json'); - return JSON.parse(Buffer.from(response['content'], 'base64').toString('utf8')); - } - /** * Fetch our latest pools.json sha from the db */ @@ -90,11 +94,13 @@ class PoolsUpdater { * Fetch our latest pools.json sha from github */ private async fetchPoolsSha(): Promise { - const response = await this.query('/repos/mempool/mining-pools/git/trees/master'); + const response = await this.query('https://api.github.com/repos/mempool/mining-pools/git/trees/master'); - for (const file of response['tree']) { - if (file['path'] === 'pools.json') { - return file['sha']; + if (response !== undefined) { + for (const file of response['tree']) { + if (file['path'] === 'pools.json') { + return file['sha']; + } } } @@ -105,35 +111,45 @@ class PoolsUpdater { /** * Http request wrapper */ - private query(path): Promise { - return new Promise((resolve, reject) => { - const options = { - host: 'api.github.com', - path: path, - method: 'GET', - headers: { 'user-agent': 'node.js' } + private async query(path): Promise { + type axiosOptions = { + httpsAgent?: https.Agent; + } + const setDelay = (secs: number = 1): Promise => new Promise(resolve => setTimeout(() => resolve(), secs * 1000)); + const axiosOptions: axiosOptions = {}; + let retry = 0; + + if (config.SOCKS5PROXY.ENABLED) { + const socksOptions: any = { + agentOptions: { + keepAlive: true, + }, + hostname: config.SOCKS5PROXY.HOST, + port: config.SOCKS5PROXY.PORT }; - logger.debug('Querying: api.github.com' + path); + if (config.SOCKS5PROXY.USERNAME && config.SOCKS5PROXY.PASSWORD) { + socksOptions.username = config.SOCKS5PROXY.USERNAME; + socksOptions.password = config.SOCKS5PROXY.PASSWORD; + } - const request = https.get(options, (response) => { - const chunks_of_data: any[] = []; - response.on('data', (fragments) => { - chunks_of_data.push(fragments); - }); - response.on('end', () => { - resolve(JSON.parse(Buffer.concat(chunks_of_data).toString())); - }); - response.on('error', (error) => { - reject(error); - }); - }); + axiosOptions.httpsAgent = new SocksProxyAgent(socksOptions); + } - request.on('error', (error) => { - logger.err('Github API query failed. Reason: ' + error); - reject(error); - }); - }); + while(retry < 5) { + try { + const data = await axios.get(path, axiosOptions); + if (data.statusText !== 'OK' || !data.data) { + throw new Error(`Could not fetch data from Github, Error: ${data.status}`); + } + return data.data; + } catch (e) { + logger.err('Could not connect to Github. Reason: ' + (e instanceof Error ? e.message : e)); + retry++; + } + await setDelay(); + } + return undefined; } } diff --git a/contributors/ayanamidev.txt b/contributors/ayanamidev.txt new file mode 100644 index 000000000..c397f7286 --- /dev/null +++ b/contributors/ayanamidev.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of May 15, 2022. + +Signed: ayanamidev diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index c9cd63c0c..4b4e9eba9 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -67,6 +67,7 @@ import { HashrateChartPoolsComponent } from './components/hashrates-chart-pools/ import { MiningStartComponent } from './components/mining-start/mining-start.component'; import { AmountShortenerPipe } from './shared/pipes/amount-shortener.pipe'; import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe'; +import { CapAddressPipe } from './shared/pipes/cap-address-pipe/cap-address-pipe'; 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'; @@ -161,6 +162,7 @@ import { BlockSizesWeightsGraphComponent } from './components/block-sizes-weight StorageService, LanguageService, ShortenStringPipe, + CapAddressPipe, { provide: HTTP_INTERCEPTORS, useClass: HttpCacheInterceptor, multi: true } ], bootstrap: [AppComponent] diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts index a0c1b6aca..c938b351f 100644 --- a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts @@ -157,7 +157,7 @@ export class BlockFeesGraphComponent implements OnInit { series: [ { zlevel: 0, - name: 'Fees', + name: $localize`:@@c20172223f84462032664d717d739297e5a9e2fe:Fees`, showSymbol: false, symbol: 'none', data: data.blockFees, diff --git a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts index 7d3089092..48d7ec10c 100644 --- a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts +++ b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts @@ -157,7 +157,7 @@ export class BlockRewardsGraphComponent implements OnInit { series: [ { zlevel: 0, - name: 'Reward', + name: $localize`:@@12f86e6747a5ad39e62d3480ddc472b1aeab5b76:Reward`, showSymbol: false, symbol: 'none', data: data.blockRewards, diff --git a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts index ef947f9d0..7c72b42ef 100644 --- a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts +++ b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts @@ -178,7 +178,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit { padding: 10, data: [ { - name: 'Size', + name: $localize`:@@7faaaa08f56427999f3be41df1093ce4089bbd75:Size`, inactiveColor: 'rgb(110, 112, 121)', textStyle: { color: 'white', @@ -186,7 +186,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit { icon: 'roundRect', }, { - name: 'Weight', + name: $localize`:@@919f2fd60a898850c24b1584362bbf18a4628bcb:Weight`, inactiveColor: 'rgb(110, 112, 121)', textStyle: { color: 'white', @@ -224,7 +224,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit { series: data.sizes.length === 0 ? [] : [ { zlevel: 1, - name: 'Size', + name: $localize`:@@7faaaa08f56427999f3be41df1093ce4089bbd75:Size`, showSymbol: false, symbol: 'none', data: data.sizes, @@ -255,7 +255,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit { { zlevel: 1, yAxisIndex: 0, - name: 'Weight', + name: $localize`:@@919f2fd60a898850c24b1584362bbf18a4628bcb:Weight`, showSymbol: false, symbol: 'none', data: data.weights, diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index 57bb2a614..c0ff29889 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -195,7 +195,7 @@
- +
diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index 6394d6975..face9452b 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -40,10 +40,10 @@ - + - + {{ block.tx_count | number }} 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 8739f18fb..2cae2ec4b 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.html @@ -11,7 +11,7 @@

-
Difficulty
+
Difficulty

{{ hashrates.currentDifficulty | amountShortener }}

@@ -64,13 +64,13 @@
-
Hashrate
+
Hashrate

-
Difficulty
+
Difficulty

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 70b98bd0c..d401f76ad 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -223,7 +223,7 @@ export class HashrateChartComponent implements OnInit { legend: (this.widget || data.hashrates.length === 0) ? undefined : { data: [ { - name: 'Hashrate', + name: $localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`, inactiveColor: 'rgb(110, 112, 121)', textStyle: { color: 'white', @@ -234,9 +234,9 @@ export class HashrateChartComponent implements OnInit { }, }, { - name: 'Difficulty', + name: $localize`:@@25148835d92465353fc5fe8897c27d5369978e5a:Difficulty`, inactiveColor: 'rgb(110, 112, 121)', - textStyle: { + textStyle: { color: 'white', }, icon: 'roundRect', @@ -290,7 +290,7 @@ export class HashrateChartComponent implements OnInit { series: data.hashrates.length === 0 ? [] : [ { zlevel: 0, - name: 'Hashrate', + name: $localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`, showSymbol: false, symbol: 'none', data: data.hashrates, @@ -302,7 +302,7 @@ export class HashrateChartComponent implements OnInit { { zlevel: 1, yAxisIndex: 1, - name: 'Difficulty', + name: $localize`:@@25148835d92465353fc5fe8897c27d5369978e5a:Difficulty`, showSymbol: false, symbol: 'none', data: data.difficulty, diff --git a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.html b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.html index 18c7404af..a392e122b 100644 --- a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.html +++ b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.html @@ -7,7 +7,6 @@ - Pools Dominance