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" | ||||
|    ``` | ||||
| 
 | ||||
| ### 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, | ||||
|     "INDEXING_BLOCKS_AMOUNT": 11000, | ||||
|     "BLOCKS_SUMMARIES_INDEXING": false, | ||||
|     "PRICE_FEED_UPDATE_INTERVAL": 600, | ||||
|     "USE_SECOND_NODE_FOR_MINFEE": false, | ||||
|     "EXTERNAL_ASSETS": [], | ||||
|     "EXTERNAL_MAX_RETRY": 1, | ||||
|  | ||||
| @ -16,7 +16,6 @@ | ||||
|     "BLOCK_WEIGHT_UNITS": 6, | ||||
|     "INITIAL_BLOCKS_AMOUNT": 7, | ||||
|     "MEMPOOL_BLOCKS_AMOUNT": 8, | ||||
|     "PRICE_FEED_UPDATE_INTERVAL": 9, | ||||
|     "USE_SECOND_NODE_FOR_MINFEE": 10, | ||||
|     "EXTERNAL_ASSETS": 11, | ||||
|     "EXTERNAL_MAX_RETRY": 12, | ||||
|  | ||||
| @ -29,7 +29,6 @@ describe('Mempool Backend Config', () => { | ||||
|         INITIAL_BLOCKS_AMOUNT: 8, | ||||
|         MEMPOOL_BLOCKS_AMOUNT: 8, | ||||
|         INDEXING_BLOCKS_AMOUNT: 11000, | ||||
|         PRICE_FEED_UPDATE_INTERVAL: 600, | ||||
|         USE_SECOND_NODE_FOR_MINFEE: false, | ||||
|         EXTERNAL_ASSETS: [], | ||||
|         EXTERNAL_MAX_RETRY: 1, | ||||
|  | ||||
| @ -1,8 +1,13 @@ | ||||
| import config from '../../config'; | ||||
| import axios, { AxiosRequestConfig } from 'axios'; | ||||
| import http from 'http'; | ||||
| import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory'; | ||||
| import { IEsploraApi } from './esplora-api.interface'; | ||||
| 
 | ||||
| const axiosConnection = axios.create({ | ||||
|   httpAgent: new http.Agent({ keepAlive: true }) | ||||
| }); | ||||
| 
 | ||||
| class ElectrsApi implements AbstractBitcoinApi { | ||||
|   axiosConfig: AxiosRequestConfig = { | ||||
|     timeout: 10000, | ||||
| @ -11,52 +16,52 @@ class ElectrsApi implements AbstractBitcoinApi { | ||||
|   constructor() { } | ||||
| 
 | ||||
|   $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); | ||||
|   } | ||||
| 
 | ||||
|   $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); | ||||
|   } | ||||
| 
 | ||||
|   $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); | ||||
|   } | ||||
| 
 | ||||
|   $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); | ||||
|   } | ||||
| 
 | ||||
|   $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); | ||||
|   } | ||||
| 
 | ||||
|   $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); | ||||
|   } | ||||
| 
 | ||||
|   $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); | ||||
|   } | ||||
| 
 | ||||
|   $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); | ||||
|   } | ||||
| 
 | ||||
|   $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); | ||||
|   } | ||||
| 
 | ||||
|   $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); }); | ||||
|   } | ||||
| 
 | ||||
| @ -77,12 +82,12 @@ class ElectrsApi implements AbstractBitcoinApi { | ||||
|   } | ||||
| 
 | ||||
|   $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); | ||||
|   } | ||||
| 
 | ||||
|   $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); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -600,9 +600,11 @@ class Blocks { | ||||
|    * Index a block if it's missing from the database. Returns the block after indexing | ||||
|    */ | ||||
|   public async $indexBlock(height: number): Promise<BlockExtended> { | ||||
|     const dbBlock = await blocksRepository.$getBlockByHeight(height); | ||||
|     if (dbBlock != null) { | ||||
|       return prepareBlock(dbBlock); | ||||
|     if (Common.indexingEnabled()) { | ||||
|       const dbBlock = await blocksRepository.$getBlockByHeight(height); | ||||
|       if (dbBlock !== null) { | ||||
|         return prepareBlock(dbBlock); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     const blockHash = await bitcoinApi.$getBlockHash(height); | ||||
|  | ||||
| @ -19,7 +19,6 @@ interface IConfig { | ||||
|     MEMPOOL_BLOCKS_AMOUNT: number; | ||||
|     INDEXING_BLOCKS_AMOUNT: number; | ||||
|     BLOCKS_SUMMARIES_INDEXING: boolean; | ||||
|     PRICE_FEED_UPDATE_INTERVAL: number; | ||||
|     USE_SECOND_NODE_FOR_MINFEE: boolean; | ||||
|     EXTERNAL_ASSETS: string[]; | ||||
|     EXTERNAL_MAX_RETRY: number; | ||||
| @ -141,7 +140,6 @@ const defaults: IConfig = { | ||||
|     'MEMPOOL_BLOCKS_AMOUNT': 8, | ||||
|     'INDEXING_BLOCKS_AMOUNT': 11000, // 0 = disable indexing, -1 = index all blocks
 | ||||
|     'BLOCKS_SUMMARIES_INDEXING': false, | ||||
|     'PRICE_FEED_UPDATE_INTERVAL': 600, | ||||
|     'USE_SECOND_NODE_FOR_MINFEE': false, | ||||
|     'EXTERNAL_ASSETS': [], | ||||
|     '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 forensicsService from './tasks/lightning/forensics.service'; | ||||
| import priceUpdater from './tasks/price-updater'; | ||||
| import { AxiosError } from 'axios'; | ||||
| 
 | ||||
| class Server { | ||||
|   private wss: WebSocket.Server | undefined; | ||||
| @ -178,7 +179,7 @@ class Server { | ||||
| 
 | ||||
|       setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS); | ||||
|       this.currentBackendRetryInterval = 5; | ||||
|     } catch (e) { | ||||
|     } catch (e: any) { | ||||
|       const loggerMsg = `runMainLoop error: ${(e instanceof Error ? e.message : e)}. Retrying in ${this.currentBackendRetryInterval} sec.`; | ||||
|       if (this.currentBackendRetryInterval > 5) { | ||||
|         logger.warn(loggerMsg); | ||||
| @ -186,7 +187,9 @@ class Server { | ||||
|       } else { | ||||
|         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); | ||||
|       this.currentBackendRetryInterval *= 2; | ||||
|       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, | ||||
|     "MEMPOOL_BLOCKS_AMOUNT": 8, | ||||
|     "BLOCKS_SUMMARIES_INDEXING": false, | ||||
|     "PRICE_FEED_UPDATE_INTERVAL": 600, | ||||
|     "USE_SECOND_NODE_FOR_MINFEE": false, | ||||
|     "EXTERNAL_ASSETS": ["https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json"], | ||||
|     "STDOUT_LOG_MIN_PRIORITY": "info", | ||||
| @ -132,7 +131,6 @@ Corresponding `docker-compose.yml` overrides: | ||||
|       MEMPOOL_INITIAL_BLOCKS_AMOUNT: "" | ||||
|       MEMPOOL_MEMPOOL_BLOCKS_AMOUNT: "" | ||||
|       MEMPOOL_BLOCKS_SUMMARIES_INDEXING: "" | ||||
|       MEMPOOL_PRICE_FEED_UPDATE_INTERVAL: "" | ||||
|       MEMPOOL_USE_SECOND_NODE_FOR_MINFEE: "" | ||||
|       MEMPOOL_EXTERNAL_ASSETS: "" | ||||
|       MEMPOOL_STDOUT_LOG_MIN_PRIORITY: "" | ||||
|  | ||||
| @ -13,7 +13,6 @@ | ||||
|     "BLOCK_WEIGHT_UNITS": __MEMPOOL_BLOCK_WEIGHT_UNITS__, | ||||
|     "INITIAL_BLOCKS_AMOUNT": __MEMPOOL_INITIAL_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__, | ||||
|     "EXTERNAL_ASSETS": __MEMPOOL_EXTERNAL_ASSETS__, | ||||
|     "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_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=11000} | ||||
| __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_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[]} | ||||
| __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_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_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_EXTERNAL_ASSETS__!${__MEMPOOL_EXTERNAL_ASSETS__}!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', () => { | ||||
|       cy.visit('/status'); | ||||
|       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('.row > :nth-child(1)').invoke('text').then((text) => { | ||||
|         expect(text).to.match(/Incoming transactions.* vB\/s/); | ||||
| @ -219,11 +219,11 @@ describe('Mainnet', () => { | ||||
|     describe('blocks navigation', () => { | ||||
| 
 | ||||
|       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.visit('/'); | ||||
|           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="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); | ||||
|             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.visit('/'); | ||||
|           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.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'); | ||||
| @ -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.visit('/'); | ||||
|           cy.waitForSkeletonGone(); | ||||
|           cy.get('.blockchain-blocks-4 > a').click().then(() => { | ||||
|           cy.get('bitcoin-block-offset-0-index-7').click().then(() => { | ||||
|             cy.waitForPageIdle(); | ||||
| 
 | ||||
|             // block 6
 | ||||
| @ -309,7 +309,7 @@ describe('Mainnet', () => { | ||||
|           cy.viewport('macbook-16'); | ||||
|           cy.visit('/'); | ||||
|           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.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'); | ||||
|  | ||||
| @ -72,22 +72,10 @@ export const chartColors = [ | ||||
| ]; | ||||
| 
 | ||||
| export const poolsColor = { | ||||
|    'foundryusa': '#D81B60', | ||||
|    'antpool': '#8E24AA', | ||||
|    'f2pool': '#5E35B1', | ||||
|    'poolin': '#3949AB', | ||||
|    'binancepool': '#1E88E5', | ||||
|    'viabtc': '#039BE5', | ||||
|    'btccom': '#00897B', | ||||
|    'braiinspool': '#00ACC1', | ||||
|    'sbicrypto': '#43A047', | ||||
|    'marapool': '#7CB342', | ||||
|    'luxor': '#C0CA33', | ||||
|    'unknown': '#FDD835', | ||||
|    'okkong': '#FFB300', | ||||
| } | ||||
|   'unknown': '#9C9C9C', | ||||
| }; | ||||
| 
 | ||||
|  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]; | ||||
| 
 | ||||
| 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"> | ||||
|     <h1 i18n="shared.address">Address</h1> | ||||
|     <div class="tx-link"> | ||||
| @ -15,7 +15,7 @@ | ||||
| 
 | ||||
|       <div class="row"> | ||||
|         <div class="col-md"> | ||||
|           <table class="table table-borderless table-striped"> | ||||
|           <table class="table table-borderless table-striped address-table"> | ||||
|             <tbody> | ||||
|               <tr *ngIf="addressInfo && addressInfo.unconfidential"> | ||||
|                 <td i18n="address.unconfidential">Unconfidential</td> | ||||
|  | ||||
| @ -91,3 +91,20 @@ h1 { | ||||
|     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({ | ||||
|                   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]), | ||||
|                   sizePerWeight: data.weights.map((val, i) => [val.timestamp * 1000, data.sizes[i].avgSize / (val.avgWeight / 4), val.avgHeight]), | ||||
|                 }); | ||||
|                 this.isLoading = false; | ||||
|               }), | ||||
| @ -124,6 +125,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit { | ||||
|       color: [ | ||||
|         '#FDD835', | ||||
|         '#D81B60', | ||||
|         '#039BE5', | ||||
|       ], | ||||
|       grid: { | ||||
|         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`; | ||||
|             } else if (tick.seriesIndex === 1) { // Weight
 | ||||
|               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>`; | ||||
|           } | ||||
| @ -192,10 +196,19 @@ export class BlockSizesWeightsGraphComponent implements OnInit { | ||||
|             }, | ||||
|             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'))  ?? { | ||||
|           'Size': true, | ||||
|           'Weight': true, | ||||
|           'Size per weight': true, | ||||
|         } | ||||
|       }, | ||||
|       yAxis: data.sizes.length === 0 ? undefined : [ | ||||
| @ -262,6 +275,18 @@ export class BlockSizesWeightsGraphComponent implements OnInit { | ||||
|           lineStyle: { | ||||
|             width: 2, | ||||
|           } | ||||
|         }, | ||||
|         { | ||||
|           zlevel: 1, | ||||
|           yAxisIndex: 0, | ||||
|           name: $localize`Size per weight`, | ||||
|           showSymbol: false, | ||||
|           symbol: 'none', | ||||
|           data: data.sizePerWeight, | ||||
|           type: 'line', | ||||
|           lineStyle: { | ||||
|             width: 2, | ||||
|           } | ||||
|         } | ||||
|       ], | ||||
|       dataZoom: [{ | ||||
|  | ||||
| @ -1,64 +1,86 @@ | ||||
| <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"> | ||||
|     <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 } }" | ||||
|           class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}"> </a> | ||||
|         <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 class="block-body"> | ||||
|           <div [attr.data-cy]="'bitcoin-block-' + i + '-fees'" class="fees"> | ||||
|             ~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container> | ||||
|           <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> | ||||
|           </div> | ||||
|           <div [attr.data-cy]="'bitcoin-block-' + i + '-fee-span'" class="fee-span" *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 [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-fee-span'" class="fee-span" | ||||
|             *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 [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 [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> | ||||
|           </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"> | ||||
|             <ng-container *ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container> | ||||
|             <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> | ||||
|             <ng-container | ||||
|               *ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container> | ||||
|             <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 [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 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> | ||||
|         </div> | ||||
|       </div> | ||||
|     </ng-container> | ||||
|     <ng-template #placeholderBlock> | ||||
|       <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> | ||||
|       </ng-container> | ||||
|     </ng-template> | ||||
|     <ng-template #loadingBlock> | ||||
|       <ng-container *ngIf="block && block.loading"> | ||||
|         <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> | ||||
|       </ng-container> | ||||
|     </ng-template> | ||||
|   </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> | ||||
| 
 | ||||
| <ng-template #loadingBlocksTemplate> | ||||
|   <div class="blocks-container" [class.time-ltr]="timeLtr"> | ||||
|     <div class="flashing"> | ||||
|       <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 *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> | ||||
|     </div> | ||||
|   </div> | ||||
| </ng-template> | ||||
| 
 | ||||
|  | ||||
| @ -123,8 +123,10 @@ | ||||
|       (pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false"> | ||||
|     </ngb-pagination> | ||||
| 
 | ||||
|     <div class="clearfix"></div> | ||||
|     <br> | ||||
|     <ng-template [ngIf]="!widget"> | ||||
|       <div class="clearfix"></div> | ||||
|       <br> | ||||
|     </ng-template> | ||||
|   </div> | ||||
|    | ||||
| </div> | ||||
|  | ||||
| @ -176,7 +176,7 @@ export class PoolRankingComponent implements OnInit { | ||||
|     // 'Other'
 | ||||
|     data.push({ | ||||
|       itemStyle: { | ||||
|         color: 'grey', | ||||
|         color: '#6b6b6b', | ||||
|       }, | ||||
|       value: totalShareOther, | ||||
|       name: 'Other' + (isMobile() ? `` : ` (${totalShareOther.toFixed(2)}%)`), | ||||
|  | ||||
| @ -216,8 +216,8 @@ | ||||
| 
 | ||||
| <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>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>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>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>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 type="how-do-block-audits-work"> | ||||
|  | ||||
| @ -151,6 +151,19 @@ export interface RewardStats { | ||||
|   totalTx: number; | ||||
| } | ||||
| 
 | ||||
| export interface BlockSizesAndWeights { | ||||
|   sizes: { | ||||
|     timestamp: number; | ||||
|     avgHeight: number; | ||||
|     avgSize: number; | ||||
|   }[]; | ||||
|   weights: { | ||||
|     timestamp: number; | ||||
|     avgHeight: number; | ||||
|     avgWeight: number; | ||||
|   }[]; | ||||
| } | ||||
| 
 | ||||
| export interface AuditScore { | ||||
|   hash: string; | ||||
|   matchRate?: number; | ||||
|  | ||||
| @ -103,6 +103,5 @@ | ||||
|   </div> | ||||
| 
 | ||||
|   <br> | ||||
| </div> | ||||
| 
 | ||||
| <br> | ||||
| </div> | ||||
| @ -1,7 +1,7 @@ | ||||
| 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, | ||||
|   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 { StateService } from './state.service'; | ||||
| import { WebsocketResponse } from '../interfaces/websocket.interface'; | ||||
| @ -222,8 +222,8 @@ export class ApiService { | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   getHistoricalBlockSizesAndWeights$(interval: string | undefined) : Observable<any> { | ||||
|     return this.httpClient.get<any[]>( | ||||
|   getHistoricalBlockSizesAndWeights$(interval: string | undefined) : Observable<HttpResponse<BlockSizesAndWeights>> { | ||||
|     return this.httpClient.get<BlockSizesAndWeights>( | ||||
|       this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/sizes-weights` + | ||||
|       (interval !== undefined ? `/${interval}` : ''), { observe: 'response' } | ||||
|     ); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user