mirror of
https://github.com/mempool/mempool.git
synced 2025-04-07 19:38:32 +02:00
Merge branch 'master' into cross-network-address-search
This commit is contained in:
commit
9a7e22806d
14
backend/package-lock.json
generated
14
backend/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
3
backend/rust-gbt/index.d.ts
vendored
3
backend/rust-gbt/index.d.ts
vendored
@ -45,5 +45,6 @@ export class GbtResult {
|
||||
blockWeights: Array<number>
|
||||
clusters: Array<Array<number>>
|
||||
rates: Array<Array<number>>
|
||||
constructor(blocks: Array<Array<number>>, blockWeights: Array<number>, clusters: Array<Array<number>>, rates: Array<Array<number>>)
|
||||
overflow: Array<number>
|
||||
constructor(blocks: Array<Array<number>>, blockWeights: Array<number>, clusters: Array<Array<number>>, rates: Array<Array<number>>, overflow: Array<number>)
|
||||
}
|
||||
|
@ -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<u32> = 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<u32> = 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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,6 +133,7 @@ pub struct GbtResult {
|
||||
pub block_weights: Vec<u32>,
|
||||
pub clusters: Vec<Vec<u32>>,
|
||||
pub rates: Vec<Vec<f64>>, // Tuples not supported. u32 fits inside f64
|
||||
pub overflow: Vec<u32>,
|
||||
}
|
||||
|
||||
/// All on another thread, this runs an arbitrary task in between
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -173,10 +173,13 @@ function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
|
||||
// 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 = [];
|
||||
|
@ -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']) {
|
||||
|
@ -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<number> {
|
||||
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 {
|
||||
|
@ -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;
|
||||
|
198
frontend/package-lock.json
generated
198
frontend/package-lock.json
generated
@ -59,7 +59,7 @@
|
||||
"@cypress/schematic": "^2.5.0",
|
||||
"@types/cypress": "^1.1.3",
|
||||
"cypress": "^13.6.0",
|
||||
"cypress-fail-on-console-error": "~5.0.0",
|
||||
"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": "*"
|
||||
}
|
||||
@ -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",
|
||||
@ -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": {
|
||||
|
@ -111,7 +111,7 @@
|
||||
"@cypress/schematic": "^2.5.0",
|
||||
"@types/cypress": "^1.1.3",
|
||||
"cypress": "^13.6.0",
|
||||
"cypress-fail-on-console-error": "~5.0.0",
|
||||
"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"
|
||||
|
@ -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;
|
||||
|
@ -59,7 +59,7 @@
|
||||
<td [innerHTML]="'‎' + (block.weight | wuBytes: 2)"></td>
|
||||
</tr>
|
||||
<tr *ngIf="auditAvailable">
|
||||
<td><ng-container i18n="latest-blocks.health">Health</ng-container> <a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="what-is-block-health"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></td>
|
||||
<td><ng-container i18n="latest-blocks.health">Health</ng-container> <a class="info-link" [routerLink]="['/docs/faq' | relativeUrl ]" fragment="what-is-block-health"><fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></a></td>
|
||||
<td>
|
||||
<span
|
||||
class="health-badge badge"
|
||||
|
@ -57,11 +57,6 @@
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
.info-link {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.difference {
|
||||
margin-left: 0.5em;
|
||||
|
||||
|
@ -3,8 +3,8 @@ import { Component, ComponentRef, ViewChild, HostListener, Input, Output, EventE
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { MempoolBlockDelta, TransactionStripped } from '../../interfaces/websocket.interface';
|
||||
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
|
||||
import { Subscription, BehaviorSubject, merge, of } from 'rxjs';
|
||||
import { switchMap, filter } from 'rxjs/operators';
|
||||
import { Subscription, BehaviorSubject, merge, of, timer } from 'rxjs';
|
||||
import { switchMap, filter, concatMap, map } from 'rxjs/operators';
|
||||
import { WebsocketService } from '../../services/websocket.service';
|
||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||
import { Router } from '@angular/router';
|
||||
@ -33,7 +33,9 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
||||
poolDirection: string = 'left';
|
||||
|
||||
blockSub: Subscription;
|
||||
deltaSub: Subscription;
|
||||
rateLimit = 1000;
|
||||
private lastEventTime = Date.now() - this.rateLimit;
|
||||
private subId = 0;
|
||||
|
||||
firstLoad: boolean = true;
|
||||
|
||||
@ -55,11 +57,39 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.blockSub = merge(
|
||||
of(true),
|
||||
this.stateService.connectionState$.pipe(filter((state) => state === 2))
|
||||
)
|
||||
.pipe(switchMap(() => this.stateService.mempoolBlockTransactions$))
|
||||
.subscribe((transactionsStripped) => {
|
||||
this.stateService.mempoolBlockTransactions$,
|
||||
this.stateService.mempoolBlockDelta$,
|
||||
).pipe(
|
||||
concatMap(update => {
|
||||
const now = Date.now();
|
||||
const timeSinceLastEvent = now - this.lastEventTime;
|
||||
this.lastEventTime = Math.max(now, this.lastEventTime + this.rateLimit);
|
||||
|
||||
const subId = this.subId;
|
||||
|
||||
// If time since last event is less than X seconds, delay this event
|
||||
if (timeSinceLastEvent < this.rateLimit) {
|
||||
return timer(this.rateLimit - timeSinceLastEvent).pipe(
|
||||
// Emit the event after the timer
|
||||
map(() => ({ update, subId }))
|
||||
);
|
||||
} else {
|
||||
// If enough time has passed, emit the event immediately
|
||||
return of({ update, subId });
|
||||
}
|
||||
})
|
||||
).subscribe(({ update, subId }) => {
|
||||
// discard stale updates after a block transition
|
||||
if (subId !== this.subId) {
|
||||
return;
|
||||
}
|
||||
// process update
|
||||
if (update['added']) {
|
||||
// delta
|
||||
this.updateBlock(update as MempoolBlockDelta);
|
||||
} else {
|
||||
const transactionsStripped = update as TransactionStripped[];
|
||||
// new transactions
|
||||
if (this.firstLoad) {
|
||||
this.replaceBlock(transactionsStripped);
|
||||
} else {
|
||||
@ -94,14 +124,13 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
||||
added
|
||||
});
|
||||
}
|
||||
});
|
||||
this.deltaSub = this.stateService.mempoolBlockDelta$.subscribe((delta) => {
|
||||
this.updateBlock(delta);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes): void {
|
||||
if (changes.index) {
|
||||
this.subId++;
|
||||
this.firstLoad = true;
|
||||
if (this.blockGraph) {
|
||||
this.blockGraph.clear(changes.index.currentValue > changes.index.previousValue ? this.chainDirection : this.poolDirection);
|
||||
@ -113,7 +142,6 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.blockSub.unsubscribe();
|
||||
this.deltaSub.unsubscribe();
|
||||
this.timeLtrSubscription.unsubscribe();
|
||||
this.websocketService.stopTrackMempoolBlock();
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.stateService.markBlock$.next({});
|
||||
this.websocketService.stopTrackMempoolBlock();
|
||||
}
|
||||
|
||||
getOrdinal(mempoolBlock: MempoolBlock): string {
|
||||
|
@ -299,7 +299,11 @@
|
||||
<td [innerHTML]="'‎' + (tx.weight / 4 | vbytes: 2)"></td>
|
||||
</tr>
|
||||
<tr *ngIf="adjustedVsize != null">
|
||||
<td i18n="transaction.adjusted-vsize|Transaction Adjusted VSize">Adjusted vsize</td>
|
||||
<td i18n="transaction.adjusted-vsize|Transaction Adjusted VSize">Adjusted vsize
|
||||
<a class="info-link" [routerLink]="['/docs/faq/' | relativeUrl]" fragment="what-is-adjusted-vsize">
|
||||
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon>
|
||||
</a>
|
||||
</td>
|
||||
<td [innerHTML]="'‎' + (adjustedVsize | vbytes: 2)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -321,7 +325,11 @@
|
||||
<td [innerHTML]="'‎' + (tx.locktime | number)"></td>
|
||||
</tr>
|
||||
<tr *ngIf="sigops != null">
|
||||
<td i18n="transaction.sigops|Transaction Sigops">Sigops</td>
|
||||
<td i18n="transaction.sigops|Transaction Sigops">Sigops
|
||||
<a class="info-link" [routerLink]="['/docs/faq/' | relativeUrl]" fragment="what-are-sigops">
|
||||
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon>
|
||||
</a>
|
||||
</td>
|
||||
<td [innerHTML]="'‎' + (sigops | number)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -368,6 +368,28 @@
|
||||
</dl>
|
||||
</ng-template>
|
||||
|
||||
<ng-template type="what-are-sigops">
|
||||
<p>A "sigop" is a way of accounting for the cost of "signature operations" in Bitcoin script, like <code>OP_CHECKSIG</code>, <code>OP_CHECKSIGVERIFY</code>, <code>OP_CHECKMULTISIG</code> and <code>OP_CHECKMULTISIGVERIFY</code></p>
|
||||
<p>These signature operations incur different costs depending on whether they are single or multi-sig operations, and on where they appear in a Bitcoin transaction.</p>
|
||||
<p>By consensus, each Bitcoin block is permitted to include a maximum of 80,000 sigops.</p>
|
||||
</ng-template>
|
||||
|
||||
<ng-template type="what-is-adjusted-vsize">
|
||||
<p>Bitcoin blocks have two independent consensus-enforced resource constraints - a 4MWU weight limit, and the 80,000 sigop limit.</p>
|
||||
<p>Most transactions use a more of the weight limit than the sigop limit. However, some transactions use a disproportionate number of sigops compared to their weight.</p>
|
||||
<p>To account for this, Bitcoin Core calculates and uses an "adjusted vsize" equal 5 times the number of sigops, or the unadjusted vsize, whichever is larger.</p>
|
||||
<p>Then, during block template construction, Bitcoin Core selects transactions in descending order of fee rate measured in satoshis per <i>adjusted vsize</i></p>
|
||||
<p>On mempool.space, effective fee rates for unconfirmed transactions are also measured in terms of satoshis per adjusted vsize, after accounting for CPFP relationships and other dependencies.</p>
|
||||
</ng-template>
|
||||
|
||||
<ng-template type="why-do-the-projected-block-fee-ranges-overlap">
|
||||
<p>The projected mempool blocks represent what we expect the next blocks would look like if they were mined right now, and so each projected block follows all of the same rules and constraints as real mined blocks.</p>
|
||||
<p>Those constraints can sometimes cause transactions with lower fee rates to be included "ahead" of transactions with higher rates.</p>
|
||||
<p>For example, if one projected block has a very small amount of space left, it might be able to fit one more tiny low fee rate transaction, while larger higher fee rate transactions have to wait for the following block.</p>
|
||||
<p>A similar effect can occur when there are a large number of transactions with very many sigops. In that scenario, each projected block can only include up to 80,000 sigops worth of transactions, after which the remaining space can only be filled by potentially much lower fee transactions with zero sigops.</p>
|
||||
<p>In extreme cases this can produce several projected blocks in a row with overlapping fee ranges, as a result of each projected block containing both high-feerate high-sigop transactions and lower feerate zero-sigop transactions.</p>
|
||||
</ng-template>
|
||||
|
||||
<ng-template type="who-runs-this-website">
|
||||
The official mempool.space website is operated by The Mempool Open Source Project. See more information on our <a [routerLink]="['/about']">About page</a>. There are also many unofficial instances of this website operated by individual members of the Bitcoin community.
|
||||
</ng-template>
|
||||
|
@ -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 },
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user