Basic block indexing WIP - Default mining pool icon - Only show mining hashrate on 1d scale
This commit is contained in:
		
							parent
							
								
									b9a047b22d
								
							
						
					
					
						commit
						4b9bfd6ca0
					
				@ -7,10 +7,10 @@ import { Common } from './common';
 | 
				
			|||||||
import diskCache from './disk-cache';
 | 
					import diskCache from './disk-cache';
 | 
				
			||||||
import transactionUtils from './transaction-utils';
 | 
					import transactionUtils from './transaction-utils';
 | 
				
			||||||
import bitcoinClient from './bitcoin/bitcoin-client';
 | 
					import bitcoinClient from './bitcoin/bitcoin-client';
 | 
				
			||||||
import { DB } from '../database';
 | 
					 | 
				
			||||||
import { IEsploraApi } from './bitcoin/esplora-api.interface';
 | 
					import { IEsploraApi } from './bitcoin/esplora-api.interface';
 | 
				
			||||||
import poolsRepository from '../repositories/PoolsRepository';
 | 
					import poolsRepository from '../repositories/PoolsRepository';
 | 
				
			||||||
import blocksRepository from '../repositories/BlocksRepository';
 | 
					import blocksRepository from '../repositories/BlocksRepository';
 | 
				
			||||||
 | 
					import BitcoinApi from './bitcoin/bitcoin-api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Blocks {
 | 
					class Blocks {
 | 
				
			||||||
  private blocks: BlockExtended[] = [];
 | 
					  private blocks: BlockExtended[] = [];
 | 
				
			||||||
@ -146,22 +146,41 @@ class Blocks {
 | 
				
			|||||||
   * Index all blocks metadata for the mining dashboard
 | 
					   * Index all blocks metadata for the mining dashboard
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public async $generateBlockDatabase() {
 | 
					  public async $generateBlockDatabase() {
 | 
				
			||||||
    let currentBlockHeight = await bitcoinApi.$getBlockHeightTip();
 | 
					    let currentBlockHeight = await bitcoinClient.getBlockCount();
 | 
				
			||||||
    let maxBlocks = 1008*2; // tmp
 | 
					    const indexedBlockCount = await blocksRepository.$blockCount();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    while (currentBlockHeight-- > 0 && maxBlocks-- > 0) {
 | 
					    logger.info(`Starting block indexing. Current tip at block #${currentBlockHeight}`);
 | 
				
			||||||
      if (await blocksRepository.$isBlockAlreadyIndexed(currentBlockHeight)) {
 | 
					    logger.info(`Need to index ${currentBlockHeight - indexedBlockCount} blocks. Working on it!`);
 | 
				
			||||||
        // logger.debug(`Block #${currentBlockHeight} already indexed, skipping`);
 | 
					
 | 
				
			||||||
 | 
					    const chunkSize = 10000;
 | 
				
			||||||
 | 
					    while (currentBlockHeight >= 0) {
 | 
				
			||||||
 | 
					      const endBlock = Math.max(0, currentBlockHeight - chunkSize + 1);
 | 
				
			||||||
 | 
					      const missingBlockHeights: number[] = await blocksRepository.$getMissingBlocksBetweenHeights(
 | 
				
			||||||
 | 
					        currentBlockHeight, endBlock);
 | 
				
			||||||
 | 
					      if (missingBlockHeights.length <= 0) {
 | 
				
			||||||
 | 
					        logger.debug(`No missing blocks between #${currentBlockHeight} to #${endBlock}, moving on`);
 | 
				
			||||||
 | 
					        currentBlockHeight -= chunkSize;
 | 
				
			||||||
        continue;
 | 
					        continue;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      logger.debug(`Indexing block #${currentBlockHeight}`);
 | 
					
 | 
				
			||||||
      const blockHash = await bitcoinApi.$getBlockHash(currentBlockHeight);
 | 
					      logger.info(`Indexing ${chunkSize} blocks from #${currentBlockHeight} to #${endBlock}`);
 | 
				
			||||||
      const block = await bitcoinApi.$getBlock(blockHash);
 | 
					
 | 
				
			||||||
      const transactions = await this.$getTransactionsExtended(blockHash, block.height, true);
 | 
					      for (const blockHeight of missingBlockHeights) {
 | 
				
			||||||
      const blockExtended = this.getBlockExtended(block, transactions);
 | 
					        try {
 | 
				
			||||||
      const miner = await this.$findBlockMiner(blockExtended.coinbaseTx);
 | 
					          logger.debug(`Indexing block #${blockHeight}`);
 | 
				
			||||||
      const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true);
 | 
					          const blockHash = await bitcoinApi.$getBlockHash(blockHeight);
 | 
				
			||||||
      await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner);
 | 
					          const block = await bitcoinApi.$getBlock(blockHash);
 | 
				
			||||||
 | 
					          const transactions = await this.$getTransactionsExtended(blockHash, block.height, true);
 | 
				
			||||||
 | 
					          const blockExtended = this.getBlockExtended(block, transactions);
 | 
				
			||||||
 | 
					          const miner = await this.$findBlockMiner(blockExtended.coinbaseTx);
 | 
				
			||||||
 | 
					          const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true);
 | 
				
			||||||
 | 
					          await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					          logger.err(`Something went wrong while indexing blocks.` + e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      currentBlockHeight -= chunkSize;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ import logger from '../logger';
 | 
				
			|||||||
const sleep = (ms: number) => new Promise(res => setTimeout(res, ms));
 | 
					const sleep = (ms: number) => new Promise(res => setTimeout(res, ms));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DatabaseMigration {
 | 
					class DatabaseMigration {
 | 
				
			||||||
  private static currentVersion = 3;
 | 
					  private static currentVersion = 4;
 | 
				
			||||||
  private queryTimeout = 120000;
 | 
					  private queryTimeout = 120000;
 | 
				
			||||||
  private statisticsAddedIndexed = false;
 | 
					  private statisticsAddedIndexed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -85,6 +85,8 @@ class DatabaseMigration {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      if (databaseSchemaVersion < 3) {
 | 
					      if (databaseSchemaVersion < 3) {
 | 
				
			||||||
        await this.$executeQuery(connection, this.getCreatePoolsTableQuery(), await this.$checkIfTableExists('pools'));
 | 
					        await this.$executeQuery(connection, this.getCreatePoolsTableQuery(), await this.$checkIfTableExists('pools'));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (databaseSchemaVersion < 4) {
 | 
				
			||||||
        await this.$executeQuery(connection, this.getCreateBlocksTableQuery(), await this.$checkIfTableExists('blocks'));
 | 
					        await this.$executeQuery(connection, this.getCreateBlocksTableQuery(), await this.$checkIfTableExists('blocks'));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      connection.release();
 | 
					      connection.release();
 | 
				
			||||||
 | 
				
			|||||||
@ -1,33 +1,30 @@
 | 
				
			|||||||
import { PoolInfo, PoolStats } from "../mempool.interfaces";
 | 
					import { PoolInfo, PoolStats } from '../mempool.interfaces';
 | 
				
			||||||
import BlocksRepository, { EmptyBlocks } from "../repositories/BlocksRepository";
 | 
					import BlocksRepository, { EmptyBlocks } from '../repositories/BlocksRepository';
 | 
				
			||||||
import PoolsRepository from "../repositories/PoolsRepository";
 | 
					import PoolsRepository from '../repositories/PoolsRepository';
 | 
				
			||||||
import bitcoinClient from "./bitcoin/bitcoin-client";
 | 
					import bitcoinClient from './bitcoin/bitcoin-client';
 | 
				
			||||||
import BitcoinApi from "./bitcoin/bitcoin-api";
 | 
					import BitcoinApi from './bitcoin/bitcoin-api';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Mining {
 | 
					class Mining {
 | 
				
			||||||
  private bitcoinApi: BitcoinApi;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor() {
 | 
					  constructor() {
 | 
				
			||||||
    this.bitcoinApi = new BitcoinApi(bitcoinClient);
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Generate high level overview of the pool ranks and general stats
 | 
					   * Generate high level overview of the pool ranks and general stats
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public async $getPoolsStats(interval: string = "100 YEAR") : Promise<object> {
 | 
					  public async $getPoolsStats(interval: string = '100 YEAR') : Promise<object> {
 | 
				
			||||||
    let poolsStatistics = {};
 | 
					    const poolsStatistics = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const blockHeightTip = await this.bitcoinApi.$getBlockHeightTip();
 | 
					    const blockHeightTip = await bitcoinClient.getBlockCount();
 | 
				
			||||||
    const lastBlockHashrate = await this.bitcoinApi.$getEstimatedHashrate(blockHeightTip);
 | 
					    const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(120, blockHeightTip);
 | 
				
			||||||
    const poolsInfo: PoolInfo[] = await PoolsRepository.$getPoolsInfo(interval);
 | 
					    const poolsInfo: PoolInfo[] = await PoolsRepository.$getPoolsInfo(interval);
 | 
				
			||||||
    const blockCount: number = await BlocksRepository.$blockCount(interval);
 | 
					    const blockCount: number = await BlocksRepository.$blockCount(interval);
 | 
				
			||||||
    const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$countEmptyBlocks(interval);
 | 
					    const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$countEmptyBlocks(interval);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let poolsStats: PoolStats[] = [];
 | 
					    const poolsStats: PoolStats[] = [];
 | 
				
			||||||
    let rank = 1;
 | 
					    let rank = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    poolsInfo.forEach((poolInfo: PoolInfo) => {
 | 
					    poolsInfo.forEach((poolInfo: PoolInfo) => {
 | 
				
			||||||
      let poolStat: PoolStats = {
 | 
					      const poolStat: PoolStats = {
 | 
				
			||||||
        poolId: poolInfo.poolId, // mysql row id
 | 
					        poolId: poolInfo.poolId, // mysql row id
 | 
				
			||||||
        name: poolInfo.name,
 | 
					        name: poolInfo.name,
 | 
				
			||||||
        link: poolInfo.link,
 | 
					        link: poolInfo.link,
 | 
				
			||||||
@ -41,11 +38,11 @@ class Mining {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      poolsStats.push(poolStat);
 | 
					      poolsStats.push(poolStat);
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    poolsStatistics["blockCount"] = blockCount;
 | 
					    poolsStatistics['blockCount'] = blockCount;
 | 
				
			||||||
    poolsStatistics["lastEstimatedHashrate"] = lastBlockHashrate;
 | 
					    poolsStatistics['lastEstimatedHashrate'] = lastBlockHashrate;
 | 
				
			||||||
    poolsStatistics["pools"] = poolsStats;
 | 
					    poolsStatistics['pools'] = poolsStats;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return poolsStatistics;
 | 
					    return poolsStatistics;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -26,6 +26,7 @@ import poolsParser from './api/pools-parser';
 | 
				
			|||||||
import syncAssets from './sync-assets';
 | 
					import syncAssets from './sync-assets';
 | 
				
			||||||
import icons from './api/liquid/icons';
 | 
					import icons from './api/liquid/icons';
 | 
				
			||||||
import { Common } from './api/common';
 | 
					import { Common } from './api/common';
 | 
				
			||||||
 | 
					import bitcoinClient from './api/bitcoin/bitcoin-client';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Server {
 | 
					class Server {
 | 
				
			||||||
  private wss: WebSocket.Server | undefined;
 | 
					  private wss: WebSocket.Server | undefined;
 | 
				
			||||||
@ -139,10 +140,13 @@ class Server {
 | 
				
			|||||||
      await blocks.$updateBlocks();
 | 
					      await blocks.$updateBlocks();
 | 
				
			||||||
      await memPool.$updateMempool();
 | 
					      await memPool.$updateMempool();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (this.blockIndexingStarted === false/* && memPool.isInSync()*/) {
 | 
					      const blockchainInfo = await bitcoinClient.getBlockchainInfo();
 | 
				
			||||||
 | 
					      if (this.blockIndexingStarted === false
 | 
				
			||||||
 | 
					        && memPool.isInSync()
 | 
				
			||||||
 | 
					        && blockchainInfo.blocks === blockchainInfo.headers
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
        blocks.$generateBlockDatabase();
 | 
					        blocks.$generateBlockDatabase();
 | 
				
			||||||
        this.blockIndexingStarted = true;
 | 
					        this.blockIndexingStarted = true;
 | 
				
			||||||
        logger.info("START OLDER BLOCK INDEXING");
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS);
 | 
					      setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,10 @@
 | 
				
			|||||||
import { BlockExtended, PoolTag } from "../mempool.interfaces";
 | 
					import { BlockExtended, PoolTag } from '../mempool.interfaces';
 | 
				
			||||||
import { DB } from "../database";
 | 
					import { DB } from '../database';
 | 
				
			||||||
import logger from "../logger";
 | 
					import logger from '../logger';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface EmptyBlocks {
 | 
					export interface EmptyBlocks {
 | 
				
			||||||
  emptyBlocks: number,
 | 
					  emptyBlocks: number;
 | 
				
			||||||
  poolId: number,
 | 
					  poolId: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BlocksRepository {
 | 
					class BlocksRepository {
 | 
				
			||||||
@ -21,9 +21,9 @@ class BlocksRepository {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const query = `INSERT INTO blocks(
 | 
					      const query = `INSERT INTO blocks(
 | 
				
			||||||
        height,  hash,     timestamp,    size,
 | 
					        height,  hash,     blockTimestamp, size,
 | 
				
			||||||
        weight,  tx_count, coinbase_raw, difficulty,
 | 
					        weight,  tx_count, coinbase_raw,   difficulty,
 | 
				
			||||||
        pool_id, fees,     fee_span,     median_fee
 | 
					        pool_id, fees,     fee_span,       median_fee
 | 
				
			||||||
      ) VALUE (
 | 
					      ) VALUE (
 | 
				
			||||||
        ?, ?, FROM_UNIXTIME(?), ?,
 | 
					        ?, ?, FROM_UNIXTIME(?), ?,
 | 
				
			||||||
        ?, ?, ?, ?,
 | 
					        ?, ?, ?, ?,
 | 
				
			||||||
@ -32,8 +32,8 @@ class BlocksRepository {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      const params: any[] = [
 | 
					      const params: any[] = [
 | 
				
			||||||
        block.height, blockHash, block.timestamp, block.size,
 | 
					        block.height, blockHash, block.timestamp, block.size,
 | 
				
			||||||
        block.weight, block.tx_count, coinbaseHex ? coinbaseHex : "", block.difficulty,
 | 
					        block.weight, block.tx_count, coinbaseHex ? coinbaseHex : '', block.difficulty,
 | 
				
			||||||
        poolTag.id, 0, "[]", block.medianFee,
 | 
					        poolTag.id, 0, '[]', block.medianFee,
 | 
				
			||||||
      ];
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      await connection.query(query, params);
 | 
					      await connection.query(query, params);
 | 
				
			||||||
@ -61,15 +61,36 @@ class BlocksRepository {
 | 
				
			|||||||
    return exists;
 | 
					    return exists;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get all block height that have not been indexed between [startHeight, endHeight]
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public async $getMissingBlocksBetweenHeights(startHeight: number, endHeight: number): Promise<number[]> {
 | 
				
			||||||
 | 
					    const connection = await DB.pool.getConnection();
 | 
				
			||||||
 | 
					    const [rows] : any[] = await connection.query(`
 | 
				
			||||||
 | 
					      SELECT height
 | 
				
			||||||
 | 
					      FROM blocks
 | 
				
			||||||
 | 
					      WHERE height <= ${startHeight} AND height >= ${endHeight}
 | 
				
			||||||
 | 
					      ORDER BY height DESC;
 | 
				
			||||||
 | 
					    `);
 | 
				
			||||||
 | 
					    connection.release();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const indexedBlockHeights: number[] = [];
 | 
				
			||||||
 | 
					    rows.forEach((row: any) => { indexedBlockHeights.push(row.height); });
 | 
				
			||||||
 | 
					    const seekedBlocks: number[] = Array.from(Array(startHeight - endHeight + 1).keys(), n => n + endHeight).reverse();
 | 
				
			||||||
 | 
					    const missingBlocksHeights =  seekedBlocks.filter(x => indexedBlockHeights.indexOf(x) === -1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return missingBlocksHeights;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Count empty blocks for all pools
 | 
					   * Count empty blocks for all pools
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public async $countEmptyBlocks(interval: string = "100 YEAR") : Promise<EmptyBlocks[]> {
 | 
					  public async $countEmptyBlocks(interval: string = '100 YEAR'): Promise<EmptyBlocks[]> {
 | 
				
			||||||
    const connection = await DB.pool.getConnection();
 | 
					    const connection = await DB.pool.getConnection();
 | 
				
			||||||
    const [rows] = await connection.query(`
 | 
					    const [rows] = await connection.query(`
 | 
				
			||||||
      SELECT pool_id as poolId
 | 
					      SELECT pool_id as poolId
 | 
				
			||||||
      FROM blocks
 | 
					      FROM blocks
 | 
				
			||||||
      WHERE timestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()
 | 
					      WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()
 | 
				
			||||||
      AND tx_count = 1;
 | 
					      AND tx_count = 1;
 | 
				
			||||||
    `);
 | 
					    `);
 | 
				
			||||||
    connection.release();
 | 
					    connection.release();
 | 
				
			||||||
@ -80,12 +101,12 @@ class BlocksRepository {
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Get blocks count for a period
 | 
					   * Get blocks count for a period
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
   public async $blockCount(interval: string = "100 YEAR") : Promise<number> {
 | 
					   public async $blockCount(interval: string = '100 YEAR'): Promise<number> {
 | 
				
			||||||
    const connection = await DB.pool.getConnection();
 | 
					    const connection = await DB.pool.getConnection();
 | 
				
			||||||
    const [rows] = await connection.query(`
 | 
					    const [rows] = await connection.query(`
 | 
				
			||||||
      SELECT count(height) as blockCount
 | 
					      SELECT count(height) as blockCount
 | 
				
			||||||
      FROM blocks
 | 
					      FROM blocks
 | 
				
			||||||
      WHERE timestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW();
 | 
					      WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW();
 | 
				
			||||||
    `);
 | 
					    `);
 | 
				
			||||||
    connection.release();
 | 
					    connection.release();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -31,7 +31,7 @@ class PoolsRepository {
 | 
				
			|||||||
      SELECT COUNT(height) as blockCount, pool_id as poolId, pools.name as name, pools.link as link
 | 
					      SELECT COUNT(height) as blockCount, pool_id as poolId, pools.name as name, pools.link as link
 | 
				
			||||||
      FROM blocks
 | 
					      FROM blocks
 | 
				
			||||||
      JOIN pools on pools.id = pool_id
 | 
					      JOIN pools on pools.id = pool_id
 | 
				
			||||||
      WHERE timestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()
 | 
					      WHERE blocks.blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()
 | 
				
			||||||
      GROUP BY pool_id
 | 
					      GROUP BY pool_id
 | 
				
			||||||
      ORDER BY COUNT(height) DESC;
 | 
					      ORDER BY COUNT(height) DESC;
 | 
				
			||||||
    `);
 | 
					    `);
 | 
				
			||||||
 | 
				
			|||||||
@ -50,7 +50,7 @@
 | 
				
			|||||||
      <th class="d-none d-md-block" i18n="latest-blocks.height">Rank</th>
 | 
					      <th class="d-none d-md-block" i18n="latest-blocks.height">Rank</th>
 | 
				
			||||||
      <th><!-- LOGO --></th>
 | 
					      <th><!-- LOGO --></th>
 | 
				
			||||||
      <th i18n="latest-blocks.timestamp">Name</th>
 | 
					      <th i18n="latest-blocks.timestamp">Name</th>
 | 
				
			||||||
      <th i18n="latest-blocks.timestamp">Hashrate</th>
 | 
					      <th *ngIf="this.poolsWindowPreference === '1d'" i18n="latest-blocks.timestamp">Hashrate</th>
 | 
				
			||||||
      <th i18n="latest-blocks.mined">Block Count (%)</th>
 | 
					      <th i18n="latest-blocks.mined">Block Count (%)</th>
 | 
				
			||||||
      <th class="d-none d-md-block" i18n="latest-blocks.transactions">Empty Blocks (%)</th>
 | 
					      <th class="d-none d-md-block" i18n="latest-blocks.transactions">Empty Blocks (%)</th>
 | 
				
			||||||
    </thead>
 | 
					    </thead>
 | 
				
			||||||
@ -59,15 +59,15 @@
 | 
				
			|||||||
        <td class="d-none d-md-block">-</td>
 | 
					        <td class="d-none d-md-block">-</td>
 | 
				
			||||||
        <td><!-- LOGO --></td>
 | 
					        <td><!-- LOGO --></td>
 | 
				
			||||||
        <td>All miners</td>
 | 
					        <td>All miners</td>
 | 
				
			||||||
        <td>{{ miningStats.lastEstimatedHashrate}} PH/s</td>
 | 
					        <td *ngIf="this.poolsWindowPreference === '1d'">{{ miningStats.lastEstimatedHashrate}} {{ miningStats.miningUnits.hashrateUnit }}</td>
 | 
				
			||||||
        <td>{{ miningStats.blockCount }}</td>
 | 
					        <td>{{ miningStats.blockCount }}</td>
 | 
				
			||||||
        <td class="d-none d-md-block">{{ miningStats.totalEmptyBlock }} ({{ miningStats.totalEmptyBlockRatio }}%)</td>
 | 
					        <td class="d-none d-md-block">{{ miningStats.totalEmptyBlock }} ({{ miningStats.totalEmptyBlockRatio }}%)</td>
 | 
				
			||||||
      </tr>
 | 
					      </tr>
 | 
				
			||||||
      <tr *ngFor="let pool of miningStats.pools">
 | 
					      <tr *ngFor="let pool of miningStats.pools">
 | 
				
			||||||
        <td class="d-none d-md-block">{{ pool.rank }}</td>
 | 
					        <td class="d-none d-md-block">{{ pool.rank }}</td>
 | 
				
			||||||
        <td><img width="25" height="25" src="{{ pool.logo }}"></td>
 | 
					        <td><img width="25" height="25" src="{{ pool.logo }}" onError="this.src = './resources/mining-pools/default.svg'"></td>
 | 
				
			||||||
        <td><a target="#" href="{{ pool.link }}">{{ pool.name }}</a></td>
 | 
					        <td><a target="#" href="{{ pool.link }}">{{ pool.name }}</a></td>
 | 
				
			||||||
        <td>{{ pool.lastEstimatedHashrate }} PH/s</td>
 | 
					        <td *ngIf="this.poolsWindowPreference === '1d'">{{ pool.lastEstimatedHashrate }} {{ miningStats.miningUnits.hashrateUnit }}</td>
 | 
				
			||||||
        <td>{{ pool.blockCount }} ({{ pool.share }}%)</td>
 | 
					        <td>{{ pool.blockCount }} ({{ pool.share }}%)</td>
 | 
				
			||||||
        <td class="d-none d-md-block">{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
 | 
					        <td class="d-none d-md-block">{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
 | 
				
			||||||
      </tr>
 | 
					      </tr>
 | 
				
			||||||
 | 
				
			|||||||
@ -2,10 +2,9 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
 | 
				
			|||||||
import { FormBuilder, FormGroup } from '@angular/forms';
 | 
					import { FormBuilder, FormGroup } from '@angular/forms';
 | 
				
			||||||
import { EChartsOption } from 'echarts';
 | 
					import { EChartsOption } from 'echarts';
 | 
				
			||||||
import { BehaviorSubject, Subscription } from 'rxjs';
 | 
					import { BehaviorSubject, Subscription } from 'rxjs';
 | 
				
			||||||
import { MiningStats } from 'src/app/interfaces/node-api.interface';
 | 
					 | 
				
			||||||
import { StateService } from 'src/app/services/state.service';
 | 
					import { StateService } from 'src/app/services/state.service';
 | 
				
			||||||
import { StorageService } from 'src/app/services/storage.service';
 | 
					import { StorageService } from 'src/app/services/storage.service';
 | 
				
			||||||
import { MiningService } from '../../services/mining.service';
 | 
					import { MiningService, MiningStats } from '../../services/mining.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Component({
 | 
					@Component({
 | 
				
			||||||
  selector: 'app-pool-ranking',
 | 
					  selector: 'app-pool-ranking',
 | 
				
			||||||
@ -70,7 +69,7 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  watchBlocks() {
 | 
					  watchBlocks() {
 | 
				
			||||||
    this.blocksSubscription = this.stateService.blocks$
 | 
					    this.blocksSubscription = this.stateService.blocks$
 | 
				
			||||||
      .subscribe(([block]) => {
 | 
					      .subscribe(() => {
 | 
				
			||||||
        if (!this.miningStats) {
 | 
					        if (!this.miningStats) {
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -98,9 +97,14 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
        label: { color: '#FFFFFF' },
 | 
					        label: { color: '#FFFFFF' },
 | 
				
			||||||
        tooltip: {
 | 
					        tooltip: {
 | 
				
			||||||
          formatter: () => {
 | 
					          formatter: () => {
 | 
				
			||||||
            return `<u><b>${pool.name}</b></u><br>` +
 | 
					            if (this.poolsWindowPreference === '1d') {
 | 
				
			||||||
              pool.lastEstimatedHashrate.toString() + ' PH/s (' + pool.share + `%)
 | 
					              return `<u><b>${pool.name}</b></u><br>` +
 | 
				
			||||||
              <br>(` + pool.blockCount.toString() + ` blocks)`;
 | 
					                pool.lastEstimatedHashrate.toString() + ' PH/s (' + pool.share + `%)
 | 
				
			||||||
 | 
					                <br>(` + pool.blockCount.toString() + ` blocks)`;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              return `<u><b>${pool.name}</b></u><br>` +
 | 
				
			||||||
 | 
					                pool.blockCount.toString() + ` blocks`;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
@ -111,8 +115,8 @@ export class PoolRankingComponent implements OnInit, OnDestroy {
 | 
				
			|||||||
  prepareChartOptions() {
 | 
					  prepareChartOptions() {
 | 
				
			||||||
    this.chartOptions = {
 | 
					    this.chartOptions = {
 | 
				
			||||||
      title: {
 | 
					      title: {
 | 
				
			||||||
        text: 'Hashrate distribution',
 | 
					        text: (this.poolsWindowPreference === '1d') ? 'Hashrate distribution' : 'Block distribution',
 | 
				
			||||||
        subtext: 'Estimated from the # of blocks mined',
 | 
					        subtext: (this.poolsWindowPreference === '1d') ? 'Estimated from the # of blocks mined' : null,
 | 
				
			||||||
        left: 'center',
 | 
					        left: 'center',
 | 
				
			||||||
        textStyle: {
 | 
					        textStyle: {
 | 
				
			||||||
          color: '#FFFFFF',
 | 
					          color: '#FFFFFF',
 | 
				
			||||||
 | 
				
			|||||||
@ -71,6 +71,8 @@ export interface PoolsStats {
 | 
				
			|||||||
  pools: SinglePoolStats[],
 | 
					  pools: SinglePoolStats[],
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ITranslators { [language: string]: string; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface MiningStats {
 | 
					export interface MiningStats {
 | 
				
			||||||
  lastEstimatedHashrate: string,
 | 
					  lastEstimatedHashrate: string,
 | 
				
			||||||
  blockCount: number,
 | 
					  blockCount: number,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,23 @@
 | 
				
			|||||||
import { Injectable } from '@angular/core';
 | 
					import { Injectable } from '@angular/core';
 | 
				
			||||||
import { Observable } from 'rxjs';
 | 
					import { Observable } from 'rxjs';
 | 
				
			||||||
import { map } from 'rxjs/operators';
 | 
					import { map } from 'rxjs/operators';
 | 
				
			||||||
import { MiningStats, PoolsStats } from '../interfaces/node-api.interface';
 | 
					import { PoolsStats, SinglePoolStats } from '../interfaces/node-api.interface';
 | 
				
			||||||
import { ApiService } from '../services/api.service';
 | 
					import { ApiService } from '../services/api.service';
 | 
				
			||||||
 | 
					import { StateService } from './state.service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface MiningUnits {
 | 
				
			||||||
 | 
					  hashrateDivider: number,
 | 
				
			||||||
 | 
					  hashrateUnit: string,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface MiningStats {
 | 
				
			||||||
 | 
					  lastEstimatedHashrate: string,
 | 
				
			||||||
 | 
					  blockCount: number,
 | 
				
			||||||
 | 
					  totalEmptyBlock: number,
 | 
				
			||||||
 | 
					  totalEmptyBlockRatio: string,
 | 
				
			||||||
 | 
					  pools: SinglePoolStats[],
 | 
				
			||||||
 | 
					  miningUnits: MiningUnits,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable({
 | 
					@Injectable({
 | 
				
			||||||
  providedIn: 'root'
 | 
					  providedIn: 'root'
 | 
				
			||||||
@ -10,6 +25,7 @@ import { ApiService } from '../services/api.service';
 | 
				
			|||||||
export class MiningService {
 | 
					export class MiningService {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
 | 
					    private stateService: StateService,
 | 
				
			||||||
    private apiService: ApiService,
 | 
					    private apiService: ApiService,
 | 
				
			||||||
  ) { }
 | 
					  ) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -19,7 +35,37 @@ export class MiningService {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Set the hashrate power of ten we want to display
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  public getMiningUnits() : MiningUnits {
 | 
				
			||||||
 | 
					    const powerTable = {
 | 
				
			||||||
 | 
					      0: "H/s",
 | 
				
			||||||
 | 
					      3: "kH/s",
 | 
				
			||||||
 | 
					      6: "MH/s",
 | 
				
			||||||
 | 
					      9: "GH/s",
 | 
				
			||||||
 | 
					      12: "TH/s",
 | 
				
			||||||
 | 
					      15: "PH/s",
 | 
				
			||||||
 | 
					      18: "EH/s",
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // I think it's fine to hardcode this since we don't have x1000 hashrate jump everyday
 | 
				
			||||||
 | 
					    // If we want to support the mining dashboard for testnet, we can hardcode it too
 | 
				
			||||||
 | 
					    let selectedPower = 15;
 | 
				
			||||||
 | 
					    if (this.stateService.network === 'testnet') {
 | 
				
			||||||
 | 
					      selectedPower = 12;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      hashrateDivider: Math.pow(10, selectedPower),
 | 
				
			||||||
 | 
					      hashrateUnit: powerTable[selectedPower],
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private generateMiningStats(stats: PoolsStats) : MiningStats {
 | 
					  private generateMiningStats(stats: PoolsStats) : MiningStats {
 | 
				
			||||||
 | 
					    const miningUnits = this.getMiningUnits();
 | 
				
			||||||
 | 
					    const hashrateDivider = miningUnits.hashrateDivider;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const totalEmptyBlock = Object.values(stats.pools).reduce((prev, cur) => {
 | 
					    const totalEmptyBlock = Object.values(stats.pools).reduce((prev, cur) => {
 | 
				
			||||||
      return prev + cur.emptyBlocks;
 | 
					      return prev + cur.emptyBlocks;
 | 
				
			||||||
    }, 0);
 | 
					    }, 0);
 | 
				
			||||||
@ -27,7 +73,7 @@ export class MiningService {
 | 
				
			|||||||
    const poolsStats = stats.pools.map((poolStat) => {
 | 
					    const poolsStats = stats.pools.map((poolStat) => {
 | 
				
			||||||
      return {
 | 
					      return {
 | 
				
			||||||
        share: (poolStat.blockCount / stats.blockCount * 100).toFixed(2),
 | 
					        share: (poolStat.blockCount / stats.blockCount * 100).toFixed(2),
 | 
				
			||||||
        lastEstimatedHashrate: (poolStat.blockCount / stats.blockCount * stats.lastEstimatedHashrate / Math.pow(10, 15)).toFixed(2),
 | 
					        lastEstimatedHashrate: (poolStat.blockCount / stats.blockCount * stats.lastEstimatedHashrate / hashrateDivider).toFixed(2),
 | 
				
			||||||
        emptyBlockRatio: (poolStat.emptyBlocks / poolStat.blockCount * 100).toFixed(2),
 | 
					        emptyBlockRatio: (poolStat.emptyBlocks / poolStat.blockCount * 100).toFixed(2),
 | 
				
			||||||
        logo: `./resources/mining-pools/` + poolStat.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg',
 | 
					        logo: `./resources/mining-pools/` + poolStat.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg',
 | 
				
			||||||
        ...poolStat
 | 
					        ...poolStat
 | 
				
			||||||
@ -35,11 +81,12 @@ export class MiningService {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      lastEstimatedHashrate: (stats.lastEstimatedHashrate / Math.pow(10, 15)).toFixed(2),
 | 
					      lastEstimatedHashrate: (stats.lastEstimatedHashrate / hashrateDivider).toFixed(2),
 | 
				
			||||||
      blockCount: stats.blockCount,
 | 
					      blockCount: stats.blockCount,
 | 
				
			||||||
      totalEmptyBlock: totalEmptyBlock,
 | 
					      totalEmptyBlock: totalEmptyBlock,
 | 
				
			||||||
      totalEmptyBlockRatio: totalEmptyBlockRatio,
 | 
					      totalEmptyBlockRatio: totalEmptyBlockRatio,
 | 
				
			||||||
      pools: poolsStats,
 | 
					      pools: poolsStats,
 | 
				
			||||||
 | 
					      miningUnits: miningUnits,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										69
									
								
								frontend/src/resources/mining-pools/default.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								frontend/src/resources/mining-pools/default.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 | 
				
			||||||
 | 
					<svg version="1.2" width="135.73mm" height="135.73mm" viewBox="0 0 13573 13573" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg" xmlns:ooo="http://xml.openoffice.org/svg/export" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:presentation="http://sun.com/xmlns/staroffice/presentation" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:anim="urn:oasis:names:tc:opendocument:xmlns:animation:1.0" xml:space="preserve">
 | 
				
			||||||
 | 
					 <defs class="ClipPathGroup">
 | 
				
			||||||
 | 
					  <clipPath id="presentation_clip_path" clipPathUnits="userSpaceOnUse">
 | 
				
			||||||
 | 
					   <rect x="0" y="0" width="13573" height="13573"/>
 | 
				
			||||||
 | 
					  </clipPath>
 | 
				
			||||||
 | 
					  <clipPath id="presentation_clip_path_shrink" clipPathUnits="userSpaceOnUse">
 | 
				
			||||||
 | 
					   <rect x="13" y="13" width="13546" height="13546"/>
 | 
				
			||||||
 | 
					  </clipPath>
 | 
				
			||||||
 | 
					 </defs>
 | 
				
			||||||
 | 
					 <defs class="TextShapeIndex">
 | 
				
			||||||
 | 
					  <g ooo:slide="id1" ooo:id-list="id3"/>
 | 
				
			||||||
 | 
					 </defs>
 | 
				
			||||||
 | 
					 <defs class="EmbeddedBulletChars">
 | 
				
			||||||
 | 
					  <g id="bullet-char-template-57356" transform="scale(0.00048828125,-0.00048828125)">
 | 
				
			||||||
 | 
					   <path d="M 580,1141 L 1163,571 580,0 -4,571 580,1141 Z"/>
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					  <g id="bullet-char-template-57354" transform="scale(0.00048828125,-0.00048828125)">
 | 
				
			||||||
 | 
					   <path d="M 8,1128 L 1137,1128 1137,0 8,0 8,1128 Z"/>
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					  <g id="bullet-char-template-10146" transform="scale(0.00048828125,-0.00048828125)">
 | 
				
			||||||
 | 
					   <path d="M 174,0 L 602,739 174,1481 1456,739 174,0 Z M 1358,739 L 309,1346 659,739 1358,739 Z"/>
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					  <g id="bullet-char-template-10132" transform="scale(0.00048828125,-0.00048828125)">
 | 
				
			||||||
 | 
					   <path d="M 2015,739 L 1276,0 717,0 1260,543 174,543 174,936 1260,936 717,1481 1274,1481 2015,739 Z"/>
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					  <g id="bullet-char-template-10007" transform="scale(0.00048828125,-0.00048828125)">
 | 
				
			||||||
 | 
					   <path d="M 0,-2 C -7,14 -16,27 -25,37 L 356,567 C 262,823 215,952 215,954 215,979 228,992 255,992 264,992 276,990 289,987 310,991 331,999 354,1012 L 381,999 492,748 772,1049 836,1024 860,1049 C 881,1039 901,1025 922,1006 886,937 835,863 770,784 769,783 710,716 594,584 L 774,223 C 774,196 753,168 711,139 L 727,119 C 717,90 699,76 672,76 641,76 570,178 457,381 L 164,-76 C 142,-110 111,-127 72,-127 30,-127 9,-110 8,-76 1,-67 -2,-52 -2,-32 -2,-23 -1,-13 0,-2 Z"/>
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					  <g id="bullet-char-template-10004" transform="scale(0.00048828125,-0.00048828125)">
 | 
				
			||||||
 | 
					   <path d="M 285,-33 C 182,-33 111,30 74,156 52,228 41,333 41,471 41,549 55,616 82,672 116,743 169,778 240,778 293,778 328,747 346,684 L 369,508 C 377,444 397,411 428,410 L 1163,1116 C 1174,1127 1196,1133 1229,1133 1271,1133 1292,1118 1292,1087 L 1292,965 C 1292,929 1282,901 1262,881 L 442,47 C 390,-6 338,-33 285,-33 Z"/>
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					  <g id="bullet-char-template-9679" transform="scale(0.00048828125,-0.00048828125)">
 | 
				
			||||||
 | 
					   <path d="M 813,0 C 632,0 489,54 383,161 276,268 223,411 223,592 223,773 276,916 383,1023 489,1130 632,1184 813,1184 992,1184 1136,1130 1245,1023 1353,916 1407,772 1407,592 1407,412 1353,268 1245,161 1136,54 992,0 813,0 Z"/>
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					  <g id="bullet-char-template-8226" transform="scale(0.00048828125,-0.00048828125)">
 | 
				
			||||||
 | 
					   <path d="M 346,457 C 273,457 209,483 155,535 101,586 74,649 74,723 74,796 101,859 155,911 209,963 273,989 346,989 419,989 480,963 531,910 582,859 608,796 608,723 608,648 583,586 532,535 482,483 420,457 346,457 Z"/>
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					  <g id="bullet-char-template-8211" transform="scale(0.00048828125,-0.00048828125)">
 | 
				
			||||||
 | 
					   <path d="M -4,459 L 1135,459 1135,606 -4,606 -4,459 Z"/>
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					  <g id="bullet-char-template-61548" transform="scale(0.00048828125,-0.00048828125)">
 | 
				
			||||||
 | 
					   <path d="M 173,740 C 173,903 231,1043 346,1159 462,1274 601,1332 765,1332 928,1332 1067,1274 1183,1159 1299,1043 1357,903 1357,740 1357,577 1299,437 1183,322 1067,206 928,148 765,148 601,148 462,206 346,322 231,437 173,577 173,740 Z"/>
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					 </defs>
 | 
				
			||||||
 | 
					 <g>
 | 
				
			||||||
 | 
					  <g id="id2" class="Master_Slide">
 | 
				
			||||||
 | 
					   <g id="bg-id2" class="Background"/>
 | 
				
			||||||
 | 
					   <g id="bo-id2" class="BackgroundObjects"/>
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					 </g>
 | 
				
			||||||
 | 
					 <g class="SlideGroup">
 | 
				
			||||||
 | 
					  <g>
 | 
				
			||||||
 | 
					   <g id="container-id1">
 | 
				
			||||||
 | 
					    <g id="id1" class="Slide" clip-path="url(#presentation_clip_path)">
 | 
				
			||||||
 | 
					     <g class="Page">
 | 
				
			||||||
 | 
					      <g class="com.sun.star.drawing.ClosedBezierShape">
 | 
				
			||||||
 | 
					       <g id="id3">
 | 
				
			||||||
 | 
					        <rect class="BoundingBox" stroke="none" fill="none" x="681" y="481" width="12413" height="12571"/>
 | 
				
			||||||
 | 
					        <path fill="rgb(178,178,178)" stroke="none" d="M 3025,482 C 2802,483 2580,504 2361,546 5189,2249 7300,4524 8967,7155 9034,5734 8462,4269 7551,3076 7178,3216 6719,3095 6402,2778 6085,2461 5964,2001 6103,1629 5158,916 4079,477 3025,482 Z M 11216,3076 L 12011,6397 10553,6630 10040,8762 11984,9797 10893,11277 11678,12442 9329,11711 9765,10551 7737,9655 8084,7418 5138,8956 5027,11026 2058,10295 1178,13050 13092,13050 13092,1022 11216,3076 Z M 6921,1567 C 6911,1567 6901,1567 6891,1568 6794,1577 6710,1613 6649,1674 6486,1837 6497,2174 6751,2428 7005,2683 7342,2693 7504,2531 7667,2368 7656,2031 7402,1777 7253,1628 7075,1562 6921,1567 Z M 5212,3389 L 682,7919 C 795,8235 974,8476 1350,8597 L 5886,4061 C 5679,3826 5454,3602 5212,3389 Z M 9412,3696 L 9658,5937 10384,3696 9412,3696 Z M 5920,5680 L 5386,6631 7837,6825 5920,5680 Z"/>
 | 
				
			||||||
 | 
					       </g>
 | 
				
			||||||
 | 
					      </g>
 | 
				
			||||||
 | 
					     </g>
 | 
				
			||||||
 | 
					    </g>
 | 
				
			||||||
 | 
					   </g>
 | 
				
			||||||
 | 
					  </g>
 | 
				
			||||||
 | 
					 </g>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 5.1 KiB  | 
@ -1,7 +0,0 @@
 | 
				
			|||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
 | 
					 | 
				
			||||||
<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="64.001" width="64.001" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
 | 
					 | 
				
			||||||
<g transform="translate(-289.60744,-317.50471)">
 | 
					 | 
				
			||||||
<path d="m352.64,357.25c-4.274,17.143-21.637,27.576-38.782,23.301-17.138-4.274-27.571-21.638-23.295-38.78,4.272-17.145,21.635-27.579,38.775-23.305,17.144,4.274,27.576,21.64,23.302,38.784z" fill="#f7931a"/>
 | 
					 | 
				
			||||||
<path d="m335.71,344.95c0.637-4.258-2.605-6.547-7.038-8.074l1.438-5.768-3.511-0.875-1.4,5.616c-0.923-0.23-1.871-0.447-2.813-0.662l1.41-5.653-3.509-0.875-1.439,5.766c-0.764-0.174-1.514-0.346-2.242-0.527l0.004-0.018-4.842-1.209-0.934,3.75s2.605,0.597,2.55,0.634c1.422,0.355,1.679,1.296,1.636,2.042l-1.638,6.571c0.098,0.025,0.225,0.061,0.365,0.117-0.117-0.029-0.242-0.061-0.371-0.092l-2.296,9.205c-0.174,0.432-0.615,1.08-1.609,0.834,0.035,0.051-2.552-0.637-2.552-0.637l-1.743,4.019,4.569,1.139c0.85,0.213,1.683,0.436,2.503,0.646l-1.453,5.834,3.507,0.875,1.439-5.772c0.958,0.26,1.888,0.5,2.798,0.726l-1.434,5.745,3.511,0.875,1.453-5.823c5.987,1.133,10.489,0.676,12.384-4.739,1.527-4.36-0.076-6.875-3.226-8.515,2.294-0.529,4.022-2.038,4.483-5.155zm-8.022,11.249c-1.085,4.36-8.426,2.003-10.806,1.412l1.928-7.729c2.38,0.594,10.012,1.77,8.878,6.317zm1.086-11.312c-0.99,3.966-7.1,1.951-9.082,1.457l1.748-7.01c1.982,0.494,8.365,1.416,7.334,5.553z" fill="#FFF"/>
 | 
					 | 
				
			||||||
</g>
 | 
					 | 
				
			||||||
</svg>
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 1.5 KiB  | 
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user