diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 1b565e71a..832efcbbf 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -31,6 +31,10 @@ jobs: wait-on-timeout: 120 record: true parallel: true + spec: | + cypress/integration/mainnet/*.spec.ts + cypress/integration/signet/*.spec.ts + cypress/integration/testnet/*.spec.ts group: Tests on ${{ matrix.browser }} (Mempool) browser: ${{ matrix.browser }} ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}' @@ -50,7 +54,9 @@ jobs: wait-on-timeout: 120 record: true parallel: true - spec: cypress/integration/liquid/liquid.spec.ts + spec: | + cypress/integration/liquid/liquid.spec.ts + cypress/integration/liquidtestnet/liquidtestnet.spec.ts group: Tests on ${{ matrix.browser }} (Liquid) browser: ${{ matrix.browser }} ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..91006c3f6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# Contributing to The Mempool Open Source Project + +Thank you for contributing to The Mempool Open Source Project managed by Mempool Space K.K. (“Mempool”). + +In order to clarify the intellectual property license granted with Contributions from any person or entity, Mempool must have a statement on file from each Contributor indicating their agreement to the Contributor License Agreement (“Agreement”). This license is for your protection as a Contributor as well as the protection of Mempool and its other contributors and users; it does not change your rights to use your own Contributions for any other purpose. + +When submitting a pull request for the first time, please create a file with a name like `/contributors/{github_username}.txt`, and in the content of that file indicate your agreement to the Contributor License Agreement terms below. An example of what that file should contain can be seen in wiz's agreement file. (This method of CLA "signing" is borrowed from Medium's open source project.) + +# Contributor License Agreement + +Last Updated: January 25, 2022 + +By accepting this Agreement, You agree to the following terms and conditions for Your present and future Contributions submitted to Mempool. Except for the license granted herein to Mempool and recipients of software distributed by Mempool, You reserve all right, title, and interest in and to Your Contributions. + +### 1. Definitions + +“You” (or “Your”) shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with Mempool. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +“Contribution” shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to Mempool for inclusion in, or documentation of, any of the products owned or managed by Mempool (“Work”). For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to Mempool or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Mempool for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as “Not a Contribution.” + +### 2. Grant of Copyright License + +Subject to the terms and conditions of this Agreement, You hereby grant to Mempool and to recipients of software distributed by Mempool a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. + +### 3. Grant of Patent License + +Subject to the terms and conditions of this Agreement, You hereby grant to Mempool and to recipients of software distributed by Mempool a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. + +### 4. Authority + +You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to Mempool, or that your employer has executed a separate Corporate Contributor License Agreement with Mempool. + +### 5. Originality + +You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware, and which are associated with any part of Your Contributions. + +### 6. Support + +You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +### 7. Third Party Contributions + +Should You wish to submit work that is not Your original creation, You may submit it to Mempool separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as “Submitted on behalf of a third-party: [named here]”. + +### 8. Notifications + +You agree to notify Mempool of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. + +EOF diff --git a/LICENSE b/LICENSE index 1826f463d..966417847 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ The Mempool Open Source Project -Copyright (c) 2019-2021 The Mempool Open Source Project Developers +Copyright (c) 2019-2022 The Mempool Open Source Project Developers This program is free software; you can redistribute it and/or modify it under the terms of (at your option) either: diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 000000000..e65205d67 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,22 @@ +# Setup backend watchers + +The backend is static. Typescript scripts are compiled into the `dist` folder and served through a node web server. + +You can avoid the manual shutdown/recompile/restart command line cycle by using a watcher. + +Make sure you are in the `backend` directory `cd backend`. + +1. Install nodemon and ts-node + +``` +sudo npm install -g ts-node nodemon +``` + +2. Run the watcher + +> Note: You can find your npm global binary folder using `npm -g bin`, where nodemon will be installed. + +``` +nodemon src/index.ts --ignore cache/ --ignore pools.json +``` + diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json index ea656c1de..8a9295b3a 100644 --- a/backend/mempool-config.sample.json +++ b/backend/mempool-config.sample.json @@ -12,9 +12,12 @@ "BLOCK_WEIGHT_UNITS": 4000000, "INITIAL_BLOCKS_AMOUNT": 8, "MEMPOOL_BLOCKS_AMOUNT": 8, + "INDEXING_BLOCKS_AMOUNT": 1100, "PRICE_FEED_UPDATE_INTERVAL": 3600, "USE_SECOND_NODE_FOR_MINFEE": false, - "EXTERNAL_ASSETS": [] + "EXTERNAL_ASSETS": [ + "https://mempool.space/resources/pools.json" + ] }, "CORE_RPC": { "HOST": "127.0.0.1", diff --git a/backend/src/api/bitcoin/bitcoin-api.ts b/backend/src/api/bitcoin/bitcoin-api.ts index 6b7e5b2ac..e754b5d65 100644 --- a/backend/src/api/bitcoin/bitcoin-api.ts +++ b/backend/src/api/bitcoin/bitcoin-api.ts @@ -112,14 +112,25 @@ 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++) { - const txOut = await this.bitcoindClient.getTxOut(txId, i); - outSpends.push({ - spent: txOut === null, - }); + if (tx.status && tx.status.block_height == 0) { + outSpends.push({ + spent: false + }); + } else { + const txOut = await this.bitcoindClient.getTxOut(txId, i); + outSpends.push({ + spent: txOut === null, + }); + } } return outSpends; } + $getEstimatedHashrate(blockHeight: number): Promise { + // 120 is the default block span in Core + return this.bitcoindClient.getNetworkHashPs(120, blockHeight); + } + protected async $convertTransaction(transaction: IBitcoinApi.Transaction, addPrevout: boolean): Promise { let esploraTransaction: IEsploraApi.Transaction = { txid: transaction.txid, diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 3d50e1684..90aaa2aba 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -2,11 +2,14 @@ import config from '../config'; import bitcoinApi from './bitcoin/bitcoin-api-factory'; import logger from '../logger'; import memPool from './mempool'; -import { BlockExtended, TransactionExtended } from '../mempool.interfaces'; +import { BlockExtended, PoolTag, TransactionExtended, TransactionMinerInfo } from '../mempool.interfaces'; import { Common } from './common'; import diskCache from './disk-cache'; import transactionUtils from './transaction-utils'; import bitcoinClient from './bitcoin/bitcoin-client'; +import { IEsploraApi } from './bitcoin/esplora-api.interface'; +import poolsRepository from '../repositories/PoolsRepository'; +import blocksRepository from '../repositories/BlocksRepository'; class Blocks { private blocks: BlockExtended[] = []; @@ -15,6 +18,7 @@ class Blocks { private lastDifficultyAdjustmentTime = 0; private previousDifficultyRetarget = 0; private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = []; + private blockIndexingStarted = false; constructor() { } @@ -30,6 +34,186 @@ class Blocks { this.newBlockCallbacks.push(fn); } + /** + * Return the list of transaction for a block + * @param blockHash + * @param blockHeight + * @param onlyCoinbase - Set to true if you only need the coinbase transaction + * @returns Promise + */ + private async $getTransactionsExtended(blockHash: string, blockHeight: number, onlyCoinbase: boolean): Promise { + const transactions: TransactionExtended[] = []; + const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash); + + const mempool = memPool.getMempool(); + let transactionsFound = 0; + let transactionsFetched = 0; + + for (let i = 0; i < txIds.length; i++) { + if (mempool[txIds[i]]) { + // We update blocks before the mempool (index.ts), therefore we can + // optimize here by directly fetching txs in the "outdated" mempool + transactions.push(mempool[txIds[i]]); + transactionsFound++; + } else if (config.MEMPOOL.BACKEND === 'esplora' || memPool.isInSync() || i === 0) { + // Otherwise we fetch the tx data through backend services (esplora, electrum, core rpc...) + if (i % (Math.round((txIds.length) / 10)) === 0 || i + 1 === txIds.length) { // Avoid log spam + logger.debug(`Indexing tx ${i + 1} of ${txIds.length} in block #${blockHeight}`); + } + try { + const tx = await transactionUtils.$getTransactionExtended(txIds[i]); + transactions.push(tx); + transactionsFetched++; + } catch (e) { + logger.debug('Error fetching block tx: ' + (e instanceof Error ? e.message : e)); + if (i === 0) { + throw new Error('Failed to fetch Coinbase transaction: ' + txIds[i]); + } + } + } + + if (onlyCoinbase === true) { + break; // Fetch the first transaction and exit + } + } + + transactions.forEach((tx) => { + if (!tx.cpfpChecked) { + Common.setRelativesAndGetCpfpInfo(tx, mempool); // Child Pay For Parent + } + }); + + logger.debug(`${transactionsFound} of ${txIds.length} found in mempool. ${transactionsFetched} fetched through backend service.`); + + return transactions; + } + + /** + * Return a block with additional data (reward, coinbase, fees...) + * @param block + * @param transactions + * @returns BlockExtended + */ + private getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): BlockExtended { + const blockExtended: BlockExtended = Object.assign({}, block); + blockExtended.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0); + blockExtended.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]); + + const transactionsTmp = [...transactions]; + transactionsTmp.shift(); + transactionsTmp.sort((a, b) => b.effectiveFeePerVsize - a.effectiveFeePerVsize); + blockExtended.medianFee = transactionsTmp.length > 0 ? Common.median(transactionsTmp.map((tx) => tx.effectiveFeePerVsize)) : 0; + blockExtended.feeRange = transactionsTmp.length > 0 ? Common.getFeesInRange(transactionsTmp, 8) : [0, 0]; + + return blockExtended; + } + + /** + * Try to find which miner found the block + * @param txMinerInfo + * @returns + */ + private async $findBlockMiner(txMinerInfo: TransactionMinerInfo | undefined): Promise { + if (txMinerInfo === undefined || txMinerInfo.vout.length < 1) { + return await poolsRepository.$getUnknownPool(); + } + + const asciiScriptSig = transactionUtils.hex2ascii(txMinerInfo.vin[0].scriptsig); + const address = txMinerInfo.vout[0].scriptpubkey_address; + + const pools: PoolTag[] = await poolsRepository.$getPools(); + for (let i = 0; i < pools.length; ++i) { + if (address !== undefined) { + const addresses: string[] = JSON.parse(pools[i].addresses); + if (addresses.indexOf(address) !== -1) { + return pools[i]; + } + } + + const regexes: string[] = JSON.parse(pools[i].regexes); + for (let y = 0; y < regexes.length; ++y) { + const match = asciiScriptSig.match(regexes[y]); + if (match !== null) { + return pools[i]; + } + } + } + + return await poolsRepository.$getUnknownPool(); + } + + /** + * Index all blocks metadata for the mining dashboard + */ + public async $generateBlockDatabase() { + if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false || // Bitcoin only + config.MEMPOOL.INDEXING_BLOCKS_AMOUNT === 0 || // Indexing must be enabled + !memPool.isInSync() || // We sync the mempool first + this.blockIndexingStarted === true // Indexing must not already be in progress + ) { + return; + } + + const blockchainInfo = await bitcoinClient.getBlockchainInfo(); + if (blockchainInfo.blocks !== blockchainInfo.headers) { + return; + } + + this.blockIndexingStarted = true; + + try { + let currentBlockHeight = blockchainInfo.blocks; + + let indexingBlockAmount = config.MEMPOOL.INDEXING_BLOCKS_AMOUNT; + if (indexingBlockAmount <= -1) { + indexingBlockAmount = currentBlockHeight + 1; + } + + const lastBlockToIndex = Math.max(0, currentBlockHeight - indexingBlockAmount + 1); + + logger.info(`Indexing blocks from #${currentBlockHeight} to #${lastBlockToIndex}`); + + const chunkSize = 10000; + while (currentBlockHeight >= lastBlockToIndex) { + const endBlock = Math.max(0, lastBlockToIndex, currentBlockHeight - chunkSize + 1); + + const missingBlockHeights: number[] = await blocksRepository.$getMissingBlocksBetweenHeights( + currentBlockHeight, endBlock); + if (missingBlockHeights.length <= 0) { + logger.debug(`No missing blocks between #${currentBlockHeight} to #${endBlock}`); + currentBlockHeight -= chunkSize; + continue; + } + + logger.debug(`Indexing ${missingBlockHeights.length} blocks from #${currentBlockHeight} to #${endBlock}`); + + for (const blockHeight of missingBlockHeights) { + if (blockHeight < lastBlockToIndex) { + break; + } + try { + logger.debug(`Indexing block #${blockHeight}`); + const blockHash = await bitcoinApi.$getBlockHash(blockHeight); + const block = await bitcoinApi.$getBlock(blockHash); + const transactions = await this.$getTransactionsExtended(blockHash, block.height, true); + const blockExtended = this.getBlockExtended(block, transactions); + const miner = await this.$findBlockMiner(blockExtended.coinbaseTx); + const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true); + await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner); + } catch (e) { + logger.err(`Something went wrong while indexing blocks.` + e); + } + } + + currentBlockHeight -= chunkSize; + } + logger.info('Block indexing completed'); + } catch (e) { + logger.err('An error occured in $generateBlockDatabase(). Skipping block indexing. ' + e); + console.log(e); + } + } + public async $updateBlocks() { const blockHeightTip = await bitcoinApi.$getBlockHeightTip(); @@ -72,49 +256,18 @@ class Blocks { logger.debug(`New block found (#${this.currentBlockHeight})!`); } - const transactions: TransactionExtended[] = []; - const blockHash = await bitcoinApi.$getBlockHash(this.currentBlockHeight); const block = await bitcoinApi.$getBlock(blockHash); const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash); + const transactions = await this.$getTransactionsExtended(blockHash, block.height, false); + const blockExtended: BlockExtended = this.getBlockExtended(block, transactions); + const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true); - const mempool = memPool.getMempool(); - let transactionsFound = 0; - - for (let i = 0; i < txIds.length; i++) { - if (mempool[txIds[i]]) { - transactions.push(mempool[txIds[i]]); - transactionsFound++; - } else if (config.MEMPOOL.BACKEND === 'esplora' || memPool.isInSync() || i === 0) { - logger.debug(`Fetching block tx ${i} of ${txIds.length}`); - try { - const tx = await transactionUtils.$getTransactionExtended(txIds[i]); - transactions.push(tx); - } catch (e) { - logger.debug('Error fetching block tx: ' + (e instanceof Error ? e.message : e)); - if (i === 0) { - throw new Error('Failed to fetch Coinbase transaction: ' + txIds[i]); - } - } - } + if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === true) { + const miner = await this.$findBlockMiner(blockExtended.coinbaseTx); + await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner); } - transactions.forEach((tx) => { - if (!tx.cpfpChecked) { - Common.setRelativesAndGetCpfpInfo(tx, mempool); - } - }); - - logger.debug(`${transactionsFound} of ${txIds.length} found in mempool. ${txIds.length - transactionsFound} not found.`); - - const blockExtended: BlockExtended = Object.assign({}, block); - blockExtended.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0); - blockExtended.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]); - transactions.shift(); - transactions.sort((a, b) => b.effectiveFeePerVsize - a.effectiveFeePerVsize); - blockExtended.medianFee = transactions.length > 0 ? Common.median(transactions.map((tx) => tx.effectiveFeePerVsize)) : 0; - blockExtended.feeRange = transactions.length > 0 ? Common.getFeesInRange(transactions, 8) : [0, 0]; - if (block.height % 2016 === 0) { this.previousDifficultyRetarget = (block.difficulty - this.currentDifficulty) / this.currentDifficulty * 100; this.lastDifficultyAdjustmentTime = block.timestamp; @@ -132,6 +285,8 @@ class Blocks { if (memPool.isInSync()) { diskCache.$saveCacheToDisk(); } + + return; } } diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index 2ac97636e..24ecc03cf 100644 --- a/backend/src/api/database-migration.ts +++ b/backend/src/api/database-migration.ts @@ -3,10 +3,10 @@ import config from '../config'; import { DB } from '../database'; import logger from '../logger'; -const sleep = (ms: number) => new Promise( res => setTimeout(res, ms)); +const sleep = (ms: number) => new Promise(res => setTimeout(res, ms)); class DatabaseMigration { - private static currentVersion = 2; + private static currentVersion = 4; private queryTimeout = 120000; private statisticsAddedIndexed = false; @@ -83,6 +83,13 @@ class DatabaseMigration { if (databaseSchemaVersion < 2 && this.statisticsAddedIndexed === false) { await this.$executeQuery(connection, `CREATE INDEX added ON statistics (added);`); } + if (databaseSchemaVersion < 3) { + await this.$executeQuery(connection, this.getCreatePoolsTableQuery(), await this.$checkIfTableExists('pools')); + } + if (databaseSchemaVersion < 4) { + await this.$executeQuery(connection, 'DROP table IF EXISTS blocks;'); + await this.$executeQuery(connection, this.getCreateBlocksTableQuery(), await this.$checkIfTableExists('blocks')); + } connection.release(); } catch (e) { connection.release(); @@ -197,7 +204,6 @@ class DatabaseMigration { const connection = await DB.pool.getConnection(); try { await this.$executeQuery(connection, 'START TRANSACTION;'); - await this.$executeQuery(connection, 'SET autocommit = 0;'); for (const query of transactionQueries) { await this.$executeQuery(connection, query); } @@ -335,6 +341,37 @@ class DatabaseMigration { final_tx int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`; } + + private getCreatePoolsTableQuery(): string { + return `CREATE TABLE IF NOT EXISTS pools ( + id int(11) NOT NULL AUTO_INCREMENT, + name varchar(50) NOT NULL, + link varchar(255) NOT NULL, + addresses text NOT NULL, + regexes text NOT NULL, + PRIMARY KEY (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`; + } + + private getCreateBlocksTableQuery(): string { + return `CREATE TABLE IF NOT EXISTS blocks ( + height int(11) unsigned NOT NULL, + hash varchar(65) NOT NULL, + blockTimestamp timestamp NOT NULL, + size int(11) unsigned NOT NULL, + weight int(11) unsigned NOT NULL, + tx_count int(11) unsigned NOT NULL, + coinbase_raw text, + difficulty bigint(20) unsigned NOT NULL, + pool_id int(11) DEFAULT -1, + fees double unsigned NOT NULL, + fee_span json NOT NULL, + median_fee double unsigned NOT NULL, + PRIMARY KEY (height), + INDEX (pool_id), + FOREIGN KEY (pool_id) REFERENCES pools (id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`; + } } export default new DatabaseMigration(); diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts new file mode 100644 index 000000000..c89ea9324 --- /dev/null +++ b/backend/src/api/mining.ts @@ -0,0 +1,69 @@ +import { PoolInfo, PoolStats } from '../mempool.interfaces'; +import BlocksRepository, { EmptyBlocks } from '../repositories/BlocksRepository'; +import PoolsRepository from '../repositories/PoolsRepository'; +import bitcoinClient from './bitcoin/bitcoin-client'; + +class Mining { + constructor() { + } + + /** + * Generate high level overview of the pool ranks and general stats + */ + public async $getPoolsStats(interval: string | null) : Promise { + let sqlInterval: string | null = null; + switch (interval) { + case '24h': sqlInterval = '1 DAY'; break; + case '3d': sqlInterval = '3 DAY'; break; + case '1w': sqlInterval = '1 WEEK'; break; + case '1m': sqlInterval = '1 MONTH'; break; + case '3m': sqlInterval = '3 MONTH'; break; + case '6m': sqlInterval = '6 MONTH'; break; + case '1y': sqlInterval = '1 YEAR'; break; + case '2y': sqlInterval = '2 YEAR'; break; + case '3y': sqlInterval = '3 YEAR'; break; + default: sqlInterval = null; break; + } + + const poolsStatistics = {}; + + const poolsInfo: PoolInfo[] = await PoolsRepository.$getPoolsInfo(sqlInterval); + const emptyBlocks: EmptyBlocks[] = await BlocksRepository.$countEmptyBlocks(sqlInterval); + + const poolsStats: PoolStats[] = []; + let rank = 1; + + poolsInfo.forEach((poolInfo: PoolInfo) => { + const poolStat: PoolStats = { + poolId: poolInfo.poolId, // mysql row id + name: poolInfo.name, + link: poolInfo.link, + blockCount: poolInfo.blockCount, + rank: rank++, + emptyBlocks: 0, + } + for (let i = 0; i < emptyBlocks.length; ++i) { + if (emptyBlocks[i].poolId === poolInfo.poolId) { + poolStat.emptyBlocks++; + } + } + poolsStats.push(poolStat); + }); + + poolsStatistics['pools'] = poolsStats; + + const oldestBlock = new Date(await BlocksRepository.$oldestBlockTimestamp()); + poolsStatistics['oldestIndexedBlockTimestamp'] = oldestBlock.getTime(); + + const blockCount: number = await BlocksRepository.$blockCount(sqlInterval); + poolsStatistics['blockCount'] = blockCount; + + const blockHeightTip = await bitcoinClient.getBlockCount(); + const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(120, blockHeightTip); + poolsStatistics['lastEstimatedHashrate'] = lastBlockHashrate; + + return poolsStatistics; + } +} + +export default new Mining(); diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts new file mode 100644 index 000000000..194ce0dd9 --- /dev/null +++ b/backend/src/api/pools-parser.ts @@ -0,0 +1,173 @@ +import { readFileSync } from 'fs'; +import { DB } from '../database'; +import logger from '../logger'; +import config from '../config'; + +interface Pool { + name: string; + link: string; + regexes: string[]; + addresses: string[]; +} + +class PoolsParser { + /** + * Parse the pools.json file, consolidate the data and dump it into the database + */ + public async migratePoolsJson() { + if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) { + return; + } + + logger.debug('Importing pools.json to the database, open ./pools.json'); + + let poolsJson: object = {}; + try { + const fileContent: string = readFileSync('./pools.json', 'utf8'); + poolsJson = JSON.parse(fileContent); + } catch (e) { + logger.err('Unable to open ./pools.json, does the file exist?'); + await this.insertUnknownPool(); + return; + } + + // First we save every entries without paying attention to pool duplication + const poolsDuplicated: Pool[] = []; + + logger.debug('Parse coinbase_tags'); + const coinbaseTags = Object.entries(poolsJson['coinbase_tags']); + for (let i = 0; i < coinbaseTags.length; ++i) { + poolsDuplicated.push({ + 'name': (coinbaseTags[i][1]).name, + 'link': (coinbaseTags[i][1]).link, + 'regexes': [coinbaseTags[i][0]], + 'addresses': [], + }); + } + logger.debug('Parse payout_addresses'); + const addressesTags = Object.entries(poolsJson['payout_addresses']); + for (let i = 0; i < addressesTags.length; ++i) { + poolsDuplicated.push({ + 'name': (addressesTags[i][1]).name, + 'link': (addressesTags[i][1]).link, + 'regexes': [], + 'addresses': [addressesTags[i][0]], + }); + } + + // Then, we find unique mining pool names + logger.debug('Identify unique mining pools'); + const poolNames: string[] = []; + for (let i = 0; i < poolsDuplicated.length; ++i) { + if (poolNames.indexOf(poolsDuplicated[i].name) === -1) { + poolNames.push(poolsDuplicated[i].name); + } + } + logger.debug(`Found ${poolNames.length} unique mining pools`); + + // Get existing pools from the db + const connection = await DB.pool.getConnection(); + let existingPools; + try { + [existingPools] = await connection.query({ sql: 'SELECT * FROM pools;', timeout: 120000 }); + } catch (e) { + logger.err('Unable to get existing pools from the database, skipping pools.json import'); + connection.release(); + return; + } + + // Finally, we generate the final consolidated pools data + const finalPoolDataAdd: Pool[] = []; + const finalPoolDataUpdate: Pool[] = []; + for (let i = 0; i < poolNames.length; ++i) { + let allAddresses: string[] = []; + let allRegexes: string[] = []; + const match = poolsDuplicated.filter((pool: Pool) => pool.name === poolNames[i]); + + for (let y = 0; y < match.length; ++y) { + allAddresses = allAddresses.concat(match[y].addresses); + allRegexes = allRegexes.concat(match[y].regexes); + } + + const finalPoolName = poolNames[i].replace(`'`, `''`); // To support single quote in names when doing db queries + + if (existingPools.find((pool) => pool.name === poolNames[i]) !== undefined) { + logger.debug(`Update '${finalPoolName}' mining pool`); + finalPoolDataUpdate.push({ + 'name': finalPoolName, + 'link': match[0].link, + 'regexes': allRegexes, + 'addresses': allAddresses, + }); + } else { + logger.debug(`Add '${finalPoolName}' mining pool`); + finalPoolDataAdd.push({ + 'name': finalPoolName, + 'link': match[0].link, + 'regexes': allRegexes, + 'addresses': allAddresses, + }); + } + } + + logger.debug(`Update pools table now`); + + // Add new mining pools into the database + let queryAdd: string = 'INSERT INTO pools(name, link, regexes, addresses) VALUES '; + for (let i = 0; i < finalPoolDataAdd.length; ++i) { + queryAdd += `('${finalPoolDataAdd[i].name}', '${finalPoolDataAdd[i].link}', + '${JSON.stringify(finalPoolDataAdd[i].regexes)}', '${JSON.stringify(finalPoolDataAdd[i].addresses)}'),`; + } + queryAdd = queryAdd.slice(0, -1) + ';'; + + // Add new mining pools into the database + const updateQueries: string[] = []; + for (let i = 0; i < finalPoolDataUpdate.length; ++i) { + updateQueries.push(` + UPDATE pools + SET name='${finalPoolDataUpdate[i].name}', link='${finalPoolDataUpdate[i].link}', + regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}' + WHERE name='${finalPoolDataUpdate[i].name}' + ;`); + } + + try { + if (finalPoolDataAdd.length > 0) { + await connection.query({ sql: queryAdd, timeout: 120000 }); + } + for (const query of updateQueries) { + await connection.query({ sql: query, timeout: 120000 }); + } + await this.insertUnknownPool(); + connection.release(); + logger.info('Mining pools.json import completed'); + } catch (e) { + connection.release(); + logger.err(`Unable to import pools in the database!`); + throw e; + } + } + + /** + * Manually add the 'unknown pool' + */ + private async insertUnknownPool() { + const connection = await DB.pool.getConnection(); + try { + const [rows]: any[] = await connection.query({ sql: 'SELECT name from pools where name="Unknown"', timeout: 120000 }); + if (rows.length === 0) { + logger.debug('Manually inserting "Unknown" mining pool into the databse'); + await connection.query({ + sql: `INSERT INTO pools(name, link, regexes, addresses) + VALUES("Unknown", "https://learnmeabitcoin.com/technical/coinbase-transaction", "[]", "[]"); + `}); + } + } catch (e) { + logger.err('Unable to insert "Unknown" mining pool'); + } + + connection.release(); + } +} + +export default new PoolsParser(); diff --git a/backend/src/api/statistics.ts b/backend/src/api/statistics.ts index ba1d540ee..886ad78ba 100644 --- a/backend/src/api/statistics.ts +++ b/backend/src/api/statistics.ts @@ -53,12 +53,16 @@ class Statistics { memPoolArray = memPoolArray.filter((tx) => tx.effectiveFeePerVsize); if (!memPoolArray.length) { - const insertIdZeroed = await this.$createZeroedStatistic(); - if (this.newStatisticsEntryCallback && insertIdZeroed) { - const newStats = await this.$get(insertIdZeroed); - if (newStats) { - this.newStatisticsEntryCallback(newStats); + try { + const insertIdZeroed = await this.$createZeroedStatistic(); + if (this.newStatisticsEntryCallback && insertIdZeroed) { + const newStats = await this.$get(insertIdZeroed); + if (newStats) { + this.newStatisticsEntryCallback(newStats); + } } + } catch (e) { + logger.err('Unable to insert zeroed statistics. ' + e); } return; } @@ -90,59 +94,63 @@ class Statistics { } }); - const insertId = await this.$create({ - added: 'NOW()', - unconfirmed_transactions: memPoolArray.length, - tx_per_second: txPerSecond, - vbytes_per_second: Math.round(vBytesPerSecond), - mempool_byte_weight: totalWeight, - total_fee: totalFee, - fee_data: '', - vsize_1: weightVsizeFees['1'] || 0, - vsize_2: weightVsizeFees['2'] || 0, - vsize_3: weightVsizeFees['3'] || 0, - vsize_4: weightVsizeFees['4'] || 0, - vsize_5: weightVsizeFees['5'] || 0, - vsize_6: weightVsizeFees['6'] || 0, - vsize_8: weightVsizeFees['8'] || 0, - vsize_10: weightVsizeFees['10'] || 0, - vsize_12: weightVsizeFees['12'] || 0, - vsize_15: weightVsizeFees['15'] || 0, - vsize_20: weightVsizeFees['20'] || 0, - vsize_30: weightVsizeFees['30'] || 0, - vsize_40: weightVsizeFees['40'] || 0, - vsize_50: weightVsizeFees['50'] || 0, - vsize_60: weightVsizeFees['60'] || 0, - vsize_70: weightVsizeFees['70'] || 0, - vsize_80: weightVsizeFees['80'] || 0, - vsize_90: weightVsizeFees['90'] || 0, - vsize_100: weightVsizeFees['100'] || 0, - vsize_125: weightVsizeFees['125'] || 0, - vsize_150: weightVsizeFees['150'] || 0, - vsize_175: weightVsizeFees['175'] || 0, - vsize_200: weightVsizeFees['200'] || 0, - vsize_250: weightVsizeFees['250'] || 0, - vsize_300: weightVsizeFees['300'] || 0, - vsize_350: weightVsizeFees['350'] || 0, - vsize_400: weightVsizeFees['400'] || 0, - vsize_500: weightVsizeFees['500'] || 0, - vsize_600: weightVsizeFees['600'] || 0, - vsize_700: weightVsizeFees['700'] || 0, - vsize_800: weightVsizeFees['800'] || 0, - vsize_900: weightVsizeFees['900'] || 0, - vsize_1000: weightVsizeFees['1000'] || 0, - vsize_1200: weightVsizeFees['1200'] || 0, - vsize_1400: weightVsizeFees['1400'] || 0, - vsize_1600: weightVsizeFees['1600'] || 0, - vsize_1800: weightVsizeFees['1800'] || 0, - vsize_2000: weightVsizeFees['2000'] || 0, - }); + try { + const insertId = await this.$create({ + added: 'NOW()', + unconfirmed_transactions: memPoolArray.length, + tx_per_second: txPerSecond, + vbytes_per_second: Math.round(vBytesPerSecond), + mempool_byte_weight: totalWeight, + total_fee: totalFee, + fee_data: '', + vsize_1: weightVsizeFees['1'] || 0, + vsize_2: weightVsizeFees['2'] || 0, + vsize_3: weightVsizeFees['3'] || 0, + vsize_4: weightVsizeFees['4'] || 0, + vsize_5: weightVsizeFees['5'] || 0, + vsize_6: weightVsizeFees['6'] || 0, + vsize_8: weightVsizeFees['8'] || 0, + vsize_10: weightVsizeFees['10'] || 0, + vsize_12: weightVsizeFees['12'] || 0, + vsize_15: weightVsizeFees['15'] || 0, + vsize_20: weightVsizeFees['20'] || 0, + vsize_30: weightVsizeFees['30'] || 0, + vsize_40: weightVsizeFees['40'] || 0, + vsize_50: weightVsizeFees['50'] || 0, + vsize_60: weightVsizeFees['60'] || 0, + vsize_70: weightVsizeFees['70'] || 0, + vsize_80: weightVsizeFees['80'] || 0, + vsize_90: weightVsizeFees['90'] || 0, + vsize_100: weightVsizeFees['100'] || 0, + vsize_125: weightVsizeFees['125'] || 0, + vsize_150: weightVsizeFees['150'] || 0, + vsize_175: weightVsizeFees['175'] || 0, + vsize_200: weightVsizeFees['200'] || 0, + vsize_250: weightVsizeFees['250'] || 0, + vsize_300: weightVsizeFees['300'] || 0, + vsize_350: weightVsizeFees['350'] || 0, + vsize_400: weightVsizeFees['400'] || 0, + vsize_500: weightVsizeFees['500'] || 0, + vsize_600: weightVsizeFees['600'] || 0, + vsize_700: weightVsizeFees['700'] || 0, + vsize_800: weightVsizeFees['800'] || 0, + vsize_900: weightVsizeFees['900'] || 0, + vsize_1000: weightVsizeFees['1000'] || 0, + vsize_1200: weightVsizeFees['1200'] || 0, + vsize_1400: weightVsizeFees['1400'] || 0, + vsize_1600: weightVsizeFees['1600'] || 0, + vsize_1800: weightVsizeFees['1800'] || 0, + vsize_2000: weightVsizeFees['2000'] || 0, + }); - if (this.newStatisticsEntryCallback && insertId) { - const newStats = await this.$get(insertId); - if (newStats) { - this.newStatisticsEntryCallback(newStats); + if (this.newStatisticsEntryCallback && insertId) { + const newStats = await this.$get(insertId); + if (newStats) { + this.newStatisticsEntryCallback(newStats); + } } + } catch (e) { + logger.err('Unable to insert statistics. ' + e); } } diff --git a/backend/src/api/transaction-utils.ts b/backend/src/api/transaction-utils.ts index 1496b810b..2e669d709 100644 --- a/backend/src/api/transaction-utils.ts +++ b/backend/src/api/transaction-utils.ts @@ -44,6 +44,14 @@ class TransactionUtils { } return transactionExtended; } + + public hex2ascii(hex: string) { + let str = ''; + for (let i = 0; i < hex.length; i += 2) { + str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); + } + return str; + } } export default new TransactionUtils(); diff --git a/backend/src/config.ts b/backend/src/config.ts index 13ad224a1..6c0ff8250 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -14,6 +14,7 @@ interface IConfig { BLOCK_WEIGHT_UNITS: number; INITIAL_BLOCKS_AMOUNT: number; MEMPOOL_BLOCKS_AMOUNT: number; + INDEXING_BLOCKS_AMOUNT: number; PRICE_FEED_UPDATE_INTERVAL: number; USE_SECOND_NODE_FOR_MINFEE: boolean; EXTERNAL_ASSETS: string[]; @@ -77,9 +78,12 @@ const defaults: IConfig = { 'BLOCK_WEIGHT_UNITS': 4000000, 'INITIAL_BLOCKS_AMOUNT': 8, 'MEMPOOL_BLOCKS_AMOUNT': 8, + 'INDEXING_BLOCKS_AMOUNT': 1100, // 0 = disable indexing, -1 = index all blocks 'PRICE_FEED_UPDATE_INTERVAL': 3600, 'USE_SECOND_NODE_FOR_MINFEE': false, - 'EXTERNAL_ASSETS': [], + 'EXTERNAL_ASSETS': [ + 'https://mempool.space/resources/pools.json' + ] }, 'ESPLORA': { 'REST_API_URL': 'http://127.0.0.1:3000', diff --git a/backend/src/index.ts b/backend/src/index.ts index f6615d1c8..f78c5922b 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -22,6 +22,7 @@ import loadingIndicators from './api/loading-indicators'; import mempool from './api/mempool'; import elementsParser from './api/liquid/elements-parser'; import databaseMigration from './api/database-migration'; +import poolsParser from './api/pools-parser'; import syncAssets from './sync-assets'; import icons from './api/liquid/icons'; import { Common } from './api/common'; @@ -88,6 +89,7 @@ class Server { await checkDbConnection(); try { await databaseMigration.$initializeOrMigrateDatabase(); + await poolsParser.migratePoolsJson(); } catch (e) { throw new Error(e instanceof Error ? e.message : 'Error'); } @@ -136,6 +138,8 @@ class Server { } await blocks.$updateBlocks(); await memPool.$updateMempool(); + blocks.$generateBlockDatabase(); + setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS); this.currentBackendRetryInterval = 5; } catch (e) { @@ -252,6 +256,16 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1y', routes.$getStatisticsByTime.bind(routes, '1y')) .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2y', routes.$getStatisticsByTime.bind(routes, '2y')) .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3y', routes.$getStatisticsByTime.bind(routes, '3y')) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/24h', routes.$getPools.bind(routes, '24h')) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3d', routes.$getPools.bind(routes, '3d')) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/1w', routes.$getPools.bind(routes, '1w')) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/1m', routes.$getPools.bind(routes, '1m')) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3m', routes.$getPools.bind(routes, '3m')) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/6m', routes.$getPools.bind(routes, '6m')) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/1y', routes.$getPools.bind(routes, '1y')) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/2y', routes.$getPools.bind(routes, '2y')) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3y', routes.$getPools.bind(routes, '3y')) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/all', routes.$getPools.bind(routes, 'all')) ; } diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 2604a233c..5fb83d792 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -1,5 +1,25 @@ import { IEsploraApi } from './api/bitcoin/esplora-api.interface'; +export interface PoolTag { + id: number | null, // mysql row id + name: string, + link: string, + regexes: string, // JSON array + addresses: string, // JSON array +} + +export interface PoolInfo { + poolId: number, // mysql row id + name: string, + link: string, + blockCount: number, +} + +export interface PoolStats extends PoolInfo { + rank: number, + emptyBlocks: number, +} + export interface MempoolBlock { blockSize: number; blockVSize: number; diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts new file mode 100644 index 000000000..947403e88 --- /dev/null +++ b/backend/src/repositories/BlocksRepository.ts @@ -0,0 +1,128 @@ +import { BlockExtended, PoolTag } from '../mempool.interfaces'; +import { DB } from '../database'; +import logger from '../logger'; + +export interface EmptyBlocks { + emptyBlocks: number; + poolId: number; +} + +class BlocksRepository { + /** + * Save indexed block data in the database + */ + public async $saveBlockInDatabase( + block: BlockExtended, + blockHash: string, + coinbaseHex: string | undefined, + poolTag: PoolTag + ) { + const connection = await DB.pool.getConnection(); + + try { + const query = `INSERT INTO blocks( + height, hash, blockTimestamp, size, + weight, tx_count, coinbase_raw, difficulty, + pool_id, fees, fee_span, median_fee + ) VALUE ( + ?, ?, FROM_UNIXTIME(?), ?, + ?, ?, ?, ?, + ?, ?, ?, ? + )`; + + const params: any[] = [ + block.height, blockHash, block.timestamp, block.size, + block.weight, block.tx_count, coinbaseHex ? coinbaseHex : '', block.difficulty, + poolTag.id, 0, '[]', block.medianFee, + ]; + + await connection.query(query, params); + } catch (e) { + logger.err('$saveBlockInDatabase() error' + (e instanceof Error ? e.message : e)); + } + + connection.release(); + } + + /** + * Get all block height that have not been indexed between [startHeight, endHeight] + */ + public async $getMissingBlocksBetweenHeights(startHeight: number, endHeight: number): Promise { + if (startHeight < endHeight) { + return []; + } + + const connection = await DB.pool.getConnection(); + const [rows] : any[] = await connection.query(` + SELECT height + FROM blocks + WHERE height <= ${startHeight} AND height >= ${endHeight} + ORDER BY height DESC; + `); + connection.release(); + + const indexedBlockHeights: number[] = []; + rows.forEach((row: any) => { indexedBlockHeights.push(row.height); }); + const seekedBlocks: number[] = Array.from(Array(startHeight - endHeight + 1).keys(), n => n + endHeight).reverse(); + const missingBlocksHeights = seekedBlocks.filter(x => indexedBlockHeights.indexOf(x) === -1); + + return missingBlocksHeights; + } + + /** + * Count empty blocks for all pools + */ + public async $countEmptyBlocks(interval: string | null): Promise { + const query = ` + SELECT pool_id as poolId + FROM blocks + WHERE tx_count = 1` + + (interval != null ? ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``) + ; + + const connection = await DB.pool.getConnection(); + const [rows] = await connection.query(query); + connection.release(); + + return rows; + } + + /** + * Get blocks count for a period + */ + public async $blockCount(interval: string | null): Promise { + const query = ` + SELECT count(height) as blockCount + FROM blocks` + + (interval != null ? ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``) + ; + + const connection = await DB.pool.getConnection(); + const [rows] = await connection.query(query); + connection.release(); + + return rows[0].blockCount; + } + + /** + * Get the oldest indexed block + */ + public async $oldestBlockTimestamp(): Promise { + const connection = await DB.pool.getConnection(); + const [rows]: any[] = await connection.query(` + SELECT blockTimestamp + FROM blocks + ORDER BY height + LIMIT 1; + `); + connection.release(); + + if (rows.length <= 0) { + return -1; + } + + return rows[0].blockTimestamp; + } +} + +export default new BlocksRepository(); \ No newline at end of file diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts new file mode 100644 index 000000000..d1fb0da9a --- /dev/null +++ b/backend/src/repositories/PoolsRepository.ts @@ -0,0 +1,46 @@ +import { DB } from '../database'; +import { PoolInfo, PoolTag } from '../mempool.interfaces'; + +class PoolsRepository { + /** + * Get all pools tagging info + */ + public async $getPools(): Promise { + const connection = await DB.pool.getConnection(); + const [rows] = await connection.query('SELECT * FROM pools;'); + connection.release(); + return rows; + } + + /** + * Get unknown pool tagging info + */ + public async $getUnknownPool(): Promise { + const connection = await DB.pool.getConnection(); + const [rows] = await connection.query('SELECT * FROM pools where name = "Unknown"'); + connection.release(); + return rows[0]; + } + + /** + * Get basic pool info and block count + */ + public async $getPoolsInfo(interval: string | null): Promise { + const query = ` + SELECT COUNT(height) as blockCount, pool_id as poolId, pools.name as name, pools.link as link + FROM blocks + JOIN pools on pools.id = pool_id` + + (interval != null ? ` WHERE blocks.blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``) + + ` GROUP BY pool_id + ORDER BY COUNT(height) DESC + `; + + const connection = await DB.pool.getConnection(); + const [rows] = await connection.query(query); + connection.release(); + + return rows; + } +} + +export default new PoolsRepository(); diff --git a/backend/src/routes.ts b/backend/src/routes.ts index b642f6c28..1525cd599 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -20,6 +20,7 @@ import { Common } from './api/common'; import bitcoinClient from './api/bitcoin/bitcoin-client'; import elementsParser from './api/liquid/elements-parser'; import icons from './api/liquid/icons'; +import miningStats from './api/mining'; class Routes { constructor() {} @@ -531,6 +532,18 @@ class Routes { } } + public async $getPools(interval: string, req: Request, res: Response) { + try { + let stats = await miningStats.$getPoolsStats(interval); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); + res.json(stats); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + public async getBlock(req: Request, res: Response) { try { const result = await bitcoinApi.$getBlock(req.params.hash); diff --git a/contributors/antonilol.txt b/contributors/antonilol.txt new file mode 100644 index 000000000..94f5ba0c6 --- /dev/null +++ b/contributors/antonilol.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of January 25, 2022. + +Signed: antonilol diff --git a/contributors/emzy.txt b/contributors/emzy.txt new file mode 100644 index 000000000..7d0a9409e --- /dev/null +++ b/contributors/emzy.txt @@ -0,0 +1 @@ +Mempool Space K.K. has a signed CLA or other agreement on file with @emzy as of January 25, 2022 diff --git a/contributors/hunicus.txt b/contributors/hunicus.txt new file mode 100644 index 000000000..bf8f3ed15 --- /dev/null +++ b/contributors/hunicus.txt @@ -0,0 +1 @@ +Mempool Space K.K. has a signed CLA or other agreement on file with @hunicus as of January 25, 2022 diff --git a/contributors/knorrium.txt b/contributors/knorrium.txt new file mode 100644 index 000000000..144a966a4 --- /dev/null +++ b/contributors/knorrium.txt @@ -0,0 +1 @@ +Mempool Space K.K. has a signed CLA or other agreement on file with @knorrium as of January 25, 2022 diff --git a/contributors/miguelmedeiros.txt b/contributors/miguelmedeiros.txt new file mode 100644 index 000000000..3a7a2baab --- /dev/null +++ b/contributors/miguelmedeiros.txt @@ -0,0 +1 @@ +Mempool Space K.K. has a signed CLA or other agreement on file with @miguelmedeiros as of January 25, 2022 diff --git a/contributors/nymkappa.txt b/contributors/nymkappa.txt new file mode 100644 index 000000000..4e86a6f38 --- /dev/null +++ b/contributors/nymkappa.txt @@ -0,0 +1 @@ +Mempool Space K.K. has a signed CLA or other agreement on file with @nymkappa as of January 25, 2022 diff --git a/contributors/softsimon.txt b/contributors/softsimon.txt new file mode 100644 index 000000000..f33f99034 --- /dev/null +++ b/contributors/softsimon.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of January 25, 2022. + +Signed: softsimon diff --git a/contributors/wiz.txt b/contributors/wiz.txt new file mode 100644 index 000000000..a006b4e04 --- /dev/null +++ b/contributors/wiz.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of January 25, 2022. + +Signed: wiz diff --git a/docker/backend/start.sh b/docker/backend/start.sh index f982a18e5..372be346e 100644 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -13,6 +13,7 @@ __MEMPOOL_RECOMMENDED_FEE_PERCENTILE__=${MEMPOOL_RECOMMENDED_FEE_PERCENTILE:=50} __MEMPOOL_BLOCK_WEIGHT_UNITS__=${MEMPOOL_BLOCK_WEIGHT_UNITS:=4000000} __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:=1100} __MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__=${MEMPOOL_PRICE_FEED_UPDATE_INTERVAL:=3600} __MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__=${MEMPOOL_USE_SECOND_NODE_FOR_MINFEE:=false} __MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[]} @@ -74,6 +75,7 @@ sed -i "s/__MEMPOOL_RECOMMENDED_FEE_PERCENTILE__/${__MEMPOOL_RECOMMENDED_FEE_PER sed -i "s/__MEMPOOL_BLOCK_WEIGHT_UNITS__/${__MEMPOOL_BLOCK_WEIGHT_UNITS__}/g" mempool-config.json sed -i "s/__MEMPOOL_INITIAL_BLOCKS_AMOUNT__/${__MEMPOOL_INITIAL_BLOCKS_AMOUNT__}/g" mempool-config.json sed -i "s/__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__/${__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__}/g" mempool-config.json +sed -i "s/__MEMPOOL_INDEXING_BLOCKS_AMOUNT__/${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}/g" mempool-config.json sed -i "s/__MEMPOOL_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 diff --git a/frontend/cypress/fixtures/pools.json b/frontend/cypress/fixtures/pools.json index ab198b6d0..1a69517e8 100644 --- a/frontend/cypress/fixtures/pools.json +++ b/frontend/cypress/fixtures/pools.json @@ -421,7 +421,7 @@ "link" : "http://www.dpool.top/" }, "/Rawpool.com/": { - "name" : "Rawpool.com", + "name" : "Rawpool", "link" : "https://www.rawpool.com/" }, "/haominer/": { @@ -488,10 +488,14 @@ "name" : "Binance Pool", "link" : "https://pool.binance.com/" }, - "/Minerium.com/" : { + "/Mined in the USA by: /Minerium.com/" : { "name" : "Minerium", "link" : "https://www.minerium.com/" }, + "/Minerium.com/" : { + "name" : "Minerium", + "link" : "https://www.minerium.com/" + }, "/Buffett/": { "name" : "Lubian.com", "link" : "" @@ -504,15 +508,15 @@ "name" : "OKKONG", "link" : "https://hash.okkong.com" }, - "/TMSPOOL/" : { - "name" : "TMSPool", + "/AAOPOOL/" : { + "name" : "AAO Pool", "link" : "https://btc.tmspool.top" }, "/one_more_mcd/" : { "name" : "EMCDPool", "link" : "https://pool.emcd.io" }, - "/Foundry USA Pool #dropgold/" : { + "Foundry USA Pool" : { "name" : "Foundry USA", "link" : "https://foundrydigital.com/" }, @@ -539,9 +543,29 @@ "/PureBTC.COM/": { "name": "PureBTC.COM", "link": "https://purebtc.com" + }, + "MARA Pool": { + "name": "MARA Pool", + "link": "https://marapool.com" + }, + "KuCoinPool": { + "name": "KuCoinPool", + "link": "https://www.kucoin.com/mining-pool/" + }, + "Entrustus" : { + "name": "Entrust Charity Pool", + "link": "pool.entustus.org" } }, "payout_addresses" : { + "1MkCDCzHpBsYQivp8MxjY5AkTGG1f2baoe": { + "name": "Luxor", + "link": "https://mining.luxor.tech" + }, + "1ArTPjj6pV3aNRhLPjJVPYoxB98VLBzUmb": { + "name" : "KuCoinPool", + "link" : "https://www.kucoin.com/mining-pool/" + }, "3Bmb9Jig8A5kHdDSxvDZ6eryj3AXd3swuJ": { "name" : "NovaBlock", "link" : "https://novablock.com" @@ -606,7 +630,7 @@ "name" : "BitMinter", "link" : "http://bitminter.com/" }, - "15xiShqUqerfjFdyfgBH1K7Gwp6cbYmsTW " : { + "15xiShqUqerfjFdyfgBH1K7Gwp6cbYmsTW" : { "name" : "EclipseMC", "link" : "https://eclipsemc.com/" }, @@ -634,6 +658,14 @@ "name" : "Huobi.pool", "link" : "https://www.hpt.com/" }, + "1BDbsWi3Mrcjp1wdop3PWFNCNZtu4R7Hjy" : { + "name" : "EMCDPool", + "link" : "https://pool.emcd.io" + }, + "12QVFmJH2b4455YUHkMpEnWLeRY3eJ4Jb5" : { + "name" : "AAO Pool", + "link" : "https://btc.tmspool.top " + }, "1ALA5v7h49QT7WYLcRsxcXqXUqEqaWmkvw" : { "name" : "CloudHashing", "link" : "https://cloudhashing.com/" @@ -915,7 +947,7 @@ "link" : "http://www.dpool.top/" }, "1FbBbv5oYqFKwiPm4CAqvAy8345n8AQ74b" : { - "name" : "Rawpool.com", + "name" : "Rawpool", "link" : "https://www.rawpool.com/" }, "1LsFmhnne74EmU4q4aobfxfrWY4wfMVd8w" : { @@ -934,6 +966,22 @@ "name" : "Poolin", "link" : "https://www.poolin.com/" }, + "1E8CZo2S3CqWg1VZSJNFCTbtT8hZPuQ2kB" : { + "name" : "Poolin", + "link" : "https://www.poolin.com/" + }, + "14sA8jqYQgMRQV9zUtGFvpeMEw7YDn77SK" : { + "name" : "Poolin", + "link" : "https://www.poolin.com/" + }, + "1GNgwA8JfG7Kc8akJ8opdNWJUihqUztfPe" : { + "name" : "Poolin", + "link" : "https://www.poolin.com/" + }, + "17tUZLvy3X2557JGhceXRiij2TNYuhRr4r" : { + "name" : "Poolin", + "link" : "https://www.poolin.com/" + }, "12Taz8FFXQ3E2AGn3ZW1SZM5bLnYGX4xR6" : { "name" : "Tangpool", "link" : "http://www.tangpool.com/" @@ -1126,6 +1174,10 @@ "name" : "Binance Pool", "link" : "https://pool.binance.com/" }, + "1JvXhnHCi6XqcanvrZJ5s2Qiv4tsmm2UMy": { + "name" : "Binance Pool", + "link" : "https://pool.binance.com/" + }, "34Jpa4Eu3ApoPVUKNTN2WeuXVVq1jzxgPi": { "name" : "Lubian.com", "link" : "http://www.lubian.com/" @@ -1173,6 +1225,14 @@ "3CLigLYNkrtoNgNcUwTaKoUSHCwr9W851W": { "name": "Rawpool", "link": "https://www.rawpool.com" + }, + "bc1qf274x7penhcd8hsv3jcmwa5xxzjl2a6pa9pxwm": { + "name" : "F2Pool", + "link" : "https://www.f2pool.com/" + }, + "1A32KFEX7JNPmU1PVjrtiXRrTQcesT3Nf1": { + "name": "MARA Pool", + "link": "https://marapool.com" } } -} +} \ No newline at end of file diff --git a/frontend/cypress/integration/bisq/bisq.spec.ts b/frontend/cypress/integration/bisq/bisq.spec.ts index bafca218a..bc9e1e3b9 100644 --- a/frontend/cypress/integration/bisq/bisq.spec.ts +++ b/frontend/cypress/integration/bisq/bisq.spec.ts @@ -1,87 +1,163 @@ describe('Bisq', () => { - const baseModule = Cypress.env("BASE_MODULE"); - const basePath = ''; - - beforeEach(() => { - cy.intercept('/sockjs-node/info*').as('socket'); - cy.intercept('/bisq/api/markets/hloc?market=btc_usd&interval=day').as('hloc'); - cy.intercept('/bisq/api/markets/ticker').as('ticker'); - cy.intercept('/bisq/api/markets/markets').as('markets'); - cy.intercept('/bisq/api/markets/volumes/7d').as('7d'); - cy.intercept('/bisq/api/markets/trades?market=all').as('trades'); - cy.intercept('/bisq/api/txs/*/*').as('txs'); - cy.intercept('/bisq/api/blocks/*/*').as('blocks'); - cy.intercept('/bisq/api/stats').as('stats'); - - Cypress.Commands.add('waitForDashboard', () => { - cy.wait('@socket'); - cy.wait('@hloc'); - cy.wait('@ticker'); - cy.wait('@markets'); - cy.wait('@7d'); - cy.wait('@trades'); - }); + const baseModule = Cypress.env("BASE_MODULE"); + const basePath = ''; + + beforeEach(() => { + cy.intercept('/sockjs-node/info*').as('socket'); + cy.intercept('/bisq/api/markets/hloc?market=btc_usd&interval=day').as('hloc'); + cy.intercept('/bisq/api/markets/ticker').as('ticker'); + cy.intercept('/bisq/api/markets/markets').as('markets'); + cy.intercept('/bisq/api/markets/volumes/7d').as('7d'); + cy.intercept('/bisq/api/markets/trades?market=all').as('trades'); + cy.intercept('/bisq/api/txs/*/*').as('txs'); + cy.intercept('/bisq/api/blocks/*/*').as('blocks'); + cy.intercept('/bisq/api/stats').as('stats'); + }); + + if (baseModule === 'bisq') { + it('loads the dashboard', () => { + cy.visit(`${basePath}`); + cy.waitForSkeletonGone(); }); - if (baseModule === 'bisq') { - - it('loads the dashboard', () => { - cy.visit(`${basePath}`); - cy.waitForSkeletonGone(); + describe("transactions", () => { + it('loads the transactions screen', () => { + cy.visit(`${basePath}`); + cy.waitForSkeletonGone(); + cy.get('#btn-transactions').click().then(() => { + cy.get('.table > tr').should('have.length', 50); }); + }); - it('loads the transactions screen', () => { - cy.visit(`${basePath}`); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(2) > a').click().then(() => { - cy.get('.table > tr').should('have.length', 50); - }); + const filters = [ + "Asset listing fee", "Blind vote", "Compensation request", + "Genesis", "Irregular", "Lockup", "Pay trade fee", "Proof of burn", + "Proposal", "Reimbursement request", "Transfer BSQ", "Unlock", "Vote reveal" + ]; + filters.forEach((filter) => { + it(`filters the transaction screen by ${filter}`, () => { + cy.visit(`${basePath}/transactions`); + cy.waitForSkeletonGone(); + cy.get('#filter').click(); + cy.contains(filter).find('input').click(); + //TODO: change this waiter + cy.wait(1000); + cy.get('td:nth-of-type(2)').each(($td) => { + expect($td.text().trim()).to.eq(filter); + }); }); + }); - it('loads the blocks screen', () => { - cy.visit(`${basePath}`); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(3) > a').click().then(() => { - cy.wait('@blocks'); - cy.get('tbody tr').should('have.length', 10); - }); + it("filters using multiple criteria", () => { + const filters = ['Proposal', 'Lockup', 'Unlock']; + cy.visit(`${basePath}/transactions`); + cy.waitForSkeletonGone(); + cy.get('#filter').click(); + filters.forEach((filter) => { + cy.contains(filter).find('input').click(); + //TODO: change this waiter + cy.wait(1000); }); + cy.get('td:nth-of-type(2)').each(($td) => { + const regex = new RegExp(`${filters.join('|')}`, 'g'); + expect($td.text().trim()).to.match(regex); + }); + }); - it('loads the stats screen', () => { - cy.visit(`${basePath}`); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(4) > a').click().then(() => { - cy.wait('@stats'); - }); - }); + const transactions = [ + { type: 'Asset listing fee', txid: '3548aa0c002b015ea700072b7d7d76d45d4f10a3573804d0d2f624c0bb255b6b' }, + { type: 'Blind vote', txid: 'f8fabb95efa1bb81325e4c961b9fc7e3508a9b9ecd4eddf1400e58867eff8d92' }, + { type: 'Compensation request', txid: 'a8cdc65fe6bb8730f5f89f99f779d0469b0a493e1ae570e20eb7afda696a18a9' }, + { type: 'Genesis', txid: '4b5417ec5ab6112bedf539c3b4f5a806ed539542d8b717e1c4470aa3180edce5' }, + { type: 'Irregular', txid: '90b06684a517388fec2237e2362a29810dc82f0e13e019c84747ec27051e6c53' }, + { type: 'Lockup', txid: '365425b3b7487229e2ba598fb8f2a9e359e3351620383e5018548649a28b78c4' }, + { type: 'Pay trade fee', txid: 'a66b30e9777e16572ab36723539df8f45bd5d8130d810b2c3d75b8c02a191eaf' }, + { type: 'Proof of burn', txid: '8325ccb87065fb9243ed9ff1cbb431fc2ac5060a60433bcde474ccbd97b76dcb' }, + { type: 'Proposal', txid: '34e2a20f045c82fbcf7cb191b42dea6fba45641777e1751ffb29d3981c4bf413' }, + { type: 'Reimbursement request', txid: '04c16c79ca6b9ec9978880024b0d0ad3100020f33286b63c85ca7b1a319421ae' }, + { type: 'Transfer BSQ', txid: '64500bd9220675ad30d5ace27de95a341a498d7eda08162ee0ce7feb8c56cb14' }, + { type: 'Unlock', txid: '5a756841bbb11137d15b0082a3fcadbe102791f41a95d661d3bd0c5ad0b3b1a3' }, + { type: 'Vote reveal', txid: 'bd7daae1d4af8837db5e47d7bd9d8b9f83dcfd35d112f85e90728b9be45191f7' } + ]; - it('loads the api screen', () => { - cy.visit(`${basePath}`); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(5) > a').click().then(() => { - cy.get('.section-header').should('have.length.at.least', 1); - cy.get('.endpoint-container').should('have.length.at.least', 1); - }); + transactions.forEach((transaction) => { + it(`loads a "${transaction.type}" transaction`, () => { + cy.visit(`${basePath}/tx/${transaction.txid}`); + cy.waitForSkeletonGone(); }); + }); + }); - it('shows blocks pagination with 5 pages (desktop)', () => { - cy.viewport(760, 800); - cy.visit(`${basePath}/blocks`); - cy.waitForSkeletonGone(); - cy.get('tbody tr').should('have.length', 10); - // 5 pages + 4 buttons = 9 buttons - cy.get('.pagination-container ul.pagination').first().children().should('have.length', 9); + describe('blocks', () => { + it('loads the blocks screen', () => { + cy.visit(`${basePath}`); + cy.waitForSkeletonGone(); + cy.get('#btn-blocks').click().then(() => { + cy.wait('@blocks'); + cy.get('tbody tr').should('have.length', 10); }); + }); - it('shows blocks pagination with 3 pages (mobile)', () => { - cy.viewport(669, 800); - cy.visit(`${basePath}/blocks`); - cy.waitForSkeletonGone(); - cy.get('tbody tr').should('have.length', 10); - // 3 pages + 4 buttons = 7 buttons - cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7); - }); - } else { - it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`); - } - }); + it('loads a specific block', () => { + cy.visit(`${basePath}/block/0000000000000000000137ef33faa63bc6e809ab30932cf77d454fb36d2bd83a`); + cy.waitForSkeletonGone(); + }); + + }); + + describe('markets', () => { + it('loads the markets screen', () => { + cy.visit(`${basePath}/markets`); + cy.waitForSkeletonGone(); + }); + + it('loads a specific market', () => { + cy.visit(`${basePath}/market/btc_eur`); + cy.waitForSkeletonGone(); + //Buy Offers + cy.get('.row > :nth-child(1) td').should('have.length.at.least', 1); + //Sell offers + cy.get('.row > :nth-child(1) td').should('have.length.at.least', 1); + //Trades + cy.get('app-bisq-trades > .table-container td').should('have.length.at.least', 1); + }); + }); + + it('loads the stats screen', () => { + cy.visit(`${basePath}`); + cy.waitForSkeletonGone(); + cy.get('#btn-stats').click().then(() => { + cy.wait('@stats'); + }); + }); + + it('loads the api screen', () => { + cy.visit(`${basePath}`); + cy.waitForSkeletonGone(); + cy.get('#btn-docs').click().then(() => { + cy.get('.section-header').should('have.length.at.least', 1); + cy.get('.endpoint-container').should('have.length.at.least', 1); + }); + }); + + it('shows blocks pagination with 5 pages (desktop)', () => { + cy.viewport(760, 800); + cy.visit(`${basePath}/blocks`); + cy.waitForSkeletonGone(); + cy.get('tbody tr').should('have.length', 10); + // 5 pages + 4 buttons = 9 buttons + cy.get('.pagination-container ul.pagination').first().children().should('have.length', 9); + }); + + it('shows blocks pagination with 3 pages (mobile)', () => { + cy.viewport(669, 800); + cy.visit(`${basePath}/blocks`); + cy.waitForSkeletonGone(); + cy.get('tbody tr').should('have.length', 10); + // 3 pages + 4 buttons = 7 buttons + cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7); + }); + } else { + it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`); + } +}); diff --git a/frontend/cypress/integration/liquid/liquid.spec.ts b/frontend/cypress/integration/liquid/liquid.spec.ts index 5661340f4..af76314a1 100644 --- a/frontend/cypress/integration/liquid/liquid.spec.ts +++ b/frontend/cypress/integration/liquid/liquid.spec.ts @@ -1,201 +1,214 @@ describe('Liquid', () => { - const baseModule = Cypress.env("BASE_MODULE"); - const basePath = ''; + const baseModule = Cypress.env("BASE_MODULE"); + const basePath = ''; - beforeEach(() => { - cy.intercept('/liquid/api/block/**').as('block'); - cy.intercept('/liquid/api/blocks/').as('blocks'); - cy.intercept('/liquid/api/tx/**/outspends').as('outspends'); - cy.intercept('/liquid/api/block/**/txs/**').as('block-txs'); - cy.intercept('/resources/pools.json').as('pools'); + beforeEach(() => { + cy.intercept('/liquid/api/block/**').as('block'); + cy.intercept('/liquid/api/blocks/').as('blocks'); + cy.intercept('/liquid/api/tx/**/outspends').as('outspends'); + cy.intercept('/liquid/api/block/**/txs/**').as('block-txs'); + cy.intercept('/resources/pools.json').as('pools'); - Cypress.Commands.add('waitForBlockData', () => { - cy.wait('@socket'); - cy.wait('@block'); - cy.wait('@outspends'); - }); + Cypress.Commands.add('waitForBlockData', () => { + cy.wait('@socket'); + cy.wait('@block'); + cy.wait('@outspends'); + }); + }); + + if (baseModule === 'liquid') { + + it('check first mempool block after skeleton loads', () => { + cy.visit(`${basePath}`); + cy.waitForSkeletonGone(); + cy.get('#mempool-block-0 > .blockLink').should('exist'); }); - if (baseModule === 'liquid') { + it('loads the dashboard', () => { + cy.visit(`${basePath}`); + cy.waitForSkeletonGone(); + }); - it('check first mempool block after skeleton loads', () => { - cy.visit(`${basePath}`); - cy.waitForSkeletonGone(); - cy.get('#mempool-block-0 > .blockLink').should('exist'); + it('loads the blocks page', () => { + cy.visit(`${basePath}`); + cy.get('#btn-blocks').click().then(() => { + cy.wait(1000); + }); + cy.waitForSkeletonGone(); + }); + + it('loads a specific block page', () => { + cy.visit(`${basePath}/block/7e1369a23a5ab861e7bdede2aadcccae4ea873ffd9caf11c7c5541eb5bcdff54`); + cy.waitForSkeletonGone(); + }); + + it('loads the graphs page', () => { + cy.visit(`${basePath}`); + cy.waitForSkeletonGone(); + cy.get('#btn-graphs').click().then(() => { + cy.wait(1000); + }); + }); + + it('loads the tv page - desktop', () => { + cy.visit(`${basePath}/tv`); + cy.waitForSkeletonGone(); + }); + + it('loads the graphs page - mobile', () => { + cy.visit(`${basePath}`) + cy.waitForSkeletonGone(); + cy.get('#btn-graphs').click().then(() => { + cy.viewport('iphone-6'); + cy.wait(1000); + cy.get('.tv-only').should('not.exist'); + }); + }); + + it('renders unconfidential addresses correctly on mobile', () => { + cy.viewport('iphone-6'); + cy.visit(`${basePath}/address/ex1qqmmjdwrlg59c8q4l75sj6wedjx57tj5grt8pat`); + cy.waitForSkeletonGone(); + //TODO: Add proper IDs for these selectors + const firstRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(1) > .table > tbody'; + const thirdRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(3)'; + cy.get(firstRowSelector).invoke('css', 'width').then(firstRowWidth => { + cy.get(thirdRowSelector).invoke('css', 'width').then(thirdRowWidth => { + expect(parseInt(firstRowWidth)).to.be.lessThan(parseInt(thirdRowWidth)); }); + }); + }); - it('loads the dashboard', () => { - cy.visit(`${basePath}`); - cy.waitForSkeletonGone(); + describe('peg in/peg out', () => { + it('loads peg in addresses', () => { + cy.visit(`${basePath}/tx/fe764f7bedfc2a37b29d9c8aef67d64a57d253a6b11c5a55555cfd5826483a58`); + cy.waitForSkeletonGone(); + //TODO: Change to an element id so we don't assert on a string + cy.get('#table-tx-vin').should('contain', 'Peg-in'); + cy.get('#table-tx-vin a').click().then(() => { + cy.waitForSkeletonGone(); + if (baseModule === 'liquid') { + cy.url().should('eq', 'https://mempool.space/tx/f148c0d854db4174ea420655235f910543f0ec3680566dcfdf84fb0a1697b592'); + } else { + //TODO: Use an environment variable to get the hostname + cy.url().should('eq', 'http://localhost:4200/tx/f148c0d854db4174ea420655235f910543f0ec3680566dcfdf84fb0a1697b592'); + } }); + }); - it('loads the blocks page', () => { - cy.visit(`${basePath}/blocks`); - cy.waitForSkeletonGone(); + it('loads peg out addresses', () => { + cy.visit(`${basePath}/tx/ecf6eba04ffb3946faa172343c87162df76f1a57b07b0d6dc6ad956b13376dc8`); + cy.waitForSkeletonGone(); + cy.get('#table-tx-vout a').first().click().then(() => { + cy.waitForSkeletonGone(); + if (baseModule === 'liquid') { + cy.url().should('eq', 'https://mempool.space/address/1BxoGcMg14oaH3CwHD2hF4gU9VcfgX5yoR'); + } else { + //TODO: Use an environment variable to get the hostname + cy.url().should('eq', 'http://localhost:4200/address/1BxoGcMg14oaH3CwHD2hF4gU9VcfgX5yoR'); + } + //TODO: Add a custom class so we don't assert on a string + cy.get('.badge').should('contain', 'Liquid Peg Out'); }); + }); + }); - it('loads a specific block page', () => { - cy.visit(`${basePath}/block/7e1369a23a5ab861e7bdede2aadcccae4ea873ffd9caf11c7c5541eb5bcdff54`); - cy.waitForSkeletonGone(); + describe('assets', () => { + it('shows the assets screen', () => { + cy.visit(`${basePath}`); + cy.get('#btn-assets'); + cy.waitForSkeletonGone(); + cy.get('table tr').should('have.length.at.least', 5); + }); + + it('allows searching assets', () => { + cy.visit(`${basePath}/assets`); + cy.waitForSkeletonGone(); + cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => { + cy.get('table tr').should('have.length', 1); }); + }); - it('loads the graphs page', () => { - cy.visit(`${basePath}/graphs`); - cy.waitForSkeletonGone(); - }); - - it('loads the tv page - desktop', () => { - cy.visit(`${basePath}`); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(3) > a').click().then(() => { - cy.wait(1000); - }); - }); - - it('loads the graphs page - mobile', () => { - cy.visit(`${basePath}`) - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(3) > a').click().then(() => { - cy.viewport('iphone-6'); - cy.wait(1000); - cy.get('.tv-only').should('not.exist'); - }); - }); - - it('renders unconfidential addresses correctly on mobile', () => { - cy.viewport('iphone-6'); - cy.visit(`${basePath}/address/ex1qqmmjdwrlg59c8q4l75sj6wedjx57tj5grt8pat`); - cy.waitForSkeletonGone(); - //TODO: Add proper IDs for these selectors - const firstRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(1) > .table > tbody'; - const thirdRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(3)'; - cy.get(firstRowSelector).invoke('css', 'width').then(firstRowWidth => { - cy.get(thirdRowSelector).invoke('css', 'width').then(thirdRowWidth => { - expect(parseInt(firstRowWidth)).to.be.lessThan(parseInt(thirdRowWidth)); - }); - }); - }); - - describe('peg in/peg out', () => { - it('loads peg in addresses', () => { - cy.visit(`${basePath}/tx/fe764f7bedfc2a37b29d9c8aef67d64a57d253a6b11c5a55555cfd5826483a58`); - cy.waitForSkeletonGone(); - //TODO: Change to an element id so we don't assert on a string - cy.get('#table-tx-vin').should('contain', 'Peg-in'); - cy.get('#table-tx-vin a').click().then(() => { - cy.waitForSkeletonGone(); - if (baseModule === 'liquid') { - cy.url().should('eq', 'https://mempool.space/tx/f148c0d854db4174ea420655235f910543f0ec3680566dcfdf84fb0a1697b592'); - } else { - //TODO: Use an environment variable to get the hostname - cy.url().should('eq', 'http://localhost:4200/tx/f148c0d854db4174ea420655235f910543f0ec3680566dcfdf84fb0a1697b592'); - } - }); - }); - - it('loads peg out addresses', () => { - cy.visit(`${basePath}/tx/ecf6eba04ffb3946faa172343c87162df76f1a57b07b0d6dc6ad956b13376dc8`); - cy.waitForSkeletonGone(); - cy.get('#table-tx-vout a').first().click().then(() => { - cy.waitForSkeletonGone(); - if (baseModule === 'liquid') { - cy.url().should('eq', 'https://mempool.space/address/1BxoGcMg14oaH3CwHD2hF4gU9VcfgX5yoR'); - } else { - //TODO: Use an environment variable to get the hostname - cy.url().should('eq', 'http://localhost:4200/address/1BxoGcMg14oaH3CwHD2hF4gU9VcfgX5yoR'); - } - //TODO: Add a custom class so we don't assert on a string - cy.get('.badge').should('contain','Liquid Peg Out'); - }); - }); - }); - - describe('assets', () => { - it('shows the assets screen', () => { - cy.visit(`${basePath}/assets`); - cy.waitForSkeletonGone(); - cy.get('table tr').should('have.length.at.least', 5); - }); - - it('allows searching assets', () => { - cy.visit(`${basePath}/assets`); - cy.waitForSkeletonGone(); - cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => { - cy.get('table tr').should('have.length', 1); - }); - }); - - it('shows a specific asset ID', () => { - cy.visit(`${basePath}/assets`); - cy.waitForSkeletonGone(); - cy.get('.container-xl input').click().type('Liquid AUD').then(() => { - cy.get('table tr td:nth-of-type(1) a').click(); - }); - }); + it('shows a specific asset ID', () => { + cy.visit(`${basePath}/assets`); + cy.waitForSkeletonGone(); + cy.get('.container-xl input').click().type('Liquid AUD').then(() => { + cy.get('table tr td:nth-of-type(1) a').click(); }); + }); + }); - describe('unblinded TX', () => { + describe('unblinded TX', () => { - it('should not show an unblinding error message for regular txs', () => { - cy.visit(`${basePath}/tx/82a479043ec3841e0d3f829afc8df4f0e2bbd675a13f013ea611b2fde0027d45`); - cy.waitForSkeletonGone(); - cy.get('.error-unblinded' ).should('not.exist'); - }); + it('should not show an unblinding error message for regular txs', () => { + cy.visit(`${basePath}/tx/82a479043ec3841e0d3f829afc8df4f0e2bbd675a13f013ea611b2fde0027d45`); + cy.waitForSkeletonGone(); + cy.get('.error-unblinded').should('not.exist'); + }); - it('show unblinded TX', () => { - cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a`); - cy.waitForSkeletonGone(); - cy.get('#table-tx-vin tr').should('have.class', 'assetBox'); - cy.get('#table-tx-vout tr').should('have.class', 'assetBox'); - }); + it('show unblinded TX', () => { + cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a`); + cy.waitForSkeletonGone(); + cy.get('#table-tx-vin tr:nth-child(1) .amount').should('contain.text', '0.02465000 L-BTC'); + cy.get('#table-tx-vin tr').should('have.class', 'assetBox'); + cy.get('#table-tx-vout tr:nth-child(1) .amount').should('contain.text', '0.00100000 L-BTC'); + cy.get('#table-tx-vout tr:nth-child(2) .amount').should('contain.text', '0.02364760 L-BTC'); + cy.get('#table-tx-vout tr').should('have.class', 'assetBox'); + }); - it('show empty unblinded TX', () => { - cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=`); - cy.waitForSkeletonGone(); - cy.get('#table-tx-vin tr').should('have.class', ''); - cy.get('#table-tx-vout tr').should('have.class', ''); - }); + it('show empty unblinded TX', () => { + cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=`); + cy.waitForSkeletonGone(); + cy.get('#table-tx-vin tr:nth-child(1)').should('have.class', ''); + cy.get('#table-tx-vin tr:nth-child(1) .amount').should('contain.text', 'Confidential'); + cy.get('#table-tx-vout tr:nth-child(1)').should('have.class', ''); + cy.get('#table-tx-vout tr:nth-child(2)').should('have.class', ''); + cy.get('#table-tx-vout tr:nth-child(1) .amount').should('contain.text', 'Confidential'); + cy.get('#table-tx-vout tr:nth-child(2) .amount').should('contain.text', 'Confidential'); + }); - it('show invalid unblinded TX hex', () => { - cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=123`); - cy.waitForSkeletonGone(); - cy.get('#table-tx-vin tr').should('have.class', ''); - cy.get('#table-tx-vout tr').should('have.class', ''); - cy.get('.error-unblinded' ).contains('Error: Invalid blinding data (invalid hex)'); - }); + it('show invalid unblinded TX hex', () => { + cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=123`); + cy.waitForSkeletonGone(); + cy.get('#table-tx-vin tr').should('have.class', ''); + cy.get('#table-tx-vout tr').should('have.class', ''); + cy.get('.error-unblinded').contains('Error: Invalid blinding data (invalid hex)'); + }); - it('show first unblinded vout', () => { - cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc`); - cy.waitForSkeletonGone(); - cy.get('#table-tx-vout tr:first-child()').should('have.class', 'assetBox'); - }); + it('show first unblinded vout', () => { + cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc`); + cy.waitForSkeletonGone(); + cy.get('#table-tx-vout tr:nth-child(1)').should('have.class', 'assetBox'); + cy.get('#table-tx-vout tr:nth-child(1) .amount').should('contain.text', '0.00100000 L-BTC'); + }); - it('show second unblinded vout', () => { - cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a`); - cy.get('#table-tx-vout tr').should('have.class', 'assetBox'); - }); + it('show second unblinded vout', () => { + cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a`); + cy.get('#table-tx-vout tr:nth-child(2').should('have.class', 'assetBox'); + cy.get('#table-tx-vout tr:nth-child(2) .amount').should('contain.text', '0.02364760 L-BTC'); + }); - it('show invalid error unblinded TX', () => { - cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3c`); - cy.waitForSkeletonGone(); - cy.get('#table-tx-vout tr').should('have.class', 'assetBox'); - cy.get('.error-unblinded' ).contains('Error: Invalid blinding data.'); - }); + it('show invalid error unblinded TX', () => { + cy.visit(`${basePath}/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3c`); + cy.waitForSkeletonGone(); + cy.get('#table-tx-vout tr').should('have.class', 'assetBox'); + cy.get('.error-unblinded').contains('Error: Invalid blinding data.'); + }); - it('shows asset peg in/out and burn transactions', () => { - cy.visit(`${basePath}/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`); - cy.waitForSkeletonGone(); - cy.get('#table-tx-vout tr').not('.assetBox'); - cy.get('#table-tx-vin tr').not('.assetBox'); - }); + it('shows asset peg in/out and burn transactions', () => { + cy.visit(`${basePath}/asset/6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d`); + cy.waitForSkeletonGone(); + cy.get('#table-tx-vout tr').not('.assetBox'); + cy.get('#table-tx-vin tr').not('.assetBox'); + }); - it('prevents regressing issue #644', () => { - cy.visit(`${basePath}/tx/393b890966f305e7c440fcfb12a13f51a7a9011cc59ff5f14f6f93214261bd82`); - cy.waitForSkeletonGone(); - }); - }); - } else { - it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`); - } + it('prevents regressing issue #644', () => { + cy.visit(`${basePath}/tx/393b890966f305e7c440fcfb12a13f51a7a9011cc59ff5f14f6f93214261bd82`); + cy.waitForSkeletonGone(); + }); + }); + } else { + it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`); + } }); diff --git a/frontend/cypress/integration/liquidtestnet/liquidtestnet.spec.ts b/frontend/cypress/integration/liquidtestnet/liquidtestnet.spec.ts new file mode 100644 index 000000000..eb75be773 --- /dev/null +++ b/frontend/cypress/integration/liquidtestnet/liquidtestnet.spec.ts @@ -0,0 +1,175 @@ +describe('Liquid Testnet', () => { + const baseModule = Cypress.env("BASE_MODULE"); + const basePath = '/testnet'; + + beforeEach(() => { + cy.intercept('/liquidtestnet/api/block/**').as('block'); + cy.intercept('/liquidtestnet/api/blocks/').as('blocks'); + cy.intercept('/liquidtestnet/api/tx/**/outspends').as('outspends'); + cy.intercept('/liquidtestnet/api/block/**/txs/**').as('block-txs'); + cy.intercept('/resources/pools.json').as('pools'); + + Cypress.Commands.add('waitForBlockData', () => { + cy.wait('@socket'); + cy.wait('@block'); + cy.wait('@outspends'); + }); + }); + + if (baseModule === 'liquid') { + + it('check first mempool block after skeleton loads', () => { + cy.visit(`${basePath}`); + cy.waitForSkeletonGone(); + cy.get('#mempool-block-0 > .blockLink').should('exist'); + }); + + it('loads the dashboard', () => { + cy.visit(`${basePath}`); + cy.waitForSkeletonGone(); + }); + + it('loads the blocks page', () => { + cy.visit(`${basePath}`) + cy.get('#btn-blocks'); + cy.waitForSkeletonGone(); + }); + + it('loads a specific block page', () => { + cy.visit(`${basePath}/block/7e1369a23a5ab861e7bdede2aadcccae4ea873ffd9caf11c7c5541eb5bcdff54`); + cy.waitForSkeletonGone(); + }); + + it('loads the graphs page', () => { + cy.visit(`${basePath}`); + cy.get('#btn-graphs'); + cy.waitForSkeletonGone(); + }); + + it('loads the tv page - desktop', () => { + cy.visit(`${basePath}/tv`); + cy.waitForSkeletonGone(); + }); + + it('loads the graphs page - mobile', () => { + cy.visit(`${basePath}`) + cy.waitForSkeletonGone(); + cy.viewport('iphone-6'); + cy.get('.tv-only').should('not.exist'); + }); + + it.skip('renders unconfidential addresses correctly on mobile', () => { + cy.viewport('iphone-6'); + cy.visit(`${basePath}/address/__TODO__`); + cy.waitForSkeletonGone(); + //TODO: Add proper IDs for these selectors + const firstRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(1) > .table > tbody'; + const thirdRowSelector = '.container-xl > :nth-child(3) > div > :nth-child(3)'; + cy.get(firstRowSelector).invoke('css', 'width').then(firstRowWidth => { + cy.get(thirdRowSelector).invoke('css', 'width').then(thirdRowWidth => { + expect(parseInt(firstRowWidth)).to.be.lessThan(parseInt(thirdRowWidth)); + }); + }); + }); + + describe('assets', () => { + it('shows the assets screen', () => { + cy.visit(`${basePath}/assets`); + cy.waitForSkeletonGone(); + cy.get('table tr').should('have.length.at.least', 5); + }); + + it('allows searching assets', () => { + cy.visit(`${basePath}/assets`); + cy.waitForSkeletonGone(); + cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => { + cy.get('table tr').should('have.length', 1); + }); + }); + + it('shows a specific asset ID', () => { + cy.visit(`${basePath}/assets`); + cy.waitForSkeletonGone(); + cy.get('.container-xl input').click().type('Liquid CAD').then(() => { + cy.get('table tr td:nth-of-type(1) a').click(); + }); + }); + }); + + describe('unblinded TX', () => { + it('should not show an unblinding error message for regular txs', () => { + cy.visit(`${basePath}/tx/82a479043ec3841e0d3f829afc8df4f0e2bbd675a13f013ea611b2fde0027d45`); + cy.waitForSkeletonGone(); + cy.get('.error-unblinded').should('not.exist'); + }); + + it('show unblinded TX', () => { + cy.visit(`${basePath}/tx/c3d908ab77891e4c569b0df71aae90f4720b157019ebb20db176f4f9c4d626b8#blinded=100000,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,df290ead654d7d110ebc5aaf0bcf11d5b5d360431a467f1cde0a856fde986893,33cb3a2fd2e76643843691cf44a78c5cd28ec652a414da752160ad63fbd37bc9,49741,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,edb0713bcbfcb3daabf601cb50978439667d208e15fed8a5ebbfea5696cda1d5,4de70115501e8c7d6bd763e229bf42781edeacf6e75e1d7bdfa4c63104bc508a`); + cy.waitForSkeletonGone(); + cy.get('#table-tx-vin tr:nth-child(1) .amount').should('contain.text', '0.00100000 tL-BTC'); + cy.get('#table-tx-vin tr').should('have.class', 'assetBox'); + cy.get('#table-tx-vout tr:nth-child(1) .amount').should('contain.text', '0.00050000 tL-BTC'); + cy.get('#table-tx-vout tr:nth-child(2) .amount').should('contain.text', '0.00049741 tL-BTC'); + cy.get('#table-tx-vout tr').should('have.class', 'assetBox'); + }); + + it('show empty unblinded TX', () => { + cy.visit(`${basePath}/tx/c3d908ab77891e4c569b0df71aae90f4720b157019ebb20db176f4f9c4d626b8#blinded=`); + cy.waitForSkeletonGone(); + cy.get('#table-tx-vin tr:nth-child(1)').should('have.class', ''); + cy.get('#table-tx-vin tr:nth-child(1) .amount').should('contain.text', 'Confidential'); + cy.get('#table-tx-vout tr:nth-child(1)').should('have.class', ''); + cy.get('#table-tx-vout tr:nth-child(2)').should('have.class', ''); + cy.get('#table-tx-vout tr:nth-child(1) .amount').should('contain.text', 'Confidential'); + cy.get('#table-tx-vout tr:nth-child(2) .amount').should('contain.text', 'Confidential'); + }); + + it('show invalid unblinded TX hex', () => { + cy.visit(`${basePath}/tx/2477f220eef1d03f8ffa4a2861c275d155c3562adf0d79523aeeb0c59ee611ba#blinded=5000`); + cy.waitForSkeletonGone(); + cy.get('#table-tx-vin tr').should('have.class', ''); + cy.get('#table-tx-vout tr').should('have.class', ''); + cy.get('.error-unblinded').contains('Error: Invalid blinding data (invalid hex)'); + }); + + it('show first unblinded vout', () => { + cy.visit(`${basePath}/tx/0877bc0c7aa5c2b8d0e4b15450425879b8783c40e341806037a605ef836fb886#blinded=5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,328de54e90e867a9154b4f1eb7fcab86267e880fa2ee9e53b41a91e61dab86e6,8885831e6b089eaf06889d53a24843f0da533d300a7b1527b136883a6819f3ae,5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,aca78b953615d69ae0ae68c4c5c3c0ee077c10bc20ad3f0c5960706004e6cb56,d2ec175afe5f761e2dbd443faf46abbb7091f341deb3387e5787d812bdb2df9f,100000,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,4b54a4ca809b3844f34dd88b68617c4c866d92a02211f02ba355755bac20a1c6,eddd02e92b0cfbad8cab89828570a50f2c643bb2a54d886c86e25ce47e818685,99729,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,8b86d565c9549eb0352bb81ee576d01d064435b64fddcc045decebeb1d9913ce,b082ce3448d40d47b5b39f15d72b285f4a1046b636b56c25f32f498ece29d062,10000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,62b04ee86198d6b41681cdd0acb450ab366af727a010aaee8ba0b9e69ff43896,3f98429bca9b538dc943c22111f25d9c4448d45a63ff0f4e58b22fd434c0365e`); + cy.waitForSkeletonGone(); + cy.get('#table-tx-vout tr:nth-child(1)').should('have.class', 'assetBox'); + cy.get('#table-tx-vout tr:nth-child(1) .amount').should('contain.text', '0.00099729 tL-BTC'); + }); + + it('show second unblinded vout (asset)', () => { + cy.visit(`${basePath}/tx/0877bc0c7aa5c2b8d0e4b15450425879b8783c40e341806037a605ef836fb886#blinded=5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,328de54e90e867a9154b4f1eb7fcab86267e880fa2ee9e53b41a91e61dab86e6,8885831e6b089eaf06889d53a24843f0da533d300a7b1527b136883a6819f3ae,5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,aca78b953615d69ae0ae68c4c5c3c0ee077c10bc20ad3f0c5960706004e6cb56,d2ec175afe5f761e2dbd443faf46abbb7091f341deb3387e5787d812bdb2df9f,100000,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,4b54a4ca809b3844f34dd88b68617c4c866d92a02211f02ba355755bac20a1c6,eddd02e92b0cfbad8cab89828570a50f2c643bb2a54d886c86e25ce47e818685,99729,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,8b86d565c9549eb0352bb81ee576d01d064435b64fddcc045decebeb1d9913ce,b082ce3448d40d47b5b39f15d72b285f4a1046b636b56c25f32f498ece29d062,10000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,62b04ee86198d6b41681cdd0acb450ab366af727a010aaee8ba0b9e69ff43896,3f98429bca9b538dc943c22111f25d9c4448d45a63ff0f4e58b22fd434c0365e`); + cy.get('#table-tx-vout tr:nth-child(2)').should('have.class', 'assetBox'); + //TODO Update after the precision bug fix is merged + cy.get('#table-tx-vout tr:nth-child(2) .amount').should('contain.text', '0 TEST'); + }); + + it('should link to the asset page from the unblinded tx', () => { + cy.visit(`${basePath}/tx/0877bc0c7aa5c2b8d0e4b15450425879b8783c40e341806037a605ef836fb886#blinded=5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,328de54e90e867a9154b4f1eb7fcab86267e880fa2ee9e53b41a91e61dab86e6,8885831e6b089eaf06889d53a24843f0da533d300a7b1527b136883a6819f3ae,5000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,aca78b953615d69ae0ae68c4c5c3c0ee077c10bc20ad3f0c5960706004e6cb56,d2ec175afe5f761e2dbd443faf46abbb7091f341deb3387e5787d812bdb2df9f,100000,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,4b54a4ca809b3844f34dd88b68617c4c866d92a02211f02ba355755bac20a1c6,eddd02e92b0cfbad8cab89828570a50f2c643bb2a54d886c86e25ce47e818685,99729,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,8b86d565c9549eb0352bb81ee576d01d064435b64fddcc045decebeb1d9913ce,b082ce3448d40d47b5b39f15d72b285f4a1046b636b56c25f32f498ece29d062,10000,38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5,62b04ee86198d6b41681cdd0acb450ab366af727a010aaee8ba0b9e69ff43896,3f98429bca9b538dc943c22111f25d9c4448d45a63ff0f4e58b22fd434c0365e`); + cy.get('#table-tx-vout tr:nth-child(2) .amount a').click().then(() => { + cy.waitForSkeletonGone(); + cy.url().should('contain', '/asset/38fca2d939696061a8f76d4e6b5eecd54e3b4221c846f24a6b279e79952850a5'); + }); + }); + + it('show invalid error unblinded TX', () => { + cy.visit(`${basePath}/tx/c3d908ab77891e4c569b0df71aae90f4720b157019ebb20db176f4f9c4d626b8#blinded=100000,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,df290ead654d7d110ebc5aaf0bcf11d5b5d360431a467f1cde0a856fde986893,33cb3a2fd2e76643843691cf44a78c5cd28ec652a414da752160ad63fbd37bc9,49741,144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49,edb0713bcbfcb3daabf601cb50978439667d208e15fed8a5ebbfea5696cda1d5,4de70115501e8c7d6bd763e229bf42781edeacf6e75e1d7bdfa4c63104bc508c`); + cy.waitForSkeletonGone(); + cy.get('#table-tx-vin tr').should('have.class', 'assetBox'); + cy.get('.error-unblinded').contains('Error: Invalid blinding data.'); + }); + + it('shows asset peg in/out and burn transactions', () => { + cy.visit(`${basePath}/asset/ac3e0ff248c5051ffd61e00155b7122e5ebc04fd397a0ecbdd4f4e4a56232926`); + cy.waitForSkeletonGone(); + cy.get('#table-tx-vout tr').not('.assetBox'); + cy.get('#table-tx-vin tr').not('.assetBox'); + }); + + }); + } else { + it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`); + } +}); diff --git a/frontend/cypress/integration/mainnet/mainnet.spec.ts b/frontend/cypress/integration/mainnet/mainnet.spec.ts index 77095781a..752617092 100644 --- a/frontend/cypress/integration/mainnet/mainnet.spec.ts +++ b/frontend/cypress/integration/mainnet/mainnet.spec.ts @@ -12,18 +12,18 @@ const baseModule = Cypress.env("BASE_MODULE"); * @returns {boolean} */ const areOverlapping = (rect1, rect2) => { - // if one rectangle is on the left side of the other - if (rect1.right < rect2.left || rect2.right < rect1.left) { - return false - } + // if one rectangle is on the left side of the other + if (rect1.right < rect2.left || rect2.right < rect1.left) { + return false + } - // if one rectangle is above the other - if (rect1.bottom < rect2.top || rect2.bottom < rect1.top) { - return false - } + // if one rectangle is above the other + if (rect1.bottom < rect2.top || rect2.bottom < rect1.top) { + return false + } - // the rectangles must overlap - return true + // the rectangles must overlap + return true } /** @@ -33,502 +33,596 @@ const areOverlapping = (rect1, rect2) => { const getRectangle = ($el) => $el[0].getBoundingClientRect(); describe('Mainnet', () => { - beforeEach(() => { - //cy.intercept('/sockjs-node/info*').as('socket'); - cy.intercept('/api/block-height/*').as('block-height'); - cy.intercept('/api/block/*').as('block'); - cy.intercept('/api/block/*/txs/0').as('block-txs'); - cy.intercept('/api/tx/*/outspends').as('tx-outspends'); - cy.intercept('/resources/pools.json').as('pools'); + beforeEach(() => { + //cy.intercept('/sockjs-node/info*').as('socket'); + cy.intercept('/api/block-height/*').as('block-height'); + cy.intercept('/api/block/*').as('block'); + cy.intercept('/api/block/*/txs/0').as('block-txs'); + cy.intercept('/api/tx/*/outspends').as('tx-outspends'); + cy.intercept('/resources/pools.json').as('pools'); - // Search Auto Complete - cy.intercept('/api/address-prefix/1wiz').as('search-1wiz'); - cy.intercept('/api/address-prefix/1wizS').as('search-1wizS'); - cy.intercept('/api/address-prefix/1wizSA').as('search-1wizSA'); + // Search Auto Complete + cy.intercept('/api/address-prefix/1wiz').as('search-1wiz'); + cy.intercept('/api/address-prefix/1wizS').as('search-1wizS'); + cy.intercept('/api/address-prefix/1wizSA').as('search-1wizSA'); - Cypress.Commands.add('waitForBlockData', () => { - cy.wait('@tx-outspends'); - cy.wait('@pools'); - }); + Cypress.Commands.add('waitForBlockData', () => { + cy.wait('@tx-outspends'); + cy.wait('@pools'); + }); + }); + + if (baseModule === 'mempool') { + + it('check first mempool block after skeleton loads', () => { + cy.visit('/'); + cy.waitForSkeletonGone(); + cy.get('#mempool-block-0 > .blockLink').should('exist'); }); - if (baseModule === 'mempool') { + 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('.footer').should('be.visible'); + cy.get('.row > :nth-child(1)').invoke('text').then((text) => { + expect(text).to.match(/Tx vBytes per second:.* vB\/s/); + }); + cy.get('.row > :nth-child(2)').invoke('text').then((text) => { + expect(text).to.match(/Unconfirmed:(.*)/); + }); + cy.get('.row > :nth-child(3)').invoke('text').then((text) => { + expect(text).to.match(/Mempool size:(.*) (kB|MB) \((\d+) (block|blocks)\)/); + }); + }); - it('check first mempool block after skeleton loads', () => { + //TODO: This test is flaky, refactor later + it.skip('loads dashboard, drop websocket and reconnect', () => { + cy.viewport('macbook-16'); + cy.mockMempoolSocket(); + cy.visit('/'); + cy.get('.badge').should('not.exist'); + dropWebSocket(); + cy.get('.badge').should('be.visible'); + cy.get('.badge', { timeout: 25000 }).should('not.exist'); + emitMempoolInfo({ + 'params': { + command: 'init' + } + }); + cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist'); + cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist'); + cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist'); + }); + + it('loads the dashboard', () => { + cy.visit('/'); + cy.waitForSkeletonGone(); + }); + + it('check op_return tx tooltip', () => { + cy.visit('/block/00000000000000000003c5f542bed265319c6cf64238cf1f1bb9bca3ebf686d2'); + cy.waitForSkeletonGone(); + cy.get('tbody > :nth-child(2) > :nth-child(1) > a').first().trigger('onmouseover'); + cy.get('tbody > :nth-child(2) > :nth-child(1) > a').first().trigger('mouseenter'); + cy.get('.tooltip-inner').should('be.visible'); + }); + + it('check op_return coinbase tooltip', () => { + cy.visit('/block/00000000000000000003c5f542bed265319c6cf64238cf1f1bb9bca3ebf686d2'); + cy.waitForSkeletonGone(); + cy.get('div > a > .badge').first().trigger('onmouseover'); + cy.get('div > a > .badge').first().trigger('mouseenter'); + cy.get('.tooltip-inner').should('be.visible'); + }); + + describe('search', () => { + it('allows searching for partial Bitcoin addresses', () => { + cy.visit('/'); + cy.get('.search-box-container > .form-control').type('1wiz').then(() => { + cy.wait('@search-1wiz'); + cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 10); + }); + + cy.get('.search-box-container > .form-control').type('S').then(() => { + cy.wait('@search-1wizS'); + cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 5); + }); + + cy.get('.search-box-container > .form-control').type('A').then(() => { + cy.wait('@search-1wizSA'); + cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 1) + }); + + cy.get('ngb-typeahead-window button.dropdown-item.active').click().then(() => { + cy.url().should('include', '/address/1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC'); + cy.waitForSkeletonGone(); + cy.get('.text-center').should('not.have.text', 'Invalid Bitcoin address'); + }); + }); + + ['BC1PQYQSZQ', 'bc1PqYqSzQ'].forEach((searchTerm) => { + it(`allows searching for partial case insensitive bech32m addresses: ${searchTerm}`, () => { + cy.visit('/'); + cy.get('.search-box-container > .form-control').type(searchTerm).then(() => { + cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 1); + cy.get('ngb-typeahead-window button.dropdown-item.active').click().then(() => { + cy.url().should('include', '/address/bc1pqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsyjer9e'); + cy.waitForSkeletonGone(); + cy.get('.text-center').should('not.have.text', 'Invalid Bitcoin address'); + }); + }); + }); + }); + + ['BC1Q000375VXCU', 'bC1q000375vXcU'].forEach((searchTerm) => { + it(`allows searching for partial case insensitive bech32 addresses: ${searchTerm}`, () => { + cy.visit('/'); + cy.get('.search-box-container > .form-control').type(searchTerm).then(() => { + cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 1); + cy.get('ngb-typeahead-window button.dropdown-item.active').click().then(() => { + cy.url().should('include', '/address/bc1q000375vxcuf5v04lmwy22vy2thvhqkxghgq7dy'); + cy.waitForSkeletonGone(); + cy.get('.text-center').should('not.have.text', 'Invalid Bitcoin address'); + }); + }); + }); + }); + + }); + + describe('blocks navigation', () => { + + describe('keyboard events', () => { + it('loads first blockchain blocks visible and keypress arrow right', () => { + cy.viewport('macbook-16'); cy.visit('/'); cy.waitForSkeletonGone(); - cy.get('#mempool-block-0 > .blockLink').should('exist'); - }); - - 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('.footer').should('be.visible'); - cy.get('.row > :nth-child(1)').invoke('text').then((text) => { - expect(text).to.match(/Tx vBytes per second:.* vB\/s/); - }); - cy.get('.row > :nth-child(2)').invoke('text').then((text) => { - expect(text).to.match(/Unconfirmed:(.*)/); - }); - cy.get('.row > :nth-child(3)').invoke('text').then((text) => { - expect(text).to.match(/Mempool size:(.*) (kB|MB) \((\d+) (block|blocks)\)/); - }); - }); - - it('loads dashboard, drop websocket and reconnect', () => { - cy.viewport('macbook-16'); - cy.mockMempoolSocket(); - cy.visit('/'); - cy.get('.badge').should('not.exist'); - dropWebSocket(); - cy.get('.badge').should('be.visible'); - cy.get('.badge', {timeout: 25000}).should('not.exist'); - emitMempoolInfo({ - 'params': { - command: 'init' - } - }); - cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist'); - cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist'); - cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist'); - }); - - it('loads the dashboard', () => { - cy.visit('/'); - cy.waitForSkeletonGone(); - }); - - it('check op_return tx tooltip', () => { - cy.visit('/block/00000000000000000003c5f542bed265319c6cf64238cf1f1bb9bca3ebf686d2'); - cy.waitForSkeletonGone(); - cy.get('tbody > :nth-child(2) > :nth-child(1) > a').first().trigger('onmouseover'); - cy.get('tbody > :nth-child(2) > :nth-child(1) > a').first().trigger('mouseenter'); - cy.get('.tooltip-inner').should('be.visible'); - }); - - it('check op_return coinbase tooltip', () => { - cy.visit('/block/00000000000000000003c5f542bed265319c6cf64238cf1f1bb9bca3ebf686d2'); - cy.waitForSkeletonGone(); - cy.get('div > a > .badge').first().trigger('onmouseover'); - cy.get('div > a > .badge').first().trigger('mouseenter'); - cy.get('.tooltip-inner').should('be.visible'); - }); - - describe('search', () => { - it('allows searching for partial Bitcoin addresses', () => { - cy.visit('/'); - cy.get('.search-box-container > .form-control').type('1wiz').then(() => { - cy.wait('@search-1wiz'); - cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 10); - }); - - cy.get('.search-box-container > .form-control').type('S').then(() => { - cy.wait('@search-1wizS'); - cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 5); - }); - - cy.get('.search-box-container > .form-control').type('A').then(() => { - cy.wait('@search-1wizSA'); - cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 1) - }); - - cy.get('ngb-typeahead-window button.dropdown-item.active').click().then(() => { - cy.url().should('include', '/address/1wizSAYSbuyXbt9d8JV8ytm5acqq2TorC'); - cy.waitForSkeletonGone(); - cy.get('.text-center').should('not.have.text', 'Invalid Bitcoin address'); - }); - }); - - ['BC1PQYQSZQ', 'bc1PqYqSzQ'].forEach((searchTerm) => { - it(`allows searching for partial case insensitive bech32m addresses: ${searchTerm}`, () => { - cy.visit('/'); - cy.get('.search-box-container > .form-control').type(searchTerm).then(() => { - cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 1); - cy.get('ngb-typeahead-window button.dropdown-item.active').click().then(() => { - cy.url().should('include', '/address/bc1pqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqsyjer9e'); - cy.waitForSkeletonGone(); - cy.get('.text-center').should('not.have.text', 'Invalid Bitcoin address'); - }); - }); - }); - }); - - ['BC1Q000375VXCU', 'bC1q000375vXcU'].forEach((searchTerm) => { - it(`allows searching for partial case insensitive bech32 addresses: ${searchTerm}`, () => { - cy.visit('/'); - cy.get('.search-box-container > .form-control').type(searchTerm).then(() => { - cy.get('ngb-typeahead-window button.dropdown-item').should('have.length', 1); - cy.get('ngb-typeahead-window button.dropdown-item.active').click().then(() => { - cy.url().should('include', '/address/bc1q000375vxcuf5v04lmwy22vy2thvhqkxghgq7dy'); - cy.waitForSkeletonGone(); - cy.get('.text-center').should('not.have.text', 'Invalid Bitcoin address'); - }); - }); - }); - }); - - }); - - describe('blocks navigation', () => { - - describe('keyboard events', () => { - it('loads first blockchain blocks visible and keypress arrow right', () => { - cy.viewport('macbook-16'); - cy.visit('/'); - cy.waitForSkeletonGone(); - cy.get('.blockchain-blocks-0 > a').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(); - cy.document().right(); - cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - }); - }); - - it('loads first blockchain blocks visible and keypress arrow left', () => { - cy.viewport('macbook-16'); - cy.visit('/'); - cy.waitForSkeletonGone(); - cy.get('.blockchain-blocks-0 > a').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'); - cy.document().left(); - cy.get('.title-block h1').invoke('text').should('equal', 'Next block'); - }); - }); - - it('loads last blockchain blocks and keypress arrow right', () => { - cy.viewport('macbook-16'); - cy.visit('/'); - cy.waitForSkeletonGone(); - cy.get('.blockchain-blocks-4 > a').click().then(() => { - cy.waitForPageIdle(); - - // block 6 - cy.document().right(); - cy.wait(5000); - cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - - // block 7 - cy.document().right(); - cy.wait(5000); - cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - - // block 8 - last visible block - cy.document().right(); - cy.wait(5000); - cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - - // block 9 - not visible at the blochchain blocks visible block - cy.document().right(); - cy.wait(5000); - cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - - }); - }); - - it('loads genesis block and keypress arrow right', () => { - cy.viewport('macbook-16'); - cy.visit('/block/0'); - cy.waitForSkeletonGone(); - cy.waitForPageIdle(); - - cy.document().right(); - cy.wait(5000); - cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist'); - }); - - it('loads genesis block and keypress arrow left', () => { - cy.viewport('macbook-16'); - cy.visit('/block/0'); - cy.waitForSkeletonGone(); - cy.waitForPageIdle(); - - cy.document().left(); - cy.wait(5000); - cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - }); - }); - describe('mouse events', () => { - it('loads first blockchain blocks visible and click on the arrow right', () => { - cy.viewport('macbook-16'); - cy.visit('/'); - cy.waitForSkeletonGone(); - cy.get('.blockchain-blocks-0 > a').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'); - cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').click().then(() => { - cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - }); - }); - }); - - it('loads genesis block and click on the arrow left', () => { - cy.viewport('macbook-16'); - cy.visit('/block/0'); - cy.waitForSkeletonGone(); - cy.waitForPageIdle(); - cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist'); - cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').click().then(() => { - cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); - }); - }); - }); - }); - - - it('loads skeleton when changes between networks', () => { - cy.visit('/'); - cy.waitForSkeletonGone(); - - cy.changeNetwork("testnet"); - cy.changeNetwork("signet"); - cy.changeNetwork("mainnet"); - }); - - it.skip('loads the dashboard with the skeleton blocks', () => { - cy.mockMempoolSocket(); - cy.visit("/"); - cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible'); - cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible'); - cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible'); - cy.get('#mempool-block-0').should('be.visible'); - cy.get('#mempool-block-1').should('be.visible'); - cy.get('#mempool-block-2').should('be.visible'); - - emitMempoolInfo({ - 'params': { - command: 'init' - } - }); - - cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist'); - cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist'); - cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist'); - }); - - it('loads the blocks screen', () => { - cy.visit('/'); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(2) > a').click().then(() => { - cy.waitForPageIdle(); - }); - }); - - it('loads the graphs screen', () => { - cy.visit('/'); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(3) > a').click().then(() => { - cy.wait(1000); - }); - }); - - describe('graphs page', () => { - it('check buttons - mobile', () => { - cy.viewport('iphone-6'); - cy.visit('/graphs'); - cy.waitForSkeletonGone(); - cy.get('.small-buttons > :nth-child(2)').should('be.visible'); - cy.get('#dropdownFees').should('be.visible'); - cy.get('.btn-group').should('be.visible'); - }); - it('check buttons - tablet', () => { - cy.viewport('ipad-2'); - cy.visit('/graphs'); - cy.waitForSkeletonGone(); - cy.get('.small-buttons > :nth-child(2)').should('be.visible'); - cy.get('#dropdownFees').should('be.visible'); - cy.get('.btn-group').should('be.visible'); - }); - it('check buttons - desktop', () => { - cy.viewport('macbook-16'); - cy.visit('/graphs'); - cy.waitForSkeletonGone(); - cy.get('.small-buttons > :nth-child(2)').should('be.visible'); - cy.get('#dropdownFees').should('be.visible'); - cy.get('.btn-group').should('be.visible'); + cy.get('.blockchain-blocks-0 > a').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(); + cy.document().right(); + cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); }); }); - it('loads the tv screen - desktop', () => { - cy.viewport('macbook-16'); - cy.visit('/'); + it('loads first blockchain blocks visible and keypress arrow left', () => { + cy.viewport('macbook-16'); + cy.visit('/'); + cy.waitForSkeletonGone(); + cy.get('.blockchain-blocks-0 > a').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'); + cy.document().left(); + cy.get('.title-block h1').invoke('text').should('equal', 'Next block'); + }); + }); + + it('loads last blockchain blocks and keypress arrow right', () => { + cy.viewport('macbook-16'); + cy.visit('/'); + cy.waitForSkeletonGone(); + cy.get('.blockchain-blocks-4 > a').click().then(() => { + cy.waitForPageIdle(); + + // block 6 + cy.document().right(); + cy.wait(5000); + cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + + // block 7 + cy.document().right(); + cy.wait(5000); + cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + + // block 8 - last visible block + cy.document().right(); + cy.wait(5000); + cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + + // block 9 - not visible at the blochchain blocks visible block + cy.document().right(); + cy.wait(5000); + cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + + }); + }); + + it('loads genesis block and keypress arrow right', () => { + cy.viewport('macbook-16'); + cy.visit('/block/0'); + cy.waitForSkeletonGone(); + cy.waitForPageIdle(); + + cy.document().right(); + cy.wait(5000); + cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist'); + }); + + it('loads genesis block and keypress arrow left', () => { + cy.viewport('macbook-16'); + cy.visit('/block/0'); + cy.waitForSkeletonGone(); + cy.waitForPageIdle(); + + cy.document().left(); + cy.wait(5000); + cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + }); + }); + describe('mouse events', () => { + it('loads first blockchain blocks visible and click on the arrow right', () => { + cy.viewport('macbook-16'); + cy.visit('/'); + cy.waitForSkeletonGone(); + cy.get('.blockchain-blocks-0 > a').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'); + cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').click().then(() => { + cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + }); + }); + }); + }); + }); + + + it('loads skeleton when changes between networks', () => { + cy.visit('/'); + cy.waitForSkeletonGone(); + + cy.changeNetwork("testnet"); + cy.changeNetwork("signet"); + cy.changeNetwork("mainnet"); + }); + + it.skip('loads the dashboard with the skeleton blocks', () => { + cy.mockMempoolSocket(); + cy.visit("/"); + cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible'); + cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible'); + cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible'); + cy.get('#mempool-block-0').should('be.visible'); + cy.get('#mempool-block-1').should('be.visible'); + cy.get('#mempool-block-2').should('be.visible'); + + emitMempoolInfo({ + 'params': { + command: 'init' + } + }); + + cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist'); + cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist'); + cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist'); + }); + + it('loads the pools screen', () => { + cy.visit('/'); + cy.waitForSkeletonGone(); + cy.get('#btn-pools').click().then(() => { + cy.waitForPageIdle(); + }); + }); + + it('loads the graphs screen', () => { + cy.visit('/'); + cy.waitForSkeletonGone(); + cy.get('#btn-graphs').click().then(() => { + cy.wait(1000); + }); + }); + + describe('graphs page', () => { + it('check buttons - mobile', () => { + cy.viewport('iphone-6'); + cy.visit('/graphs'); + cy.waitForSkeletonGone(); + cy.get('.small-buttons > :nth-child(2)').should('be.visible'); + cy.get('#dropdownFees').should('be.visible'); + cy.get('.btn-group').should('be.visible'); + }); + it('check buttons - tablet', () => { + cy.viewport('ipad-2'); + cy.visit('/graphs'); + cy.waitForSkeletonGone(); + cy.get('.small-buttons > :nth-child(2)').should('be.visible'); + cy.get('#dropdownFees').should('be.visible'); + cy.get('.btn-group').should('be.visible'); + }); + it('check buttons - desktop', () => { + cy.viewport('macbook-16'); + cy.visit('/graphs'); + cy.waitForSkeletonGone(); + cy.get('.small-buttons > :nth-child(2)').should('be.visible'); + cy.get('#dropdownFees').should('be.visible'); + cy.get('.btn-group').should('be.visible'); + }); + }); + + it('loads the tv screen - desktop', () => { + cy.viewport('macbook-16'); + cy.visit('/'); + cy.waitForSkeletonGone(); + cy.get('#btn-tv').click().then(() => { + cy.viewport('macbook-16'); + cy.get('.chart-holder'); + cy.get('.blockchain-wrapper').should('be.visible'); + cy.get('#mempool-block-0').should('be.visible'); + }); + }); + + it('loads the tv screen - mobile', () => { + cy.viewport('iphone-6'); + cy.visit('/tv'); + cy.waitForSkeletonGone(); + cy.get('.chart-holder'); + cy.get('.blockchain-wrapper').should('not.visible'); + }); + + it('loads genesis block and click on the arrow left', () => { + cy.viewport('macbook-16'); + cy.visit('/block/0'); + cy.waitForSkeletonGone(); + cy.waitForPageIdle(); + cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist'); + cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').click().then(() => { + cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible'); + }); + }); + + it('loads skeleton when changes between networks', () => { + cy.visit('/'); + cy.waitForSkeletonGone(); + + cy.changeNetwork("testnet"); + cy.changeNetwork("signet"); + cy.changeNetwork("mainnet"); + }); + + it.skip('loads the dashboard with the skeleton blocks', () => { + cy.mockMempoolSocket(); + cy.visit("/"); + cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible'); + cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible'); + cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible'); + cy.get('#mempool-block-0').should('be.visible'); + cy.get('#mempool-block-1').should('be.visible'); + cy.get('#mempool-block-2').should('be.visible'); + + emitMempoolInfo({ + 'params': { + command: 'init' + } + }); + + cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist'); + cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist'); + cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist'); + }); + + it('loads the pools screen', () => { + cy.visit('/'); + cy.waitForSkeletonGone(); + cy.get('#btn-pools').click().then(() => { + cy.wait(1000); + }); + }); + + it('loads the graphs screen', () => { + cy.visit('/'); + cy.waitForSkeletonGone(); + cy.get('#btn-graphs').click().then(() => { + cy.wait(1000); + }); + }); + + describe('graphs page', () => { + it('check buttons - mobile', () => { + cy.viewport('iphone-6'); + cy.visit('/graphs'); + cy.waitForSkeletonGone(); + cy.get('.small-buttons > :nth-child(2)').should('be.visible'); + cy.get('#dropdownFees').should('be.visible'); + cy.get('.btn-group').should('be.visible'); + }); + it('check buttons - tablet', () => { + cy.viewport('ipad-2'); + cy.visit('/graphs'); + cy.waitForSkeletonGone(); + cy.get('.small-buttons > :nth-child(2)').should('be.visible'); + cy.get('#dropdownFees').should('be.visible'); + cy.get('.btn-group').should('be.visible'); + }); + it('check buttons - desktop', () => { + cy.viewport('macbook-16'); + cy.visit('/graphs'); + cy.waitForSkeletonGone(); + cy.get('.small-buttons > :nth-child(2)').should('be.visible'); + cy.get('#dropdownFees').should('be.visible'); + cy.get('.btn-group').should('be.visible'); + }); + }); + + it('loads the tv screen - desktop', () => { + cy.viewport('macbook-16'); + cy.visit('/'); + cy.waitForSkeletonGone(); + cy.get('#btn-tv').click().then(() => { + cy.viewport('macbook-16'); + cy.get('.chart-holder'); + cy.get('.blockchain-wrapper').should('be.visible'); + cy.get('#mempool-block-0').should('be.visible'); + }); + }); + + it('loads the tv screen - mobile', () => { + cy.viewport('iphone-6'); + cy.visit('/tv'); + cy.waitForSkeletonGone(); + cy.get('.chart-holder'); + cy.get('.blockchain-wrapper').should('not.visible'); + }); + + it('loads the api screen', () => { + cy.visit('/'); + cy.waitForSkeletonGone(); + cy.get('#btn-docs').click().then(() => { + cy.wait(1000); + }); + }); + + describe('blocks', () => { + it('shows empty blocks properly', () => { + cy.visit('/block/0000000000000000000bd14f744ef2e006e61c32214670de7eb891a5732ee775'); + cy.waitForSkeletonGone(); + cy.waitForPageIdle(); + cy.get('h2').invoke('text').should('equal', '1 transaction'); + }); + + it('expands and collapses the block details', () => { + cy.visit('/block/0'); + cy.waitForSkeletonGone(); + cy.waitForPageIdle(); + cy.get('.btn.btn-outline-info').click().then(() => { + cy.get('#details').should('be.visible'); + }); + + cy.get('.btn.btn-outline-info').click().then(() => { + cy.get('#details').should('not.be.visible'); + }); + }); + it('shows blocks with no pagination', () => { + cy.visit('/block/00000000000000000001ba40caf1ad4cec0ceb77692662315c151953bfd7c4c4'); + cy.waitForSkeletonGone(); + cy.waitForPageIdle(); + cy.get('.block-tx-title h2').invoke('text').should('equal', '19 transactions'); + cy.get('.pagination-container ul.pagination').first().children().should('have.length', 5); + }); + + it('supports pagination on the block screen', () => { + // 41 txs + cy.visit('/block/00000000000000000009f9b7b0f63ad50053ad12ec3b7f5ca951332f134f83d8'); + cy.waitForSkeletonGone(); + cy.get('.pagination-container a').invoke('text').then((text1) => { + cy.get('.active + li').first().click().then(() => { cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(4) > a').click().then(() => { - cy.viewport('macbook-16'); - cy.get('.chart-holder'); - cy.get('.blockchain-wrapper').should('be.visible'); - cy.get('#mempool-block-0').should('be.visible'); + cy.waitForPageIdle(); + cy.get('.header-bg.box > a').invoke('text').then((text2) => { + expect(text1).not.to.eq(text2); }); + }); + }); + }); + + it('shows blocks pagination with 5 pages (desktop)', () => { + cy.viewport(760, 800); + cy.visit('/block/000000000000000000049281946d26fcba7d99fdabc1feac524bc3a7003d69b3').then(() => { + cy.waitForSkeletonGone(); + cy.waitForPageIdle(); }); - it('loads the tv screen - mobile', () => { - cy.viewport('iphone-6'); - cy.visit('/tv'); - cy.waitForSkeletonGone(); - cy.get('.chart-holder'); - cy.get('.blockchain-wrapper').should('not.visible'); + // 5 pages + 4 buttons = 9 buttons + cy.get('.pagination-container ul.pagination').first().children().should('have.length', 9); + }); + + it('shows blocks pagination with 3 pages (mobile)', () => { + cy.viewport(669, 800); + cy.visit('/block/000000000000000000049281946d26fcba7d99fdabc1feac524bc3a7003d69b3').then(() => { + cy.waitForSkeletonGone(); + cy.waitForPageIdle(); }); - it('loads the api screen', () => { - cy.visit('/'); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(5) > a').click().then(() => { - cy.wait(1000); - }); + // 3 pages + 4 buttons = 7 buttons + cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7); + }); + }); + + describe('RBF transactions', () => { + it('shows RBF transactions properly (mobile)', () => { + cy.viewport('iphone-xr'); + cy.mockMempoolSocket(); + cy.visit('/tx/f81a08699b62b2070ad8fe0f2a076f8bea0386a2fdcd8124caee42cbc564a0d5'); + + cy.waitForSkeletonGone(); + + emitMempoolInfo({ + 'params': { + command: 'init' + } }); - describe('blocks', () => { - it('shows empty blocks properly', () => { - cy.visit('/block/0000000000000000000bd14f744ef2e006e61c32214670de7eb891a5732ee775'); - cy.waitForSkeletonGone(); - cy.waitForPageIdle(); - cy.get('h2').invoke('text').should('equal', '1 transaction'); - }); + cy.get('#mempool-block-0'); - it('expands and collapses the block details', () => { - cy.visit('/block/0'); - cy.waitForSkeletonGone(); - cy.waitForPageIdle(); - cy.get('.btn.btn-outline-info').click().then(() => { - cy.get('#details').should('be.visible'); - }); - - cy.get('.btn.btn-outline-info').click().then(() => { - cy.get('#details').should('not.be.visible'); - }); - }); - it('shows blocks with no pagination', () => { - cy.visit('/block/00000000000000000001ba40caf1ad4cec0ceb77692662315c151953bfd7c4c4'); - cy.waitForSkeletonGone(); - cy.waitForPageIdle(); - cy.get('.block-tx-title h2').invoke('text').should('equal', '19 transactions'); - cy.get('.pagination-container ul.pagination').first().children().should('have.length', 5); - }); - - it('supports pagination on the block screen', () => { - // 41 txs - cy.visit('/block/00000000000000000009f9b7b0f63ad50053ad12ec3b7f5ca951332f134f83d8'); - cy.waitForSkeletonGone(); - cy.get('.pagination-container a').invoke('text').then((text1) => { - cy.get('.active + li').first().click().then(() => { - cy.waitForSkeletonGone(); - cy.waitForPageIdle(); - cy.get('.header-bg.box > a').invoke('text').then((text2) => { - expect(text1).not.to.eq(text2); - }); - }); - }); - }); - - it('shows blocks pagination with 5 pages (desktop)', () => { - cy.viewport(760, 800); - cy.visit('/block/000000000000000000049281946d26fcba7d99fdabc1feac524bc3a7003d69b3').then(() => { - cy.waitForSkeletonGone(); - cy.waitForPageIdle(); - }); - - // 5 pages + 4 buttons = 9 buttons - cy.get('.pagination-container ul.pagination').first().children().should('have.length', 9); - }); - - it('shows blocks pagination with 3 pages (mobile)', () => { - cy.viewport(669, 800); - cy.visit('/block/000000000000000000049281946d26fcba7d99fdabc1feac524bc3a7003d69b3').then(() => { - cy.waitForSkeletonGone(); - cy.waitForPageIdle(); - }); - - // 3 pages + 4 buttons = 7 buttons - cy.get('.pagination-container ul.pagination').first().children().should('have.length', 7); - }); + emitMempoolInfo({ + 'params': { + command: 'rbfTransaction' + } }); - describe('RBF transactions', () => { - it('shows RBF transactions properly (mobile)', () => { - cy.viewport('iphone-xr'); - cy.mockMempoolSocket(); - cy.visit('/tx/f81a08699b62b2070ad8fe0f2a076f8bea0386a2fdcd8124caee42cbc564a0d5'); - - cy.waitForSkeletonGone(); - - emitMempoolInfo({ - 'params': { - command: 'init' - } - }); - - cy.get('#mempool-block-0'); - - emitMempoolInfo({ - 'params': { - command: 'rbfTransaction' - } - }); - - cy.get('.alert-mempool').should('be.visible'); - cy.get('.alert-mempool').invoke('css', 'width').then((alertWidth) => { - cy.get('.container-xl > :nth-child(3)').invoke('css', 'width').should('equal', alertWidth); - }); - - cy.get('.btn-success').then(getRectangle).then((rectA) => { - cy.get('.alert-mempool').then(getRectangle).then((rectB) => { - expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false; - }); - }); - }); - - it('shows RBF transactions properly (desktop)', () => { - cy.viewport('macbook-16'); - cy.mockMempoolSocket(); - cy.visit('/tx/f81a08699b62b2070ad8fe0f2a076f8bea0386a2fdcd8124caee42cbc564a0d5'); - - cy.waitForSkeletonGone(); - - emitMempoolInfo({ - 'params': { - command: 'init' - } - }); - - cy.get('#mempool-block-0'); - - emitMempoolInfo({ - 'params': { - command: 'rbfTransaction' - } - }); - - cy.get('.alert-mempool').should('be.visible'); - - const alertLocator = '.alert-mempool'; - const tableLocator = '.container-xl > :nth-child(3)'; - - cy.get(tableLocator).invoke('css', 'width').then((firstWidth) => { - cy.get(alertLocator).invoke('css', 'width').should('equal', firstWidth); - }); - - cy.get('.btn-success').then(getRectangle).then((rectA) => { - cy.get('.alert-mempool').then(getRectangle).then((rectB) => { - expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false; - }); - }); - }); + cy.get('.alert-mempool').should('be.visible'); + cy.get('.alert-mempool').invoke('css', 'width').then((alertWidth) => { + cy.get('.container-xl > :nth-child(3)').invoke('css', 'width').should('equal', alertWidth); }); - } else { - it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`); - } + + cy.get('.btn-success').then(getRectangle).then((rectA) => { + cy.get('.alert-mempool').then(getRectangle).then((rectB) => { + expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false; + }); + }); + }); + + it('shows RBF transactions properly (desktop)', () => { + cy.viewport('macbook-16'); + cy.mockMempoolSocket(); + cy.visit('/tx/f81a08699b62b2070ad8fe0f2a076f8bea0386a2fdcd8124caee42cbc564a0d5'); + + cy.waitForSkeletonGone(); + + emitMempoolInfo({ + 'params': { + command: 'init' + } + }); + + cy.get('#mempool-block-0'); + + emitMempoolInfo({ + 'params': { + command: 'rbfTransaction' + } + }); + + cy.get('.alert-mempool').should('be.visible'); + + const alertLocator = '.alert-mempool'; + const tableLocator = '.container-xl > :nth-child(3)'; + + cy.get(tableLocator).invoke('css', 'width').then((firstWidth) => { + cy.get(alertLocator).invoke('css', 'width').should('equal', firstWidth); + }); + + cy.get('.btn-success').then(getRectangle).then((rectA) => { + cy.get('.alert-mempool').then(getRectangle).then((rectB) => { + expect(areOverlapping(rectA, rectB), 'Confirmations box and RBF alert are overlapping').to.be.false; + }); + }); + }); + }); + } else { + it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`); + } }); diff --git a/frontend/cypress/integration/signet/signet.spec.ts b/frontend/cypress/integration/signet/signet.spec.ts index 8e40fdc73..d2bbd1196 100644 --- a/frontend/cypress/integration/signet/signet.spec.ts +++ b/frontend/cypress/integration/signet/signet.spec.ts @@ -13,8 +13,8 @@ describe('Signet', () => { if (baseModule === 'mempool') { it('loads the dashboard', () => { - cy.visit('/signet'); - cy.waitForSkeletonGone(); + cy.visit('/signet'); + cy.waitForSkeletonGone(); }); it('check first mempool block after skeleton loads', () => { @@ -24,116 +24,116 @@ describe('Signet', () => { }); it.skip('loads the dashboard with the skeleton blocks', () => { - cy.mockMempoolSocket(); - cy.visit("/signet"); - cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible'); - cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible'); - cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible'); - cy.get('#mempool-block-0').should('be.visible'); - cy.get('#mempool-block-1').should('be.visible'); - cy.get('#mempool-block-2').should('be.visible'); + cy.mockMempoolSocket(); + cy.visit("/signet"); + cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible'); + cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible'); + cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible'); + cy.get('#mempool-block-0').should('be.visible'); + cy.get('#mempool-block-1').should('be.visible'); + cy.get('#mempool-block-2').should('be.visible'); - emitMempoolInfo({ - 'params': { - "network": "signet" - } - }); + emitMempoolInfo({ + 'params': { + "network": "signet" + } + }); - cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist'); - cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist'); - cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist'); + cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist'); + cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist'); + cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist'); }); - it('loads the blocks screen', () => { - cy.visit('/signet'); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(2) > a').click().then(() => { - cy.wait(1000); - }); + it('loads the pools screen', () => { + cy.visit('/signet'); + cy.waitForSkeletonGone(); + cy.get('#btn-pools').click().then(() => { + cy.wait(1000); + }); }); it('loads the graphs screen', () => { - cy.visit('/signet'); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(3) > a').click().then(() => { - cy.wait(1000); - }); + cy.visit('/signet'); + cy.waitForSkeletonGone(); + cy.get('#btn-graphs').click().then(() => { + cy.wait(1000); + }); }); describe('tv mode', () => { - it('loads the tv screen - desktop', () => { - cy.viewport('macbook-16'); - cy.visit('/signet'); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(4) > a').click().then(() => { - cy.get('.chart-holder').should('be.visible'); - cy.get('#mempool-block-0').should('be.visible'); - cy.get('.tv-only').should('not.exist'); - }); + it('loads the tv screen - desktop', () => { + cy.viewport('macbook-16'); + cy.visit('/signet'); + cy.waitForSkeletonGone(); + cy.get('#btn-tv').click().then(() => { + cy.get('.chart-holder').should('be.visible'); + cy.get('#mempool-block-0').should('be.visible'); + cy.get('.tv-only').should('not.exist'); }); + }); - it('loads the tv screen - mobile', () => { - cy.visit('/signet'); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(4) > a').click().then(() => { - cy.viewport('iphone-8'); - cy.get('.chart-holder').should('be.visible'); - //TODO: Remove comment when the bug is fixed - //cy.get('#mempool-block-0').should('be.visible'); - cy.get('.tv-only').should('not.exist'); - }); + it('loads the tv screen - mobile', () => { + cy.visit('/signet'); + cy.waitForSkeletonGone(); + cy.get('#btn-tv').click().then(() => { + cy.viewport('iphone-8'); + cy.get('.chart-holder').should('be.visible'); + cy.get('.tv-only').should('not.exist'); + //TODO: Remove comment when the bug is fixed + //cy.get('#mempool-block-0').should('be.visible'); }); + }); }); it('loads the api screen', () => { - cy.visit('/signet'); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(5) > a').click().then(() => { - cy.wait(1000); - }); + cy.visit('/signet'); + cy.waitForSkeletonGone(); + cy.get('#btn-docs').click().then(() => { + cy.wait(1000); + }); }); describe('blocks', () => { - it('shows empty blocks properly', () => { - cy.visit('/signet/block/00000133d54e4589f6436703b067ec23209e0a21b8a9b12f57d0592fd85f7a42'); - cy.waitForSkeletonGone(); - cy.get('h2').invoke('text').should('equal', '1 transaction'); + it('shows empty blocks properly', () => { + cy.visit('/signet/block/00000133d54e4589f6436703b067ec23209e0a21b8a9b12f57d0592fd85f7a42'); + cy.waitForSkeletonGone(); + cy.get('h2').invoke('text').should('equal', '1 transaction'); + }); + + it('expands and collapses the block details', () => { + cy.visit('/signet/block/0'); + cy.waitForSkeletonGone(); + cy.get('.btn.btn-outline-info').click().then(() => { + cy.get('#details').should('be.visible'); }); - it('expands and collapses the block details', () => { - cy.visit('/signet/block/0'); - cy.waitForSkeletonGone(); - cy.get('.btn.btn-outline-info').click().then(() => { - cy.get('#details').should('be.visible'); - }); - - cy.get('.btn.btn-outline-info').click().then(() => { - cy.get('#details').should('not.be.visible'); - }); - }); - - it('shows blocks with no pagination', () => { - cy.visit('/signet/block/00000078f920a96a69089877b934ce7fd009ab55e3170920a021262cb258e7cc'); - cy.waitForSkeletonGone(); - cy.get('h2').invoke('text').should('equal', '13 transactions'); - cy.get('ul.pagination').first().children().should('have.length', 5); - }); - - it('supports pagination on the block screen', () => { - // 43 txs - cy.visit('/signet/block/00000094bd52f73bdbfc4bece3a94c21fec2dc968cd54210496e69e4059d66a6'); - cy.waitForSkeletonGone(); - cy.get('.header-bg.box > a').invoke('text').then((text1) => { - cy.get('.active + li').first().click().then(() => { - cy.get('.header-bg.box > a').invoke('text').then((text2) => { - expect(text1).not.to.eq(text2); - }); - }); + cy.get('.btn.btn-outline-info').click().then(() => { + cy.get('#details').should('not.be.visible'); + }); + }); + + it('shows blocks with no pagination', () => { + cy.visit('/signet/block/00000078f920a96a69089877b934ce7fd009ab55e3170920a021262cb258e7cc'); + cy.waitForSkeletonGone(); + cy.get('h2').invoke('text').should('equal', '13 transactions'); + cy.get('ul.pagination').first().children().should('have.length', 5); + }); + + it('supports pagination on the block screen', () => { + // 43 txs + cy.visit('/signet/block/00000094bd52f73bdbfc4bece3a94c21fec2dc968cd54210496e69e4059d66a6'); + cy.waitForSkeletonGone(); + cy.get('.header-bg.box > a').invoke('text').then((text1) => { + cy.get('.active + li').first().click().then(() => { + cy.get('.header-bg.box > a').invoke('text').then((text2) => { + expect(text1).not.to.eq(text2); }); + }); }); + }); }); - } else { - it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`); - } - }); + } else { + it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`); + } +}); diff --git a/frontend/cypress/integration/testnet/testnet.spec.ts b/frontend/cypress/integration/testnet/testnet.spec.ts index e92480eff..c0c07aa74 100644 --- a/frontend/cypress/integration/testnet/testnet.spec.ts +++ b/frontend/cypress/integration/testnet/testnet.spec.ts @@ -13,8 +13,8 @@ describe('Testnet', () => { if (baseModule === 'mempool') { it('loads the dashboard', () => { - cy.visit('/testnet'); - cy.waitForSkeletonGone(); + cy.visit('/testnet'); + cy.waitForSkeletonGone(); }); it('check first mempool block after skeleton loads', () => { @@ -24,113 +24,115 @@ describe('Testnet', () => { }); it.skip('loads the dashboard with the skeleton blocks', () => { - cy.mockMempoolSocket(); - cy.visit("/testnet"); - cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible'); - cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible'); - cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible'); - cy.get('#mempool-block-0').should('be.visible'); - cy.get('#mempool-block-1').should('be.visible'); - cy.get('#mempool-block-2').should('be.visible'); + cy.mockMempoolSocket(); + cy.visit("/testnet"); + cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible'); + cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible'); + cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible'); + cy.get('#mempool-block-0').should('be.visible'); + cy.get('#mempool-block-1').should('be.visible'); + cy.get('#mempool-block-2').should('be.visible'); - emitMempoolInfo({ - 'params': { - loaded: true - } - }); + emitMempoolInfo({ + 'params': { + loaded: true + } + }); - cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist'); - cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist'); - cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist'); + cy.get(':nth-child(1) > #bitcoin-block-0').should('not.exist'); + cy.get(':nth-child(2) > #bitcoin-block-0').should('not.exist'); + cy.get(':nth-child(3) > #bitcoin-block-0').should('not.exist'); }); - it('loads the blocks screen', () => { - cy.visit('/testnet'); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(2) > a').click().then(() => { - cy.wait(1000); - }); + it('loads the pools screen', () => { + cy.visit('/testnet'); + cy.waitForSkeletonGone(); + cy.get('#btn-pools').click().then(() => { + cy.wait(1000); + }); }); it('loads the graphs screen', () => { - cy.visit('/testnet'); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(3) > a').click().then(() => { - cy.wait(1000); - }); + cy.visit('/testnet'); + cy.waitForSkeletonGone(); + cy.get('#btn-graphs').click().then(() => { + cy.wait(1000); + }); }); describe('tv mode', () => { - it('loads the tv screen - desktop', () => { - cy.viewport('macbook-16'); - cy.visit('/testnet'); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(4) > a').click().then(() => { - cy.wait(1000); - cy.get('.tv-only').should('not.exist'); - }); + it('loads the tv screen - desktop', () => { + cy.viewport('macbook-16'); + cy.visit('/testnet'); + cy.waitForSkeletonGone(); + cy.get('#btn-tv').click().then(() => { + cy.wait(1000); + cy.get('.tv-only').should('not.exist'); + //TODO: Remove comment when the bug is fixed + //cy.get('#mempool-block-0').should('be.visible'); }); + }); - it('loads the tv screen - mobile', () => { - cy.visit('/testnet'); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(4) > a').click().then(() => { - cy.viewport('iphone-6'); - cy.wait(1000); - cy.get('.tv-only').should('not.exist'); - }); + it('loads the tv screen - mobile', () => { + cy.visit('/testnet'); + cy.waitForSkeletonGone(); + cy.get('#btn-tv').click().then(() => { + cy.viewport('iphone-6'); + cy.wait(1000); + cy.get('.tv-only').should('not.exist'); }); + }); }); it('loads the api screen', () => { - cy.visit('/testnet'); - cy.waitForSkeletonGone(); - cy.get('li:nth-of-type(5) > a').click().then(() => { - cy.wait(1000); - }); + cy.visit('/testnet'); + cy.waitForSkeletonGone(); + cy.get('#btn-docs').click().then(() => { + cy.wait(1000); + }); }); describe('blocks', () => { - it('shows empty blocks properly', () => { - cy.visit('/testnet/block/0'); - cy.waitForSkeletonGone(); - cy.get('h2').invoke('text').should('equal', '1 transaction'); + it('shows empty blocks properly', () => { + cy.visit('/testnet/block/0'); + cy.waitForSkeletonGone(); + cy.get('h2').invoke('text').should('equal', '1 transaction'); + }); + + it('expands and collapses the block details', () => { + cy.visit('/testnet/block/0'); + cy.waitForSkeletonGone(); + cy.get('.btn.btn-outline-info').click().then(() => { + cy.get('#details').should('be.visible'); }); - it('expands and collapses the block details', () => { - cy.visit('/testnet/block/0'); - cy.waitForSkeletonGone(); - cy.get('.btn.btn-outline-info').click().then(() => { - cy.get('#details').should('be.visible'); - }); - - cy.get('.btn.btn-outline-info').click().then(() => { - cy.get('#details').should('not.be.visible'); - }); - }); - - it('shows blocks with no pagination', () => { - cy.visit('/testnet/block/000000000000002f8ce27716e74ecc7ad9f7b5101fed12d09e28bb721b9460ea'); - cy.waitForSkeletonGone(); - cy.get('h2').invoke('text').should('equal', '11 transactions'); - cy.get('ul.pagination').first().children().should('have.length', 5); - }); - - it('supports pagination on the block screen', () => { - // 48 txs - cy.visit('/testnet/block/000000000000002ca3878ebd98b313a1c2d531f2e70a6575d232ca7564dea7a9'); - cy.waitForSkeletonGone(); - cy.get('.header-bg.box > a').invoke('text').then((text1) => { - cy.get('.active + li').first().click().then(() => { - cy.get('.header-bg.box > a').invoke('text').then((text2) => { - expect(text1).not.to.eq(text2); - }); - }); + cy.get('.btn.btn-outline-info').click().then(() => { + cy.get('#details').should('not.be.visible'); + }); + }); + + it('shows blocks with no pagination', () => { + cy.visit('/testnet/block/000000000000002f8ce27716e74ecc7ad9f7b5101fed12d09e28bb721b9460ea'); + cy.waitForSkeletonGone(); + cy.get('h2').invoke('text').should('equal', '11 transactions'); + cy.get('ul.pagination').first().children().should('have.length', 5); + }); + + it('supports pagination on the block screen', () => { + // 48 txs + cy.visit('/testnet/block/000000000000002ca3878ebd98b313a1c2d531f2e70a6575d232ca7564dea7a9'); + cy.waitForSkeletonGone(); + cy.get('.header-bg.box > a').invoke('text').then((text1) => { + cy.get('.active + li').first().click().then(() => { + cy.get('.header-bg.box > a').invoke('text').then((text2) => { + expect(text1).not.to.eq(text2); }); + }); }); + }); }); - } else { - it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`); - } - }); + } else { + it.skip(`Tests cannot be run on the selected BASE_MODULE ${baseModule}`); + } +}); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index befb9d3ac..a53d1d0aa 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -70,7 +70,7 @@ }, "optionalDependencies": { "@cypress/schematic": "^1.3.0", - "cypress": "^9.1.1", + "cypress": "^9.3.1", "cypress-fail-on-console-error": "^2.1.3", "cypress-wait-until": "^1.7.1", "mock-socket": "^9.0.3", @@ -3801,9 +3801,9 @@ } }, "node_modules/@types/sinonjs__fake-timers": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz", - "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", "optional": true }, "node_modules/@types/sizzle": { @@ -5746,17 +5746,18 @@ } }, "node_modules/cli-table3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz", - "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz", + "integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==", "optional": true, "dependencies": { - "colors": "^1.1.2", - "object-assign": "^4.1.0", "string-width": "^4.2.0" }, "engines": { "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "colors": "1.4.0" } }, "node_modules/cli-truncate": { @@ -6786,25 +6787,26 @@ "devOptional": true }, "node_modules/cypress": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.1.1.tgz", - "integrity": "sha512-yWcYD8SEQ8F3okFbRPqSDj5V0xhrZBT5QRIH+P1J2vYvtEmZ4KGciHE7LCcZZLILOrs7pg4WNCqkj/XRvReQlQ==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.3.1.tgz", + "integrity": "sha512-BODdPesxX6bkVUnH8BVsV8I/jn57zQtO1FEOUTiuG2us3kslW7g0tcuwiny7CKCmJUZz8S/D587ppC+s58a+5Q==", "hasInstallScript": true, "optional": true, "dependencies": { "@cypress/request": "^2.88.10", "@cypress/xvfb": "^1.2.4", "@types/node": "^14.14.31", - "@types/sinonjs__fake-timers": "^6.0.2", + "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", "blob-util": "^2.0.2", - "bluebird": "3.7.2", + "bluebird": "^3.7.2", + "buffer": "^5.6.0", "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.0", + "cli-table3": "~0.6.1", "commander": "^5.1.0", "common-tags": "^1.8.0", "dayjs": "^1.10.4", @@ -6984,16 +6986,6 @@ "node": ">=8" } }, - "node_modules/cypress/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "optional": true, - "dependencies": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, "node_modules/cypress/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -7074,9 +7066,9 @@ } }, "node_modules/date-format": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", - "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.3.tgz", + "integrity": "sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ==", "devOptional": true, "engines": { "node": ">=4.0" @@ -8848,9 +8840,9 @@ } }, "node_modules/flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", + "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "devOptional": true }, "node_modules/flatten": { @@ -8946,17 +8938,26 @@ } }, "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", "devOptional": true, "dependencies": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=12" + } + }, + "node_modules/fs-extra/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "devOptional": true, + "engines": { + "node": ">= 10.0.0" } }, "node_modules/fs-minipass": { @@ -10484,14 +10485,26 @@ "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==" }, "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "devOptional": true, + "dependencies": { + "universalify": "^2.0.0" + }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, + "node_modules/jsonfile/node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "devOptional": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", @@ -11346,21 +11359,38 @@ } }, "node_modules/log4js": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", - "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.0.tgz", + "integrity": "sha512-ysc/XUecZJuN8NoKOssk3V0cQ29xY4fra6fnigZa5VwxFsCsvdqsdnEuAxNN89LlHpbE4KUD3zGcn+kFqonSVQ==", "devOptional": true, "dependencies": { - "date-format": "^3.0.0", - "debug": "^4.1.1", - "flatted": "^2.0.1", - "rfdc": "^1.1.4", - "streamroller": "^2.2.4" + "date-format": "^4.0.3", + "debug": "^4.3.3", + "flatted": "^3.2.4", + "rfdc": "^1.3.0", + "streamroller": "^3.0.2" }, "engines": { "node": ">=8.0" } }, + "node_modules/log4js/node_modules/debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "devOptional": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -12028,9 +12058,9 @@ } }, "node_modules/nanoid": { - "version": "3.1.30", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", - "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -16245,28 +16275,19 @@ } }, "node_modules/streamroller": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", - "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.2.tgz", + "integrity": "sha512-ur6y5S5dopOaRXBuRIZ1u6GC5bcEXHRZKgfBjfCglMhmIf+roVCECjvkEYzNQOXIN2/JPnkMPW/8B3CZoKaEPA==", "devOptional": true, "dependencies": { - "date-format": "^2.1.0", + "date-format": "^4.0.3", "debug": "^4.1.1", - "fs-extra": "^8.1.0" + "fs-extra": "^10.0.0" }, "engines": { "node": ">=8.0" } }, - "node_modules/streamroller/node_modules/date-format": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", - "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", - "devOptional": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -17178,7 +17199,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "devOptional": true, + "dev": true, "engines": { "node": ">= 4.0.0" } @@ -20861,9 +20882,9 @@ } }, "@types/sinonjs__fake-timers": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz", - "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", "optional": true }, "@types/sizzle": { @@ -22536,13 +22557,12 @@ "integrity": "sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==" }, "cli-table3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz", - "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz", + "integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==", "optional": true, "requires": { - "colors": "^1.1.2", - "object-assign": "^4.1.0", + "colors": "1.4.0", "string-width": "^4.2.0" } }, @@ -23373,24 +23393,25 @@ "devOptional": true }, "cypress": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.1.1.tgz", - "integrity": "sha512-yWcYD8SEQ8F3okFbRPqSDj5V0xhrZBT5QRIH+P1J2vYvtEmZ4KGciHE7LCcZZLILOrs7pg4WNCqkj/XRvReQlQ==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-9.3.1.tgz", + "integrity": "sha512-BODdPesxX6bkVUnH8BVsV8I/jn57zQtO1FEOUTiuG2us3kslW7g0tcuwiny7CKCmJUZz8S/D587ppC+s58a+5Q==", "optional": true, "requires": { "@cypress/request": "^2.88.10", "@cypress/xvfb": "^1.2.4", "@types/node": "^14.14.31", - "@types/sinonjs__fake-timers": "^6.0.2", + "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", "blob-util": "^2.0.2", - "bluebird": "3.7.2", + "bluebird": "^3.7.2", + "buffer": "^5.6.0", "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.0", + "cli-table3": "~0.6.1", "commander": "^5.1.0", "common-tags": "^1.8.0", "dayjs": "^1.10.4", @@ -23522,16 +23543,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "optional": true }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "optional": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -23616,9 +23627,9 @@ } }, "date-format": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", - "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.3.tgz", + "integrity": "sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ==", "devOptional": true }, "dayjs": { @@ -25039,9 +25050,9 @@ } }, "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.4.tgz", + "integrity": "sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==", "devOptional": true }, "flatten": { @@ -25110,14 +25121,22 @@ } }, "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", "devOptional": true, "requires": { "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "devOptional": true + } } }, "fs-minipass": { @@ -26331,12 +26350,21 @@ "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==" }, "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "devOptional": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "devOptional": true + } } }, "jsonparse": { @@ -27019,16 +27047,27 @@ } }, "log4js": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", - "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.0.tgz", + "integrity": "sha512-ysc/XUecZJuN8NoKOssk3V0cQ29xY4fra6fnigZa5VwxFsCsvdqsdnEuAxNN89LlHpbE4KUD3zGcn+kFqonSVQ==", "devOptional": true, "requires": { - "date-format": "^3.0.0", - "debug": "^4.1.1", - "flatted": "^2.0.1", - "rfdc": "^1.1.4", - "streamroller": "^2.2.4" + "date-format": "^4.0.3", + "debug": "^4.3.3", + "flatted": "^3.2.4", + "rfdc": "^1.3.0", + "streamroller": "^3.0.2" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "devOptional": true, + "requires": { + "ms": "2.1.2" + } + } } }, "lru-cache": { @@ -27552,9 +27591,9 @@ } }, "nanoid": { - "version": "3.1.30", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.30.tgz", - "integrity": "sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", + "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==" }, "needle": { "version": "2.9.1", @@ -30844,22 +30883,14 @@ } }, "streamroller": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", - "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.2.tgz", + "integrity": "sha512-ur6y5S5dopOaRXBuRIZ1u6GC5bcEXHRZKgfBjfCglMhmIf+roVCECjvkEYzNQOXIN2/JPnkMPW/8B3CZoKaEPA==", "devOptional": true, "requires": { - "date-format": "^2.1.0", + "date-format": "^4.0.3", "debug": "^4.1.1", - "fs-extra": "^8.1.0" - }, - "dependencies": { - "date-format": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", - "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", - "devOptional": true - } + "fs-extra": "^10.0.0" } }, "string_decoder": { @@ -31585,7 +31616,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "devOptional": true + "dev": true }, "unpipe": { "version": "1.0.0", diff --git a/frontend/package.json b/frontend/package.json index 44ca13e90..f00594803 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,9 +42,9 @@ "lint": "ng lint", "e2e": "npm run generate-config && ng e2e", "e2e:ci": "npm run cypress:run:ci", - "config:defaults:mempool": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=mempool && npm run generate-config", - "config:defaults:liquid": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=liquid && npm run generate-config", - "config:defaults:bisq": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=bisq && npm run generate-config", + "config:defaults:mempool": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true LIQUID_TESTNET_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=mempool BLOCK_WEIGHT_UNITS=4000000 && npm run generate-config", + "config:defaults:liquid": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true LIQUID_TESTNET_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=liquid BLOCK_WEIGHT_UNITS=300000 && npm run generate-config", + "config:defaults:bisq": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=bisq BLOCK_WEIGHT_UNITS=4000000 && npm run generate-config", "dev:ssr": "npm run generate-config && ng run mempool:serve-ssr", "serve:ssr": "node server.run.js", "build:ssr": "npm run build && ng run mempool:server:production && ./node_modules/typescript/bin/tsc server.run.ts", @@ -117,7 +117,7 @@ }, "optionalDependencies": { "@cypress/schematic": "^1.3.0", - "cypress": "^9.1.1", + "cypress": "^9.3.1", "cypress-fail-on-console-error": "^2.1.3", "cypress-wait-until": "^1.7.1", "mock-socket": "^9.0.3", diff --git a/frontend/proxy.conf.js b/frontend/proxy.conf.js index faae04499..4a0489c77 100644 --- a/frontend/proxy.conf.js +++ b/frontend/proxy.conf.js @@ -61,10 +61,7 @@ PROXY_CONFIG = [ }, { context: ['/api/liquidtestnet**', '/liquidtestnet/api/**'], - target: "https://liquid.network/testnet", - pathRewrite: { - "^/api/liquidtestnet/": "/liquidtestnet/api" - }, + target: "https://liquid.network", ws: true, secure: false, changeOrigin: true @@ -73,7 +70,9 @@ PROXY_CONFIG = [ if (configContent && configContent.BASE_MODULE == "liquid") { PROXY_CONFIG.push({ - context: ['/resources/pools.json', '/resources/assets.json', '/resources/assets.minimal.json'], + context: ['/resources/pools.json', + '/resources/assets.json', '/resources/assets.minimal.json', + '/resources/assets-testnet.json', '/resources/assets-testnet.minimal.json'], target: "https://liquid.network", secure: false, changeOrigin: true, diff --git a/frontend/server.ts b/frontend/server.ts index af27fcd08..df4ab1294 100644 --- a/frontend/server.ts +++ b/frontend/server.ts @@ -6,7 +6,6 @@ import * as express from 'express'; import * as fs from 'fs'; import * as path from 'path'; import * as domino from 'domino'; -import { createProxyMiddleware } from 'http-proxy-middleware'; import { join } from 'path'; import { AppServerModule } from './src/main.server'; @@ -66,6 +65,7 @@ export function app(locale: string): express.Express { server.get('/mempool-block/*', getLocalizedSSR(indexHtml)); server.get('/address/*', getLocalizedSSR(indexHtml)); server.get('/blocks', getLocalizedSSR(indexHtml)); + server.get('/mining/pools', getLocalizedSSR(indexHtml)); server.get('/graphs', getLocalizedSSR(indexHtml)); server.get('/liquid', getLocalizedSSR(indexHtml)); server.get('/liquid/tx/*', getLocalizedSSR(indexHtml)); @@ -86,6 +86,7 @@ export function app(locale: string): express.Express { server.get('/testnet/mempool-block/*', getLocalizedSSR(indexHtml)); server.get('/testnet/address/*', getLocalizedSSR(indexHtml)); server.get('/testnet/blocks', getLocalizedSSR(indexHtml)); + server.get('/testnet/mining/pools', getLocalizedSSR(indexHtml)); server.get('/testnet/graphs', getLocalizedSSR(indexHtml)); server.get('/testnet/api', getLocalizedSSR(indexHtml)); server.get('/testnet/tv', getLocalizedSSR(indexHtml)); @@ -97,6 +98,7 @@ export function app(locale: string): express.Express { server.get('/signet/mempool-block/*', getLocalizedSSR(indexHtml)); server.get('/signet/address/*', getLocalizedSSR(indexHtml)); server.get('/signet/blocks', getLocalizedSSR(indexHtml)); + server.get('/signet/mining/pools', getLocalizedSSR(indexHtml)); server.get('/signet/graphs', getLocalizedSSR(indexHtml)); server.get('/signet/api', getLocalizedSSR(indexHtml)); server.get('/signet/tv', getLocalizedSSR(indexHtml)); diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index c72e6d186..69c368653 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -22,6 +22,7 @@ import { BisqMasterPageComponent } from './components/bisq-master-page/bisq-mast import { SponsorComponent } from './components/sponsor/sponsor.component'; import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component'; import { PushTransactionComponent } from './components/push-transaction/push-transaction.component'; +import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component'; let routes: Routes = [ { @@ -58,6 +59,10 @@ let routes: Routes = [ path: 'blocks', component: LatestBlocksComponent, }, + { + path: 'mining/pools', + component: PoolRankingComponent, + }, { path: 'graphs', component: StatisticsComponent, @@ -142,6 +147,10 @@ let routes: Routes = [ path: 'blocks', component: LatestBlocksComponent, }, + { + path: 'mining/pools', + component: PoolRankingComponent, + }, { path: 'graphs', component: StatisticsComponent, @@ -220,6 +229,10 @@ let routes: Routes = [ path: 'blocks', component: LatestBlocksComponent, }, + { + path: 'mining/pools', + component: PoolRankingComponent, + }, { path: 'graphs', component: StatisticsComponent, diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 3e2c40b25..f9eae0666 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -37,6 +37,7 @@ import { IncomingTransactionsGraphComponent } from './components/incoming-transa import { TimeSpanComponent } from './components/time-span/time-span.component'; import { SeoService } from './services/seo.service'; import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component'; +import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component'; import { LbtcPegsGraphComponent } from './components/lbtc-pegs-graph/lbtc-pegs-graph.component'; import { AssetComponent } from './components/asset/asset.component'; import { AssetsComponent } from './assets/assets.component'; @@ -48,7 +49,7 @@ import { FeesBoxComponent } from './components/fees-box/fees-box.component'; import { DashboardComponent } from './dashboard/dashboard.component'; import { DifficultyComponent } from './components/difficulty/difficulty.component'; import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome'; -import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faDatabase, faExchangeAlt, faInfoCircle, +import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle, faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl } from '@fortawesome/free-solid-svg-icons'; import { ApiDocsComponent } from './components/docs/api-docs.component'; import { DocsComponent } from './components/docs/docs.component'; @@ -91,6 +92,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; FeeDistributionGraphComponent, IncomingTransactionsGraphComponent, MempoolGraphComponent, + PoolRankingComponent, LbtcPegsGraphComponent, AssetComponent, AssetsComponent, @@ -143,6 +145,7 @@ export class AppModule { library.addIcons(faTv); library.addIcons(faTachometerAlt); library.addIcons(faCubes); + library.addIcons(faHammer); library.addIcons(faCogs); library.addIcons(faThList); library.addIcons(faList); diff --git a/frontend/src/app/assets/assets.component.html b/frontend/src/app/assets/assets.component.html index 5d5118af0..c8962cd15 100644 --- a/frontend/src/app/assets/assets.component.html +++ b/frontend/src/app/assets/assets.component.html @@ -43,7 +43,7 @@ Name Ticker - Issuer domain + Issuer domain Asset ID diff --git a/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.html b/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.html index 57862fed6..2b3964caa 100644 --- a/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.html +++ b/frontend/src/app/bisq/bisq-transaction/bisq-transaction.component.html @@ -7,7 +7,7 @@ - + {{ bisqTx.id | shortenString : 24 }} {{ bisqTx.id }} diff --git a/frontend/src/app/bisq/bisq-transactions/bisq-transactions.component.html b/frontend/src/app/bisq/bisq-transactions/bisq-transactions.component.html index f85c29a9b..d1064972e 100644 --- a/frontend/src/app/bisq/bisq-transactions/bisq-transactions.component.html +++ b/frontend/src/app/bisq/bisq-transactions/bisq-transactions.component.html @@ -1,7 +1,7 @@

BSQ Transactions

-
+
@@ -39,7 +39,7 @@ {{ tx.blockHeight }} - + diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index 847ed152a..b9050d41e 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -102,7 +102,7 @@ RoninDojo - + Citadel @@ -220,7 +220,7 @@ -
-
- - @@ -69,6 +62,13 @@
Pegged in {{ formatAmount(asset.chain_stats.peg_in_amount, assetContract[3]) | number: '1.0-' + assetContract[3] }} {{ assetContract[1] }}
+
+
+ + + + +
@@ -109,28 +109,39 @@ - -
- - - - - - - - - - - - -
-
-
-
- - +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
diff --git a/frontend/src/app/components/asset/asset.component.scss b/frontend/src/app/components/asset/asset.component.scss index 270ad97e3..45e68042d 100644 --- a/frontend/src/app/components/asset/asset.component.scss +++ b/frontend/src/app/components/asset/asset.component.scss @@ -50,3 +50,31 @@ h1 { } } +.assetIcon { + height: 150px; + margin: 25px; + @media (min-width: 768px) { + height: 250px; + margin: 0; + } +} + +.icon-holder { + display: flex; + justify-content: center; + align-items: center; +} + +.defaultIcon { + margin: 25px; + height: 150px; +} + +.defaultIcon.skeleton { + opacity: 0.5; +} + +.assetName { + word-break: break-word; + white-space: normal; +} diff --git a/frontend/src/app/components/asset/asset.component.ts b/frontend/src/app/components/asset/asset.component.ts index 74b074d97..ecb216052 100644 --- a/frontend/src/app/components/asset/asset.component.ts +++ b/frontend/src/app/components/asset/asset.component.ts @@ -32,6 +32,7 @@ export class AssetComponent implements OnInit, OnDestroy { isNativeAsset = false; error: any; mainSubscription: Subscription; + imageError = false; totalConfirmedTxCount = 0; loadedConfirmedTxCount = 0; diff --git a/frontend/src/app/components/bisq-master-page/bisq-master-page.component.html b/frontend/src/app/components/bisq-master-page/bisq-master-page.component.html index bd9b989d7..b063ec42a 100644 --- a/frontend/src/app/components/bisq-master-page/bisq-master-page.component.html +++ b/frontend/src/app/components/bisq-master-page/bisq-master-page.component.html @@ -28,22 +28,22 @@
-
+
Current Period
{{ epochData.progress | number: '1.2-2' }} %
 
+
+
Next halving
+
+ + {{ i }} blocks + {{ i }} block +
+
+
diff --git a/frontend/src/app/components/difficulty/difficulty.component.ts b/frontend/src/app/components/difficulty/difficulty.component.ts index 312c1b2d0..ff44e5aeb 100644 --- a/frontend/src/app/components/difficulty/difficulty.component.ts +++ b/frontend/src/app/components/difficulty/difficulty.component.ts @@ -14,6 +14,8 @@ interface EpochProgress { timeAvg: string; remainingTime: number; previousRetarget: number; + blocksUntilHalving: number; + timeUntilHalving: number; } @Component({ @@ -26,6 +28,9 @@ export class DifficultyComponent implements OnInit { isLoadingWebSocket$: Observable; difficultyEpoch$: Observable; + @Input() showProgress: boolean = true; + @Input() showHalving: boolean = false; + constructor( public stateService: StateService, ) { } @@ -92,6 +97,9 @@ export class DifficultyComponent implements OnInit { colorPreviousAdjustments = '#ffffff66'; } + const blocksUntilHalving = block.height % 210000; + const timeUntilHalving = (blocksUntilHalving * timeAvgMins * 60 * 1000) + (now * 1000); + return { base: `${progress}%`, change, @@ -104,6 +112,8 @@ export class DifficultyComponent implements OnInit { newDifficultyHeight, remainingTime, previousRetarget, + blocksUntilHalving, + timeUntilHalving, }; }) ); diff --git a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html index e4f8cba86..9ac3ab5f0 100644 --- a/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html +++ b/frontend/src/app/components/liquid-master-page/liquid-master-page.component.html @@ -29,13 +29,13 @@