Index difficulty adjustments
This commit is contained in:
		
							parent
							
								
									570d8cfc74
								
							
						
					
					
						commit
						acfdc8163b
					
				@ -19,6 +19,9 @@ import HashratesRepository from '../repositories/HashratesRepository';
 | 
			
		||||
import indexer from '../indexer';
 | 
			
		||||
import poolsParser from './pools-parser';
 | 
			
		||||
import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository';
 | 
			
		||||
import mining from './mining';
 | 
			
		||||
import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository';
 | 
			
		||||
import difficultyAdjustment from './difficulty-adjustment';
 | 
			
		||||
 | 
			
		||||
class Blocks {
 | 
			
		||||
  private blocks: BlockExtended[] = [];
 | 
			
		||||
@ -449,7 +452,10 @@ class Blocks {
 | 
			
		||||
              const newBlock = await this.$indexBlock(lastBlock['height'] - i);
 | 
			
		||||
              await this.$getStrippedBlockTransactions(newBlock.id, true, true);
 | 
			
		||||
            }
 | 
			
		||||
            logger.info(`Re-indexed 10 blocks and summaries`);
 | 
			
		||||
            await mining.$indexDifficultyAdjustments();
 | 
			
		||||
            await DifficultyAdjustmentsRepository.$deleteLastAdjustment();
 | 
			
		||||
            logger.info(`Re-indexed 10 blocks and summaries. Also re-indexed the last difficulty adjustments. Will re-index latest hashrates in a few seconds.`);
 | 
			
		||||
            indexer.reindex();
 | 
			
		||||
          }
 | 
			
		||||
          await blocksRepository.$saveBlockInDatabase(blockExtended);
 | 
			
		||||
 | 
			
		||||
