Merge branch 'master' into nymkappa/bugfix/channel-geo
This commit is contained in:
		
						commit
						250f07732f
					
				@ -218,3 +218,21 @@ Generate block at regular interval (every 10 seconds in this example):
 | 
				
			|||||||
   ```
 | 
					   ```
 | 
				
			||||||
   watch -n 10 "./src/bitcoin-cli -regtest -rpcport=8332 generatetoaddress 1 $address"
 | 
					   watch -n 10 "./src/bitcoin-cli -regtest -rpcport=8332 generatetoaddress 1 $address"
 | 
				
			||||||
   ```
 | 
					   ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Re-index tables
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can manually force the nodejs backend to drop all data from a specified set of tables for future re-index. This is mostly useful for the mining dashboard and the lightning explorer.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Use the `--reindex` command to specify a list of comma separated table which will be truncated at start. Note that a 5 seconds delay will be observed before truncating tables in order to give you a chance to cancel (CTRL+C) in case of misuse of the command.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Usage:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					npm run start --reindex=blocks,hashrates
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					Example output:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					Feb 13 14:55:27 [63246] WARN: <lightning> Indexed data for "hashrates" tables will be erased in 5 seconds (using '--reindex')
 | 
				
			||||||
 | 
					Feb 13 14:55:32 [63246] NOTICE: <lightning> Table hashrates has been truncated
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Reference: https://github.com/mempool/mempool/pull/1269
 | 
				
			||||||
@ -15,7 +15,6 @@
 | 
				
			|||||||
    "MEMPOOL_BLOCKS_AMOUNT": 8,
 | 
					    "MEMPOOL_BLOCKS_AMOUNT": 8,
 | 
				
			||||||
    "INDEXING_BLOCKS_AMOUNT": 11000,
 | 
					    "INDEXING_BLOCKS_AMOUNT": 11000,
 | 
				
			||||||
    "BLOCKS_SUMMARIES_INDEXING": false,
 | 
					    "BLOCKS_SUMMARIES_INDEXING": false,
 | 
				
			||||||
    "PRICE_FEED_UPDATE_INTERVAL": 600,
 | 
					 | 
				
			||||||
    "USE_SECOND_NODE_FOR_MINFEE": false,
 | 
					    "USE_SECOND_NODE_FOR_MINFEE": false,
 | 
				
			||||||
    "EXTERNAL_ASSETS": [],
 | 
					    "EXTERNAL_ASSETS": [],
 | 
				
			||||||
    "EXTERNAL_MAX_RETRY": 1,
 | 
					    "EXTERNAL_MAX_RETRY": 1,
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,6 @@
 | 
				
			|||||||
    "BLOCK_WEIGHT_UNITS": 6,
 | 
					    "BLOCK_WEIGHT_UNITS": 6,
 | 
				
			||||||
    "INITIAL_BLOCKS_AMOUNT": 7,
 | 
					    "INITIAL_BLOCKS_AMOUNT": 7,
 | 
				
			||||||
    "MEMPOOL_BLOCKS_AMOUNT": 8,
 | 
					    "MEMPOOL_BLOCKS_AMOUNT": 8,
 | 
				
			||||||
    "PRICE_FEED_UPDATE_INTERVAL": 9,
 | 
					 | 
				
			||||||
    "USE_SECOND_NODE_FOR_MINFEE": 10,
 | 
					    "USE_SECOND_NODE_FOR_MINFEE": 10,
 | 
				
			||||||
    "EXTERNAL_ASSETS": 11,
 | 
					    "EXTERNAL_ASSETS": 11,
 | 
				
			||||||
    "EXTERNAL_MAX_RETRY": 12,
 | 
					    "EXTERNAL_MAX_RETRY": 12,
 | 
				
			||||||
 | 
				
			|||||||
@ -29,7 +29,6 @@ describe('Mempool Backend Config', () => {
 | 
				
			|||||||
        INITIAL_BLOCKS_AMOUNT: 8,
 | 
					        INITIAL_BLOCKS_AMOUNT: 8,
 | 
				
			||||||
        MEMPOOL_BLOCKS_AMOUNT: 8,
 | 
					        MEMPOOL_BLOCKS_AMOUNT: 8,
 | 
				
			||||||
        INDEXING_BLOCKS_AMOUNT: 11000,
 | 
					        INDEXING_BLOCKS_AMOUNT: 11000,
 | 
				
			||||||
        PRICE_FEED_UPDATE_INTERVAL: 600,
 | 
					 | 
				
			||||||
        USE_SECOND_NODE_FOR_MINFEE: false,
 | 
					        USE_SECOND_NODE_FOR_MINFEE: false,
 | 
				
			||||||
        EXTERNAL_ASSETS: [],
 | 
					        EXTERNAL_ASSETS: [],
 | 
				
			||||||
        EXTERNAL_MAX_RETRY: 1,
 | 
					        EXTERNAL_MAX_RETRY: 1,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,13 @@
 | 
				
			|||||||
import config from '../../config';
 | 
					import config from '../../config';
 | 
				
			||||||
import axios, { AxiosRequestConfig } from 'axios';
 | 
					import axios, { AxiosRequestConfig } from 'axios';
 | 
				
			||||||
 | 
					import http from 'http';
 | 
				
			||||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
 | 
					import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
 | 
				
			||||||
import { IEsploraApi } from './esplora-api.interface';
 | 
					import { IEsploraApi } from './esplora-api.interface';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const axiosConnection = axios.create({
 | 
				
			||||||
 | 
					  httpAgent: new http.Agent({ keepAlive: true })
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ElectrsApi implements AbstractBitcoinApi {
 | 
					class ElectrsApi implements AbstractBitcoinApi {
 | 
				
			||||||
  axiosConfig: AxiosRequestConfig = {
 | 
					  axiosConfig: AxiosRequestConfig = {
 | 
				
			||||||
    timeout: 10000,
 | 
					    timeout: 10000,
 | 
				
			||||||
@ -11,52 +16,52 @@ class ElectrsApi implements AbstractBitcoinApi {
 | 
				
			|||||||
  constructor() { }
 | 
					  constructor() { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $getRawMempool(): Promise<IEsploraApi.Transaction['txid'][]> {
 | 
					  $getRawMempool(): Promise<IEsploraApi.Transaction['txid'][]> {
 | 
				
			||||||
    return axios.get<IEsploraApi.Transaction['txid'][]>(config.ESPLORA.REST_API_URL + '/mempool/txids', this.axiosConfig)
 | 
					    return axiosConnection.get<IEsploraApi.Transaction['txid'][]>(config.ESPLORA.REST_API_URL + '/mempool/txids', this.axiosConfig)
 | 
				
			||||||
      .then((response) => response.data);
 | 
					      .then((response) => response.data);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $getRawTransaction(txId: string): Promise<IEsploraApi.Transaction> {
 | 
					  $getRawTransaction(txId: string): Promise<IEsploraApi.Transaction> {
 | 
				
			||||||
    return axios.get<IEsploraApi.Transaction>(config.ESPLORA.REST_API_URL + '/tx/' + txId, this.axiosConfig)
 | 
					    return axiosConnection.get<IEsploraApi.Transaction>(config.ESPLORA.REST_API_URL + '/tx/' + txId, this.axiosConfig)
 | 
				
			||||||
      .then((response) => response.data);
 | 
					      .then((response) => response.data);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $getTransactionHex(txId: string): Promise<string> {
 | 
					  $getTransactionHex(txId: string): Promise<string> {
 | 
				
			||||||
    return axios.get<string>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/hex', this.axiosConfig)
 | 
					    return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/hex', this.axiosConfig)
 | 
				
			||||||
      .then((response) => response.data);
 | 
					      .then((response) => response.data);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $getBlockHeightTip(): Promise<number> {
 | 
					  $getBlockHeightTip(): Promise<number> {
 | 
				
			||||||
    return axios.get<number>(config.ESPLORA.REST_API_URL + '/blocks/tip/height', this.axiosConfig)
 | 
					    return axiosConnection.get<number>(config.ESPLORA.REST_API_URL + '/blocks/tip/height', this.axiosConfig)
 | 
				
			||||||
      .then((response) => response.data);
 | 
					      .then((response) => response.data);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $getBlockHashTip(): Promise<string> {
 | 
					  $getBlockHashTip(): Promise<string> {
 | 
				
			||||||
    return axios.get<string>(config.ESPLORA.REST_API_URL + '/blocks/tip/hash', this.axiosConfig)
 | 
					    return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/blocks/tip/hash', this.axiosConfig)
 | 
				
			||||||
      .then((response) => response.data);
 | 
					      .then((response) => response.data);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $getTxIdsForBlock(hash: string): Promise<string[]> {
 | 
					  $getTxIdsForBlock(hash: string): Promise<string[]> {
 | 
				
			||||||
    return axios.get<string[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids', this.axiosConfig)
 | 
					    return axiosConnection.get<string[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids', this.axiosConfig)
 | 
				
			||||||
      .then((response) => response.data);
 | 
					      .then((response) => response.data);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $getBlockHash(height: number): Promise<string> {
 | 
					  $getBlockHash(height: number): Promise<string> {
 | 
				
			||||||
    return axios.get<string>(config.ESPLORA.REST_API_URL + '/block-height/' + height, this.axiosConfig)
 | 
					    return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/block-height/' + height, this.axiosConfig)
 | 
				
			||||||
      .then((response) => response.data);
 | 
					      .then((response) => response.data);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $getBlockHeader(hash: string): Promise<string> {
 | 
					  $getBlockHeader(hash: string): Promise<string> {
 | 
				
			||||||
    return axios.get<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/header', this.axiosConfig)
 | 
					    return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/header', this.axiosConfig)
 | 
				
			||||||
      .then((response) => response.data);
 | 
					      .then((response) => response.data);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $getBlock(hash: string): Promise<IEsploraApi.Block> {
 | 
					  $getBlock(hash: string): Promise<IEsploraApi.Block> {
 | 
				
			||||||
    return axios.get<IEsploraApi.Block>(config.ESPLORA.REST_API_URL + '/block/' + hash, this.axiosConfig)
 | 
					    return axiosConnection.get<IEsploraApi.Block>(config.ESPLORA.REST_API_URL + '/block/' + hash, this.axiosConfig)
 | 
				
			||||||
      .then((response) => response.data);
 | 
					      .then((response) => response.data);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $getRawBlock(hash: string): Promise<Buffer> {
 | 
					  $getRawBlock(hash: string): Promise<Buffer> {
 | 
				
			||||||
    return axios.get<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + "/raw", { ...this.axiosConfig, responseType: 'arraybuffer' })
 | 
					    return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + "/raw", { ...this.axiosConfig, responseType: 'arraybuffer' })
 | 
				
			||||||
      .then((response) => { return Buffer.from(response.data); });
 | 
					      .then((response) => { return Buffer.from(response.data); });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -77,12 +82,12 @@ class ElectrsApi implements AbstractBitcoinApi {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
 | 
					  $getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
 | 
				
			||||||
    return axios.get<IEsploraApi.Outspend>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspend/' + vout, this.axiosConfig)
 | 
					    return axiosConnection.get<IEsploraApi.Outspend>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspend/' + vout, this.axiosConfig)
 | 
				
			||||||
      .then((response) => response.data);
 | 
					      .then((response) => response.data);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $getOutspends(txId: string): Promise<IEsploraApi.Outspend[]> {
 | 
					  $getOutspends(txId: string): Promise<IEsploraApi.Outspend[]> {
 | 
				
			||||||
    return axios.get<IEsploraApi.Outspend[]>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspends', this.axiosConfig)
 | 
					    return axiosConnection.get<IEsploraApi.Outspend[]>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspends', this.axiosConfig)
 | 
				
			||||||
      .then((response) => response.data);
 | 
					      .then((response) => response.data);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -600,9 +600,11 @@ class Blocks {
 | 
				
			|||||||
   * Index a block if it's missing from the database. Returns the block after indexing
 | 
					   * Index a block if it's missing from the database. Returns the block after indexing
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  public async $indexBlock(height: number): Promise<BlockExtended> {
 | 
					  public async $indexBlock(height: number): Promise<BlockExtended> {
 | 
				
			||||||
    const dbBlock = await blocksRepository.$getBlockByHeight(height);
 | 
					    if (Common.indexingEnabled()) {
 | 
				
			||||||
    if (dbBlock != null) {
 | 
					      const dbBlock = await blocksRepository.$getBlockByHeight(height);
 | 
				
			||||||
      return prepareBlock(dbBlock);
 | 
					      if (dbBlock !== null) {
 | 
				
			||||||
 | 
					        return prepareBlock(dbBlock);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const blockHash = await bitcoinApi.$getBlockHash(height);
 | 
					    const blockHash = await bitcoinApi.$getBlockHash(height);
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,6 @@ interface IConfig {
 | 
				
			|||||||
    MEMPOOL_BLOCKS_AMOUNT: number;
 | 
					    MEMPOOL_BLOCKS_AMOUNT: number;
 | 
				
			||||||
    INDEXING_BLOCKS_AMOUNT: number;
 | 
					    INDEXING_BLOCKS_AMOUNT: number;
 | 
				
			||||||
    BLOCKS_SUMMARIES_INDEXING: boolean;
 | 
					    BLOCKS_SUMMARIES_INDEXING: boolean;
 | 
				
			||||||
    PRICE_FEED_UPDATE_INTERVAL: number;
 | 
					 | 
				
			||||||
    USE_SECOND_NODE_FOR_MINFEE: boolean;
 | 
					    USE_SECOND_NODE_FOR_MINFEE: boolean;
 | 
				
			||||||
    EXTERNAL_ASSETS: string[];
 | 
					    EXTERNAL_ASSETS: string[];
 | 
				
			||||||
    EXTERNAL_MAX_RETRY: number;
 | 
					    EXTERNAL_MAX_RETRY: number;
 | 
				
			||||||
@ -141,7 +140,6 @@ const defaults: IConfig = {
 | 
				
			|||||||
    'MEMPOOL_BLOCKS_AMOUNT': 8,
 | 
					    'MEMPOOL_BLOCKS_AMOUNT': 8,
 | 
				
			||||||
    'INDEXING_BLOCKS_AMOUNT': 11000, // 0 = disable indexing, -1 = index all blocks
 | 
					    'INDEXING_BLOCKS_AMOUNT': 11000, // 0 = disable indexing, -1 = index all blocks
 | 
				
			||||||
    'BLOCKS_SUMMARIES_INDEXING': false,
 | 
					    'BLOCKS_SUMMARIES_INDEXING': false,
 | 
				
			||||||
    'PRICE_FEED_UPDATE_INTERVAL': 600,
 | 
					 | 
				
			||||||
    'USE_SECOND_NODE_FOR_MINFEE': false,
 | 
					    'USE_SECOND_NODE_FOR_MINFEE': false,
 | 
				
			||||||
    'EXTERNAL_ASSETS': [],
 | 
					    'EXTERNAL_ASSETS': [],
 | 
				
			||||||
    'EXTERNAL_MAX_RETRY': 1,
 | 
					    'EXTERNAL_MAX_RETRY': 1,
 | 
				
			||||||
 | 
				
			|||||||
@ -36,6 +36,7 @@ import bitcoinRoutes from './api/bitcoin/bitcoin.routes';
 | 
				
			|||||||
import fundingTxFetcher from './tasks/lightning/sync-tasks/funding-tx-fetcher';
 | 
					import fundingTxFetcher from './tasks/lightning/sync-tasks/funding-tx-fetcher';
 | 
				
			||||||
import forensicsService from './tasks/lightning/forensics.service';
 | 
					import forensicsService from './tasks/lightning/forensics.service';
 | 
				
			||||||
import priceUpdater from './tasks/price-updater';
 | 
					import priceUpdater from './tasks/price-updater';
 | 
				
			||||||
 | 
					import { AxiosError } from 'axios';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Server {
 | 
					class Server {
 | 
				
			||||||
  private wss: WebSocket.Server | undefined;
 | 
					  private wss: WebSocket.Server | undefined;
 | 
				
			||||||
@ -178,7 +179,7 @@ class Server {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS);
 | 
					      setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS);
 | 
				
			||||||
      this.currentBackendRetryInterval = 5;
 | 
					      this.currentBackendRetryInterval = 5;
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e: any) {
 | 
				
			||||||
      const loggerMsg = `runMainLoop error: ${(e instanceof Error ? e.message : e)}. Retrying in ${this.currentBackendRetryInterval} sec.`;
 | 
					      const loggerMsg = `runMainLoop error: ${(e instanceof Error ? e.message : e)}. Retrying in ${this.currentBackendRetryInterval} sec.`;
 | 
				
			||||||
      if (this.currentBackendRetryInterval > 5) {
 | 
					      if (this.currentBackendRetryInterval > 5) {
 | 
				
			||||||
        logger.warn(loggerMsg);
 | 
					        logger.warn(loggerMsg);
 | 
				
			||||||
@ -186,7 +187,9 @@ class Server {
 | 
				
			|||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        logger.debug(loggerMsg);
 | 
					        logger.debug(loggerMsg);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      logger.debug(JSON.stringify(e));
 | 
					      if (e instanceof AxiosError) {
 | 
				
			||||||
 | 
					        logger.debug(`AxiosError: ${e?.message}`);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      setTimeout(this.runMainUpdateLoop.bind(this), 1000 * this.currentBackendRetryInterval);
 | 
					      setTimeout(this.runMainUpdateLoop.bind(this), 1000 * this.currentBackendRetryInterval);
 | 
				
			||||||
      this.currentBackendRetryInterval *= 2;
 | 
					      this.currentBackendRetryInterval *= 2;
 | 
				
			||||||
      this.currentBackendRetryInterval = Math.min(this.currentBackendRetryInterval, 60);
 | 
					      this.currentBackendRetryInterval = Math.min(this.currentBackendRetryInterval, 60);
 | 
				
			||||||
 | 
				
			|||||||
@ -101,7 +101,6 @@ Below we list all settings from `mempool-config.json` and the corresponding over
 | 
				
			|||||||
    "INITIAL_BLOCKS_AMOUNT": 8,
 | 
					    "INITIAL_BLOCKS_AMOUNT": 8,
 | 
				
			||||||
    "MEMPOOL_BLOCKS_AMOUNT": 8,
 | 
					    "MEMPOOL_BLOCKS_AMOUNT": 8,
 | 
				
			||||||
    "BLOCKS_SUMMARIES_INDEXING": false,
 | 
					    "BLOCKS_SUMMARIES_INDEXING": false,
 | 
				
			||||||
    "PRICE_FEED_UPDATE_INTERVAL": 600,
 | 
					 | 
				
			||||||
    "USE_SECOND_NODE_FOR_MINFEE": false,
 | 
					    "USE_SECOND_NODE_FOR_MINFEE": false,
 | 
				
			||||||
    "EXTERNAL_ASSETS": ["https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json"],
 | 
					    "EXTERNAL_ASSETS": ["https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json"],
 | 
				
			||||||
    "STDOUT_LOG_MIN_PRIORITY": "info",
 | 
					    "STDOUT_LOG_MIN_PRIORITY": "info",
 | 
				
			||||||
@ -132,7 +131,6 @@ Corresponding `docker-compose.yml` overrides:
 | 
				
			|||||||
      MEMPOOL_INITIAL_BLOCKS_AMOUNT: ""
 | 
					      MEMPOOL_INITIAL_BLOCKS_AMOUNT: ""
 | 
				
			||||||
      MEMPOOL_MEMPOOL_BLOCKS_AMOUNT: ""
 | 
					      MEMPOOL_MEMPOOL_BLOCKS_AMOUNT: ""
 | 
				
			||||||
      MEMPOOL_BLOCKS_SUMMARIES_INDEXING: ""
 | 
					      MEMPOOL_BLOCKS_SUMMARIES_INDEXING: ""
 | 
				
			||||||
      MEMPOOL_PRICE_FEED_UPDATE_INTERVAL: ""
 | 
					 | 
				
			||||||
      MEMPOOL_USE_SECOND_NODE_FOR_MINFEE: ""
 | 
					      MEMPOOL_USE_SECOND_NODE_FOR_MINFEE: ""
 | 
				
			||||||
      MEMPOOL_EXTERNAL_ASSETS: ""
 | 
					      MEMPOOL_EXTERNAL_ASSETS: ""
 | 
				
			||||||
      MEMPOOL_STDOUT_LOG_MIN_PRIORITY: ""
 | 
					      MEMPOOL_STDOUT_LOG_MIN_PRIORITY: ""
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,6 @@
 | 
				
			|||||||
    "BLOCK_WEIGHT_UNITS": __MEMPOOL_BLOCK_WEIGHT_UNITS__,
 | 
					    "BLOCK_WEIGHT_UNITS": __MEMPOOL_BLOCK_WEIGHT_UNITS__,
 | 
				
			||||||
    "INITIAL_BLOCKS_AMOUNT": __MEMPOOL_INITIAL_BLOCKS_AMOUNT__,
 | 
					    "INITIAL_BLOCKS_AMOUNT": __MEMPOOL_INITIAL_BLOCKS_AMOUNT__,
 | 
				
			||||||
    "MEMPOOL_BLOCKS_AMOUNT": __MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__,
 | 
					    "MEMPOOL_BLOCKS_AMOUNT": __MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__,
 | 
				
			||||||
    "PRICE_FEED_UPDATE_INTERVAL": __MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__,
 | 
					 | 
				
			||||||
    "USE_SECOND_NODE_FOR_MINFEE": __MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__,
 | 
					    "USE_SECOND_NODE_FOR_MINFEE": __MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__,
 | 
				
			||||||
    "EXTERNAL_ASSETS": __MEMPOOL_EXTERNAL_ASSETS__,
 | 
					    "EXTERNAL_ASSETS": __MEMPOOL_EXTERNAL_ASSETS__,
 | 
				
			||||||
    "EXTERNAL_MAX_RETRY": __MEMPOOL_EXTERNAL_MAX_RETRY__,
 | 
					    "EXTERNAL_MAX_RETRY": __MEMPOOL_EXTERNAL_MAX_RETRY__,
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,6 @@ __MEMPOOL_INITIAL_BLOCKS_AMOUNT__=${MEMPOOL_INITIAL_BLOCKS_AMOUNT:=8}
 | 
				
			|||||||
__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8}
 | 
					__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8}
 | 
				
			||||||
__MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=11000}
 | 
					__MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=11000}
 | 
				
			||||||
__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__=${MEMPOOL_BLOCKS_SUMMARIES_INDEXING:=false}
 | 
					__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__=${MEMPOOL_BLOCKS_SUMMARIES_INDEXING:=false}
 | 
				
			||||||
__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__=${MEMPOOL_PRICE_FEED_UPDATE_INTERVAL:=600}
 | 
					 | 
				
			||||||
__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__=${MEMPOOL_USE_SECOND_NODE_FOR_MINFEE:=false}
 | 
					__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__=${MEMPOOL_USE_SECOND_NODE_FOR_MINFEE:=false}
 | 
				
			||||||
__MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[]}
 | 
					__MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[]}
 | 
				
			||||||
__MEMPOOL_EXTERNAL_MAX_RETRY__=${MEMPOOL_EXTERNAL_MAX_RETRY:=1}
 | 
					__MEMPOOL_EXTERNAL_MAX_RETRY__=${MEMPOOL_EXTERNAL_MAX_RETRY:=1}
 | 
				
			||||||
@ -129,7 +128,6 @@ sed -i "s/__MEMPOOL_INITIAL_BLOCKS_AMOUNT__/${__MEMPOOL_INITIAL_BLOCKS_AMOUNT__}
 | 
				
			|||||||
sed -i "s/__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__/${__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__}/g" mempool-config.json
 | 
					sed -i "s/__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__/${__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__}/g" mempool-config.json
 | 
				
			||||||
sed -i "s/__MEMPOOL_INDEXING_BLOCKS_AMOUNT__/${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}/g" mempool-config.json
 | 
					sed -i "s/__MEMPOOL_INDEXING_BLOCKS_AMOUNT__/${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}/g" mempool-config.json
 | 
				
			||||||
sed -i "s/__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__/${__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__}/g" mempool-config.json
 | 
					sed -i "s/__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__/${__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__}/g" mempool-config.json
 | 
				
			||||||
sed -i "s/__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__/${__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__}/g" mempool-config.json
 | 
					 | 
				
			||||||
sed -i "s/__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__/${__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__}/g" mempool-config.json
 | 
					sed -i "s/__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__/${__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__}/g" mempool-config.json
 | 
				
			||||||
sed -i "s!__MEMPOOL_EXTERNAL_ASSETS__!${__MEMPOOL_EXTERNAL_ASSETS__}!g" mempool-config.json
 | 
					sed -i "s!__MEMPOOL_EXTERNAL_ASSETS__!${__MEMPOOL_EXTERNAL_ASSETS__}!g" mempool-config.json
 | 
				
			||||||
sed -i "s!__MEMPOOL_EXTERNAL_MAX_RETRY__!${__MEMPOOL_EXTERNAL_MAX_RETRY__}!g" mempool-config.json
 | 
					sed -i "s!__MEMPOOL_EXTERNAL_MAX_RETRY__!${__MEMPOOL_EXTERNAL_MAX_RETRY__}!g" mempool-config.json
 | 
				
			||||||
 | 
				
			|||||||
@ -64,7 +64,7 @@ describe('Mainnet', () => {
 | 
				
			|||||||
    it('loads the status screen', () => {
 | 
					    it('loads the status screen', () => {
 | 
				
			||||||
      cy.visit('/status');
 | 
					      cy.visit('/status');
 | 
				
			||||||
      cy.get('#mempool-block-0').should('be.visible');
 | 
					      cy.get('#mempool-block-0').should('be.visible');
 | 
				
			||||||
      cy.get('[id^="bitcoin-block-"]').should('have.length', 8);
 | 
					      cy.get('[id^="bitcoin-block-"]').should('have.length', 22);
 | 
				
			||||||
      cy.get('.footer').should('be.visible');
 | 
					      cy.get('.footer').should('be.visible');
 | 
				
			||||||
      cy.get('.row > :nth-child(1)').invoke('text').then((text) => {
 | 
					      cy.get('.row > :nth-child(1)').invoke('text').then((text) => {
 | 
				
			||||||
        expect(text).to.match(/Incoming transactions.* vB\/s/);
 | 
					        expect(text).to.match(/Incoming transactions.* vB\/s/);
 | 
				
			||||||
@ -219,11 +219,11 @@ describe('Mainnet', () => {
 | 
				
			|||||||
    describe('blocks navigation', () => {
 | 
					    describe('blocks navigation', () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      describe('keyboard events', () => {
 | 
					      describe('keyboard events', () => {
 | 
				
			||||||
        it('loads first blockchain blocks visible and keypress arrow right', () => {
 | 
					        it('loads first blockchain block visible and keypress arrow right', () => {
 | 
				
			||||||
          cy.viewport('macbook-16');
 | 
					          cy.viewport('macbook-16');
 | 
				
			||||||
          cy.visit('/');
 | 
					          cy.visit('/');
 | 
				
			||||||
          cy.waitForSkeletonGone();
 | 
					          cy.waitForSkeletonGone();
 | 
				
			||||||
          cy.get('.blockchain-blocks-0 > a').click().then(() => {
 | 
					          cy.get('[data-cy="bitcoin-block-offset-0-index-0"]').click().then(() => {
 | 
				
			||||||
            cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
 | 
					            cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
 | 
				
			||||||
            cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
 | 
					            cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
 | 
				
			||||||
            cy.waitForPageIdle();
 | 
					            cy.waitForPageIdle();
 | 
				
			||||||
@ -233,11 +233,11 @@ describe('Mainnet', () => {
 | 
				
			|||||||
          });
 | 
					          });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it('loads first blockchain blocks visible and keypress arrow left', () => {
 | 
					        it('loads first blockchain block visible and keypress arrow left', () => {
 | 
				
			||||||
          cy.viewport('macbook-16');
 | 
					          cy.viewport('macbook-16');
 | 
				
			||||||
          cy.visit('/');
 | 
					          cy.visit('/');
 | 
				
			||||||
          cy.waitForSkeletonGone();
 | 
					          cy.waitForSkeletonGone();
 | 
				
			||||||
          cy.get('.blockchain-blocks-0 > a').click().then(() => {
 | 
					          cy.get('[data-cy="bitcoin-block-offset-0-index-0"]').click().then(() => {
 | 
				
			||||||
            cy.waitForPageIdle();
 | 
					            cy.waitForPageIdle();
 | 
				
			||||||
            cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
 | 
					            cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
 | 
				
			||||||
            cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
 | 
					            cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
 | 
				
			||||||
@ -246,11 +246,11 @@ describe('Mainnet', () => {
 | 
				
			|||||||
          });
 | 
					          });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it('loads last blockchain blocks and keypress arrow right', () => {
 | 
					        it.skip('loads last blockchain block and keypress arrow right', () => { //Skip for now as "last" doesn't really work with infinite scrolling
 | 
				
			||||||
          cy.viewport('macbook-16');
 | 
					          cy.viewport('macbook-16');
 | 
				
			||||||
          cy.visit('/');
 | 
					          cy.visit('/');
 | 
				
			||||||
          cy.waitForSkeletonGone();
 | 
					          cy.waitForSkeletonGone();
 | 
				
			||||||
          cy.get('.blockchain-blocks-4 > a').click().then(() => {
 | 
					          cy.get('bitcoin-block-offset-0-index-7').click().then(() => {
 | 
				
			||||||
            cy.waitForPageIdle();
 | 
					            cy.waitForPageIdle();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // block 6
 | 
					            // block 6
 | 
				
			||||||
@ -309,7 +309,7 @@ describe('Mainnet', () => {
 | 
				
			|||||||
          cy.viewport('macbook-16');
 | 
					          cy.viewport('macbook-16');
 | 
				
			||||||
          cy.visit('/');
 | 
					          cy.visit('/');
 | 
				
			||||||
          cy.waitForSkeletonGone();
 | 
					          cy.waitForSkeletonGone();
 | 
				
			||||||
          cy.get('.blockchain-blocks-0 > a').click().then(() => {
 | 
					          cy.get('[data-cy="bitcoin-block-offset-0-index-0"]').click().then(() => {
 | 
				
			||||||
            cy.waitForPageIdle();
 | 
					            cy.waitForPageIdle();
 | 
				
			||||||
            cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
 | 
					            cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
 | 
				
			||||||
            cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
 | 
					            cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
 | 
				
			||||||
 | 
				
			|||||||
@ -72,22 +72,10 @@ export const chartColors = [
 | 
				
			|||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const poolsColor = {
 | 
					export const poolsColor = {
 | 
				
			||||||
   'foundryusa': '#D81B60',
 | 
					  'unknown': '#9C9C9C',
 | 
				
			||||||
   'antpool': '#8E24AA',
 | 
					};
 | 
				
			||||||
   'f2pool': '#5E35B1',
 | 
					 | 
				
			||||||
   'poolin': '#3949AB',
 | 
					 | 
				
			||||||
   'binancepool': '#1E88E5',
 | 
					 | 
				
			||||||
   'viabtc': '#039BE5',
 | 
					 | 
				
			||||||
   'btccom': '#00897B',
 | 
					 | 
				
			||||||
   'braiinspool': '#00ACC1',
 | 
					 | 
				
			||||||
   'sbicrypto': '#43A047',
 | 
					 | 
				
			||||||
   'marapool': '#7CB342',
 | 
					 | 
				
			||||||
   'luxor': '#C0CA33',
 | 
					 | 
				
			||||||
   'unknown': '#FDD835',
 | 
					 | 
				
			||||||
   'okkong': '#FFB300',
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
 | 
					export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
 | 
				
			||||||
  250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
 | 
					  250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface Language {
 | 
					export interface Language {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
<div class="container-xl">
 | 
					<div class="container-xl" [class.liquid-address]="network === 'liquid' || network === 'liquidtestnet'">
 | 
				
			||||||
  <div class="title-address">
 | 
					  <div class="title-address">
 | 
				
			||||||
    <h1 i18n="shared.address">Address</h1>
 | 
					    <h1 i18n="shared.address">Address</h1>
 | 
				
			||||||
    <div class="tx-link">
 | 
					    <div class="tx-link">
 | 
				
			||||||
@ -15,7 +15,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      <div class="row">
 | 
					      <div class="row">
 | 
				
			||||||
        <div class="col-md">
 | 
					        <div class="col-md">
 | 
				
			||||||
          <table class="table table-borderless table-striped">
 | 
					          <table class="table table-borderless table-striped address-table">
 | 
				
			||||||
            <tbody>
 | 
					            <tbody>
 | 
				
			||||||
              <tr *ngIf="addressInfo && addressInfo.unconfidential">
 | 
					              <tr *ngIf="addressInfo && addressInfo.unconfidential">
 | 
				
			||||||
                <td i18n="address.unconfidential">Unconfidential</td>
 | 
					                <td i18n="address.unconfidential">Unconfidential</td>
 | 
				
			||||||
 | 
				
			|||||||
@ -91,3 +91,20 @@ h1 {
 | 
				
			|||||||
    margin-bottom: 10px;
 | 
					    margin-bottom: 10px;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.liquid-address {
 | 
				
			||||||
 | 
					  .address-table {
 | 
				
			||||||
 | 
					    table-layout: fixed;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tr td:first-child {
 | 
				
			||||||
 | 
					      width: 170px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    tr td:last-child {
 | 
				
			||||||
 | 
					      width: 80%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .qrcode-col {
 | 
				
			||||||
 | 
					    flex-grow: 0.5;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -90,6 +90,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
 | 
				
			|||||||
                this.prepareChartOptions({
 | 
					                this.prepareChartOptions({
 | 
				
			||||||
                  sizes: data.sizes.map(val => [val.timestamp * 1000, val.avgSize / 1000000, val.avgHeight]),
 | 
					                  sizes: data.sizes.map(val => [val.timestamp * 1000, val.avgSize / 1000000, val.avgHeight]),
 | 
				
			||||||
                  weights: data.weights.map(val => [val.timestamp * 1000, val.avgWeight / 1000000, val.avgHeight]),
 | 
					                  weights: data.weights.map(val => [val.timestamp * 1000, val.avgWeight / 1000000, val.avgHeight]),
 | 
				
			||||||
 | 
					                  sizePerWeight: data.weights.map((val, i) => [val.timestamp * 1000, data.sizes[i].avgSize / (val.avgWeight / 4), val.avgHeight]),
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
                this.isLoading = false;
 | 
					                this.isLoading = false;
 | 
				
			||||||
              }),
 | 
					              }),
 | 
				
			||||||
@ -124,6 +125,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
 | 
				
			|||||||
      color: [
 | 
					      color: [
 | 
				
			||||||
        '#FDD835',
 | 
					        '#FDD835',
 | 
				
			||||||
        '#D81B60',
 | 
					        '#D81B60',
 | 
				
			||||||
 | 
					        '#039BE5',
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      grid: {
 | 
					      grid: {
 | 
				
			||||||
        top: 30,
 | 
					        top: 30,
 | 
				
			||||||
@ -153,6 +155,8 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
 | 
				
			|||||||
              tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.2-2')} MB`;
 | 
					              tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.2-2')} MB`;
 | 
				
			||||||
            } else if (tick.seriesIndex === 1) { // Weight
 | 
					            } else if (tick.seriesIndex === 1) { // Weight
 | 
				
			||||||
              tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.2-2')} MWU`;
 | 
					              tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.2-2')} MWU`;
 | 
				
			||||||
 | 
					            } else if (tick.seriesIndex === 2) { // Size per weight
 | 
				
			||||||
 | 
					              tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.2-2')} B/vB`;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            tooltip += `<br>`;
 | 
					            tooltip += `<br>`;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@ -192,10 +196,19 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
 | 
				
			|||||||
            },
 | 
					            },
 | 
				
			||||||
            icon: 'roundRect',
 | 
					            icon: 'roundRect',
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            name: $localize`Size per weight`,
 | 
				
			||||||
 | 
					            inactiveColor: 'rgb(110, 112, 121)',
 | 
				
			||||||
 | 
					            textStyle: {
 | 
				
			||||||
 | 
					              color: 'white',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            icon: 'roundRect',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        selected: JSON.parse(this.storageService.getValue('sizes_weights_legend'))  ?? {
 | 
					        selected: JSON.parse(this.storageService.getValue('sizes_weights_legend'))  ?? {
 | 
				
			||||||
          'Size': true,
 | 
					          'Size': true,
 | 
				
			||||||
          'Weight': true,
 | 
					          'Weight': true,
 | 
				
			||||||
 | 
					          'Size per weight': true,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      yAxis: data.sizes.length === 0 ? undefined : [
 | 
					      yAxis: data.sizes.length === 0 ? undefined : [
 | 
				
			||||||
@ -262,6 +275,18 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
 | 
				
			|||||||
          lineStyle: {
 | 
					          lineStyle: {
 | 
				
			||||||
            width: 2,
 | 
					            width: 2,
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          zlevel: 1,
 | 
				
			||||||
 | 
					          yAxisIndex: 0,
 | 
				
			||||||
 | 
					          name: $localize`Size per weight`,
 | 
				
			||||||
 | 
					          showSymbol: false,
 | 
				
			||||||
 | 
					          symbol: 'none',
 | 
				
			||||||
 | 
					          data: data.sizePerWeight,
 | 
				
			||||||
 | 
					          type: 'line',
 | 
				
			||||||
 | 
					          lineStyle: {
 | 
				
			||||||
 | 
					            width: 2,
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      ],
 | 
					      ],
 | 
				
			||||||
      dataZoom: [{
 | 
					      dataZoom: [{
 | 
				
			||||||
 | 
				
			|||||||
@ -1,42 +1,62 @@
 | 
				
			|||||||
<div class="blocks-container blockchain-blocks-container" [class.time-ltr]="timeLtr" [style.left]="static ? (offset || 0) + 'px' : null" *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
 | 
					<div class="blocks-container blockchain-blocks-container" [class.time-ltr]="timeLtr"
 | 
				
			||||||
 | 
					  [style.left]="static ? (offset || 0) + 'px' : null"
 | 
				
			||||||
 | 
					  *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
 | 
				
			||||||
  <div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn">
 | 
					  <div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn">
 | 
				
			||||||
    <ng-container *ngIf="block && !block.loading && !block.placeholder; else placeholderBlock">
 | 
					    <ng-container *ngIf="block && !block.loading && !block.placeholder; else placeholderBlock">
 | 
				
			||||||
      <div [attr.data-cy]="'bitcoin-block-' + i" class="text-center bitcoin-block mined-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]" [class.blink-bg]="(specialBlocks[block.height] !== undefined)">
 | 
					      <div [attr.data-cy]="'bitcoin-block-offset-' + offset + '-index-' + i"
 | 
				
			||||||
 | 
					        class="text-center bitcoin-block mined-block blockchain-blocks-offset-{{ offset }}-index-{{ i }}"
 | 
				
			||||||
 | 
					        id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]"
 | 
				
			||||||
 | 
					        [class.blink-bg]="(specialBlocks[block.height] !== undefined)">
 | 
				
			||||||
        <a draggable="false" [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }"
 | 
					        <a draggable="false" [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }"
 | 
				
			||||||
          class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}"> </a>
 | 
					          class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}"> </a>
 | 
				
			||||||
        <div [attr.data-cy]="'bitcoin-block-' + i + '-height'" class="block-height">
 | 
					        <div [attr.data-cy]="'bitcoin-block-' + i + '-height'" class="block-height">
 | 
				
			||||||
          <a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
 | 
					          <a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height
 | 
				
			||||||
 | 
					            }}</a>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="block-body">
 | 
					        <div class="block-body">
 | 
				
			||||||
          <div [attr.data-cy]="'bitcoin-block-' + i + '-fees'" class="fees">
 | 
					          <div [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees">
 | 
				
			||||||
            ~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
 | 
					            ~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container
 | 
				
			||||||
 | 
					              i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div [attr.data-cy]="'bitcoin-block-' + i + '-fee-span'" class="fee-span" *ngIf="block?.extras?.feeRange">
 | 
					          <div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-fee-span'" class="fee-span"
 | 
				
			||||||
            {{ block?.extras?.feeRange?.[1] | number:feeRounding }} - {{ block?.extras?.feeRange[block?.extras?.feeRange?.length - 1] | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
 | 
					            *ngIf="block?.extras?.feeRange">
 | 
				
			||||||
 | 
					            {{ block?.extras?.feeRange?.[1] | number:feeRounding }} - {{
 | 
				
			||||||
 | 
					            block?.extras?.feeRange[block?.extras?.feeRange?.length - 1] | number:feeRounding }} <ng-container
 | 
				
			||||||
 | 
					              i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div [attr.data-cy]="'bitcoin-block-' + i + '-fee-span'" class="fee-span" *ngIf="!block?.extras?.feeRange">
 | 
					          <div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-fee-span'" class="fee-span"
 | 
				
			||||||
 | 
					            *ngIf="!block?.extras?.feeRange">
 | 
				
			||||||
             
 | 
					             
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div [attr.data-cy]="'bitcoin-block-' + i + '-total-fees'" *ngIf="showMiningInfo" class="block-size">
 | 
					          <div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-total-fees'" *ngIf="showMiningInfo"
 | 
				
			||||||
 | 
					            class="block-size">
 | 
				
			||||||
            <app-amount [satoshis]="block.extras?.totalFees ?? 0" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
 | 
					            <app-amount [satoshis]="block.extras?.totalFees ?? 0" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div [attr.data-cy]="'bitcoin-block-' + i + 'block-size'" *ngIf="!showMiningInfo" class="block-size" [innerHTML]="'‎' + (block.size | bytes: 2)"></div>
 | 
					          <div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + 'block-size'" *ngIf="!showMiningInfo"
 | 
				
			||||||
 | 
					            class="block-size" [innerHTML]="'‎' + (block.size | bytes: 2)"></div>
 | 
				
			||||||
          <div [attr.data-cy]="'bitcoin-block-' + i + '-transactions'" class="transaction-count">
 | 
					          <div [attr.data-cy]="'bitcoin-block-' + i + '-transactions'" class="transaction-count">
 | 
				
			||||||
            <ng-container *ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container>
 | 
					            <ng-container
 | 
				
			||||||
            <ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
 | 
					              *ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container>
 | 
				
			||||||
            <ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
 | 
					            <ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }}
 | 
				
			||||||
 | 
					              transaction</ng-template>
 | 
				
			||||||
 | 
					            <ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }}
 | 
				
			||||||
 | 
					              transactions</ng-template>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div [attr.data-cy]="'bitcoin-block-' + i + '-time'" class="time-difference"><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since></div>
 | 
					          <div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-time'" class="time-difference">
 | 
				
			||||||
 | 
					            <app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since></div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
        <div class="animated" [class]="showMiningInfo ? 'show' : 'hide'" *ngIf="block.extras?.pool != undefined">
 | 
					        <div class="animated" [class]="showMiningInfo ? 'show' : 'hide'" *ngIf="block.extras?.pool != undefined">
 | 
				
			||||||
          <a [attr.data-cy]="'bitcoin-block-' + i + '-pool'" class="badge badge-primary" [routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
 | 
					          <a [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-pool'" class="badge badge-primary"
 | 
				
			||||||
 | 
					            [routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
 | 
				
			||||||
            {{ block.extras.pool.name}}</a>
 | 
					            {{ block.extras.pool.name}}</a>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </ng-container>
 | 
					    </ng-container>
 | 
				
			||||||
    <ng-template #placeholderBlock>
 | 
					    <ng-template #placeholderBlock>
 | 
				
			||||||
      <ng-container *ngIf="block && block.placeholder; else loadingBlock">
 | 
					      <ng-container *ngIf="block && block.placeholder; else loadingBlock">
 | 
				
			||||||
        <div [attr.data-cy]="'bitcoin-block-' + i" class="text-center bitcoin-block mined-block placeholder-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]">
 | 
					        <div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i"
 | 
				
			||||||
 | 
					          class="text-center bitcoin-block mined-block placeholder-block blockchain-blocks-{{ i }}"
 | 
				
			||||||
 | 
					          id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </ng-container>
 | 
					      </ng-container>
 | 
				
			||||||
@ -44,21 +64,23 @@
 | 
				
			|||||||
    <ng-template #loadingBlock>
 | 
					    <ng-template #loadingBlock>
 | 
				
			||||||
      <ng-container *ngIf="block && block.loading">
 | 
					      <ng-container *ngIf="block && block.loading">
 | 
				
			||||||
        <div class="flashing">
 | 
					        <div class="flashing">
 | 
				
			||||||
          <div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]"></div>
 | 
					          <div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}"
 | 
				
			||||||
 | 
					            [ngStyle]="blockStyles[i]"></div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </ng-container>
 | 
					      </ng-container>
 | 
				
			||||||
    </ng-template>
 | 
					    </ng-template>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
<div [hidden]="!arrowVisible" id="arrow-up" [style.transition]="arrowTransition" [ngStyle]="{'left': arrowLeftPx + 'px' }"></div>
 | 
					  <div [hidden]="!arrowVisible" id="arrow-up" [style.transition]="arrowTransition"
 | 
				
			||||||
 | 
					    [ngStyle]="{'left': arrowLeftPx + 'px' }"></div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<ng-template #loadingBlocksTemplate>
 | 
					<ng-template #loadingBlocksTemplate>
 | 
				
			||||||
  <div class="blocks-container" [class.time-ltr]="timeLtr">
 | 
					  <div class="blocks-container" [class.time-ltr]="timeLtr">
 | 
				
			||||||
    <div class="flashing">
 | 
					    <div class="flashing">
 | 
				
			||||||
      <div *ngFor="let block of emptyBlocks; let i = index; trackBy: trackByBlocksFn" >
 | 
					      <div *ngFor="let block of emptyBlocks; let i = index; trackBy: trackByBlocksFn">
 | 
				
			||||||
        <div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" [ngStyle]="emptyBlockStyles[i]"></div>
 | 
					        <div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}"
 | 
				
			||||||
 | 
					          [ngStyle]="emptyBlockStyles[i]"></div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
</ng-template>
 | 
					</ng-template>
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -123,8 +123,10 @@
 | 
				
			|||||||
      (pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
 | 
					      (pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
 | 
				
			||||||
    </ngb-pagination>
 | 
					    </ngb-pagination>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="clearfix"></div>
 | 
					    <ng-template [ngIf]="!widget">
 | 
				
			||||||
    <br>
 | 
					      <div class="clearfix"></div>
 | 
				
			||||||
 | 
					      <br>
 | 
				
			||||||
 | 
					    </ng-template>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
				
			|||||||
@ -176,7 +176,7 @@ export class PoolRankingComponent implements OnInit {
 | 
				
			|||||||
    // 'Other'
 | 
					    // 'Other'
 | 
				
			||||||
    data.push({
 | 
					    data.push({
 | 
				
			||||||
      itemStyle: {
 | 
					      itemStyle: {
 | 
				
			||||||
        color: 'grey',
 | 
					        color: '#6b6b6b',
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      value: totalShareOther,
 | 
					      value: totalShareOther,
 | 
				
			||||||
      name: 'Other' + (isMobile() ? `` : ` (${totalShareOther.toFixed(2)}%)`),
 | 
					      name: 'Other' + (isMobile() ? `` : ` (${totalShareOther.toFixed(2)}%)`),
 | 
				
			||||||
 | 
				
			|||||||
@ -216,8 +216,8 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<ng-template type="why-dont-fee-ranges-match">
 | 
					<ng-template type="why-dont-fee-ranges-match">
 | 
				
			||||||
  <p>Mempool aims to show you the <i>effective feerate</i> range for blocks—how much would you actually need to pay to get a transaction included in a block.</p>
 | 
					  <p>Mempool aims to show you the <i>effective feerate</i> range for blocks—how much would you actually need to pay to get a transaction included in a block.</p>
 | 
				
			||||||
  <p>A transaction's effective feerate is not always the same as the feerate explicitly set for it. For example, if you see a 1 s/vb transaction in a block with a displayed feerate range of 5 s/vb to 72 s/vb, chances are that 1 s/vb transaction had a high-feerate child transaction that boosted its effective feerate to 5 s/vb or higher (this is how CPFP fee-bumping works). In such a case, it would be misleading to use 1 s/vb as the lower bound of the block's feerate range because it actually required more than 1 s/vb to confirm that transaction in that block.</p>
 | 
					  <p>A transaction's effective feerate is not always the same as the feerate explicitly set for it. For example, if you see a 1 s/vb transaction in a block with a displayed feerate range of 5 s/vb to 72 s/vb, chances are that 1 s/vb transaction had a high-feerate child transaction that boosted its effective feerate to 5 s/vb or higher (this is how CPFP fee-bumping works). In such a case, it would be misleading to use 1 s/vb as the lower bound of the block's feerate range since it actually required more than 1 s/vb to confirm that transaction in that block.</p>
 | 
				
			||||||
  <p>For unconfirmed CPFP transactions, Mempool will show the effective feerate (along with descendent & ancestor transaction information) on the transaction page. For confirmed transactions, CPFP relationships are not stored, so this additional information is not shown.</p>
 | 
					  <p>You can find a transaction's feerate on its transaction details page. If the transaction has any CPFP relationships, the page will also show the transaction's effective feerate along with links to descendent and/or ancestor transactions.</p>
 | 
				
			||||||
</ng-template>
 | 
					</ng-template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<ng-template type="how-do-block-audits-work">
 | 
					<ng-template type="how-do-block-audits-work">
 | 
				
			||||||
 | 
				
			|||||||
@ -151,6 +151,19 @@ export interface RewardStats {
 | 
				
			|||||||
  totalTx: number;
 | 
					  totalTx: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface BlockSizesAndWeights {
 | 
				
			||||||
 | 
					  sizes: {
 | 
				
			||||||
 | 
					    timestamp: number;
 | 
				
			||||||
 | 
					    avgHeight: number;
 | 
				
			||||||
 | 
					    avgSize: number;
 | 
				
			||||||
 | 
					  }[];
 | 
				
			||||||
 | 
					  weights: {
 | 
				
			||||||
 | 
					    timestamp: number;
 | 
				
			||||||
 | 
					    avgHeight: number;
 | 
				
			||||||
 | 
					    avgWeight: number;
 | 
				
			||||||
 | 
					  }[];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface AuditScore {
 | 
					export interface AuditScore {
 | 
				
			||||||
  hash: string;
 | 
					  hash: string;
 | 
				
			||||||
  matchRate?: number;
 | 
					  matchRate?: number;
 | 
				
			||||||
 | 
				
			|||||||
@ -103,6 +103,5 @@
 | 
				
			|||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <br>
 | 
					  <br>
 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
<br>
 | 
					</div>
 | 
				
			||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { Injectable } from '@angular/core';
 | 
					import { Injectable } from '@angular/core';
 | 
				
			||||||
import { HttpClient, HttpParams } from '@angular/common/http';
 | 
					import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
 | 
				
			||||||
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators,
 | 
					import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators,
 | 
				
			||||||
  PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore } from '../interfaces/node-api.interface';
 | 
					  PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights } from '../interfaces/node-api.interface';
 | 
				
			||||||
import { Observable } from 'rxjs';
 | 
					import { Observable } from 'rxjs';
 | 
				
			||||||
import { StateService } from './state.service';
 | 
					import { StateService } from './state.service';
 | 
				
			||||||
import { WebsocketResponse } from '../interfaces/websocket.interface';
 | 
					import { WebsocketResponse } from '../interfaces/websocket.interface';
 | 
				
			||||||
@ -222,8 +222,8 @@ export class ApiService {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getHistoricalBlockSizesAndWeights$(interval: string | undefined) : Observable<any> {
 | 
					  getHistoricalBlockSizesAndWeights$(interval: string | undefined) : Observable<HttpResponse<BlockSizesAndWeights>> {
 | 
				
			||||||
    return this.httpClient.get<any[]>(
 | 
					    return this.httpClient.get<BlockSizesAndWeights>(
 | 
				
			||||||
      this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/sizes-weights` +
 | 
					      this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/sizes-weights` +
 | 
				
			||||||
      (interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
 | 
					      (interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user