diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index aed2d0004..7a9589348 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -108,14 +108,23 @@ class Blocks { blockExtended.extras.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0); blockExtended.extras.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]); - const stats = await bitcoinClient.getBlockStats(block.id); const coinbaseRaw: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true); blockExtended.extras.coinbaseRaw = coinbaseRaw.hex; - blockExtended.extras.medianFee = stats.feerate_percentiles[2]; // 50th percentiles - blockExtended.extras.feeRange = [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat(); - blockExtended.extras.totalFees = stats.totalfee; - blockExtended.extras.avgFee = stats.avgfee; - blockExtended.extras.avgFeeRate = stats.avgfeerate; + + if (block.height === 0) { + blockExtended.extras.medianFee = 0; // 50th percentiles + blockExtended.extras.feeRange = [0, 0, 0, 0, 0, 0, 0]; + blockExtended.extras.totalFees = 0; + blockExtended.extras.avgFee = 0; + blockExtended.extras.avgFeeRate = 0; + } else { + const stats = await bitcoinClient.getBlockStats(block.id); + blockExtended.extras.medianFee = stats.feerate_percentiles[2]; // 50th percentiles + blockExtended.extras.feeRange = [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat(); + blockExtended.extras.totalFees = stats.totalfee; + blockExtended.extras.avgFee = stats.avgfee; + blockExtended.extras.avgFeeRate = stats.avgfeerate; + } if (Common.indexingEnabled()) { let pool: PoolTag; @@ -336,10 +345,13 @@ class Blocks { await blocksRepository.$saveBlockInDatabase(blockExtended); - return blockExtended; + return this.prepareBlock(blockExtended); } public async $getBlocksExtras(fromHeight: number, limit: number = 15): Promise { + // Note - This API is breaking if indexing is not available. For now it is okay because we only + // use it for the mining pages, and mining pages should not be available if indexing is turned off. + // I'll need to fix it before we refactor the block(s) related pages try { loadingIndicators.setProgress('blocks', 0); @@ -363,7 +375,7 @@ class Blocks { for (let i = 0; i < limit && currentHeight >= 0; i++) { let block = this.getBlocks().find((b) => b.height === currentHeight); if (!block && Common.indexingEnabled()) { - block = this.prepareBlock(await this.$indexBlock(currentHeight)); + block = await this.$indexBlock(currentHeight); } else if (!block) { block = this.prepareBlock(await bitcoinApi.$getBlock(nextHash)); } @@ -383,24 +395,25 @@ class Blocks { private prepareBlock(block: any): BlockExtended { return { id: block.id ?? block.hash, // hash for indexed block - timestamp: block?.timestamp ?? block?.blockTimestamp, // blockTimestamp for indexed block - height: block?.height, - version: block?.version, - bits: block?.bits, - nonce: block?.nonce, - difficulty: block?.difficulty, - merkle_root: block?.merkle_root, - tx_count: block?.tx_count, - size: block?.size, - weight: block?.weight, - previousblockhash: block?.previousblockhash, + timestamp: block.timestamp ?? block.blockTimestamp, // blockTimestamp for indexed block + height: block.height, + version: block.version, + bits: block.bits, + nonce: block.nonce, + difficulty: block.difficulty, + merkle_root: block.merkle_root, + tx_count: block.tx_count, + size: block.size, + weight: block.weight, + previousblockhash: block.previousblockhash, extras: { - medianFee: block?.medianFee, - feeRange: block?.feeRange ?? [], // TODO - reward: block?.reward, + medianFee: block.medianFee ?? block.median_fee ?? block.extras?.medianFee, + feeRange: block.feeRange ?? block.fee_range ?? block?.extras?.feeSpan, + reward: block.reward ?? block?.extras?.reward, + totalFees: block.totalFees ?? block?.fees ?? block?.extras.totalFees, pool: block?.extras?.pool ?? (block?.pool_id ? { - id: block?.pool_id, - name: block?.pool_name, + id: block.pool_id, + name: block.pool_name, } : undefined), } }; diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 0cab3c0db..041086f73 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -277,7 +277,10 @@ class BlocksRepository { const connection = await DB.pool.getConnection(); try { const [rows]: any[] = await connection.query(` - SELECT *, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, pools.addresses as pool_addresses, pools.regexes as pool_regexes + SELECT *, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, + pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, + pools.addresses as pool_addresses, pools.regexes as pool_regexes, + previous_block_hash as previousblockhash FROM blocks JOIN pools ON blocks.pool_id = pools.id WHERE height = ${height}; diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index 480df9f3f..2a4818c4f 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -7,32 +7,32 @@ + - - + - - + + @@ -50,51 +50,41 @@ - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - + +
HeightPool Timestamp MinedPool Reward Fees Txs Size
{{ block.height }} - ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} - - - {{ block.extras.pool.name }} + ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} + + +
+ + + + + + + + + + + + + + + +
- - - - - - - - - - - - - - - -
- + + \ No newline at end of file diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.scss b/frontend/src/app/components/blocks-list/blocks-list.component.scss index 3d7ffe631..71d96323c 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.scss +++ b/frontend/src/app/components/blocks-list/blocks-list.component.scss @@ -1,5 +1,6 @@ .container-xl { max-width: 1400px; + padding-bottom: 0; } .container { @@ -11,6 +12,15 @@ padding-bottom: 15px; } +.disabled { + pointer-events: none; + opacity: 0.5; +} + +.progress { + background-color: #2d3348; +} + .pool-name { display: inline-block; vertical-align: text-top; @@ -18,7 +28,7 @@ } .height { - width: 12%; + width: 10%; @media (max-width: 1100px) { width: 10%; } @@ -31,6 +41,7 @@ } .mined { + width: 13%; @media (max-width: 576px) { display: none; } @@ -53,7 +64,7 @@ } .pool { - width: 12%; + width: 17%; } .reward { @@ -71,4 +82,4 @@ @media (max-width: 650px) { width: 20%; } -} \ No newline at end of file +} diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.ts b/frontend/src/app/components/blocks-list/blocks-list.component.ts index 56563d7e6..3143b4f61 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.ts +++ b/frontend/src/app/components/blocks-list/blocks-list.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; -import { Observable } from 'rxjs'; -import { map, repeat, tap } from 'rxjs/operators'; +import { BehaviorSubject, Observable, timer } from 'rxjs'; +import { delayWhen, map, retryWhen, switchMap, tap } from 'rxjs/operators'; import { BlockExtended } from 'src/app/interfaces/node-api.interface'; import { ApiService } from 'src/app/services/api.service'; import { StateService } from 'src/app/services/state.service'; @@ -13,33 +13,52 @@ import { StateService } from 'src/app/services/state.service'; }) export class BlocksList implements OnInit { blocks$: Observable = undefined + isLoading = true; - oldestBlockHeight = undefined; + fromBlockHeight = undefined; + paginationMaxSize: number; + page = 1; + blocksCount: number; + fromHeightSubject: BehaviorSubject = new BehaviorSubject(this.fromBlockHeight); constructor( private apiService: ApiService, - public stateService: StateService + public stateService: StateService, ) { } ngOnInit(): void { - this.blocks$ = this.apiService.getBlocks$(this.oldestBlockHeight) - .pipe( - tap(blocks => { - this.isLoading = false; - }), - map(blocks => { - for (const block of blocks) { - // @ts-ignore - block.extras.pool.logo = `./resources/mining-pools/` + - block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; - this.oldestBlockHeight = block.height; - } - return blocks; - }), - repeat(2), - ); + this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5; + + this.blocks$ = this.fromHeightSubject.pipe( + switchMap(() => { + this.isLoading = true; + return this.apiService.getBlocks$(this.fromBlockHeight) + .pipe( + tap(blocks => { + if (this.blocksCount === undefined) { + this.blocksCount = blocks[0].height; + } + this.isLoading = false; + }), + map(blocks => { + for (const block of blocks) { + // @ts-ignore: Need to add an extra field for the template + block.extras.pool.logo = `./resources/mining-pools/` + + block.extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; + } + return blocks; + }), + retryWhen(errors => errors.pipe(delayWhen(() => timer(1000)))) + ) + }) + ); + } + + pageChange(page: number) { + this.fromBlockHeight = this.blocksCount - (page - 1) * 15; + this.fromHeightSubject.next(this.fromBlockHeight); } trackByBlock(index: number, block: BlockExtended) {