Merge pull request #1928 from mempool/nymkappa/feature/hashrate-resolution
Add resolution scaling to hashrate and difficulty chart
This commit is contained in:
		
						commit
						b0c334fbe3
					
				| @ -9,6 +9,7 @@ import loadingIndicators from './loading-indicators'; | |||||||
| import { escape } from 'mysql2'; | import { escape } from 'mysql2'; | ||||||
| import indexer from '../indexer'; | import indexer from '../indexer'; | ||||||
| import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository'; | import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository'; | ||||||
|  | import config from '../config'; | ||||||
| 
 | 
 | ||||||
| class Mining { | class Mining { | ||||||
|   constructor() { |   constructor() { | ||||||
| @ -304,7 +305,7 @@ class Mining { | |||||||
|       while (toTimestamp > genesisTimestamp) { |       while (toTimestamp > genesisTimestamp) { | ||||||
|         const fromTimestamp = toTimestamp - 86400000; |         const fromTimestamp = toTimestamp - 86400000; | ||||||
| 
 | 
 | ||||||
|         // Skip already indexed weeks
 |         // Skip already indexed days
 | ||||||
|         if (indexedTimestamp.includes(toTimestamp / 1000)) { |         if (indexedTimestamp.includes(toTimestamp / 1000)) { | ||||||
|           toTimestamp -= 86400000; |           toTimestamp -= 86400000; | ||||||
|           ++totalIndexed; |           ++totalIndexed; | ||||||
| @ -315,7 +316,7 @@ class Mining { | |||||||
|         // we are currently indexing has complete data)
 |         // we are currently indexing has complete data)
 | ||||||
|         const blockStatsPreviousDay: any = await BlocksRepository.$blockCountBetweenTimestamp( |         const blockStatsPreviousDay: any = await BlocksRepository.$blockCountBetweenTimestamp( | ||||||
|           null, (fromTimestamp - 86400000) / 1000, (toTimestamp - 86400000) / 1000); |           null, (fromTimestamp - 86400000) / 1000, (toTimestamp - 86400000) / 1000); | ||||||
|         if (blockStatsPreviousDay.blockCount === 0) { // We are done indexing
 |         if (blockStatsPreviousDay.blockCount === 0 && config.MEMPOOL.NETWORK === 'mainnet') { // We are done indexing
 | ||||||
|           break; |           break; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -359,9 +360,10 @@ class Mining { | |||||||
|       // Add genesis block manually
 |       // Add genesis block manually
 | ||||||
|       if (toTimestamp <= genesisTimestamp && !indexedTimestamp.includes(genesisTimestamp)) { |       if (toTimestamp <= genesisTimestamp && !indexedTimestamp.includes(genesisTimestamp)) { | ||||||
|         hashrates.push({ |         hashrates.push({ | ||||||
|           hashrateTimestamp: genesisTimestamp, |           hashrateTimestamp: genesisTimestamp / 1000, | ||||||
|           avgHashrate: await bitcoinClient.getNetworkHashPs(1, 1), |           avgHashrate: await bitcoinClient.getNetworkHashPs(1, 1), | ||||||
|           poolId: null, |           poolId: 0, | ||||||
|  |           share: 1, | ||||||
|           type: 'daily', |           type: 'daily', | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
| @ -396,6 +398,15 @@ class Mining { | |||||||
|     let currentDifficulty = 0; |     let currentDifficulty = 0; | ||||||
|     let totalIndexed = 0; |     let totalIndexed = 0; | ||||||
| 
 | 
 | ||||||
|  |     if (indexedHeights[0] === false) { | ||||||
|  |       await DifficultyAdjustmentsRepository.$saveAdjustments({ | ||||||
|  |         time: 1231006505, | ||||||
|  |         height: 0, | ||||||
|  |         difficulty: 1.0, | ||||||
|  |         adjustment: 0.0, | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     for (const block of blocks) { |     for (const block of blocks) { | ||||||
|       if (block.difficulty !== currentDifficulty) { |       if (block.difficulty !== currentDifficulty) { | ||||||
|         if (block.height === 0 || indexedHeights[block.height] === true) { // Already indexed
 |         if (block.height === 0 || indexedHeights[block.height] === true) { // Already indexed
 | ||||||
|  | |||||||
| @ -285,6 +285,7 @@ class Server { | |||||||
|         .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug', routes.$getPool) |         .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug', routes.$getPool) | ||||||
|         .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/pools/:interval', routes.$getPoolsHistoricalHashrate) |         .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/pools/:interval', routes.$getPoolsHistoricalHashrate) | ||||||
|         .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/:interval', routes.$getHistoricalHashrate) |         .get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/:interval', routes.$getHistoricalHashrate) | ||||||
|  |         .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments', routes.$getDifficultyAdjustments) | ||||||
|         .get(config.MEMPOOL.API_URL_PREFIX + 'mining/reward-stats/:blockCount', routes.$getRewardStats) |         .get(config.MEMPOOL.API_URL_PREFIX + 'mining/reward-stats/:blockCount', routes.$getRewardStats) | ||||||
|         .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fees/:interval', routes.$getHistoricalBlockFees) |         .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fees/:interval', routes.$getHistoricalBlockFees) | ||||||
|         .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/rewards/:interval', routes.$getHistoricalBlockRewards) |         .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/rewards/:interval', routes.$getHistoricalBlockRewards) | ||||||
|  | |||||||
| @ -436,7 +436,7 @@ class BlocksRepository { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (blocks[idx].previous_block_hash !== blocks[idx - 1].hash) { |         if (blocks[idx].previous_block_hash !== blocks[idx - 1].hash) { | ||||||
|           logger.warn(`Chain divergence detected at block ${blocks[idx - 1].height}, re-indexing newer blocks and hashrates`); |           logger.warn(`Chain divergence detected at block ${blocks[idx - 1].height}`); | ||||||
|           await this.$deleteBlocksFrom(blocks[idx - 1].height); |           await this.$deleteBlocksFrom(blocks[idx - 1].height); | ||||||
|           await BlocksSummariesRepository.$deleteBlocksFrom(blocks[idx - 1].height); |           await BlocksSummariesRepository.$deleteBlocksFrom(blocks[idx - 1].height); | ||||||
|           await HashratesRepository.$deleteHashratesFromTimestamp(blocks[idx - 1].timestamp - 604800); |           await HashratesRepository.$deleteHashratesFromTimestamp(blocks[idx - 1].timestamp - 604800); | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| import { Common } from '../api/common'; | import { Common } from '../api/common'; | ||||||
|  | import config from '../config'; | ||||||
| import DB from '../database'; | import DB from '../database'; | ||||||
| import logger from '../logger'; | import logger from '../logger'; | ||||||
| import { IndexedDifficultyAdjustment } from '../mempool.interfaces'; | import { IndexedDifficultyAdjustment } from '../mempool.interfaces'; | ||||||
| @ -31,13 +32,19 @@ class DifficultyAdjustmentsRepository { | |||||||
|   public async $getAdjustments(interval: string | null, descOrder: boolean = false): Promise<IndexedDifficultyAdjustment[]> { |   public async $getAdjustments(interval: string | null, descOrder: boolean = false): Promise<IndexedDifficultyAdjustment[]> { | ||||||
|     interval = Common.getSqlInterval(interval); |     interval = Common.getSqlInterval(interval); | ||||||
| 
 | 
 | ||||||
|     let query = `SELECT UNIX_TIMESTAMP(time) as time, height, difficulty, adjustment
 |     let query = `SELECT 
 | ||||||
|  |       CAST(AVG(UNIX_TIMESTAMP(time)) as INT) as time, | ||||||
|  |       CAST(AVG(height) AS INT) as height, | ||||||
|  |       CAST(AVG(difficulty) as DOUBLE) as difficulty, | ||||||
|  |       CAST(AVG(adjustment) as DOUBLE) as adjustment | ||||||
|       FROM difficulty_adjustments`;
 |       FROM difficulty_adjustments`;
 | ||||||
| 
 | 
 | ||||||
|     if (interval) { |     if (interval) { | ||||||
|       query += ` WHERE time BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; |       query += ` WHERE time BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     query += ` GROUP BY UNIX_TIMESTAMP(time) DIV ${86400}`; | ||||||
|  | 
 | ||||||
|     if (descOrder === true) { |     if (descOrder === true) { | ||||||
|       query += ` ORDER BY time DESC`; |       query += ` ORDER BY time DESC`; | ||||||
|     } else { |     } else { | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import { escape } from 'mysql2'; | import { escape } from 'mysql2'; | ||||||
| import { Common } from '../api/common'; | import { Common } from '../api/common'; | ||||||
|  | import config from '../config'; | ||||||
| import DB from '../database'; | import DB from '../database'; | ||||||
| import logger from '../logger'; | import logger from '../logger'; | ||||||
| import PoolsRepository from './PoolsRepository'; | import PoolsRepository from './PoolsRepository'; | ||||||
| @ -32,7 +33,9 @@ class HashratesRepository { | |||||||
|   public async $getNetworkDailyHashrate(interval: string | null): Promise<any[]> { |   public async $getNetworkDailyHashrate(interval: string | null): Promise<any[]> { | ||||||
|     interval = Common.getSqlInterval(interval); |     interval = Common.getSqlInterval(interval); | ||||||
| 
 | 
 | ||||||
|     let query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp, avg_hashrate as avgHashrate
 |     let query = `SELECT
 | ||||||
|  |       CAST(AVG(UNIX_TIMESTAMP(hashrate_timestamp)) as INT) as timestamp, | ||||||
|  |       CAST(AVG(avg_hashrate) as DOUBLE) as avgHashrate | ||||||
|       FROM hashrates`;
 |       FROM hashrates`;
 | ||||||
| 
 | 
 | ||||||
|     if (interval) { |     if (interval) { | ||||||
| @ -42,6 +45,7 @@ class HashratesRepository { | |||||||
|       query += ` WHERE hashrates.type = 'daily'`; |       query += ` WHERE hashrates.type = 'daily'`; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     query += ` GROUP BY UNIX_TIMESTAMP(hashrate_timestamp) DIV ${86400}`; | ||||||
|     query += ` ORDER by hashrate_timestamp`; |     query += ` ORDER by hashrate_timestamp`; | ||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ import { StorageService } from 'src/app/services/storage.service'; | |||||||
| import { MiningService } from 'src/app/services/mining.service'; | import { MiningService } from 'src/app/services/mining.service'; | ||||||
| import { download } from 'src/app/shared/graphs.utils'; | import { download } from 'src/app/shared/graphs.utils'; | ||||||
| import { ActivatedRoute } from '@angular/router'; | import { ActivatedRoute } from '@angular/router'; | ||||||
|  | import { StateService } from 'src/app/services/state.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-hashrate-chart', |   selector: 'app-hashrate-chart', | ||||||
| @ -47,7 +48,7 @@ export class HashrateChartComponent implements OnInit { | |||||||
|   formatNumber = formatNumber; |   formatNumber = formatNumber; | ||||||
|   timespan = ''; |   timespan = ''; | ||||||
|   chartInstance: any = undefined; |   chartInstance: any = undefined; | ||||||
|   maResolution: number =  30; |   network = ''; | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     @Inject(LOCALE_ID) public locale: string, |     @Inject(LOCALE_ID) public locale: string, | ||||||
| @ -57,10 +58,13 @@ export class HashrateChartComponent implements OnInit { | |||||||
|     private storageService: StorageService, |     private storageService: StorageService, | ||||||
|     private miningService: MiningService, |     private miningService: MiningService, | ||||||
|     private route: ActivatedRoute, |     private route: ActivatedRoute, | ||||||
|  |     private stateService: StateService | ||||||
|   ) { |   ) { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|  |     this.stateService.networkChanged$.subscribe((network) => this.network = network); | ||||||
|  | 
 | ||||||
|     let firstRun = true; |     let firstRun = true; | ||||||
| 
 | 
 | ||||||
|     if (this.widget) { |     if (this.widget) { | ||||||
| @ -124,17 +128,14 @@ export class HashrateChartComponent implements OnInit { | |||||||
|                   ++diffIndex; |                   ++diffIndex; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 this.maResolution = 30; |                 let maResolution = 15; | ||||||
|                 if (["3m", "6m"].includes(this.timespan)) { |  | ||||||
|                   this.maResolution = 7; |  | ||||||
|                 } |  | ||||||
|                 const hashrateMa = []; |                 const hashrateMa = []; | ||||||
|                 for (let i = this.maResolution - 1; i < data.hashrates.length; ++i) { |                 for (let i = maResolution - 1; i < data.hashrates.length; ++i) { | ||||||
|                   let avg = 0; |                   let avg = 0; | ||||||
|                   for (let y = this.maResolution - 1; y >= 0; --y) { |                   for (let y = maResolution - 1; y >= 0; --y) { | ||||||
|                     avg += data.hashrates[i - y].avgHashrate; |                     avg += data.hashrates[i - y].avgHashrate; | ||||||
|                   } |                   } | ||||||
|                   avg /= this.maResolution; |                   avg /= maResolution; | ||||||
|                   hashrateMa.push([data.hashrates[i].timestamp * 1000, avg]); |                   hashrateMa.push([data.hashrates[i].timestamp * 1000, avg]); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
| @ -276,7 +277,7 @@ export class HashrateChartComponent implements OnInit { | |||||||
|             }, |             }, | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             name: $localize`::Difficulty`, |             name: $localize`:@@25148835d92465353fc5fe8897c27d5369978e5a:Difficulty`, | ||||||
|             inactiveColor: 'rgb(110, 112, 121)', |             inactiveColor: 'rgb(110, 112, 121)', | ||||||
|             textStyle: { |             textStyle: { | ||||||
|               color: 'white', |               color: 'white', | ||||||
| @ -284,7 +285,7 @@ export class HashrateChartComponent implements OnInit { | |||||||
|             icon: 'roundRect', |             icon: 'roundRect', | ||||||
|           }, |           }, | ||||||
|           { |           { | ||||||
|             name: $localize`Hashrate` + ` (MA${this.maResolution})`, |             name: $localize`Hashrate (MA)`, | ||||||
|             inactiveColor: 'rgb(110, 112, 121)', |             inactiveColor: 'rgb(110, 112, 121)', | ||||||
|             textStyle: { |             textStyle: { | ||||||
|               color: 'white', |               color: 'white', | ||||||
| @ -295,11 +296,18 @@ export class HashrateChartComponent implements OnInit { | |||||||
|             }, |             }, | ||||||
|           }, |           }, | ||||||
|         ], |         ], | ||||||
|  |         selected: JSON.parse(this.storageService.getValue('hashrate_difficulty_legend')) ?? { | ||||||
|  |           '$localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`': true, | ||||||
|  |           '$localize`::Difficulty`': this.network === '', | ||||||
|  |           '$localize`Hashrate (MA)`': true, | ||||||
|  |         }, | ||||||
|       }, |       }, | ||||||
|       yAxis: data.hashrates.length === 0 ? undefined : [ |       yAxis: data.hashrates.length === 0 ? undefined : [ | ||||||
|         { |         { | ||||||
|           min: (value) => { |           min: (value) => { | ||||||
|             return value.min * 0.9; |             const selectedPowerOfTen: any = selectPowerOfTen(value.min); | ||||||
|  |             const newMin = Math.floor(value.min / selectedPowerOfTen.divider / 10); | ||||||
|  |             return newMin * selectedPowerOfTen.divider * 10; | ||||||
|           }, |           }, | ||||||
|           type: 'value', |           type: 'value', | ||||||
|           axisLabel: { |           axisLabel: { | ||||||
| @ -363,7 +371,7 @@ export class HashrateChartComponent implements OnInit { | |||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
|           zlevel: 2, |           zlevel: 2, | ||||||
|           name: $localize`Hashrate` + ` (MA${this.maResolution})`, |           name: $localize`Hashrate (MA)`, | ||||||
|           showSymbol: false, |           showSymbol: false, | ||||||
|           symbol: 'none', |           symbol: 'none', | ||||||
|           data: data.hashrateMa, |           data: data.hashrateMa, | ||||||
| @ -404,6 +412,10 @@ export class HashrateChartComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
|   onChartInit(ec) { |   onChartInit(ec) { | ||||||
|     this.chartInstance = ec; |     this.chartInstance = ec; | ||||||
|  | 
 | ||||||
|  |     this.chartInstance.on('legendselectchanged', (e) => { | ||||||
|  |       this.storageService.setValue('hashrate_difficulty_legend', JSON.stringify(e.selected)); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   isMobile() { |   isMobile() { | ||||||
|  | |||||||
| @ -40,7 +40,7 @@ | |||||||
|       <div class="card"> |       <div class="card"> | ||||||
|         <div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2"> |         <div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2"> | ||||||
|           <app-hashrate-chart [widget]="true"></app-hashrate-chart> |           <app-hashrate-chart [widget]="true"></app-hashrate-chart> | ||||||
|           <div class="mt-1"><a [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]" i18n="dashboard.view-more">View more »</a></div> |           <div class="mt-1"><a [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]" fragment="1y" i18n="dashboard.view-more">View more »</a></div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user