Compare commits
54 Commits
v2.5.0-dev
...
mononaut/v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe3059fbb0 | ||
|
|
a0d116c069 | ||
|
|
5fba2a2531 | ||
|
|
5349857692 | ||
|
|
819932bee9 | ||
|
|
fc6ebf3ccf | ||
|
|
c8decb9e46 | ||
|
|
563ce3cfb9 | ||
|
|
b3819dfb84 | ||
|
|
93f1a4e6d4 | ||
|
|
72c6ddef75 | ||
|
|
bb1ee90b0b | ||
|
|
2773bbfd00 | ||
|
|
d172233aff | ||
|
|
9c3e676391 | ||
|
|
4fed3a90a7 | ||
|
|
e7261d2613 | ||
|
|
80160fa37c | ||
|
|
e13b3ebfdc | ||
|
|
74cd15b8fd | ||
|
|
6715e20e0b | ||
|
|
57eddac7f0 | ||
|
|
af115b49aa | ||
|
|
332f9a2f5e | ||
|
|
34eef3553b | ||
|
|
9e4ce42b6a | ||
|
|
4c4a91ae95 | ||
|
|
93d46d5c5b | ||
|
|
8788d4f898 | ||
|
|
e28650c46c | ||
|
|
855c11f02c | ||
|
|
3f8e91bd46 | ||
|
|
6722e45109 | ||
|
|
414383638d | ||
|
|
2575b79c05 | ||
|
|
c7cab4c877 | ||
|
|
85c2f0ba30 | ||
|
|
edfbede704 | ||
|
|
5f60cb821a | ||
|
|
8486c1117d | ||
|
|
61f24562fd | ||
|
|
28de93d0ff | ||
|
|
1fd85b729d | ||
|
|
5681ae3f5c | ||
|
|
9d9e0976ae | ||
|
|
6180837636 | ||
|
|
17beaf7d4f | ||
|
|
ce8f471b27 | ||
|
|
b3e36fdd99 | ||
|
|
f971ddf1fa | ||
|
|
c0c37922c3 | ||
|
|
1eb9e58331 | ||
|
|
f8a35a110c | ||
|
|
c4d13fb5b7 |
18
backend/package-lock.json
generated
18
backend/package-lock.json
generated
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "mempool-backend",
|
||||
"version": "2.5.0-dev",
|
||||
"version": "2.5.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mempool-backend",
|
||||
"version": "2.5.0-dev",
|
||||
"version": "2.5.0",
|
||||
"license": "GNU Affero General Public License v3.0",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.12",
|
||||
"@mempool/electrum-client": "^1.1.7",
|
||||
"@mempool/electrum-client": "1.1.9",
|
||||
"@types/node": "^16.18.11",
|
||||
"axios": "~0.27.2",
|
||||
"bitcoinjs-lib": "~6.1.0",
|
||||
@@ -1272,9 +1272,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mempool/electrum-client": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@mempool/electrum-client/-/electrum-client-1.1.8.tgz",
|
||||
"integrity": "sha512-6YP6UJstlk2GgC++NwPJthMPvLozyEMlqPq7RjvIWSwrL0smvM0Q0PAOohwZJtJFDWspuEUtNRF7aHQT2ztnYg==",
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@mempool/electrum-client/-/electrum-client-1.1.9.tgz",
|
||||
"integrity": "sha512-mlvPiCzUlaETpYW3i6V87A24jjMYgsebaXtUo3WQyyLnYUuxs0KiXQ2mnKh3h15j8Xg/hfxeGIi+5OC9u0nftQ==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
@@ -7248,9 +7248,9 @@
|
||||
}
|
||||
},
|
||||
"@mempool/electrum-client": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@mempool/electrum-client/-/electrum-client-1.1.8.tgz",
|
||||
"integrity": "sha512-6YP6UJstlk2GgC++NwPJthMPvLozyEMlqPq7RjvIWSwrL0smvM0Q0PAOohwZJtJFDWspuEUtNRF7aHQT2ztnYg=="
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@mempool/electrum-client/-/electrum-client-1.1.9.tgz",
|
||||
"integrity": "sha512-mlvPiCzUlaETpYW3i6V87A24jjMYgsebaXtUo3WQyyLnYUuxs0KiXQ2mnKh3h15j8Xg/hfxeGIi+5OC9u0nftQ=="
|
||||
},
|
||||
"@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mempool-backend",
|
||||
"version": "2.5.0-dev",
|
||||
"version": "2.5.0",
|
||||
"description": "Bitcoin mempool visualizer and blockchain explorer backend",
|
||||
"license": "GNU Affero General Public License v3.0",
|
||||
"homepage": "https://mempool.space",
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.12",
|
||||
"@mempool/electrum-client": "^1.1.7",
|
||||
"@mempool/electrum-client": "1.1.9",
|
||||
"@types/node": "^16.18.11",
|
||||
"axios": "~0.27.2",
|
||||
"bitcoinjs-lib": "~6.1.0",
|
||||
|
||||
@@ -14,11 +14,11 @@ describe('Mempool Difficulty Adjustment', () => {
|
||||
750134, // Current block height
|
||||
0.6280047707459726, // Previous retarget % (Passed through)
|
||||
'mainnet', // Network (if testnet, next value is non-zero)
|
||||
0, // If not testnet, not used
|
||||
0, // Latest block timestamp in seconds (only used if difficulty already locked in)
|
||||
],
|
||||
{ // Expected Result
|
||||
progressPercent: 9.027777777777777,
|
||||
difficultyChange: 12.562233927411782,
|
||||
difficultyChange: 13.180707740199772,
|
||||
estimatedRetargetDate: 1661895424692,
|
||||
remainingBlocks: 1834,
|
||||
remainingTime: 977591692,
|
||||
@@ -41,7 +41,7 @@ describe('Mempool Difficulty Adjustment', () => {
|
||||
],
|
||||
{ // Expected Result is same other than timeOffset
|
||||
progressPercent: 9.027777777777777,
|
||||
difficultyChange: 12.562233927411782,
|
||||
difficultyChange: 13.180707740199772,
|
||||
estimatedRetargetDate: 1661895424692,
|
||||
remainingBlocks: 1834,
|
||||
remainingTime: 977591692,
|
||||
@@ -54,6 +54,29 @@ describe('Mempool Difficulty Adjustment', () => {
|
||||
expectedBlocks: 161.68833333333333,
|
||||
},
|
||||
],
|
||||
[ // Vector 3 (mainnet lock-in (epoch ending 788255))
|
||||
[ // Inputs
|
||||
dt('2023-04-20T09:57:33.000Z'), // Last DA time (in seconds)
|
||||
dt('2023-05-04T14:54:09.000Z'), // Current time (now) (in seconds)
|
||||
788255, // Current block height
|
||||
1.7220298879531821, // Previous retarget % (Passed through)
|
||||
'mainnet', // Network (if testnet, next value is non-zero)
|
||||
dt('2023-05-04T14:54:26.000Z'), // Latest block timestamp in seconds
|
||||
],
|
||||
{ // Expected Result
|
||||
progressPercent: 99.95039682539682,
|
||||
difficultyChange: -1.4512637555574193,
|
||||
estimatedRetargetDate: 1683212658129,
|
||||
remainingBlocks: 1,
|
||||
remainingTime: 609129,
|
||||
previousRetarget: 1.7220298879531821,
|
||||
previousTime: 1681984653,
|
||||
nextRetargetHeight: 788256,
|
||||
timeAvg: 609129,
|
||||
timeOffset: 0,
|
||||
expectedBlocks: 2045.66,
|
||||
},
|
||||
],
|
||||
] as [[number, number, number, number, string, number], DifficultyAdjustment][];
|
||||
|
||||
for (const vector of vectors) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import config from '../config';
|
||||
import logger from '../logger';
|
||||
import { TransactionExtended, MempoolBlockWithTransactions } from '../mempool.interfaces';
|
||||
|
||||
const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first seen after which it is assumed to have propagated to all miners
|
||||
@@ -39,17 +40,19 @@ class Audit {
|
||||
} else {
|
||||
isCensored[txid] = true;
|
||||
}
|
||||
displacedWeight += mempool[txid].weight;
|
||||
displacedWeight += mempool[txid]?.weight || 0;
|
||||
} else {
|
||||
matchedWeight += mempool[txid].weight;
|
||||
matchedWeight += mempool[txid]?.weight || 0;
|
||||
}
|
||||
projectedWeight += mempool[txid].weight;
|
||||
projectedWeight += mempool[txid]?.weight || 0;
|
||||
inTemplate[txid] = true;
|
||||
}
|
||||
|
||||
displacedWeight += (4000 - transactions[0].weight);
|
||||
projectedWeight += transactions[0].weight;
|
||||
matchedWeight += transactions[0].weight;
|
||||
if (transactions[0]) {
|
||||
displacedWeight += (4000 - transactions[0].weight);
|
||||
projectedWeight += transactions[0].weight;
|
||||
matchedWeight += transactions[0].weight;
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -59,19 +62,24 @@ class Audit {
|
||||
let failures = 0;
|
||||
while (projectedBlocks[1] && index < projectedBlocks[1].transactionIds.length && failures < 500) {
|
||||
const txid = projectedBlocks[1].transactionIds[index];
|
||||
const fits = (mempool[txid].weight - displacedWeightRemaining) < 4000;
|
||||
const feeMatches = mempool[txid].effectiveFeePerVsize >= lastFeeRate;
|
||||
if (fits || feeMatches) {
|
||||
isDisplaced[txid] = true;
|
||||
if (fits) {
|
||||
lastFeeRate = Math.min(lastFeeRate, mempool[txid].effectiveFeePerVsize);
|
||||
const tx = mempool[txid];
|
||||
if (tx) {
|
||||
const fits = (tx.weight - displacedWeightRemaining) < 4000;
|
||||
const feeMatches = tx.effectiveFeePerVsize >= lastFeeRate;
|
||||
if (fits || feeMatches) {
|
||||
isDisplaced[txid] = true;
|
||||
if (fits) {
|
||||
lastFeeRate = Math.min(lastFeeRate, tx.effectiveFeePerVsize);
|
||||
}
|
||||
if (tx.firstSeen == null || (now - (tx?.firstSeen || 0)) > PROPAGATION_MARGIN) {
|
||||
displacedWeightRemaining -= tx.weight;
|
||||
}
|
||||
failures = 0;
|
||||
} else {
|
||||
failures++;
|
||||
}
|
||||
if (mempool[txid].firstSeen == null || (now - (mempool[txid]?.firstSeen || 0)) > PROPAGATION_MARGIN) {
|
||||
displacedWeightRemaining -= mempool[txid].weight;
|
||||
}
|
||||
failures = 0;
|
||||
} else {
|
||||
failures++;
|
||||
logger.warn('projected transaction missing from mempool cache');
|
||||
}
|
||||
index++;
|
||||
}
|
||||
@@ -85,17 +93,7 @@ class Audit {
|
||||
} else {
|
||||
if (!isDisplaced[tx.txid]) {
|
||||
added.push(tx.txid);
|
||||
} else {
|
||||
}
|
||||
let blockIndex = -1;
|
||||
let index = -1;
|
||||
projectedBlocks.forEach((block, bi) => {
|
||||
const i = block.transactionIds.indexOf(tx.txid);
|
||||
if (i >= 0) {
|
||||
blockIndex = bi;
|
||||
index = i;
|
||||
}
|
||||
});
|
||||
overflowWeight += tx.weight;
|
||||
}
|
||||
totalWeight += tx.weight;
|
||||
@@ -108,20 +106,25 @@ class Audit {
|
||||
index = projectedBlocks[0].transactionIds.length - 1;
|
||||
while (index >= 0) {
|
||||
const txid = projectedBlocks[0].transactionIds[index];
|
||||
if (overflowWeightRemaining > 0) {
|
||||
if (isCensored[txid]) {
|
||||
delete isCensored[txid];
|
||||
}
|
||||
if (mempool[txid].effectiveFeePerVsize > maxOverflowRate) {
|
||||
maxOverflowRate = mempool[txid].effectiveFeePerVsize;
|
||||
rateThreshold = (Math.ceil(maxOverflowRate * 100) / 100) + 0.005;
|
||||
}
|
||||
} else if (mempool[txid].effectiveFeePerVsize <= rateThreshold) { // tolerance of 0.01 sat/vb + rounding
|
||||
if (isCensored[txid]) {
|
||||
delete isCensored[txid];
|
||||
const tx = mempool[txid];
|
||||
if (tx) {
|
||||
if (overflowWeightRemaining > 0) {
|
||||
if (isCensored[txid]) {
|
||||
delete isCensored[txid];
|
||||
}
|
||||
if (tx.effectiveFeePerVsize > maxOverflowRate) {
|
||||
maxOverflowRate = tx.effectiveFeePerVsize;
|
||||
rateThreshold = (Math.ceil(maxOverflowRate * 100) / 100) + 0.005;
|
||||
}
|
||||
} else if (tx.effectiveFeePerVsize <= rateThreshold) { // tolerance of 0.01 sat/vb + rounding
|
||||
if (isCensored[txid]) {
|
||||
delete isCensored[txid];
|
||||
}
|
||||
}
|
||||
overflowWeightRemaining -= (mempool[txid]?.weight || 0);
|
||||
} else {
|
||||
logger.warn('projected transaction missing from mempool cache');
|
||||
}
|
||||
overflowWeightRemaining -= (mempool[txid]?.weight || 0);
|
||||
index--;
|
||||
}
|
||||
|
||||
|
||||
@@ -520,6 +520,8 @@ class Blocks {
|
||||
}
|
||||
|
||||
public async $updateBlocks() {
|
||||
diskCache.lock();
|
||||
|
||||
let fastForwarded = false;
|
||||
const blockHeightTip = await bitcoinApi.$getBlockHeightTip();
|
||||
|
||||
@@ -558,7 +560,7 @@ class Blocks {
|
||||
}
|
||||
|
||||
while (this.currentBlockHeight < blockHeightTip) {
|
||||
if (this.currentBlockHeight < blockHeightTip - config.MEMPOOL.INITIAL_BLOCKS_AMOUNT) {
|
||||
if (this.currentBlockHeight === 0) {
|
||||
this.currentBlockHeight = blockHeightTip;
|
||||
} else {
|
||||
this.currentBlockHeight++;
|
||||
@@ -581,11 +583,10 @@ class Blocks {
|
||||
if (!fastForwarded) {
|
||||
const lastBlock = await blocksRepository.$getBlockByHeight(blockExtended.height - 1);
|
||||
if (lastBlock !== null && blockExtended.previousblockhash !== lastBlock.id) {
|
||||
logger.warn(`Chain divergence detected at block ${lastBlock.height}, re-indexing most recent data`);
|
||||
logger.warn(`Chain divergence detected at block ${lastBlock.height}, re-indexing most recent data`, logger.tags.mining);
|
||||
// We assume there won't be a reorg with more than 10 block depth
|
||||
await BlocksRepository.$deleteBlocksFrom(lastBlock.height - 10);
|
||||
await HashratesRepository.$deleteLastEntries();
|
||||
await BlocksSummariesRepository.$deleteBlocksFrom(lastBlock.height - 10);
|
||||
await cpfpRepository.$deleteClustersFrom(lastBlock.height - 10);
|
||||
for (let i = 10; i >= 0; --i) {
|
||||
const newBlock = await this.$indexBlock(lastBlock.height - i);
|
||||
@@ -596,7 +597,7 @@ class Blocks {
|
||||
}
|
||||
await mining.$indexDifficultyAdjustments();
|
||||
await DifficultyAdjustmentsRepository.$deleteLastAdjustment();
|
||||
logger.info(`Re-indexed 10 blocks and summaries. Also re-indexed the last difficulty adjustments. Will re-index latest hashrates in a few seconds.`);
|
||||
logger.info(`Re-indexed 10 blocks and summaries. Also re-indexed the last difficulty adjustments. Will re-index latest hashrates in a few seconds.`, logger.tags.mining);
|
||||
indexer.reindex();
|
||||
}
|
||||
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
||||
@@ -658,6 +659,8 @@ class Blocks {
|
||||
// wait for pending async callbacks to finish
|
||||
await Promise.all(callbackPromises);
|
||||
}
|
||||
|
||||
diskCache.unlock();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -728,7 +731,7 @@ class Blocks {
|
||||
|
||||
// Index the response if needed
|
||||
if (Common.blocksSummariesIndexingEnabled() === true) {
|
||||
await BlocksSummariesRepository.$saveSummary({height: block.height, mined: summary});
|
||||
await BlocksSummariesRepository.$saveTransactions(block.height, block.hash, summary.transactions);
|
||||
}
|
||||
|
||||
return summary.transactions;
|
||||
@@ -844,7 +847,7 @@ class Blocks {
|
||||
if (cleanBlock.fee_amt_percentiles === null) {
|
||||
const block = await bitcoinClient.getBlock(cleanBlock.hash, 2);
|
||||
const summary = this.summarizeBlock(block);
|
||||
await BlocksSummariesRepository.$saveSummary({ height: block.height, mined: summary });
|
||||
await BlocksSummariesRepository.$saveTransactions(cleanBlock.height, cleanBlock.hash, summary.transactions);
|
||||
cleanBlock.fee_amt_percentiles = await BlocksSummariesRepository.$getFeePercentilesByBlockId(cleanBlock.hash);
|
||||
}
|
||||
if (cleanBlock.fee_amt_percentiles !== null) {
|
||||
|
||||
@@ -497,6 +497,7 @@ class DatabaseMigration {
|
||||
this.uniqueLog(logger.notice, this.blocksTruncatedMessage);
|
||||
await this.$executeQuery('DELETE FROM `pools`');
|
||||
await this.$executeQuery('ALTER TABLE pools AUTO_INCREMENT = 1');
|
||||
await this.$executeQuery(`UPDATE state SET string = NULL WHERE name = 'pools_json_sha'`);
|
||||
this.uniqueLog(logger.notice, '`pools` table has been truncated`');
|
||||
await this.updateToSchemaVersion(56);
|
||||
}
|
||||
|
||||
@@ -24,31 +24,29 @@ export function calcDifficultyAdjustment(
|
||||
network: string,
|
||||
latestBlockTimestamp: number,
|
||||
): DifficultyAdjustment {
|
||||
const ESTIMATE_LAG_BLOCKS = 146; // For first 7.2% of epoch, don't estimate.
|
||||
const EPOCH_BLOCK_LENGTH = 2016; // Bitcoin mainnet
|
||||
const BLOCK_SECONDS_TARGET = 600; // Bitcoin mainnet
|
||||
const TESTNET_MAX_BLOCK_SECONDS = 1200; // Bitcoin testnet
|
||||
|
||||
const diffSeconds = nowSeconds - DATime;
|
||||
const diffSeconds = Math.max(0, nowSeconds - DATime);
|
||||
const blocksInEpoch = (blockHeight >= 0) ? blockHeight % EPOCH_BLOCK_LENGTH : 0;
|
||||
const progressPercent = (blockHeight >= 0) ? blocksInEpoch / EPOCH_BLOCK_LENGTH * 100 : 100;
|
||||
const remainingBlocks = EPOCH_BLOCK_LENGTH - blocksInEpoch;
|
||||
const nextRetargetHeight = (blockHeight >= 0) ? blockHeight + remainingBlocks : 0;
|
||||
const expectedBlocks = diffSeconds / BLOCK_SECONDS_TARGET;
|
||||
const actualTimespan = (blocksInEpoch === 2015 ? latestBlockTimestamp : nowSeconds) - DATime;
|
||||
|
||||
let difficultyChange = 0;
|
||||
let timeAvgSecs = diffSeconds / blocksInEpoch;
|
||||
// Only calculate the estimate once we have 7.2% of blocks in current epoch
|
||||
if (blocksInEpoch >= ESTIMATE_LAG_BLOCKS) {
|
||||
difficultyChange = (BLOCK_SECONDS_TARGET / timeAvgSecs - 1) * 100;
|
||||
// Max increase is x4 (+300%)
|
||||
if (difficultyChange > 300) {
|
||||
difficultyChange = 300;
|
||||
}
|
||||
// Max decrease is /4 (-75%)
|
||||
if (difficultyChange < -75) {
|
||||
difficultyChange = -75;
|
||||
}
|
||||
let timeAvgSecs = blocksInEpoch ? diffSeconds / blocksInEpoch : BLOCK_SECONDS_TARGET;
|
||||
|
||||
difficultyChange = (BLOCK_SECONDS_TARGET / (actualTimespan / (blocksInEpoch + 1)) - 1) * 100;
|
||||
// Max increase is x4 (+300%)
|
||||
if (difficultyChange > 300) {
|
||||
difficultyChange = 300;
|
||||
}
|
||||
// Max decrease is /4 (-75%)
|
||||
if (difficultyChange < -75) {
|
||||
difficultyChange = -75;
|
||||
}
|
||||
|
||||
// Testnet difficulty is set to 1 after 20 minutes of no blocks,
|
||||
|
||||
@@ -18,6 +18,11 @@ class DiskCache {
|
||||
private static CHUNK_FILES = 25;
|
||||
private isWritingCache = false;
|
||||
|
||||
private semaphore: { resume: (() => void)[], locks: number } = {
|
||||
resume: [],
|
||||
locks: 0,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
if (!cluster.isPrimary) {
|
||||
return;
|
||||
@@ -43,7 +48,9 @@ class DiskCache {
|
||||
const mempool = memPool.getMempool();
|
||||
const mempoolArray: TransactionExtended[] = [];
|
||||
for (const tx in mempool) {
|
||||
mempoolArray.push(mempool[tx]);
|
||||
if (mempool[tx] && !mempool[tx].deleteAfter) {
|
||||
mempoolArray.push(mempool[tx]);
|
||||
}
|
||||
}
|
||||
|
||||
Common.shuffleArray(mempoolArray);
|
||||
@@ -71,6 +78,7 @@ class DiskCache {
|
||||
fs.renameSync(DiskCache.TMP_FILE_NAMES.replace('{number}', i.toString()), DiskCache.FILE_NAMES.replace('{number}', i.toString()));
|
||||
}
|
||||
} else {
|
||||
await this.$yield();
|
||||
await fsPromises.writeFile(DiskCache.TMP_FILE_NAME, JSON.stringify({
|
||||
network: config.MEMPOOL.NETWORK,
|
||||
cacheSchemaVersion: this.cacheSchemaVersion,
|
||||
@@ -80,6 +88,7 @@ class DiskCache {
|
||||
mempoolArray: mempoolArray.splice(0, chunkSize),
|
||||
}), { flag: 'w' });
|
||||
for (let i = 1; i < DiskCache.CHUNK_FILES; i++) {
|
||||
await this.$yield();
|
||||
await fsPromises.writeFile(DiskCache.TMP_FILE_NAMES.replace('{number}', i.toString()), JSON.stringify({
|
||||
mempool: {},
|
||||
mempoolArray: mempoolArray.splice(0, chunkSize),
|
||||
@@ -122,7 +131,7 @@ class DiskCache {
|
||||
}
|
||||
}
|
||||
|
||||
loadMempoolCache(): void {
|
||||
async $loadMempoolCache(): Promise<void> {
|
||||
if (!fs.existsSync(DiskCache.FILE_NAME)) {
|
||||
return;
|
||||
}
|
||||
@@ -166,13 +175,39 @@ class DiskCache {
|
||||
}
|
||||
}
|
||||
|
||||
memPool.setMempool(data.mempool);
|
||||
await memPool.$setMempool(data.mempool);
|
||||
blocks.setBlocks(data.blocks);
|
||||
blocks.setBlockSummaries(data.blockSummaries || []);
|
||||
} catch (e) {
|
||||
logger.warn('Failed to parse mempoool and blocks cache. Skipping. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
private $yield(): Promise<void> {
|
||||
if (this.semaphore.locks) {
|
||||
logger.debug('Pause writing mempool and blocks data to disk cache (async)');
|
||||
return new Promise((resolve) => {
|
||||
this.semaphore.resume.push(resolve);
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
public lock(): void {
|
||||
this.semaphore.locks++;
|
||||
}
|
||||
|
||||
public unlock(): void {
|
||||
this.semaphore.locks = Math.max(0, this.semaphore.locks - 1);
|
||||
if (!this.semaphore.locks && this.semaphore.resume.length) {
|
||||
const nextResume = this.semaphore.resume.shift();
|
||||
if (nextResume) {
|
||||
logger.debug('Resume writing mempool and blocks data to disk cache (async)');
|
||||
nextResume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new DiskCache();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logger from '../logger';
|
||||
import { MempoolBlock, TransactionExtended, ThreadTransaction, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor } from '../mempool.interfaces';
|
||||
import { MempoolBlock, TransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction } from '../mempool.interfaces';
|
||||
import { Common } from './common';
|
||||
import config from '../config';
|
||||
import { Worker } from 'worker_threads';
|
||||
@@ -10,6 +10,9 @@ class MempoolBlocks {
|
||||
private mempoolBlockDeltas: MempoolBlockDelta[] = [];
|
||||
private txSelectionWorker: Worker | null = null;
|
||||
|
||||
private nextUid: number = 1;
|
||||
private uidMap: Map<number, string> = new Map(); // map short numerical uids to full txids
|
||||
|
||||
constructor() {}
|
||||
|
||||
public getMempoolBlocks(): MempoolBlock[] {
|
||||
@@ -87,19 +90,34 @@ class MempoolBlocks {
|
||||
|
||||
private calculateMempoolBlocks(transactionsSorted: TransactionExtended[], prevBlocks: MempoolBlockWithTransactions[]): MempoolBlockWithTransactions[] {
|
||||
const mempoolBlocks: MempoolBlockWithTransactions[] = [];
|
||||
let blockWeight = 0;
|
||||
let blockSize = 0;
|
||||
let blockWeight = 0;
|
||||
let blockVsize = 0;
|
||||
let blockFees = 0;
|
||||
const sizeLimit = (config.MEMPOOL.BLOCK_WEIGHT_UNITS / 4) * 1.2;
|
||||
let transactionIds: string[] = [];
|
||||
let transactions: TransactionExtended[] = [];
|
||||
transactionsSorted.forEach((tx) => {
|
||||
if (blockWeight + tx.weight <= config.MEMPOOL.BLOCK_WEIGHT_UNITS
|
||||
|| mempoolBlocks.length === config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT - 1) {
|
||||
blockWeight += tx.weight;
|
||||
blockVsize += tx.vsize;
|
||||
blockSize += tx.size;
|
||||
blockFees += tx.fee;
|
||||
transactions.push(tx);
|
||||
transactionIds.push(tx.txid);
|
||||
} else {
|
||||
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, mempoolBlocks.length));
|
||||
blockVsize = 0;
|
||||
tx.position = {
|
||||
block: mempoolBlocks.length,
|
||||
vsize: blockVsize + (tx.vsize / 2),
|
||||
};
|
||||
blockVsize += tx.vsize;
|
||||
blockWeight = tx.weight;
|
||||
blockSize = tx.size;
|
||||
blockFees = tx.fee;
|
||||
transactionIds = [tx.txid];
|
||||
transactions = [tx];
|
||||
}
|
||||
});
|
||||
@@ -147,19 +165,29 @@ class MempoolBlocks {
|
||||
return mempoolBlockDeltas;
|
||||
}
|
||||
|
||||
public async makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, saveResults: boolean = false): Promise<MempoolBlockWithTransactions[]> {
|
||||
public async $makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, saveResults: boolean = false): Promise<MempoolBlockWithTransactions[]> {
|
||||
const start = Date.now();
|
||||
|
||||
// reset mempool short ids
|
||||
this.resetUids();
|
||||
for (const tx of Object.values(newMempool)) {
|
||||
this.setUid(tx);
|
||||
}
|
||||
|
||||
// prepare a stripped down version of the mempool with only the minimum necessary data
|
||||
// to reduce the overhead of passing this data to the worker thread
|
||||
const strippedMempool: { [txid: string]: ThreadTransaction } = {};
|
||||
const strippedMempool: Map<number, CompactThreadTransaction> = new Map();
|
||||
Object.values(newMempool).forEach(entry => {
|
||||
strippedMempool[entry.txid] = {
|
||||
txid: entry.txid,
|
||||
fee: entry.fee,
|
||||
weight: entry.weight,
|
||||
feePerVsize: entry.fee / (entry.weight / 4),
|
||||
effectiveFeePerVsize: entry.fee / (entry.weight / 4),
|
||||
vin: entry.vin.map(v => v.txid),
|
||||
};
|
||||
if (entry.uid != null) {
|
||||
strippedMempool.set(entry.uid, {
|
||||
uid: entry.uid,
|
||||
fee: entry.fee,
|
||||
weight: entry.weight,
|
||||
feePerVsize: entry.fee / (entry.weight / 4),
|
||||
effectiveFeePerVsize: entry.effectiveFeePerVsize || (entry.fee / (entry.weight / 4)),
|
||||
inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => uid != null) as number[],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// (re)initialize tx selection worker thread
|
||||
@@ -178,7 +206,7 @@ class MempoolBlocks {
|
||||
// run the block construction algorithm in a separate thread, and wait for a result
|
||||
let threadErrorListener;
|
||||
try {
|
||||
const workerResultPromise = new Promise<{ blocks: ThreadTransaction[][], clusters: { [root: string]: string[] } }>((resolve, reject) => {
|
||||
const workerResultPromise = new Promise<{ blocks: number[][], rates: Map<number, number>, clusters: Map<number, number[]> }>((resolve, reject) => {
|
||||
threadErrorListener = reject;
|
||||
this.txSelectionWorker?.once('message', (result): void => {
|
||||
resolve(result);
|
||||
@@ -186,103 +214,149 @@ class MempoolBlocks {
|
||||
this.txSelectionWorker?.once('error', reject);
|
||||
});
|
||||
this.txSelectionWorker.postMessage({ type: 'set', mempool: strippedMempool });
|
||||
const { blocks, clusters } = await workerResultPromise;
|
||||
const { blocks, rates, clusters } = this.convertResultTxids(await workerResultPromise);
|
||||
|
||||
// clean up thread error listener
|
||||
this.txSelectionWorker?.removeListener('error', threadErrorListener);
|
||||
|
||||
return this.processBlockTemplates(newMempool, blocks, clusters, saveResults);
|
||||
const processed = this.processBlockTemplates(newMempool, blocks, rates, clusters, saveResults);
|
||||
logger.debug(`makeBlockTemplates completed in ${(Date.now() - start)/1000} seconds`);
|
||||
return processed;
|
||||
} catch (e) {
|
||||
logger.err('makeBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
return this.mempoolBlocks;
|
||||
}
|
||||
|
||||
public async updateBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, added: TransactionExtended[], removed: string[], saveResults: boolean = false): Promise<void> {
|
||||
public async $updateBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, added: TransactionExtended[], removed: TransactionExtended[], saveResults: boolean = false): Promise<void> {
|
||||
if (!this.txSelectionWorker) {
|
||||
// need to reset the worker
|
||||
this.makeBlockTemplates(newMempool, saveResults);
|
||||
await this.$makeBlockTemplates(newMempool, saveResults);
|
||||
return;
|
||||
}
|
||||
|
||||
const start = Date.now();
|
||||
|
||||
for (const tx of Object.values(added)) {
|
||||
this.setUid(tx);
|
||||
}
|
||||
const removedUids = removed.map(tx => this.getUid(tx)).filter(uid => uid != null) as number[];
|
||||
// prepare a stripped down version of the mempool with only the minimum necessary data
|
||||
// to reduce the overhead of passing this data to the worker thread
|
||||
const addedStripped: ThreadTransaction[] = added.map(entry => {
|
||||
const addedStripped: CompactThreadTransaction[] = added.filter(entry => entry.uid != null).map(entry => {
|
||||
return {
|
||||
txid: entry.txid,
|
||||
uid: entry.uid || 0,
|
||||
fee: entry.fee,
|
||||
weight: entry.weight,
|
||||
feePerVsize: entry.fee / (entry.weight / 4),
|
||||
effectiveFeePerVsize: entry.fee / (entry.weight / 4),
|
||||
vin: entry.vin.map(v => v.txid),
|
||||
effectiveFeePerVsize: entry.effectiveFeePerVsize || (entry.fee / (entry.weight / 4)),
|
||||
inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => uid != null) as number[],
|
||||
};
|
||||
});
|
||||
|
||||
// run the block construction algorithm in a separate thread, and wait for a result
|
||||
let threadErrorListener;
|
||||
try {
|
||||
const workerResultPromise = new Promise<{ blocks: ThreadTransaction[][], clusters: { [root: string]: string[] } }>((resolve, reject) => {
|
||||
const workerResultPromise = new Promise<{ blocks: number[][], rates: Map<number, number>, clusters: Map<number, number[]> }>((resolve, reject) => {
|
||||
threadErrorListener = reject;
|
||||
this.txSelectionWorker?.once('message', (result): void => {
|
||||
resolve(result);
|
||||
});
|
||||
this.txSelectionWorker?.once('error', reject);
|
||||
});
|
||||
this.txSelectionWorker.postMessage({ type: 'update', added: addedStripped, removed });
|
||||
const { blocks, clusters } = await workerResultPromise;
|
||||
this.txSelectionWorker.postMessage({ type: 'update', added: addedStripped, removed: removedUids });
|
||||
const { blocks, rates, clusters } = this.convertResultTxids(await workerResultPromise);
|
||||
|
||||
this.removeUids(removedUids);
|
||||
|
||||
// clean up thread error listener
|
||||
this.txSelectionWorker?.removeListener('error', threadErrorListener);
|
||||
|
||||
this.processBlockTemplates(newMempool, blocks, clusters, saveResults);
|
||||
this.processBlockTemplates(newMempool, blocks, rates, clusters, saveResults);
|
||||
logger.debug(`updateBlockTemplates completed in ${(Date.now() - start) / 1000} seconds`);
|
||||
} catch (e) {
|
||||
logger.err('updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
private processBlockTemplates(mempool, blocks, clusters, saveResults): MempoolBlockWithTransactions[] {
|
||||
// update this thread's mempool with the results
|
||||
blocks.forEach(block => {
|
||||
block.forEach(tx => {
|
||||
if (tx.txid in mempool) {
|
||||
if (tx.effectiveFeePerVsize != null) {
|
||||
mempool[tx.txid].effectiveFeePerVsize = tx.effectiveFeePerVsize;
|
||||
}
|
||||
if (tx.cpfpRoot && tx.cpfpRoot in clusters) {
|
||||
const ancestors: Ancestor[] = [];
|
||||
const descendants: Ancestor[] = [];
|
||||
const cluster = clusters[tx.cpfpRoot];
|
||||
let matched = false;
|
||||
cluster.forEach(txid => {
|
||||
if (txid === tx.txid) {
|
||||
matched = true;
|
||||
} else {
|
||||
const relative = {
|
||||
txid: txid,
|
||||
fee: mempool[txid].fee,
|
||||
weight: mempool[txid].weight,
|
||||
};
|
||||
if (matched) {
|
||||
descendants.push(relative);
|
||||
} else {
|
||||
ancestors.push(relative);
|
||||
}
|
||||
}
|
||||
});
|
||||
mempool[tx.txid].ancestors = ancestors;
|
||||
mempool[tx.txid].descendants = descendants;
|
||||
mempool[tx.txid].bestDescendant = null;
|
||||
}
|
||||
mempool[tx.txid].cpfpChecked = tx.cpfpChecked;
|
||||
}
|
||||
});
|
||||
});
|
||||
private processBlockTemplates(mempool, blocks: string[][], rates: { [root: string]: number }, clusters: { [root: string]: string[] }, saveResults): MempoolBlockWithTransactions[] {
|
||||
for (const txid of Object.keys(rates)) {
|
||||
if (txid in mempool) {
|
||||
mempool[txid].effectiveFeePerVsize = rates[txid];
|
||||
}
|
||||
}
|
||||
|
||||
// unpack the condensed blocks into proper mempool blocks
|
||||
const mempoolBlocks = blocks.map((transactions, blockIndex) => {
|
||||
return this.dataToMempoolBlocks(transactions.map(tx => {
|
||||
return mempool[tx.txid] || null;
|
||||
}).filter(tx => !!tx), blockIndex);
|
||||
});
|
||||
const readyBlocks: { transactionIds, transactions, totalSize, totalWeight, totalFees }[] = [];
|
||||
const sizeLimit = (config.MEMPOOL.BLOCK_WEIGHT_UNITS / 4) * 1.2;
|
||||
// update this thread's mempool with the results
|
||||
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
|
||||
const block: string[] = blocks[blockIndex];
|
||||
let txid: string;
|
||||
let mempoolTx: TransactionExtended;
|
||||
let totalSize = 0;
|
||||
let totalVsize = 0;
|
||||
let totalWeight = 0;
|
||||
let totalFees = 0;
|
||||
const transactions: TransactionExtended[] = [];
|
||||
for (let txIndex = 0; txIndex < block.length; txIndex++) {
|
||||
txid = block[txIndex];
|
||||
if (txid) {
|
||||
mempoolTx = mempool[txid];
|
||||
// save position in projected blocks
|
||||
mempoolTx.position = {
|
||||
block: blockIndex,
|
||||
vsize: totalVsize + (mempoolTx.vsize / 2),
|
||||
};
|
||||
mempoolTx.cpfpChecked = true;
|
||||
|
||||
totalSize += mempoolTx.size;
|
||||
totalVsize += mempoolTx.vsize;
|
||||
totalWeight += mempoolTx.weight;
|
||||
totalFees += mempoolTx.fee;
|
||||
|
||||
transactions.push(mempoolTx);
|
||||
}
|
||||
}
|
||||
readyBlocks.push({
|
||||
transactionIds: block,
|
||||
transactions,
|
||||
totalSize,
|
||||
totalWeight,
|
||||
totalFees
|
||||
});
|
||||
}
|
||||
|
||||
for (const cluster of Object.values(clusters)) {
|
||||
for (const memberTxid of cluster) {
|
||||
if (memberTxid in mempool) {
|
||||
const mempoolTx = mempool[memberTxid];
|
||||
const ancestors: Ancestor[] = [];
|
||||
const descendants: Ancestor[] = [];
|
||||
let matched = false;
|
||||
cluster.forEach(txid => {
|
||||
if (txid === memberTxid) {
|
||||
matched = true;
|
||||
} else {
|
||||
const relative = {
|
||||
txid: txid,
|
||||
fee: mempool[txid].fee,
|
||||
weight: mempool[txid].weight,
|
||||
};
|
||||
if (matched) {
|
||||
descendants.push(relative);
|
||||
} else {
|
||||
ancestors.push(relative);
|
||||
}
|
||||
}
|
||||
});
|
||||
mempoolTx.ancestors = ancestors;
|
||||
mempoolTx.descendants = descendants;
|
||||
mempoolTx.bestDescendant = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mempoolBlocks = readyBlocks.map((b, i) => this.dataToMempoolBlocks(b.transactions, i));
|
||||
|
||||
if (saveResults) {
|
||||
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks);
|
||||
@@ -324,6 +398,56 @@ class MempoolBlocks {
|
||||
transactions: fitTransactions.map((tx) => Common.stripTransaction(tx)),
|
||||
};
|
||||
}
|
||||
|
||||
private resetUids(): void {
|
||||
this.uidMap.clear();
|
||||
this.nextUid = 1;
|
||||
}
|
||||
|
||||
private setUid(tx: TransactionExtended): number {
|
||||
const uid = this.nextUid;
|
||||
this.nextUid++;
|
||||
this.uidMap.set(uid, tx.txid);
|
||||
tx.uid = uid;
|
||||
return uid;
|
||||
}
|
||||
|
||||
private getUid(tx: TransactionExtended): number | void {
|
||||
if (tx?.uid != null && this.uidMap.has(tx.uid)) {
|
||||
return tx.uid;
|
||||
}
|
||||
}
|
||||
|
||||
private removeUids(uids: number[]): void {
|
||||
for (const uid of uids) {
|
||||
this.uidMap.delete(uid);
|
||||
}
|
||||
}
|
||||
|
||||
private convertResultTxids({ blocks, rates, clusters }: { blocks: number[][], rates: Map<number, number>, clusters: Map<number, number[]>})
|
||||
: { blocks: string[][], rates: { [root: string]: number }, clusters: { [root: string]: string[] }} {
|
||||
const convertedBlocks: string[][] = blocks.map(block => block.map(uid => {
|
||||
return this.uidMap.get(uid) || '';
|
||||
}));
|
||||
const convertedRates = {};
|
||||
for (const rateUid of rates.keys()) {
|
||||
const rateTxid = this.uidMap.get(rateUid);
|
||||
if (rateTxid) {
|
||||
convertedRates[rateTxid] = rates.get(rateUid);
|
||||
}
|
||||
}
|
||||
const convertedClusters = {};
|
||||
for (const rootUid of clusters.keys()) {
|
||||
const rootTxid = this.uidMap.get(rootUid);
|
||||
if (rootTxid) {
|
||||
const members = clusters.get(rootUid)?.map(uid => {
|
||||
return this.uidMap.get(uid);
|
||||
});
|
||||
convertedClusters[rootTxid] = members;
|
||||
}
|
||||
}
|
||||
return { blocks: convertedBlocks, rates: convertedRates, clusters: convertedClusters } as { blocks: string[][], rates: { [root: string]: number }, clusters: { [root: string]: string[] }};
|
||||
}
|
||||
}
|
||||
|
||||
export default new MempoolBlocks();
|
||||
|
||||
@@ -20,7 +20,7 @@ class Mempool {
|
||||
maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
|
||||
private mempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
||||
deletedTransactions: TransactionExtended[]) => void) | undefined;
|
||||
private asyncMempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
||||
private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
||||
deletedTransactions: TransactionExtended[]) => Promise<void>) | undefined;
|
||||
|
||||
private txPerSecondArray: number[] = [];
|
||||
@@ -38,7 +38,6 @@ class Mempool {
|
||||
|
||||
constructor() {
|
||||
setInterval(this.updateTxPerSecond.bind(this), 1000);
|
||||
setInterval(this.deleteExpiredTransactions.bind(this), 20000);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,20 +71,20 @@ class Mempool {
|
||||
|
||||
public setAsyncMempoolChangedCallback(fn: (newMempool: { [txId: string]: TransactionExtended; },
|
||||
newTransactions: TransactionExtended[], deletedTransactions: TransactionExtended[]) => Promise<void>) {
|
||||
this.asyncMempoolChangedCallback = fn;
|
||||
this.$asyncMempoolChangedCallback = fn;
|
||||
}
|
||||
|
||||
public getMempool(): { [txid: string]: TransactionExtended } {
|
||||
return this.mempoolCache;
|
||||
}
|
||||
|
||||
public setMempool(mempoolData: { [txId: string]: TransactionExtended }) {
|
||||
public async $setMempool(mempoolData: { [txId: string]: TransactionExtended }) {
|
||||
this.mempoolCache = mempoolData;
|
||||
if (this.mempoolChangedCallback) {
|
||||
this.mempoolChangedCallback(this.mempoolCache, [], []);
|
||||
}
|
||||
if (this.asyncMempoolChangedCallback) {
|
||||
this.asyncMempoolChangedCallback(this.mempoolCache, [], []);
|
||||
if (this.$asyncMempoolChangedCallback) {
|
||||
await this.$asyncMempoolChangedCallback(this.mempoolCache, [], []);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,8 +222,8 @@ class Mempool {
|
||||
if (this.mempoolChangedCallback && (hasChange || deletedTransactions.length)) {
|
||||
this.mempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions);
|
||||
}
|
||||
if (this.asyncMempoolChangedCallback && (hasChange || deletedTransactions.length)) {
|
||||
await this.asyncMempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions);
|
||||
if (this.$asyncMempoolChangedCallback && (hasChange || deletedTransactions.length)) {
|
||||
await this.$asyncMempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions);
|
||||
}
|
||||
|
||||
const end = new Date().getTime();
|
||||
@@ -256,7 +255,7 @@ class Mempool {
|
||||
}
|
||||
}
|
||||
|
||||
private deleteExpiredTransactions() {
|
||||
public deleteExpiredTransactions() {
|
||||
const now = new Date().getTime();
|
||||
for (const tx in this.mempoolCache) {
|
||||
const lazyDeleteAt = this.mempoolCache[tx].deleteAfter;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import config from '../config';
|
||||
import logger from '../logger';
|
||||
import { ThreadTransaction, MempoolBlockWithTransactions, AuditTransaction } from '../mempool.interfaces';
|
||||
import { CompactThreadTransaction, AuditTransaction } from '../mempool.interfaces';
|
||||
import { PairingHeap } from '../utils/pairing-heap';
|
||||
import { Common } from './common';
|
||||
import { parentPort } from 'worker_threads';
|
||||
|
||||
let mempool: { [txid: string]: ThreadTransaction } = {};
|
||||
let mempool: Map<number, CompactThreadTransaction> = new Map();
|
||||
|
||||
if (parentPort) {
|
||||
parentPort.on('message', (params) => {
|
||||
@@ -13,18 +13,18 @@ if (parentPort) {
|
||||
mempool = params.mempool;
|
||||
} else if (params.type === 'update') {
|
||||
params.added.forEach(tx => {
|
||||
mempool[tx.txid] = tx;
|
||||
mempool.set(tx.uid, tx);
|
||||
});
|
||||
params.removed.forEach(txid => {
|
||||
delete mempool[txid];
|
||||
params.removed.forEach(uid => {
|
||||
mempool.delete(uid);
|
||||
});
|
||||
}
|
||||
|
||||
const { blocks, clusters } = makeBlockTemplates(mempool);
|
||||
const { blocks, rates, clusters } = makeBlockTemplates(mempool);
|
||||
|
||||
// return the result to main thread.
|
||||
if (parentPort) {
|
||||
parentPort.postMessage({ blocks, clusters });
|
||||
parentPort.postMessage({ blocks, rates, clusters });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -33,26 +33,25 @@ if (parentPort) {
|
||||
* Build projected mempool blocks using an approximation of the transaction selection algorithm from Bitcoin Core
|
||||
* (see BlockAssembler in https://github.com/bitcoin/bitcoin/blob/master/src/node/miner.cpp)
|
||||
*/
|
||||
function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
|
||||
: { blocks: ThreadTransaction[][], clusters: { [root: string]: string[] } } {
|
||||
function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
|
||||
: { blocks: number[][], rates: Map<number, number>, clusters: Map<number, number[]> } {
|
||||
const start = Date.now();
|
||||
const auditPool: { [txid: string]: AuditTransaction } = {};
|
||||
const auditPool: Map<number, AuditTransaction> = new Map();
|
||||
const mempoolArray: AuditTransaction[] = [];
|
||||
const restOfArray: ThreadTransaction[] = [];
|
||||
const cpfpClusters: { [root: string]: string[] } = {};
|
||||
const cpfpClusters: Map<number, number[]> = new Map();
|
||||
|
||||
// grab the top feerate txs up to maxWeight
|
||||
Object.values(mempool).sort((a, b) => b.feePerVsize - a.feePerVsize).forEach(tx => {
|
||||
mempool.forEach(tx => {
|
||||
tx.dirty = false;
|
||||
// initializing everything up front helps V8 optimize property access later
|
||||
auditPool[tx.txid] = {
|
||||
txid: tx.txid,
|
||||
auditPool.set(tx.uid, {
|
||||
uid: tx.uid,
|
||||
fee: tx.fee,
|
||||
weight: tx.weight,
|
||||
feePerVsize: tx.feePerVsize,
|
||||
effectiveFeePerVsize: tx.feePerVsize,
|
||||
vin: tx.vin,
|
||||
inputs: tx.inputs || [],
|
||||
relativesSet: false,
|
||||
ancestorMap: new Map<string, AuditTransaction>(),
|
||||
ancestorMap: new Map<number, AuditTransaction>(),
|
||||
children: new Set<AuditTransaction>(),
|
||||
ancestorFee: 0,
|
||||
ancestorWeight: 0,
|
||||
@@ -60,8 +59,8 @@ function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
|
||||
used: false,
|
||||
modified: false,
|
||||
modifiedNode: null,
|
||||
};
|
||||
mempoolArray.push(auditPool[tx.txid]);
|
||||
});
|
||||
mempoolArray.push(auditPool.get(tx.uid) as AuditTransaction);
|
||||
});
|
||||
|
||||
// Build relatives graph & calculate ancestor scores
|
||||
@@ -72,15 +71,28 @@ function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
|
||||
}
|
||||
|
||||
// Sort by descending ancestor score
|
||||
mempoolArray.sort((a, b) => (b.score || 0) - (a.score || 0));
|
||||
mempoolArray.sort((a, b) => {
|
||||
if (b.score === a.score) {
|
||||
// tie-break by uid for stability
|
||||
return a.uid < b.uid ? -1 : 1;
|
||||
} else {
|
||||
return (b.score || 0) - (a.score || 0);
|
||||
}
|
||||
});
|
||||
|
||||
// Build blocks by greedily choosing the highest feerate package
|
||||
// (i.e. the package rooted in the transaction with the best ancestor score)
|
||||
const blocks: ThreadTransaction[][] = [];
|
||||
const blocks: number[][] = [];
|
||||
let blockWeight = 4000;
|
||||
let blockSize = 0;
|
||||
let transactions: AuditTransaction[] = [];
|
||||
const modified: PairingHeap<AuditTransaction> = new PairingHeap((a, b): boolean => (a.score || 0) > (b.score || 0));
|
||||
const modified: PairingHeap<AuditTransaction> = new PairingHeap((a, b): boolean => {
|
||||
if (a.score === b.score) {
|
||||
// tie-break by uid for stability
|
||||
return a.uid > b.uid;
|
||||
} else {
|
||||
return (a.score || 0) > (b.score || 0);
|
||||
}
|
||||
});
|
||||
let overflow: AuditTransaction[] = [];
|
||||
let failures = 0;
|
||||
let top = 0;
|
||||
@@ -107,30 +119,36 @@ function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
|
||||
|
||||
if (nextTx && !nextTx?.used) {
|
||||
// Check if the package fits into this block
|
||||
if (blockWeight + nextTx.ancestorWeight < config.MEMPOOL.BLOCK_WEIGHT_UNITS) {
|
||||
if (blocks.length >= 7 || (blockWeight + nextTx.ancestorWeight < config.MEMPOOL.BLOCK_WEIGHT_UNITS)) {
|
||||
const ancestors: AuditTransaction[] = Array.from(nextTx.ancestorMap.values());
|
||||
// sort ancestors by dependency graph (equivalent to sorting by ascending ancestor count)
|
||||
const sortedTxSet = [...ancestors.sort((a, b) => { return (a.ancestorMap.size || 0) - (b.ancestorMap.size || 0); }), nextTx];
|
||||
let isCluster = false;
|
||||
if (sortedTxSet.length > 1) {
|
||||
cpfpClusters[nextTx.txid] = sortedTxSet.map(tx => tx.txid);
|
||||
cpfpClusters.set(nextTx.uid, sortedTxSet.map(tx => tx.uid));
|
||||
isCluster = true;
|
||||
}
|
||||
const effectiveFeeRate = nextTx.ancestorFee / (nextTx.ancestorWeight / 4);
|
||||
const used: AuditTransaction[] = [];
|
||||
while (sortedTxSet.length) {
|
||||
const ancestor = sortedTxSet.pop();
|
||||
const mempoolTx = mempool[ancestor.txid];
|
||||
const mempoolTx = mempool.get(ancestor.uid);
|
||||
if (!mempoolTx) {
|
||||
continue;
|
||||
}
|
||||
ancestor.used = true;
|
||||
ancestor.usedBy = nextTx.txid;
|
||||
ancestor.usedBy = nextTx.uid;
|
||||
// update original copy of this tx with effective fee rate & relatives data
|
||||
mempoolTx.effectiveFeePerVsize = effectiveFeeRate;
|
||||
if (isCluster) {
|
||||
mempoolTx.cpfpRoot = nextTx.txid;
|
||||
if (mempoolTx.effectiveFeePerVsize !== effectiveFeeRate) {
|
||||
mempoolTx.effectiveFeePerVsize = effectiveFeeRate;
|
||||
mempoolTx.dirty = true;
|
||||
}
|
||||
if (mempoolTx.cpfpRoot !== nextTx.uid) {
|
||||
mempoolTx.cpfpRoot = isCluster ? nextTx.uid : null;
|
||||
mempoolTx.dirty;
|
||||
}
|
||||
mempoolTx.cpfpChecked = true;
|
||||
transactions.push(ancestor);
|
||||
blockSize += ancestor.size;
|
||||
blockWeight += ancestor.weight;
|
||||
used.push(ancestor);
|
||||
}
|
||||
@@ -156,11 +174,10 @@ function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
|
||||
if ((exceededPackageTries || queueEmpty) && blocks.length < 7) {
|
||||
// construct this block
|
||||
if (transactions.length) {
|
||||
blocks.push(transactions.map(t => mempool[t.txid]));
|
||||
blocks.push(transactions.map(t => t.uid));
|
||||
}
|
||||
// reset for the next block
|
||||
transactions = [];
|
||||
blockSize = 0;
|
||||
blockWeight = 4000;
|
||||
|
||||
// 'overflow' packages didn't fit in this block, but are valid candidates for the next
|
||||
@@ -175,50 +192,38 @@ function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
|
||||
overflow = [];
|
||||
}
|
||||
}
|
||||
// pack any leftover transactions into the last block
|
||||
for (const tx of overflow) {
|
||||
if (!tx || tx?.used) {
|
||||
continue;
|
||||
}
|
||||
blockWeight += tx.weight;
|
||||
const mempoolTx = mempool[tx.txid];
|
||||
// update original copy of this tx with effective fee rate & relatives data
|
||||
mempoolTx.effectiveFeePerVsize = tx.score;
|
||||
if (tx.ancestorMap.size > 0) {
|
||||
cpfpClusters[tx.txid] = Array.from(tx.ancestorMap?.values()).map(a => a.txid);
|
||||
mempoolTx.cpfpRoot = tx.txid;
|
||||
}
|
||||
mempoolTx.cpfpChecked = true;
|
||||
transactions.push(tx);
|
||||
tx.used = true;
|
||||
|
||||
if (overflow.length > 0) {
|
||||
logger.warn('GBT overflow list unexpectedly non-empty after final block constructed');
|
||||
}
|
||||
const blockTransactions = transactions.map(t => mempool[t.txid]);
|
||||
restOfArray.forEach(tx => {
|
||||
blockWeight += tx.weight;
|
||||
tx.effectiveFeePerVsize = tx.feePerVsize;
|
||||
tx.cpfpChecked = false;
|
||||
blockTransactions.push(tx);
|
||||
});
|
||||
if (blockTransactions.length) {
|
||||
blocks.push(blockTransactions);
|
||||
// add the final unbounded block if it contains any transactions
|
||||
if (transactions.length > 0) {
|
||||
blocks.push(transactions.map(t => t.uid));
|
||||
}
|
||||
|
||||
// get map of dirty transactions
|
||||
const rates = new Map<number, number>();
|
||||
for (const tx of mempool.values()) {
|
||||
if (tx?.dirty) {
|
||||
rates.set(tx.uid, tx.effectiveFeePerVsize || tx.feePerVsize);
|
||||
}
|
||||
}
|
||||
transactions = [];
|
||||
|
||||
const end = Date.now();
|
||||
const time = end - start;
|
||||
logger.debug('Mempool templates calculated in ' + time / 1000 + ' seconds');
|
||||
|
||||
return { blocks, clusters: cpfpClusters };
|
||||
return { blocks, rates, clusters: cpfpClusters };
|
||||
}
|
||||
|
||||
// traverse in-mempool ancestors
|
||||
// recursion unavoidable, but should be limited to depth < 25 by mempool policy
|
||||
function setRelatives(
|
||||
tx: AuditTransaction,
|
||||
mempool: { [txid: string]: AuditTransaction },
|
||||
mempool: Map<number, AuditTransaction>,
|
||||
): void {
|
||||
for (const parent of tx.vin) {
|
||||
const parentTx = mempool[parent];
|
||||
for (const parent of tx.inputs) {
|
||||
const parentTx = mempool.get(parent);
|
||||
if (parentTx && !tx.ancestorMap?.has(parent)) {
|
||||
tx.ancestorMap.set(parent, parentTx);
|
||||
parentTx.children.add(tx);
|
||||
@@ -227,7 +232,7 @@ function setRelatives(
|
||||
setRelatives(parentTx, mempool);
|
||||
}
|
||||
parentTx.ancestorMap.forEach((ancestor) => {
|
||||
tx.ancestorMap.set(ancestor.txid, ancestor);
|
||||
tx.ancestorMap.set(ancestor.uid, ancestor);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -245,7 +250,7 @@ function setRelatives(
|
||||
// avoids recursion to limit call stack depth
|
||||
function updateDescendants(
|
||||
rootTx: AuditTransaction,
|
||||
mempool: { [txid: string]: AuditTransaction },
|
||||
mempool: Map<number, AuditTransaction>,
|
||||
modified: PairingHeap<AuditTransaction>,
|
||||
): void {
|
||||
const descendantSet: Set<AuditTransaction> = new Set();
|
||||
@@ -261,9 +266,9 @@ function updateDescendants(
|
||||
});
|
||||
while (descendants.length) {
|
||||
descendantTx = descendants.pop();
|
||||
if (descendantTx && descendantTx.ancestorMap && descendantTx.ancestorMap.has(rootTx.txid)) {
|
||||
if (descendantTx && descendantTx.ancestorMap && descendantTx.ancestorMap.has(rootTx.uid)) {
|
||||
// remove tx as ancestor
|
||||
descendantTx.ancestorMap.delete(rootTx.txid);
|
||||
descendantTx.ancestorMap.delete(rootTx.uid);
|
||||
descendantTx.ancestorFee -= rootTx.fee;
|
||||
descendantTx.ancestorWeight -= rootTx.weight;
|
||||
tmpScore = descendantTx.score;
|
||||
|
||||
@@ -211,6 +211,7 @@ class WebsocketHandler {
|
||||
if (!_blocks) {
|
||||
_blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
|
||||
}
|
||||
const da = difficultyAdjustment.getDifficultyAdjustment();
|
||||
return {
|
||||
'mempoolInfo': memPool.getMempoolInfo(),
|
||||
'vBytesPerSecond': memPool.getVBytesPerSecond(),
|
||||
@@ -220,7 +221,7 @@ class WebsocketHandler {
|
||||
'transactions': memPool.getLatestTransactions(),
|
||||
'backendInfo': backendInfo.getBackendInfo(),
|
||||
'loadingIndicators': loadingIndicators.getLoadingIndicators(),
|
||||
'da': difficultyAdjustment.getDifficultyAdjustment(),
|
||||
'da': da?.previousTime ? da : undefined,
|
||||
'fees': feeApi.getRecommendedFee(),
|
||||
...this.extraInitProperties
|
||||
};
|
||||
@@ -246,14 +247,14 @@ class WebsocketHandler {
|
||||
});
|
||||
}
|
||||
|
||||
async handleMempoolChange(newMempool: { [txid: string]: TransactionExtended },
|
||||
async $handleMempoolChange(newMempool: { [txid: string]: TransactionExtended },
|
||||
newTransactions: TransactionExtended[], deletedTransactions: TransactionExtended[]): Promise<void> {
|
||||
if (!this.wss) {
|
||||
throw new Error('WebSocket.Server is not set');
|
||||
}
|
||||
|
||||
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
||||
await mempoolBlocks.updateBlockTemplates(newMempool, newTransactions, deletedTransactions.map(tx => tx.txid), true);
|
||||
await mempoolBlocks.$updateBlockTemplates(newMempool, newTransactions, deletedTransactions, true);
|
||||
} else {
|
||||
mempoolBlocks.updateMempoolBlocks(newMempool, true);
|
||||
}
|
||||
@@ -278,7 +279,9 @@ class WebsocketHandler {
|
||||
response['mempoolInfo'] = mempoolInfo;
|
||||
response['vBytesPerSecond'] = vBytesPerSecond;
|
||||
response['transactions'] = newTransactions.slice(0, 6).map((tx) => Common.stripTransaction(tx));
|
||||
response['da'] = da;
|
||||
if (da?.previousTime) {
|
||||
response['da'] = da;
|
||||
}
|
||||
response['fees'] = recommendedFees;
|
||||
}
|
||||
|
||||
@@ -422,13 +425,19 @@ class WebsocketHandler {
|
||||
|
||||
if (config.MEMPOOL.AUDIT) {
|
||||
let projectedBlocks;
|
||||
let auditMempool = _memPool;
|
||||
// template calculation functions have mempool side effects, so calculate audits using
|
||||
// a cloned copy of the mempool if we're running a different algorithm for mempool updates
|
||||
const auditMempool = (config.MEMPOOL.ADVANCED_GBT_AUDIT === config.MEMPOOL.ADVANCED_GBT_MEMPOOL) ? _memPool : deepClone(_memPool);
|
||||
if (config.MEMPOOL.ADVANCED_GBT_AUDIT) {
|
||||
projectedBlocks = await mempoolBlocks.makeBlockTemplates(auditMempool, false);
|
||||
const separateAudit = config.MEMPOOL.ADVANCED_GBT_AUDIT !== config.MEMPOOL.ADVANCED_GBT_MEMPOOL;
|
||||
if (separateAudit) {
|
||||
auditMempool = deepClone(_memPool);
|
||||
if (config.MEMPOOL.ADVANCED_GBT_AUDIT) {
|
||||
projectedBlocks = await mempoolBlocks.$makeBlockTemplates(auditMempool, false);
|
||||
} else {
|
||||
projectedBlocks = mempoolBlocks.updateMempoolBlocks(auditMempool, false);
|
||||
}
|
||||
} else {
|
||||
projectedBlocks = mempoolBlocks.updateMempoolBlocks(auditMempool, false);
|
||||
projectedBlocks = mempoolBlocks.getMempoolBlocksWithTransactions();
|
||||
}
|
||||
|
||||
if (Common.indexingEnabled() && memPool.isInSync()) {
|
||||
@@ -474,16 +483,14 @@ class WebsocketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
const removed: string[] = [];
|
||||
// Update mempool to remove transactions included in the new block
|
||||
for (const txId of txIds) {
|
||||
delete _memPool[txId];
|
||||
removed.push(txId);
|
||||
rbfCache.evict(txId);
|
||||
}
|
||||
|
||||
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
||||
await mempoolBlocks.updateBlockTemplates(_memPool, [], removed, true);
|
||||
await mempoolBlocks.$makeBlockTemplates(_memPool, true);
|
||||
} else {
|
||||
mempoolBlocks.updateMempoolBlocks(_memPool, true);
|
||||
}
|
||||
@@ -505,7 +512,7 @@ class WebsocketHandler {
|
||||
const response = {
|
||||
'block': block,
|
||||
'mempoolInfo': memPool.getMempoolInfo(),
|
||||
'da': da,
|
||||
'da': da?.previousTime ? da : undefined,
|
||||
'fees': fees,
|
||||
};
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ class Server {
|
||||
await poolsUpdater.updatePoolsJson(); // Needs to be done before loading the disk cache because we sometimes wipe it
|
||||
await syncAssets.syncAssets$();
|
||||
if (config.MEMPOOL.ENABLED) {
|
||||
diskCache.loadMempoolCache();
|
||||
await diskCache.$loadMempoolCache();
|
||||
}
|
||||
|
||||
if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED && cluster.isPrimary) {
|
||||
@@ -179,6 +179,7 @@ class Server {
|
||||
}
|
||||
}
|
||||
await blocks.$updateBlocks();
|
||||
memPool.deleteExpiredTransactions();
|
||||
await memPool.$updateMempool();
|
||||
indexer.$run();
|
||||
|
||||
@@ -205,6 +206,8 @@ class Server {
|
||||
setTimeout(this.runMainUpdateLoop.bind(this), 1000 * this.currentBackendRetryInterval);
|
||||
this.currentBackendRetryInterval *= 2;
|
||||
this.currentBackendRetryInterval = Math.min(this.currentBackendRetryInterval, 60);
|
||||
} finally {
|
||||
diskCache.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +240,7 @@ class Server {
|
||||
websocketHandler.setupConnectionHandling();
|
||||
if (config.MEMPOOL.ENABLED) {
|
||||
statistics.setNewStatisticsEntryCallback(websocketHandler.handleNewStatistic.bind(websocketHandler));
|
||||
memPool.setAsyncMempoolChangedCallback(websocketHandler.handleMempoolChange.bind(websocketHandler));
|
||||
memPool.setAsyncMempoolChangedCallback(websocketHandler.$handleMempoolChange.bind(websocketHandler));
|
||||
blocks.setNewAsyncBlockCallback(websocketHandler.handleNewBlock.bind(websocketHandler));
|
||||
}
|
||||
priceUpdater.setRatesChangedCallback(websocketHandler.handleNewConversionRates.bind(websocketHandler));
|
||||
|
||||
@@ -80,17 +80,22 @@ export interface TransactionExtended extends IEsploraApi.Transaction {
|
||||
bestDescendant?: BestDescendant | null;
|
||||
cpfpChecked?: boolean;
|
||||
deleteAfter?: number;
|
||||
position?: {
|
||||
block: number,
|
||||
vsize: number,
|
||||
};
|
||||
uid?: number;
|
||||
}
|
||||
|
||||
export interface AuditTransaction {
|
||||
txid: string;
|
||||
uid: number;
|
||||
fee: number;
|
||||
weight: number;
|
||||
feePerVsize: number;
|
||||
effectiveFeePerVsize: number;
|
||||
vin: string[];
|
||||
inputs: number[];
|
||||
relativesSet: boolean;
|
||||
ancestorMap: Map<string, AuditTransaction>;
|
||||
ancestorMap: Map<number, AuditTransaction>;
|
||||
children: Set<AuditTransaction>;
|
||||
ancestorFee: number;
|
||||
ancestorWeight: number;
|
||||
@@ -100,13 +105,25 @@ export interface AuditTransaction {
|
||||
modifiedNode: HeapNode<AuditTransaction>;
|
||||
}
|
||||
|
||||
export interface CompactThreadTransaction {
|
||||
uid: number;
|
||||
fee: number;
|
||||
weight: number;
|
||||
feePerVsize: number;
|
||||
effectiveFeePerVsize?: number;
|
||||
inputs: number[];
|
||||
cpfpRoot?: string;
|
||||
cpfpChecked?: boolean;
|
||||
dirty?: boolean;
|
||||
}
|
||||
|
||||
export interface ThreadTransaction {
|
||||
txid: string;
|
||||
fee: number;
|
||||
weight: number;
|
||||
feePerVsize: number;
|
||||
effectiveFeePerVsize?: number;
|
||||
vin: string[];
|
||||
inputs: number[];
|
||||
cpfpRoot?: string;
|
||||
cpfpChecked?: boolean;
|
||||
}
|
||||
@@ -309,9 +326,11 @@ export interface IDifficultyAdjustment {
|
||||
remainingBlocks: number;
|
||||
remainingTime: number;
|
||||
previousRetarget: number;
|
||||
previousTime: number;
|
||||
nextRetargetHeight: number;
|
||||
timeAvg: number;
|
||||
timeOffset: number;
|
||||
expectedBlocks: number;
|
||||
}
|
||||
|
||||
export interface IndexedDifficultyAdjustment {
|
||||
|
||||
@@ -466,30 +466,6 @@ class BlocksRepository {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get one block by hash
|
||||
*/
|
||||
public async $getBlockByHash(hash: string): Promise<object | null> {
|
||||
try {
|
||||
const query = `
|
||||
SELECT ${BLOCK_DB_FIELDS}
|
||||
FROM blocks
|
||||
JOIN pools ON blocks.pool_id = pools.id
|
||||
WHERE hash = ?;
|
||||
`;
|
||||
const [rows]: any[] = await DB.query(query, [hash]);
|
||||
|
||||
if (rows.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await this.formatDbBlockIntoExtendedBlock(rows[0]);
|
||||
} catch (e) {
|
||||
logger.err(`Cannot get indexed block ${hash}. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return blocks difficulty
|
||||
*/
|
||||
@@ -599,7 +575,6 @@ class BlocksRepository {
|
||||
if (blocks[idx].previous_block_hash !== blocks[idx - 1].hash) {
|
||||
logger.warn(`Chain divergence detected at block ${blocks[idx - 1].height}`);
|
||||
await this.$deleteBlocksFrom(blocks[idx - 1].height);
|
||||
await BlocksSummariesRepository.$deleteBlocksFrom(blocks[idx - 1].height);
|
||||
await HashratesRepository.$deleteHashratesFromTimestamp(blocks[idx - 1].timestamp - 604800);
|
||||
await DifficultyAdjustmentsRepository.$deleteAdjustementsFromHeight(blocks[idx - 1].height);
|
||||
return false;
|
||||
@@ -619,7 +594,7 @@ class BlocksRepository {
|
||||
* Delete blocks from the database from blockHeight
|
||||
*/
|
||||
public async $deleteBlocksFrom(blockHeight: number) {
|
||||
logger.info(`Delete newer blocks from height ${blockHeight} from the database`);
|
||||
logger.info(`Delete newer blocks from height ${blockHeight} from the database`, logger.tags.mining);
|
||||
|
||||
try {
|
||||
await DB.query(`DELETE FROM blocks where height >= ${blockHeight}`);
|
||||
@@ -978,6 +953,7 @@ class BlocksRepository {
|
||||
}
|
||||
|
||||
// If we're missing block summary related field, check if we can populate them on the fly now
|
||||
// This is for example triggered upon re-org
|
||||
if (Common.blocksSummariesIndexingEnabled() &&
|
||||
(extras.medianFeeAmt === null || extras.feePercentiles === null))
|
||||
{
|
||||
@@ -985,7 +961,7 @@ class BlocksRepository {
|
||||
if (extras.feePercentiles === null) {
|
||||
const block = await bitcoinClient.getBlock(dbBlk.id, 2);
|
||||
const summary = blocks.summarizeBlock(block);
|
||||
await BlocksSummariesRepository.$saveSummary({ height: block.height, mined: summary });
|
||||
await BlocksSummariesRepository.$saveTransactions(dbBlk.height, dbBlk.hash, summary.transactions);
|
||||
extras.feePercentiles = await BlocksSummariesRepository.$getFeePercentilesByBlockId(dbBlk.id);
|
||||
}
|
||||
if (extras.feePercentiles !== null) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import DB from '../database';
|
||||
import logger from '../logger';
|
||||
import { BlockSummary } from '../mempool.interfaces';
|
||||
import { BlockSummary, TransactionStripped } from '../mempool.interfaces';
|
||||
|
||||
class BlocksSummariesRepository {
|
||||
public async $getByBlockId(id: string): Promise<BlockSummary | undefined> {
|
||||
@@ -17,7 +17,7 @@ class BlocksSummariesRepository {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async $saveSummary(params: { height: number, mined?: BlockSummary}) {
|
||||
public async $saveSummary(params: { height: number, mined?: BlockSummary}): Promise<void> {
|
||||
const blockId = params.mined?.id;
|
||||
try {
|
||||
const transactions = JSON.stringify(params.mined?.transactions || []);
|
||||
@@ -37,6 +37,20 @@ class BlocksSummariesRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public async $saveTransactions(blockHeight: number, blockId: string, transactions: TransactionStripped[]): Promise<void> {
|
||||
try {
|
||||
const transactionsStr = JSON.stringify(transactions);
|
||||
await DB.query(`
|
||||
INSERT INTO blocks_summaries
|
||||
SET height = ?, transactions = ?, id = ?
|
||||
ON DUPLICATE KEY UPDATE transactions = ?`,
|
||||
[blockHeight, transactionsStr, blockId, transactionsStr]);
|
||||
} catch (e: any) {
|
||||
logger.debug(`Cannot save block summary transactions for ${blockId}. Reason: ${e instanceof Error ? e.message : e}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async $saveTemplate(params: { height: number, template: BlockSummary}) {
|
||||
const blockId = params.template?.id;
|
||||
try {
|
||||
@@ -68,19 +82,6 @@ class BlocksSummariesRepository {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete blocks from the database from blockHeight
|
||||
*/
|
||||
public async $deleteBlocksFrom(blockHeight: number) {
|
||||
logger.info(`Delete newer blocks summary from height ${blockHeight} from the database`);
|
||||
|
||||
try {
|
||||
await DB.query(`DELETE FROM blocks_summaries where height >= ${blockHeight}`);
|
||||
} catch (e) {
|
||||
logger.err('Cannot delete indexed blocks summaries. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fee percentiles if the block has already been indexed, [] otherwise
|
||||
*
|
||||
|
||||
@@ -220,7 +220,7 @@ class HashratesRepository {
|
||||
* Delete hashrates from the database from timestamp
|
||||
*/
|
||||
public async $deleteHashratesFromTimestamp(timestamp: number) {
|
||||
logger.info(`Delete newer hashrates from timestamp ${new Date(timestamp * 1000).toUTCString()} from the database`);
|
||||
logger.info(`Delete newer hashrates from timestamp ${new Date(timestamp * 1000).toUTCString()} from the database`, logger.tags.mining);
|
||||
|
||||
try {
|
||||
await DB.query(`DELETE FROM hashrates WHERE hashrate_timestamp >= FROM_UNIXTIME(?)`, [timestamp]);
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "mempool-frontend",
|
||||
"version": "2.5.0-dev",
|
||||
"version": "2.5.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mempool-frontend",
|
||||
"version": "2.5.0-dev",
|
||||
"version": "2.5.0",
|
||||
"license": "GNU Affero General Public License v3.0",
|
||||
"dependencies": {
|
||||
"@angular-devkit/build-angular": "^14.2.10",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mempool-frontend",
|
||||
"version": "2.5.0-dev",
|
||||
"version": "2.5.0",
|
||||
"description": "Bitcoin mempool visualizer and blockchain explorer backend",
|
||||
"license": "GNU Affero General Public License v3.0",
|
||||
"homepage": "https://mempool.space",
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
<span>Umbrel</span>
|
||||
</a>
|
||||
<a href="https://github.com/rootzoll/raspiblitz" target="_blank" title="RaspiBlitz">
|
||||
<img class="image" src="/resources/profile/raspiblitz.jpg" />
|
||||
<img class="image" src="/resources/profile/raspiblitz.svg" />
|
||||
<span>RaspiBlitz</span>
|
||||
</a>
|
||||
<a href="https://github.com/mynodebtc/mynode" target="_blank" title="MyNode">
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<a [routerLink]="['/block' | relativeUrl, diffChange.height]">{{ diffChange.height }}</a>
|
||||
</td>
|
||||
<td class="date text-left">
|
||||
<app-time kind="since" [time]="diffChange.timestamp" [fastRender]="true"></app-time>
|
||||
<app-time kind="since" [time]="diffChange.timestamp" [fastRender]="true" [precision]="1"></app-time>
|
||||
</td>
|
||||
<td class="text-right">{{ diffChange.difficultyShorten }}</td>
|
||||
<td class="text-right" [style]="diffChange.change >= 0 ? 'color: #42B747' : 'color: #B74242'">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
|
||||
<ng-template #blocksSingular let-i i18n="shared.block">{{ i }} <span class="shared-block">block</span></ng-template>
|
||||
</div>
|
||||
<div class="symbol"><app-time kind="until" [time]="epochData.estimatedRetargetDate" [fastRender]="true"></app-time></div>
|
||||
<div class="symbol"><app-time kind="until" [time]="epochData.estimatedRetargetDate" [fastRender]="true" [precision]="1"></app-time></div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="difficulty-box.estimate">Estimate</h5>
|
||||
@@ -54,7 +54,7 @@
|
||||
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
|
||||
<ng-template #blocksSingular let-i i18n="shared.block">{{ i }} <span class="shared-block">block</span></ng-template>
|
||||
</div>
|
||||
<div class="symbol"><app-time kind="until" [time]="epochData.timeUntilHalving" [fastRender]="true"></app-time></div>
|
||||
<div class="symbol"><app-time kind="until" [time]="epochData.timeUntilHalving" [fastRender]="true" [precision]="1"></app-time></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<div class="difficulty-stats">
|
||||
<div class="item">
|
||||
<div class="card-text">
|
||||
~<app-time [time]="epochData.timeAvg / 1000" [forceFloorOnTimeIntervals]="['minute']" [fractionDigits]="1"></app-time>
|
||||
~<app-time [time]="epochData.timeAvg / 1000" [fractionDigits]="1"></app-time>
|
||||
</div>
|
||||
<div class="symbol" i18n="difficulty-box.average-block-time">Average block time</div>
|
||||
</div>
|
||||
@@ -68,7 +68,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="card-text"><app-time kind="until" [time]="epochData.estimatedRetargetDate" [fastRender]="true"></app-time></div>
|
||||
<div class="card-text"><app-time kind="until" [time]="epochData.estimatedRetargetDate" [fastRender]="true" [precision]="1"></app-time></div>
|
||||
<div class="symbol">
|
||||
{{ epochData.retargetDateString }}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="fee-estimation-wrapper" *ngIf="(isLoadingWebSocket$ | async) === false && (recommendedFees$ | async) as recommendedFees; else loadingFees">
|
||||
<div class="fee-estimation-wrapper" *ngIf="(isLoading$ | async) === false && (recommendedFees$ | async) as recommendedFees; else loadingFees">
|
||||
<div class="d-flex">
|
||||
<div class="fee-progress-bar" [style.background]="noPriority">
|
||||
<span class="fee-label" i18n="fees-box.no-priority" i18n-ngbTooltip="Transaction feerate tooltip (economy)" ngbTooltip="Either 2x the minimum, or the Low Priority rate (whichever is lower)" placement="top">No Priority</span>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Observable, combineLatest } from 'rxjs';
|
||||
import { Recommendedfees } from '../../interfaces/websocket.interface';
|
||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { map, startWith, tap } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-fees-box',
|
||||
@@ -12,7 +12,7 @@ import { tap } from 'rxjs/operators';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class FeesBoxComponent implements OnInit {
|
||||
isLoadingWebSocket$: Observable<boolean>;
|
||||
isLoading$: Observable<boolean>;
|
||||
recommendedFees$: Observable<Recommendedfees>;
|
||||
gradient = 'linear-gradient(to right, #2e324e, #2e324e)';
|
||||
noPriority = '#2e324e';
|
||||
@@ -22,7 +22,12 @@ export class FeesBoxComponent implements OnInit {
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$;
|
||||
this.isLoading$ = combineLatest(
|
||||
this.stateService.isLoadingWebSocket$.pipe(startWith(false)),
|
||||
this.stateService.loadingIndicators$.pipe(startWith({ mempool: 0 })),
|
||||
).pipe(map(([socket, indicators]) => {
|
||||
return socket || (indicators.mempool != null && indicators.mempool !== 100);
|
||||
}));
|
||||
this.recommendedFees$ = this.stateService.recommendedFees$
|
||||
.pipe(
|
||||
tap((fees) => {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<app-time kind="until" [time]="(1 * i) + now + 61000" [fastRender]="false" [fixedRender]="true"></app-time>
|
||||
</ng-template>
|
||||
<ng-template #timeDiffMainnet>
|
||||
<app-time kind="until" [time]="da.timeAvg * (i + 1) + now + da.timeOffset" [fastRender]="false" [fixedRender]="true" [forceFloorOnTimeIntervals]="['hour']"></app-time>
|
||||
<app-time kind="until" [time]="da.timeAvg * (i + 1) + now + da.timeOffset" [fastRender]="false" [fixedRender]="true"></app-time>
|
||||
</ng-template>
|
||||
</div>
|
||||
<ng-template #mergedBlock>
|
||||
|
||||
@@ -153,7 +153,7 @@ export class SearchFormComponent implements OnInit {
|
||||
const matchesBlockHeight = this.regexBlockheight.test(searchText);
|
||||
const matchesTxId = this.regexTransaction.test(searchText) && !this.regexBlockhash.test(searchText);
|
||||
const matchesBlockHash = this.regexBlockhash.test(searchText);
|
||||
const matchesAddress = this.regexAddress.test(searchText);
|
||||
const matchesAddress = !matchesTxId && this.regexAddress.test(searchText);
|
||||
|
||||
if (matchesAddress && this.network === 'bisq') {
|
||||
searchText = 'B' + searchText;
|
||||
@@ -198,7 +198,7 @@ export class SearchFormComponent implements OnInit {
|
||||
const searchText = result || this.searchForm.value.searchText.trim();
|
||||
if (searchText) {
|
||||
this.isSearching = true;
|
||||
if (this.regexAddress.test(searchText)) {
|
||||
if (!this.regexTransaction.test(searchText) && this.regexAddress.test(searchText)) {
|
||||
this.navigate('/address/', searchText);
|
||||
} else if (this.regexBlockhash.test(searchText) || this.regexBlockheight.test(searchText)) {
|
||||
this.navigate('/block/', searchText);
|
||||
|
||||
@@ -10,6 +10,16 @@ import { dates } from '../../shared/i18n/dates';
|
||||
export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
interval: number;
|
||||
text: string;
|
||||
units: string[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];
|
||||
precisionThresholds = {
|
||||
year: 100,
|
||||
month: 18,
|
||||
week: 12,
|
||||
day: 31,
|
||||
hour: 48,
|
||||
minute: 90,
|
||||
second: 90
|
||||
};
|
||||
intervals = {};
|
||||
|
||||
@Input() time: number;
|
||||
@@ -18,7 +28,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() fastRender = false;
|
||||
@Input() fixedRender = false;
|
||||
@Input() relative = false;
|
||||
@Input() forceFloorOnTimeIntervals: string[];
|
||||
@Input() precision: number = 0;
|
||||
@Input() fractionDigits: number = 0;
|
||||
|
||||
constructor(
|
||||
@@ -83,23 +93,24 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
let counter: number;
|
||||
for (const i in this.intervals) {
|
||||
if (this.kind !== 'until' || this.forceFloorOnTimeIntervals && this.forceFloorOnTimeIntervals.indexOf(i) > -1) {
|
||||
counter = Math.floor(seconds / this.intervals[i]);
|
||||
} else {
|
||||
counter = Math.round(seconds / this.intervals[i]);
|
||||
for (const [index, unit] of this.units.entries()) {
|
||||
let precisionUnit = this.units[Math.min(this.units.length - 1, index + this.precision)];
|
||||
counter = Math.floor(seconds / this.intervals[unit]);
|
||||
const precisionCounter = Math.floor(seconds / this.intervals[precisionUnit]);
|
||||
if (precisionCounter > this.precisionThresholds[precisionUnit]) {
|
||||
precisionUnit = unit;
|
||||
}
|
||||
let rounded = counter;
|
||||
if (this.fractionDigits) {
|
||||
const roundFactor = Math.pow(10,this.fractionDigits);
|
||||
rounded = Math.round((seconds / this.intervals[i]) * roundFactor) / roundFactor;
|
||||
}
|
||||
const dateStrings = dates(rounded);
|
||||
if (counter > 0) {
|
||||
let rounded = Math.round(seconds / this.intervals[precisionUnit]);
|
||||
if (this.fractionDigits) {
|
||||
const roundFactor = Math.pow(10,this.fractionDigits);
|
||||
rounded = Math.round((seconds / this.intervals[precisionUnit]) * roundFactor) / roundFactor;
|
||||
}
|
||||
const dateStrings = dates(rounded);
|
||||
switch (this.kind) {
|
||||
case 'since':
|
||||
if (counter === 1) {
|
||||
switch (i) { // singular (1 day)
|
||||
if (rounded === 1) {
|
||||
switch (precisionUnit) { // singular (1 day)
|
||||
case 'year': return $localize`:@@time-since:${dateStrings.i18nYear}:DATE: ago`; break;
|
||||
case 'month': return $localize`:@@time-since:${dateStrings.i18nMonth}:DATE: ago`; break;
|
||||
case 'week': return $localize`:@@time-since:${dateStrings.i18nWeek}:DATE: ago`; break;
|
||||
@@ -109,7 +120,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
case 'second': return $localize`:@@time-since:${dateStrings.i18nSecond}:DATE: ago`; break;
|
||||
}
|
||||
} else {
|
||||
switch (i) { // plural (2 days)
|
||||
switch (precisionUnit) { // plural (2 days)
|
||||
case 'year': return $localize`:@@time-since:${dateStrings.i18nYears}:DATE: ago`; break;
|
||||
case 'month': return $localize`:@@time-since:${dateStrings.i18nMonths}:DATE: ago`; break;
|
||||
case 'week': return $localize`:@@time-since:${dateStrings.i18nWeeks}:DATE: ago`; break;
|
||||
@@ -121,8 +132,8 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
break;
|
||||
case 'until':
|
||||
if (counter === 1) {
|
||||
switch (i) { // singular (In ~1 day)
|
||||
if (rounded === 1) {
|
||||
switch (precisionUnit) { // singular (In ~1 day)
|
||||
case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYear}:DATE:`; break;
|
||||
case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonth}:DATE:`; break;
|
||||
case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeek}:DATE:`; break;
|
||||
@@ -132,7 +143,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSecond}:DATE:`;
|
||||
}
|
||||
} else {
|
||||
switch (i) { // plural (In ~2 days)
|
||||
switch (precisionUnit) { // plural (In ~2 days)
|
||||
case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYears}:DATE:`; break;
|
||||
case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonths}:DATE:`; break;
|
||||
case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeeks}:DATE:`; break;
|
||||
@@ -144,8 +155,8 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
break;
|
||||
case 'span':
|
||||
if (counter === 1) {
|
||||
switch (i) { // singular (1 day)
|
||||
if (rounded === 1) {
|
||||
switch (precisionUnit) { // singular (1 day)
|
||||
case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYear}:DATE:`; break;
|
||||
case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonth}:DATE:`; break;
|
||||
case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeek}:DATE:`; break;
|
||||
@@ -155,7 +166,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSecond}:DATE:`; break;
|
||||
}
|
||||
} else {
|
||||
switch (i) { // plural (2 days)
|
||||
switch (precisionUnit) { // plural (2 days)
|
||||
case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYears}:DATE:`; break;
|
||||
case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonths}:DATE:`; break;
|
||||
case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeeks}:DATE:`; break;
|
||||
@@ -167,8 +178,8 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (counter === 1) {
|
||||
switch (i) { // singular (1 day)
|
||||
if (rounded === 1) {
|
||||
switch (precisionUnit) { // singular (1 day)
|
||||
case 'year': return dateStrings.i18nYear; break;
|
||||
case 'month': return dateStrings.i18nMonth; break;
|
||||
case 'week': return dateStrings.i18nWeek; break;
|
||||
@@ -178,7 +189,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
|
||||
case 'second': return dateStrings.i18nSecond; break;
|
||||
}
|
||||
} else {
|
||||
switch (i) { // plural (2 days)
|
||||
switch (precisionUnit) { // plural (2 days)
|
||||
case 'year': return dateStrings.i18nYears; break;
|
||||
case 'month': return dateStrings.i18nMonths; break;
|
||||
case 'week': return dateStrings.i18nWeeks; break;
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
<app-time kind="until" [time]="(60 * 1000 * txInBlockIndex) + now" [fastRender]="false" [fixedRender]="true"></app-time>
|
||||
</ng-template>
|
||||
<ng-template #timeEstimateDefault>
|
||||
<app-time kind="until" *ngIf="(timeAvg$ | async) as timeAvg;" [time]="(timeAvg * txInBlockIndex) + now + timeAvg" [fastRender]="false" [fixedRender]="true" [forceFloorOnTimeIntervals]="['hour']"></app-time>
|
||||
<app-time kind="until" *ngIf="(timeAvg$ | async) as timeAvg;" [time]="(timeAvg * txInBlockIndex) + now + timeAvg" [fastRender]="false" [fixedRender]="true"></app-time>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
@@ -49,6 +49,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
blocksSubscription: Subscription;
|
||||
queryParamsSubscription: Subscription;
|
||||
urlFragmentSubscription: Subscription;
|
||||
mempoolBlocksSubscription: Subscription;
|
||||
fragmentParams: URLSearchParams;
|
||||
rbfTransaction: undefined | Transaction;
|
||||
replaced: boolean = false;
|
||||
@@ -59,7 +60,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
fetchRbfHistory$ = new Subject<string>();
|
||||
fetchCachedTx$ = new Subject<string>();
|
||||
isCached: boolean = false;
|
||||
now = new Date().getTime();
|
||||
now = Date.now();
|
||||
timeAvg$: Observable<number>;
|
||||
liquidUnblinding = new LiquidUnblinding();
|
||||
inputIndex: number;
|
||||
@@ -308,7 +309,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.isLoadingTx = false;
|
||||
this.error = undefined;
|
||||
this.waitingForTransaction = false;
|
||||
this.setMempoolBlocksSubscription();
|
||||
this.websocketService.startTrackTransaction(tx.txid);
|
||||
this.graphExpanded = false;
|
||||
this.setupGraph();
|
||||
@@ -391,6 +391,34 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.setFlowEnabled();
|
||||
this.setGraphSize();
|
||||
});
|
||||
|
||||
this.mempoolBlocksSubscription = this.stateService.mempoolBlocks$.subscribe((mempoolBlocks) => {
|
||||
if (!this.tx) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.now = Date.now();
|
||||
|
||||
const txFeePerVSize =
|
||||
this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
|
||||
|
||||
let found = false;
|
||||
this.txInBlockIndex = 0;
|
||||
for (const block of mempoolBlocks) {
|
||||
for (let i = 0; i < block.feeRange.length - 1 && !found; i++) {
|
||||
if (
|
||||
txFeePerVSize <= block.feeRange[i + 1] &&
|
||||
txFeePerVSize >= block.feeRange[i]
|
||||
) {
|
||||
this.txInBlockIndex = mempoolBlocks.indexOf(block);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found && txFeePerVSize < mempoolBlocks[mempoolBlocks.length - 1].feeRange[0]) {
|
||||
this.txInBlockIndex = 7;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
@@ -407,28 +435,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
return of(false);
|
||||
}
|
||||
|
||||
setMempoolBlocksSubscription() {
|
||||
this.stateService.mempoolBlocks$.subscribe((mempoolBlocks) => {
|
||||
if (!this.tx) {
|
||||
return;
|
||||
}
|
||||
|
||||
const txFeePerVSize =
|
||||
this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
|
||||
|
||||
for (const block of mempoolBlocks) {
|
||||
for (let i = 0; i < block.feeRange.length - 1; i++) {
|
||||
if (
|
||||
txFeePerVSize <= block.feeRange[i + 1] &&
|
||||
txFeePerVSize >= block.feeRange[i]
|
||||
) {
|
||||
this.txInBlockIndex = mempoolBlocks.indexOf(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getTransactionTime() {
|
||||
this.apiService
|
||||
.getTransactionTimes$([this.tx.txid])
|
||||
@@ -536,6 +542,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
this.queryParamsSubscription.unsubscribe();
|
||||
this.flowPrefSubscription.unsubscribe();
|
||||
this.urlFragmentSubscription.unsubscribe();
|
||||
this.mempoolBlocksSubscription.unsubscribe();
|
||||
this.leaveTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,9 +184,9 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
onScroll(): void {
|
||||
const scrollHeight = document.body.scrollHeight;
|
||||
const scrollTop = document.documentElement.scrollTop;
|
||||
if (scrollHeight > 0){
|
||||
if (scrollHeight > 0) {
|
||||
const percentageScrolled = scrollTop * 100 / scrollHeight;
|
||||
if (percentageScrolled > 70){
|
||||
if (percentageScrolled > 50) {
|
||||
this.loadMore.emit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3203,6 +3203,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="0c65c3ee0ce537e507e0b053b479012e5803d2cf" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks expected</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> الكتل المتوقعة </target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
@@ -3211,6 +3212,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="ec9f27d00a7778cd1cfe1806105d2ca3314fa506" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block expected</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/>الكتله المتوقعه </target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">14</context>
|
||||
@@ -3219,6 +3221,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="b89cb92adf0a831d4a263ecdba02139abbda02ae" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks mined</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/>الكتل المعدنه </target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">18</context>
|
||||
@@ -3227,6 +3230,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="4f7e823fd45c6def13a3f15f678888c7fe254fa5" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block mined</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/>الكتله المعدنه</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">19</context>
|
||||
@@ -3235,6 +3239,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="229dfb17b342aa8b9a1db27557069445ea1a7051" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks remaining</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/>الكتل المتبقية</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">24</context>
|
||||
@@ -3243,6 +3248,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="13ff0d092caf85cd23815f0235e316dc3a6d1bbe" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block remaining</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/>الكتلة المتبقية</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">25</context>
|
||||
@@ -3251,6 +3257,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="4f78348af343fb64016891d67b53bdab473f9dbf" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks ahead</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/>كتل قادمه</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">29</context>
|
||||
@@ -3259,6 +3266,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="15c5f3475966bf3be381378b046a65849f0f6bb6" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block ahead</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/>كتله قادمه</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">30</context>
|
||||
@@ -3267,6 +3275,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="697b8cb1caaf1729809bc5c065d4dd873810550a" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks behind</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/>كتل وراء</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">34</context>
|
||||
@@ -3275,6 +3284,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="32137887e3f5a25b3a016eb03357f4e363fccb0b" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block behind</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/>كتله وراء</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">35</context>
|
||||
@@ -3283,6 +3293,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="5e78899c9b98f29856ce3c7c265e1344bc7a5a18" datatype="html">
|
||||
<source>Average block time</source>
|
||||
<target>متوسط وقت الكتل</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty.component.html</context>
|
||||
<context context-type="linenumber">42,45</context>
|
||||
@@ -3984,6 +3995,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="312539377512157124" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="i"/> blocks</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="i"/>كتل</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/pool-ranking/pool-ranking.component.ts</context>
|
||||
<context context-type="linenumber">165,163</context>
|
||||
@@ -4003,6 +4015,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="3666195172774554282" datatype="html">
|
||||
<source>Other (<x id="PH" equiv-text="percentage"/>)</source>
|
||||
<target>اخرى (<x id="PH" equiv-text="percentage"/>)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/pool-ranking/pool-ranking.component.ts</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
@@ -4565,6 +4578,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="time-until" datatype="html">
|
||||
<source>In ~<x id="DATE" equiv-text="dateStrings.i18nYear"/></source>
|
||||
<target>في ~ <x id="DATE" equiv-text="dateStrings.i18nYear"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/time/time.component.ts</context>
|
||||
<context context-type="linenumber">126</context>
|
||||
@@ -5348,6 +5362,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="8f2791f5d9656271dd6c385f5ad572716e90f4a2" datatype="html">
|
||||
<source><x id="START_BOLD_TEXT" ctype="x-b" equiv-text="mempool.space merely provides data about the Bitcoin network.</b> It cannot help you with"/>mempool.space merely provides data about the Bitcoin network.<x id="CLOSE_BOLD_TEXT" ctype="x-b" equiv-text="</b>"/> It cannot help you with retrieving funds, confirming your transaction quicker, etc.</source>
|
||||
<target><x id="START_BOLD_TEXT" ctype="x-b" equiv-text="mempool.space merely provides data about the Bitcoin network.</b> It cannot help you with"/>يوفر mempool.space بيانات حول شبكة البتكوين. <x id="CLOSE_BOLD_TEXT" ctype="x-b" equiv-text="</b>"/> لا يمكننا مساعدتك في استرداد الأموال ، وتأكيد معاملتك بشكل أسرع ، وما إلى ذلك.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/docs/api-docs/api-docs.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
@@ -5873,6 +5888,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="96508700250272816" datatype="html">
|
||||
<source>Force closed with penalty</source>
|
||||
<target>أغلقت بالقوة هناك خطآ</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/closing-type/closing-type.component.ts</context>
|
||||
<context context-type="linenumber">28</context>
|
||||
@@ -6006,6 +6022,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="cfcc7201138b0ef9901e9604c35f550e91629295" datatype="html">
|
||||
<source>avg</source>
|
||||
<target>متوسط</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">3,5</context>
|
||||
@@ -6014,6 +6031,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="ba9117dcc11814c44437cf9d7561874ba8b98a2a" datatype="html">
|
||||
<source>med</source>
|
||||
<target>متوسط</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">6,9</context>
|
||||
@@ -6408,6 +6426,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="7254919336112973896" datatype="html">
|
||||
<source>Outgoing Fees</source>
|
||||
<target>الرسوم الصادرة</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node-fee-chart/node-fee-chart.component.ts</context>
|
||||
<context context-type="linenumber">170</context>
|
||||
@@ -6419,6 +6438,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="484887099976974152" datatype="html">
|
||||
<source>Incoming Fees</source>
|
||||
<target>رسوم واردة</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node-fee-chart/node-fee-chart.component.ts</context>
|
||||
<context context-type="linenumber">178</context>
|
||||
@@ -6706,6 +6726,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="599038141003770125" datatype="html">
|
||||
<source>Clearnet and Darknet</source>
|
||||
<target>كليرنت ودارك نت</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts</context>
|
||||
<context context-type="linenumber">164,161</context>
|
||||
@@ -6717,6 +6738,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="1282458597026430784" datatype="html">
|
||||
<source>Clearnet Only (IPv4, IPv6)</source>
|
||||
<target>كليرنت فقط (IPv4 ، IPv6)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts</context>
|
||||
<context context-type="linenumber">185,182</context>
|
||||
@@ -6728,6 +6750,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="2165336009914523952" datatype="html">
|
||||
<source>Darknet Only (Tor, I2P, cjdns)</source>
|
||||
<target>الداركنت فقط (Tor، I2P، cjdns)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts</context>
|
||||
<context context-type="linenumber">206,203</context>
|
||||
@@ -6752,6 +6775,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="5222540403093176126" datatype="html">
|
||||
<source><x id="PH" equiv-text="nodeCount"/> nodes</source>
|
||||
<target><x id="PH" equiv-text="nodeCount"/>العقد</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts</context>
|
||||
<context context-type="linenumber">104,103</context>
|
||||
|
||||
@@ -3235,6 +3235,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="229dfb17b342aa8b9a1db27557069445ea1a7051" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks remaining</source>
|
||||
<target>zbývá <x id="INTERPOLATION" equiv-text="{{ i }}"/> bloků</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">24</context>
|
||||
@@ -3243,6 +3244,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="13ff0d092caf85cd23815f0235e316dc3a6d1bbe" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block remaining</source>
|
||||
<target>zbývá <x id="INTERPOLATION" equiv-text="{{ i }}"/> blok</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">25</context>
|
||||
@@ -3251,6 +3253,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="4f78348af343fb64016891d67b53bdab473f9dbf" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks ahead</source>
|
||||
<target>za <x id="INTERPOLATION" equiv-text="{{ i }}"/> bloků</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">29</context>
|
||||
@@ -3259,6 +3262,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="15c5f3475966bf3be381378b046a65849f0f6bb6" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block ahead</source>
|
||||
<target>za <x id="INTERPOLATION" equiv-text="{{ i }}"/> blok</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">30</context>
|
||||
@@ -3267,6 +3271,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="697b8cb1caaf1729809bc5c065d4dd873810550a" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks behind</source>
|
||||
<target>Před <x id="INTERPOLATION" equiv-text="{{ i }}"/> bloků</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">34</context>
|
||||
@@ -3275,6 +3280,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="32137887e3f5a25b3a016eb03357f4e363fccb0b" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block behind</source>
|
||||
<target>Před <x id="INTERPOLATION" equiv-text="{{ i }}"/> blok</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">35</context>
|
||||
@@ -3283,6 +3289,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="5e78899c9b98f29856ce3c7c265e1344bc7a5a18" datatype="html">
|
||||
<source>Average block time</source>
|
||||
<target>Průměrná perioda bloků</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty.component.html</context>
|
||||
<context context-type="linenumber">42,45</context>
|
||||
@@ -3984,6 +3991,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="312539377512157124" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="i"/> blocks</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="i"/> bloků</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/pool-ranking/pool-ranking.component.ts</context>
|
||||
<context context-type="linenumber">165,163</context>
|
||||
@@ -4505,7 +4513,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="time-since" datatype="html">
|
||||
<source><x id="DATE" equiv-text="dateStrings.i18nYear"/> ago</source>
|
||||
<target>před <x id="DATE" equiv-text="dateStrings.i18nYear"/></target>
|
||||
<target>Před <x id="DATE" equiv-text="dateStrings.i18nYear"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/time/time.component.ts</context>
|
||||
<context context-type="linenumber">103</context>
|
||||
@@ -7104,7 +7112,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="date-base.minute" datatype="html">
|
||||
<source><x id="DATE" equiv-text="counter"/> minute</source>
|
||||
<target><x id="DATE" equiv-text="counter"/> minuta</target>
|
||||
<target><x id="DATE" equiv-text="counter"/> min</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/shared/i18n/dates.ts</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
@@ -7112,7 +7120,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="date-base.minutes" datatype="html">
|
||||
<source><x id="DATE" equiv-text="counter"/> minutes</source>
|
||||
<target><x id="DATE" equiv-text="counter"/> minuty</target>
|
||||
<target><x id="DATE" equiv-text="counter"/> min</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/shared/i18n/dates.ts</context>
|
||||
<context context-type="linenumber">14</context>
|
||||
|
||||
@@ -3203,6 +3203,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="0c65c3ee0ce537e507e0b053b479012e5803d2cf" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks expected</source>
|
||||
<target> <x id="INTERPOLATION" equiv-text="{{ i }}"/> blokke forventes</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
@@ -3211,6 +3212,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="ec9f27d00a7778cd1cfe1806105d2ca3314fa506" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block expected</source>
|
||||
<target> <x id="INTERPOLATION" equiv-text="{{ i }}"/> blok forventet</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">14</context>
|
||||
@@ -3219,6 +3221,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="b89cb92adf0a831d4a263ecdba02139abbda02ae" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks mined</source>
|
||||
<target> <x id="INTERPOLATION" equiv-text="{{ i }}"/> blokke fundet</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">18</context>
|
||||
@@ -3227,6 +3230,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="4f7e823fd45c6def13a3f15f678888c7fe254fa5" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block mined</source>
|
||||
<target> <x id="INTERPOLATION" equiv-text="{{ i }}"/> blok fundet</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">19</context>
|
||||
@@ -3235,6 +3239,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="229dfb17b342aa8b9a1db27557069445ea1a7051" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks remaining</source>
|
||||
<target> <x id="INTERPOLATION" equiv-text="{{ i }}"/> blokke tilbage</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">24</context>
|
||||
@@ -3243,6 +3248,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="13ff0d092caf85cd23815f0235e316dc3a6d1bbe" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block remaining</source>
|
||||
<target> <x id="INTERPOLATION" equiv-text="{{ i }}"/> blok tilbage</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">25</context>
|
||||
@@ -3251,6 +3257,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="4f78348af343fb64016891d67b53bdab473f9dbf" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks ahead</source>
|
||||
<target> <x id="INTERPOLATION" equiv-text="{{ i }}"/> blokke foran</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">29</context>
|
||||
@@ -3259,6 +3266,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="15c5f3475966bf3be381378b046a65849f0f6bb6" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block ahead</source>
|
||||
<target> <x id="INTERPOLATION" equiv-text="{{ i }}"/> blok foran</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">30</context>
|
||||
@@ -3267,6 +3275,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="697b8cb1caaf1729809bc5c065d4dd873810550a" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks behind</source>
|
||||
<target> <x id="INTERPOLATION" equiv-text="{{ i }}"/> blokke bagved</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">34</context>
|
||||
@@ -3275,6 +3284,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="32137887e3f5a25b3a016eb03357f4e363fccb0b" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block behind</source>
|
||||
<target> <x id="INTERPOLATION" equiv-text="{{ i }}"/> blok bagved</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">35</context>
|
||||
|
||||
@@ -432,7 +432,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="07193288a0312875e18f38c3a2486b927a15520a" datatype="html">
|
||||
<source>Timestamp</source>
|
||||
<target>Sello de tiempo</target>
|
||||
<target>Timestamp</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/bisq/bisq-block/bisq-block.component.html</context>
|
||||
<context context-type="linenumber">23</context>
|
||||
@@ -1440,7 +1440,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="4b137ec8bf73a47063740b75c0c40d5fd3c48015" datatype="html">
|
||||
<source>The Mempool Open Source Project</source>
|
||||
<target>Proyecto de Código Abierto The Mempool</target>
|
||||
<target>El proyecto de código abierto Mempool</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/about/about.component.html</context>
|
||||
<context context-type="linenumber">12,13</context>
|
||||
@@ -3355,7 +3355,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="ee847b69ef2dc81bb3e9b8cd30f02f8d63adbe07" datatype="html">
|
||||
<source>Medium Priority</source>
|
||||
<target>Prioridad media</target>
|
||||
<target>Media prioridad</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/fees-box/fees-box.component.html</context>
|
||||
<context context-type="linenumber">9,10</context>
|
||||
|
||||
@@ -3549,7 +3549,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="8573a1576789bd2c4faeaed23037c4917812c6cf" datatype="html">
|
||||
<source>Lightning Nodes Per ISP</source>
|
||||
<target>צמתי ברק לפי ספקי תשתית</target>
|
||||
<target>צמתי ברק לפי ספקיות</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">37</context>
|
||||
@@ -3717,6 +3717,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="142e923d3b04186ac6ba23387265d22a2fa404e0" datatype="html">
|
||||
<source>Lightning Explorer</source>
|
||||
<target>חוקר רשת הברק</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/master-page/master-page.component.html</context>
|
||||
<context context-type="linenumber">44,47</context>
|
||||
@@ -4370,6 +4371,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="7deec1c1520f06170e1f8e8ddfbe4532312f638f" datatype="html">
|
||||
<source>Explore the full Bitcoin ecosystem</source>
|
||||
<target>חקור את מרחב רשתות הביטקוין</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/search-form/search-form.component.html</context>
|
||||
<context context-type="linenumber">4,5</context>
|
||||
@@ -5373,6 +5375,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="cd2330c7e9c74256f6a91e83bccf10e2905f8556" datatype="html">
|
||||
<source>REST API service</source>
|
||||
<target>שירותי API</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/docs/api-docs/api-docs.component.html</context>
|
||||
<context context-type="linenumber">42,43</context>
|
||||
@@ -5491,6 +5494,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="6acd06bd5a3af583cd46c6d9f7954d7a2b44095e" datatype="html">
|
||||
<source>mSats</source>
|
||||
<target>מיליסאטס</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">35</context>
|
||||
@@ -5547,6 +5551,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="055060668d0b9902c37abfb6168a08a36eba4496" datatype="html">
|
||||
<source>Min HTLC</source>
|
||||
<target>HTLC מינימלי</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">57</context>
|
||||
@@ -5555,6 +5560,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="c3d94c1a5aef6211f4a902027bd08540d7222b0d" datatype="html">
|
||||
<source>Max HTLC</source>
|
||||
<target>HTLC מקסימלי</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">63</context>
|
||||
@@ -5751,6 +5757,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="8fd0077b032e360ece45c4fd655f85b2400dcb83" datatype="html">
|
||||
<source>ppm</source>
|
||||
<target>פפ"מ</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-preview.component.html</context>
|
||||
<context context-type="linenumber">34,35</context>
|
||||
@@ -6059,6 +6066,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="db1f0c0605ab0c4a904523635982253ff72eed40" datatype="html">
|
||||
<source>The average fee rate charged by routing nodes, ignoring fee rates > 0.5% or 5000ppm</source>
|
||||
<target>ממוצע העמלות שנגבה בידי צמתי ניתוב, בהתעלמות מתעריף העמלה > 0.5% או 5000 פפ"מ</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">28,30</context>
|
||||
@@ -6080,6 +6088,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="0a46218f4a7b17b6445460898d75ab78e7e7979b" datatype="html">
|
||||
<source>The average base fee charged by routing nodes, ignoring base fees > 5000ppm</source>
|
||||
<target>ממוצע העמלות שנגבה בידי צמתי ניתוב, בהתעלמות מתעריף העמלה > 5000 פפ"מ</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">43,45</context>
|
||||
@@ -6106,6 +6115,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="cb4dae32e1b4d6a2ba6287d9f7bd859ca7259468" datatype="html">
|
||||
<source>The median fee rate charged by routing nodes, ignoring fee rates > 0.5% or 5000ppm</source>
|
||||
<target>חציון העמלות שנגבה בידי צמתי ניתוב, בהתעלמות תעריף העמלה > 0.5% או 5000 פפ"מ</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">74,76</context>
|
||||
@@ -6123,6 +6133,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="b8539025268617abfcab1c3f2a2c60cd8d7485fb" datatype="html">
|
||||
<source>The median base fee charged by routing nodes, ignoring base fees > 5000ppm</source>
|
||||
<target>חציון העמלות שנגבה על ידי צמתי ניתוב, בהתעלמות מבסיס העמלה > 5000 פפ"מ</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">89,91</context>
|
||||
@@ -6368,6 +6379,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="2d9883d230a47fbbb2ec969e32a186597ea27405" datatype="html">
|
||||
<source>Liquidity Ranking</source>
|
||||
<target>דירוג נזילות</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/lightning-dashboard/lightning-dashboard.component.html</context>
|
||||
<context context-type="linenumber">62</context>
|
||||
@@ -6392,6 +6404,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="c50bf442cf99f6fc5f8b687c460f33234b879869" datatype="html">
|
||||
<source>Connectivity Ranking</source>
|
||||
<target>דירוג חיבוריות</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/lightning-dashboard/lightning-dashboard.component.html</context>
|
||||
<context context-type="linenumber">76</context>
|
||||
@@ -6533,7 +6546,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="5b9904cb31f6f28314443f6385dc5facab7ea851" datatype="html">
|
||||
<source>ISP</source>
|
||||
<target>ספק אינטרנט</target>
|
||||
<target>רשת גלויה</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">89,90</context>
|
||||
@@ -6555,6 +6568,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="e128630e07a4c467f51b246a31c5734d5fb1a2c2" datatype="html">
|
||||
<source>Liquidity ad</source>
|
||||
<target>פרסומת ליקווידיטי</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">141,144</context>
|
||||
@@ -6563,6 +6577,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="bc84b5a9a70217104a53c7139e30b392be6520b7" datatype="html">
|
||||
<source>Lease fee rate</source>
|
||||
<target>שיעור עמלת שכירות</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">147,150</context>
|
||||
@@ -6572,6 +6587,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="ee807dd54b4a45eeba284744c64774de1ab5e4f1" datatype="html">
|
||||
<source>Lease base fee</source>
|
||||
<target>עמלת בסיס לשכירות</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">155,157</context>
|
||||
@@ -6608,6 +6624,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="e8e09fa12864e94f094a2a7c8c97cfdf0cff8aab" datatype="html">
|
||||
<source>Compact lease</source>
|
||||
<target>שכירות קומפקטית</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">191,193</context>
|
||||
@@ -6616,6 +6633,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="aa687f4987e2d4e0010be692d402174962ece70e" datatype="html">
|
||||
<source>TLV extension records</source>
|
||||
<target>תיעוד TLV מורחב</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">202,205</context>
|
||||
@@ -6671,6 +6689,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="8199511328474154549" datatype="html">
|
||||
<source>Lightning Nodes Channels World Map</source>
|
||||
<target>מפת ערוצי רשת הברק העולמית</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts</context>
|
||||
<context context-type="linenumber">69</context>
|
||||
@@ -6793,6 +6812,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="4498ec29c37744fef46809ebc3db67c5fb789917" datatype="html">
|
||||
<source>ISP Count</source>
|
||||
<target>כמות ספקיות</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-country/nodes-per-country.component.html</context>
|
||||
<context context-type="linenumber">34,38</context>
|
||||
@@ -6801,6 +6821,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="90a6a964ba53464578003e3b4b2873ef5d2132a1" datatype="html">
|
||||
<source>Top ISP</source>
|
||||
<target>ספקיות מובילות</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-country/nodes-per-country.component.html</context>
|
||||
<context context-type="linenumber">38,40</context>
|
||||
@@ -6830,6 +6851,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="ccabb31683868066778a1d664aa53ee9fcf77d6b" datatype="html">
|
||||
<source>How much liquidity is running on nodes advertising at least one clearnet IP address</source>
|
||||
<target>כמה ליקווידיטי רץ על צמתים המפרסמים לפחות כתובת IP גלויה אחת</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html</context>
|
||||
<context context-type="linenumber">8,9</context>
|
||||
@@ -6851,6 +6873,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="26fb07e8754b87bba4bf12c5137ffa77dac389a8" datatype="html">
|
||||
<source>How much liquidity is running on nodes which ISP was not identifiable</source>
|
||||
<target>כמה ליקווידיטי רץ על צמתים ללא ספקית רשת מזוהה</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html</context>
|
||||
<context context-type="linenumber">15,16</context>
|
||||
@@ -6872,6 +6895,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="23549ef4e1f846f06abcf07ceecb115945a0cf61" datatype="html">
|
||||
<source>How much liquidity is running on nodes advertising only Tor addresses</source>
|
||||
<target>כמה ליקווידיטי רץ על צמתים המפרסמים כתובות תור בלבד</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html</context>
|
||||
<context context-type="linenumber">22,23</context>
|
||||
@@ -6880,6 +6904,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="e008f2a76179fdcd7110b41ca624131f91075949" datatype="html">
|
||||
<source>Top 100 ISPs hosting LN nodes</source>
|
||||
<target>100 הספקיות המובילות באיחסון צמתי רשת הברק</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html</context>
|
||||
<context context-type="linenumber">31,33</context>
|
||||
@@ -6900,6 +6925,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="c18497e4f0db0d0ad0c71ba294295f42b3d312c9" datatype="html">
|
||||
<source>Lightning ISP</source>
|
||||
<target>ספקיות ברק</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.html</context>
|
||||
<context context-type="linenumber">3,5</context>
|
||||
@@ -6930,6 +6956,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="5735693498020397727" datatype="html">
|
||||
<source>Lightning nodes on ISP: <x id="PH" equiv-text="response.isp"/> [AS<x id="PH_1" equiv-text="this.route.snapshot.params.isp"/>]</source>
|
||||
<target>צמתי ברק אצל ספקיות: <x id="PH" equiv-text="response.isp"/> [כ <x id="PH_1" equiv-text="this.route.snapshot.params.isp"/>]</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.ts</context>
|
||||
<context context-type="linenumber">44</context>
|
||||
@@ -6941,6 +6968,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="d82f436f033a7d81680b8430275f94dda530151c" datatype="html">
|
||||
<source>Lightning nodes on ISP: <x id="INTERPOLATION" equiv-text="{{ isp?.name }}"/></source>
|
||||
<target>צמתי ברק ברשת הגלויה: <x id="INTERPOLATION" equiv-text="{{ isp?.name }}"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp/nodes-per-isp.component.html</context>
|
||||
<context context-type="linenumber">2,4</context>
|
||||
@@ -6949,6 +6977,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="ca0b795795658155d44ddca02e95f1feeeb4a88f" datatype="html">
|
||||
<source>ASN</source>
|
||||
<target>ASN</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp/nodes-per-isp.component.html</context>
|
||||
<context context-type="linenumber">11,14</context>
|
||||
|
||||
@@ -3203,6 +3203,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="0c65c3ee0ce537e507e0b053b479012e5803d2cf" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks expected</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> blokkra várva</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
@@ -3211,6 +3212,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="ec9f27d00a7778cd1cfe1806105d2ca3314fa506" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block expected</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> blokk várva</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">14</context>
|
||||
@@ -3219,6 +3221,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="b89cb92adf0a831d4a263ecdba02139abbda02ae" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks mined</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> blokk bányászva</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">18</context>
|
||||
@@ -3227,6 +3230,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="4f7e823fd45c6def13a3f15f678888c7fe254fa5" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block mined</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> blokk bányászva</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">19</context>
|
||||
@@ -3667,6 +3671,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="5ee5eb7db86675abd5f0b0db835bf362ee9b23ff" datatype="html">
|
||||
<source>Indexing network hashrate</source>
|
||||
<target>Hálózati hashrate indexelése</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/indexing-progress/indexing-progress.component.html</context>
|
||||
<context context-type="linenumber">2</context>
|
||||
@@ -3881,6 +3886,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="500e13dffc7300bf7e4822a6bbf29a71a55d7b75" datatype="html">
|
||||
<source>How many unique pools found at least one block over the past week.</source>
|
||||
<target>Hány egyedi pool talált legalább egy blokkot az elmúlt héten.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/pool-ranking/pool-ranking.component.html</context>
|
||||
<context context-type="linenumber">19,23</context>
|
||||
@@ -3906,6 +3912,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="c9e8defa185fa8e342548958bf206de97afc97a6" datatype="html">
|
||||
<source>The number of blocks found over the past week.</source>
|
||||
<target>Az elmúlt héten talált blokkok száma.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/pool-ranking/pool-ranking.component.html</context>
|
||||
<context context-type="linenumber">27,31</context>
|
||||
@@ -4278,6 +4285,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="79b0842a2010172290ad09062bf51f09d8842f65" datatype="html">
|
||||
<source>Amount being paid to miners in the past 144 blocks</source>
|
||||
<target>A bányászoknak fizetett összeg az elmúlt 144 blokkban</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/reward-stats/reward-stats.component.html</context>
|
||||
<context context-type="linenumber">6,8</context>
|
||||
@@ -4299,6 +4307,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="a0d4ab5b063e7be1c9ea980f5fd6ce1b5384ad0b" datatype="html">
|
||||
<source>Average fees per block in the past 144 blocks</source>
|
||||
<target>Átlagos díjak blokkonként az elmúlt 144 blokkban</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/reward-stats/reward-stats.component.html</context>
|
||||
<context context-type="linenumber">18,20</context>
|
||||
@@ -5049,6 +5058,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="d70397ee91f6c9ec91f1c1dff88126f8f9b7c2c4" datatype="html">
|
||||
<source>Show more inputs to reveal fee data</source>
|
||||
<target>További bemenetek megjelenítése a díjadatok megjelenítéséhez</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/transactions-list/transactions-list.component.html</context>
|
||||
<context context-type="linenumber">290,293</context>
|
||||
@@ -5110,6 +5120,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="25d58cd5c18fd9c1c89d6062d67dcc2482161410" datatype="html">
|
||||
<source>This transaction saved <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedSegwitGains * 100 | number: '1.0-0' }}"/>% on fees by using native SegWit</source>
|
||||
<target>Ez a tranzakció <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedSegwitGains * 100 | number: '1.0-0' }}"/>% -ot takarított meg a díjakból a natív SegWit használatával</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-features/tx-features.component.html</context>
|
||||
<context context-type="linenumber">2</context>
|
||||
@@ -5136,6 +5147,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="b6a3f6afdac6873e2d261647d834c02c91376893" datatype="html">
|
||||
<source>This transaction saved <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedSegwitGains * 100 | number: '1.0-0' }}"/>% on fees by using SegWit and could save <x id="INTERPOLATION_1" equiv-text="{{ segwitGains.potentialSegwitGains * 100 | number : '1.0-0' }}"/>% more by fully upgrading to native SegWit</source>
|
||||
<target>Ez a tranzakció <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedSegwitGains * 100 | number: '1.0-0' }}"/>% -os díjakat takarított meg a SegWit használatával, és további <x id="INTERPOLATION_1" equiv-text="{{ segwitGains.potentialSegwitGains * 100 | number : '1.0-0' }}"/>% -os megtakarítást érhetett el, ha teljes mértékben vált a natív SegWitre</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-features/tx-features.component.html</context>
|
||||
<context context-type="linenumber">4</context>
|
||||
@@ -5144,6 +5156,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="a67530e246368aa7e5d010061fd84c3c4fe755c2" datatype="html">
|
||||
<source>This transaction could save <x id="INTERPOLATION" equiv-text="{{ segwitGains.potentialSegwitGains * 100 | number : '1.0-0' }}"/>% on fees by upgrading to native SegWit or <x id="INTERPOLATION_1" equiv-text="{{ segwitGains.potentialP2shSegwitGains * 100 | number: '1.0-0' }}"/>% by upgrading to SegWit-P2SH</source>
|
||||
<target>Ez a tranzakció <x id="INTERPOLATION" equiv-text="{{ segwitGains.potentialSegwitGains * 100 | number : '1.0-0' }}"/>% -ot takaríthat meg a díjakból, ha natív SegWitre vált, vagy <x id="INTERPOLATION_1" equiv-text="{{ segwitGains.potentialP2shSegwitGains * 100 | number: '1.0-0' }}"/>% -ot, ha SegWit-P2SH-ra frissít</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-features/tx-features.component.html</context>
|
||||
<context context-type="linenumber">6</context>
|
||||
@@ -5152,6 +5165,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="17e9c05e053cbd29d3835d8ecb19508d0f07241b" datatype="html">
|
||||
<source>This transaction uses Taproot and thereby saved at least <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedTaprootGains * 100 | number: '1.0-0' }}"/>% on fees</source>
|
||||
<target>Ez a tranzakció Taprootot használ, és ezáltal legalább <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedTaprootGains * 100 | number: '1.0-0' }}"/>% -ot takarít meg a díjakon</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-features/tx-features.component.html</context>
|
||||
<context context-type="linenumber">12</context>
|
||||
@@ -5186,6 +5200,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="47b821c7df420c96de0b22844a88c04d52628540" datatype="html">
|
||||
<source>This transaction uses Taproot and already saved at least <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedTaprootGains * 100 | number: '1.0-0' }}"/>% on fees, but could save an additional <x id="INTERPOLATION_1" equiv-text="{{ segwitGains.potentialTaprootGains * 100 | number: '1.0-0' }}"/>% by fully using Taproot</source>
|
||||
<target>Ez a tranzakció Taprootot használ, és már legalább <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedTaprootGains * 100 | number: '1.0-0' }}"/>% -ot megtakarított a díjakból, de további <x id="INTERPOLATION_1" equiv-text="{{ segwitGains.potentialTaprootGains * 100 | number: '1.0-0' }}"/>% -ot takaríthat meg a Taproot teljes használatával</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-features/tx-features.component.html</context>
|
||||
<context context-type="linenumber">14</context>
|
||||
@@ -5194,6 +5209,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="aa31fc4d29f35b2fd36080bb6ff84be8eaab66fd" datatype="html">
|
||||
<source>This transaction could save <x id="INTERPOLATION" equiv-text="{{ segwitGains.potentialTaprootGains * 100 | number: '1.0-0' }}"/>% on fees by using Taproot</source>
|
||||
<target>Ez a tranzakció <x id="INTERPOLATION" equiv-text="{{ segwitGains.potentialTaprootGains * 100 | number: '1.0-0' }}"/>% -ot takaríthat meg a díjakból a Taproot használatával</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-features/tx-features.component.html</context>
|
||||
<context context-type="linenumber">16</context>
|
||||
@@ -5220,6 +5236,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="07883574bb93ea23b764861f56a525bdaf907513" datatype="html">
|
||||
<source>This transaction supports Replace-By-Fee (RBF) allowing fee bumping</source>
|
||||
<target>Ez a tranzakció támogatja a Replace-By-Fee (RBF) funkciót, amely lehetővé teszi a díjak felfutását</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-features/tx-features.component.html</context>
|
||||
<context context-type="linenumber">28</context>
|
||||
@@ -5345,6 +5362,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="8f2791f5d9656271dd6c385f5ad572716e90f4a2" datatype="html">
|
||||
<source><x id="START_BOLD_TEXT" ctype="x-b" equiv-text="mempool.space merely provides data about the Bitcoin network.</b> It cannot help you with"/>mempool.space merely provides data about the Bitcoin network.<x id="CLOSE_BOLD_TEXT" ctype="x-b" equiv-text="</b>"/> It cannot help you with retrieving funds, confirming your transaction quicker, etc.</source>
|
||||
<target><x id="START_BOLD_TEXT" ctype="x-b" equiv-text="mempool.space merely provides data about the Bitcoin network.</b> It cannot help you with"/>A mempool.space csupán adatokat szolgáltat a Bitcoin hálózatáról.<x id="CLOSE_BOLD_TEXT" ctype="x-b" equiv-text="</b>"/> Nem segíthet a pénzeszközök visszaszerzésében, a tranzakció gyorsabb visszaigazolásában stb.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/docs/api-docs/api-docs.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
@@ -5497,6 +5515,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="fb2137ba0df55f21a9d6b6ad08d56d74ad852e0e" datatype="html">
|
||||
<source>This channel supports zero base fee routing</source>
|
||||
<target>Ez a csatorna támogatja a nulla alapdíjas útválasztást</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">44</context>
|
||||
@@ -5514,6 +5533,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="b5e42e06ea8a4012a38eef209104bbd9dd1a0fc0" datatype="html">
|
||||
<source>This channel does not support zero base fee routing</source>
|
||||
<target>Ez a csatorna nem támogatja a nulla alapdíjas útválasztást</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">50</context>
|
||||
@@ -6046,6 +6066,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="db1f0c0605ab0c4a904523635982253ff72eed40" datatype="html">
|
||||
<source>The average fee rate charged by routing nodes, ignoring fee rates > 0.5% or 5000ppm</source>
|
||||
<target>Az útválasztási nódok által felszámított átlagos díjtétel, figyelmen kívül hagyva a > 0,5% -os vagy 5000ppm-es díjakat</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">28,30</context>
|
||||
@@ -6067,6 +6088,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="0a46218f4a7b17b6445460898d75ab78e7e7979b" datatype="html">
|
||||
<source>The average base fee charged by routing nodes, ignoring base fees > 5000ppm</source>
|
||||
<target>Az útválasztási nódok által felszámított átlagos alapdíj, figyelmen kívül hagyva az alapdíjakat > 5000ppm</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">43,45</context>
|
||||
@@ -6093,6 +6115,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="cb4dae32e1b4d6a2ba6287d9f7bd859ca7259468" datatype="html">
|
||||
<source>The median fee rate charged by routing nodes, ignoring fee rates > 0.5% or 5000ppm</source>
|
||||
<target>Az útválasztási nódok által felszámított medián díjtétel, figyelmen kívül hagyva a > 0,5% vagy 5000 ppm díjakat</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">74,76</context>
|
||||
@@ -6110,6 +6133,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="b8539025268617abfcab1c3f2a2c60cd8d7485fb" datatype="html">
|
||||
<source>The median base fee charged by routing nodes, ignoring base fees > 5000ppm</source>
|
||||
<target>Az útválasztási nódok által felszámított medián alapdíj, figyelmen kívül hagyva a > 5000ppm alapdíjakat</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">89,91</context>
|
||||
@@ -6426,6 +6450,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="027f48063a5512e5c26b6ca88f7d7734e2d333a7" datatype="html">
|
||||
<source>Percentage change past week</source>
|
||||
<target>Százalékos változás a múlt héten</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node-statistics/node-statistics.component.html</context>
|
||||
<context context-type="linenumber">5,7</context>
|
||||
@@ -6543,6 +6568,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="e128630e07a4c467f51b246a31c5734d5fb1a2c2" datatype="html">
|
||||
<source>Liquidity ad</source>
|
||||
<target>Likviditási hirdetés</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">141,144</context>
|
||||
@@ -6570,6 +6596,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="5e348f3d51c3bb283c16572bee1e293ea991cf49" datatype="html">
|
||||
<source>Funding weight</source>
|
||||
<target>A finanszírozás súlya</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">161,162</context>
|
||||
@@ -6824,6 +6851,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="ccabb31683868066778a1d664aa53ee9fcf77d6b" datatype="html">
|
||||
<source>How much liquidity is running on nodes advertising at least one clearnet IP address</source>
|
||||
<target>Mennyi likviditás fut a legalább egy clearnet IP-címet hirdető nódokon</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html</context>
|
||||
<context context-type="linenumber">8,9</context>
|
||||
@@ -6845,6 +6873,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="26fb07e8754b87bba4bf12c5137ffa77dac389a8" datatype="html">
|
||||
<source>How much liquidity is running on nodes which ISP was not identifiable</source>
|
||||
<target>Mennyi likviditás fut azon nódokon, amelyek internetszolgáltatója nem volt azonosítható</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html</context>
|
||||
<context context-type="linenumber">15,16</context>
|
||||
@@ -6866,6 +6895,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="23549ef4e1f846f06abcf07ceecb115945a0cf61" datatype="html">
|
||||
<source>How much liquidity is running on nodes advertising only Tor addresses</source>
|
||||
<target>Mennyi likviditás fut a csak Tor-címeket hirdető nódokon</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html</context>
|
||||
<context context-type="linenumber">22,23</context>
|
||||
@@ -6874,6 +6904,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="e008f2a76179fdcd7110b41ca624131f91075949" datatype="html">
|
||||
<source>Top 100 ISPs hosting LN nodes</source>
|
||||
<target>A 100 legjobb LN-nódot szolgáltató internetszolgáltató</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.html</context>
|
||||
<context context-type="linenumber">31,33</context>
|
||||
@@ -6925,6 +6956,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="5735693498020397727" datatype="html">
|
||||
<source>Lightning nodes on ISP: <x id="PH" equiv-text="response.isp"/> [AS<x id="PH_1" equiv-text="this.route.snapshot.params.isp"/>]</source>
|
||||
<target>Villám nódok az internetszolgáltatón: <x id="PH" equiv-text="response.isp"/> [MINT<x id="PH_1" equiv-text="this.route.snapshot.params.isp"/>]</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp/nodes-per-isp-preview.component.ts</context>
|
||||
<context context-type="linenumber">44</context>
|
||||
@@ -6936,6 +6968,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="d82f436f033a7d81680b8430275f94dda530151c" datatype="html">
|
||||
<source>Lightning nodes on ISP: <x id="INTERPOLATION" equiv-text="{{ isp?.name }}"/></source>
|
||||
<target>Villámnódok az internetszolgáltatón: <x id="INTERPOLATION" equiv-text="{{ isp?.name }}"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp/nodes-per-isp.component.html</context>
|
||||
<context context-type="linenumber">2,4</context>
|
||||
|
||||
@@ -3775,7 +3775,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="26e78cd052d05a0c1a7db43fac8df52ec6950672" datatype="html">
|
||||
<source>Reward stats</source>
|
||||
<target>Belonnings-statistikk</target>
|
||||
<target>Belønnings-statistikk</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/mining-dashboard/mining-dashboard.component.html</context>
|
||||
<context context-type="linenumber">9</context>
|
||||
@@ -3850,7 +3850,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="ea1a87734b5cc78ea8b268343497d92136855cd1" datatype="html">
|
||||
<source>Pools luck</source>
|
||||
<target>Utvinningsgrupper flaks</target>
|
||||
<target>Flaks</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/pool-ranking/pool-ranking.component.html</context>
|
||||
<context context-type="linenumber">9,11</context>
|
||||
@@ -3877,7 +3877,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="1107f1b39cd8474087d438971892967a331a6c7d" datatype="html">
|
||||
<source>Pools count</source>
|
||||
<target>Antall utvinningsgrupper</target>
|
||||
<target>Antall grupper</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/pool-ranking/pool-ranking.component.html</context>
|
||||
<context context-type="linenumber">17,19</context>
|
||||
@@ -4294,7 +4294,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="1d9f405ab98a5f79d98b439de29fc8baca46b97c" datatype="html">
|
||||
<source>Avg Block Fees</source>
|
||||
<target>Gjennomsnittlig blokkavgift</target>
|
||||
<target>Gj.sn. blokkavgift</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/reward-stats/reward-stats.component.html</context>
|
||||
<context context-type="linenumber">17</context>
|
||||
|
||||
@@ -3203,6 +3203,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="0c65c3ee0ce537e507e0b053b479012e5803d2cf" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks expected</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> blokken verwacht</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
@@ -3211,6 +3212,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="ec9f27d00a7778cd1cfe1806105d2ca3314fa506" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block expected</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> blok verwacht</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">14</context>
|
||||
@@ -3219,6 +3221,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="b89cb92adf0a831d4a263ecdba02139abbda02ae" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks mined</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> blokken gemined</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">18</context>
|
||||
@@ -3227,6 +3230,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="4f7e823fd45c6def13a3f15f678888c7fe254fa5" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block mined</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> blok gemined</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">19</context>
|
||||
@@ -3235,6 +3239,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="229dfb17b342aa8b9a1db27557069445ea1a7051" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks remaining</source>
|
||||
<target>Nog <x id="INTERPOLATION" equiv-text="{{ i }}"/> blokken over </target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">24</context>
|
||||
@@ -3243,6 +3248,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="13ff0d092caf85cd23815f0235e316dc3a6d1bbe" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block remaining</source>
|
||||
<target>Nog <x id="INTERPOLATION" equiv-text="{{ i }}"/> blok over</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">25</context>
|
||||
@@ -3251,6 +3257,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="4f78348af343fb64016891d67b53bdab473f9dbf" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks ahead</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> blokken verder</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">29</context>
|
||||
@@ -3259,6 +3266,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="15c5f3475966bf3be381378b046a65849f0f6bb6" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block ahead</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> blok verder</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">30</context>
|
||||
@@ -3267,6 +3275,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="697b8cb1caaf1729809bc5c065d4dd873810550a" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> blocks behind</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> blokken achter</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">34</context>
|
||||
@@ -3275,6 +3284,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="32137887e3f5a25b3a016eb03357f4e363fccb0b" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> block behind</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="{{ i }}"/> blok achter</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty-tooltip.component.html</context>
|
||||
<context context-type="linenumber">35</context>
|
||||
@@ -3283,6 +3293,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="5e78899c9b98f29856ce3c7c265e1344bc7a5a18" datatype="html">
|
||||
<source>Average block time</source>
|
||||
<target>Gemiddelde bloktijd</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty.component.html</context>
|
||||
<context context-type="linenumber">42,45</context>
|
||||
@@ -3984,6 +3995,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="312539377512157124" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="i"/> blocks</source>
|
||||
<target><x id="INTERPOLATION" equiv-text="i"/> blokken</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/pool-ranking/pool-ranking.component.ts</context>
|
||||
<context context-type="linenumber">165,163</context>
|
||||
@@ -4003,6 +4015,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="3666195172774554282" datatype="html">
|
||||
<source>Other (<x id="PH" equiv-text="percentage"/>)</source>
|
||||
<target>Ander (<x id="PH" equiv-text="percentage"/>)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/pool-ranking/pool-ranking.component.ts</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
@@ -6713,6 +6726,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="599038141003770125" datatype="html">
|
||||
<source>Clearnet and Darknet</source>
|
||||
<target>Clearnet en Darknet</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts</context>
|
||||
<context context-type="linenumber">164,161</context>
|
||||
@@ -6724,6 +6738,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="1282458597026430784" datatype="html">
|
||||
<source>Clearnet Only (IPv4, IPv6)</source>
|
||||
<target>Alleen Clearnet (IPv4, IPv6)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts</context>
|
||||
<context context-type="linenumber">185,182</context>
|
||||
@@ -6735,6 +6750,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="2165336009914523952" datatype="html">
|
||||
<source>Darknet Only (Tor, I2P, cjdns)</source>
|
||||
<target>Alleen Darknet (Tor, I2P, cjdns)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts</context>
|
||||
<context context-type="linenumber">206,203</context>
|
||||
@@ -6759,6 +6775,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="5222540403093176126" datatype="html">
|
||||
<source><x id="PH" equiv-text="nodeCount"/> nodes</source>
|
||||
<target><x id="PH" equiv-text="nodeCount"/> nodes</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts</context>
|
||||
<context context-type="linenumber">104,103</context>
|
||||
|
||||
@@ -4452,7 +4452,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="2abc4d0d3ae0b49fa9e94a2efb8c2e1a47e680f4" datatype="html">
|
||||
<source>Go to "<x id="INTERPOLATION" equiv-text="{{ x }}"/>"</source>
|
||||
<target>"<x id="INTERPOLATION" equiv-text="{{ x }}"/>" 'a git</target>
|
||||
<target>"<x id="INTERPOLATION" equiv-text="{{ x }}"/>" 'e git</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/search-form/search-results/search-results.component.html</context>
|
||||
<context context-type="linenumber">52</context>
|
||||
|
||||
@@ -3283,6 +3283,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="5e78899c9b98f29856ce3c7c265e1344bc7a5a18" datatype="html">
|
||||
<source>Average block time</source>
|
||||
<target>Thời gian khối trung bình</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/difficulty/difficulty.component.html</context>
|
||||
<context context-type="linenumber">42,45</context>
|
||||
@@ -4004,6 +4005,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="3666195172774554282" datatype="html">
|
||||
<source>Other (<x id="PH" equiv-text="percentage"/>)</source>
|
||||
<target>Khác (<x id="PH" equiv-text="percentage"/>)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/pool-ranking/pool-ranking.component.ts</context>
|
||||
<context context-type="linenumber">201</context>
|
||||
@@ -6726,6 +6728,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="1282458597026430784" datatype="html">
|
||||
<source>Clearnet Only (IPv4, IPv6)</source>
|
||||
<target>Chỉ Clearnet (IPv4, IPv6)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts</context>
|
||||
<context context-type="linenumber">185,182</context>
|
||||
@@ -6737,6 +6740,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="2165336009914523952" datatype="html">
|
||||
<source>Darknet Only (Tor, I2P, cjdns)</source>
|
||||
<target>Chỉ Darknet (Tor, I2P, cjdns)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts</context>
|
||||
<context context-type="linenumber">206,203</context>
|
||||
@@ -6761,6 +6765,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="5222540403093176126" datatype="html">
|
||||
<source><x id="PH" equiv-text="nodeCount"/> nodes</source>
|
||||
<target>nút <x id="PH" equiv-text="nodeCount"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts</context>
|
||||
<context context-type="linenumber">104,103</context>
|
||||
|
||||
1
frontend/src/resources/profile/raspiblitz.svg
Normal file
1
frontend/src/resources/profile/raspiblitz.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52.67 80.82"><defs><style>.cls-1{fill:#ffe000;}</style></defs><title>RaspiBlitz_Logo_Icon_Negative</title><path class="cls-1" d="M133.56,119l-17-24.25,4.94-28.13L80.89,95l17,24.25-5,28.13ZM91.94,97l18.43-12.91-2.19,12.49L122.5,117l-18.43,12.88,2.22-12.49Z" transform="translate(-80.89 -66.59)"/></svg>
|
||||
|
After Width: | Height: | Size: 388 B |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"MEMPOOL": {
|
||||
"ENABLED": false,
|
||||
"NETWORK": "mainnet",
|
||||
"BACKEND": "esplora",
|
||||
"HTTP_PORT": 8993,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"MEMPOOL": {
|
||||
"ENABLED": false,
|
||||
"NETWORK": "signet",
|
||||
"BACKEND": "esplora",
|
||||
"HTTP_PORT": 8991,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"MEMPOOL": {
|
||||
"ENABLED": false,
|
||||
"NETWORK": "testnet",
|
||||
"BACKEND": "esplora",
|
||||
"HTTP_PORT": 8992,
|
||||
|
||||
Reference in New Issue
Block a user