@ -461,6 +467,15 @@ class Blocks {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (block.height % 2016 === 0) {
 | 
			
		||||
        if (Common.indexingEnabled()) {
 | 
			
		||||
          await DifficultyAdjustmentsRepository.$saveAdjustments({
 | 
			
		||||
            time: block.timestamp,
 | 
			
		||||
            height: block.height,
 | 
			
		||||
            difficulty: block.difficulty,
 | 
			
		||||
            adjustment: Math.round((block.difficulty / this.currentDifficulty) * 1000000) / 1000000, // Remove float point noise
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.previousDifficultyRetarget = (block.difficulty - this.currentDifficulty) / this.currentDifficulty * 100;
 | 
			
		||||
        this.lastDifficultyAdjustmentTime = block.timestamp;
 | 
			
		||||
        this.currentDifficulty = block.difficulty;
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ import logger from '../logger';
 | 
			
		||||
import { Common } from './common';
 | 
			
		||||
 | 
			
		||||
class DatabaseMigration {
 | 
			
		||||
  private static currentVersion = 21;
 | 
			
		||||
  private static currentVersion = 22;
 | 
			
		||||
  private queryTimeout = 120000;
 | 
			
		||||
  private statisticsAddedIndexed = false;
 | 
			
		||||
  private uniqueLogs: string[] = [];
 | 
			
		||||
@ -226,6 +226,11 @@ class DatabaseMigration {
 | 
			
		||||
        await this.$executeQuery('DROP TABLE IF EXISTS `rates`');
 | 
			
		||||
        await this.$executeQuery(this.getCreatePricesTableQuery(), await this.$checkIfTableExists('prices'));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (databaseSchemaVersion < 22 && isBitcoin === true) {
 | 
			
		||||
        await this.$executeQuery('DROP TABLE IF EXISTS `difficulty_adjustments`');
 | 
			
		||||
        await this.$executeQuery(this.getCreateDifficultyAdjustmentsTableQuery(), await this.$checkIfTableExists('difficulty_adjustments'));
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
@ -513,7 +518,7 @@ class DatabaseMigration {
 | 
			
		||||
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getCreateRatesTableQuery(): string {
 | 
			
		||||
  private getCreateRatesTableQuery(): string { // This table has been replaced by the prices table
 | 
			
		||||
    return `CREATE TABLE IF NOT EXISTS rates (
 | 
			
		||||
      height int(10) unsigned NOT NULL,
 | 
			
		||||
      bisq_rates JSON NOT NULL,
 | 
			
		||||
@ -539,6 +544,17 @@ class DatabaseMigration {
 | 
			
		||||
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getCreateDifficultyAdjustmentsTableQuery(): string {
 | 
			
		||||
    return `CREATE TABLE IF NOT EXISTS difficulty_adjustments (
 | 
			
		||||
      time timestamp NOT NULL,
 | 
			
		||||
      height int(10) unsigned NOT NULL,
 | 
			
		||||
      difficulty double unsigned NOT NULL,
 | 
			
		||||
      adjustment float NOT NULL,
 | 
			
		||||
      PRIMARY KEY (height),
 | 
			
		||||
      INDEX (time)
 | 
			
		||||
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $truncateIndexedData(tables: string[]) {
 | 
			
		||||
    const allowedTables = ['blocks', 'hashrates', 'prices'];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { PoolInfo, PoolStats, RewardStats } from '../mempool.interfaces';
 | 
			
		||||
import { IndexedDifficultyAdjustment, PoolInfo, PoolStats, RewardStats } from '../mempool.interfaces';
 | 
			
		||||
import BlocksRepository from '../repositories/BlocksRepository';
 | 
			
		||||
import PoolsRepository from '../repositories/PoolsRepository';
 | 
			
		||||
import HashratesRepository from '../repositories/HashratesRepository';
 | 
			
		||||
@ -7,6 +7,7 @@ import logger from '../logger';
 | 
			
		||||
import { Common } from './common';
 | 
			
		||||
import loadingIndicators from './loading-indicators';
 | 
			
		||||
import { escape } from 'mysql2';
 | 
			
		||||
import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository';
 | 
			
		||||
 | 
			
		||||
class Mining {
 | 
			
		||||
  constructor() {
 | 
			
		||||
@ -377,6 +378,48 @@ class Mining {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Index difficulty adjustments
 | 
			
		||||
   */
 | 
			
		||||
  public async $indexDifficultyAdjustments(): Promise<void> {
 | 
			
		||||
    const indexedHeightsArray = await DifficultyAdjustmentsRepository.$getAdjustmentsHeights();
 | 
			
		||||
    const indexedHeights = {};
 | 
			
		||||
    for (const height of indexedHeightsArray) {
 | 
			
		||||
      indexedHeights[height] = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const blocks: any = await BlocksRepository.$getBlocksDifficulty();
 | 
			
		||||
 | 
			
		||||
    let currentDifficulty = 0;
 | 
			
		||||
    let totalIndexed = 0;
 | 
			
		||||
 | 
			
		||||
    for (const block of blocks) {
 | 
			
		||||
      if (block.difficulty !== currentDifficulty) {
 | 
			
		||||
        if (block.height === 0 || indexedHeights[block.height] === true) { // Already indexed
 | 
			
		||||
          currentDifficulty = block.difficulty;
 | 
			
		||||
          continue;          
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let adjustment = block.difficulty / Math.max(1, currentDifficulty);
 | 
			
		||||
        adjustment = Math.round(adjustment * 1000000) / 1000000; // Remove float point noise
 | 
			
		||||
 | 
			
		||||
        await DifficultyAdjustmentsRepository.$saveAdjustments({
 | 
			
		||||
          time: block.time,
 | 
			
		||||
          height: block.height,
 | 
			
		||||
          difficulty: block.difficulty,
 | 
			
		||||
          adjustment: adjustment,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        totalIndexed++;
 | 
			
		||||
        currentDifficulty = block.difficulty;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (totalIndexed > 0) {
 | 
			
		||||
      logger.notice(`Indexed ${totalIndexed} difficulty adjustments`);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getDateMidnight(date: Date): Date {
 | 
			
		||||
    date.setUTCHours(0);
 | 
			
		||||
    date.setUTCMinutes(0);
 | 
			
		||||
 | 
			
		||||
@ -290,6 +290,7 @@ class Server {
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/rewards/:interval', routes.$getHistoricalBlockRewards)
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fee-rates/:interval', routes.$getHistoricalBlockFeeRates)
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/sizes-weights/:interval', routes.$getHistoricalBlockSizeAndWeight)
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments/:interval', routes.$getDifficultyAdjustments)
 | 
			
		||||
        ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -36,6 +36,7 @@ class Indexer {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      await mining.$indexDifficultyAdjustments();
 | 
			
		||||
      await this.$resetHashratesIndexingState();
 | 
			
		||||
      await mining.$generateNetworkHashrateHistory();
 | 
			
		||||
      await mining.$generatePoolHashrateHistory();
 | 
			
		||||
 | 
			
		||||
@ -224,6 +224,13 @@ export interface IDifficultyAdjustment {
 | 
			
		||||
  timeOffset: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IndexedDifficultyAdjustment {
 | 
			
		||||
  time: number; // UNIX timestamp
 | 
			
		||||
  height: number; // Block height
 | 
			
		||||
  difficulty: number;
 | 
			
		||||
  adjustment: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface RewardStats {
 | 
			
		||||
  totalReward: number;
 | 
			
		||||
  totalFee: number;
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ import PoolsRepository from './PoolsRepository';
 | 
			
		||||
import HashratesRepository from './HashratesRepository';
 | 
			
		||||
import { escape } from 'mysql2';
 | 
			
		||||
import BlocksSummariesRepository from './BlocksSummariesRepository';
 | 
			
		||||
import DifficultyAdjustmentsRepository from './DifficultyAdjustmentsRepository';
 | 
			
		||||
 | 
			
		||||
class BlocksRepository {
 | 
			
		||||
  /**
 | 
			
		||||
@ -381,48 +382,9 @@ class BlocksRepository {
 | 
			
		||||
  /**
 | 
			
		||||
   * Return blocks difficulty
 | 
			
		||||
   */
 | 
			
		||||
  public async $getBlocksDifficulty(interval: string | null): Promise<object[]> {
 | 
			
		||||
    interval = Common.getSqlInterval(interval);
 | 
			
		||||
 | 
			
		||||
    // :D ... Yeah don't ask me about this one https://stackoverflow.com/a/40303162
 | 
			
		||||
    // Basically, using temporary user defined fields, we are able to extract all
 | 
			
		||||
    // difficulty adjustments from the blocks tables.
 | 
			
		||||
    // This allow use to avoid indexing it in another table.
 | 
			
		||||
    let query = `
 | 
			
		||||
      SELECT
 | 
			
		||||
      *
 | 
			
		||||
      FROM
 | 
			
		||||
      (
 | 
			
		||||
        SELECT
 | 
			
		||||
        UNIX_TIMESTAMP(blockTimestamp) as timestamp, difficulty, height,
 | 
			
		||||
        IF(@prevStatus = YT.difficulty, @rn := @rn + 1,
 | 
			
		||||
          IF(@prevStatus := YT.difficulty, @rn := 1, @rn := 1)
 | 
			
		||||
        ) AS rn
 | 
			
		||||
        FROM blocks YT
 | 
			
		||||
        CROSS JOIN
 | 
			
		||||
        (
 | 
			
		||||
          SELECT @prevStatus := -1, @rn := 1
 | 
			
		||||
        ) AS var
 | 
			
		||||
    `;
 | 
			
		||||
 | 
			
		||||
    if (interval) {
 | 
			
		||||
      query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    query += `
 | 
			
		||||
        ORDER BY YT.height
 | 
			
		||||
      ) AS t
 | 
			
		||||
      WHERE t.rn = 1
 | 
			
		||||
      ORDER BY t.height
 | 
			
		||||
    `;
 | 
			
		||||
 | 
			
		||||
  public async $getBlocksDifficulty(): Promise<object[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const [rows]: any[] = await DB.query(query);
 | 
			
		||||
 | 
			
		||||
      for (const row of rows) {
 | 
			
		||||
        delete row['rn'];
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const [rows]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(blockTimestamp) as time, height, difficulty FROM blocks`);
 | 
			
		||||
      return rows;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err('Cannot generate difficulty history. Reason: ' + (e instanceof Error ? e.message : e));
 | 
			
		||||
@ -452,26 +414,6 @@ class BlocksRepository {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
   * Check if the last 10 blocks chain is valid
 | 
			
		||||
   */
 | 
			
		||||
  public async $validateRecentBlocks(): Promise<boolean> {
 | 
			
		||||
    try {
 | 
			
		||||
      const [lastBlocks]: any[] = await DB.query(`SELECT height, hash, previous_block_hash FROM blocks ORDER BY height DESC LIMIT 10`);
 | 
			
		||||
 | 
			
		||||
      for (let i = 0; i < lastBlocks.length - 1; ++i) {
 | 
			
		||||
        if (lastBlocks[i].previous_block_hash !== lastBlocks[i + 1].hash) {
 | 
			
		||||
          logger.warn(`Chain divergence detected at block ${lastBlocks[i].height}, re-indexing most recent data`);
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return true;
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      return true; // Don't do anything if there is a db error
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check if the chain of block hash is valid and delete data from the stale branch if needed
 | 
			
		||||
   */
 | 
			
		||||
@ -498,6 +440,7 @@ class BlocksRepository {
 | 
			
		||||
          await this.$deleteBlocksFrom(blocks[idx - 1].height);
 | 
			
		||||
          await BlocksSummariesRepository.$deleteBlocksFrom(blocks[idx - 1].height);
 | 
			
		||||
          await HashratesRepository.$deleteHashratesFromTimestamp(blocks[idx - 1].timestamp - 604800);
 | 
			
		||||
          await DifficultyAdjustmentsRepository.$deleteAdjustementsFromHeight(blocks[idx - 1].height);
 | 
			
		||||
          return false;
 | 
			
		||||
        }
 | 
			
		||||
        ++idx;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										88
									
								
								backend/src/repositories/DifficultyAdjustmentsRepository.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								backend/src/repositories/DifficultyAdjustmentsRepository.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,88 @@
 | 
			
		||||
import { Common } from '../api/common';
 | 
			
		||||
import DB from '../database';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
import { IndexedDifficultyAdjustment } from '../mempool.interfaces';
 | 
			
		||||
 | 
			
		||||
class DifficultyAdjustmentsRepository {
 | 
			
		||||
  public async $saveAdjustments(adjustment: IndexedDifficultyAdjustment): Promise<void> {
 | 
			
		||||
    if (adjustment.height === 1) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const query = `INSERT INTO difficulty_adjustments(time, height, difficulty, adjustment) VALUE (FROM_UNIXTIME(?), ?, ?, ?)`;
 | 
			
		||||
      const params: any[] = [
 | 
			
		||||
        adjustment.time,
 | 
			
		||||
        adjustment.height,
 | 
			
		||||
        adjustment.difficulty,
 | 
			
		||||
        adjustment.adjustment,
 | 
			
		||||
      ];
 | 
			
		||||
      await DB.query(query, params);
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
      if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
 | 
			
		||||
        logger.debug(`Cannot save difficulty adjustment at block ${adjustment.height}, already indexed, ignoring`);
 | 
			
		||||
      } else {
 | 
			
		||||
        logger.err(`Cannot save difficulty adjustment at block ${adjustment.height}. Reason: ${e instanceof Error ? e.message : e}`);
 | 
			
		||||
        throw e;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getAdjustments(interval: string | null, descOrder: boolean = false): Promise<IndexedDifficultyAdjustment[]> {
 | 
			
		||||
    interval = Common.getSqlInterval(interval);
 | 
			
		||||
 | 
			
		||||
    let query = `SELECT UNIX_TIMESTAMP(time) as time, height, difficulty, adjustment
 | 
			
		||||
      FROM difficulty_adjustments`;
 | 
			
		||||
 | 
			
		||||
    if (interval) {
 | 
			
		||||
      query += ` WHERE time BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (descOrder === true) {
 | 
			
		||||
      query += ` ORDER BY time DESC`;
 | 
			
		||||
    } else {
 | 
			
		||||
      query += ` ORDER BY time`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const [rows] = await DB.query(query);
 | 
			
		||||
      return rows as IndexedDifficultyAdjustment[];
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      logger.err(`Cannot get difficulty adjustments from the database. Reason: ` + (e instanceof Error ? e.message : e));
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getAdjustmentsHeights(): Promise<number[]> {
 | 
			
		||||
    try {
 | 
			
		||||
      const [rows]: any[] = await DB.query(`SELECT height FROM difficulty_adjustments`);
 | 
			
		||||
      return rows.map(block => block.height);
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
      logger.err(`Cannot get difficulty adjustment block heights. Reason: ${e instanceof Error ? e.message : e}`);
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $deleteAdjustementsFromHeight(height: number): Promise<void> {
 | 
			
		||||
    try {
 | 
			
		||||
      logger.info(`Delete newer difficulty adjustments from height ${height} from the database`);
 | 
			
		||||
      await DB.query(`DELETE FROM difficulty_adjustments WHERE height >= ?`, [height]);
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
      logger.err(`Cannot delete difficulty adjustments from the database. Reason: ${e instanceof Error ? e.message : e}`);
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $deleteLastAdjustment(): Promise<void> {
 | 
			
		||||
    try {
 | 
			
		||||
      logger.info(`Delete last difficulty adjustment from the database`);
 | 
			
		||||
      await DB.query(`DELETE FROM difficulty_adjustments ORDER BY time LIMIT 1`);
 | 
			
		||||
    } catch (e: any) {
 | 
			
		||||
      logger.err(`Cannot delete last difficulty adjustment from the database. Reason: ${e instanceof Error ? e.message : e}`);
 | 
			
		||||
      throw e;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new DifficultyAdjustmentsRepository();
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,7 @@ import mining from './api/mining';
 | 
			
		||||
import BlocksRepository from './repositories/BlocksRepository';
 | 
			
		||||
import HashratesRepository from './repositories/HashratesRepository';
 | 
			
		||||
import difficultyAdjustment from './api/difficulty-adjustment';
 | 
			
		||||
import DifficultyAdjustmentsRepository from './repositories/DifficultyAdjustmentsRepository';
 | 
			
		||||
 | 
			
		||||
class Routes {
 | 
			
		||||
  constructor() {}
 | 
			
		||||
@ -653,7 +654,7 @@ class Routes {
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const hashrates = await HashratesRepository.$getNetworkDailyHashrate(req.params.interval);
 | 
			
		||||
      const difficulty = await BlocksRepository.$getBlocksDifficulty(req.params.interval);
 | 
			
		||||
      const difficulty = await DifficultyAdjustmentsRepository.$getAdjustments(req.params.interval, false);
 | 
			
		||||
      const blockCount = await BlocksRepository.$blockCount(null, null);
 | 
			
		||||
      res.header('Pragma', 'public');
 | 
			
		||||
      res.header('Cache-control', 'public');
 | 
			
		||||
@ -730,6 +731,18 @@ class Routes {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getDifficultyAdjustments(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const difficulty = await DifficultyAdjustmentsRepository.$getAdjustments(req.params.interval, true);
 | 
			
		||||
      res.header('Pragma', 'public');
 | 
			
		||||
      res.header('Cache-control', 'public');
 | 
			
		||||
      res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
 | 
			
		||||
      res.json(difficulty.map(adj => [adj.time, adj.height, adj.difficulty, adj.adjustment]));
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).send(e instanceof Error ? e.message : e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async getBlock(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const block = await blocks.$getBlock(req.params.hash);
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@
 | 
			
		||||
      </tr>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody *ngIf="(hashrateObservable$ | async) as data">
 | 
			
		||||
      <tr *ngFor="let diffChange of data.difficulty">
 | 
			
		||||
      <tr *ngFor="let diffChange of data">
 | 
			
		||||
        <td class="d-none d-md-block"><a [routerLink]="['/block' | relativeUrl, diffChange.height]">{{ diffChange.height
 | 
			
		||||
            }}</a></td>
 | 
			
		||||
        <td class="text-left">
 | 
			
		||||
@ -17,7 +17,7 @@
 | 
			
		||||
        </td>
 | 
			
		||||
        <td class="text-right">{{ diffChange.difficultyShorten }}</td>
 | 
			
		||||
        <td class="text-right" [style]="diffChange.change >= 0 ? 'color: #42B747' : 'color: #B74242'">
 | 
			
		||||
          {{ diffChange.change >= 0 ? '+' : '' }}{{ diffChange.change | amountShortener }}%
 | 
			
		||||
          {{ diffChange.change >= 0 ? '+' : '' }}{{ diffChange.change | amountShortener: 2 }}%
 | 
			
		||||
        </td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </tbody>
 | 
			
		||||
 | 
			
		||||
@ -30,27 +30,24 @@ export class DifficultyAdjustmentsTable implements OnInit {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.hashrateObservable$ = this.apiService.getHistoricalHashrate$('1y')
 | 
			
		||||
    this.hashrateObservable$ = this.apiService.getDifficultyAdjustments$('3m')
 | 
			
		||||
      .pipe(
 | 
			
		||||
        map((response) => {
 | 
			
		||||
          const data = response.body;
 | 
			
		||||
          const tableData = [];
 | 
			
		||||
          for (let i = data.difficulty.length - 1; i > 0; --i) {
 | 
			
		||||
            const selectedPowerOfTen: any = selectPowerOfTen(data.difficulty[i].difficulty);
 | 
			
		||||
            const change = (data.difficulty[i].difficulty / data.difficulty[i - 1].difficulty - 1) * 100;
 | 
			
		||||
 | 
			
		||||
            tableData.push(Object.assign(data.difficulty[i], {
 | 
			
		||||
              change: Math.round(change * 100) / 100,
 | 
			
		||||
          for (const adjustment of data) {
 | 
			
		||||
            const selectedPowerOfTen: any = selectPowerOfTen(adjustment[2]);
 | 
			
		||||
            tableData.push({
 | 
			
		||||
              height: adjustment[1],
 | 
			
		||||
              timestamp: adjustment[0],
 | 
			
		||||
              change: (adjustment[3] - 1) * 100,
 | 
			
		||||
              difficultyShorten: formatNumber(
 | 
			
		||||
                data.difficulty[i].difficulty / selectedPowerOfTen.divider,
 | 
			
		||||
                adjustment[2] / selectedPowerOfTen.divider,
 | 
			
		||||
                this.locale, '1.2-2') + selectedPowerOfTen.unit
 | 
			
		||||
            }));
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
          this.isLoading = false;
 | 
			
		||||
 | 
			
		||||
          return {
 | 
			
		||||
            difficulty: tableData.slice(0, 6),
 | 
			
		||||
          };
 | 
			
		||||
          return tableData.slice(0, 6);
 | 
			
		||||
        }),
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -95,6 +95,7 @@ export class HashrateChartComponent implements OnInit {
 | 
			
		||||
            .pipe(
 | 
			
		||||
              tap((response) => {
 | 
			
		||||
                const data = response.body;
 | 
			
		||||
 | 
			
		||||
                // We generate duplicated data point so the tooltip works nicely
 | 
			
		||||
                const diffFixed = [];
 | 
			
		||||
                let diffIndex = 1;
 | 
			
		||||
@ -112,7 +113,7 @@ export class HashrateChartComponent implements OnInit {
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  while (hashIndex < data.hashrates.length && diffIndex < data.difficulty.length &&
 | 
			
		||||
                    data.hashrates[hashIndex].timestamp <= data.difficulty[diffIndex].timestamp
 | 
			
		||||
                    data.hashrates[hashIndex].timestamp <= data.difficulty[diffIndex].time
 | 
			
		||||
                  ) {
 | 
			
		||||
                    diffFixed.push({
 | 
			
		||||
                      timestamp: data.hashrates[hashIndex].timestamp,
 | 
			
		||||
 | 
			
		||||
@ -140,7 +140,7 @@ export class ApiService {
 | 
			
		||||
      this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pools` +
 | 
			
		||||
      (interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  }  
 | 
			
		||||
 | 
			
		||||
  getPoolStats$(slug: string): Observable<PoolStat> {
 | 
			
		||||
    return this.httpClient.get<PoolStat>(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}`);
 | 
			
		||||
@ -172,6 +172,13 @@ export class ApiService {
 | 
			
		||||
    return this.httpClient.get<TransactionStripped[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash + '/summary');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getDifficultyAdjustments$(interval: string | undefined): Observable<any> {
 | 
			
		||||
    return this.httpClient.get<any[]>(
 | 
			
		||||
        this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty-adjustments` +
 | 
			
		||||
        (interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getHistoricalHashrate$(interval: string | undefined): Observable<any> {
 | 
			
		||||
    return this.httpClient.get<any[]>(
 | 
			
		||||
        this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/hashrate` +
 | 
			
		||||
 | 
			
		||||
@ -67,6 +67,16 @@ do for url in / \
 | 
			
		||||
	'/api/v1/mining/blocks/fee-rates/2y' \
 | 
			
		||||
	'/api/v1/mining/blocks/fee-rates/3y' \
 | 
			
		||||
	'/api/v1/mining/blocks/fee-rates/all' \
 | 
			
		||||
	'/api/v1/mining/difficulty-adjustments/24h' \
 | 
			
		||||
	'/api/v1/mining/difficulty-adjustments/3d' \
 | 
			
		||||
	'/api/v1/mining/difficulty-adjustments/1w' \
 | 
			
		||||
	'/api/v1/mining/difficulty-adjustments/1m' \
 | 
			
		||||
	'/api/v1/mining/difficulty-adjustments/3m' \
 | 
			
		||||
	'/api/v1/mining/difficulty-adjustments/6m' \
 | 
			
		||||
	'/api/v1/mining/difficulty-adjustments/1y' \
 | 
			
		||||
	'/api/v1/mining/difficulty-adjustments/2y' \
 | 
			
		||||
	'/api/v1/mining/difficulty-adjustments/3y' \
 | 
			
		||||
	'/api/v1/mining/difficulty-adjustments/all' \
 | 
			
		||||
 | 
			
		||||
	do
 | 
			
		||||
		curl -s "https://${hostname}${url}" >/dev/null
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user