Merge branch 'master' into natsoni/fix-network-errors
This commit is contained in:
commit
974eaeb02f
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: Install ${{ steps.gettoolchain.outputs.toolchain }} Rust toolchain
|
- name: Install ${{ steps.gettoolchain.outputs.toolchain }} Rust toolchain
|
||||||
# Latest version available on this commit is 1.71.1
|
# Latest version available on this commit is 1.71.1
|
||||||
# Commit date is Aug 3, 2023
|
# Commit date is Aug 3, 2023
|
||||||
uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248
|
uses: dtolnay/rust-toolchain@dc6353516c68da0f06325f42ad880f76a5e77ec9
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ steps.gettoolchain.outputs.toolchain }}
|
toolchain: ${{ steps.gettoolchain.outputs.toolchain }}
|
||||||
|
|
||||||
|
@ -7,13 +7,14 @@ const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first
|
|||||||
|
|
||||||
class Audit {
|
class Audit {
|
||||||
auditBlock(transactions: MempoolTransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: MempoolTransactionExtended }, useAccelerations: boolean = false)
|
auditBlock(transactions: MempoolTransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: MempoolTransactionExtended }, useAccelerations: boolean = false)
|
||||||
: { censored: string[], added: string[], fresh: string[], sigop: string[], fullrbf: string[], accelerated: string[], score: number, similarity: number } {
|
: { censored: string[], added: string[], prioritized: string[], fresh: string[], sigop: string[], fullrbf: string[], accelerated: string[], score: number, similarity: number } {
|
||||||
if (!projectedBlocks?.[0]?.transactionIds || !mempool) {
|
if (!projectedBlocks?.[0]?.transactionIds || !mempool) {
|
||||||
return { censored: [], added: [], fresh: [], sigop: [], fullrbf: [], accelerated: [], score: 1, similarity: 1 };
|
return { censored: [], added: [], prioritized: [], fresh: [], sigop: [], fullrbf: [], accelerated: [], score: 1, similarity: 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const matches: string[] = []; // present in both mined block and template
|
const matches: string[] = []; // present in both mined block and template
|
||||||
const added: string[] = []; // present in mined block, not in template
|
const added: string[] = []; // present in mined block, not in template
|
||||||
|
const prioritized: string[] = [] // present in the mined block, not in the template, but further down in the mempool
|
||||||
const fresh: string[] = []; // missing, but firstSeen or lastBoosted within PROPAGATION_MARGIN
|
const fresh: string[] = []; // missing, but firstSeen or lastBoosted within PROPAGATION_MARGIN
|
||||||
const rbf: string[] = []; // either missing or present, and either part of a full-rbf replacement, or a conflict with the mined block
|
const rbf: string[] = []; // either missing or present, and either part of a full-rbf replacement, or a conflict with the mined block
|
||||||
const accelerated: string[] = []; // prioritized by the mempool accelerator
|
const accelerated: string[] = []; // prioritized by the mempool accelerator
|
||||||
@ -68,20 +69,27 @@ class Audit {
|
|||||||
|
|
||||||
// we can expect an honest miner to include 'displaced' transactions in place of recent arrivals and censored txs
|
// we can expect an honest miner to include 'displaced' transactions in place of recent arrivals and censored txs
|
||||||
// these displaced transactions should occupy the first N weight units of the next projected block
|
// these displaced transactions should occupy the first N weight units of the next projected block
|
||||||
let displacedWeightRemaining = displacedWeight;
|
let displacedWeightRemaining = displacedWeight + 4000;
|
||||||
let index = 0;
|
let index = 0;
|
||||||
let lastFeeRate = Infinity;
|
let lastFeeRate = Infinity;
|
||||||
let failures = 0;
|
let failures = 0;
|
||||||
while (projectedBlocks[1] && index < projectedBlocks[1].transactionIds.length && failures < 500) {
|
let blockIndex = 1;
|
||||||
const txid = projectedBlocks[1].transactionIds[index];
|
while (projectedBlocks[blockIndex] && failures < 500) {
|
||||||
|
if (index >= projectedBlocks[blockIndex].transactionIds.length) {
|
||||||
|
index = 0;
|
||||||
|
blockIndex++;
|
||||||
|
}
|
||||||
|
const txid = projectedBlocks[blockIndex].transactionIds[index];
|
||||||
const tx = mempool[txid];
|
const tx = mempool[txid];
|
||||||
if (tx) {
|
if (tx) {
|
||||||
const fits = (tx.weight - displacedWeightRemaining) < 4000;
|
const fits = (tx.weight - displacedWeightRemaining) < 4000;
|
||||||
const feeMatches = tx.effectiveFeePerVsize >= lastFeeRate;
|
// 0.005 margin of error for any remaining vsize rounding issues
|
||||||
|
const feeMatches = tx.effectiveFeePerVsize >= (lastFeeRate - 0.005);
|
||||||
if (fits || feeMatches) {
|
if (fits || feeMatches) {
|
||||||
isDisplaced[txid] = true;
|
isDisplaced[txid] = true;
|
||||||
if (fits) {
|
if (fits) {
|
||||||
lastFeeRate = Math.min(lastFeeRate, tx.effectiveFeePerVsize);
|
// (tx.effectiveFeePerVsize * tx.vsize) / Math.ceil(tx.vsize) attempts to correct for vsize rounding in the simple non-CPFP case
|
||||||
|
lastFeeRate = Math.min(lastFeeRate, (tx.effectiveFeePerVsize * tx.vsize) / Math.ceil(tx.vsize));
|
||||||
}
|
}
|
||||||
if (tx.firstSeen == null || (now - (tx?.firstSeen || 0)) > PROPAGATION_MARGIN) {
|
if (tx.firstSeen == null || (now - (tx?.firstSeen || 0)) > PROPAGATION_MARGIN) {
|
||||||
displacedWeightRemaining -= tx.weight;
|
displacedWeightRemaining -= tx.weight;
|
||||||
@ -106,7 +114,11 @@ class Audit {
|
|||||||
if (rbfCache.has(tx.txid)) {
|
if (rbfCache.has(tx.txid)) {
|
||||||
rbf.push(tx.txid);
|
rbf.push(tx.txid);
|
||||||
} else if (!isDisplaced[tx.txid]) {
|
} else if (!isDisplaced[tx.txid]) {
|
||||||
added.push(tx.txid);
|
if (mempool[tx.txid]) {
|
||||||
|
prioritized.push(tx.txid);
|
||||||
|
} else {
|
||||||
|
added.push(tx.txid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
overflowWeight += tx.weight;
|
overflowWeight += tx.weight;
|
||||||
}
|
}
|
||||||
@ -155,6 +167,7 @@ class Audit {
|
|||||||
return {
|
return {
|
||||||
censored: Object.keys(isCensored),
|
censored: Object.keys(isCensored),
|
||||||
added,
|
added,
|
||||||
|
prioritized,
|
||||||
fresh,
|
fresh,
|
||||||
sigop: [],
|
sigop: [],
|
||||||
fullrbf: rbf,
|
fullrbf: rbf,
|
||||||
|
@ -552,6 +552,7 @@ export class Common {
|
|||||||
value: tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0),
|
value: tx.vout.reduce((acc, vout) => acc + (vout.value ? vout.value : 0), 0),
|
||||||
acc: tx.acceleration || undefined,
|
acc: tx.acceleration || undefined,
|
||||||
rate: tx.effectiveFeePerVsize,
|
rate: tx.effectiveFeePerVsize,
|
||||||
|
time: tx.firstSeen || undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
|||||||
import { RowDataPacket } from 'mysql2';
|
import { RowDataPacket } from 'mysql2';
|
||||||
|
|
||||||
class DatabaseMigration {
|
class DatabaseMigration {
|
||||||
private static currentVersion = 75;
|
private static currentVersion = 76;
|
||||||
private queryTimeout = 3600_000;
|
private queryTimeout = 3600_000;
|
||||||
private statisticsAddedIndexed = false;
|
private statisticsAddedIndexed = false;
|
||||||
private uniqueLogs: string[] = [];
|
private uniqueLogs: string[] = [];
|
||||||
@ -654,6 +654,11 @@ class DatabaseMigration {
|
|||||||
await this.$executeQuery('ALTER TABLE `prices` ADD `ZAR` float DEFAULT "-1"');
|
await this.$executeQuery('ALTER TABLE `prices` ADD `ZAR` float DEFAULT "-1"');
|
||||||
await this.updateToSchemaVersion(75);
|
await this.updateToSchemaVersion(75);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 76 && isBitcoin === true) {
|
||||||
|
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD prioritized_txs JSON DEFAULT "[]"');
|
||||||
|
await this.updateToSchemaVersion(76);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -598,7 +598,8 @@ class MempoolBlocks {
|
|||||||
tx.value,
|
tx.value,
|
||||||
Math.round((tx.rate || (tx.fee / tx.vsize)) * 100) / 100,
|
Math.round((tx.rate || (tx.fee / tx.vsize)) * 100) / 100,
|
||||||
tx.flags,
|
tx.flags,
|
||||||
1
|
tx.time || 0,
|
||||||
|
1,
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
return [
|
return [
|
||||||
@ -608,6 +609,7 @@ class MempoolBlocks {
|
|||||||
tx.value,
|
tx.value,
|
||||||
Math.round((tx.rate || (tx.fee / tx.vsize)) * 100) / 100,
|
Math.round((tx.rate || (tx.fee / tx.vsize)) * 100) / 100,
|
||||||
tx.flags,
|
tx.flags,
|
||||||
|
tx.time || 0,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,7 @@ class WebsocketHandler {
|
|||||||
const _blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
|
const _blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
|
||||||
const da = difficultyAdjustment.getDifficultyAdjustment();
|
const da = difficultyAdjustment.getDifficultyAdjustment();
|
||||||
this.updateSocketDataFields({
|
this.updateSocketDataFields({
|
||||||
|
'backend': config.MEMPOOL.BACKEND,
|
||||||
'mempoolInfo': memPool.getMempoolInfo(),
|
'mempoolInfo': memPool.getMempoolInfo(),
|
||||||
'vBytesPerSecond': memPool.getVBytesPerSecond(),
|
'vBytesPerSecond': memPool.getVBytesPerSecond(),
|
||||||
'blocks': _blocks,
|
'blocks': _blocks,
|
||||||
@ -868,7 +869,7 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Common.indexingEnabled()) {
|
if (Common.indexingEnabled()) {
|
||||||
const { censored, added, fresh, sigop, fullrbf, accelerated, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool);
|
const { censored, added, prioritized, fresh, sigop, fullrbf, accelerated, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool);
|
||||||
const matchRate = Math.round(score * 100 * 100) / 100;
|
const matchRate = Math.round(score * 100 * 100) / 100;
|
||||||
|
|
||||||
const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions : [];
|
const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions : [];
|
||||||
@ -894,6 +895,7 @@ class WebsocketHandler {
|
|||||||
height: block.height,
|
height: block.height,
|
||||||
hash: block.id,
|
hash: block.id,
|
||||||
addedTxs: added,
|
addedTxs: added,
|
||||||
|
prioritizedTxs: prioritized,
|
||||||
missingTxs: censored,
|
missingTxs: censored,
|
||||||
freshTxs: fresh,
|
freshTxs: fresh,
|
||||||
sigopTxs: sigop,
|
sigopTxs: sigop,
|
||||||
|
@ -37,6 +37,7 @@ export interface BlockAudit {
|
|||||||
sigopTxs: string[],
|
sigopTxs: string[],
|
||||||
fullrbfTxs: string[],
|
fullrbfTxs: string[],
|
||||||
addedTxs: string[],
|
addedTxs: string[],
|
||||||
|
prioritizedTxs: string[],
|
||||||
acceleratedTxs: string[],
|
acceleratedTxs: string[],
|
||||||
matchRate: number,
|
matchRate: number,
|
||||||
expectedFees?: number,
|
expectedFees?: number,
|
||||||
@ -200,6 +201,7 @@ export interface TransactionStripped {
|
|||||||
value: number;
|
value: number;
|
||||||
acc?: boolean;
|
acc?: boolean;
|
||||||
rate?: number; // effective fee rate
|
rate?: number; // effective fee rate
|
||||||
|
time?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransactionClassified extends TransactionStripped {
|
export interface TransactionClassified extends TransactionStripped {
|
||||||
@ -207,7 +209,7 @@ export interface TransactionClassified extends TransactionStripped {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// [txid, fee, vsize, value, rate, flags, acceleration?]
|
// [txid, fee, vsize, value, rate, flags, acceleration?]
|
||||||
export type TransactionCompressed = [string, number, number, number, number, number, 1?];
|
export type TransactionCompressed = [string, number, number, number, number, number, number, 1?];
|
||||||
// [txid, rate, flags, acceleration?]
|
// [txid, rate, flags, acceleration?]
|
||||||
export type MempoolDeltaChange = [string, number, number, (1|0)];
|
export type MempoolDeltaChange = [string, number, number, (1|0)];
|
||||||
|
|
||||||
|
@ -114,6 +114,7 @@ class AuditReplication {
|
|||||||
time: auditSummary.timestamp || auditSummary.time,
|
time: auditSummary.timestamp || auditSummary.time,
|
||||||
missingTxs: auditSummary.missingTxs || [],
|
missingTxs: auditSummary.missingTxs || [],
|
||||||
addedTxs: auditSummary.addedTxs || [],
|
addedTxs: auditSummary.addedTxs || [],
|
||||||
|
prioritizedTxs: auditSummary.prioritizedTxs || [],
|
||||||
freshTxs: auditSummary.freshTxs || [],
|
freshTxs: auditSummary.freshTxs || [],
|
||||||
sigopTxs: auditSummary.sigopTxs || [],
|
sigopTxs: auditSummary.sigopTxs || [],
|
||||||
fullrbfTxs: auditSummary.fullrbfTxs || [],
|
fullrbfTxs: auditSummary.fullrbfTxs || [],
|
||||||
|
@ -6,9 +6,9 @@ import { BlockAudit, AuditScore } from '../mempool.interfaces';
|
|||||||
class BlocksAuditRepositories {
|
class BlocksAuditRepositories {
|
||||||
public async $saveAudit(audit: BlockAudit): Promise<void> {
|
public async $saveAudit(audit: BlockAudit): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, fresh_txs, sigop_txs, fullrbf_txs, accelerated_txs, match_rate, expected_fees, expected_weight)
|
await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, prioritized_txs, fresh_txs, sigop_txs, fullrbf_txs, accelerated_txs, match_rate, expected_fees, expected_weight)
|
||||||
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs),
|
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs),
|
||||||
JSON.stringify(audit.addedTxs), JSON.stringify(audit.freshTxs), JSON.stringify(audit.sigopTxs), JSON.stringify(audit.fullrbfTxs), JSON.stringify(audit.acceleratedTxs), audit.matchRate, audit.expectedFees, audit.expectedWeight]);
|
JSON.stringify(audit.addedTxs), JSON.stringify(audit.prioritizedTxs), JSON.stringify(audit.freshTxs), JSON.stringify(audit.sigopTxs), JSON.stringify(audit.fullrbfTxs), JSON.stringify(audit.acceleratedTxs), audit.matchRate, audit.expectedFees, audit.expectedWeight]);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
||||||
logger.debug(`Cannot save block audit for block ${audit.hash} because it has already been indexed, ignoring`);
|
logger.debug(`Cannot save block audit for block ${audit.hash} because it has already been indexed, ignoring`);
|
||||||
@ -66,6 +66,7 @@ class BlocksAuditRepositories {
|
|||||||
template,
|
template,
|
||||||
missing_txs as missingTxs,
|
missing_txs as missingTxs,
|
||||||
added_txs as addedTxs,
|
added_txs as addedTxs,
|
||||||
|
prioritized_txs as prioritizedTxs,
|
||||||
fresh_txs as freshTxs,
|
fresh_txs as freshTxs,
|
||||||
sigop_txs as sigopTxs,
|
sigop_txs as sigopTxs,
|
||||||
fullrbf_txs as fullrbfTxs,
|
fullrbf_txs as fullrbfTxs,
|
||||||
@ -81,6 +82,7 @@ class BlocksAuditRepositories {
|
|||||||
if (rows.length) {
|
if (rows.length) {
|
||||||
rows[0].missingTxs = JSON.parse(rows[0].missingTxs);
|
rows[0].missingTxs = JSON.parse(rows[0].missingTxs);
|
||||||
rows[0].addedTxs = JSON.parse(rows[0].addedTxs);
|
rows[0].addedTxs = JSON.parse(rows[0].addedTxs);
|
||||||
|
rows[0].prioritizedTxs = JSON.parse(rows[0].prioritizedTxs);
|
||||||
rows[0].freshTxs = JSON.parse(rows[0].freshTxs);
|
rows[0].freshTxs = JSON.parse(rows[0].freshTxs);
|
||||||
rows[0].sigopTxs = JSON.parse(rows[0].sigopTxs);
|
rows[0].sigopTxs = JSON.parse(rows[0].sigopTxs);
|
||||||
rows[0].fullrbfTxs = JSON.parse(rows[0].fullrbfTxs);
|
rows[0].fullrbfTxs = JSON.parse(rows[0].fullrbfTxs);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM node:20.11.1-buster-slim AS builder
|
FROM node:20.12.0-buster-slim AS builder
|
||||||
|
|
||||||
ARG commitHash
|
ARG commitHash
|
||||||
ENV MEMPOOL_COMMIT_HASH=${commitHash}
|
ENV MEMPOOL_COMMIT_HASH=${commitHash}
|
||||||
@ -17,7 +17,7 @@ ENV PATH="/root/.cargo/bin:$PATH"
|
|||||||
RUN npm install --omit=dev --omit=optional
|
RUN npm install --omit=dev --omit=optional
|
||||||
RUN npm run package
|
RUN npm run package
|
||||||
|
|
||||||
FROM node:20.11.1-buster-slim
|
FROM node:20.12.0-buster-slim
|
||||||
|
|
||||||
WORKDIR /backend
|
WORKDIR /backend
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM node:20.11.1-buster-slim AS builder
|
FROM node:20.12.0-buster-slim AS builder
|
||||||
|
|
||||||
ARG commitHash
|
ARG commitHash
|
||||||
ENV DOCKER_COMMIT_HASH=${commitHash}
|
ENV DOCKER_COMMIT_HASH=${commitHash}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
"@typescript-eslint/no-this-alias": 1,
|
"@typescript-eslint/no-this-alias": 1,
|
||||||
"@typescript-eslint/no-var-requires": 1,
|
"@typescript-eslint/no-var-requires": 1,
|
||||||
"@typescript-eslint/explicit-function-return-type": 1,
|
"@typescript-eslint/explicit-function-return-type": 1,
|
||||||
|
"@typescript-eslint/no-unused-vars": 1,
|
||||||
"no-case-declarations": 1,
|
"no-case-declarations": 1,
|
||||||
"no-console": 1,
|
"no-console": 1,
|
||||||
"no-constant-condition": 1,
|
"no-constant-condition": 1,
|
||||||
|
@ -223,11 +223,11 @@
|
|||||||
"serve": {
|
"serve": {
|
||||||
"builder": "@angular-devkit/build-angular:dev-server",
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "mempool:build"
|
"buildTarget": "mempool:build"
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"browserTarget": "mempool:build:production"
|
"buildTarget": "mempool:build:production"
|
||||||
},
|
},
|
||||||
"local": {
|
"local": {
|
||||||
"proxyConfig": "proxy.conf.local.js",
|
"proxyConfig": "proxy.conf.local.js",
|
||||||
@ -264,7 +264,7 @@
|
|||||||
"extract-i18n": {
|
"extract-i18n": {
|
||||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "mempool:build"
|
"buildTarget": "mempool:build"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"e2e": {
|
"e2e": {
|
||||||
@ -303,7 +303,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"serve-ssr": {
|
"serve-ssr": {
|
||||||
"builder": "@nguniversal/builders:ssr-dev-server",
|
"builder": "@angular-devkit/build-angular:ssr-dev-server",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "mempool:build",
|
"browserTarget": "mempool:build",
|
||||||
"serverTarget": "mempool:server"
|
"serverTarget": "mempool:server"
|
||||||
@ -318,7 +318,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prerender": {
|
"prerender": {
|
||||||
"builder": "@nguniversal/builders:prerender",
|
"builder": "@angular-devkit/build-angular:prerender",
|
||||||
"options": {
|
"options": {
|
||||||
"browserTarget": "mempool:build:production",
|
"browserTarget": "mempool:build:production",
|
||||||
"serverTarget": "mempool:server:production",
|
"serverTarget": "mempool:server:production",
|
||||||
|
11999
frontend/package-lock.json
generated
11999
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -64,24 +64,25 @@
|
|||||||
"cypress:run:ci:staging": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:run:record"
|
"cypress:run:ci:staging": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:run:record"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular-devkit/build-angular": "^16.1.1",
|
"@angular-devkit/build-angular": "^17.3.1",
|
||||||
"@angular/animations": "^16.1.1",
|
"@angular/animations": "^17.3.1",
|
||||||
"@angular/cli": "^16.1.1",
|
"@angular/cli": "^17.3.1",
|
||||||
"@angular/common": "^16.1.1",
|
"@angular/common": "^17.3.1",
|
||||||
"@angular/compiler": "^16.1.1",
|
"@angular/compiler": "^17.3.1",
|
||||||
"@angular/core": "^16.1.1",
|
"@angular/core": "^17.3.1",
|
||||||
"@angular/forms": "^16.1.1",
|
"@angular/forms": "^17.3.1",
|
||||||
"@angular/localize": "^16.1.1",
|
"@angular/localize": "^17.3.1",
|
||||||
"@angular/platform-browser": "^16.1.1",
|
"@angular/platform-browser": "^17.3.1",
|
||||||
"@angular/platform-browser-dynamic": "^16.1.1",
|
"@angular/platform-browser-dynamic": "^17.3.1",
|
||||||
"@angular/platform-server": "^16.1.1",
|
"@angular/platform-server": "^17.3.1",
|
||||||
"@angular/router": "^16.1.1",
|
"@angular/router": "^17.3.1",
|
||||||
"@fortawesome/angular-fontawesome": "~0.13.0",
|
"@angular/ssr": "^17.3.1",
|
||||||
|
"@fortawesome/angular-fontawesome": "~0.14.1",
|
||||||
"@fortawesome/fontawesome-common-types": "~6.5.1",
|
"@fortawesome/fontawesome-common-types": "~6.5.1",
|
||||||
"@fortawesome/fontawesome-svg-core": "~6.5.1",
|
"@fortawesome/fontawesome-svg-core": "~6.5.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "~6.5.1",
|
"@fortawesome/free-solid-svg-icons": "~6.5.1",
|
||||||
"@mempool/mempool.js": "2.3.0",
|
"@mempool/mempool.js": "2.3.0",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^15.1.0",
|
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
|
||||||
"@types/qrcode": "~1.5.0",
|
"@types/qrcode": "~1.5.0",
|
||||||
"bootstrap": "~4.6.2",
|
"bootstrap": "~4.6.2",
|
||||||
"browserify": "^17.0.0",
|
"browserify": "^17.0.0",
|
||||||
@ -89,29 +90,29 @@
|
|||||||
"domino": "^2.1.6",
|
"domino": "^2.1.6",
|
||||||
"echarts": "~5.5.0",
|
"echarts": "~5.5.0",
|
||||||
"lightweight-charts": "~3.8.0",
|
"lightweight-charts": "~3.8.0",
|
||||||
"ngx-echarts": "~16.2.0",
|
"ngx-echarts": "~17.1.0",
|
||||||
"ngx-infinite-scroll": "^16.0.0",
|
"ngx-infinite-scroll": "^17.0.0",
|
||||||
"qrcode": "1.5.1",
|
"qrcode": "1.5.1",
|
||||||
"rxjs": "~7.8.1",
|
"rxjs": "~7.8.1",
|
||||||
"tinyify": "^3.1.0",
|
"esbuild": "^0.20.2",
|
||||||
|
"tinyify": "^4.0.0",
|
||||||
"tlite": "^0.1.9",
|
"tlite": "^0.1.9",
|
||||||
"tslib": "~2.6.0",
|
"tslib": "~2.6.0",
|
||||||
"zone.js": "~0.13.1"
|
"zone.js": "~0.14.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/compiler-cli": "^16.1.1",
|
"@angular/compiler-cli": "^17.3.1",
|
||||||
"@angular/language-service": "^16.1.1",
|
"@angular/language-service": "^17.3.1",
|
||||||
"@nguniversal/builders": "16.1.1",
|
|
||||||
"@nguniversal/express-engine": "16.1.1",
|
|
||||||
"@types/node": "^18.11.9",
|
"@types/node": "^18.11.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.48.1",
|
"@typescript-eslint/eslint-plugin": "^7.4.0",
|
||||||
"@typescript-eslint/parser": "^5.48.1",
|
"@typescript-eslint/parser": "^7.4.0",
|
||||||
"eslint": "^8.31.0",
|
"eslint": "^8.57.0",
|
||||||
|
"browser-sync": "^3.0.0",
|
||||||
"http-proxy-middleware": "~2.0.6",
|
"http-proxy-middleware": "~2.0.6",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"ts-node": "~10.9.1",
|
"ts-node": "~10.9.1",
|
||||||
"typescript": "~4.9.3"
|
"typescript": "~5.4.3"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@cypress/schematic": "^2.5.0",
|
"@cypress/schematic": "^2.5.0",
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'zone.js/dist/zone-node';
|
|
||||||
import './src/resources/config.js';
|
import './src/resources/config.js';
|
||||||
|
|
||||||
import * as domino from 'domino';
|
import * as domino from 'domino';
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import 'zone.js/dist/zone-node';
|
import 'zone.js';
|
||||||
import './src/resources/config.js';
|
import './src/resources/config.js';
|
||||||
|
|
||||||
import { ngExpressEngine } from '@nguniversal/express-engine';
|
import { CommonEngine } from '@angular/ssr';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as domino from 'domino';
|
import * as domino from 'domino';
|
||||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
|
||||||
|
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { AppServerModule } from './src/main.server';
|
import { AppServerModule } from './src/main.server';
|
||||||
@ -15,6 +14,8 @@ import { existsSync } from 'fs';
|
|||||||
|
|
||||||
import { ResizeObserver } from './shims';
|
import { ResizeObserver } from './shims';
|
||||||
|
|
||||||
|
const commonEngine = new CommonEngine();
|
||||||
|
|
||||||
const template = fs.readFileSync(path.join(process.cwd(), 'dist/mempool/browser/en-US/', 'index.html')).toString();
|
const template = fs.readFileSync(path.join(process.cwd(), 'dist/mempool/browser/en-US/', 'index.html')).toString();
|
||||||
const win = domino.createWindow(template);
|
const win = domino.createWindow(template);
|
||||||
|
|
||||||
@ -58,35 +59,32 @@ global['localStorage'] = {
|
|||||||
export function app(locale: string): express.Express {
|
export function app(locale: string): express.Express {
|
||||||
const server = express();
|
const server = express();
|
||||||
const distFolder = join(process.cwd(), `dist/mempool/browser/${locale}`);
|
const distFolder = join(process.cwd(), `dist/mempool/browser/${locale}`);
|
||||||
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
|
const indexHtml = join(distFolder, 'index.html');
|
||||||
|
|
||||||
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
|
|
||||||
server.engine('html', ngExpressEngine({
|
|
||||||
bootstrap: AppServerModule,
|
|
||||||
}));
|
|
||||||
|
|
||||||
server.set('view engine', 'html');
|
server.set('view engine', 'html');
|
||||||
server.set('views', distFolder);
|
server.set('views', distFolder);
|
||||||
|
|
||||||
|
|
||||||
// static file handler so we send HTTP 404 to nginx
|
// static file handler so we send HTTP 404 to nginx
|
||||||
server.get('/**.(css|js|json|ico|webmanifest|png|jpg|jpeg|svg|mp4)*', express.static(distFolder, { maxAge: '1y', fallthrough: false }));
|
server.get('/**.(css|js|json|ico|webmanifest|png|jpg|jpeg|svg|mp4)*', express.static(distFolder, { maxAge: '1y', fallthrough: false }));
|
||||||
// handle page routes
|
// handle page routes
|
||||||
server.get('/**', getLocalizedSSR(indexHtml));
|
server.get('*', (req, res, next) => {
|
||||||
|
const { protocol, originalUrl, baseUrl, headers } = req;
|
||||||
|
|
||||||
|
commonEngine
|
||||||
|
.render({
|
||||||
|
bootstrap: AppServerModule,
|
||||||
|
documentFilePath: indexHtml,
|
||||||
|
url: `${protocol}://${headers.host}${originalUrl}`,
|
||||||
|
publicPath: distFolder,
|
||||||
|
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
|
||||||
|
})
|
||||||
|
.then((html) => res.send(html))
|
||||||
|
.catch((err) => next(err));
|
||||||
|
});
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLocalizedSSR(indexHtml) {
|
|
||||||
return (req, res) => {
|
|
||||||
res.render(indexHtml, {
|
|
||||||
req,
|
|
||||||
providers: [
|
|
||||||
{ provide: APP_BASE_HREF, useValue: req.baseUrl }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// only used for development mode
|
// only used for development mode
|
||||||
function run(): void {
|
function run(): void {
|
||||||
@ -107,6 +105,4 @@ const mainModule = __non_webpack_require__.main;
|
|||||||
const moduleFilename = mainModule && mainModule.filename || '';
|
const moduleFilename = mainModule && mainModule.filename || '';
|
||||||
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
|
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
|
|
||||||
export * from './src/main.server';
|
|
@ -51,7 +51,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="address && transactions && transactions.length > 2">
|
<ng-container *ngIf="(stateService.backend$ | async) === 'esplora' && address && transactions && transactions.length > 2">
|
||||||
<br>
|
<br>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -44,7 +44,7 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private electrsApiService: ElectrsApiService,
|
private electrsApiService: ElectrsApiService,
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
private stateService: StateService,
|
public stateService: StateService,
|
||||||
private audioService: AudioService,
|
private audioService: AudioService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
[blockConversion]="blockConversion"
|
[blockConversion]="blockConversion"
|
||||||
[filterFlags]="activeFilterFlags"
|
[filterFlags]="activeFilterFlags"
|
||||||
[filterMode]="filterMode"
|
[filterMode]="filterMode"
|
||||||
|
[relativeTime]="relativeTime"
|
||||||
></app-block-overview-tooltip>
|
></app-block-overview-tooltip>
|
||||||
<app-block-filters *ngIf="webGlEnabled && showFilters && filtersAvailable" [excludeFilters]="excludeFilters" [cssWidth]="cssWidth" (onFilterChanged)="setFilterFlags($event)"></app-block-filters>
|
<app-block-filters *ngIf="webGlEnabled && showFilters && filtersAvailable" [excludeFilters]="excludeFilters" [cssWidth]="cssWidth" (onFilterChanged)="setFilterFlags($event)"></app-block-filters>
|
||||||
<div *ngIf="!webGlEnabled" class="placeholder">
|
<div *ngIf="!webGlEnabled" class="placeholder">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, ElementRef, ViewChild, HostListener, Input, Output, EventEmitter, NgZone, AfterViewInit, OnDestroy, OnChanges } from '@angular/core';
|
import { Component, ElementRef, ViewChild, HostListener, Input, Output, EventEmitter, NgZone, AfterViewInit, OnDestroy, OnChanges } from '@angular/core';
|
||||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
|
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
import { FastVertexArray } from './fast-vertex-array';
|
import { FastVertexArray } from './fast-vertex-array';
|
||||||
import BlockScene from './block-scene';
|
import BlockScene from './block-scene';
|
||||||
import TxSprite from './tx-sprite';
|
import TxSprite from './tx-sprite';
|
||||||
@ -20,7 +20,7 @@ const unmatchedAuditColors = {
|
|||||||
censored: setOpacity(defaultAuditColors.censored, unmatchedOpacity),
|
censored: setOpacity(defaultAuditColors.censored, unmatchedOpacity),
|
||||||
missing: setOpacity(defaultAuditColors.missing, unmatchedOpacity),
|
missing: setOpacity(defaultAuditColors.missing, unmatchedOpacity),
|
||||||
added: setOpacity(defaultAuditColors.added, unmatchedOpacity),
|
added: setOpacity(defaultAuditColors.added, unmatchedOpacity),
|
||||||
selected: setOpacity(defaultAuditColors.selected, unmatchedOpacity),
|
prioritized: setOpacity(defaultAuditColors.prioritized, unmatchedOpacity),
|
||||||
accelerated: setOpacity(defaultAuditColors.accelerated, unmatchedOpacity),
|
accelerated: setOpacity(defaultAuditColors.accelerated, unmatchedOpacity),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -46,6 +46,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||||||
@Input() excludeFilters: string[] = [];
|
@Input() excludeFilters: string[] = [];
|
||||||
@Input() filterFlags: bigint | null = null;
|
@Input() filterFlags: bigint | null = null;
|
||||||
@Input() filterMode: FilterMode = 'and';
|
@Input() filterMode: FilterMode = 'and';
|
||||||
|
@Input() relativeTime: number | null;
|
||||||
@Input() blockConversion: Price;
|
@Input() blockConversion: Price;
|
||||||
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
|
@Input() overrideColors: ((tx: TxView) => Color) | null = null;
|
||||||
@Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>();
|
@Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { FastVertexArray } from './fast-vertex-array';
|
import { FastVertexArray } from './fast-vertex-array';
|
||||||
import TxView from './tx-view';
|
import TxView from './tx-view';
|
||||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
|
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
import { Color, Position, Square, ViewUpdateParams } from './sprite-types';
|
import { Color, Position, Square, ViewUpdateParams } from './sprite-types';
|
||||||
import { defaultColorFunction } from './utils';
|
import { defaultColorFunction } from './utils';
|
||||||
|
|
||||||
|
@ -32,7 +32,8 @@ export default class TxView implements TransactionStripped {
|
|||||||
rate?: number;
|
rate?: number;
|
||||||
flags: number;
|
flags: number;
|
||||||
bigintFlags?: bigint | null = 0b00000100_00000000_00000000_00000000n;
|
bigintFlags?: bigint | null = 0b00000100_00000000_00000000_00000000n;
|
||||||
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated';
|
time?: number;
|
||||||
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'prioritized' | 'censored' | 'selected' | 'rbf' | 'accelerated';
|
||||||
context?: 'projected' | 'actual';
|
context?: 'projected' | 'actual';
|
||||||
scene?: BlockScene;
|
scene?: BlockScene;
|
||||||
|
|
||||||
@ -53,6 +54,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
this.scene = scene;
|
this.scene = scene;
|
||||||
this.context = tx.context;
|
this.context = tx.context;
|
||||||
this.txid = tx.txid;
|
this.txid = tx.txid;
|
||||||
|
this.time = tx.time || 0;
|
||||||
this.fee = tx.fee;
|
this.fee = tx.fee;
|
||||||
this.vsize = tx.vsize;
|
this.vsize = tx.vsize;
|
||||||
this.value = tx.value;
|
this.value = tx.value;
|
||||||
|
@ -45,7 +45,7 @@ export const defaultAuditColors = {
|
|||||||
censored: hexToColor('f344df'),
|
censored: hexToColor('f344df'),
|
||||||
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7),
|
||||||
added: hexToColor('0099ff'),
|
added: hexToColor('0099ff'),
|
||||||
selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
|
prioritized: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7),
|
||||||
accelerated: hexToColor('8F5FF6'),
|
accelerated: hexToColor('8F5FF6'),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,6 +81,8 @@ export function defaultColorFunction(
|
|||||||
return auditColors.missing;
|
return auditColors.missing;
|
||||||
case 'added':
|
case 'added':
|
||||||
return auditColors.added;
|
return auditColors.added;
|
||||||
|
case 'prioritized':
|
||||||
|
return auditColors.prioritized;
|
||||||
case 'selected':
|
case 'selected':
|
||||||
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
||||||
case 'accelerated':
|
case 'accelerated':
|
||||||
|
@ -14,6 +14,26 @@
|
|||||||
<a [routerLink]="['/tx/' | relativeUrl, txid]">{{ txid | shortenString : 16}}</a>
|
<a [routerLink]="['/tx/' | relativeUrl, txid]">{{ txid | shortenString : 16}}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr *ngIf="time">
|
||||||
|
<ng-container [ngSwitch]="timeMode">
|
||||||
|
<ng-container *ngSwitchCase="'mempool'">
|
||||||
|
<td class="label" i18n="transaction.first-seen|Transaction first seen">First seen</td>
|
||||||
|
<td class="value"><i><app-time kind="since" [time]="time" [fastRender]="true"></app-time></i></td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngSwitchCase="'missed'">
|
||||||
|
<td class="label" i18n="transaction.first-seen|Transaction first seen">First seen</td>
|
||||||
|
<td class="value"><i><app-time kind="before" [time]="relativeTime - time"></app-time></i></td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngSwitchCase="'after'">
|
||||||
|
<td class="label" i18n="transaction.first-seen|Transaction first seen">First seen</td>
|
||||||
|
<td class="value"><i><app-time kind="span" [time]="time - relativeTime"></app-time></i></td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngSwitchCase="'mined'">
|
||||||
|
<td class="label" i18n="transaction.confirmed-after|Transaction confirmed after">Confirmed</td>
|
||||||
|
<td class="value"><i><app-time kind="span" [time]="relativeTime - time"></app-time></i></td>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="label" i18n="dashboard.latest-transactions.amount">Amount</td>
|
<td class="label" i18n="dashboard.latest-transactions.amount">Amount</td>
|
||||||
<td class="value"><app-amount [blockConversion]="blockConversion" [satoshis]="value" [noFiat]="true"></app-amount></td>
|
<td class="value"><app-amount [blockConversion]="blockConversion" [satoshis]="value" [noFiat]="true"></app-amount></td>
|
||||||
@ -54,6 +74,7 @@
|
|||||||
<span *ngSwitchCase="'fresh'" class="badge badge-warning" i18n="transaction.audit.recently-broadcasted">Recently broadcasted</span>
|
<span *ngSwitchCase="'fresh'" class="badge badge-warning" i18n="transaction.audit.recently-broadcasted">Recently broadcasted</span>
|
||||||
<span *ngSwitchCase="'freshcpfp'" class="badge badge-warning" i18n="transaction.audit.recently-cpfped">Recently CPFP'd</span>
|
<span *ngSwitchCase="'freshcpfp'" class="badge badge-warning" i18n="transaction.audit.recently-cpfped">Recently CPFP'd</span>
|
||||||
<span *ngSwitchCase="'added'" class="badge badge-warning" i18n="transaction.audit.added">Added</span>
|
<span *ngSwitchCase="'added'" class="badge badge-warning" i18n="transaction.audit.added">Added</span>
|
||||||
|
<span *ngSwitchCase="'prioritized'" class="badge badge-warning" i18n="transaction.audit.prioritized">Prioritized</span>
|
||||||
<span *ngSwitchCase="'selected'" class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span>
|
<span *ngSwitchCase="'selected'" class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span>
|
||||||
<span *ngSwitchCase="'rbf'" class="badge badge-warning" i18n="transaction.audit.conflicting">Conflicting</span>
|
<span *ngSwitchCase="'rbf'" class="badge badge-warning" i18n="transaction.audit.conflicting">Conflicting</span>
|
||||||
<span *ngSwitchCase="'accelerated'" class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span>
|
<span *ngSwitchCase="'accelerated'" class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span>
|
||||||
|
@ -3,6 +3,7 @@ import { Position } from '../../components/block-overview-graph/sprite-types.js'
|
|||||||
import { Price } from '../../services/price.service';
|
import { Price } from '../../services/price.service';
|
||||||
import { TransactionStripped } from '../../interfaces/node-api.interface.js';
|
import { TransactionStripped } from '../../interfaces/node-api.interface.js';
|
||||||
import { Filter, FilterMode, TransactionFlags, toFilters } from '../../shared/filters.utils';
|
import { Filter, FilterMode, TransactionFlags, toFilters } from '../../shared/filters.utils';
|
||||||
|
import { Block } from '../../interfaces/electrs.interface.js';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block-overview-tooltip',
|
selector: 'app-block-overview-tooltip',
|
||||||
@ -11,6 +12,7 @@ import { Filter, FilterMode, TransactionFlags, toFilters } from '../../shared/fi
|
|||||||
})
|
})
|
||||||
export class BlockOverviewTooltipComponent implements OnChanges {
|
export class BlockOverviewTooltipComponent implements OnChanges {
|
||||||
@Input() tx: TransactionStripped | void;
|
@Input() tx: TransactionStripped | void;
|
||||||
|
@Input() relativeTime?: number;
|
||||||
@Input() cursorPosition: Position;
|
@Input() cursorPosition: Position;
|
||||||
@Input() clickable: boolean;
|
@Input() clickable: boolean;
|
||||||
@Input() auditEnabled: boolean = false;
|
@Input() auditEnabled: boolean = false;
|
||||||
@ -19,6 +21,7 @@ export class BlockOverviewTooltipComponent implements OnChanges {
|
|||||||
@Input() filterMode: FilterMode = 'and';
|
@Input() filterMode: FilterMode = 'and';
|
||||||
|
|
||||||
txid = '';
|
txid = '';
|
||||||
|
time: number = 0;
|
||||||
fee = 0;
|
fee = 0;
|
||||||
value = 0;
|
value = 0;
|
||||||
vsize = 1;
|
vsize = 1;
|
||||||
@ -26,6 +29,7 @@ export class BlockOverviewTooltipComponent implements OnChanges {
|
|||||||
effectiveRate;
|
effectiveRate;
|
||||||
acceleration;
|
acceleration;
|
||||||
hasEffectiveRate: boolean = false;
|
hasEffectiveRate: boolean = false;
|
||||||
|
timeMode: 'mempool' | 'mined' | 'missed' | 'after' = 'mempool';
|
||||||
filters: Filter[] = [];
|
filters: Filter[] = [];
|
||||||
activeFilters: { [key: string]: boolean } = {};
|
activeFilters: { [key: string]: boolean } = {};
|
||||||
|
|
||||||
@ -56,6 +60,7 @@ export class BlockOverviewTooltipComponent implements OnChanges {
|
|||||||
|
|
||||||
if (this.tx && (changes.tx || changes.filterFlags || changes.filterMode)) {
|
if (this.tx && (changes.tx || changes.filterFlags || changes.filterMode)) {
|
||||||
this.txid = this.tx.txid || '';
|
this.txid = this.tx.txid || '';
|
||||||
|
this.time = this.tx.time || 0;
|
||||||
this.fee = this.tx.fee || 0;
|
this.fee = this.tx.fee || 0;
|
||||||
this.value = this.tx.value || 0;
|
this.value = this.tx.value || 0;
|
||||||
this.vsize = this.tx.vsize || 1;
|
this.vsize = this.tx.vsize || 1;
|
||||||
@ -72,6 +77,22 @@ export class BlockOverviewTooltipComponent implements OnChanges {
|
|||||||
this.activeFilters[filter.key] = true;
|
this.activeFilters[filter.key] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.relativeTime) {
|
||||||
|
this.timeMode = 'mempool';
|
||||||
|
} else {
|
||||||
|
if (this.tx?.context === 'actual' || this.tx?.status === 'found') {
|
||||||
|
this.timeMode = 'mined';
|
||||||
|
} else {
|
||||||
|
const time = this.relativeTime || Date.now();
|
||||||
|
if (this.time <= time) {
|
||||||
|
this.timeMode = 'missed';
|
||||||
|
} else {
|
||||||
|
this.timeMode = 'after';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
[orientation]="'top'"
|
[orientation]="'top'"
|
||||||
[flip]="false"
|
[flip]="false"
|
||||||
[disableSpinner]="true"
|
[disableSpinner]="true"
|
||||||
|
[relativeTime]="block?.timestamp"
|
||||||
(txClickEvent)="onTxClick($event)"
|
(txClickEvent)="onTxClick($event)"
|
||||||
></app-block-overview-graph>
|
></app-block-overview-graph>
|
||||||
</div>
|
</div>
|
||||||
|
@ -117,6 +117,7 @@
|
|||||||
[blockConversion]="blockConversion"
|
[blockConversion]="blockConversion"
|
||||||
[showFilters]="true"
|
[showFilters]="true"
|
||||||
[excludeFilters]="['replacement']"
|
[excludeFilters]="['replacement']"
|
||||||
|
[relativeTime]="block?.timestamp"
|
||||||
(txClickEvent)="onTxClick($event)"
|
(txClickEvent)="onTxClick($event)"
|
||||||
></app-block-overview-graph>
|
></app-block-overview-graph>
|
||||||
<ng-container *ngTemplateOutlet="emptyBlockInfo"></ng-container>
|
<ng-container *ngTemplateOutlet="emptyBlockInfo"></ng-container>
|
||||||
@ -232,7 +233,7 @@
|
|||||||
<app-block-overview-graph #blockGraphProjected [isLoading]="!stateService.isBrowser || isLoadingOverview" [resolution]="86"
|
<app-block-overview-graph #blockGraphProjected [isLoading]="!stateService.isBrowser || isLoadingOverview" [resolution]="86"
|
||||||
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" [auditHighlighting]="showAudit"
|
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" [auditHighlighting]="showAudit"
|
||||||
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="!isMobile && !showAudit"
|
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="!isMobile && !showAudit"
|
||||||
[showFilters]="true" [excludeFilters]="['replacement']"></app-block-overview-graph>
|
[showFilters]="true" [excludeFilters]="['replacement']" [relativeTime]="block?.timestamp"></app-block-overview-graph>
|
||||||
<ng-container *ngIf="!isMobile || mode !== 'actual'; else emptyBlockInfo"></ng-container>
|
<ng-container *ngIf="!isMobile || mode !== 'actual'; else emptyBlockInfo"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="network !== 'liquid'">
|
<ng-container *ngIf="network !== 'liquid'">
|
||||||
@ -247,7 +248,7 @@
|
|||||||
<app-block-overview-graph #blockGraphActual [isLoading]="!stateService.isBrowser || isLoadingOverview" [resolution]="86"
|
<app-block-overview-graph #blockGraphActual [isLoading]="!stateService.isBrowser || isLoadingOverview" [resolution]="86"
|
||||||
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined" [auditHighlighting]="showAudit"
|
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined" [auditHighlighting]="showAudit"
|
||||||
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="isMobile && !showAudit"
|
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="isMobile && !showAudit"
|
||||||
[showFilters]="true" [excludeFilters]="['replacement']"></app-block-overview-graph>
|
[showFilters]="true" [excludeFilters]="['replacement']" [relativeTime]="block?.timestamp"></app-block-overview-graph>
|
||||||
<ng-container *ngTemplateOutlet="emptyBlockInfo"></ng-container>
|
<ng-container *ngTemplateOutlet="emptyBlockInfo"></ng-container>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="network !== 'liquid'">
|
<ng-container *ngIf="network !== 'liquid'">
|
||||||
|
@ -371,6 +371,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
const inTemplate = {};
|
const inTemplate = {};
|
||||||
const inBlock = {};
|
const inBlock = {};
|
||||||
const isAdded = {};
|
const isAdded = {};
|
||||||
|
const isPrioritized = {};
|
||||||
const isCensored = {};
|
const isCensored = {};
|
||||||
const isMissing = {};
|
const isMissing = {};
|
||||||
const isSelected = {};
|
const isSelected = {};
|
||||||
@ -394,6 +395,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
for (const txid of blockAudit.addedTxs) {
|
for (const txid of blockAudit.addedTxs) {
|
||||||
isAdded[txid] = true;
|
isAdded[txid] = true;
|
||||||
}
|
}
|
||||||
|
for (const txid of blockAudit.prioritizedTxs) {
|
||||||
|
isPrioritized[txid] = true;
|
||||||
|
}
|
||||||
for (const txid of blockAudit.missingTxs) {
|
for (const txid of blockAudit.missingTxs) {
|
||||||
isCensored[txid] = true;
|
isCensored[txid] = true;
|
||||||
}
|
}
|
||||||
@ -443,6 +447,8 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
tx.status = null;
|
tx.status = null;
|
||||||
} else if (isAdded[tx.txid]) {
|
} else if (isAdded[tx.txid]) {
|
||||||
tx.status = 'added';
|
tx.status = 'added';
|
||||||
|
} else if (isPrioritized[tx.txid]) {
|
||||||
|
tx.status = 'prioritized';
|
||||||
} else if (inTemplate[tx.txid]) {
|
} else if (inTemplate[tx.txid]) {
|
||||||
tx.status = 'found';
|
tx.status = 'found';
|
||||||
} else if (isRbf[tx.txid]) {
|
} else if (isRbf[tx.txid]) {
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
[animationDuration]="animationDuration"
|
[animationDuration]="animationDuration"
|
||||||
[animationOffset]="animationOffset"
|
[animationOffset]="animationOffset"
|
||||||
[disableSpinner]="true"
|
[disableSpinner]="true"
|
||||||
|
[relativeTime]="blockInfo[i]?.timestamp"
|
||||||
(txClickEvent)="onTxClick($event)"
|
(txClickEvent)="onTxClick($event)"
|
||||||
></app-block-overview-graph>
|
></app-block-overview-graph>
|
||||||
<div *ngIf="showInfo && blockInfo[i]" class="info" @infoChange>
|
<div *ngIf="showInfo && blockInfo[i]" class="info" @infoChange>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { HostListener, OnChanges, OnDestroy } from '@angular/core';
|
import { HostListener, OnChanges, OnDestroy } from '@angular/core';
|
||||||
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||||
import { TransactionStripped } from '../../interfaces/websocket.interface';
|
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { VbytesPipe } from '../../shared/pipes/bytes-pipe/vbytes.pipe';
|
import { VbytesPipe } from '../../shared/pipes/bytes-pipe/vbytes.pipe';
|
||||||
import { selectPowerOfTen } from '../../bitcoin.utils';
|
import { selectPowerOfTen } from '../../bitcoin.utils';
|
||||||
|
@ -265,8 +265,8 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
|
|||||||
type: 'value',
|
type: 'value',
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
fontSize: 11,
|
fontSize: 11,
|
||||||
formatter: (value) => {
|
formatter: (value): string => {
|
||||||
return this.weightMode ? value * 4 : value;
|
return this.weightMode ? (value * 4).toString() : value.toString();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Component, ComponentRef, ViewChild, HostListener, Input, Output, EventEmitter,
|
import { Component, ComponentRef, ViewChild, HostListener, Input, Output, EventEmitter,
|
||||||
OnInit, OnDestroy, OnChanges, ChangeDetectionStrategy, ChangeDetectorRef, AfterViewInit } from '@angular/core';
|
OnInit, OnDestroy, OnChanges, ChangeDetectionStrategy, ChangeDetectorRef, AfterViewInit } from '@angular/core';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { MempoolBlockDelta, TransactionStripped } from '../../interfaces/websocket.interface';
|
import { MempoolBlockDelta } from '../../interfaces/websocket.interface';
|
||||||
|
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
|
import { BlockOverviewGraphComponent } from '../../components/block-overview-graph/block-overview-graph.component';
|
||||||
import { Subscription, BehaviorSubject, merge, of, timer } from 'rxjs';
|
import { Subscription, BehaviorSubject, merge, of, timer } from 'rxjs';
|
||||||
import { switchMap, filter, concatMap, map } from 'rxjs/operators';
|
import { switchMap, filter, concatMap, map } from 'rxjs/operators';
|
||||||
|
@ -3,7 +3,8 @@ import { detectWebGL } from '../../shared/graphs.utils';
|
|||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
import { switchMap, map, tap, filter } from 'rxjs/operators';
|
import { switchMap, map, tap, filter } from 'rxjs/operators';
|
||||||
import { MempoolBlock, TransactionStripped } from '../../interfaces/websocket.interface';
|
import { MempoolBlock } from '../../interfaces/websocket.interface';
|
||||||
|
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
import { Observable, BehaviorSubject } from 'rxjs';
|
import { Observable, BehaviorSubject } from 'rxjs';
|
||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||||
|
@ -20,10 +20,12 @@
|
|||||||
-
|
-
|
||||||
<app-fee-rate [fee]="projectedBlock.feeRange[projectedBlock.feeRange.length - 1]" rounding="1.0-0" unitClass=""></app-fee-rate>
|
<app-fee-rate [fee]="projectedBlock.feeRange[projectedBlock.feeRange.length - 1]" rounding="1.0-0" unitClass=""></app-fee-rate>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="showMiningInfo" class="block-size">
|
<div *ngIf="showMiningInfo$ | async; else noMiningInfo" class="block-size">
|
||||||
<app-amount [attr.data-cy]="'mempool-block-' + i + '-total-fees'" [satoshis]="projectedBlock.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
<app-amount [attr.data-cy]="'mempool-block-' + i + '-total-fees'" [satoshis]="projectedBlock.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="!showMiningInfo" class="block-size" [innerHTML]="'‎' + (projectedBlock.blockSize | bytes: 2)"></div>
|
<ng-template #noMiningInfo>
|
||||||
|
<div class="block-size" [innerHTML]="'‎' + (projectedBlock.blockSize | bytes: 2)"></div>
|
||||||
|
</ng-template>
|
||||||
<div [attr.data-cy]="'mempool-block-' + i + '-transaction-count'" class="transaction-count">
|
<div [attr.data-cy]="'mempool-block-' + i + '-transaction-count'" class="transaction-count">
|
||||||
<ng-container *ngTemplateOutlet="projectedBlock.nTx === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: projectedBlock.nTx | number}"></ng-container>
|
<ng-container *ngTemplateOutlet="projectedBlock.nTx === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: projectedBlock.nTx | number}"></ng-container>
|
||||||
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
|
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, Input, OnChanges, SimpleChanges, Output, EventEmitter } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, Input, OnChanges, SimpleChanges, Output, EventEmitter } from '@angular/core';
|
||||||
import { Subscription, Observable, of, combineLatest } from 'rxjs';
|
import { Subscription, Observable, of, combineLatest, BehaviorSubject } from 'rxjs';
|
||||||
import { MempoolBlock } from '../../interfaces/websocket.interface';
|
import { MempoolBlock } from '../../interfaces/websocket.interface';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
@ -42,6 +42,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
mempoolBlocks$: Observable<MempoolBlock[]>;
|
mempoolBlocks$: Observable<MempoolBlock[]>;
|
||||||
difficultyAdjustments$: Observable<DifficultyAdjustment>;
|
difficultyAdjustments$: Observable<DifficultyAdjustment>;
|
||||||
loadingBlocks$: Observable<boolean>;
|
loadingBlocks$: Observable<boolean>;
|
||||||
|
showMiningInfo$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||||
blocksSubscription: Subscription;
|
blocksSubscription: Subscription;
|
||||||
|
|
||||||
mempoolBlocksFull: MempoolBlock[] = [];
|
mempoolBlocksFull: MempoolBlock[] = [];
|
||||||
@ -57,10 +58,8 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
network = '';
|
network = '';
|
||||||
now = new Date().getTime();
|
now = new Date().getTime();
|
||||||
timeOffset = 0;
|
timeOffset = 0;
|
||||||
showMiningInfo = false;
|
|
||||||
timeLtrSubscription: Subscription;
|
timeLtrSubscription: Subscription;
|
||||||
timeLtr: boolean;
|
timeLtr: boolean;
|
||||||
showMiningInfoSubscription: Subscription;
|
|
||||||
animateEntry: boolean = false;
|
animateEntry: boolean = false;
|
||||||
|
|
||||||
blockOffset: number = 155;
|
blockOffset: number = 155;
|
||||||
@ -98,10 +97,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.widthChange.emit(this.mempoolWidth);
|
this.widthChange.emit(this.mempoolWidth);
|
||||||
|
|
||||||
if (['', 'testnet', 'signet'].includes(this.stateService.network)) {
|
if (['', 'testnet', 'signet'].includes(this.stateService.network)) {
|
||||||
this.showMiningInfoSubscription = this.stateService.showMiningInfo$.subscribe((showMiningInfo) => {
|
this.showMiningInfo$ = this.stateService.showMiningInfo$;
|
||||||
this.showMiningInfo = showMiningInfo;
|
|
||||||
this.cd.markForCheck();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
|
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
|
||||||
@ -267,7 +263,6 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.chainTipSubscription.unsubscribe();
|
this.chainTipSubscription.unsubscribe();
|
||||||
this.keySubscription.unsubscribe();
|
this.keySubscription.unsubscribe();
|
||||||
this.isTabHiddenSubscription.unsubscribe();
|
this.isTabHiddenSubscription.unsubscribe();
|
||||||
this.showMiningInfoSubscription.unsubscribe();
|
|
||||||
clearTimeout(this.resetTransitionTimeout);
|
clearTimeout(this.resetTransitionTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,7 +411,6 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
padding: [20, 0, 0, 0],
|
padding: [20, 0, 0, 0],
|
||||||
},
|
},
|
||||||
type: 'time',
|
type: 'time',
|
||||||
boundaryGap: false,
|
|
||||||
axisLine: { onZero: true },
|
axisLine: { onZero: true },
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
margin: 20,
|
margin: 20,
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<span class="menu-click text-nowrap ellipsis">
|
<span class="menu-click text-nowrap ellipsis">
|
||||||
<strong>
|
<strong>
|
||||||
<span *ngIf="user.username.includes('@'); else usernamenospace">{{ user.username }}</span>
|
<span *ngIf="user.username.includes('@'); else usernamenospace">{{ user.username }}</span>
|
||||||
<ng-template #usernamenospace>@{{ user.username }}</ng-template>
|
<ng-template #usernamenospace>@{{ user.username }}</ng-template>
|
||||||
</strong>
|
</strong>
|
||||||
</span>
|
</span>
|
||||||
<span class="badge mr-1 badge-og" *ngIf="user.ogRank">
|
<span class="badge mr-1 badge-og" *ngIf="user.ogRank">
|
||||||
|
@ -23,7 +23,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
@Input() time: number;
|
@Input() time: number;
|
||||||
@Input() dateString: number;
|
@Input() dateString: number;
|
||||||
@Input() kind: 'plain' | 'since' | 'until' | 'span' = 'plain';
|
@Input() kind: 'plain' | 'since' | 'until' | 'span' | 'before' = 'plain';
|
||||||
@Input() fastRender = false;
|
@Input() fastRender = false;
|
||||||
@Input() fixedRender = false;
|
@Input() fixedRender = false;
|
||||||
@Input() relative = false;
|
@Input() relative = false;
|
||||||
@ -86,7 +86,9 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
seconds = Math.floor(this.time);
|
seconds = Math.floor(this.time);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seconds < 60) {
|
if (seconds < 1 && this.kind === 'span') {
|
||||||
|
return $localize`:@@date-base.immediately:Immediately`;
|
||||||
|
} else if (seconds < 60) {
|
||||||
if (this.relative || this.kind === 'since') {
|
if (this.relative || this.kind === 'since') {
|
||||||
return $localize`:@@date-base.just-now:Just now`;
|
return $localize`:@@date-base.just-now:Just now`;
|
||||||
} else if (this.kind === 'until') {
|
} else if (this.kind === 'until') {
|
||||||
@ -206,6 +208,29 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'before':
|
||||||
|
if (number === 1) {
|
||||||
|
switch (unit) { // singular (1 day)
|
||||||
|
case 'year': return $localize`:@@time-span:${dateStrings.i18nYear}:DATE: before`; break;
|
||||||
|
case 'month': return $localize`:@@time-span:${dateStrings.i18nMonth}:DATE: before`; break;
|
||||||
|
case 'week': return $localize`:@@time-span:${dateStrings.i18nWeek}:DATE: before`; break;
|
||||||
|
case 'day': return $localize`:@@time-span:${dateStrings.i18nDay}:DATE: before`; break;
|
||||||
|
case 'hour': return $localize`:@@time-span:${dateStrings.i18nHour}:DATE: before`; break;
|
||||||
|
case 'minute': return $localize`:@@time-span:${dateStrings.i18nMinute}:DATE: before`; break;
|
||||||
|
case 'second': return $localize`:@@time-span:${dateStrings.i18nSecond}:DATE: before`; break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (unit) { // plural (2 days)
|
||||||
|
case 'year': return $localize`:@@time-span:${dateStrings.i18nYears}:DATE: before`; break;
|
||||||
|
case 'month': return $localize`:@@time-span:${dateStrings.i18nMonths}:DATE: before`; break;
|
||||||
|
case 'week': return $localize`:@@time-span:${dateStrings.i18nWeeks}:DATE: before`; break;
|
||||||
|
case 'day': return $localize`:@@time-span:${dateStrings.i18nDays}:DATE: before`; break;
|
||||||
|
case 'hour': return $localize`:@@time-span:${dateStrings.i18nHours}:DATE: before`; break;
|
||||||
|
case 'minute': return $localize`:@@time-span:${dateStrings.i18nMinutes}:DATE: before`; break;
|
||||||
|
case 'second': return $localize`:@@time-span:${dateStrings.i18nSeconds}:DATE: before`; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
if (number === 1) {
|
if (number === 1) {
|
||||||
switch (unit) { // singular (1 day)
|
switch (unit) { // singular (1 day)
|
||||||
|
@ -326,7 +326,7 @@
|
|||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<p>If you have any questions about this Policy, would like to speak with us about the use of our Marks in ways not described in the Policy, or see any abuse of our Marks, please email us at <legal@mempool.space></p>
|
<p>If you have any questions about this Policy, would like to speak with us about the use of our Marks in ways not described in the Policy, or see any abuse of our Marks, please email us at <legal@mempool.space></p>
|
||||||
|
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
@ -77,8 +77,9 @@
|
|||||||
<span *ngIf="auditStatus.coinbase; else expected" class="badge badge-primary mr-1" i18n="tx-features.tag.coinbase|Coinbase">Coinbase</span>
|
<span *ngIf="auditStatus.coinbase; else expected" class="badge badge-primary mr-1" i18n="tx-features.tag.coinbase|Coinbase">Coinbase</span>
|
||||||
<ng-template #expected><span *ngIf="auditStatus.expected; else seen" class="badge badge-success mr-1" i18n-ngbTooltip="Expected in block tooltip" ngbTooltip="This transaction was projected to be included in the block" placement="bottom" i18n="tx-features.tag.expected|Expected in Block">Expected in Block</span></ng-template>
|
<ng-template #expected><span *ngIf="auditStatus.expected; else seen" class="badge badge-success mr-1" i18n-ngbTooltip="Expected in block tooltip" ngbTooltip="This transaction was projected to be included in the block" placement="bottom" i18n="tx-features.tag.expected|Expected in Block">Expected in Block</span></ng-template>
|
||||||
<ng-template #seen><span *ngIf="auditStatus.seen; else notSeen" class="badge badge-success mr-1" i18n-ngbTooltip="Seen in mempool tooltip" ngbTooltip="This transaction was seen in the mempool prior to mining" placement="bottom" i18n="tx-features.tag.seen|Seen in Mempool">Seen in Mempool</span></ng-template>
|
<ng-template #seen><span *ngIf="auditStatus.seen; else notSeen" class="badge badge-success mr-1" i18n-ngbTooltip="Seen in mempool tooltip" ngbTooltip="This transaction was seen in the mempool prior to mining" placement="bottom" i18n="tx-features.tag.seen|Seen in Mempool">Seen in Mempool</span></ng-template>
|
||||||
<ng-template #notSeen><span class="badge badge-warning mr-1" i18n-ngbTooltip="Not seen in mempool tooltip" ngbTooltip="This transaction was missing from our mempool prior to mining" placement="bottom" i18n="tx-features.tag.not-seen|Not seen in Mempool">Not seen in Mempool</span></ng-template>
|
<ng-template #notSeen><span *ngIf="!auditStatus.conflict" class="badge badge-warning mr-1" i18n-ngbTooltip="Not seen in mempool tooltip" ngbTooltip="This transaction was missing from our mempool prior to mining" placement="bottom" i18n="tx-features.tag.not-seen|Not seen in Mempool">Not seen in Mempool</span></ng-template>
|
||||||
<span *ngIf="auditStatus.added" class="badge badge-warning mr-1" i18n-ngbTooltip="Added transaction tooltip" ngbTooltip="This transaction may have been added or prioritized out-of-band" placement="bottom" i18n="tx-features.tag.added|Added">Added</span>
|
<span *ngIf="auditStatus.added" class="badge badge-warning mr-1" i18n-ngbTooltip="Added transaction tooltip" ngbTooltip="This transaction may have been added out-of-band" placement="bottom" i18n="tx-features.tag.added|Added">Added</span>
|
||||||
|
<span *ngIf="auditStatus.prioritized" class="badge badge-warning mr-1" i18n-ngbTooltip="Prioritized transaction tooltip" ngbTooltip="This transaction may have been prioritized out-of-band" placement="bottom" i18n="tx-features.tag.prioritized|Prioritized">Prioritized</span>
|
||||||
<span *ngIf="auditStatus.conflict" class="badge badge-warning mr-1" i18n-ngbTooltip="Conflict in mempool tooltip" ngbTooltip="This transaction conflicted with another version in our mempool" placement="bottom" i18n="tx-features.tag.conflict|Conflict">Conflict</span>
|
<span *ngIf="auditStatus.conflict" class="badge badge-warning mr-1" i18n-ngbTooltip="Conflict in mempool tooltip" ngbTooltip="This transaction conflicted with another version in our mempool" placement="bottom" i18n="tx-features.tag.conflict|Conflict">Conflict</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</td>
|
</td>
|
||||||
|
@ -42,6 +42,7 @@ interface AuditStatus {
|
|||||||
seen?: boolean;
|
seen?: boolean;
|
||||||
expected?: boolean;
|
expected?: boolean;
|
||||||
added?: boolean;
|
added?: boolean;
|
||||||
|
prioritized?: boolean;
|
||||||
delayed?: number;
|
delayed?: number;
|
||||||
accelerated?: boolean;
|
accelerated?: boolean;
|
||||||
conflict?: boolean;
|
conflict?: boolean;
|
||||||
@ -317,13 +318,15 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
fetchAudit ? this.apiService.getBlockAudit$(hash).pipe(
|
fetchAudit ? this.apiService.getBlockAudit$(hash).pipe(
|
||||||
map(audit => {
|
map(audit => {
|
||||||
const isAdded = audit.addedTxs.includes(txid);
|
const isAdded = audit.addedTxs.includes(txid);
|
||||||
|
const isPrioritized = audit.prioritizedTxs.includes(txid);
|
||||||
const isAccelerated = audit.acceleratedTxs.includes(txid);
|
const isAccelerated = audit.acceleratedTxs.includes(txid);
|
||||||
const isConflict = audit.fullrbfTxs.includes(txid);
|
const isConflict = audit.fullrbfTxs.includes(txid);
|
||||||
const isExpected = audit.template.some(tx => tx.txid === txid);
|
const isExpected = audit.template.some(tx => tx.txid === txid);
|
||||||
return {
|
return {
|
||||||
seen: isExpected || !(isAdded || isConflict),
|
seen: isExpected || isPrioritized || isAccelerated,
|
||||||
expected: isExpected,
|
expected: isExpected,
|
||||||
added: isAdded,
|
added: isAdded,
|
||||||
|
prioritized: isPrioritized,
|
||||||
conflict: isConflict,
|
conflict: isConflict,
|
||||||
accelerated: isAccelerated,
|
accelerated: isAccelerated,
|
||||||
};
|
};
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
|
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
|
||||||
import { combineLatest, EMPTY, fromEvent, interval, merge, Observable, of, Subject, Subscription, timer } from 'rxjs';
|
import { combineLatest, EMPTY, fromEvent, interval, merge, Observable, of, Subject, Subscription, timer } from 'rxjs';
|
||||||
import { catchError, delayWhen, distinctUntilChanged, filter, map, scan, share, shareReplay, startWith, switchMap, takeUntil, tap, throttleTime } from 'rxjs/operators';
|
import { catchError, delayWhen, distinctUntilChanged, filter, map, scan, share, shareReplay, startWith, switchMap, takeUntil, tap, throttleTime } from 'rxjs/operators';
|
||||||
import { AuditStatus, BlockExtended, CurrentPegs, FederationAddress, FederationUtxo, OptimizedMempoolStats, PegsVolume, RecentPeg } from '../interfaces/node-api.interface';
|
import { AuditStatus, BlockExtended, CurrentPegs, FederationAddress, FederationUtxo, OptimizedMempoolStats, PegsVolume, RecentPeg, TransactionStripped } from '../interfaces/node-api.interface';
|
||||||
import { MempoolInfo, TransactionStripped, ReplacementInfo } from '../interfaces/websocket.interface';
|
import { MempoolInfo, ReplacementInfo } from '../interfaces/websocket.interface';
|
||||||
import { ApiService } from '../services/api.service';
|
import { ApiService } from '../services/api.service';
|
||||||
import { StateService } from '../services/state.service';
|
import { StateService } from '../services/state.service';
|
||||||
import { WebsocketService } from '../services/websocket.service';
|
import { WebsocketService } from '../services/websocket.service';
|
||||||
|
@ -208,6 +208,7 @@ export interface BlockExtended extends Block {
|
|||||||
export interface BlockAudit extends BlockExtended {
|
export interface BlockAudit extends BlockExtended {
|
||||||
missingTxs: string[],
|
missingTxs: string[],
|
||||||
addedTxs: string[],
|
addedTxs: string[],
|
||||||
|
prioritizedTxs: string[],
|
||||||
freshTxs: string[],
|
freshTxs: string[],
|
||||||
sigopTxs: string[],
|
sigopTxs: string[],
|
||||||
fullrbfTxs: string[],
|
fullrbfTxs: string[],
|
||||||
@ -230,7 +231,8 @@ export interface TransactionStripped {
|
|||||||
rate?: number; // effective fee rate
|
rate?: number; // effective fee rate
|
||||||
acc?: boolean;
|
acc?: boolean;
|
||||||
flags?: number | null;
|
flags?: number | null;
|
||||||
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated';
|
time?: number;
|
||||||
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'prioritized' | 'censored' | 'selected' | 'rbf' | 'accelerated';
|
||||||
context?: 'projected' | 'actual';
|
context?: 'projected' | 'actual';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { SafeResourceUrl } from '@angular/platform-browser';
|
import { SafeResourceUrl } from '@angular/platform-browser';
|
||||||
import { ILoadingIndicators } from '../services/state.service';
|
import { ILoadingIndicators } from '../services/state.service';
|
||||||
import { Transaction } from './electrs.interface';
|
import { Transaction } from './electrs.interface';
|
||||||
import { BlockExtended, DifficultyAdjustment, RbfTree } from './node-api.interface';
|
import { BlockExtended, DifficultyAdjustment, RbfTree, TransactionStripped } from './node-api.interface';
|
||||||
|
|
||||||
export interface WebsocketResponse {
|
export interface WebsocketResponse {
|
||||||
|
backend?: 'esplora' | 'electrum' | 'none';
|
||||||
block?: BlockExtended;
|
block?: BlockExtended;
|
||||||
blocks?: BlockExtended[];
|
blocks?: BlockExtended[];
|
||||||
conversions?: any;
|
conversions?: any;
|
||||||
@ -92,20 +93,8 @@ export interface MempoolInfo {
|
|||||||
minrelaytxfee: number; // (numeric) Current minimum relay fee for transactions
|
minrelaytxfee: number; // (numeric) Current minimum relay fee for transactions
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TransactionStripped {
|
|
||||||
txid: string;
|
|
||||||
fee: number;
|
|
||||||
vsize: number;
|
|
||||||
value: number;
|
|
||||||
acc?: boolean; // is accelerated?
|
|
||||||
rate?: number; // effective fee rate
|
|
||||||
flags?: number;
|
|
||||||
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'rbf' | 'accelerated';
|
|
||||||
context?: 'projected' | 'actual';
|
|
||||||
}
|
|
||||||
|
|
||||||
// [txid, fee, vsize, value, rate, flags, acceleration?]
|
// [txid, fee, vsize, value, rate, flags, acceleration?]
|
||||||
export type TransactionCompressed = [string, number, number, number, number, number, 1?];
|
export type TransactionCompressed = [string, number, number, number, number, number, number, 1?];
|
||||||
// [txid, rate, flags, acceleration?]
|
// [txid, rate, flags, acceleration?]
|
||||||
export type MempoolDeltaChange = [string, number, number, (1|0)];
|
export type MempoolDeltaChange = [string, number, number, (1|0)];
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
|
import { Inject, Injectable, PLATFORM_ID, makeStateKey, TransferState } from '@angular/core';
|
||||||
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { catchError, tap } from 'rxjs/operators';
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
import { TransferState, makeStateKey } from '@angular/platform-browser';
|
|
||||||
import { isPlatformBrowser } from '@angular/common';
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
||||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
|
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
|
||||||
import { Transaction } from '../interfaces/electrs.interface';
|
import { Transaction } from '../interfaces/electrs.interface';
|
||||||
import { HealthCheckHost, IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
import { HealthCheckHost, IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo } from '../interfaces/websocket.interface';
|
||||||
import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
|
import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree, TransactionStripped } from '../interfaces/node-api.interface';
|
||||||
import { Router, NavigationStart } from '@angular/router';
|
import { Router, NavigationStart } from '@angular/router';
|
||||||
import { isPlatformBrowser } from '@angular/common';
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
import { filter, map, scan, shareReplay } from 'rxjs/operators';
|
import { filter, map, scan, shareReplay } from 'rxjs/operators';
|
||||||
@ -92,6 +92,7 @@ const defaultEnv: Env = {
|
|||||||
export class StateService {
|
export class StateService {
|
||||||
isBrowser: boolean = isPlatformBrowser(this.platformId);
|
isBrowser: boolean = isPlatformBrowser(this.platformId);
|
||||||
isMempoolSpaceBuild = window['isMempoolSpaceBuild'] ?? false;
|
isMempoolSpaceBuild = window['isMempoolSpaceBuild'] ?? false;
|
||||||
|
backend: 'esplora' | 'electrum' | 'none' = 'esplora';
|
||||||
network = '';
|
network = '';
|
||||||
lightning = false;
|
lightning = false;
|
||||||
blockVSize: number;
|
blockVSize: number;
|
||||||
@ -99,6 +100,7 @@ export class StateService {
|
|||||||
latestBlockHeight = -1;
|
latestBlockHeight = -1;
|
||||||
blocks: BlockExtended[] = [];
|
blocks: BlockExtended[] = [];
|
||||||
|
|
||||||
|
backend$ = new BehaviorSubject<'esplora' | 'electrum' | 'none'>('esplora');
|
||||||
networkChanged$ = new ReplaySubject<string>(1);
|
networkChanged$ = new ReplaySubject<string>(1);
|
||||||
lightningChanged$ = new ReplaySubject<boolean>(1);
|
lightningChanged$ = new ReplaySubject<boolean>(1);
|
||||||
blocksSubject$ = new BehaviorSubject<BlockExtended[]>([]);
|
blocksSubject$ = new BehaviorSubject<BlockExtended[]>([]);
|
||||||
@ -257,6 +259,10 @@ export class StateService {
|
|||||||
|
|
||||||
const rateUnitPreference = this.storageService.getValue('rate-unit-preference');
|
const rateUnitPreference = this.storageService.getValue('rate-unit-preference');
|
||||||
this.rateUnits$ = new BehaviorSubject<string>(rateUnitPreference || 'vb');
|
this.rateUnits$ = new BehaviorSubject<string>(rateUnitPreference || 'vb');
|
||||||
|
|
||||||
|
this.backend$.subscribe(backend => {
|
||||||
|
this.backend = backend;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setNetworkBasedonUrl(url: string) {
|
setNetworkBasedonUrl(url: string) {
|
||||||
|
@ -62,6 +62,7 @@ export class WebsocketService {
|
|||||||
if (theInitData.body.blocks) {
|
if (theInitData.body.blocks) {
|
||||||
theInitData.body.blocks = theInitData.body.blocks.reverse();
|
theInitData.body.blocks = theInitData.body.blocks.reverse();
|
||||||
}
|
}
|
||||||
|
this.stateService.backend$.next(theInitData.backend);
|
||||||
this.stateService.isLoadingWebSocket$.next(false);
|
this.stateService.isLoadingWebSocket$.next(false);
|
||||||
this.handleResponse(theInitData.body);
|
this.handleResponse(theInitData.body);
|
||||||
this.startSubscription(false, true);
|
this.startSubscription(false, true);
|
||||||
@ -290,6 +291,10 @@ export class WebsocketService {
|
|||||||
handleResponse(response: WebsocketResponse) {
|
handleResponse(response: WebsocketResponse) {
|
||||||
let reinitBlocks = false;
|
let reinitBlocks = false;
|
||||||
|
|
||||||
|
if (response.backend) {
|
||||||
|
this.stateService.backend$.next(response.backend);
|
||||||
|
}
|
||||||
|
|
||||||
if (response.blocks && response.blocks.length) {
|
if (response.blocks && response.blocks.length) {
|
||||||
const blocks = response.blocks;
|
const blocks = response.blocks;
|
||||||
this.stateService.resetBlocks(blocks);
|
this.stateService.resetBlocks(blocks);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { MempoolBlockDelta, MempoolBlockDeltaCompressed, MempoolDeltaChange, TransactionCompressed, TransactionStripped } from "../interfaces/websocket.interface";
|
import { MempoolBlockDelta, MempoolBlockDeltaCompressed, MempoolDeltaChange, TransactionCompressed } from "../interfaces/websocket.interface";
|
||||||
|
import { TransactionStripped } from "../interfaces/node-api.interface";
|
||||||
|
|
||||||
export function isMobile(): boolean {
|
export function isMobile(): boolean {
|
||||||
return (window.innerWidth <= 767.98);
|
return (window.innerWidth <= 767.98);
|
||||||
@ -164,7 +165,8 @@ export function uncompressTx(tx: TransactionCompressed): TransactionStripped {
|
|||||||
value: tx[3],
|
value: tx[3],
|
||||||
rate: tx[4],
|
rate: tx[4],
|
||||||
flags: tx[5],
|
flags: tx[5],
|
||||||
acc: !!tx[6],
|
time: tx[6],
|
||||||
|
acc: !!tx[7],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,5 +7,5 @@ if (environment.production) {
|
|||||||
enableProdMode();
|
enableProdMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
export { AppServerModule } from './app/app.server.module';
|
export { AppServerModule } from './app/app.module.server';
|
||||||
export { renderModule } from '@angular/platform-server';
|
export { renderModule } from '@angular/platform-server';
|
||||||
|
@ -32,19 +32,19 @@ const githubSecret = process.env.GITHUB_TOKEN;
|
|||||||
const CONFIG_FILE_NAME = 'mempool-frontend-config.json';
|
const CONFIG_FILE_NAME = 'mempool-frontend-config.json';
|
||||||
let configContent = {};
|
let configContent = {};
|
||||||
|
|
||||||
var PATH;
|
var ASSETS_PATH;
|
||||||
if (process.argv[2]) {
|
if (process.argv[2]) {
|
||||||
PATH = process.argv[2];
|
ASSETS_PATH = process.argv[2];
|
||||||
PATH += PATH.endsWith("/") ? "" : "/"
|
ASSETS_PATH += ASSETS_PATH.endsWith("/") ? "" : "/"
|
||||||
PATH = path.resolve(path.normalize(PATH));
|
ASSETS_PATH = path.resolve(path.normalize(ASSETS_PATH));
|
||||||
console.log(`[sync-assets] using PATH ${PATH}`);
|
console.log(`[sync-assets] using ASSETS_PATH ${ASSETS_PATH}`);
|
||||||
if (!fs.existsSync(PATH)){
|
if (!fs.existsSync(ASSETS_PATH)){
|
||||||
console.log(`${LOG_TAG} ${PATH} does not exist, creating`);
|
console.log(`${LOG_TAG} ${ASSETS_PATH} does not exist, creating`);
|
||||||
fs.mkdirSync(PATH, { recursive: true });
|
fs.mkdirSync(ASSETS_PATH, { recursive: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PATH) {
|
if (!ASSETS_PATH) {
|
||||||
throw new Error('Resource path argument is not set');
|
throw new Error('Resource path argument is not set');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +125,8 @@ function downloadMiningPoolLogos$() {
|
|||||||
if (verbose) {
|
if (verbose) {
|
||||||
console.log(`${LOG_TAG} Processing ${poolLogo.name}`);
|
console.log(`${LOG_TAG} Processing ${poolLogo.name}`);
|
||||||
}
|
}
|
||||||
const filePath = `${PATH}/mining-pools/${poolLogo.name}`;
|
console.log(`${ASSETS_PATH}/mining-pools/${poolLogo.name}`);
|
||||||
|
const filePath = `${ASSETS_PATH}/mining-pools/${poolLogo.name}`;
|
||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
const localHash = getLocalHash(filePath);
|
const localHash = getLocalHash(filePath);
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
@ -152,7 +153,7 @@ function downloadMiningPoolLogos$() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(`${LOG_TAG} \t\t${poolLogo.name} is missing, downloading...`);
|
console.log(`${LOG_TAG} \t\t${poolLogo.name} is missing, downloading...`);
|
||||||
const miningPoolsDir = `${PATH}/mining-pools/`;
|
const miningPoolsDir = `${ASSETS_PATH}/mining-pools/`;
|
||||||
if (!fs.existsSync(miningPoolsDir)){
|
if (!fs.existsSync(miningPoolsDir)){
|
||||||
fs.mkdirSync(miningPoolsDir, { recursive: true });
|
fs.mkdirSync(miningPoolsDir, { recursive: true });
|
||||||
}
|
}
|
||||||
@ -219,7 +220,7 @@ function downloadPromoVideoSubtiles$() {
|
|||||||
if (verbose) {
|
if (verbose) {
|
||||||
console.log(`${LOG_TAG} Processing ${language.name}`);
|
console.log(`${LOG_TAG} Processing ${language.name}`);
|
||||||
}
|
}
|
||||||
const filePath = `${PATH}/promo-video/${language.name}`;
|
const filePath = `${ASSETS_PATH}/promo-video/${language.name}`;
|
||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
console.log(`${LOG_TAG} \t${language.name} remote promo video hash ${language.sha}`);
|
console.log(`${LOG_TAG} \t${language.name} remote promo video hash ${language.sha}`);
|
||||||
@ -245,7 +246,7 @@ function downloadPromoVideoSubtiles$() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(`${LOG_TAG} \t\t${language.name} is missing, downloading`);
|
console.log(`${LOG_TAG} \t\t${language.name} is missing, downloading`);
|
||||||
const promoVideosDir = `${PATH}/promo-video/`;
|
const promoVideosDir = `${ASSETS_PATH}/promo-video/`;
|
||||||
if (!fs.existsSync(promoVideosDir)){
|
if (!fs.existsSync(promoVideosDir)){
|
||||||
fs.mkdirSync(promoVideosDir, { recursive: true });
|
fs.mkdirSync(promoVideosDir, { recursive: true });
|
||||||
}
|
}
|
||||||
@ -313,7 +314,7 @@ function downloadPromoVideo$() {
|
|||||||
if (item.name !== 'promo.mp4') {
|
if (item.name !== 'promo.mp4') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const filePath = `${PATH}/promo-video/mempool-promo.mp4`;
|
const filePath = `${ASSETS_PATH}/promo-video/mempool-promo.mp4`;
|
||||||
if (fs.existsSync(filePath)) {
|
if (fs.existsSync(filePath)) {
|
||||||
const localHash = getLocalHash(filePath);
|
const localHash = getLocalHash(filePath);
|
||||||
|
|
||||||
@ -373,16 +374,16 @@ if (configContent.BASE_MODULE && configContent.BASE_MODULE === 'liquid') {
|
|||||||
const testnetAssetsMinimalJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.minimal.json';
|
const testnetAssetsMinimalJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.minimal.json';
|
||||||
|
|
||||||
console.log(`${LOG_TAG} Downloading assets`);
|
console.log(`${LOG_TAG} Downloading assets`);
|
||||||
download(`${PATH}/assets.json`, assetsJsonUrl);
|
download(`${ASSETS_PATH}/assets.json`, assetsJsonUrl);
|
||||||
|
|
||||||
console.log(`${LOG_TAG} Downloading assets minimal`);
|
console.log(`${LOG_TAG} Downloading assets minimal`);
|
||||||
download(`${PATH}/assets.minimal.json`, assetsMinimalJsonUrl);
|
download(`${ASSETS_PATH}/assets.minimal.json`, assetsMinimalJsonUrl);
|
||||||
|
|
||||||
console.log(`${LOG_TAG} Downloading testnet assets`);
|
console.log(`${LOG_TAG} Downloading testnet assets`);
|
||||||
download(`${PATH}/assets-testnet.json`, testnetAssetsJsonUrl);
|
download(`${ASSETS_PATH}/assets-testnet.json`, testnetAssetsJsonUrl);
|
||||||
|
|
||||||
console.log(`${LOG_TAG} Downloading testnet assets minimal`);
|
console.log(`${LOG_TAG} Downloading testnet assets minimal`);
|
||||||
download(`${PATH}/assets-testnet.minimal.json`, testnetAssetsMinimalJsonUrl);
|
download(`${ASSETS_PATH}/assets-testnet.minimal.json`, testnetAssetsMinimalJsonUrl);
|
||||||
} else {
|
} else {
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
console.log(`${LOG_TAG} BASE_MODULE is not set to Liquid (currently ${configContent.BASE_MODULE}), skipping downloading assets`);
|
console.log(`${LOG_TAG} BASE_MODULE is not set to Liquid (currently ${configContent.BASE_MODULE}), skipping downloading assets`);
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"declaration": false,
|
"declaration": false,
|
||||||
"downlevelIteration": true,
|
"downlevelIteration": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"module": "ES2020",
|
"module": "ES2022",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
@ -15,7 +15,7 @@
|
|||||||
"node_modules/@types"
|
"node_modules/@types"
|
||||||
],
|
],
|
||||||
"lib": [
|
"lib": [
|
||||||
"ES2018",
|
"ES2022",
|
||||||
"dom",
|
"dom",
|
||||||
"dom.iterable"
|
"dom.iterable"
|
||||||
]
|
]
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
#!/usr/bin/env zsh
|
#!/usr/bin/env zsh
|
||||||
#for j in fmt va1 fra tk7;do for i in 1 2 3 4 5 6;do echo -n 20$i.$j: ;curl -i -s https://node20$i.$j.mempool.space/api/v1/services/accelerator/accelerations|head -1;done;done
|
#for j in fmt va1 fra tk7;do for i in 1 2 3 4 5 6;do echo -n 20$i.$j: ;curl -i -s https://node20$i.$j.mempool.space/api/v1/services/accelerator/accelerations|head -1;done;done
|
||||||
check_mempoolspace_frontend_git_hash() {
|
check_mempoolspace_frontend_git_hash() {
|
||||||
echo curl -s --connect-to "::node${1}.${2}.mempool.space:443" https://mempool.space/resources/config.js
|
echo -n $(curl -s --connect-to "::node${1}.${2}.mempool.space:443" https://mempool.space/en-US/resources/config.js|grep GIT_COMMIT_HASH|cut -d "'" -f2|cut -c1-8)
|
||||||
echo -n $(curl -s --connect-to "::node${1}.${2}.mempool.space:443" https://mempool.space/resources/config.js|grep GIT_COMMIT_HASH|cut -d "'" -f2|cut -c1-8)
|
|
||||||
}
|
}
|
||||||
check_mempoolfoss_frontend_git_hash() {
|
check_mempoolfoss_frontend_git_hash() {
|
||||||
echo -n $(curl -s "https://node${1}.${2}.mempool.space/resources/config.js"|grep GIT_COMMIT_HASH|cut -d "'" -f2|cut -c1-8)
|
echo -n $(curl -s "https://node${1}.${2}.mempool.space/resources/config.js"|grep GIT_COMMIT_HASH|cut -d "'" -f2|cut -c1-8)
|
||||||
@ -13,19 +12,24 @@ check_mempoolspace_frontend_md5_hash() {
|
|||||||
check_mempoolfoss_frontend_md5_hash() {
|
check_mempoolfoss_frontend_md5_hash() {
|
||||||
echo -n $(curl -s https://node${1}.${2}.mempool.space|md5|cut -c1-8)
|
echo -n $(curl -s https://node${1}.${2}.mempool.space|md5|cut -c1-8)
|
||||||
}
|
}
|
||||||
|
check_mempool_electrs_git_hash() {
|
||||||
|
echo -n $(curl -s -i https://node${1}.${2}.mempool.space/api/mempool|grep -i x-powered-by|cut -d ' ' -f3)
|
||||||
|
}
|
||||||
for site in fmt va1 fra tk7;do
|
for site in fmt va1 fra tk7;do
|
||||||
echo "${site}"
|
echo "${site}"
|
||||||
for node in 201 202 203 204 205 206 207 208 209 210 211 212 213 214;do
|
for node in 201 202 203 204 205 206 207 208 209 210 211 212 213 214;do
|
||||||
[ "${site}" = "fmt" ] && [ "${node}" -gt 206 ] && continue
|
[ "${site}" = "fmt" ] && [ "${node}" -gt 206 ] && continue
|
||||||
[ "${site}" = "tk7" ] && [ "${node}" -gt 206 ] && continue
|
[ "${site}" = "tk7" ] && [ "${node}" -gt 206 ] && continue
|
||||||
echo -n "node${node}.${site}: "
|
echo -n "node${node}.${site}: "
|
||||||
#check_mempoolspace_frontend_git_hash $node $site
|
check_mempoolspace_frontend_git_hash $node $site
|
||||||
#echo -n " "
|
|
||||||
check_mempoolspace_frontend_md5_hash $node $site
|
|
||||||
echo -n " "
|
echo -n " "
|
||||||
check_mempoolfoss_frontend_git_hash $node $site
|
check_mempoolfoss_frontend_git_hash $node $site
|
||||||
echo -n " "
|
echo -n " "
|
||||||
|
check_mempoolspace_frontend_md5_hash $node $site
|
||||||
|
echo -n " "
|
||||||
check_mempoolfoss_frontend_md5_hash $node $site
|
check_mempoolfoss_frontend_md5_hash $node $site
|
||||||
|
echo -n " "
|
||||||
|
check_mempool_electrs_git_hash $node $site
|
||||||
echo
|
echo
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
Loading…
x
Reference in New Issue
Block a user