diff --git a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts index 53c731e1f..71269da31 100644 --- a/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts +++ b/backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts @@ -2,7 +2,7 @@ import { IEsploraApi } from './esplora-api.interface'; export interface AbstractBitcoinApi { $getRawMempool(): Promise; - $getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean): Promise; + $getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean, lazyPrevouts?: boolean): Promise; $getBlockHeightTip(): Promise; $getTxIdsForBlock(hash: string): Promise; $getBlockHash(height: number): Promise; diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index e52aaffaf..41671ede1 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -31,7 +31,8 @@ class BitcoinApi implements AbstractBitcoinApi { }; } - $getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise { + + $getRawTransaction(txId: string, skipConversion = false, addPrevout = false, lazyPrevouts = false): Promise { // If the transaction is in the mempool we already converted and fetched the fee. Only prevouts are missing const txInMempool = mempool.getMempool()[txId]; if (txInMempool && addPrevout) { @@ -46,7 +47,7 @@ class BitcoinApi implements AbstractBitcoinApi { }); return transaction; } - return this.$convertTransaction(transaction, addPrevout); + return this.$convertTransaction(transaction, addPrevout, lazyPrevouts); }) .catch((e: Error) => { if (e.message.startsWith('The genesis block coinbase')) { @@ -126,7 +127,7 @@ class BitcoinApi implements AbstractBitcoinApi { const outSpends: IEsploraApi.Outspend[] = []; const tx = await this.$getRawTransaction(txId, true, false); for (let i = 0; i < tx.vout.length; i++) { - if (tx.status && tx.status.block_height == 0) { + if (tx.status && tx.status.block_height === 0) { outSpends.push({ spent: false }); @@ -145,7 +146,7 @@ class BitcoinApi implements AbstractBitcoinApi { return this.bitcoindClient.getNetworkHashPs(120, blockHeight); } - protected async $convertTransaction(transaction: IBitcoinApi.Transaction, addPrevout: boolean): Promise { + protected async $convertTransaction(transaction: IBitcoinApi.Transaction, addPrevout: boolean, lazyPrevouts = false): Promise { let esploraTransaction: IEsploraApi.Transaction = { txid: transaction.txid, version: transaction.version, @@ -192,7 +193,7 @@ class BitcoinApi implements AbstractBitcoinApi { } if (addPrevout) { - esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction); + esploraTransaction = await this.$calculateFeeFromInputs(esploraTransaction, false, lazyPrevouts); } else if (!transaction.confirmations) { esploraTransaction = await this.$appendMempoolFeeData(esploraTransaction); } @@ -268,20 +269,30 @@ class BitcoinApi implements AbstractBitcoinApi { return this.bitcoindClient.getRawMemPool(true); } - private async $calculateFeeFromInputs(transaction: IEsploraApi.Transaction): Promise { + + private async $calculateFeeFromInputs(transaction: IEsploraApi.Transaction, addPrevout: boolean, lazyPrevouts: boolean): Promise { if (transaction.vin[0].is_coinbase) { transaction.fee = 0; return transaction; } let totalIn = 0; - for (const vin of transaction.vin) { - const innerTx = await this.$getRawTransaction(vin.txid, false, false); - vin.prevout = innerTx.vout[vin.vout]; - this.addInnerScriptsToVin(vin); - totalIn += innerTx.vout[vin.vout].value; + + for (let i = 0; i < transaction.vin.length; i++) { + if (lazyPrevouts && i > 12) { + transaction.vin[i].lazy = true; + continue; + } + const innerTx = await this.$getRawTransaction(transaction.vin[i].txid, false, false); + transaction.vin[i].prevout = innerTx.vout[transaction.vin[i].vout]; + this.addInnerScriptsToVin(transaction.vin[i]); + totalIn += innerTx.vout[transaction.vin[i].vout].value; + } + if (lazyPrevouts && transaction.vin.length > 12) { + transaction.fee = -1; + } else { + const totalOut = transaction.vout.reduce((p, output) => p + output.value, 0); + transaction.fee = parseFloat((totalIn - totalOut).toFixed(8)); } - const totalOut = transaction.vout.reduce((p, output) => p + output.value, 0); - transaction.fee = parseFloat((totalIn - totalOut).toFixed(8)); return transaction; } diff --git a/backend/src/api/bitcoin/esplora-api.interface.ts b/backend/src/api/bitcoin/esplora-api.interface.ts index 73ccbd88f..f825c60f9 100644 --- a/backend/src/api/bitcoin/esplora-api.interface.ts +++ b/backend/src/api/bitcoin/esplora-api.interface.ts @@ -33,6 +33,8 @@ export namespace IEsploraApi { // Elements is_pegin?: boolean; issuance?: Issuance; + // Custom + lazy?: boolean; } interface Issuance { diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 293862e93..b9b7b3aff 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -15,6 +15,7 @@ import BitcoinApi from './bitcoin/bitcoin-api'; import { prepareBlock } from '../utils/blocks-utils'; import BlocksRepository from '../repositories/BlocksRepository'; import HashratesRepository from '../repositories/HashratesRepository'; +import indexer from '../indexer'; class Blocks { private blocks: BlockExtended[] = []; @@ -23,9 +24,6 @@ class Blocks { private lastDifficultyAdjustmentTime = 0; private previousDifficultyRetarget = 0; private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = []; - private blockIndexingStarted = false; - public blockIndexingCompleted = false; - public reindexFlag = false; constructor() { } @@ -197,24 +195,15 @@ class Blocks { * [INDEXING] Index all blocks metadata for the mining dashboard */ public async $generateBlockDatabase() { - if (this.blockIndexingStarted && !this.reindexFlag) { - return; - } - - this.reindexFlag = false; - const blockchainInfo = await bitcoinClient.getBlockchainInfo(); if (blockchainInfo.blocks !== blockchainInfo.headers) { // Wait for node to sync return; } - this.blockIndexingStarted = true; - this.blockIndexingCompleted = false; - try { let currentBlockHeight = blockchainInfo.blocks; - let indexingBlockAmount = config.MEMPOOL.INDEXING_BLOCKS_AMOUNT; + let indexingBlockAmount = Math.min(config.MEMPOOL.INDEXING_BLOCKS_AMOUNT, blockchainInfo.blocks); if (indexingBlockAmount <= -1) { indexingBlockAmount = currentBlockHeight + 1; } @@ -275,14 +264,14 @@ class Blocks { loadingIndicators.setProgress('block-indexing', 100); } catch (e) { logger.err('Block indexing failed. Trying again later. Reason: ' + (e instanceof Error ? e.message : e)); - this.blockIndexingStarted = false; loadingIndicators.setProgress('block-indexing', 100); return; } const chainValid = await BlocksRepository.$validateChain(); - this.reindexFlag = !chainValid; - this.blockIndexingCompleted = chainValid; + if (!chainValid) { + indexer.reindex(); + } } public async $updateBlocks() { @@ -299,6 +288,8 @@ class Blocks { logger.info(`${blockHeightTip - this.currentBlockHeight} blocks since tip. Fast forwarding to the ${config.MEMPOOL.INITIAL_BLOCKS_AMOUNT} recent blocks`); this.currentBlockHeight = blockHeightTip - config.MEMPOOL.INITIAL_BLOCKS_AMOUNT; fastForwarded = true; + logger.info(`Re-indexing skipped blocks and corresponding hashrates data`); + indexer.reindex(); // Make sure to index the skipped blocks #1619 } if (!this.lastDifficultyAdjustmentTime) { @@ -426,10 +417,7 @@ class Blocks { return 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 + public async $getBlocks(fromHeight?: number, limit: number = 15): Promise { try { let currentHeight = fromHeight !== undefined ? fromHeight : this.getCurrentBlockHeight(); const returnBlocks: BlockExtended[] = []; diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index cbd412068..8d11abfa7 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -4,14 +4,11 @@ import PoolsRepository from '../repositories/PoolsRepository'; import HashratesRepository from '../repositories/HashratesRepository'; import bitcoinClient from './bitcoin/bitcoin-client'; import logger from '../logger'; -import blocks from './blocks'; import { Common } from './common'; import loadingIndicators from './loading-indicators'; +import { escape } from 'mysql2'; class Mining { - hashrateIndexingStarted = false; - weeklyHashrateIndexingStarted = false; - constructor() { } @@ -110,7 +107,7 @@ class Mining { public async $getPoolStat(slug: string): Promise { const pool = await PoolsRepository.$getPool(slug); if (!pool) { - throw new Error(`This mining pool does not exist`); + throw new Error('This mining pool does not exist ' + escape(slug)); } const blockCount: number = await BlocksRepository.$blockCount(pool.id); @@ -152,14 +149,9 @@ class Mining { * [INDEXING] Generate weekly mining pool hashrate history */ public async $generatePoolHashrateHistory(): Promise { - if (!blocks.blockIndexingCompleted || this.hashrateIndexingStarted || this.weeklyHashrateIndexingStarted) { - return; - } - const now = new Date(); try { - this.weeklyHashrateIndexingStarted = true; const lastestRunDate = await HashratesRepository.$getLatestRun('last_weekly_hashrates_indexing'); // Run only if: @@ -167,11 +159,9 @@ class Mining { // * we started a new week (around Monday midnight) const runIndexing = lastestRunDate === 0 || now.getUTCDay() === 1 && lastestRunDate !== now.getUTCDate(); if (!runIndexing) { - this.weeklyHashrateIndexingStarted = false; return; } } catch (e) { - this.weeklyHashrateIndexingStarted = false; throw e; } @@ -191,6 +181,7 @@ class Mining { const startedAt = new Date().getTime() / 1000; let timer = new Date().getTime() / 1000; + logger.debug(`Indexing weekly mining pool hashrate`); loadingIndicators.setProgress('weekly-hashrate-indexing', 0); while (toTimestamp > genesisTimestamp) { @@ -255,7 +246,6 @@ class Mining { ++indexedThisRun; ++totalIndexed; } - this.weeklyHashrateIndexingStarted = false; await HashratesRepository.$setLatestRun('last_weekly_hashrates_indexing', new Date().getUTCDate()); if (newlyIndexed > 0) { logger.info(`Indexed ${newlyIndexed} pools weekly hashrate`); @@ -263,7 +253,6 @@ class Mining { loadingIndicators.setProgress('weekly-hashrate-indexing', 100); } catch (e) { loadingIndicators.setProgress('weekly-hashrate-indexing', 100); - this.weeklyHashrateIndexingStarted = false; throw e; } } @@ -272,22 +261,14 @@ class Mining { * [INDEXING] Generate daily hashrate data */ public async $generateNetworkHashrateHistory(): Promise { - if (!blocks.blockIndexingCompleted || this.hashrateIndexingStarted) { - return; - } - try { - this.hashrateIndexingStarted = true; - // We only run this once a day around midnight const latestRunDate = await HashratesRepository.$getLatestRun('last_hashrates_indexing'); const now = new Date().getUTCDate(); if (now === latestRunDate) { - this.hashrateIndexingStarted = false; return; } } catch (e) { - this.hashrateIndexingStarted = false; throw e; } @@ -305,6 +286,7 @@ class Mining { const startedAt = new Date().getTime() / 1000; let timer = new Date().getTime() / 1000; + logger.debug(`Indexing daily network hashrate`); loadingIndicators.setProgress('daily-hashrate-indexing', 0); while (toTimestamp > genesisTimestamp) { @@ -376,14 +358,12 @@ class Mining { await HashratesRepository.$saveHashrates(hashrates); await HashratesRepository.$setLatestRun('last_hashrates_indexing', new Date().getUTCDate()); - this.hashrateIndexingStarted = false; if (newlyIndexed > 0) { logger.info(`Indexed ${newlyIndexed} day of network hashrate`); } loadingIndicators.setProgress('daily-hashrate-indexing', 100); } catch (e) { loadingIndicators.setProgress('daily-hashrate-indexing', 100); - this.hashrateIndexingStarted = false; throw e; } } diff --git a/backend/src/api/transaction-utils.ts b/backend/src/api/transaction-utils.ts index 2e669d709..5b92cea5f 100644 --- a/backend/src/api/transaction-utils.ts +++ b/backend/src/api/transaction-utils.ts @@ -21,8 +21,8 @@ class TransactionUtils { }; } - public async $getTransactionExtended(txId: string, addPrevouts = false): Promise { - const transaction: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(txId, false, addPrevouts); + public async $getTransactionExtended(txId: string, addPrevouts = false, lazyPrevouts = false): Promise { + const transaction: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(txId, false, addPrevouts, lazyPrevouts); return this.extendTransaction(transaction); } diff --git a/backend/src/index.ts b/backend/src/index.ts index c6eab6c17..30c5ecf37 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -27,8 +27,8 @@ import icons from './api/liquid/icons'; import { Common } from './api/common'; import mining from './api/mining'; import HashratesRepository from './repositories/HashratesRepository'; -import BlocksRepository from './repositories/BlocksRepository'; import poolsUpdater from './tasks/pools-updater'; +import indexer from './indexer'; class Server { private wss: WebSocket.Server | undefined; @@ -99,7 +99,7 @@ class Server { } await databaseMigration.$initializeOrMigrateDatabase(); if (Common.indexingEnabled()) { - await this.$resetHashratesIndexingState(); + await indexer.$resetHashratesIndexingState(); } } catch (e) { throw new Error(e instanceof Error ? e.message : 'Error'); @@ -154,7 +154,7 @@ class Server { await poolsUpdater.updatePoolsJson(); await blocks.$updateBlocks(); await memPool.$updateMempool(); - this.$runIndexingWhenReady(); + indexer.$run(); setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS); this.currentBackendRetryInterval = 5; @@ -173,29 +173,6 @@ class Server { } } - async $resetHashratesIndexingState() { - try { - await HashratesRepository.$setLatestRun('last_hashrates_indexing', 0); - await HashratesRepository.$setLatestRun('last_weekly_hashrates_indexing', 0); - } catch (e) { - logger.err(`Cannot reset hashrate indexing timestamps. Reason: ` + (e instanceof Error ? e.message : e)); - } - } - - async $runIndexingWhenReady() { - if (!Common.indexingEnabled() || mempool.hasPriority()) { - return; - } - - try { - await blocks.$generateBlockDatabase(); - await mining.$generateNetworkHashrateHistory(); - await mining.$generatePoolHashrateHistory(); - } catch (e) { - logger.err(`Indexing failed, trying again later. Reason: ` + (e instanceof Error ? e.message : e)); - } - } - setUpWebsocketHandling() { if (this.wss) { websocketHandler.setWebsocketServer(this.wss); @@ -337,8 +314,8 @@ class Server { } this.app - .get(config.MEMPOOL.API_URL_PREFIX + 'blocks-extras', routes.getBlocksExtras) - .get(config.MEMPOOL.API_URL_PREFIX + 'blocks-extras/:height', routes.getBlocksExtras) + .get(config.MEMPOOL.API_URL_PREFIX + 'blocks', routes.getBlocks.bind(routes)) + .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/:height', routes.getBlocks.bind(routes)) .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash', routes.getBlock); if (config.MEMPOOL.BACKEND !== 'esplora') { @@ -352,8 +329,6 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/status', routes.getTransactionStatus) .get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/outspends', routes.getTransactionOutspends) .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/header', routes.getBlockHeader) - .get(config.MEMPOOL.API_URL_PREFIX + 'blocks', routes.getBlocks) - .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/:height', routes.getBlocks) .get(config.MEMPOOL.API_URL_PREFIX + 'blocks/tip/height', routes.getBlockTipHeight) .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txs', routes.getBlockTransactions) .get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txs/:index', routes.getBlockTransactions) diff --git a/backend/src/indexer.ts b/backend/src/indexer.ts new file mode 100644 index 000000000..7ddc2a47f --- /dev/null +++ b/backend/src/indexer.ts @@ -0,0 +1,52 @@ +import { Common } from './api/common'; +import blocks from './api/blocks'; +import mempool from './api/mempool'; +import mining from './api/mining'; +import logger from './logger'; +import HashratesRepository from './repositories/HashratesRepository'; + +class Indexer { + runIndexer = true; + indexerRunning = false; + + constructor() { + } + + public reindex() { + this.runIndexer = true; + } + + public async $run() { + if (!Common.indexingEnabled() || this.runIndexer === false || + this.indexerRunning === true || mempool.hasPriority() + ) { + return; + } + + this.runIndexer = false; + this.indexerRunning = true; + + try { + await blocks.$generateBlockDatabase(); + await this.$resetHashratesIndexingState(); + await mining.$generateNetworkHashrateHistory(); + await mining.$generatePoolHashrateHistory(); + } catch (e) { + this.reindex(); + logger.err(`Indexer failed, trying again later. Reason: ` + (e instanceof Error ? e.message : e)); + } + + this.indexerRunning = false; + } + + async $resetHashratesIndexingState() { + try { + await HashratesRepository.$setLatestRun('last_hashrates_indexing', 0); + await HashratesRepository.$setLatestRun('last_weekly_hashrates_indexing', 0); + } catch (e) { + logger.err(`Cannot reset hashrate indexing timestamps. Reason: ` + (e instanceof Error ? e.message : e)); + } + } +} + +export default new Indexer(); diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 0081bd34f..60b07da1b 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -81,7 +81,7 @@ export interface TransactionStripped { export interface BlockExtension { totalFees?: number; - medianFee?: number; // Actually the median fee rate that we compute ourself + medianFee?: number; feeRange?: number[]; reward?: number; coinbaseTx?: TransactionMinerInfo; diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 47a18fc9b..dcd5b48d1 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -5,6 +5,7 @@ import { Common } from '../api/common'; import { prepareBlock } from '../utils/blocks-utils'; import PoolsRepository from './PoolsRepository'; import HashratesRepository from './HashratesRepository'; +import { escape } from 'mysql2'; class BlocksRepository { /** @@ -235,7 +236,7 @@ class BlocksRepository { public async $getBlocksByPool(slug: string, startHeight?: number): Promise { const pool = await PoolsRepository.$getPool(slug); if (!pool) { - throw new Error(`This mining pool does not exist`); + throw new Error('This mining pool does not exist ' + escape(slug)); } const params: any[] = []; @@ -474,9 +475,9 @@ class BlocksRepository { public async $getHistoricalBlockFees(div: number, interval: string | null): Promise { try { let query = `SELECT - CAST(AVG(height) as INT) as avg_height, + CAST(AVG(height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(fees) as INT) as avg_fees + CAST(AVG(fees) as INT) as avgFees FROM blocks`; if (interval !== null) { @@ -499,9 +500,9 @@ class BlocksRepository { public async $getHistoricalBlockRewards(div: number, interval: string | null): Promise { try { let query = `SELECT - CAST(AVG(height) as INT) as avg_height, + CAST(AVG(height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(reward) as INT) as avg_rewards + CAST(AVG(reward) as INT) as avgRewards FROM blocks`; if (interval !== null) { @@ -524,15 +525,15 @@ class BlocksRepository { public async $getHistoricalBlockFeeRates(div: number, interval: string | null): Promise { try { let query = `SELECT - CAST(AVG(height) as INT) as avg_height, + CAST(AVG(height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(JSON_EXTRACT(fee_span, '$[0]')) as INT) as avg_fee_0, - CAST(AVG(JSON_EXTRACT(fee_span, '$[1]')) as INT) as avg_fee_10, - CAST(AVG(JSON_EXTRACT(fee_span, '$[2]')) as INT) as avg_fee_25, - CAST(AVG(JSON_EXTRACT(fee_span, '$[3]')) as INT) as avg_fee_50, - CAST(AVG(JSON_EXTRACT(fee_span, '$[4]')) as INT) as avg_fee_75, - CAST(AVG(JSON_EXTRACT(fee_span, '$[5]')) as INT) as avg_fee_90, - CAST(AVG(JSON_EXTRACT(fee_span, '$[6]')) as INT) as avg_fee_100 + CAST(AVG(JSON_EXTRACT(fee_span, '$[0]')) as INT) as avgFee_0, + CAST(AVG(JSON_EXTRACT(fee_span, '$[1]')) as INT) as avgFee_10, + CAST(AVG(JSON_EXTRACT(fee_span, '$[2]')) as INT) as avgFee_25, + CAST(AVG(JSON_EXTRACT(fee_span, '$[3]')) as INT) as avgFee_50, + CAST(AVG(JSON_EXTRACT(fee_span, '$[4]')) as INT) as avgFee_75, + CAST(AVG(JSON_EXTRACT(fee_span, '$[5]')) as INT) as avgFee_90, + CAST(AVG(JSON_EXTRACT(fee_span, '$[6]')) as INT) as avgFee_100 FROM blocks`; if (interval !== null) { @@ -555,9 +556,9 @@ class BlocksRepository { public async $getHistoricalBlockSizes(div: number, interval: string | null): Promise { try { let query = `SELECT - CAST(AVG(height) as INT) as avg_height, + CAST(AVG(height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(size) as INT) as avg_size + CAST(AVG(size) as INT) as avgSize FROM blocks`; if (interval !== null) { @@ -580,9 +581,9 @@ class BlocksRepository { public async $getHistoricalBlockWeights(div: number, interval: string | null): Promise { try { let query = `SELECT - CAST(AVG(height) as INT) as avg_height, + CAST(AVG(height) as INT) as avgHeight, CAST(AVG(UNIX_TIMESTAMP(blockTimestamp)) as INT) as timestamp, - CAST(AVG(weight) as INT) as avg_weight + CAST(AVG(weight) as INT) as avgWeight FROM blocks`; if (interval !== null) { diff --git a/backend/src/repositories/HashratesRepository.ts b/backend/src/repositories/HashratesRepository.ts index 661535aa3..531b6cdcf 100644 --- a/backend/src/repositories/HashratesRepository.ts +++ b/backend/src/repositories/HashratesRepository.ts @@ -1,3 +1,4 @@ +import { escape } from 'mysql2'; import { Common } from '../api/common'; import DB from '../database'; import logger from '../logger'; @@ -105,7 +106,7 @@ class HashratesRepository { public async $getPoolWeeklyHashrate(slug: string): Promise { const pool = await PoolsRepository.$getPool(slug); if (!pool) { - throw new Error(`This mining pool does not exist`); + throw new Error('This mining pool does not exist ' + escape(slug)); } // Find hashrate boundaries diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index 037a6250a..c7cc6cba3 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -78,7 +78,6 @@ class PoolsRepository { const [rows]: any[] = await DB.query(query, [slug]); if (rows.length < 1) { - logger.debug(`This slug does not match any known pool`); return null; } diff --git a/backend/src/routes.ts b/backend/src/routes.ts index d65b4f836..fa298cc22 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -720,20 +720,22 @@ class Routes { } } - public async getBlocksExtras(req: Request, res: Response) { + public async getBlocks(req: Request, res: Response) { try { - const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10); - res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); - res.json(await blocks.$getBlocksExtras(height, 15)); + if (['mainnet', 'testnet', 'signet', 'regtest'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin + const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); + res.json(await blocks.$getBlocks(height, 15)); + } else { // Liquid, Bisq + return await this.getLegacyBlocks(req, res); + } } catch (e) { res.status(500).send(e instanceof Error ? e.message : e); } } - public async getBlocks(req: Request, res: Response) { + public async getLegacyBlocks(req: Request, res: Response) { try { - loadingIndicators.setProgress('blocks', 0); - const returnBlocks: IEsploraApi.Block[] = []; const fromHeight = parseInt(req.params.height, 10) || blocks.getCurrentBlockHeight(); @@ -757,16 +759,15 @@ class Routes { returnBlocks.push(block); nextHash = block.previousblockhash; } - loadingIndicators.setProgress('blocks', i / 10 * 100); } + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); res.json(returnBlocks); } catch (e) { - loadingIndicators.setProgress('blocks', 100); res.status(500).send(e instanceof Error ? e.message : e); } } - + public async getBlockTransactions(req: Request, res: Response) { try { loadingIndicators.setProgress('blocktxs-' + req.params.hash, 0); @@ -778,9 +779,9 @@ class Routes { const endIndex = Math.min(startingIndex + 10, txIds.length); for (let i = startingIndex; i < endIndex; i++) { try { - const transaction = await transactionUtils.$getTransactionExtended(txIds[i], true); + const transaction = await transactionUtils.$getTransactionExtended(txIds[i], true, true); transactions.push(transaction); - loadingIndicators.setProgress('blocktxs-' + req.params.hash, (i + 1) / endIndex * 100); + loadingIndicators.setProgress('blocktxs-' + req.params.hash, (i - startingIndex + 1) / (endIndex - startingIndex) * 100); } catch (e) { logger.debug('getBlockTransactions error: ' + (e instanceof Error ? e.message : e)); } diff --git a/backend/src/utils/blocks-utils.ts b/backend/src/utils/blocks-utils.ts index 8760a08c0..937a37448 100644 --- a/backend/src/utils/blocks-utils.ts +++ b/backend/src/utils/blocks-utils.ts @@ -15,7 +15,7 @@ export function prepareBlock(block: any): BlockExtended { weight: block.weight, previousblockhash: block.previousblockhash, extras: { - coinbaseRaw: block.coinbase_raw ?? block.extras.coinbaseRaw, + coinbaseRaw: block.coinbase_raw ?? block.extras?.coinbaseRaw, medianFee: block.medianFee ?? block.median_fee ?? block.extras?.medianFee, feeRange: block.feeRange ?? block.fee_span, reward: block.reward ?? block?.extras?.reward, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6996073e5..dc5f7a01a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -3900,9 +3900,9 @@ } }, "node_modules/@socket.io/component-emitter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz", - "integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", "dev": true }, "node_modules/@tootallnate/once": { @@ -4755,15 +4755,17 @@ } }, "node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dependencies": { + "lodash": "^4.17.14" + } }, "node_modules/async-each-series": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-0.1.1.tgz", - "integrity": "sha1-dhfBkXQB/Yykooqtzj266Yr+tDI=", + "integrity": "sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ==", "dev": true, "engines": { "node": ">=0.8.0" @@ -4996,12 +4998,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -5254,13 +5250,13 @@ } }, "node_modules/browser-sync": { - "version": "2.27.9", - "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.27.9.tgz", - "integrity": "sha512-3zBtggcaZIeU9so4ja9yxk7/CZu9B3DOL6zkxFpzHCHsQmkGBPVXg61jItbeoa+WXgNLnr1sYES/2yQwyEZ2+w==", + "version": "2.27.10", + "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.27.10.tgz", + "integrity": "sha512-xKm+6KJmJu6RuMWWbFkKwOCSqQOxYe3nOrFkKI5Tr/ZzjPxyU3pFShKK3tWnazBo/3lYQzN7fzjixG8fwJh1Xw==", "dev": true, "dependencies": { - "browser-sync-client": "^2.27.9", - "browser-sync-ui": "^2.27.9", + "browser-sync-client": "^2.27.10", + "browser-sync-ui": "^2.27.10", "bs-recipes": "1.3.4", "bs-snippet-injector": "^2.0.1", "chokidar": "^3.5.1", @@ -5277,7 +5273,7 @@ "localtunnel": "^2.0.1", "micromatch": "^4.0.2", "opn": "5.3.0", - "portscanner": "2.1.1", + "portscanner": "2.2.0", "qs": "6.2.3", "raw-body": "^2.3.2", "resp-modifier": "6.0.2", @@ -5298,15 +5294,16 @@ } }, "node_modules/browser-sync-client": { - "version": "2.27.9", - "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.27.9.tgz", - "integrity": "sha512-FHW8kydp7FXo6jnX3gXJCpHAHtWNLK0nx839nnK+boMfMI1n4KZd0+DmTxHBsHsF3OHud4V4jwoN8U5HExMIdQ==", + "version": "2.27.10", + "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.27.10.tgz", + "integrity": "sha512-KCFKA1YDj6cNul0VsA28apohtBsdk5Wv8T82ClOZPZMZWxPj4Ny5AUbrj9UlAb/k6pdxE5HABrWDhP9+cjt4HQ==", "dev": true, "dependencies": { "etag": "1.8.1", "fresh": "0.5.2", "mitt": "^1.1.3", - "rxjs": "^5.5.6" + "rxjs": "^5.5.6", + "typescript": "^4.6.2" }, "engines": { "node": ">=8.0.0" @@ -5333,10 +5330,23 @@ "node": ">=0.10.0" } }, + "node_modules/browser-sync-client/node_modules/typescript": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", + "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/browser-sync-ui": { - "version": "2.27.9", - "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.27.9.tgz", - "integrity": "sha512-rsduR2bRIwFvM8CX6iY/Nu5aWub0WB9zfSYg9Le/RV5N5DEyxJYey0VxdfWCnzDOoelassTDzYQo+r0iJno3qw==", + "version": "2.27.10", + "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.27.10.tgz", + "integrity": "sha512-elbJILq4Uo6OQv6gsvS3Y9vRAJlWu+h8j0JDkF0X/ua+3S6SVbbiWnZc8sNOFlG7yvVGIwBED3eaYQ0iBo1Dtw==", "dev": true, "dependencies": { "async-each-series": "0.1.1", @@ -7891,20 +7901,16 @@ } }, "node_modules/engine.io-client": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.1.1.tgz", - "integrity": "sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz", + "integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==", "dev": true, "dependencies": { - "@socket.io/component-emitter": "~3.0.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", - "has-cors": "1.1.0", - "parseqs": "0.0.6", - "parseuri": "0.0.6", + "engine.io-parser": "~5.0.3", "ws": "~8.2.3", - "xmlhttprequest-ssl": "~2.0.0", - "yeast": "0.1.2" + "xmlhttprequest-ssl": "~2.0.0" } }, "node_modules/engine.io-client/node_modules/ws": { @@ -9571,12 +9577,6 @@ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" }, - "node_modules/has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "dev": true - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -13181,18 +13181,6 @@ "parse5": "^6.0.1" } }, - "node_modules/parseqs": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", - "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==", - "dev": true - }, - "node_modules/parseuri": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", - "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", - "dev": true - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -13366,14 +13354,6 @@ "node": ">= 0.12.0" } }, - "node_modules/portfinder/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dependencies": { - "lodash": "^4.17.14" - } - }, "node_modules/portfinder/node_modules/debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -13383,12 +13363,12 @@ } }, "node_modules/portscanner": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.1.1.tgz", - "integrity": "sha1-6rtAnk3iSVD1oqUW01rnaTQ/u5Y=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", + "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", "dev": true, "dependencies": { - "async": "1.5.2", + "async": "^2.6.0", "is-number-like": "^1.0.3" }, "engines": { @@ -15098,29 +15078,27 @@ "devOptional": true }, "node_modules/socket.io-client": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.4.1.tgz", - "integrity": "sha512-N5C/L5fLNha5Ojd7Yeb/puKcPWWcoB/A09fEjjNsg91EDVr5twk/OEyO6VT9dlLSUNY85NpW6KBhVMvaLKQ3vQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.1.tgz", + "integrity": "sha512-e6nLVgiRYatS+AHXnOnGi4ocOpubvOUCGhyWw8v+/FxW8saHkinG6Dfhi9TU0Kt/8mwJIAASxvw6eujQmjdZVA==", "dev": true, "dependencies": { - "@socket.io/component-emitter": "~3.0.0", - "backo2": "~1.0.2", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", - "engine.io-client": "~6.1.1", - "parseuri": "0.0.6", - "socket.io-parser": "~4.1.1" + "engine.io-client": "~6.2.1", + "socket.io-parser": "~4.2.0" }, "engines": { "node": ">=10.0.0" } }, "node_modules/socket.io-client/node_modules/socket.io-parser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.2.tgz", - "integrity": "sha512-j3kk71QLJuyQ/hh5F/L2t1goqzdTL0gvDzuhTuNSwihfuFUrcSji0qFZmJJPtG6Rmug153eOPsUizeirf1IIog==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.0.tgz", + "integrity": "sha512-tLfmEwcEwnlQTxFB7jibL/q2+q8dlVQzj4JdRLJ/W/G1+Fu9VSxCx1Lo+n1HvXxKnM//dUuD0xgiA7tQf57Vng==", "dev": true, "dependencies": { - "@socket.io/component-emitter": "~3.0.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" }, "engines": { @@ -17264,12 +17242,6 @@ "fd-slicer": "~1.1.0" } }, - "node_modules/yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "dev": true - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -20020,9 +19992,9 @@ "devOptional": true }, "@socket.io/component-emitter": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz", - "integrity": "sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", "dev": true }, "@tootallnate/once": { @@ -20794,15 +20766,17 @@ "optional": true }, "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "requires": { + "lodash": "^4.17.14" + } }, "async-each-series": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-0.1.1.tgz", - "integrity": "sha1-dhfBkXQB/Yykooqtzj266Yr+tDI=", + "integrity": "sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ==", "dev": true }, "asynckit": { @@ -20976,12 +20950,6 @@ "@babel/helper-define-polyfill-provider": "^0.3.1" } }, - "backo2": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", - "dev": true - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -21210,13 +21178,13 @@ } }, "browser-sync": { - "version": "2.27.9", - "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.27.9.tgz", - "integrity": "sha512-3zBtggcaZIeU9so4ja9yxk7/CZu9B3DOL6zkxFpzHCHsQmkGBPVXg61jItbeoa+WXgNLnr1sYES/2yQwyEZ2+w==", + "version": "2.27.10", + "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.27.10.tgz", + "integrity": "sha512-xKm+6KJmJu6RuMWWbFkKwOCSqQOxYe3nOrFkKI5Tr/ZzjPxyU3pFShKK3tWnazBo/3lYQzN7fzjixG8fwJh1Xw==", "dev": true, "requires": { - "browser-sync-client": "^2.27.9", - "browser-sync-ui": "^2.27.9", + "browser-sync-client": "^2.27.10", + "browser-sync-ui": "^2.27.10", "bs-recipes": "1.3.4", "bs-snippet-injector": "^2.0.1", "chokidar": "^3.5.1", @@ -21233,7 +21201,7 @@ "localtunnel": "^2.0.1", "micromatch": "^4.0.2", "opn": "5.3.0", - "portscanner": "2.1.1", + "portscanner": "2.2.0", "qs": "6.2.3", "raw-body": "^2.3.2", "resp-modifier": "6.0.2", @@ -21343,15 +21311,16 @@ } }, "browser-sync-client": { - "version": "2.27.9", - "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.27.9.tgz", - "integrity": "sha512-FHW8kydp7FXo6jnX3gXJCpHAHtWNLK0nx839nnK+boMfMI1n4KZd0+DmTxHBsHsF3OHud4V4jwoN8U5HExMIdQ==", + "version": "2.27.10", + "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.27.10.tgz", + "integrity": "sha512-KCFKA1YDj6cNul0VsA28apohtBsdk5Wv8T82ClOZPZMZWxPj4Ny5AUbrj9UlAb/k6pdxE5HABrWDhP9+cjt4HQ==", "dev": true, "requires": { "etag": "1.8.1", "fresh": "0.5.2", "mitt": "^1.1.3", - "rxjs": "^5.5.6" + "rxjs": "^5.5.6", + "typescript": "^4.6.2" }, "dependencies": { "rxjs": { @@ -21368,13 +21337,19 @@ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", "dev": true + }, + "typescript": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", + "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", + "dev": true } } }, "browser-sync-ui": { - "version": "2.27.9", - "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.27.9.tgz", - "integrity": "sha512-rsduR2bRIwFvM8CX6iY/Nu5aWub0WB9zfSYg9Le/RV5N5DEyxJYey0VxdfWCnzDOoelassTDzYQo+r0iJno3qw==", + "version": "2.27.10", + "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.27.10.tgz", + "integrity": "sha512-elbJILq4Uo6OQv6gsvS3Y9vRAJlWu+h8j0JDkF0X/ua+3S6SVbbiWnZc8sNOFlG7yvVGIwBED3eaYQ0iBo1Dtw==", "dev": true, "requires": { "async-each-series": "0.1.1", @@ -23395,20 +23370,16 @@ } }, "engine.io-client": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.1.1.tgz", - "integrity": "sha512-V05mmDo4gjimYW+FGujoGmmmxRaDsrVr7AXA3ZIfa04MWM1jOfZfUwou0oNqhNwy/votUDvGDt4JA4QF4e0b4g==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz", + "integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==", "dev": true, "requires": { - "@socket.io/component-emitter": "~3.0.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", - "has-cors": "1.1.0", - "parseqs": "0.0.6", - "parseuri": "0.0.6", + "engine.io-parser": "~5.0.3", "ws": "~8.2.3", - "xmlhttprequest-ssl": "~2.0.0", - "yeast": "0.1.2" + "xmlhttprequest-ssl": "~2.0.0" }, "dependencies": { "ws": { @@ -24622,12 +24593,6 @@ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" }, - "has-cors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=", - "dev": true - }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -27435,18 +27400,6 @@ "parse5": "^6.0.1" } }, - "parseqs": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", - "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==", - "dev": true - }, - "parseuri": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", - "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==", - "dev": true - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -27576,14 +27529,6 @@ "mkdirp": "^0.5.5" }, "dependencies": { - "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "requires": { - "lodash": "^4.17.14" - } - }, "debug": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", @@ -27595,12 +27540,12 @@ } }, "portscanner": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.1.1.tgz", - "integrity": "sha1-6rtAnk3iSVD1oqUW01rnaTQ/u5Y=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", + "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", "dev": true, "requires": { - "async": "1.5.2", + "async": "^2.6.0", "is-number-like": "^1.0.3" } }, @@ -28879,26 +28824,24 @@ "devOptional": true }, "socket.io-client": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.4.1.tgz", - "integrity": "sha512-N5C/L5fLNha5Ojd7Yeb/puKcPWWcoB/A09fEjjNsg91EDVr5twk/OEyO6VT9dlLSUNY85NpW6KBhVMvaLKQ3vQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.1.tgz", + "integrity": "sha512-e6nLVgiRYatS+AHXnOnGi4ocOpubvOUCGhyWw8v+/FxW8saHkinG6Dfhi9TU0Kt/8mwJIAASxvw6eujQmjdZVA==", "dev": true, "requires": { - "@socket.io/component-emitter": "~3.0.0", - "backo2": "~1.0.2", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", - "engine.io-client": "~6.1.1", - "parseuri": "0.0.6", - "socket.io-parser": "~4.1.1" + "engine.io-client": "~6.2.1", + "socket.io-parser": "~4.2.0" }, "dependencies": { "socket.io-parser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.1.2.tgz", - "integrity": "sha512-j3kk71QLJuyQ/hh5F/L2t1goqzdTL0gvDzuhTuNSwihfuFUrcSji0qFZmJJPtG6Rmug153eOPsUizeirf1IIog==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.0.tgz", + "integrity": "sha512-tLfmEwcEwnlQTxFB7jibL/q2+q8dlVQzj4JdRLJ/W/G1+Fu9VSxCx1Lo+n1HvXxKnM//dUuD0xgiA7tQf57Vng==", "dev": true, "requires": { - "@socket.io/component-emitter": "~3.0.0", + "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" } } @@ -30543,12 +30486,6 @@ "fd-slicer": "~1.1.0" } }, - "yeast": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", - "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", - "dev": true - }, "yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 672d68686..6951accb2 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -7,7 +7,6 @@ import { AddressComponent } from './components/address/address.component'; import { MasterPageComponent } from './components/master-page/master-page.component'; import { AboutComponent } from './components/about/about.component'; import { StatusViewComponent } from './components/status-view/status-view.component'; -import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component'; import { TermsOfServiceComponent } from './components/terms-of-service/terms-of-service.component'; import { PrivacyPolicyComponent } from './components/privacy-policy/privacy-policy.component'; import { TrademarkPolicyComponent } from './components/trademark-policy/trademark-policy.component'; @@ -36,19 +35,20 @@ let routes: Routes = [ component: MasterPageComponent, children: [ { - path: 'tx/push', - component: PushTransactionComponent, + path: 'mining/blocks', + redirectTo: 'blocks', + pathMatch: 'full' }, { - path: 'blocks', - component: LatestBlocksComponent, + path: 'tx/push', + component: PushTransactionComponent, }, { path: 'about', component: AboutComponent, }, { - path: 'mining/blocks', + path: 'blocks', component: BlocksList, }, { @@ -115,6 +115,11 @@ let routes: Routes = [ { path: 'signet', children: [ + { + path: 'mining/blocks', + redirectTo: 'blocks', + pathMatch: 'full' + }, { path: '', pathMatch: 'full', @@ -128,16 +133,12 @@ let routes: Routes = [ path: 'tx/push', component: PushTransactionComponent, }, - { - path: 'blocks', - component: LatestBlocksComponent, - }, { path: 'about', component: AboutComponent, }, { - path: 'mining/blocks', + path: 'blocks', component: BlocksList, }, { @@ -211,19 +212,20 @@ let routes: Routes = [ component: MasterPageComponent, children: [ { - path: 'tx/push', - component: PushTransactionComponent, + path: 'mining/blocks', + redirectTo: 'blocks', + pathMatch: 'full' }, { - path: 'blocks', - component: LatestBlocksComponent, + path: 'tx/push', + component: PushTransactionComponent, }, { path: 'about', component: AboutComponent, }, { - path: 'mining/blocks', + path: 'blocks', component: BlocksList, }, { @@ -321,16 +323,12 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { path: 'tx/push', component: PushTransactionComponent, }, - { - path: 'blocks', - component: LatestBlocksComponent, - }, { path: 'about', component: AboutComponent, }, { - path: 'mining/blocks', + path: 'blocks', component: BlocksList, }, { @@ -429,16 +427,12 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') { path: 'tx/push', component: PushTransactionComponent, }, - { - path: 'blocks', - component: LatestBlocksComponent, - }, { path: 'about', component: AboutComponent, }, { - path: 'mining/blocks', + path: 'blocks', component: BlocksList, }, { diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index 27892626c..47489457c 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -250,6 +250,10 @@ Marina + + + Schildbach + diff --git a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts index 656a00d45..2cd421f26 100644 --- a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts +++ b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts @@ -89,13 +89,13 @@ export class BlockFeeRatesGraphComponent implements OnInit { }; for (const rate of data.blockFeeRates) { const timestamp = rate.timestamp * 1000; - seriesData['Min'].push([timestamp, rate.avg_fee_0, rate.avg_height]); - seriesData['10th'].push([timestamp, rate.avg_fee_10, rate.avg_height]); - seriesData['25th'].push([timestamp, rate.avg_fee_25, rate.avg_height]); - seriesData['Median'].push([timestamp, rate.avg_fee_50, rate.avg_height]); - seriesData['75th'].push([timestamp, rate.avg_fee_75, rate.avg_height]); - seriesData['90th'].push([timestamp, rate.avg_fee_90, rate.avg_height]); - seriesData['Max'].push([timestamp, rate.avg_fee_100, rate.avg_height]); + seriesData['Min'].push([timestamp, rate.avgFee_0, rate.avgHeight]); + seriesData['10th'].push([timestamp, rate.avgFee_10, rate.avgHeight]); + seriesData['25th'].push([timestamp, rate.avgFee_25, rate.avgHeight]); + seriesData['Median'].push([timestamp, rate.avgFee_50, rate.avgHeight]); + seriesData['75th'].push([timestamp, rate.avgFee_75, rate.avgHeight]); + seriesData['90th'].push([timestamp, rate.avgFee_90, rate.avgHeight]); + seriesData['Max'].push([timestamp, rate.avgFee_100, rate.avgHeight]); } // Prepare chart diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts index c938b351f..185938d5d 100644 --- a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts @@ -71,7 +71,7 @@ export class BlockFeesGraphComponent implements OnInit { .pipe( tap((response) => { this.prepareChartOptions({ - blockFees: response.body.map(val => [val.timestamp * 1000, val.avg_fees / 100000000]), + blockFees: response.body.map(val => [val.timestamp * 1000, val.avgFees / 100000000]), }); this.isLoading = false; }), diff --git a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts index 48d7ec10c..24614903c 100644 --- a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts +++ b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts @@ -69,7 +69,7 @@ export class BlockRewardsGraphComponent implements OnInit { .pipe( tap((response) => { this.prepareChartOptions({ - blockRewards: response.body.map(val => [val.timestamp * 1000, val.avg_rewards / 100000000]), + blockRewards: response.body.map(val => [val.timestamp * 1000, val.avgRewards / 100000000]), }); this.isLoading = false; }), diff --git a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts index 7c72b42ef..3255fc4af 100644 --- a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts +++ b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts @@ -83,8 +83,8 @@ export class BlockSizesWeightsGraphComponent implements OnInit { tap((response) => { const data = response.body; this.prepareChartOptions({ - sizes: data.sizes.map(val => [val.timestamp * 1000, val.avg_size / 1000000, val.avg_height]), - weights: data.weights.map(val => [val.timestamp * 1000, val.avg_weight / 1000000, val.avg_height]), + 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]), }); this.isLoading = false; }), diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index c0ff29889..07ac76c21 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -197,7 +197,18 @@ - + +
+
+ Error loading data. +

+ {{ transactionsError.status }}: {{ transactionsError.error }} +
+
+
+
+ +
@@ -271,9 +282,9 @@
- Error loading block data. + Error loading data.

- {{ error.error }} + {{ error.code }}: {{ error.error }}
diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index 57417a5c3..bd70e8628 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -38,6 +38,7 @@ export class BlockComponent implements OnInit, OnDestroy { showDetails = false; showPreviousBlocklink = true; showNextBlocklink = true; + transactionsError: any = null; subscription: Subscription; keyNavigationSubscription: Subscription; @@ -152,12 +153,13 @@ export class BlockComponent implements OnInit, OnDestroy { this.stateService.markBlock$.next({ blockHeight: this.blockHeight }); this.isLoadingTransactions = true; this.transactions = null; + this.transactionsError = null; }), debounceTime(300), switchMap((block) => this.electrsApiService.getBlockTransactions$(block.id) .pipe( catchError((err) => { - console.log(err); + this.transactionsError = err; return of([]); })) ), @@ -218,9 +220,16 @@ export class BlockComponent implements OnInit, OnDestroy { const start = (page - 1) * this.itemsPerPage; this.isLoadingTransactions = true; this.transactions = null; + this.transactionsError = null; target.scrollIntoView(); // works for chrome this.electrsApiService.getBlockTransactions$(this.block.id, start) + .pipe( + catchError((err) => { + this.transactionsError = err; + return of([]); + }) + ) .subscribe((transactions) => { this.transactions = transactions; this.isLoadingTransactions = false; 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 face9452b..0e0f5d1e1 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -1,6 +1,6 @@ -
+

Blocks

@@ -8,22 +8,22 @@
- - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - 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 ead712be0..cea30e126 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.scss +++ b/frontend/src/app/components/blocks-list/blocks-list.component.scss @@ -6,6 +6,9 @@ padding-left: 0px; padding-bottom: 0px; } +.container-xl.legacy { + max-width: 1140px; +} .container { max-width: 100%; @@ -58,6 +61,9 @@ tr, td, th { width: 10%; } } +.height.legacy { + width: 15%; +} .timestamp { width: 18%; @@ -65,6 +71,9 @@ tr, td, th { display: none; } } +.timestamp.legacy { + width: 20%; +} .mined { width: 13%; @@ -72,6 +81,12 @@ tr, td, th { display: none; } } +.mined.legacy { + width: 15%; + @media (max-width: 576px) { + display: table-cell; + } +} .txs { padding-right: 40px; @@ -88,6 +103,10 @@ tr, td, th { display: none; } } +.txs.legacy { + padding-right: 80px; + width: 10%; +} .fees { width: 10%; @@ -126,6 +145,12 @@ tr, td, th { display: none; } } +.size.legacy { + width: 20%; + @media (max-width: 576px) { + display: table-cell; + } +} /* Tooltip text */ .tooltip-custom { 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 b5b66b22b..7cca2af62 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.ts +++ b/frontend/src/app/components/blocks-list/blocks-list.component.ts @@ -17,6 +17,7 @@ export class BlocksList implements OnInit { blocks$: Observable = undefined; + indexingAvailable = false; isLoading = true; fromBlockHeight = undefined; paginationMaxSize: number; @@ -35,6 +36,9 @@ export class BlocksList implements OnInit { } ngOnInit(): void { + this.indexingAvailable = (this.stateService.env.BASE_MODULE === 'mempool' && + this.stateService.env.MINING_DASHBOARD === true); + if (!this.widget) { this.websocketService.want(['blocks']); } @@ -55,17 +59,19 @@ export class BlocksList implements OnInit { 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'; + if (this.indexingAvailable) { + 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'; + } } if (this.widget) { return blocks.slice(0, 6); } return blocks; }), - retryWhen(errors => errors.pipe(delayWhen(() => timer(1000)))) + retryWhen(errors => errors.pipe(delayWhen(() => timer(10000)))) ) }) ), @@ -81,9 +87,11 @@ export class BlocksList implements OnInit { return blocks[0]; } this.blocksCount = Math.max(this.blocksCount, blocks[1][0].height) + 1; - // @ts-ignore: Need to add an extra field for the template - blocks[1][0].extras.pool.logo = `./resources/mining-pools/` + - blocks[1][0].extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; + if (this.stateService.env.MINING_DASHBOARD) { + // @ts-ignore: Need to add an extra field for the template + blocks[1][0].extras.pool.logo = `./resources/mining-pools/` + + blocks[1][0].extras.pool.name.toLowerCase().replace(' ', '').replace('.', '') + '.svg'; + } acc.unshift(blocks[1][0]); acc = acc.slice(0, this.widget ? 6 : 15); return acc; diff --git a/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.html b/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.html index 6e80d828a..5f11c2608 100644 --- a/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.html +++ b/frontend/src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.html @@ -1,4 +1,4 @@ -
+
HeightPoolTimestampMinedRewardFeesTXsSizeHeightPoolTimestampMinedRewardFeesTXsTransactionsSize
- {{ block.height - }} + {{ block.height }} + + + + + {{ block.tx_count | number }} +
@@ -60,29 +60,29 @@
+ + + + + + + +
diff --git a/frontend/src/app/components/graphs/graphs.component.html b/frontend/src/app/components/graphs/graphs.component.html index 6c59645ea..b02adae5e 100644 --- a/frontend/src/app/components/graphs/graphs.component.html +++ b/frontend/src/app/components/graphs/graphs.component.html @@ -1,7 +1,7 @@ -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
HeightTimestampMinedTransactionsSize
{{ block.height }}‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }}{{ block.tx_count | number }} -
-
-
-
-
-
-
-
-
- - -
- Error loading blocks -
- {{ error.error }} -
-
- -
diff --git a/frontend/src/app/components/latest-blocks/latest-blocks.component.scss b/frontend/src/app/components/latest-blocks/latest-blocks.component.scss deleted file mode 100644 index 0f2246c99..000000000 --- a/frontend/src/app/components/latest-blocks/latest-blocks.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -.progress { - background-color: #2d3348; -} - -@media (min-width: 768px) { - .d-md-block { - display: table-cell !important; - } -} -@media (min-width: 992px) { - .d-lg-block { - display: table-cell !important; - } -} diff --git a/frontend/src/app/components/latest-blocks/latest-blocks.component.ts b/frontend/src/app/components/latest-blocks/latest-blocks.component.ts deleted file mode 100644 index 3d4ae91ad..000000000 --- a/frontend/src/app/components/latest-blocks/latest-blocks.component.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; -import { ElectrsApiService } from '../../services/electrs-api.service'; -import { StateService } from '../../services/state.service'; -import { Block } from '../../interfaces/electrs.interface'; -import { Subscription, Observable, merge, of } from 'rxjs'; -import { SeoService } from '../../services/seo.service'; -import { WebsocketService } from 'src/app/services/websocket.service'; -import { map } from 'rxjs/operators'; - -@Component({ - selector: 'app-latest-blocks', - templateUrl: './latest-blocks.component.html', - styleUrls: ['./latest-blocks.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class LatestBlocksComponent implements OnInit, OnDestroy { - network$: Observable; - error: any; - blocks: any[] = []; - blockSubscription: Subscription; - isLoading = true; - interval: any; - blocksLoadingStatus$: Observable; - - latestBlockHeight: number; - - heightOfPageUntilBlocks = 150; - heightOfBlocksTableChunk = 470; - - constructor( - private electrsApiService: ElectrsApiService, - public stateService: StateService, - private seoService: SeoService, - private websocketService: WebsocketService, - private cd: ChangeDetectorRef, - ) { } - - ngOnInit() { - this.seoService.setTitle($localize`:@@8a7b4bd44c0ac71b2e72de0398b303257f7d2f54:Blocks`); - this.websocketService.want(['blocks']); - - this.network$ = merge(of(''), this.stateService.networkChanged$); - - this.blocksLoadingStatus$ = this.stateService.loadingIndicators$ - .pipe( - map((indicators) => indicators['blocks'] !== undefined ? indicators['blocks'] : 0) - ); - - this.blockSubscription = this.stateService.blocks$ - .subscribe(([block]) => { - if (block === null || !this.blocks.length) { - return; - } - - this.latestBlockHeight = block.height; - - if (block.height === this.blocks[0].height) { - return; - } - - // If we are out of sync, reload the blocks instead - if (block.height > this.blocks[0].height + 1) { - this.loadInitialBlocks(); - return; - } - - if (block.height <= this.blocks[0].height) { - return; - } - - this.blocks.pop(); - this.blocks.unshift(block); - this.cd.markForCheck(); - }); - - this.loadInitialBlocks(); - } - - ngOnDestroy() { - clearInterval(this.interval); - this.blockSubscription.unsubscribe(); - } - - loadInitialBlocks() { - this.electrsApiService.listBlocks$() - .subscribe((blocks) => { - this.blocks = blocks; - this.isLoading = false; - this.error = undefined; - - this.latestBlockHeight = blocks[0].height; - - const spaceForBlocks = window.innerHeight - this.heightOfPageUntilBlocks; - const chunks = Math.ceil(spaceForBlocks / this.heightOfBlocksTableChunk) - 1; - if (chunks > 0) { - this.loadMore(chunks); - } - this.cd.markForCheck(); - }, - (error) => { - console.log(error); - this.error = error; - this.isLoading = false; - this.cd.markForCheck(); - }); - } - - loadMore(chunks = 0) { - if (this.isLoading) { - return; - } - const height = this.blocks[this.blocks.length - 1].height - 1; - if (height < 0) { - return; - } - this.isLoading = true; - this.electrsApiService.listBlocks$(height) - .subscribe((blocks) => { - this.blocks = this.blocks.concat(blocks); - this.isLoading = false; - this.error = undefined; - - const chunksLeft = chunks - 1; - if (chunksLeft > 0) { - this.loadMore(chunksLeft); - } - this.cd.markForCheck(); - }, - (error) => { - console.log(error); - this.error = error; - this.isLoading = false; - this.cd.markForCheck(); - }); - } - - trackByBlock(index: number, block: Block) { - return block.height; - } -} diff --git a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html index 6a208e5c7..fe7c08ba2 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html @@ -51,7 +51,7 @@
diff --git a/frontend/src/app/components/reward-stats/reward-stats.component.html b/frontend/src/app/components/reward-stats/reward-stats.component.html index f666ecdff..eed9aa9da 100644 --- a/frontend/src/app/components/reward-stats/reward-stats.component.html +++ b/frontend/src/app/components/reward-stats/reward-stats.component.html @@ -8,7 +8,7 @@
- + diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index c76fab3aa..9a2629f08 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -125,7 +125,7 @@ export class TransactionComponent implements OnInit, OnDestroy { }), switchMap(() => { let transactionObservable$: Observable; - if (history.state.data) { + if (history.state.data && history.state.data.fee !== -1) { transactionObservable$ = of(history.state.data); } else { transactionObservable$ = this.electrsApiService diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html index 1147810ca..1b34692a8 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -60,7 +60,10 @@ - {{ vin.issuance ? 'Issuance' : 'UNKNOWN' }} + + + {{ vin.issuance ? 'Issuance' : 'UNKNOWN' }} + @@ -87,6 +90,7 @@
+ @@ -141,7 +145,7 @@
- + @@ -261,9 +265,10 @@
-
+
{{ tx.fee / (tx.weight / 4) | feeRounding }} sat/vB  – {{ tx.fee | number }} sat
+
Show all inputs to reveal fee data
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.scss b/frontend/src/app/components/transactions-list/transactions-list.component.scss index 6690337ce..11e013e23 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.scss +++ b/frontend/src/app/components/transactions-list/transactions-list.component.scss @@ -139,4 +139,10 @@ h2 { .addr-right { font-family: monospace; -} \ No newline at end of file +} + +.grey-info-text { + color:#6c757d; + font-style: italic; + font-size: 12px; +} diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts index b4ef42d84..025faf322 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -175,6 +175,17 @@ export class TransactionsListComponent implements OnInit, OnChanges { } } + loadMoreInputs(tx: Transaction) { + tx['@vinLimit'] = false; + + this.electrsApiService.getTransaction$(tx.txid) + .subscribe((newTx) => { + tx.vin = newTx.vin; + tx.fee = newTx.fee; + this.ref.markForCheck(); + }); + } + ngOnDestroy() { this.outspendsSubscription.unsubscribe(); } diff --git a/frontend/src/app/dashboard/dashboard.component.html b/frontend/src/app/dashboard/dashboard.component.html index 2ab42a6a0..d26b6660a 100644 --- a/frontend/src/app/dashboard/dashboard.component.html +++ b/frontend/src/app/dashboard/dashboard.component.html @@ -136,7 +136,7 @@ - +
diff --git a/frontend/src/app/dashboard/dashboard.component.scss b/frontend/src/app/dashboard/dashboard.component.scss index 47fb5b757..e6ddb5f84 100644 --- a/frontend/src/app/dashboard/dashboard.component.scss +++ b/frontend/src/app/dashboard/dashboard.component.scss @@ -198,9 +198,7 @@ display: none; text-align: right; width: 20%; - @media (min-width: 376px) { - display: table-cell; - } + display: table-cell; } .table-cell-size { display: none; diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index c018c572a..fcb60a154 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -13,6 +13,7 @@ const showJsExamplesDefault = { "": true, "testnet": true, "signet": true, "liqu const showJsExamplesDefaultFalse = { "": false, "testnet": false, "signet": false, "liquid": false, "liquidtestnet": false, "bisq": false }; export const wsApiDocsData = { + showJsExamples: showJsExamplesDefault, codeTemplate: { curl: `/api/v1/ws`, commonJS: ` @@ -2967,7 +2968,7 @@ export const restApiDocsData = [ "weight": 3931749, "previousblockhash": "00000000000000000002b5b2afc1c62e61e53f966b965a9a8ce99112e24066ae" }, - ... + ... ]`, }, codeSampleTestnet: { @@ -3047,7 +3048,7 @@ export const restApiDocsData = [ "emptyBlocks": 0, "slug": "antpool" }, - ... + ... "oldestIndexedBlockTimestamp": 1231006505, "blockCount": 1005, "lastEstimatedHashrate": 230086716765559200000 @@ -3833,34 +3834,34 @@ export const restApiDocsData = [ curl: [`1w`], response: `[ { - "avg_height": 735644, + "avgHeight": 735644, "timestamp": 1652119111, - "avg_fees": 24212890 + "avgFees": 24212890 }, { - "avg_height": 735646, + "avgHeight": 735646, "timestamp": 1652120252, - "avg_fees": 21655996 + "avgFees": 21655996 }, { - "avg_height": 735648, + "avgHeight": 735648, "timestamp": 1652121214, - "avg_fees": 20678859 + "avgFees": 20678859 }, { - "avg_height": 735649, + "avgHeight": 735649, "timestamp": 1652121757, - "avg_fees": 21020140 + "avgFees": 21020140 }, { - "avg_height": 735650, + "avgHeight": 735650, "timestamp": 1652122367, - "avg_fees": 23064200 + "avgFees": 23064200 }, { - "avg_height": 735652, + "avgHeight": 735652, "timestamp": 1652122893, - "avg_fees": 17620723 + "avgFees": 17620723 }, ... ]` @@ -3871,14 +3872,14 @@ export const restApiDocsData = [ curl: [`1w`], response: `[ { - "avg_height": 2224253, + "avgHeight": 2224253, "timestamp": 1652346420, - "avg_fees": 211686 + "avgFees": 211686 }, { - "avg_height": 2224254, + "avgHeight": 2224254, "timestamp": 1652346850, - "avg_fees": 2565952 + "avgFees": 2565952 }, ... ]` @@ -3889,14 +3890,14 @@ export const restApiDocsData = [ curl: [`1w`], response: `[ { - "avg_height": 89978, + "avgHeight": 89978, "timestamp": 1652346573, - "avg_fees": 1071 + "avgFees": 1071 }, { - "avg_height": 89979, + "avgHeight": 89979, "timestamp": 1652346970, - "avg_fees": 1224 + "avgFees": 1224 }, ... ]` @@ -3932,29 +3933,29 @@ export const restApiDocsData = [ curl: [`1d`], response: `[ { - "avg_height": 599992, + "avgHeight": 599992, "timestamp": 1571438412, - "avg_rewards": 1260530933 + "avgRewards": 1260530933 }, { - "avg_height": 600000, + "avgHeight": 600000, "timestamp": 1571443398, - "avg_rewards": 1264314538 + "avgRewards": 1264314538 }, { - "avg_height": 725441, + "avgHeight": 725441, "timestamp": 1646139035, - "avg_rewards": 637067563 + "avgRewards": 637067563 }, { - "avg_height": 725585, + "avgHeight": 725585, "timestamp": 1646222444, - "avg_rewards": 646519104 + "avgRewards": 646519104 }, { - "avg_height": 725727, + "avgHeight": 725727, "timestamp": 1646308374, - "avg_rewards": 638709605 + "avgRewards": 638709605 }, ... ]` @@ -3965,14 +3966,14 @@ export const restApiDocsData = [ curl: [`1d`], response: `[ { - "avg_height": 12, + "avgHeight": 12, "timestamp": 1296689648, - "avg_rewards": 5000000000 + "avgRewards": 5000000000 }, { - "avg_height": 269, + "avgHeight": 269, "timestamp": 1296717674, - "avg_rewards": 5000091820 + "avgRewards": 5000091820 }, ... ]` @@ -3983,14 +3984,14 @@ export const restApiDocsData = [ curl: [`1d`], response: `[ { - "avg_height": 183, + "avgHeight": 183, "timestamp": 1598962247, - "avg_rewards": 5000000000 + "avgRewards": 5000000000 }, { - "avg_height": 576, + "avgHeight": 576, "timestamp": 1599047892, - "avg_rewards": 5000000000 + "avgRewards": 5000000000 }, ... ]` @@ -4028,37 +4029,37 @@ export const restApiDocsData = [ "oldestIndexedBlockTimestamp": 1571434851, "blockFeeRates": [ { - "avg_height": 732152, + "avgHeight": 732152, "timestamp": 1650132959, - "avg_fee_0": 1, - "avg_fee_10": 2, - "avg_fee_25": 2, - "avg_fee_50": 3, - "avg_fee_75": 4, - "avg_fee_90": 8, - "avg_fee_100": 393 + "avgFee_0": 1, + "avgFee_10": 2, + "avgFee_25": 2, + "avgFee_50": 3, + "avgFee_75": 4, + "avgFee_90": 8, + "avgFee_100": 393 }, { - "avg_height": 732158, + "avgHeight": 732158, "timestamp": 1650134432, - "avg_fee_0": 1, - "avg_fee_10": 1, - "avg_fee_25": 2, - "avg_fee_50": 4, - "avg_fee_75": 6, - "avg_fee_90": 10, - "avg_fee_100": 240 + "avgFee_0": 1, + "avgFee_10": 1, + "avgFee_25": 2, + "avgFee_50": 4, + "avgFee_75": 6, + "avgFee_90": 10, + "avgFee_100": 240 }, { - "avg_height": 732161, + "avgHeight": 732161, "timestamp": 1650135818, - "avg_fee_0": 1, - "avg_fee_10": 1, - "avg_fee_25": 1, - "avg_fee_50": 2, - "avg_fee_75": 5, - "avg_fee_90": 8, - "avg_fee_100": 251 + "avgFee_0": 1, + "avgFee_10": 1, + "avgFee_25": 1, + "avgFee_50": 2, + "avgFee_75": 5, + "avgFee_90": 8, + "avgFee_100": 251 }, ... ] @@ -4072,26 +4073,26 @@ export const restApiDocsData = [ "oldestIndexedBlockTimestamp": 1296688602, "blockFeeRates": [ { - "avg_height": 2196306, + "avgHeight": 2196306, "timestamp": 1650360168, - "avg_fee_0": 1, - "avg_fee_10": 1, - "avg_fee_25": 1, - "avg_fee_50": 1, - "avg_fee_75": 2, - "avg_fee_90": 28, - "avg_fee_100": 2644 + "avgFee_0": 1, + "avgFee_10": 1, + "avgFee_25": 1, + "avgFee_50": 1, + "avgFee_75": 2, + "avgFee_90": 28, + "avgFee_100": 2644 }, { - "avg_height": 2196308, + "avgHeight": 2196308, "timestamp": 1650361209, - "avg_fee_0": 1, - "avg_fee_10": 1, - "avg_fee_25": 1, - "avg_fee_50": 4, - "avg_fee_75": 12, - "avg_fee_90": 65, - "avg_fee_100": 102 + "avgFee_0": 1, + "avgFee_10": 1, + "avgFee_25": 1, + "avgFee_50": 4, + "avgFee_75": 12, + "avgFee_90": 65, + "avgFee_100": 102 }, ... ] @@ -4105,26 +4106,26 @@ export const restApiDocsData = [ "oldestIndexedBlockTimestamp": 1598918400, "blockFeeRates": [ { - "avg_height": 86620, + "avgHeight": 86620, "timestamp": 1650360010, - "avg_fee_0": 1, - "avg_fee_10": 1, - "avg_fee_25": 1, - "avg_fee_50": 1, - "avg_fee_75": 1, - "avg_fee_90": 1, - "avg_fee_100": 1 + "avgFee_0": 1, + "avgFee_10": 1, + "avgFee_25": 1, + "avgFee_50": 1, + "avgFee_75": 1, + "avgFee_90": 1, + "avgFee_100": 1 }, { - "avg_height": 86623, + "avgHeight": 86623, "timestamp": 1650361330, - "avg_fee_0": 1, - "avg_fee_10": 1, - "avg_fee_25": 1, - "avg_fee_50": 1, - "avg_fee_75": 1, - "avg_fee_90": 1, - "avg_fee_100": 1 + "avgFee_0": 1, + "avgFee_10": 1, + "avgFee_25": 1, + "avgFee_50": 1, + "avgFee_75": 1, + "avgFee_90": 1, + "avgFee_100": 1 }, ... ] @@ -4162,47 +4163,47 @@ export const restApiDocsData = [ response: `{ "sizes": [ { - "avg_height": 576650, + "avgHeight": 576650, "timestamp": 1558212081, - "avg_size": 1271404 + "avgSize": 1271404 }, { - "avg_height": 576715, + "avgHeight": 576715, "timestamp": 1558246272, - "avg_size": 1105893 + "avgSize": 1105893 }, { - "avg_height": 576797, + "avgHeight": 576797, "timestamp": 1558289379, - "avg_size": 1141071 + "avgSize": 1141071 }, { - "avg_height": 576885, + "avgHeight": 576885, "timestamp": 1558330184, - "avg_size": 1108166 + "avgSize": 1108166 }, ... ], "weights": [ { - "avg_height": 576650, + "avgHeight": 576650, "timestamp": 1558212081, - "avg_weight": 3994002 + "avgWeight": 3994002 }, { - "avg_height": 576715, + "avgHeight": 576715, "timestamp": 1558246272, - "avg_weight": 3756312 + "avgWeight": 3756312 }, { - "avg_height": 576797, + "avgHeight": 576797, "timestamp": 1558289379, - "avg_weight": 3719625 + "avgWeight": 3719625 }, { - "avg_height": 576885, + "avgHeight": 576885, "timestamp": 1558330184, - "avg_weight": 3631381 + "avgWeight": 3631381 }, ... ] @@ -4215,27 +4216,27 @@ export const restApiDocsData = [ response: `{ "sizes": [ { - "avg_height": 1517188, + "avgHeight": 1517188, "timestamp": 1558262730, - "avg_size": 25089 + "avgSize": 25089 }, { - "avg_height": 1517275, + "avgHeight": 1517275, "timestamp": 1558290933, - "avg_size": 21679 + "avgSize": 21679 }, ... ], "weights": [ { - "avg_height": 1517188, + "avgHeight": 1517188, "timestamp": 1558262730, - "avg_weight": 74921 + "avgWeight": 74921 }, { - "avg_height": 1517275, + "avgHeight": 1517275, "timestamp": 1558290933, - "avg_weight": 65164 + "avgWeight": 65164 }, ... ] @@ -4248,27 +4249,27 @@ export const restApiDocsData = [ response: `{ "sizes": [ { - "avg_height": 83, + "avgHeight": 83, "timestamp": 1598937527, - "avg_size": 329 + "avgSize": 329 }, { - "avg_height": 266, + "avgHeight": 266, "timestamp": 1598982991, - "avg_size": 330 + "avgSize": 330 }, ... ], "weights": [ { - "avg_height": 83, + "avgHeight": 83, "timestamp": 1598937527, - "avg_weight": 1209 + "avgWeight": 1209 }, { - "avg_height": 266, + "avgHeight": 266, "timestamp": 1598982991, - "avg_weight": 1212 + "avgWeight": 1212 }, ... ] diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index e8bc65559..d25a60653 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -101,7 +101,7 @@
Description
Default push: {{ '{' }} action: 'want', data: ['blocks', ...] {{ '}' }} to express what you want pushed. Available: blocks, mempool-blocks, live-2h-chart, and stats.

Push transactions related to address: {{ '{' }} 'track-address': '3PbJ...bF9B' {{ '}' }} to receive all new transactions containing that address as input or output. Returns an array of transactions. address-transactions for new mempool transactions, and block-transactions for new block confirmed transactions.
- + diff --git a/frontend/src/app/interfaces/electrs.interface.ts b/frontend/src/app/interfaces/electrs.interface.ts index ecd0ac598..9c873d2eb 100644 --- a/frontend/src/app/interfaces/electrs.interface.ts +++ b/frontend/src/app/interfaces/electrs.interface.ts @@ -54,6 +54,8 @@ export interface Vin { // Elements is_pegin?: boolean; issuance?: Issuance; + // Custom + lazy?: boolean; } interface Issuance { diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 8ef656013..7a523a7b3 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -149,7 +149,7 @@ export class ApiService { getBlocks$(from: number): Observable { return this.httpClient.get( - this.apiBaseUrl + this.apiBasePath + `/api/v1/blocks-extras` + + this.apiBaseUrl + this.apiBasePath + `/api/v1/blocks` + (from !== undefined ? `/${from}` : ``) ); } diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 488131d84..eb0bf5b87 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -48,7 +48,6 @@ import { TransactionsListComponent } from '../components/transactions-list/trans import { BlockComponent } from '../components/block/block.component'; import { AddressComponent } from '../components/address/address.component'; import { SearchFormComponent } from '../components/search-form/search-form.component'; -import { LatestBlocksComponent } from '../components/latest-blocks/latest-blocks.component'; import { AddressLabelsComponent } from '../components/address-labels/address-labels.component'; import { FooterComponent } from '../components/footer/footer.component'; import { TimeSpanComponent } from '../components/time-span/time-span.component'; @@ -113,7 +112,6 @@ import { IndexingProgressComponent } from '../components/indexing-progress/index BlockComponent, TransactionsListComponent, AddressComponent, - LatestBlocksComponent, SearchFormComponent, TimeSpanComponent, AddressLabelsComponent, @@ -208,7 +206,6 @@ import { IndexingProgressComponent } from '../components/indexing-progress/index BlockComponent, TransactionsListComponent, AddressComponent, - LatestBlocksComponent, SearchFormComponent, TimeSpanComponent, AddressLabelsComponent, diff --git a/frontend/src/resources/profile/schildbach.svg b/frontend/src/resources/profile/schildbach.svg new file mode 100644 index 000000000..976deec88 --- /dev/null +++ b/frontend/src/resources/profile/schildbach.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/production/nginx-cache-warmer b/production/nginx-cache-warmer index 165a97860..366a07345 100755 --- a/production/nginx-cache-warmer +++ b/production/nginx-cache-warmer @@ -4,6 +4,7 @@ slugs=(`curl -sSL https://raw.githubusercontent.com/mempool/mining-pools/master/ while true do for url in / \ + '/api/v1/blocks' \ '/api/v1/statistics/2h' \ '/api/v1/statistics/24h' \ '/api/v1/statistics/1w' \ @@ -36,7 +37,6 @@ do for url in / \ '/api/v1/mining/hashrate/pools/3y' \ '/api/v1/mining/hashrate/pools/all' \ '/api/v1/mining/reward-stats/144' \ - '/api/v1/mining/blocks-extras' \ '/api/v1/mining/blocks/fees/24h' \ '/api/v1/mining/blocks/fees/3d' \ '/api/v1/mining/blocks/fees/1w' \