diff --git a/backend/package-lock.json b/backend/package-lock.json index 3e9e31988..b3b659459 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -17,7 +17,7 @@ "crypto-js": "~4.2.0", "express": "~4.18.2", "maxmind": "~4.3.11", - "mysql2": "~3.6.0", + "mysql2": "~3.7.0", "redis": "^4.6.6", "rust-gbt": "file:./rust-gbt", "socks-proxy-agent": "~7.0.0", @@ -6110,9 +6110,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mysql2": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.6.0.tgz", - "integrity": "sha512-EWUGAhv6SphezurlfI2Fpt0uJEWLmirrtQR7SkbTHFC+4/mJBrPiSzHESHKAWKG7ALVD6xaG/NBjjd1DGJGQQQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.7.0.tgz", + "integrity": "sha512-c45jA3Jc1X8yJKzrWu1GpplBKGwv/wIV6ITZTlCSY7npF2YfJR+6nMP5e+NTQhUeJPSyOQAbGDCGEHbAl8HN9w==", "dependencies": { "denque": "^2.1.0", "generate-function": "^2.3.1", @@ -12230,9 +12230,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mysql2": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.6.0.tgz", - "integrity": "sha512-EWUGAhv6SphezurlfI2Fpt0uJEWLmirrtQR7SkbTHFC+4/mJBrPiSzHESHKAWKG7ALVD6xaG/NBjjd1DGJGQQQ==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.7.0.tgz", + "integrity": "sha512-c45jA3Jc1X8yJKzrWu1GpplBKGwv/wIV6ITZTlCSY7npF2YfJR+6nMP5e+NTQhUeJPSyOQAbGDCGEHbAl8HN9w==", "requires": { "denque": "^2.1.0", "generate-function": "^2.3.1", diff --git a/backend/package.json b/backend/package.json index 0c1d3cc4a..cd1255392 100644 --- a/backend/package.json +++ b/backend/package.json @@ -35,7 +35,8 @@ "lint": "./node_modules/.bin/eslint . --ext .ts", "lint:fix": "./node_modules/.bin/eslint . --ext .ts --fix", "prettier": "./node_modules/.bin/prettier --write \"src/**/*.{js,ts}\"", - "rust-build": "cd rust-gbt && npm run build-release" + "rust-clean": "cd rust-gbt && rm -f *.node index.d.ts index.js && rm -rf target && cd ../", + "rust-build": "npm run rust-clean && cd rust-gbt && npm run build-release" }, "dependencies": { "@babel/core": "^7.23.2", @@ -46,7 +47,7 @@ "crypto-js": "~4.2.0", "express": "~4.18.2", "maxmind": "~4.3.11", - "mysql2": "~3.6.0", + "mysql2": "~3.7.0", "rust-gbt": "file:./rust-gbt", "redis": "^4.6.6", "socks-proxy-agent": "~7.0.0", diff --git a/backend/rust-gbt/index.d.ts b/backend/rust-gbt/index.d.ts index 2bd8a620a..d1cb85b92 100644 --- a/backend/rust-gbt/index.d.ts +++ b/backend/rust-gbt/index.d.ts @@ -45,5 +45,6 @@ export class GbtResult { blockWeights: Array clusters: Array> rates: Array> - constructor(blocks: Array>, blockWeights: Array, clusters: Array>, rates: Array>) + overflow: Array + constructor(blocks: Array>, blockWeights: Array, clusters: Array>, rates: Array>, overflow: Array) } diff --git a/backend/rust-gbt/src/gbt.rs b/backend/rust-gbt/src/gbt.rs index fb28dc299..38bf826a6 100644 --- a/backend/rust-gbt/src/gbt.rs +++ b/backend/rust-gbt/src/gbt.rs @@ -60,6 +60,7 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat indexed_accelerations[acceleration.uid as usize] = Some(acceleration); } + info!("Initializing working vecs with uid capacity for {}", max_uid + 1); let mempool_len = mempool.len(); let mut audit_pool: AuditPool = Vec::with_capacity(max_uid + 1); audit_pool.resize(max_uid + 1, None); @@ -127,74 +128,75 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat let next_from_stack = next_valid_from_stack(&mut mempool_stack, &audit_pool); let next_from_queue = next_valid_from_queue(&mut modified, &audit_pool); if next_from_stack.is_none() && next_from_queue.is_none() { - continue; - } - let (next_tx, from_stack) = match (next_from_stack, next_from_queue) { - (Some(stack_tx), Some(queue_tx)) => match queue_tx.cmp(stack_tx) { - std::cmp::Ordering::Less => (stack_tx, true), - _ => (queue_tx, false), - }, - (Some(stack_tx), None) => (stack_tx, true), - (None, Some(queue_tx)) => (queue_tx, false), - (None, None) => unreachable!(), - }; - - if from_stack { - mempool_stack.pop(); + info!("No transactions left! {:#?} in overflow", overflow.len()); } else { - modified.pop(); - } + let (next_tx, from_stack) = match (next_from_stack, next_from_queue) { + (Some(stack_tx), Some(queue_tx)) => match queue_tx.cmp(stack_tx) { + std::cmp::Ordering::Less => (stack_tx, true), + _ => (queue_tx, false), + }, + (Some(stack_tx), None) => (stack_tx, true), + (None, Some(queue_tx)) => (queue_tx, false), + (None, None) => unreachable!(), + }; - if blocks.len() < (MAX_BLOCKS - 1) - && ((block_weight + (4 * next_tx.ancestor_sigop_adjusted_vsize()) - >= MAX_BLOCK_WEIGHT_UNITS) - || (block_sigops + next_tx.ancestor_sigops() > BLOCK_SIGOPS)) - { - // hold this package in an overflow list while we check for smaller options - overflow.push(next_tx.uid); - failures += 1; - } else { - let mut package: Vec<(u32, u32, usize)> = Vec::new(); - let mut cluster: Vec = Vec::new(); - let is_cluster: bool = !next_tx.ancestors.is_empty(); - for ancestor_id in &next_tx.ancestors { - if let Some(Some(ancestor)) = audit_pool.get(*ancestor_id as usize) { - package.push((*ancestor_id, ancestor.order(), ancestor.ancestors.len())); - } - } - package.sort_unstable_by(|a, b| -> Ordering { - if a.2 != b.2 { - // order by ascending ancestor count - a.2.cmp(&b.2) - } else if a.1 != b.1 { - // tie-break by ascending partial txid - a.1.cmp(&b.1) - } else { - // tie-break partial txid collisions by ascending uid - a.0.cmp(&b.0) - } - }); - package.push((next_tx.uid, next_tx.order(), next_tx.ancestors.len())); - - let cluster_rate = next_tx.cluster_rate(); - - for (txid, _, _) in &package { - cluster.push(*txid); - if let Some(Some(tx)) = audit_pool.get_mut(*txid as usize) { - tx.used = true; - tx.set_dirty_if_different(cluster_rate); - transactions.push(tx.uid); - block_weight += tx.weight; - block_sigops += tx.sigops; - } - update_descendants(*txid, &mut audit_pool, &mut modified, cluster_rate); + if from_stack { + mempool_stack.pop(); + } else { + modified.pop(); } - if is_cluster { - clusters.push(cluster); - } + if blocks.len() < (MAX_BLOCKS - 1) + && ((block_weight + (4 * next_tx.ancestor_sigop_adjusted_vsize()) + >= MAX_BLOCK_WEIGHT_UNITS) + || (block_sigops + next_tx.ancestor_sigops() > BLOCK_SIGOPS)) + { + // hold this package in an overflow list while we check for smaller options + overflow.push(next_tx.uid); + failures += 1; + } else { + let mut package: Vec<(u32, u32, usize)> = Vec::new(); + let mut cluster: Vec = Vec::new(); + let is_cluster: bool = !next_tx.ancestors.is_empty(); + for ancestor_id in &next_tx.ancestors { + if let Some(Some(ancestor)) = audit_pool.get(*ancestor_id as usize) { + package.push((*ancestor_id, ancestor.order(), ancestor.ancestors.len())); + } + } + package.sort_unstable_by(|a, b| -> Ordering { + if a.2 != b.2 { + // order by ascending ancestor count + a.2.cmp(&b.2) + } else if a.1 != b.1 { + // tie-break by ascending partial txid + a.1.cmp(&b.1) + } else { + // tie-break partial txid collisions by ascending uid + a.0.cmp(&b.0) + } + }); + package.push((next_tx.uid, next_tx.order(), next_tx.ancestors.len())); - failures = 0; + let cluster_rate = next_tx.cluster_rate(); + + for (txid, _, _) in &package { + cluster.push(*txid); + if let Some(Some(tx)) = audit_pool.get_mut(*txid as usize) { + tx.used = true; + tx.set_dirty_if_different(cluster_rate); + transactions.push(tx.uid); + block_weight += tx.weight; + block_sigops += tx.sigops; + } + update_descendants(*txid, &mut audit_pool, &mut modified, cluster_rate); + } + + if is_cluster { + clusters.push(cluster); + } + + failures = 0; + } } // this block is full @@ -203,10 +205,14 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat let queue_is_empty = mempool_stack.is_empty() && modified.is_empty(); if (exceeded_package_tries || queue_is_empty) && blocks.len() < (MAX_BLOCKS - 1) { // finalize this block - if !transactions.is_empty() { - blocks.push(transactions); - block_weights.push(block_weight); + if transactions.is_empty() { + info!("trying to push an empty block! breaking loop! mempool {:#?} | modified {:#?} | overflow {:#?}", mempool_stack.len(), modified.len(), overflow.len()); + break; } + + blocks.push(transactions); + block_weights.push(block_weight); + // reset for the next block transactions = Vec::with_capacity(initial_txes_per_block); block_weight = BLOCK_RESERVED_WEIGHT; @@ -265,6 +271,7 @@ pub fn gbt(mempool: &mut ThreadTransactionsMap, accelerations: &[ThreadAccelerat block_weights, clusters, rates, + overflow, } } diff --git a/backend/rust-gbt/src/lib.rs b/backend/rust-gbt/src/lib.rs index 53db0ba21..edc9714ee 100644 --- a/backend/rust-gbt/src/lib.rs +++ b/backend/rust-gbt/src/lib.rs @@ -133,6 +133,7 @@ pub struct GbtResult { pub block_weights: Vec, pub clusters: Vec>, pub rates: Vec>, // Tuples not supported. u32 fits inside f64 + pub overflow: Vec, } /// All on another thread, this runs an arbitrary task in between diff --git a/backend/src/api/bitcoin/bitcoin-core.routes.ts b/backend/src/api/bitcoin/bitcoin-core.routes.ts new file mode 100644 index 000000000..7933dc17b --- /dev/null +++ b/backend/src/api/bitcoin/bitcoin-core.routes.ts @@ -0,0 +1,249 @@ +import { Application, NextFunction, Request, Response } from 'express'; +import logger from '../../logger'; +import bitcoinClient from './bitcoin-client'; + +/** + * Define a set of routes used by the accelerator server + * Those routes are not designed to be public + */ +class BitcoinBackendRoutes { + private static tag = 'BitcoinBackendRoutes'; + + public initRoutes(app: Application) { + app + .get('/api/internal/bitcoin-core/' + 'get-mempool-entry', this.disableCache, this.$getMempoolEntry) + .post('/api/internal/bitcoin-core/' + 'decode-raw-transaction', this.disableCache, this.$decodeRawTransaction) + .get('/api/internal/bitcoin-core/' + 'get-raw-transaction', this.disableCache, this.$getRawTransaction) + .post('/api/internal/bitcoin-core/' + 'send-raw-transaction', this.disableCache, this.$sendRawTransaction) + .post('/api/internal/bitcoin-core/' + 'test-mempool-accept', this.disableCache, this.$testMempoolAccept) + .get('/api/internal/bitcoin-core/' + 'get-mempool-ancestors', this.disableCache, this.$getMempoolAncestors) + .get('/api/internal/bitcoin-core/' + 'get-block', this.disableCache, this.$getBlock) + .get('/api/internal/bitcoin-core/' + 'get-block-hash', this.disableCache, this.$getBlockHash) + .get('/api/internal/bitcoin-core/' + 'get-block-count', this.disableCache, this.$getBlockCount) + ; + } + + /** + * Disable caching for bitcoin core routes + * + * @param req + * @param res + * @param next + */ + private disableCache(req: Request, res: Response, next: NextFunction): void { + res.setHeader('Pragma', 'no-cache'); + res.setHeader('Cache-control', 'private, no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'); + res.setHeader('expires', -1); + next(); + } + + /** + * Exeption handler to return proper details to the accelerator server + * + * @param e + * @param fnName + * @param res + */ + private static handleException(e: any, fnName: string, res: Response): void { + if (typeof(e.code) === 'number') { + res.status(400).send(JSON.stringify(e, ['code', 'message'])); + } else { + const err = `exception in ${fnName}. ${e}. Details: ${JSON.stringify(e, ['code', 'message'])}`; + logger.err(err, BitcoinBackendRoutes.tag); + res.status(500).send(err); + } + } + + private async $getMempoolEntry(req: Request, res: Response): Promise { + const txid = req.query.txid; + try { + if (typeof(txid) !== 'string' || txid.length !== 64) { + res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`); + return; + } + const mempoolEntry = await bitcoinClient.getMempoolEntry(txid); + if (!mempoolEntry) { + res.status(404).send(`no mempool entry found for txid ${txid}`); + return; + } + res.status(200).send(mempoolEntry); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'getMempoolEntry', res); + } + } + + private async $decodeRawTransaction(req: Request, res: Response): Promise { + const rawTx = req.body.rawTx; + try { + if (typeof(rawTx) !== 'string') { + res.status(400).send(`invalid param rawTx ${rawTx}. must be a string`); + return; + } + const decodedTx = await bitcoinClient.decodeRawTransaction(rawTx); + if (!decodedTx) { + res.status(400).send(`unable to decode rawTx ${rawTx}`); + return; + } + res.status(200).send(decodedTx); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'decodeRawTransaction', res); + } + } + + private async $getRawTransaction(req: Request, res: Response): Promise { + const txid = req.query.txid; + const verbose = req.query.verbose; + try { + if (typeof(txid) !== 'string' || txid.length !== 64) { + res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`); + return; + } + if (typeof(verbose) !== 'string') { + res.status(400).send(`invalid param verbose ${verbose}. must be a string representing an integer`); + return; + } + const verboseNumber = parseInt(verbose, 10); + if (typeof(verboseNumber) !== 'number') { + res.status(400).send(`invalid param verbose ${verbose}. must be a valid integer`); + return; + } + + const decodedTx = await bitcoinClient.getRawTransaction(txid, verboseNumber); + if (!decodedTx) { + res.status(400).send(`unable to get raw transaction for txid ${txid}`); + return; + } + res.status(200).send(decodedTx); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'decodeRawTransaction', res); + } + } + + private async $sendRawTransaction(req: Request, res: Response): Promise { + const rawTx = req.body.rawTx; + try { + if (typeof(rawTx) !== 'string') { + res.status(400).send(`invalid param rawTx ${rawTx}. must be a string`); + return; + } + const txHex = await bitcoinClient.sendRawTransaction(rawTx); + if (!txHex) { + res.status(400).send(`unable to send rawTx ${rawTx}`); + return; + } + res.status(200).send(txHex); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'sendRawTransaction', res); + } + } + + private async $testMempoolAccept(req: Request, res: Response): Promise { + const rawTxs = req.body.rawTxs; + try { + if (typeof(rawTxs) !== 'object') { + res.status(400).send(`invalid param rawTxs ${JSON.stringify(rawTxs)}. must be an array of string`); + return; + } + const txHex = await bitcoinClient.testMempoolAccept(rawTxs); + if (typeof(txHex) !== 'object' || txHex.length === 0) { + res.status(400).send(`testmempoolaccept failed for raw txs ${JSON.stringify(rawTxs)}, got an empty result`); + return; + } + res.status(200).send(txHex); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'testMempoolAccept', res); + } + } + + private async $getMempoolAncestors(req: Request, res: Response): Promise { + const txid = req.query.txid; + const verbose = req.query.verbose; + try { + if (typeof(txid) !== 'string' || txid.length !== 64) { + res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`); + return; + } + if (typeof(verbose) !== 'string' || (verbose !== 'true' && verbose !== 'false')) { + res.status(400).send(`invalid param verbose ${verbose}. must be a string ('true' | 'false')`); + return; + } + + const ancestors = await bitcoinClient.getMempoolAncestors(txid, verbose === 'true' ? true : false); + if (!ancestors) { + res.status(400).send(`unable to get mempool ancestors for txid ${txid}`); + return; + } + res.status(200).send(ancestors); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'getMempoolAncestors', res); + } + } + + private async $getBlock(req: Request, res: Response): Promise { + const blockHash = req.query.hash; + const verbosity = req.query.verbosity; + try { + if (typeof(blockHash) !== 'string' || blockHash.length !== 64) { + res.status(400).send(`invalid param blockHash ${blockHash}. must be a string of 64 char`); + return; + } + if (typeof(verbosity) !== 'string') { + res.status(400).send(`invalid param verbosity ${verbosity}. must be a string representing an integer`); + return; + } + const verbosityNumber = parseInt(verbosity, 10); + if (typeof(verbosityNumber) !== 'number') { + res.status(400).send(`invalid param verbosity ${verbosity}. must be a valid integer`); + return; + } + + const block = await bitcoinClient.getBlock(blockHash, verbosityNumber); + if (!block) { + res.status(400).send(`unable to get block for block hash ${blockHash}`); + return; + } + res.status(200).send(block); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'getBlock', res); + } + } + + private async $getBlockHash(req: Request, res: Response): Promise { + const blockHeight = req.query.height; + try { + if (typeof(blockHeight) !== 'string') { + res.status(400).send(`invalid param blockHeight ${blockHeight}, must be a string representing an integer`); + return; + } + const blockHeightNumber = parseInt(blockHeight, 10); + if (typeof(blockHeightNumber) !== 'number') { + res.status(400).send(`invalid param blockHeight ${blockHeight}. must be a valid integer`); + return; + } + + const block = await bitcoinClient.getBlockHash(blockHeightNumber); + if (!block) { + res.status(400).send(`unable to get block hash for block height ${blockHeightNumber}`); + return; + } + res.status(200).send(block); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'getBlockHash', res); + } + } + + private async $getBlockCount(req: Request, res: Response): Promise { + try { + const count = await bitcoinClient.getBlockCount(); + if (!count) { + res.status(400).send(`unable to get block count`); + return; + } + res.status(200).send(`${count}`); + } catch (e: any) { + BitcoinBackendRoutes.handleException(e, 'getBlockCount', res); + } + } +} + +export default new BitcoinBackendRoutes \ No newline at end of file diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 358a98c98..af93b9622 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -263,8 +263,13 @@ export class Common { case 'v0_p2wsh': flags |= TransactionFlags.p2wsh; break; case 'v1_p2tr': { flags |= TransactionFlags.p2tr; - if (vin.witness.length > 2) { - const asm = vin.inner_witnessscript_asm || transactionUtils.convertScriptSigAsm(vin.witness[vin.witness.length - 2]); + // in taproot, if the last witness item begins with 0x50, it's an annex + const hasAnnex = vin.witness?.[vin.witness.length - 1].startsWith('50'); + // script spends have more than one witness item, not counting the annex (if present) + if (vin.witness.length > (hasAnnex ? 2 : 1)) { + // the script itself is the second-to-last witness item, not counting the annex + const asm = vin.inner_witnessscript_asm || transactionUtils.convertScriptSigAsm(vin.witness[vin.witness.length - (hasAnnex ? 3 : 2)]); + // inscriptions smuggle data within an 'OP_0 OP_IF ... OP_ENDIF' envelope if (asm?.includes('OP_0 OP_IF')) { flags |= TransactionFlags.inscription; } diff --git a/backend/src/api/fee-api.ts b/backend/src/api/fee-api.ts index 0cab5a295..24fd25a4b 100644 --- a/backend/src/api/fee-api.ts +++ b/backend/src/api/fee-api.ts @@ -39,15 +39,25 @@ class FeeApi { const secondMedianFee = pBlocks[1] ? this.optimizeMedianFee(pBlocks[1], pBlocks[2], firstMedianFee) : this.defaultFee; const thirdMedianFee = pBlocks[2] ? this.optimizeMedianFee(pBlocks[2], pBlocks[3], secondMedianFee) : this.defaultFee; + let fastestFee = Math.max(minimumFee, firstMedianFee); + let halfHourFee = Math.max(minimumFee, secondMedianFee); + let hourFee = Math.max(minimumFee, thirdMedianFee); + const economyFee = Math.max(minimumFee, Math.min(2 * minimumFee, thirdMedianFee)); + + // ensure recommendations always increase w/ priority + fastestFee = Math.max(fastestFee, halfHourFee, hourFee, economyFee); + halfHourFee = Math.max(halfHourFee, hourFee, economyFee); + hourFee = Math.max(hourFee, economyFee); + // explicitly enforce a minimum of ceil(mempoolminfee) on all recommendations. // simply rounding up recommended rates is insufficient, as the purging rate // can exceed the median rate of projected blocks in some extreme scenarios // (see https://bitcoin.stackexchange.com/a/120024) return { - 'fastestFee': Math.max(minimumFee, firstMedianFee), - 'halfHourFee': Math.max(minimumFee, secondMedianFee), - 'hourFee': Math.max(minimumFee, thirdMedianFee), - 'economyFee': Math.max(minimumFee, Math.min(2 * minimumFee, thirdMedianFee)), + 'fastestFee': fastestFee, + 'halfHourFee': halfHourFee, + 'hourFee': hourFee, + 'economyFee': economyFee, 'minimumFee': minimumFee, }; } diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index a7f00f6e8..0ca550f4c 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -368,12 +368,15 @@ class MempoolBlocks { // run the block construction algorithm in a separate thread, and wait for a result const rustGbt = saveResults ? this.rustGbtGenerator : new GbtGenerator(); try { - const { blocks, blockWeights, rates, clusters } = this.convertNapiResultTxids( + const { blocks, blockWeights, rates, clusters, overflow } = this.convertNapiResultTxids( await rustGbt.make(Object.values(newMempool) as RustThreadTransaction[], convertedAccelerations as RustThreadAcceleration[], this.nextUid), ); if (saveResults) { this.rustInitialized = true; } + const mempoolSize = Object.keys(newMempool).length; + const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0) + overflow.length; + logger.debug(`RUST updateBlockTemplates returned ${resultMempoolSize} txs out of ${mempoolSize} in the mempool, ${overflow.length} were unmineable`); const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, accelerations, accelerationPool, saveResults); logger.debug(`RUST makeBlockTemplates completed in ${(Date.now() - start)/1000} seconds`); return processed; @@ -424,7 +427,7 @@ class MempoolBlocks { // run the block construction algorithm in a separate thread, and wait for a result try { - const { blocks, blockWeights, rates, clusters } = this.convertNapiResultTxids( + const { blocks, blockWeights, rates, clusters, overflow } = this.convertNapiResultTxids( await this.rustGbtGenerator.update( added as RustThreadTransaction[], removedUids, @@ -432,9 +435,10 @@ class MempoolBlocks { this.nextUid, ), ); - const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0); + const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0) + overflow.length; + logger.debug(`RUST updateBlockTemplates returned ${resultMempoolSize} txs out of ${mempoolSize} in the mempool, ${overflow.length} were unmineable`); if (mempoolSize !== resultMempoolSize) { - throw new Error('GBT returned wrong number of transactions, cache is probably out of sync'); + throw new Error('GBT returned wrong number of transactions , cache is probably out of sync'); } else { const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, accelerations, accelerationPool, true); this.removeUids(removedUids); @@ -658,8 +662,8 @@ class MempoolBlocks { return { blocks: convertedBlocks, rates: convertedRates, clusters: convertedClusters } as { blocks: string[][], rates: { [root: string]: number }, clusters: { [root: string]: string[] }}; } - private convertNapiResultTxids({ blocks, blockWeights, rates, clusters }: GbtResult) - : { blocks: string[][], blockWeights: number[], rates: [string, number][], clusters: string[][] } { + private convertNapiResultTxids({ blocks, blockWeights, rates, clusters, overflow }: GbtResult) + : { blocks: string[][], blockWeights: number[], rates: [string, number][], clusters: string[][], overflow: string[] } { const convertedBlocks: string[][] = blocks.map(block => block.map(uid => { const txid = this.uidMap.get(uid); if (txid !== undefined) { @@ -677,7 +681,15 @@ class MempoolBlocks { for (const cluster of clusters) { convertedClusters.push(cluster.map(uid => this.uidMap.get(uid)) as string[]); } - return { blocks: convertedBlocks, blockWeights, rates: convertedRates, clusters: convertedClusters }; + const convertedOverflow: string[] = overflow.map(uid => { + const txid = this.uidMap.get(uid); + if (txid !== undefined) { + return txid; + } else { + throw new Error('GBT returned an unmineable transaction with unknown uid'); + } + }); + return { blocks: convertedBlocks, blockWeights, rates: convertedRates, clusters: convertedClusters, overflow: convertedOverflow }; } } diff --git a/backend/src/api/tx-selection-worker.ts b/backend/src/api/tx-selection-worker.ts index 0acc2f65e..8ac7328fe 100644 --- a/backend/src/api/tx-selection-worker.ts +++ b/backend/src/api/tx-selection-worker.ts @@ -173,10 +173,13 @@ function makeBlockTemplates(mempool: Map) // this block is full const exceededPackageTries = failures > 1000 && blockWeight > (config.MEMPOOL.BLOCK_WEIGHT_UNITS - 4000); const queueEmpty = top >= mempoolArray.length && modified.isEmpty(); + if ((exceededPackageTries || queueEmpty) && blocks.length < 7) { // construct this block if (transactions.length) { blocks.push(transactions.map(t => t.uid)); + } else { + break; } // reset for the next block transactions = []; diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 34d4682d2..937d4a7c5 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -968,7 +968,7 @@ class WebsocketHandler { if (client['track-tx']) { numTxSubs++; } - if (client['track-mempool-block'] >= 0) { + if (client['track-mempool-block'] != null && client['track-mempool-block'] >= 0) { numProjectedSubs++; } if (client['track-rbf']) { diff --git a/backend/src/index.ts b/backend/src/index.ts index 44fe87e3a..a7b2ad4df 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -44,6 +44,7 @@ import v8 from 'v8'; import { formatBytes, getBytesUnit } from './utils/format'; import redisCache from './api/redis-cache'; import accelerationApi from './api/services/acceleration'; +import bitcoinCoreRoutes from './api/bitcoin/bitcoin-core.routes'; class Server { private wss: WebSocket.Server | undefined; @@ -282,6 +283,7 @@ class Server { setUpHttpApiRoutes(): void { bitcoinRoutes.initRoutes(this.app); + bitcoinCoreRoutes.initRoutes(this.app); pricesRoutes.initRoutes(this.app); if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED && config.MEMPOOL.ENABLED) { statisticsRoutes.initRoutes(this.app); diff --git a/backend/src/rpc-api/commands.ts b/backend/src/rpc-api/commands.ts index 78f5e12f4..ecfb2ed7c 100644 --- a/backend/src/rpc-api/commands.ts +++ b/backend/src/rpc-api/commands.ts @@ -91,4 +91,5 @@ module.exports = { walletPassphraseChange: 'walletpassphrasechange', getTxoutSetinfo: 'gettxoutsetinfo', getIndexInfo: 'getindexinfo', + testMempoolAccept: 'testmempoolaccept', }; diff --git a/backend/src/tasks/price-feeds/coinbase-api.ts b/backend/src/tasks/price-feeds/coinbase-api.ts index 424ac8867..d2c6d063a 100644 --- a/backend/src/tasks/price-feeds/coinbase-api.ts +++ b/backend/src/tasks/price-feeds/coinbase-api.ts @@ -5,14 +5,14 @@ class CoinbaseApi implements PriceFeed { public name: string = 'Coinbase'; public currencies: string[] = ['USD', 'EUR', 'GBP']; - public url: string = 'https://api.coinbase.com/v2/prices/spot?currency='; + public url: string = 'https://api.coinbase.com/v2/prices/BTC-{CURRENCY}/spot'; public urlHist: string = 'https://api.exchange.coinbase.com/products/BTC-{CURRENCY}/candles?granularity={GRANULARITY}'; constructor() { } public async $fetchPrice(currency): Promise { - const response = await query(this.url + currency); + const response = await query(this.url.replace('{CURRENCY}', currency)); if (response && response['data'] && response['data']['amount']) { return parseInt(response['data']['amount'], 10); } else { diff --git a/backend/src/tasks/price-updater.ts b/backend/src/tasks/price-updater.ts index fd799fb87..0d5ca5958 100644 --- a/backend/src/tasks/price-updater.ts +++ b/backend/src/tasks/price-updater.ts @@ -23,6 +23,14 @@ export interface PriceHistory { [timestamp: number]: ApiPrice; } +function getMedian(arr: number[]): number { + const sortedArr = arr.slice().sort((a, b) => a - b); + const mid = Math.floor(sortedArr.length / 2); + return sortedArr.length % 2 !== 0 + ? sortedArr[mid] + : (sortedArr[mid - 1] + sortedArr[mid]) / 2; +} + class PriceUpdater { public historyInserted = false; private timeBetweenUpdatesMs = 360_0000 / config.MEMPOOL.PRICE_UPDATES_PER_HOUR; @@ -173,7 +181,7 @@ class PriceUpdater { if (prices.length === 0) { this.latestPrices[currency] = -1; } else { - this.latestPrices[currency] = Math.round((prices.reduce((partialSum, a) => partialSum + a, 0)) / prices.length); + this.latestPrices[currency] = Math.round(getMedian(prices)); } } @@ -300,9 +308,7 @@ class PriceUpdater { if (grouped[time][currency].length === 0) { continue; } - prices[currency] = Math.round((grouped[time][currency].reduce( - (partialSum, a) => partialSum + a, 0) - ) / grouped[time][currency].length); + prices[currency] = Math.round(getMedian(grouped[time][currency])); } await PricesRepository.$savePrices(parseInt(time, 10), prices); ++totalInserted; diff --git a/contributors/ncois.txt b/contributors/natsee.txt similarity index 91% rename from contributors/ncois.txt rename to contributors/natsee.txt index 04a436613..c391ce823 100644 --- a/contributors/ncois.txt +++ b/contributors/natsee.txt @@ -1,3 +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 November 16, 2023. -Signed: ncois +Signed: natsee diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b02ee1c50..f49fd614a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -58,8 +58,8 @@ "optionalDependencies": { "@cypress/schematic": "^2.5.0", "@types/cypress": "^1.1.3", - "cypress": "^13.6.0", - "cypress-fail-on-console-error": "~5.0.0", + "cypress": "^13.6.2", + "cypress-fail-on-console-error": "~5.1.0", "cypress-wait-until": "^2.0.1", "mock-socket": "~9.3.1", "start-server-and-test": "~2.0.0" @@ -4068,9 +4068,9 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", "optional": true, "dependencies": { "@sinonjs/commons": "^3.0.0" @@ -6242,17 +6242,18 @@ "optional": true }, "node_modules/chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.0.tgz", + "integrity": "sha512-x9cHNq1uvkCdU+5xTkNh5WtgD4e4yDFCsp9jVc7N7qVeKeftv3gO/ZrviX5d+3ZfxdYnZXZYujjRInu1RogU6A==", "optional": true, "dependencies": { "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "type-detect": "^4.0.8" }, "engines": { "node": ">=4" @@ -6277,10 +6278,13 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "optional": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, "engines": { "node": "*" } @@ -7079,9 +7083,9 @@ "peer": true }, "node_modules/cypress": { - "version": "13.6.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.0.tgz", - "integrity": "sha512-quIsnFmtj4dBUEJYU4OH0H12bABJpSujvWexC24Ju1gTlKMJbeT6tTO0vh7WNfiBPPjoIXLN+OUqVtiKFs6SGw==", + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.2.tgz", + "integrity": "sha512-TW3bGdPU4BrfvMQYv1z3oMqj71YI4AlgJgnrycicmPZAXtvywVFZW9DAToshO65D97rCWfG/kqMFsYB6Kp91gQ==", "hasInstallScript": true, "optional": true, "dependencies": { @@ -7137,13 +7141,13 @@ } }, "node_modules/cypress-fail-on-console-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-5.0.0.tgz", - "integrity": "sha512-xui/aSu8rmExZjZNgId3iX0MsGZih6ZoFH+54vNHrK3HaqIZZX5hUuNhAcmfSoM1rIDc2DeITeVaMn/hiQ9IWQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-5.1.0.tgz", + "integrity": "sha512-u/AXLE9obLd9KcGHkGJluJVZeOj1EEOFOs0URxxca4FrftUDJQ3u+IoNfjRUjsrBKmJxgM4vKd0G10D+ZT1uIA==", "optional": true, "dependencies": { - "chai": "^4.3.4", - "sinon": "^15.0.0", + "chai": "^4.3.10", + "sinon": "^17.0.0", "sinon-chai": "^3.7.0", "type-detect": "^4.0.8" } @@ -7403,15 +7407,15 @@ "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" }, "node_modules/deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", "optional": true, "dependencies": { "type-detect": "^4.0.0" }, "engines": { - "node": ">=0.12" + "node": ">=6" } }, "node_modules/deep-equal": { @@ -9268,9 +9272,9 @@ "devOptional": true }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "funding": [ { "type": "individual", @@ -11759,6 +11763,15 @@ "node": ">=8.0" } }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "optional": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -12537,9 +12550,9 @@ } }, "node_modules/nise": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", - "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.5.tgz", + "integrity": "sha512-VJuPIfUFaXNRzETTQEEItTOP8Y171ijr+JLq42wHes3DiryR8vT+1TXQW/Rx8JNUhyYYWyIvjXTU6dOhJcs9Nw==", "optional": true, "dependencies": { "@sinonjs/commons": "^2.0.0", @@ -12558,6 +12571,24 @@ "type-detect": "4.0.8" } }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "optional": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "optional": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, "node_modules/nise/node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -14842,16 +14873,16 @@ ] }, "node_modules/sinon": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.2.0.tgz", - "integrity": "sha512-nPS85arNqwBXaIsFCkolHjGIkFo+Oxu9vbgmBJizLAhqe6P2o3Qmj3KCUoRkfhHtvgDhZdWD3risLHAUJ8npjw==", + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", "optional": true, "dependencies": { "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/fake-timers": "^11.2.2", "@sinonjs/samsam": "^8.0.0", "diff": "^5.1.0", - "nise": "^5.1.4", + "nise": "^5.1.5", "supports-color": "^7.2.0" }, "funding": { @@ -19882,9 +19913,9 @@ } }, "@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", "optional": true, "requires": { "@sinonjs/commons": "^3.0.0" @@ -21594,17 +21625,18 @@ "optional": true }, "chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.0.tgz", + "integrity": "sha512-x9cHNq1uvkCdU+5xTkNh5WtgD4e4yDFCsp9jVc7N7qVeKeftv3gO/ZrviX5d+3ZfxdYnZXZYujjRInu1RogU6A==", "optional": true, "requires": { "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "type-detect": "^4.0.8" } }, "chalk": { @@ -21623,10 +21655,13 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "optional": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "optional": true, + "requires": { + "get-func-name": "^2.0.2" + } }, "check-more-types": { "version": "2.24.0", @@ -22237,9 +22272,9 @@ "peer": true }, "cypress": { - "version": "13.6.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.0.tgz", - "integrity": "sha512-quIsnFmtj4dBUEJYU4OH0H12bABJpSujvWexC24Ju1gTlKMJbeT6tTO0vh7WNfiBPPjoIXLN+OUqVtiKFs6SGw==", + "version": "13.6.2", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.2.tgz", + "integrity": "sha512-TW3bGdPU4BrfvMQYv1z3oMqj71YI4AlgJgnrycicmPZAXtvywVFZW9DAToshO65D97rCWfG/kqMFsYB6Kp91gQ==", "optional": true, "requires": { "@cypress/request": "^3.0.0", @@ -22403,13 +22438,13 @@ } }, "cypress-fail-on-console-error": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-5.0.0.tgz", - "integrity": "sha512-xui/aSu8rmExZjZNgId3iX0MsGZih6ZoFH+54vNHrK3HaqIZZX5hUuNhAcmfSoM1rIDc2DeITeVaMn/hiQ9IWQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-5.1.0.tgz", + "integrity": "sha512-u/AXLE9obLd9KcGHkGJluJVZeOj1EEOFOs0URxxca4FrftUDJQ3u+IoNfjRUjsrBKmJxgM4vKd0G10D+ZT1uIA==", "optional": true, "requires": { - "chai": "^4.3.4", - "sinon": "^15.0.0", + "chai": "^4.3.10", + "sinon": "^17.0.0", "sinon-chai": "^3.7.0", "type-detect": "^4.0.8" } @@ -22490,9 +22525,9 @@ "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" }, "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", "optional": true, "requires": { "type-detect": "^4.0.0" @@ -23957,9 +23992,9 @@ "devOptional": true }, "follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==" }, "foreach": { "version": "2.0.5", @@ -25754,6 +25789,15 @@ "streamroller": "^3.0.2" } }, + "loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "optional": true, + "requires": { + "get-func-name": "^2.0.1" + } + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -26361,9 +26405,9 @@ } }, "nise": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.4.tgz", - "integrity": "sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.5.tgz", + "integrity": "sha512-VJuPIfUFaXNRzETTQEEItTOP8Y171ijr+JLq42wHes3DiryR8vT+1TXQW/Rx8JNUhyYYWyIvjXTU6dOhJcs9Nw==", "optional": true, "requires": { "@sinonjs/commons": "^2.0.0", @@ -26382,6 +26426,26 @@ "type-detect": "4.0.8" } }, + "@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "optional": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "optional": true, + "requires": { + "type-detect": "4.0.8" + } + } + } + }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -28036,16 +28100,16 @@ "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" }, "sinon": { - "version": "15.2.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.2.0.tgz", - "integrity": "sha512-nPS85arNqwBXaIsFCkolHjGIkFo+Oxu9vbgmBJizLAhqe6P2o3Qmj3KCUoRkfhHtvgDhZdWD3risLHAUJ8npjw==", + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", "optional": true, "requires": { "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/fake-timers": "^11.2.2", "@sinonjs/samsam": "^8.0.0", "diff": "^5.1.0", - "nise": "^5.1.4", + "nise": "^5.1.5", "supports-color": "^7.2.0" }, "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index a4a4ac462..330250871 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -110,8 +110,8 @@ "optionalDependencies": { "@cypress/schematic": "^2.5.0", "@types/cypress": "^1.1.3", - "cypress": "^13.6.0", - "cypress-fail-on-console-error": "~5.0.0", + "cypress": "^13.6.2", + "cypress-fail-on-console-error": "~5.1.0", "cypress-wait-until": "^2.0.1", "mock-socket": "~9.3.1", "start-server-and-test": "~2.0.0" diff --git a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.html b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.html index 9ae0ddade..98095aa07 100644 --- a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.html +++ b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.html @@ -29,7 +29,7 @@
-
Out-of-band Fees Per Block
+
Total Bid Boost
diff --git a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts index d27b10690..4460bffe4 100644 --- a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts +++ b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts @@ -81,7 +81,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy { }), map(([accelerations, blockFeesResponse]) => { return { - avgFeesPaid: accelerations.filter(acc => acc.status === 'completed').reduce((total, acc) => total + acc.feePaid, 0) / accelerations.length + avgFeesPaid: accelerations.filter(acc => acc.status === 'completed').reduce((total, acc) => total + (acc.feePaid - acc.baseFee - acc.vsizeFee), 0) / accelerations.length }; }), ); @@ -151,7 +151,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy { while (last <= val.avgHeight) { blockCount++; totalFeeDelta += (blockAccelerations[last] || []).reduce((total, acc) => total + acc.feeDelta, 0); - totalFeePaid += (blockAccelerations[last] || []).reduce((total, acc) => total + acc.feePaid, 0); + totalFeePaid += (blockAccelerations[last] || []).reduce((total, acc) => total + (acc.feePaid - acc.baseFee - acc.vsizeFee), 0); totalCount += (blockAccelerations[last] || []).length; last++; } @@ -246,7 +246,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy { icon: 'roundRect', }, { - name: 'Out-of-band fees per block', + name: 'Total bid boost per block', inactiveColor: 'rgb(110, 112, 121)', textStyle: { color: 'white', @@ -256,7 +256,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy { ], selected: { 'In-band fees per block': false, - 'Out-of-band fees per block': true, + 'Total bid boost per block': true, }, show: !this.widget, }, @@ -299,7 +299,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy { { legendHoverLink: false, zlevel: 1, - name: 'Out-of-band fees per block', + name: 'Total bid boost per block', data: data.map(block => [block.timestamp * 1000, block.avgFeePaid, block.avgHeight]), stack: 'Total', type: 'bar', diff --git a/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html b/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html index 21cd57ae0..9cdd6d70b 100644 --- a/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html +++ b/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html @@ -1,14 +1,14 @@
-
Transactions
+
Requests
{{ stats.count }}
accelerated
-
Out-of-band Fees
+
Total Bid Boost
{{ stats.totalFeesPaid / 100_000_000 | amountShortener: 4 }} BTC
@@ -29,14 +29,14 @@
-
Transactions
+
Requests
-
Out-of-band Fees
+
Total Bid Boost
diff --git a/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts b/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts index d83303619..0a6ef065c 100644 --- a/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts +++ b/frontend/src/app/components/acceleration/acceleration-stats/acceleration-stats.component.ts @@ -27,11 +27,11 @@ export class AccelerationStatsComponent implements OnInit { let totalFeesPaid = 0; let totalSucceeded = 0; let totalCanceled = 0; - for (const acceleration of accelerations) { - if (acceleration.status === 'completed') { + for (const acc of accelerations) { + if (acc.status === 'completed') { totalSucceeded++; - totalFeesPaid += acceleration.feePaid || 0; - } else if (acceleration.status === 'failed') { + totalFeesPaid += (acc.feePaid - acc.baseFee - acc.vsizeFee) || 0; + } else if (acc.status === 'failed') { totalCanceled++; } } diff --git a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html index f2265282f..9a919ca54 100644 --- a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html +++ b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.html @@ -14,7 +14,7 @@ Requested - Out-of-band Fee + Bid Boost Block Status @@ -39,7 +39,7 @@ - {{ (acceleration.feePaid) | number }} sat + {{ (acceleration.boost) | number }} sat ~ @@ -48,7 +48,7 @@ {{ acceleration.blockHeight }} - Pending + Pending Mined Canceled diff --git a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.ts b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.ts index ddd89d31c..69af8b966 100644 --- a/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.ts +++ b/frontend/src/app/components/acceleration/accelerations-list/accelerations-list.component.ts @@ -49,6 +49,9 @@ export class AccelerationsListComponent implements OnInit { acceleration.status = acceleration.status || 'accelerating'; } } + for (const acc of accelerations) { + acc.boost = acc.feePaid - acc.baseFee - acc.vsizeFee; + } if (this.widget) { return of(accelerations.slice(-6).reverse()); } else { diff --git a/frontend/src/app/components/acceleration/pending-stats/pending-stats.component.html b/frontend/src/app/components/acceleration/pending-stats/pending-stats.component.html index c94bbf43a..1b1a2c381 100644 --- a/frontend/src/app/components/acceleration/pending-stats/pending-stats.component.html +++ b/frontend/src/app/components/acceleration/pending-stats/pending-stats.component.html @@ -1,10 +1,10 @@
-
Transactions
+
Requests
{{ stats.count }}
-
accelerated
+
pending
@@ -29,7 +29,7 @@
-
Transactions
+
Requests
diff --git a/frontend/src/app/components/assets/asset-group/asset-group.component.html b/frontend/src/app/components/assets/asset-group/asset-group.component.html index a4c9e9da0..600a8a9bd 100644 --- a/frontend/src/app/components/assets/asset-group/asset-group.component.html +++ b/frontend/src/app/components/assets/asset-group/asset-group.component.html @@ -4,7 +4,7 @@
-

{{ group.name }}

+

{{ group['group'].name }}

Group of {{ group.assets.length | number }} assets
diff --git a/frontend/src/app/components/block-overview-graph/block-scene.ts b/frontend/src/app/components/block-overview-graph/block-scene.ts index cb589527d..adcf736fc 100644 --- a/frontend/src/app/components/block-overview-graph/block-scene.ts +++ b/frontend/src/app/components/block-overview-graph/block-scene.ts @@ -11,7 +11,7 @@ export default class BlockScene { getColor: ((tx: TxView) => Color) = defaultColorFunction; orientation: string; flip: boolean; - animationDuration: number = 1000; + animationDuration: number = 900; configAnimationOffset: number | null; animationOffset: number; highlightingEnabled: boolean; diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index e908d5b24..1e6edd2c8 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -47,7 +47,7 @@ Timestamp - + @@ -59,7 +59,7 @@ - Health + Health 
- + + +
@@ -245,7 +247,9 @@
- + + +
@@ -452,5 +456,24 @@ + + + + + + + + + + + + + + + + +
Total fees
Weight
Transactions
+
+

diff --git a/frontend/src/app/components/block/block.component.scss b/frontend/src/app/components/block/block.component.scss index c413b1fce..6deb2cb4b 100644 --- a/frontend/src/app/components/block/block.component.scss +++ b/frontend/src/app/components/block/block.component.scss @@ -57,11 +57,6 @@ text-align: left; } } - .info-link { - color: rgba(255, 255, 255, 0.4); - margin-left: 5px; - } - .difference { margin-left: 0.5em; diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index 85e2ea17f..838c7cb4e 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -46,7 +46,7 @@
- ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} + ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm:ss' }}
diff --git a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts index 26a67cee6..dece98ddb 100644 --- a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts +++ b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts @@ -1,7 +1,7 @@ import { AfterViewInit, ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { share } from 'rxjs/operators'; -import { INodesRanking } from '../../interfaces/node-api.interface'; +import { INodesRanking, INodesStatistics } from '../../interfaces/node-api.interface'; import { SeoService } from '../../services/seo.service'; import { StateService } from '../../services/state.service'; import { LightningApiService } from '../lightning-api.service'; @@ -13,7 +13,7 @@ import { LightningApiService } from '../lightning-api.service'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class LightningDashboardComponent implements OnInit, AfterViewInit { - statistics$: Observable; + statistics$: Observable; nodesRanking$: Observable; officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE; diff --git a/frontend/src/app/lightning/node-statistics/node-statistics.component.ts b/frontend/src/app/lightning/node-statistics/node-statistics.component.ts index c42720427..338e17ab8 100644 --- a/frontend/src/app/lightning/node-statistics/node-statistics.component.ts +++ b/frontend/src/app/lightning/node-statistics/node-statistics.component.ts @@ -1,5 +1,6 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; +import { INodesStatistics } from '../../interfaces/node-api.interface'; @Component({ selector: 'app-node-statistics', @@ -8,7 +9,7 @@ import { Observable } from 'rxjs'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class NodeStatisticsComponent implements OnInit { - @Input() statistics$: Observable; + @Input() statistics$: Observable; constructor() { } diff --git a/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html index 5bd03941e..51836880d 100644 --- a/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html +++ b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.html @@ -1,7 +1,7 @@ - + - + diff --git a/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.ts b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.ts index 373751be9..8a1eae3dc 100644 --- a/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.ts +++ b/frontend/src/app/lightning/nodes-ranking/nodes-ranking.component.ts @@ -1,5 +1,9 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { LightningApiService } from '../lightning-api.service'; +import { share } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { INodesStatistics } from '../../interfaces/node-api.interface'; @Component({ selector: 'app-nodes-ranking', @@ -9,10 +13,15 @@ import { ActivatedRoute } from '@angular/router'; }) export class NodesRanking implements OnInit { type: string; + statistics$: Observable; - constructor(private route: ActivatedRoute) {} + constructor( + private route: ActivatedRoute, + private lightningApiService: LightningApiService, + ) {} ngOnInit(): void { + this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share()); this.route.data.subscribe(data => { this.type = data.type; }); diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html index 3efbc8594..27af80564 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html @@ -16,8 +16,8 @@ Last update Location - - + +
@@ -27,12 +27,14 @@ +  ({{ (node?.capacity / data.statistics.totalCapacity * 100) | number:'1.1-1' }}%) {{ node.channels | number }} +  ({{ (node?.channels / data.statistics.totalChannels * 100) | number:'1.1-1' }}%) diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.scss b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.scss index 3547c447f..89f144135 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.scss +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.scss @@ -41,6 +41,11 @@ tr, td, th { } } +.capacity-ratio { + font-size: 12px; + color: darkgrey; +} + .fiat { width: 15%; @media (min-width: 768px) and (max-width: 991px) { diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts index 054fa2f3c..0b8c03bbd 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; -import { map, Observable } from 'rxjs'; -import { INodesRanking, ITopNodesPerCapacity } from '../../../interfaces/node-api.interface'; +import { combineLatest, map, Observable } from 'rxjs'; +import { INodesRanking, INodesStatistics, ITopNodesPerCapacity } from '../../../interfaces/node-api.interface'; import { SeoService } from '../../../services/seo.service'; import { StateService } from '../../../services/state.service'; import { GeolocationData } from '../../../shared/components/geolocation/geolocation.component'; @@ -14,9 +14,10 @@ import { LightningApiService } from '../../lightning-api.service'; }) export class TopNodesPerCapacity implements OnInit { @Input() nodes$: Observable; + @Input() statistics$: Observable; @Input() widget: boolean = false; - topNodesPerCapacity$: Observable; + topNodesPerCapacity$: Observable<{ nodes: ITopNodesPerCapacity[]; statistics: { totalCapacity: number; totalChannels?: number; } }>; skeletonRows: number[] = []; currency$: Observable; @@ -39,8 +40,12 @@ export class TopNodesPerCapacity implements OnInit { } if (this.widget === false) { - this.topNodesPerCapacity$ = this.apiService.getTopNodesByCapacity$().pipe( - map((ranking) => { + this.topNodesPerCapacity$ = combineLatest([ + this.apiService.getTopNodesByCapacity$(), + this.statistics$ + ]) + .pipe( + map(([ranking, statistics]) => { for (const i in ranking) { ranking[i].geolocation = { country: ranking[i].country?.en, @@ -49,13 +54,28 @@ export class TopNodesPerCapacity implements OnInit { iso: ranking[i].iso_code, }; } - return ranking; + return { + nodes: ranking, + statistics: { + totalCapacity: statistics.latest.total_capacity, + totalChannels: statistics.latest.channel_count, + } + } }) ); } else { - this.topNodesPerCapacity$ = this.nodes$.pipe( - map((ranking) => { - return ranking.topByCapacity.slice(0, 6); + this.topNodesPerCapacity$ = combineLatest([ + this.nodes$, + this.statistics$ + ]) + .pipe( + map(([ranking, statistics]) => { + return { + nodes: ranking.topByCapacity.slice(0, 6), + statistics: { + totalCapacity: statistics.latest.total_capacity, + } + } }) ); } diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html index 94a887bb3..87f7f1ad2 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html @@ -16,8 +16,8 @@ Last update Location - - + +
@@ -27,9 +27,11 @@ {{ node.channels ? (node.channels | number) : '~' }} +  ({{ (node?.channels / data.statistics.totalChannels * 100) | number:'1.1-1' }}%) +  ({{ (node.capacity / data.statistics.totalCapacity * 100) | number:'1.1-1' }}%) diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.scss b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.scss index a42599d69..63d65bcf8 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.scss +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.scss @@ -44,6 +44,11 @@ tr, td, th { } } +.capacity-ratio { + font-size: 12px; + color: darkgrey; +} + .geolocation { @media (min-width: 768px) and (max-width: 991px) { display: none !important; diff --git a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts index 3de177cc7..56d55a5d3 100644 --- a/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts +++ b/frontend/src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; -import { map, Observable } from 'rxjs'; -import { INodesRanking, ITopNodesPerChannels } from '../../../interfaces/node-api.interface'; +import { combineLatest, map, Observable } from 'rxjs'; +import { INodesRanking, INodesStatistics, ITopNodesPerChannels } from '../../../interfaces/node-api.interface'; import { SeoService } from '../../../services/seo.service'; import { StateService } from '../../../services/state.service'; import { GeolocationData } from '../../../shared/components/geolocation/geolocation.component'; @@ -14,12 +14,13 @@ import { LightningApiService } from '../../lightning-api.service'; }) export class TopNodesPerChannels implements OnInit { @Input() nodes$: Observable; + @Input() statistics$: Observable; @Input() widget: boolean = false; - topNodesPerChannels$: Observable; + topNodesPerChannels$: Observable<{ nodes: ITopNodesPerChannels[]; statistics: { totalChannels: number; totalCapacity?: number; } }>; skeletonRows: number[] = []; currency$: Observable; - + constructor( private apiService: LightningApiService, private stateService: StateService, @@ -37,8 +38,12 @@ export class TopNodesPerChannels implements OnInit { this.seoService.setTitle($localize`:@@c50bf442cf99f6fc5f8b687c460f33234b879869:Connectivity Ranking`); this.seoService.setDescription($localize`:@@meta.description.lightning.ranking.channels:See Lightning nodes with the most channels open along with high-level stats like total node capacity, node age, and more.`); - this.topNodesPerChannels$ = this.apiService.getTopNodesByChannels$().pipe( - map((ranking) => { + this.topNodesPerChannels$ = combineLatest([ + this.apiService.getTopNodesByChannels$(), + this.statistics$ + ]) + .pipe( + map(([ranking, statistics]) => { for (const i in ranking) { ranking[i].geolocation = { country: ranking[i].country?.en, @@ -47,12 +52,22 @@ export class TopNodesPerChannels implements OnInit { iso: ranking[i].iso_code, }; } - return ranking; + return { + nodes: ranking, + statistics: { + totalChannels: statistics.latest.channel_count, + totalCapacity: statistics.latest.total_capacity, + } + } }) ); } else { - this.topNodesPerChannels$ = this.nodes$.pipe( - map((ranking) => { + this.topNodesPerChannels$ = combineLatest([ + this.nodes$, + this.statistics$ + ]) + .pipe( + map(([ranking, statistics]) => { for (const i in ranking.topByChannels) { ranking.topByChannels[i].geolocation = { country: ranking.topByChannels[i].country?.en, @@ -61,7 +76,12 @@ export class TopNodesPerChannels implements OnInit { iso: ranking.topByChannels[i].iso_code, }; } - return ranking.topByChannels.slice(0, 6); + return { + nodes: ranking.topByChannels.slice(0, 6), + statistics: { + totalChannels: statistics.latest.channel_count, + } + } }) ); } diff --git a/frontend/src/app/liquid/liquid-master-page.module.ts b/frontend/src/app/liquid/liquid-master-page.module.ts index 10d87bc4b..bb6e4cff8 100644 --- a/frontend/src/app/liquid/liquid-master-page.module.ts +++ b/frontend/src/app/liquid/liquid-master-page.module.ts @@ -10,6 +10,7 @@ import { PushTransactionComponent } from '../components/push-transaction/push-tr import { BlocksList } from '../components/blocks-list/blocks-list.component'; import { AssetGroupComponent } from '../components/assets/asset-group/asset-group.component'; import { AssetsComponent } from '../components/assets/assets.component'; +import { AssetsFeaturedComponent } from '../components/assets/assets-featured/assets-featured.component' import { AssetComponent } from '../components/asset/asset.component'; import { AssetsNavComponent } from '../components/assets/assets-nav/assets-nav.component'; @@ -73,6 +74,11 @@ const routes: Routes = [ data: { networks: ['liquid'] }, component: AssetsComponent, }, + { + path: 'featured', + data: { networks: ['liquid'] }, + component: AssetsFeaturedComponent, + }, { path: 'asset/:id', data: { networkSpecific: true }, @@ -85,7 +91,7 @@ const routes: Routes = [ }, { path: '**', - redirectTo: 'all' + redirectTo: 'featured' } ] }, diff --git a/frontend/src/app/shared/components/global-footer/global-footer.component.html b/frontend/src/app/shared/components/global-footer/global-footer.component.html index 1fe3ec838..9eb1ea876 100644 --- a/frontend/src/app/shared/components/global-footer/global-footer.component.html +++ b/frontend/src/app/shared/components/global-footer/global-footer.component.html @@ -7,11 +7,11 @@
-

+

Explore the full Bitcoin ecosystem ®

-
+ - -

+

Explore the full Bitcoin ecosystem ®

diff --git a/frontend/src/app/shared/components/global-footer/global-footer.component.scss b/frontend/src/app/shared/components/global-footer/global-footer.component.scss index 79c7dcfce..c2bfe71dd 100644 --- a/frontend/src/app/shared/components/global-footer/global-footer.component.scss +++ b/frontend/src/app/shared/components/global-footer/global-footer.component.scss @@ -132,10 +132,36 @@ footer .row.version p a { footer .sponsor { height: 31px; align-items: center; + margin-right: 5px; margin-left: 5px; max-width: 160px; } +.explore-tagline-desktop { + display: none; +} + +.explore-tagline-mobile { + display: block; +} + +@media (min-width: 901px) { + :host-context(.ltr-layout) .language-selector { + float: right !important; + } + :host-context(.rtl-layout) .language-selector { + float: left !important; + } + + .explore-tagline-desktop { + display: block; + } + + .explore-tagline-mobile { + display: none; + } +} + @media (max-width: 1200px) { .main-logo { @@ -195,10 +221,6 @@ footer .sponsor { float: none; margin-top: 15px; } - - footer .selector:not(:last-child) { - margin-right: 10px; - } } @media (max-width: 1147px) { diff --git a/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts b/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts index d7fe612fe..4211765df 100644 --- a/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts +++ b/frontend/src/app/shared/pipes/relative-url/relative-url.pipe.ts @@ -10,8 +10,9 @@ export class RelativeUrlPipe implements PipeTransform { private stateService: StateService, ) { } - transform(value: string): string { - let network = this.stateService.network; + transform(value: string, swapNetwork?: string): string { + let network = swapNetwork || this.stateService.network; + if (network === 'mainnet') network = ''; if (this.stateService.env.BASE_MODULE === 'liquid' && network === 'liquidtestnet') { network = 'testnet'; } else if (this.stateService.env.BASE_MODULE !== 'mempool') { diff --git a/frontend/src/app/shared/regex.utils.ts b/frontend/src/app/shared/regex.utils.ts new file mode 100644 index 000000000..128f7566e --- /dev/null +++ b/frontend/src/app/shared/regex.utils.ts @@ -0,0 +1,343 @@ +import { Env } from '../services/state.service'; + +// all base58 characters +const BASE58_CHARS = `[a-km-zA-HJ-NP-Z1-9]`; + +// all bech32 characters (after the separator) +const BECH32_CHARS_LW = `[ac-hj-np-z02-9]`; +const BECH32_CHARS_UP = `[AC-HJ-NP-Z02-9]`; + +// Hex characters +const HEX_CHARS = `[a-fA-F0-9]`; + +// A regex to say "A single 0 OR any number with no leading zeroes" +// Capped at 9 digits so as to not be confused with lightning channel IDs (which are around 17 digits) +// (?: // Start a non-capturing group +// 0 // A single 0 +// | // OR +// [1-9][0-9]{0,8} // Any succession of numbers up to 9 digits starting with 1-9 +// ) // End the non-capturing group. +const ZERO_INDEX_NUMBER_CHARS = `(?:0|[1-9][0-9]{0,8})`; + +// Simple digits only regex +const NUMBER_CHARS = `[0-9]`; + +// Formatting of the address regex is for readability, +// We should ignore formatting it with automated formatting tools like prettier. +// +// prettier-ignore +const ADDRESS_CHARS: { + [k in Network]: { + base58: string; + bech32: string; + }; +} = { + mainnet: { + base58: `[13]` // Starts with a single 1 or 3 + + BASE58_CHARS + + `{26,33}`, // Repeat the previous char 26-33 times. + // Version byte 0x00 (P2PKH) can be as short as 27 characters, up to 34 length + // P2SH must be 34 length + bech32: `(?:` + + `bc1` // Starts with bc1 + + BECH32_CHARS_LW + + `{20,100}` // As per bech32, 6 char checksum is minimum + + `|` + + `BC1` // All upper case version + + BECH32_CHARS_UP + + `{20,100}` + + `)`, + }, + testnet: { + base58: `[mn2]` // Starts with a single m, n, or 2 (P2PKH is m or n, 2 is P2SH) + + BASE58_CHARS + + `{33,34}`, // m|n is 34 length, 2 is 35 length (We match the first letter separately) + bech32: `(?:` + + `tb1` // Starts with tb1 + + BECH32_CHARS_LW + + `{20,100}` // As per bech32, 6 char checksum is minimum + + `|` + + `TB1` // All upper case version + + BECH32_CHARS_UP + + `{20,100}` + + `)`, + }, + signet: { + base58: `[mn2]` + + BASE58_CHARS + + `{33,34}`, + bech32: `(?:` + + `tb1` // Starts with tb1 + + BECH32_CHARS_LW + + `{20,100}` + + `|` + + `TB1` // All upper case version + + BECH32_CHARS_UP + + `{20,100}` + + `)`, + }, + liquid: { + base58: `[GHPQ]` // G|H is P2PKH, P|Q is P2SH + + BASE58_CHARS + + `{33}`, // All min-max lengths are 34 + bech32: `(?:` + + `(?:` // bech32 liquid starts with ex1 or lq1 + + `ex1` + + `|` + + `lq1` + + `)` + + BECH32_CHARS_LW // blech32 and bech32 are the same alphabet and protocol, different checksums. + + `{20,100}` + + `|` + + `(?:` // Same as above but all upper case + + `EX1` + + `|` + + `LQ1` + + `)` + + BECH32_CHARS_UP + + `{20,100}` + + `)`, + }, + liquidtestnet: { + base58: `[89]` // ???(TODO: find version) is P2PKH, 8|9 is P2SH + + BASE58_CHARS + + `{33}`, // P2PKH is ???(TODO: find size), P2SH is 34 + bech32: `(?:` + + `(?:` // bech32 liquid testnet starts with tex or tlq + + `tex1` // TODO: Why does mempool use this and not ert|el like in the elements source? + + `|` + + `tlq1` // TODO: does this exist? + + `)` + + BECH32_CHARS_LW // blech32 and bech32 are the same alphabet and protocol, different checksums. + + `{20,100}` + + `|` + + `(?:` // Same as above but all upper case + + `TEX1` + + `|` + + `TLQ1` + + `)` + + BECH32_CHARS_UP + + `{20,100}` + + `)`, + }, + bisq: { + base58: `(?:[bB][13]` // b or B at the start, followed by a single 1 or 3 + + BASE58_CHARS + + `{26,33})`, + bech32: `(?:` + + `[bB]bc1` // b or B at the start, followed by bc1 + + BECH32_CHARS_LW + + `{20,100}` + + `|` + + `[bB]BC1` // b or B at the start, followed by BC1 + + BECH32_CHARS_UP + + `{20,100}` + + `)`, + }, +} +type RegexTypeNoAddrNoBlockHash = | `transaction` | `blockheight` | `date` | `timestamp`; +export type RegexType = `address` | `blockhash` | RegexTypeNoAddrNoBlockHash; + +export const NETWORKS = [`testnet`, `signet`, `liquid`, `liquidtestnet`, `bisq`, `mainnet`] as const; +export type Network = typeof NETWORKS[number]; // Turn const array into union type + +export const ADDRESS_REGEXES: [RegExp, Network][] = NETWORKS + .map(network => [getRegex('address', network), network]) + +export function findOtherNetworks(address: string, skipNetwork: Network, env: Env): { network: Network, address: string, isNetworkAvailable: boolean }[] { + return ADDRESS_REGEXES + .filter(([regex, network]) => network !== skipNetwork && regex.test(address)) + .map(([, network]) => ({ network, address, isNetworkAvailable: isNetworkAvailable(network, env) })); +} + +function isNetworkAvailable(network: Network, env: Env): boolean { + switch (network) { + case 'testnet': + return env.TESTNET_ENABLED === true; + case 'signet': + return env.SIGNET_ENABLED === true; + case 'liquid': + return env.LIQUID_ENABLED === true; + case 'liquidtestnet': + return env.LIQUID_TESTNET_ENABLED === true; + case 'bisq': + return env.BISQ_ENABLED === true; + case 'mainnet': + return true; // There is no "MAINNET_ENABLED" flag + default: + return false; + } +} + +export function needBaseModuleChange(fromBaseModule: 'mempool' | 'liquid' | 'bisq', toNetwork: Network): boolean { + if (!toNetwork) return false; // No target network means no change needed + if (fromBaseModule === 'mempool') { + return toNetwork !== 'mainnet' && toNetwork !== 'testnet' && toNetwork !== 'signet'; + } + if (fromBaseModule === 'liquid') { + return toNetwork !== 'liquid' && toNetwork !== 'liquidtestnet'; + } + if (fromBaseModule === 'bisq') { + return toNetwork !== 'bisq'; + } +} + +export function getTargetUrl(toNetwork: Network, address: string, env: Env): string { + let targetUrl = ''; + if (toNetwork === 'liquid' || toNetwork === 'liquidtestnet') { + targetUrl = env.LIQUID_WEBSITE_URL; + targetUrl += (toNetwork === 'liquidtestnet' ? '/testnet' : ''); + targetUrl += '/address/'; + targetUrl += address; + } + if (toNetwork === 'bisq') { + targetUrl = env.BISQ_WEBSITE_URL; + targetUrl += '/address/'; + targetUrl += address; + } + if (toNetwork === 'mainnet' || toNetwork === 'testnet' || toNetwork === 'signet') { + targetUrl = env.MEMPOOL_WEBSITE_URL; + targetUrl += (toNetwork === 'mainnet' ? '' : `/${toNetwork}`); + targetUrl += '/address/'; + targetUrl += address; + } + return targetUrl; +} + +export function getRegex(type: RegexTypeNoAddrNoBlockHash): RegExp; +export function getRegex(type: 'address', network: Network): RegExp; +export function getRegex(type: 'blockhash', network: Network): RegExp; +export function getRegex(type: RegexType, network?: Network): RegExp { + let regex = `^`; // ^ = Start of string + switch (type) { + // Match a block height number + // [Testing Order]: any order is fine + case `blockheight`: + regex += ZERO_INDEX_NUMBER_CHARS; // block height is a 0 indexed number + break; + // Match a 32 byte block hash in hex. + // [Testing Order]: Must always be tested before `transaction` + case `blockhash`: + if (!network) { + throw new Error(`Must pass network when type is blockhash`); + } + let leadingZeroes: number; + switch (network) { + case `mainnet`: + leadingZeroes = 8; // Assumes at least 32 bits of difficulty + break; + case `testnet`: + leadingZeroes = 8; // Assumes at least 32 bits of difficulty + break; + case `signet`: + leadingZeroes = 5; + break; + case `liquid`: + leadingZeroes = 8; // We are not interested in Liquid block hashes + break; + case `liquidtestnet`: + leadingZeroes = 8; // We are not interested in Liquid block hashes + break; + case `bisq`: + leadingZeroes = 8; // Assumes at least 32 bits of difficulty + break; + default: + throw new Error(`Invalid Network ${network} (Unreachable error in TypeScript)`); + } + regex += `0{${leadingZeroes}}`; + regex += `${HEX_CHARS}{${64 - leadingZeroes}}`; // Exactly 64 hex letters/numbers + break; + // Match a 32 byte tx hash in hex. Contains optional output index specifier. + // [Testing Order]: Must always be tested after `blockhash` + case `transaction`: + regex += `${HEX_CHARS}{64}`; // Exactly 64 hex letters/numbers + regex += `(?:`; // Start a non-capturing group + regex += `:`; // 1 instances of the symbol ":" + regex += ZERO_INDEX_NUMBER_CHARS; // A zero indexed number + regex += `)?`; // End the non-capturing group. This group appears 0 or 1 times + break; + // Match any one of the many address types + // [Testing Order]: While possible that a bech32 address happens to be 64 hex + // characters in the future (current lengths are not 64), it is highly unlikely + // Order therefore, does not matter. + case `address`: + if (!network) { + throw new Error(`Must pass network when type is address`); + } + regex += `(?:`; // Start a non-capturing group (each network has multiple options) + switch (network) { + case `mainnet`: + regex += ADDRESS_CHARS.mainnet.base58; + regex += `|`; // OR + regex += ADDRESS_CHARS.mainnet.bech32; + regex += `|`; // OR + regex += `04${HEX_CHARS}{128}`; // Uncompressed pubkey + regex += `|`; // OR + regex += `(?:02|03)${HEX_CHARS}{64}`; // Compressed pubkey + break; + case `testnet`: + regex += ADDRESS_CHARS.testnet.base58; + regex += `|`; // OR + regex += ADDRESS_CHARS.testnet.bech32; + regex += `|`; // OR + regex += `04${HEX_CHARS}{128}`; // Uncompressed pubkey + regex += `|`; // OR + regex += `(?:02|03)${HEX_CHARS}{64}`; // Compressed pubkey + break; + case `signet`: + regex += ADDRESS_CHARS.signet.base58; + regex += `|`; // OR + regex += ADDRESS_CHARS.signet.bech32; + regex += `|`; // OR + regex += `04${HEX_CHARS}{128}`; // Uncompressed pubkey + regex += `|`; // OR + regex += `(?:02|03)${HEX_CHARS}{64}`; // Compressed pubkey + break; + case `liquid`: + regex += ADDRESS_CHARS.liquid.base58; + regex += `|`; // OR + regex += ADDRESS_CHARS.liquid.bech32; + break; + case `liquidtestnet`: + regex += ADDRESS_CHARS.liquidtestnet.base58; + regex += `|`; // OR + regex += ADDRESS_CHARS.liquidtestnet.bech32; + break; + case `bisq`: + regex += ADDRESS_CHARS.bisq.base58; + regex += `|`; // OR + regex += ADDRESS_CHARS.bisq.bech32; + break; + default: + throw new Error(`Invalid Network ${network} (Unreachable error in TypeScript)`); + } + regex += `)`; // End the non-capturing group + break; + // Match a date in the format YYYY-MM-DD (optional: HH:MM) + // [Testing Order]: any order is fine + case `date`: + regex += `(?:`; // Start a non-capturing group + regex += `${NUMBER_CHARS}{4}`; // Exactly 4 digits + regex += `[-/]`; // 1 instance of the symbol "-" or "/" + regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits + regex += `[-/]`; // 1 instance of the symbol "-" or "/" + regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits + regex += `(?:`; // Start a non-capturing group + regex += ` `; // 1 instance of the symbol " " + regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits + regex += `:`; // 1 instance of the symbol ":" + regex += `${NUMBER_CHARS}{1,2}`; // Exactly 4 digits + regex += `)?`; // End the non-capturing group. This group appears 0 or 1 times + regex += `)`; // End the non-capturing group + break; + // Match a unix timestamp + // [Testing Order]: any order is fine + case `timestamp`: + regex += `${NUMBER_CHARS}{10}`; // Exactly 10 digits + break; + default: + throw new Error(`Invalid RegexType ${type} (Unreachable error in TypeScript)`); + } + regex += `$`; // $ = End of string + return new RegExp(regex); +} diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index f092e81bc..6a80e9851 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -4,7 +4,7 @@ import { NgbCollapseModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstra import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome'; import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle, faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faClock, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown, - faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck } from '@fortawesome/free-solid-svg-icons'; + faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faFastForward, faWallet, faUserClock, faWrench, faUserFriends, faQuestionCircle, faHistory, faSignOutAlt, faKey, faSuitcase, faIdCardAlt, faNetworkWired, faUserCheck, faCircleCheck, faUserCircle, faCheck, faRocket } from '@fortawesome/free-solid-svg-icons'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; import { MenuComponent } from '../components/menu/menu.component'; import { PreviewTitleComponent } from '../components/master-page-preview/preview-title.component'; @@ -384,5 +384,6 @@ export class SharedModule { library.addIcons(faCircleCheck); library.addIcons(faUserCircle); library.addIcons(faCheck); + library.addIcons(faRocket); } } diff --git a/frontend/src/locale/messages.nb.xlf b/frontend/src/locale/messages.nb.xlf index a035d3765..7870acc32 100644 --- a/frontend/src/locale/messages.nb.xlf +++ b/frontend/src/locale/messages.nb.xlf @@ -59,6 +59,7 @@ + node_modules/src/ngb-config.ts 13 @@ -66,6 +67,7 @@ Slide of + Lysbilde av node_modules/src/ngb-config.ts 13 @@ -404,6 +406,7 @@ See current balance, pending transactions, and history of confirmed transactions for BSQ address . + Se gjeldende saldo, ventende transaksjoner og historikk for bekreftede transaksjoner for BSQ-adresse . src/app/bisq/bisq-address/bisq-address.component.ts 44 @@ -500,6 +503,7 @@ See all BSQ transactions in Bitcoin block (block hash ). + Se alle BSQ-transaksjoner i Bitcoin-blokk (blokkhash ). src/app/bisq/bisq-block/bisq-block.component.ts 92 @@ -627,6 +631,7 @@ See a list of recent Bitcoin blocks with BSQ transactions, total BSQ sent per block, and more. + Se en liste over nylige Bitcoin-blokker med BSQ-transaksjoner, totalt BSQ sendt per blokk og mer. src/app/bisq/bisq-blocks/bisq-blocks.component.ts 39 @@ -756,6 +761,7 @@ Markets + Markeder src/app/bisq/bisq-dashboard/bisq-dashboard.component.ts 32 @@ -763,6 +769,7 @@ Explore the full Bitcoin ecosystem with The Mempool Open Source Project™. See Bisq market prices, trading activity, and more. + Utforsk hele Bitcoin-økosystemet med The Mempool Open Source Project™. Se Bisq markedspriser, handelsaktivitet og mer. src/app/bisq/bisq-dashboard/bisq-dashboard.component.ts 33 @@ -848,6 +855,7 @@ Bisq market: + Bisq-markedet: src/app/bisq/bisq-market/bisq-market.component.ts 51 @@ -855,6 +863,7 @@ See price history, current buy/sell offers, and latest trades for the market on Bisq. + Se prishistorikk, gjeldende kjøps-/salgstilbud og siste handler for -markedet på Bisq. src/app/bisq/bisq-market/bisq-market.component.ts 52 @@ -990,6 +999,7 @@ See high-level stats on the BSQ economy: supply metrics, number of addresses, BSQ price, market cap, and more. + Se statistikk om BSQ-økonomien: forsyningsstatistikk, antall adresser, BSQ-pris, markedsverdi og mer. src/app/bisq/bisq-stats/bisq-stats.component.ts 29 @@ -1169,6 +1179,7 @@ Fee per weight unit + Avgift per vektenhet src/app/bisq/bisq-transaction/bisq-transaction.component.html 68 @@ -1237,6 +1248,7 @@ See inputs, outputs, transaction type, burnt amount, and more for transaction with txid . + Se innganger, utganger, transaksjonstype, brent beløp og mer for transaksjon med txid . src/app/bisq/bisq-transaction/bisq-transaction.component.ts 51 @@ -1426,6 +1438,7 @@ See recent BSQ transactions: amount, txid, associated Bitcoin block, transaction type, and more. + Se nylige BSQ-transaksjoner: beløp, txid, tilhørende Bitcoin-blokk, transaksjonstype og mer. src/app/bisq/bisq-transactions/bisq-transactions.component.ts 82 @@ -1466,6 +1479,7 @@ Become a Community Sponsor + Bli en samfunnssponsor src/app/components/about/about.component.html 39 @@ -1474,6 +1488,7 @@ Become an Enterprise Sponsor + Bli en bedriftssponsor src/app/components/about/about.component.html 46 @@ -1491,6 +1506,7 @@ Whale Sponsors + Hvalsponsorer src/app/components/about/about.component.html 202 @@ -1499,6 +1515,7 @@ Chad Sponsors + Chadsponsorer src/app/components/about/about.component.html 215 @@ -1507,6 +1524,7 @@ OG Sponsors ❤️ + OG Sponsorer ❤️ src/app/components/about/about.component.html 228 @@ -1589,6 +1607,7 @@ Learn more about The Mempool Open Source Project®: enterprise sponsors, individual sponsors, integrations, who contributes, FOSS licensing, and more. + Lær mer om The Mempool Open Source Project®: bedriftssponsorer, individuelle sponsorer, integrasjoner, hvem som bidrar, FOSS-lisensiering og mer. src/app/components/about/about.component.ts 46 @@ -1818,7 +1837,8 @@ - See mempool transactions, confirmed transactions, balance, and more for address . + See mempool transactions, confirmed transactions, balance, and more for address . + Se mempool-transaksjoner, bekreftede transaksjoner, saldo og mer for adresse . src/app/components/address/address-preview.component.ts 72 @@ -2114,6 +2134,7 @@ Explore all the assets issued on the Liquid network like L-BTC, L-CAD, USDT, and more. + Utforsk alle eiendelene utstedt på Liquid-nettverket som L-BTC, L-CAD, USDT og mer. src/app/components/assets/assets-nav/assets-nav.component.ts 43 @@ -2276,6 +2297,7 @@ See Bitcoin feerates visualized over time, including minimum and maximum feerates per block along with feerates at various percentiles. + Se Bitcoin-gebyrer visualisert over tid, inkludert minimums- og maksimumsgebyrer per blokk sammen med gebyrer på forskjellige persentiler. src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts 67 @@ -2332,6 +2354,7 @@ See the average mining fees earned per Bitcoin block visualized in BTC and USD over time. + Se gjennomsnittlig utvinningsavgift tjent per Bitcoin-blokk visualisert i BTC og USD over tid. src/app/components/block-fees-graph/block-fees-graph.component.ts 68 @@ -2375,6 +2398,7 @@ Block Health + Blokkhelse src/app/components/block-health-graph/block-health-graph.component.html 6 @@ -2387,6 +2411,7 @@ Block Health + Blokkhelse src/app/components/block-health-graph/block-health-graph.component.ts 63 @@ -2394,6 +2419,7 @@ See Bitcoin block health visualized over time. Block health is a measure of how many expected transactions were included in an actual mined block. Expected transactions are determined using Mempool's re-implementation of Bitcoin Core's transaction selection algorithm. + Se Bitcoin-blokkhelse visualisert over tid. Blokkhelse er et mål på hvor mange forventede transaksjoner som ble inkludert i en faktisk utvunnet blokk. Forventede transaksjoner bestemmes ved hjelp av Mempools re-implementering av Bitcoin Cores transaksjonsvalgalgoritme. src/app/components/block-health-graph/block-health-graph.component.ts 64 @@ -2416,7 +2442,7 @@ src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts - 215 + 216 src/app/lightning/nodes-map/nodes-map.component.ts @@ -2425,6 +2451,7 @@ Health + Helse src/app/components/block-health-graph/block-health-graph.component.ts 190 @@ -2515,6 +2542,7 @@ Accelerated fee rate + Akselerert gebyrsats src/app/components/block-overview-tooltip/block-overview-tooltip.component.html 33 @@ -2546,6 +2574,7 @@ Weight + Vekt src/app/components/block-overview-tooltip/block-overview-tooltip.component.html 43 @@ -2603,6 +2632,7 @@ High sigop count + Høyt antall sigops src/app/components/block-overview-tooltip/block-overview-tooltip.component.html 52 @@ -2620,6 +2650,7 @@ Recently CPFP'd + Nylig CPFPet src/app/components/block-overview-tooltip/block-overview-tooltip.component.html 54 @@ -2637,6 +2668,7 @@ Conflicting + Motstridende src/app/components/block-overview-tooltip/block-overview-tooltip.component.html 57 @@ -2645,6 +2677,7 @@ Accelerated + Akselerert src/app/components/block-overview-tooltip/block-overview-tooltip.component.html 58 @@ -2670,6 +2703,7 @@ See Bitcoin block rewards in BTC and USD visualized over time. Block rewards are the total funds miners earn from the block subsidy and fees. + Se Bitcoin-blokkbelønninger i BTC og USD visualisert over tid. Blokkbelønninger er de totale midlene utvinnerene tjener fra blokksubsidier og gebyr. src/app/components/block-rewards-graph/block-rewards-graph.component.ts 66 @@ -2694,6 +2728,7 @@ See Bitcoin block sizes (MB) and block weights (weight units) visualized over time. + Se Bitcoin-blokkstørrelser (MB) og blokkvekter (vektenheter) visualisert over tid. src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts 63 @@ -2809,6 +2844,7 @@ See size, weight, fee range, included transactions, and more for Liquid block (). + Se størrelse, vekt, gebyrområde, inkluderte transaksjoner og mer for Liquid blokk (). src/app/components/block-view/block-view.component.ts 112 @@ -2824,6 +2860,7 @@ See size, weight, fee range, included transactions, audit (expected v actual), and more for Bitcoin block (). + Se størrelse, vekt, gebyrområde, inkluderte transaksjoner, revisjon (forventet v faktisk) og mer for Bitcoin blokk ( ). src/app/components/block-view/block-view.component.ts 114 @@ -2912,6 +2949,7 @@ This block does not belong to the main chain, it has been replaced by: + Denne blokken tilhører ikke hovedkjeden, den er erstattet av: src/app/components/block/block.component.html 5 @@ -2947,6 +2985,7 @@ Stale + Foreldet src/app/components/block/block.component.html 30 @@ -3412,6 +3451,7 @@ Blocks + Blokker src/app/components/blocks-list/blocks-list.component.ts 59 @@ -3419,6 +3459,7 @@ See the most recent Liquid blocks along with basic stats such as block height, block size, and more. + Se de nyeste Liquid-blokkene sammen med grunnleggende statistikk som blokkhøyde, blokkstørrelse og mer. src/app/components/blocks-list/blocks-list.component.ts 62 @@ -3426,6 +3467,7 @@ See the most recent Bitcoin blocks along with basic stats such as block height, block reward, block size, and more. + Se de nyeste Bitcoin-blokkene sammen med grunnleggende statistikk som blokkhøyde, blokkbelønning, blokkstørrelse og mer. src/app/components/blocks-list/blocks-list.component.ts 64 @@ -3433,6 +3475,7 @@ Calculator + Kalkulator src/app/components/calculator/calculator.component.html 3 @@ -3470,6 +3513,7 @@ Memory Usage + Minnebruk src/app/components/clock/clock.component.html 65 @@ -3549,6 +3593,7 @@ blocks + blokker src/app/components/difficulty-mining/difficulty-mining.component.html 10,11 @@ -3852,6 +3897,7 @@ WU/s + WU/s src/app/components/footer/footer.component.html 14 @@ -4024,7 +4070,7 @@ src/app/lightning/nodes-channels-map/nodes-channels-map.component.html - 6 + 19 lightning.nodes-channels-world-map @@ -4072,6 +4118,7 @@ See hashrate and difficulty for the Bitcoin network visualized over time. + Se hashrate og vanskelighetsgrad for Bitcoin-nettverket visualisert over tid. src/app/components/hashrate-chart/hashrate-chart.component.ts 75 @@ -4179,6 +4226,7 @@ See stats for transactions in the mempool: fee range, aggregate size, and more. Mempool blocks are updated in real-time as the network receives new transactions. + Se statistikk for transaksjoner i mempoolen: gebyrområde, samlet størrelse og mer. Mempool-blokker oppdateres i sanntid etter hvert som nettverket mottar nye transaksjoner. src/app/components/mempool-block/mempool-block.component.ts 58 @@ -4202,6 +4250,7 @@ Count + Antall src/app/components/mempool-graph/mempool-graph.component.ts 325 @@ -4229,6 +4278,7 @@ Sign in + Logg inn src/app/components/menu/menu.component.html 10 @@ -4255,6 +4305,7 @@ Recent Blocks + Nylige blokker src/app/components/mining-dashboard/mining-dashboard.component.html 52 @@ -4280,6 +4331,7 @@ Get real-time Bitcoin mining stats like hashrate, difficulty adjustment, block rewards, pool dominance, and more. + Få sanntids Bitcoin-utvinningsstatistikk som hashrate, vanskeliggradsjustering, blokkbelønninger, gruppedominans og mer. src/app/components/mining-dashboard/mining-dashboard.component.ts 21 @@ -4296,6 +4348,7 @@ Pools Luck + Gruppeflaks src/app/components/pool-ranking/pool-ranking.component.html 9 @@ -4326,6 +4379,7 @@ Pools Count + Antall grupper src/app/components/pool-ranking/pool-ranking.component.html 17 @@ -4440,6 +4494,7 @@ Empty Blocks + Tomme blokker src/app/components/pool-ranking/pool-ranking.component.html 98 @@ -4465,6 +4520,7 @@ See the top Bitcoin mining pools ranked by number of blocks mined, over your desired timeframe. + Se de beste utvinningsgruppene rangert etter antall blokker utvunnet, over ønsket tidsramme. src/app/components/pool-ranking/pool-ranking.component.ts 59 @@ -4554,6 +4610,7 @@ See mining pool stats for : most recent mined blocks, hashrate over time, total block reward to date, known coinbase addresses, and more. + Se utvinningsgruppe-statistikk for : siste utvunnede blokker, hashrate over tid, total blokkbelønning hittil, kjente coinbaseadresser og mer. src/app/components/pool/pool-preview.component.ts 86 @@ -4620,6 +4677,7 @@ Blocks (24h) + Blokker (24 timer) src/app/components/pool/pool.component.html 146 @@ -4734,6 +4792,7 @@ Broadcast Transaction + Kringkast transaksjon src/app/components/push-transaction/push-transaction.component.ts 31 @@ -4741,6 +4800,7 @@ Broadcast a transaction to the network using the transaction's hash. + Kringkast en transaksjon til -nettverket ved å bruke transaksjonens hash. src/app/components/push-transaction/push-transaction.component.ts 32 @@ -4748,6 +4808,7 @@ RBF Replacements + RBF-erstatninger src/app/components/rbf-list/rbf-list.component.html 2 @@ -4760,6 +4821,7 @@ Full RBF + Full RBF src/app/components/rbf-list/rbf-list.component.html 24 @@ -4776,6 +4838,7 @@ There are no replacements in the mempool yet! + Det er ingen erstatninger i mempoolen ennå! src/app/components/rbf-list/rbf-list.component.html 34 @@ -4784,6 +4847,7 @@ See the most recent RBF replacements on the Bitcoin network, updated in real-time. + Se de siste RBF-erstatningene på Bitcoin-nettverket, oppdatert i sanntid. src/app/components/rbf-list/rbf-list.component.ts 59 @@ -4829,6 +4893,7 @@ Status + Status src/app/components/rbf-timeline/rbf-timeline-tooltip.component.html 33 @@ -4846,6 +4911,7 @@ RBF + RBF src/app/components/rbf-timeline/rbf-timeline-tooltip.component.html 36 @@ -5065,6 +5131,7 @@ Clock (Mempool) + Klokke (Mempool) src/app/components/statistics/statistics.component.html 17 @@ -5117,6 +5184,7 @@ Cap outliers + Fjern avvikere src/app/components/statistics/statistics.component.html 122 @@ -5125,6 +5193,7 @@ See mempool size (in MvB) and transactions per second (in vB/s) visualized over time. + Se mempoolstørrelse (i MvB) og transaksjoner per sekund (i vB/s) visualisert over tid. src/app/components/statistics/statistics.component.ts 67 @@ -5132,6 +5201,7 @@ See Bitcoin blocks and mempool congestion in real-time in a simplified format perfect for a TV. + Se Bitcoin-blokker og mempool i sanntid i et forenklet format perfekt for en TV. src/app/components/television/television.component.ts 40 @@ -5343,7 +5413,8 @@ transactions-list.coinbase - Get real-time status, addresses, fees, script info, and more for transaction with txid {txid}. + Get real-time status, addresses, fees, script info, and more for transaction with txid . + Få sanntidsstatus, adresser, gebyrer, skriptinformasjon og mer for transaksjon med txid . src/app/components/transaction/transaction-preview.component.ts 91 @@ -5385,6 +5456,7 @@ Accelerate + Akselerer src/app/components/transaction/transaction.component.html 123 @@ -5422,6 +5494,7 @@ RBF History + RBF historie src/app/components/transaction/transaction.component.html 218 @@ -5493,6 +5566,7 @@ Adjusted vsize + Justert vsize src/app/components/transaction/transaction.component.html 298 @@ -5511,6 +5585,7 @@ Sigops + Sigops src/app/components/transaction/transaction.component.html 320 @@ -5538,6 +5613,7 @@ Accelerated fee rate + Akselerert gebyrsats src/app/components/transaction/transaction.component.html 516 @@ -5893,6 +5969,7 @@ Recent Replacements + Nylige erstatninger src/app/dashboard/dashboard.component.html 79 @@ -5901,6 +5978,7 @@ Previous fee + Tidligere avgift src/app/dashboard/dashboard.component.html 86 @@ -5909,6 +5987,7 @@ New fee + Ny avgift src/app/dashboard/dashboard.component.html 87 @@ -5917,6 +5996,7 @@ Recent Transactions + Nylige transaksjoner src/app/dashboard/dashboard.component.html 153 @@ -5954,6 +6034,7 @@ Incoming Transactions + Innkommende transaksjoner src/app/dashboard/dashboard.component.html 259 @@ -5962,6 +6043,7 @@ mempool.space merely provides data about the Bitcoin network. It cannot help you with retrieving funds, wallet issues, etc.For any such requests, you need to get in touch with the entity that helped make the transaction (wallet software, exchange company, etc). + mempool.space gir bare data om Bitcoin-nettverket. Det kan ikke hjelpe deg med å hente midler, lommebokproblemer osv.For slike forespørsler må du ta kontakt med enheten som bidro til transaksjonen (lommebokprogramvare, børsselskap osv.). src/app/docs/api-docs/api-docs.component.html 15,16 @@ -6056,6 +6138,7 @@ FAQ + FAQ src/app/docs/docs/docs.component.ts 45 @@ -6063,6 +6146,7 @@ Get answers to common questions like: What is a mempool? Why isn't my transaction confirming? How can I run my own instance of The Mempool Open Source Project? And more. + Få svar på vanlige spørsmål som: Hva er en mempool? Hvorfor bekreftes ikke transaksjonen min? Hvordan kan jeg kjøre min egen instans av Mempool Open Source Project? Og mer. src/app/docs/docs/docs.component.ts 46 @@ -6070,6 +6154,7 @@ REST API + REST API src/app/docs/docs/docs.component.ts 49 @@ -6077,6 +6162,7 @@ Documentation for the liquid.network REST API service: get info on addresses, transactions, assets, blocks, and more. + Dokumentasjon for liquid.network REST API-tjenesten: få informasjon om adresser, transaksjoner, eiendeler, blokker og mer. src/app/docs/docs/docs.component.ts 51 @@ -6084,6 +6170,7 @@ Documentation for the bisq.markets REST API service: get info on recent trades, current offers, transactions, network state, and more. + Dokumentasjon for bisq.markets REST API-tjenesten: få informasjon om nylige handler, nåværende tilbud, transaksjoner, nettverkstilstand og mer. src/app/docs/docs/docs.component.ts 53 @@ -6091,6 +6178,7 @@ Documentation for the mempool.space REST API service: get info on addresses, transactions, blocks, fees, mining, the Lightning network, and more. + Dokumentasjon for mempool.space REST API-tjenesten: få informasjon om adresser, transaksjoner, blokker, avgifter, utvinning, Lightning-nettverket og mer. src/app/docs/docs/docs.component.ts 55 @@ -6098,6 +6186,7 @@ WebSocket API + WebSocket API src/app/docs/docs/docs.component.ts 59 @@ -6105,6 +6194,7 @@ Documentation for the liquid.network WebSocket API service: get real-time info on blocks, mempools, transactions, addresses, and more. + Dokumentasjon for liquid.network WebSocket API-tjenesten: få sanntidsinformasjon om blokker, mempooler, transaksjoner, adresser og mer. src/app/docs/docs/docs.component.ts 61 @@ -6112,6 +6202,7 @@ Documentation for the mempool.space WebSocket API service: get real-time info on blocks, mempools, transactions, addresses, and more. + Dokumentasjon for mempool.space WebSocket API-tjenesten: få sanntidsinformasjon om blokker, mempooler, transaksjoner, adresser og mer. src/app/docs/docs/docs.component.ts 63 @@ -6119,6 +6210,7 @@ Electrum RPC + Electrum RPC src/app/docs/docs/docs.component.ts 67 @@ -6126,6 +6218,7 @@ Documentation for our Electrum RPC interface: get instant, convenient, and reliable access to an Esplora instance. + Dokumentasjon for vårt Electrum RPC-grensesnitt: få umiddelbar, praktisk og pålitelig tilgang til en Esplora-instans. src/app/docs/docs/docs.component.ts 68 @@ -6438,6 +6531,7 @@ Overview for Lightning channel . See channel capacity, the Lightning nodes involved, related on-chain transactions, and more. + Oversikt for Lightning-kanalen . Se kanalkapasitet, Lightning-nodene som er involvert, relaterte transaksjoner i kjeden og mer. src/app/lightning/channel/channel-preview.component.ts 37 @@ -6936,6 +7030,7 @@ Connect + Koble til src/app/lightning/group/group.component.html 73 @@ -6986,6 +7081,7 @@ Penalties + Straffer src/app/lightning/justice-list/justice-list.component.html 4 @@ -7062,7 +7158,8 @@ lightning.connectivity-ranking - Get stats on the Lightning network (aggregate capacity, connectivity, etc) and Lightning nodes (channels, liquidity, etc) and Lightning channels (status, fees, etc). + Get stats on the Lightning network (aggregate capacity, connectivity, etc), Lightning nodes (channels, liquidity, etc) and Lightning channels (status, fees, etc). + Få statistikk om Lightning-nettverket (samlet kapasitet, tilkobling osv.), Lightning-noder (kanaler, likviditet osv.) og Lightning-kanaler (status, avgifter osv.). src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts 28 @@ -7172,6 +7269,7 @@ Overview for the Lightning network node named . See channels, capacity, location, fee stats, and more. + Oversikt for Lightning-nettverksnoden . Se kanaler, kapasitet, plassering, gebyrstatistikk og mer. src/app/lightning/node/node-preview.component.ts 52 @@ -7232,6 +7330,7 @@ Decoded + Dekodet src/app/lightning/node/node.component.html 136 @@ -7344,7 +7443,7 @@ (Tor-noder ekskludert) src/app/lightning/nodes-channels-map/nodes-channels-map.component.html - 8 + 21 src/app/lightning/nodes-map/nodes-map.component.html @@ -7365,14 +7464,15 @@ Lightning-kanaler verdenskart src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts - 68 + 69 See the channels of non-Tor Lightning network nodes visualized on a world map. Hover/tap on points on the map for node names and details. + Se kanalene til ikke-Tor Lightning-nettverksnoder visualisert på et verdenskart. Hold musepekeren/trykk på punkter på kartet for nodenavn og detaljer. src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts - 69 + 70 @@ -7380,7 +7480,7 @@ Ingen geolokaliseringsdata tilgjengelig src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts - 227 + 228 @@ -7394,6 +7494,7 @@ See the locations of non-Tor Lightning network nodes visualized on a world map. Hover/tap on points on the map for node names and details. + Se plasseringen til ikke-Tor Lightning-nettverksnoder visualisert på et verdenskart. Hold musepekeren/trykk på punkter på kartet for nodenavn og detaljer. src/app/lightning/nodes-map/nodes-map.component.ts 51 @@ -7401,6 +7502,7 @@ See the number of Lightning network nodes visualized over time by network: clearnet only (IPv4, IPv6), darknet (Tor, I2p, cjdns), and both. + Se antall Lightning-nettverksnoder visualisert over tid etter nettverk: bare clearnet (IPv4, IPv6), darknet (Tor, I2p, cjdns) og begge deler. src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts 68 @@ -7469,6 +7571,7 @@ See a geographical breakdown of the Lightning network: how many Lightning nodes are hosted in countries around the world, aggregate BTC capacity for each country, and more. + Se en geografisk inndeling av Lightning-nettverket: hvor mange Lightning-noder er det i land rundt om i verden, samlet BTC-kapasitet for hvert land, og mer. src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts 47 @@ -7539,6 +7642,7 @@ Explore all the Lightning nodes hosted in and see an overview of each node's capacity, number of open channels, and more. + Utforsk alle Lightning-nodene som er i og se en oversikt over hver nodes kapasitet, antall åpne kanaler og mer. src/app/lightning/nodes-per-country/nodes-per-country.component.ts 36 @@ -7689,6 +7793,7 @@ Browse all Bitcoin Lightning nodes using the [AS] ISP and see aggregate stats like total number of nodes, total capacity, and more for the ISP. + Bla gjennom alle Bitcoin Lightning-noder ved å bruke [AS] ISP og se samlet statistikk som totalt antall noder, total kapasitet, og mer for Internett-leverandøren. src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.ts 45 @@ -7744,6 +7849,7 @@ See the oldest nodes on the Lightning network along with their capacity, number of channels, location, etc. + Se de eldste nodene på Lightning-nettverket sammen med deres kapasitet, antall kanaler, plassering osv. src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.ts 28 @@ -7751,6 +7857,7 @@ See Lightning nodes with the most BTC liquidity deployed along with high-level stats like number of open channels, location, node age, and more. + Se Lightning-noder med mest BTC-likviditet utplassert sammen med statistikk som antall åpne kanaler, plassering, nodens alder og mer. src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts 34 @@ -7758,6 +7865,7 @@ See Lightning nodes with the most channels open along with high-level stats like total node capacity, node age, and more. + Se Lightning-noder med flest åpne kanaler sammen med statistikk som total nodekapasitet, nodealder og mer. src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts 38 @@ -7781,7 +7889,8 @@ - See top the Lightning network nodes ranked by liquidity, connectivity, and age. + See the top Lightning network nodes ranked by liquidity, connectivity, and age. + Se de beste Lightning-nettverksnodene rangert etter likviditet, tilkobling og alder. src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.ts 23 @@ -7789,6 +7898,7 @@ See the capacity of the Lightning network visualized over time in terms of the number of open channels and total bitcoin capacity. + Se kapasiteten til Lightning-nettverket visualisert over tid i form av antall åpne kanaler og total bitcoin-kapasitet. src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts 67 @@ -7796,6 +7906,7 @@ confirmation + bekreftelse src/app/shared/components/confirmations/confirmations.component.html 4 @@ -7805,6 +7916,7 @@ confirmations + bekreftelser src/app/shared/components/confirmations/confirmations.component.html 5 @@ -7814,6 +7926,7 @@ Replaced + Erstattet src/app/shared/components/confirmations/confirmations.component.html 12 @@ -7823,6 +7936,7 @@ Removed + Fjernet src/app/shared/components/confirmations/confirmations.component.html 15 @@ -7842,6 +7956,7 @@ sat/WU + sat/WU src/app/shared/components/fee-rate/fee-rate.component.html 3 @@ -7851,6 +7966,7 @@ My Account + Min konto src/app/shared/components/global-footer/global-footer.component.html 25 @@ -7863,6 +7979,7 @@ Sign In + Logg inn src/app/shared/components/global-footer/global-footer.component.html 26 @@ -7875,6 +7992,7 @@ Explore + Utforsk src/app/shared/components/global-footer/global-footer.component.html 41 @@ -7883,6 +8001,7 @@ Connect to our Nodes + Koble til nodene våre src/app/shared/components/global-footer/global-footer.component.html 46 @@ -7891,6 +8010,7 @@ API Documentation + API-dokumentasjon src/app/shared/components/global-footer/global-footer.component.html 47 @@ -7899,6 +8019,7 @@ Learn + Lær src/app/shared/components/global-footer/global-footer.component.html 50 @@ -7907,6 +8028,7 @@ What is a mempool? + Hva er en mempool? src/app/shared/components/global-footer/global-footer.component.html 51 @@ -7915,6 +8037,7 @@ What is a block explorer? + Hva er en blokkutforsker? src/app/shared/components/global-footer/global-footer.component.html 52 @@ -7923,6 +8046,7 @@ What is a mempool explorer? + Hva er en mempool utforsker? src/app/shared/components/global-footer/global-footer.component.html 53 @@ -7931,6 +8055,7 @@ Why isn't my transaction confirming? + Hvorfor bekreftes ikke transaksjonen min? src/app/shared/components/global-footer/global-footer.component.html 54 @@ -7939,6 +8064,7 @@ More FAQs » + Flere vanlige spørsmål » src/app/shared/components/global-footer/global-footer.component.html 55 @@ -7947,6 +8073,7 @@ Networks + Nettverk src/app/shared/components/global-footer/global-footer.component.html 59 @@ -7955,6 +8082,7 @@ Mainnet Explorer + Mainnet utforsker src/app/shared/components/global-footer/global-footer.component.html 60 @@ -7963,6 +8091,7 @@ Testnet Explorer + Testnet utforsker src/app/shared/components/global-footer/global-footer.component.html 61 @@ -7971,6 +8100,7 @@ Signet Explorer + Signet utforsker src/app/shared/components/global-footer/global-footer.component.html 62 @@ -7979,6 +8109,7 @@ Liquid Testnet Explorer + Liquid testnet utforsker src/app/shared/components/global-footer/global-footer.component.html 63 @@ -7987,6 +8118,7 @@ Liquid Explorer + Liquid utforsker src/app/shared/components/global-footer/global-footer.component.html 64 @@ -7995,6 +8127,7 @@ Bisq Explorer + Bisq utforsker src/app/shared/components/global-footer/global-footer.component.html 65 @@ -8003,6 +8136,7 @@ Tools + Verktøy src/app/shared/components/global-footer/global-footer.component.html 69 @@ -8011,6 +8145,7 @@ Clock (Mined) + Klokke (utvunnet) src/app/shared/components/global-footer/global-footer.component.html 71 @@ -8019,6 +8154,7 @@ Legal + Lovlig src/app/shared/components/global-footer/global-footer.component.html 76 @@ -8047,6 +8183,7 @@ Trademark Policy + Varemerkepolitikk src/app/shared/components/global-footer/global-footer.component.html 79 @@ -8056,6 +8193,7 @@ This is a test network. Coins have no value. + Dette er et testnettverk. Mynter har ingen verdi. src/app/shared/components/testnet-alert/testnet-alert.component.html 3 diff --git a/frontend/src/locale/messages.xlf b/frontend/src/locale/messages.xlf index e4fa9090c..63d8ebec0 100644 --- a/frontend/src/locale/messages.xlf +++ b/frontend/src/locale/messages.xlf @@ -323,7 +323,7 @@ src/app/components/block/block.component.html - 322 + 326 src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -347,7 +347,7 @@ src/app/components/block/block.component.html - 323 + 327 src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -431,6 +431,10 @@ src/app/components/pool/pool.component.html 274 + + src/app/components/search-form/search-results/search-results.component.html + 15 + src/app/components/transaction/transaction.component.html 53 @@ -532,11 +536,15 @@ src/app/components/block/block.component.html - 414 + 418 src/app/components/block/block.component.html - 443 + 447 + + + src/app/components/block/block.component.html + 471 src/app/components/blocks-list/blocks-list.component.html @@ -572,7 +580,7 @@ src/app/components/master-page/master-page.component.html - 62 + 66 src/app/components/pool-ranking/pool-ranking.component.html @@ -740,6 +748,10 @@ src/app/bisq/bisq-main-dashboard/bisq-main-dashboard.component.html 101 + + src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html + 52 + src/app/components/mining-dashboard/mining-dashboard.component.html 32 @@ -933,6 +945,10 @@ src/app/bisq/bisq-trades/bisq-trades.component.html 4 + + src/app/components/search-form/search-results/search-results.component.html + 9 + Amount @@ -994,7 +1010,7 @@ src/app/components/transaction/transaction.component.html - 164 + 168 src/app/components/transactions-list/transactions-list.component.html @@ -1009,11 +1025,11 @@ src/app/components/block/block.component.html - 264 + 268 src/app/components/transaction/transaction.component.html - 312 + 320 transaction.version @@ -1070,7 +1086,7 @@ src/app/components/transaction/transaction.component.html - 141 + 145 src/app/lightning/node/node.component.html @@ -1109,11 +1125,11 @@ src/app/components/transaction/transaction.component.html - 282 + 286 src/app/components/transaction/transaction.component.html - 434 + 446 transaction.details @@ -1129,11 +1145,11 @@ src/app/components/transaction/transaction.component.html - 269 + 273 src/app/components/transaction/transaction.component.html - 405 + 417 Transaction inputs and outputs transaction.inputs-and-outputs @@ -1150,7 +1166,7 @@ src/app/components/transaction/transaction.component.ts - 291 + 314 @@ -1173,9 +1189,13 @@ src/app/bisq/bisq-transactions/bisq-transactions.component.html 18 + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 10 + src/app/components/transaction/transaction.component.html - 165 + 169 src/app/dashboard/dashboard.component.html @@ -1344,6 +1364,22 @@ 100 + + Become a Community Sponsor + + src/app/components/about/about-sponsors.component.html + 4 + + about.community-sponsor-button + + + Become an Enterprise Sponsor + + src/app/components/about/about-sponsors.component.html + 11 + + about.enterprise-sponsor-button + The Mempool Open Source Project @@ -1359,27 +1395,11 @@ 14 - - Become a Community Sponsor - - src/app/components/about/about.component.html - 39 - - about.community-sponsor-button - - - Become an Enterprise Sponsor - - src/app/components/about/about.component.html - 46 - - about.enterprise-sponsor-button - Enterprise Sponsors 🚀 src/app/components/about/about.component.html - 55 + 40 about.sponsors.enterprise.withRocket @@ -1387,7 +1407,7 @@ Whale Sponsors src/app/components/about/about.component.html - 202 + 187 about.sponsors.withHeart @@ -1395,7 +1415,7 @@ Chad Sponsors src/app/components/about/about.component.html - 215 + 200 about.sponsors.withHeart @@ -1403,7 +1423,7 @@ OG Sponsors ❤️ src/app/components/about/about.component.html - 228 + 213 about.sponsors.withHeart @@ -1411,7 +1431,7 @@ Community Integrations src/app/components/about/about.component.html - 239 + 224 about.community-integrations @@ -1419,7 +1439,7 @@ Community Alliances src/app/components/about/about.component.html - 353 + 338 about.alliances @@ -1427,7 +1447,7 @@ Project Translators src/app/components/about/about.component.html - 369 + 354 about.translators @@ -1435,7 +1455,7 @@ Project Contributors src/app/components/about/about.component.html - 383 + 368 about.contributors @@ -1443,7 +1463,7 @@ Project Members src/app/components/about/about.component.html - 395 + 380 about.project_members @@ -1451,7 +1471,7 @@ Project Maintainers src/app/components/about/about.component.html - 408 + 393 about.maintainers @@ -1459,7 +1479,7 @@ About src/app/components/about/about.component.ts - 45 + 47 src/app/components/bisq-master-page/bisq-master-page.component.html @@ -1471,14 +1491,14 @@ src/app/components/master-page/master-page.component.html - 71 + 75 Learn more about The Mempool Open Source Project®: enterprise sponsors, individual sponsors, integrations, who contributes, FOSS licensing, and more. src/app/components/about/about.component.ts - 46 + 48 @@ -1487,6 +1507,14 @@ src/app/components/accelerate-preview/accelerate-fee-graph.component.html 15 + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 34 + + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 42 + src/app/components/block-overview-tooltip/block-overview-tooltip.component.html 23 @@ -1501,7 +1529,7 @@ src/app/components/transaction/transaction.component.html - 503 + 515 src/app/components/transactions-list/transactions-list.component.html @@ -1514,35 +1542,35 @@ sats src/app/components/accelerate-preview/accelerate-preview.component.html - 54 + 59 src/app/components/accelerate-preview/accelerate-preview.component.html - 79 + 84 src/app/components/accelerate-preview/accelerate-preview.component.html - 113 + 118 src/app/components/accelerate-preview/accelerate-preview.component.html - 133 + 138 src/app/components/accelerate-preview/accelerate-preview.component.html - 145 + 150 src/app/components/accelerate-preview/accelerate-preview.component.html - 162 + 167 src/app/components/accelerate-preview/accelerate-preview.component.html - 185 + 190 src/app/components/accelerate-preview/accelerate-preview.component.html - 208 + 213 src/app/components/calculator/calculator.component.html @@ -1610,15 +1638,15 @@ sat/vB src/app/components/accelerate-preview/accelerate-preview.component.html - 103 + 108 src/app/components/accelerate-preview/accelerate-preview.component.html - 168 + 173 src/app/components/accelerate-preview/accelerate-preview.component.html - 193 + 198 src/app/shared/components/fee-rate/fee-rate.component.html @@ -1627,6 +1655,391 @@ sat/vB shared.sat-vbyte + + Acceleration Fees + + src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.html + 6 + + accelerator.acceleration-fees + + + Total Bid Boost + + src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.html + 32 + + + src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html + 11 + + + src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html + 39 + + acceleration.total-bid-boost + + + Acceleration Fees + + src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts + 69 + + + src/app/components/block-fees-graph/block-fees-graph.component.html + 6 + + + src/app/components/block-fees-graph/block-fees-graph.component.ts + 67 + + + src/app/components/graphs/graphs.component.html + 19 + + + + At block: + + src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts + 211 + + + src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts + 255 + + + src/app/components/block-health-graph/block-health-graph.component.ts + 143 + + + src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts + 166 + + + + Around block: + + src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts + 213 + + + src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts + 257 + + + src/app/components/block-health-graph/block-health-graph.component.ts + 145 + + + src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts + 168 + + + + Requests + + src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html + 4 + + + src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html + 32 + + + src/app/components/acceleration/pending-stats/pending-stats.component.html + 4 + + + src/app/components/acceleration/pending-stats/pending-stats.component.html + 32 + + accelerator.requests + + + accelerated + + src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html + 7 + + accelerator.total-accelerated + + + BTC + + src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html + 13 + + + src/app/components/acceleration/pending-stats/pending-stats.component.html + 13 + + BTC + shared.btc + + + Success rate + + src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html + 20 + + + src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html + 46 + + accelerator.success-rate + + + mined + + src/app/components/acceleration/acceleration-stats/acceleration-stats.component.html + 23 + + accelerator.mined-next-block + + + Accelerations + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 2 + + master-page.blocks + + + Fee Rate + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 12 + + Transaction fee + transaction.fee + + + Acceleration Bid + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 13 + + Transaction fee + transaction.fee + + + Requested + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 14 + + accelerator.block + + + Bid Boost + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 17 + + Bid Boost + transaction.bid-boost + + + Block + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 18 + + + src/app/components/block/block-preview.component.html + 3 + + + src/app/components/block/block.component.html + 9 + + accelerator.block + + + Status + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 19 + + + src/app/components/rbf-timeline/rbf-timeline-tooltip.component.html + 33 + + + src/app/dashboard/dashboard.component.html + 88 + + + src/app/lightning/channels-list/channels-list.component.html + 40 + + Transaction Status + transaction.status + + + Pending + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 51 + + accelerator.pending + + + Mined + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 52 + + + src/app/components/pool/pool.component.html + 213 + + + src/app/components/pool/pool.component.html + 275 + + + src/app/components/rbf-list/rbf-list.component.html + 23 + + + src/app/components/rbf-timeline/rbf-timeline-tooltip.component.html + 38 + + + src/app/dashboard/dashboard.component.html + 100 + + + src/app/dashboard/dashboard.component.html + 120 + + transaction.rbf.mined + + + Canceled + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 53 + + accelerator.canceled + + + There are no active accelerations + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 86 + + accelerations.no-accelerations + + + There are no recent accelerations + + src/app/components/acceleration/accelerations-list/accelerations-list.component.html + 87 + + accelerations.no-accelerations + + + Active accelerations + + src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html + 10 + + accelerator.pending-accelerations + + + Acceleration stats + + src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html + 24 + + accelerator.acceleration-stats + + + (1 month) + + src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html + 25 + + mining.144-blocks + + + Active Accelerations + + src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html + 72 + + dashboard.recent-accelerations + + + Recent Accelerations + + src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.html + 84 + + dashboard.recent-accelerations + + + Accelerator Dashboard + + src/app/components/acceleration/accelerator-dashboard/accelerator-dashboard.component.ts + 39 + + + src/app/components/master-page/master-page.component.html + 59 + + + src/app/components/mining-dashboard/mining-dashboard.component.ts + 20 + + + src/app/shared/components/global-footer/global-footer.component.html + 42 + + + + pending + + src/app/components/acceleration/pending-stats/pending-stats.component.html + 7 + + accelerator.total-pending + + + Avg Max Bid + + src/app/components/acceleration/pending-stats/pending-stats.component.html + 11 + + + src/app/components/acceleration/pending-stats/pending-stats.component.html + 39 + + accelerator.average-max-bid + + + Total vsize + + src/app/components/acceleration/pending-stats/pending-stats.component.html + 20 + + + src/app/components/acceleration/pending-stats/pending-stats.component.html + 46 + + accelerator.total-vsize + + + of next block + + src/app/components/acceleration/pending-stats/pending-stats.component.html + 23 + + accelerator.percent-of-next-block + Multisig of @@ -2079,7 +2492,7 @@ src/app/components/master-page/master-page.component.html - 52 + 53 master-page.dashboard @@ -2111,7 +2524,7 @@ src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts - 66 + 72 src/app/components/graphs/graphs.component.html @@ -2119,59 +2532,37 @@ mining.block-fee-rates + + Avg Block Fee (24h) + + src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html + 51 + + + src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html + 76 + + mining.avg-block-fee-24h + + + Avg Block Fee (1m) + + src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html + 57 + + + src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.html + 84 + + mining.avg-block-fee-1m + See Bitcoin feerates visualized over time, including minimum and maximum feerates per block along with feerates at various percentiles. src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts - 67 + 73 - - At block: - - src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts - 194 - - - src/app/components/block-health-graph/block-health-graph.component.ts - 143 - - - src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts - 166 - - - - Around block: - - src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts - 196 - - - src/app/components/block-health-graph/block-health-graph.component.ts - 145 - - - src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts - 168 - - - - Block Fees - - src/app/components/block-fees-graph/block-fees-graph.component.html - 6 - - - src/app/components/block-fees-graph/block-fees-graph.component.ts - 67 - - - src/app/components/graphs/graphs.component.html - 19 - - mining.block-fees - See the average mining fees earned per Bitcoin block visualized in BTC and USD over time. @@ -2209,10 +2600,22 @@ src/app/components/pool/pool-preview.component.ts 120 + + + select filter categories to highlight matching transactions - src/app/components/pool/pool.component.ts - 124 + src/app/components/block-filters/block-filters.component.html + 2 + Mempool Goggles tooltip + + + beta + + src/app/components/block-filters/block-filters.component.html + 3 + + beta Block Health @@ -2294,7 +2697,7 @@ src/app/components/transaction/transaction.component.html - 502 + 514 src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html @@ -2315,11 +2718,11 @@ src/app/components/transaction/transaction.component.html - 168 + 172 src/app/components/transaction/transaction.component.html - 506 + 518 src/app/lightning/channel/channel-box/channel-box.component.html @@ -2344,7 +2747,7 @@ src/app/components/transaction/transaction.component.html - 517 + 529 Effective transaction fee rate transaction.effective-fee-rate @@ -2370,11 +2773,11 @@ src/app/components/transaction/transaction.component.html - 166 + 170 src/app/components/transaction/transaction.component.html - 294 + 298 Transaction Virtual Size transaction.vsize @@ -2391,7 +2794,7 @@ src/app/components/transaction/transaction.component.html - 167 + 171 Transaction Weight transaction.weight @@ -2478,6 +2881,14 @@ src/app/components/block-overview-tooltip/block-overview-tooltip.component.html 58 + + src/app/components/block-overview-tooltip/block-overview-tooltip.component.html + 63 + + + src/app/components/transaction/transaction.component.html + 539 + transaction.audit.accelerated @@ -2566,7 +2977,7 @@ src/app/components/transaction/transaction.component.html - 290 + 294 src/app/dashboard/dashboard.component.html @@ -2593,15 +3004,19 @@ src/app/components/block/block.component.html - 410 + 414 src/app/components/block/block.component.html - 434 + 438 + + + src/app/components/block/block.component.html + 467 src/app/components/transaction/transaction.component.html - 302 + 310 @@ -2660,18 +3075,6 @@ 269 - - Block - - src/app/components/block/block-preview.component.html - 3 - - - src/app/components/block/block.component.html - 9 - - shared.block-title - Median fee @@ -2704,11 +3107,15 @@ src/app/components/block/block.component.html - 404 + 408 src/app/components/block/block.component.html - 425 + 429 + + + src/app/components/block/block.component.html + 463 src/app/components/mempool-block/mempool-block.component.html @@ -2750,7 +3157,7 @@ src/app/components/mempool-block/mempool-block.component.ts - 81 + 84 Next Block @@ -2904,18 +3311,6 @@ block.expected - - beta - - src/app/components/block/block.component.html - 222 - - - src/app/components/block/block.component.html - 228 - - beta - Actual @@ -2936,7 +3331,7 @@ Actual Block src/app/components/block/block.component.html - 240 + 242 block.actual-block @@ -2944,7 +3339,7 @@ Taproot src/app/components/block/block.component.html - 265 + 269 src/app/components/tx-features/tx-features.component.html @@ -2973,7 +3368,7 @@ Bits src/app/components/block/block.component.html - 268 + 272 block.bits @@ -2981,7 +3376,7 @@ Merkle root src/app/components/block/block.component.html - 272 + 276 block.merkle-root @@ -2989,7 +3384,7 @@ Difficulty src/app/components/block/block.component.html - 283 + 287 src/app/components/difficulty-adjustments-table/difficulty-adjustments-table.component.html @@ -3005,11 +3400,11 @@ src/app/components/hashrate-chart/hashrate-chart.component.ts - 310 + 304 src/app/components/hashrate-chart/hashrate-chart.component.ts - 397 + 391 block.difficulty @@ -3017,7 +3412,7 @@ Nonce src/app/components/block/block.component.html - 287 + 291 block.nonce @@ -3025,7 +3420,7 @@ Block Header Hex src/app/components/block/block.component.html - 291 + 295 block.header @@ -3033,7 +3428,7 @@ Audit src/app/components/block/block.component.html - 309 + 313 Toggle Audit block.toggle-audit @@ -3042,11 +3437,11 @@ Details src/app/components/block/block.component.html - 316 + 320 src/app/components/transaction/transaction.component.html - 274 + 278 src/app/lightning/channel/channel.component.html @@ -3075,11 +3470,11 @@ Error loading data. src/app/components/block/block.component.html - 335 + 339 src/app/components/block/block.component.html - 374 + 378 src/app/lightning/channel/channel-preview.component.html @@ -3099,7 +3494,7 @@ Why is this block empty? src/app/components/block/block.component.html - 396 + 400 block.empty-block-explanation @@ -3199,7 +3594,7 @@ src/app/dashboard/dashboard.component.html - 232 + 254 dashboard.txs @@ -3267,7 +3662,7 @@ src/app/dashboard/dashboard.component.html - 236 + 258 Memory usage dashboard.memory-usage @@ -3284,7 +3679,7 @@ src/app/dashboard/dashboard.component.html - 230 + 252 Unconfirmed count dashboard.unconfirmed @@ -3595,7 +3990,7 @@ src/app/dashboard/dashboard.component.html - 262 + 284 dashboard.backend-is-synchronizing @@ -3607,7 +4002,7 @@ src/app/dashboard/dashboard.component.html - 267 + 289 vB/s shared.vbytes-per-second @@ -3620,7 +4015,7 @@ src/app/dashboard/dashboard.component.html - 268 + 290 WU/s shared.weight-per-second @@ -3790,11 +4185,11 @@ src/app/components/hashrate-chart/hashrate-chart.component.ts - 299 + 293 src/app/components/hashrate-chart/hashrate-chart.component.ts - 385 + 379 src/app/components/pool-ranking/pool-ranking.component.html @@ -3829,11 +4224,11 @@ Hashrate (MA) src/app/components/hashrate-chart/hashrate-chart.component.ts - 318 + 312 src/app/components/hashrate-chart/hashrate-chart.component.ts - 408 + 402 @@ -3865,7 +4260,7 @@ src/app/components/master-page/master-page.component.html - 65 + 69 src/app/components/statistics/statistics.component.ts @@ -3873,27 +4268,19 @@ master-page.graphs - - Mining Dashboard + + Acceleration Dashboard src/app/components/master-page/master-page.component.html - 55 + 56 - - src/app/components/mining-dashboard/mining-dashboard.component.ts - 20 - - - src/app/shared/components/global-footer/global-footer.component.html - 42 - - mining.mining-dashboard + master-page.acceleration-dashboard Lightning Explorer src/app/components/master-page/master-page.component.html - 58 + 62 src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts @@ -3909,7 +4296,7 @@ Documentation src/app/components/master-page/master-page.component.html - 68 + 72 src/app/docs/docs/docs.component.html @@ -3921,21 +4308,21 @@ See stats for transactions in the mempool: fee range, aggregate size, and more. Mempool blocks are updated in real-time as the network receives new transactions. src/app/components/mempool-block/mempool-block.component.ts - 58 + 59 Stack of mempool blocks src/app/components/mempool-block/mempool-block.component.ts - 83 + 86 Mempool block src/app/components/mempool-block/mempool-block.component.ts - 85 + 88 @@ -4291,6 +4678,10 @@ src/app/components/pool/pool.component.html 68 + + src/app/components/rbf-timeline/rbf-timeline.component.html + 57 + src/app/components/transactions-list/transactions-list.component.html 129 @@ -4373,34 +4764,6 @@ 1w - - Mined - - src/app/components/pool/pool.component.html - 213 - - - src/app/components/pool/pool.component.html - 275 - - - src/app/components/rbf-list/rbf-list.component.html - 23 - - - src/app/components/rbf-timeline/rbf-timeline-tooltip.component.html - 38 - - - src/app/dashboard/dashboard.component.html - 100 - - - src/app/dashboard/dashboard.component.html - 120 - - latest-blocks.mined - Coinbase tag @@ -4413,6 +4776,13 @@ latest-blocks.coinbasetag + + Not enough data yet + + src/app/components/pool/pool.component.ts + 124 + + Broadcast Transaction @@ -4438,7 +4808,7 @@ src/app/components/transaction/transaction.component.html - 324 + 336 transaction.hex @@ -4507,7 +4877,7 @@ src/app/components/transaction/transaction.component.html - 108 + 112 src/app/lightning/node/node.component.html @@ -4536,23 +4906,6 @@ Transaction first seen transaction.first-seen - - Status - - src/app/components/rbf-timeline/rbf-timeline-tooltip.component.html - 33 - - - src/app/dashboard/dashboard.component.html - 88 - - - src/app/lightning/channels-list/channels-list.component.html - 40 - - Transaction Status - transaction.status - RBF @@ -4583,6 +4936,34 @@ RBF tx-features.tag.rbf + + Show less + + src/app/components/rbf-timeline/rbf-timeline.component.html + 61 + + + src/app/components/transaction/transaction.component.html + 257 + + + src/app/components/transactions-list/transactions-list.component.html + 130 + + show-less + + + remaining + + src/app/components/rbf-timeline/rbf-timeline.component.html + 86 + + + src/app/components/transactions-list/transactions-list.component.html + 343 + + x-remaining + Miners Reward @@ -4693,7 +5074,7 @@ Bitcoin Transaction src/app/components/search-form/search-results/search-results.component.html - 9 + 21 search.bitcoin-transaction @@ -4701,7 +5082,7 @@ Bitcoin Address src/app/components/search-form/search-results/search-results.component.html - 15 + 27 search.bitcoin-address @@ -4709,15 +5090,23 @@ Bitcoin Block src/app/components/search-form/search-results/search-results.component.html - 21 + 33 search.bitcoin-block + + Other Network Address + + src/app/components/search-form/search-results/search-results.component.html + 39 + + search.other-networks + Bitcoin Addresses src/app/components/search-form/search-results/search-results.component.html - 27 + 47 search.bitcoin-addresses @@ -4725,7 +5114,7 @@ Lightning Nodes src/app/components/search-form/search-results/search-results.component.html - 35 + 55 search.lightning-nodes @@ -4733,7 +5122,7 @@ Lightning Channels src/app/components/search-form/search-results/search-results.component.html - 43 + 63 search.lightning-channels @@ -4741,7 +5130,7 @@ Go to "" src/app/components/search-form/search-results/search-results.component.html - 52 + 72 search.go-to @@ -5024,14 +5413,14 @@ transactions-list.coinbase - Get real-time status, addresses, fees, script info, and more for transaction with txid . + Get real-time status, addresses, fees, script info, and more for transaction with txid . src/app/components/transaction/transaction-preview.component.ts - 91 + 93 src/app/components/transaction/transaction.component.ts - 293 + 318 @@ -5043,11 +5432,19 @@ RBF replacement transaction.rbf.replacement + + Hide accelerator + + src/app/components/transaction/transaction.component.html + 90 + + hide-accelerator + ETA src/app/components/transaction/transaction.component.html - 114 + 118 Transaction ETA transaction.eta @@ -5056,7 +5453,7 @@ In several hours (or more) src/app/components/transaction/transaction.component.html - 122 + 126 Transaction ETA in several hours or more transaction.eta.in-several-hours @@ -5065,11 +5462,11 @@ Accelerate src/app/components/transaction/transaction.component.html - 123 + 127 src/app/components/transaction/transaction.component.html - 133 + 137 Accelerate button label transaction.accelerate @@ -5078,11 +5475,11 @@ Descendant src/app/components/transaction/transaction.component.html - 175 + 179 src/app/components/transaction/transaction.component.html - 187 + 191 Descendant transaction.descendant @@ -5091,7 +5488,7 @@ Ancestor src/app/components/transaction/transaction.component.html - 199 + 203 Transaction Ancestor transaction.ancestor @@ -5100,7 +5497,7 @@ RBF History src/app/components/transaction/transaction.component.html - 218 + 222 RBF History transaction.rbf-history @@ -5109,11 +5506,11 @@ Flow src/app/components/transaction/transaction.component.html - 227 + 231 src/app/components/transaction/transaction.component.html - 374 + 386 Transaction flow transaction.flow @@ -5122,7 +5519,7 @@ Hide diagram src/app/components/transaction/transaction.component.html - 230 + 234 hide-diagram @@ -5130,7 +5527,7 @@ Show more src/app/components/transaction/transaction.component.html - 251 + 255 src/app/components/transactions-list/transactions-list.component.html @@ -5142,31 +5539,19 @@ show-more - - Show less - - src/app/components/transaction/transaction.component.html - 253 - - - src/app/components/transactions-list/transactions-list.component.html - 130 - - show-less - Show diagram src/app/components/transaction/transaction.component.html - 273 + 277 show-diagram - - Adjusted vsize + + Adjusted vsize src/app/components/transaction/transaction.component.html - 298 + 302,306 Transaction Adjusted VSize transaction.adjusted-vsize @@ -5175,15 +5560,15 @@ Locktime src/app/components/transaction/transaction.component.html - 316 + 324 transaction.locktime - - Sigops + + Sigops src/app/components/transaction/transaction.component.html - 320 + 328,332 Transaction Sigops transaction.sigops @@ -5192,7 +5577,7 @@ Transaction not found. src/app/components/transaction/transaction.component.html - 483 + 495 transaction.error.transaction-not-found @@ -5200,7 +5585,7 @@ Waiting for it to appear in the mempool... src/app/components/transaction/transaction.component.html - 484 + 496 transaction.error.waiting-for-it-to-appear @@ -5208,7 +5593,7 @@ Accelerated fee rate src/app/components/transaction/transaction.component.html - 516 + 528 Accelerated transaction fee rate transaction.accelerated-fee-rate @@ -5337,14 +5722,6 @@ transactions-list.load-to-reveal-fee-info - - remaining - - src/app/components/transactions-list/transactions-list.component.html - 343 - - x-remaining - other inputs @@ -5560,7 +5937,7 @@ Minimum fee src/app/dashboard/dashboard.component.html - 223 + 245 Minimum mempool fee dashboard.minimum-fee @@ -5569,7 +5946,7 @@ Purging src/app/dashboard/dashboard.component.html - 224 + 246 Purgin below fee dashboard.purging @@ -5578,7 +5955,7 @@ L-BTC in circulation src/app/dashboard/dashboard.component.html - 250 + 272 dashboard.lbtc-pegs-in-circulation @@ -5586,7 +5963,7 @@ Incoming Transactions src/app/dashboard/dashboard.component.html - 259 + 281 dashboard.incoming-transactions @@ -6600,7 +6977,7 @@ src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts - 33 + 34 src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html @@ -6620,7 +6997,7 @@ src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts - 37 + 38 src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html @@ -7263,14 +7640,14 @@ See Lightning nodes with the most BTC liquidity deployed along with high-level stats like number of open channels, location, node age, and more. src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.ts - 34 + 35 See Lightning nodes with the most channels open along with high-level stats like total node capacity, node age, and more. src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.ts - 38 + 39 diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index be8cec328..8a4fe3c9a 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -1191,3 +1191,7 @@ app-global-footer { line-height: 0.5; border-radius: 0.2rem; } + +.info-link fa-icon { + color: rgba(255, 255, 255, 0.4); +} diff --git a/production/bitcoin.conf b/production/bitcoin.conf index fb8ad52e5..cdfb5715c 100644 --- a/production/bitcoin.conf +++ b/production/bitcoin.conf @@ -44,6 +44,12 @@ zmqpubrawtx=tcp://127.0.0.1:8335 #addnode=[2401:b140:3::92:204]:8333 #addnode=[2401:b140:3::92:205]:8333 #addnode=[2401:b140:3::92:206]:8333 +#addnode=[2401:b140:4::92:201]:8333 +#addnode=[2401:b140:4::92:202]:8333 +#addnode=[2401:b140:4::92:203]:8333 +#addnode=[2401:b140:4::92:204]:8333 +#addnode=[2401:b140:4::92:205]:8333 +#addnode=[2401:b140:4::92:206]:8333 [test] daemon=1 @@ -71,6 +77,12 @@ zmqpubrawtx=tcp://127.0.0.1:18335 #addnode=[2401:b140:3::92:204]:18333 #addnode=[2401:b140:3::92:205]:18333 #addnode=[2401:b140:3::92:206]:18333 +#addnode=[2401:b140:4::92:201]:18333 +#addnode=[2401:b140:4::92:202]:18333 +#addnode=[2401:b140:4::92:203]:18333 +#addnode=[2401:b140:4::92:204]:18333 +#addnode=[2401:b140:4::92:205]:18333 +#addnode=[2401:b140:4::92:206]:18333 [signet] daemon=1 @@ -98,3 +110,9 @@ zmqpubrawtx=tcp://127.0.0.1:38335 #addnode=[2401:b140:3::92:204]:38333 #addnode=[2401:b140:3::92:205]:38333 #addnode=[2401:b140:3::92:206]:38333 +#addnode=[2401:b140:4::92:201]:38333 +#addnode=[2401:b140:4::92:202]:38333 +#addnode=[2401:b140:4::92:203]:38333 +#addnode=[2401:b140:4::92:204]:38333 +#addnode=[2401:b140:4::92:205]:38333 +#addnode=[2401:b140:4::92:206]:38333 diff --git a/production/bitcoin.crontab b/production/bitcoin.crontab index ba1fa9701..429e9d793 100644 --- a/production/bitcoin.crontab +++ b/production/bitcoin.crontab @@ -1,5 +1,5 @@ @reboot sleep 5 ; /usr/local/bin/bitcoind -testnet >/dev/null 2>&1 @reboot sleep 5 ; /usr/local/bin/bitcoind -signet >/dev/null 2>&1 -@reboot sleep 10 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet -@reboot sleep 10 ; screen -dmS testnet /bitcoin/electrs/electrs-start-testnet -@reboot sleep 10 ; screen -dmS signet /bitcoin/electrs/electrs-start-signet +@reboot sleep 10 ; screen -dmS mainnet /bitcoin/electrs/start mainnet +@reboot sleep 10 ; screen -dmS testnet /bitcoin/electrs/start testnet +@reboot sleep 10 ; screen -dmS signet /bitcoin/electrs/start signet diff --git a/production/elements.crontab b/production/elements.crontab index f4a42ed39..4f837706e 100644 --- a/production/elements.crontab +++ b/production/elements.crontab @@ -3,8 +3,8 @@ @reboot sleep 5 ; /usr/local/bin/elementsd -chain=liquidtestnet >/dev/null 2>&1 # start electrs on reboot -@reboot sleep 20 ; screen -dmS liquidv1 /elements/electrs/electrs-start-liquid -@reboot sleep 20 ; screen -dmS liquidtestnet /elements/electrs/electrs-start-liquidtestnet +@reboot sleep 20 ; screen -dmS liquidv1 /elements/electrs/start liquid +@reboot sleep 20 ; screen -dmS liquidtestnet /elements/electrs/start liquidtestnet # hourly asset update and electrs restart 6 * * * * cd $HOME/asset_registry_db && git pull --quiet origin master && cd $HOME/asset_registry_testnet_db && git pull --quiet origin master && killall electrs diff --git a/production/mempool-config.mainnet.json b/production/mempool-config.mainnet.json index 36310e59d..e3ce58ceb 100644 --- a/production/mempool-config.mainnet.json +++ b/production/mempool-config.mainnet.json @@ -3,6 +3,7 @@ "NETWORK": "mainnet", "BACKEND": "esplora", "HTTP_PORT": 8999, + "CACHE_ENABLED": false, "MINED_BLOCKS_CACHE": 144, "SPAWN_CLUSTER_PROCS": 0, "API_URL_PREFIX": "/api/v1/", diff --git a/production/nginx/http-basic.conf b/production/nginx/http-basic.conf index dc880cad5..fd5cc4b94 100644 --- a/production/nginx/http-basic.conf +++ b/production/nginx/http-basic.conf @@ -19,6 +19,8 @@ client_header_timeout 10s; keepalive_timeout 69s; # maximum time between packets nginx is allowed to pause when sending the client data send_timeout 69s; +# maximum time to wait for response from upstream backends +proxy_read_timeout 120s; # number of requests per connection, does not affect SPDY keepalive_requests 1337;