mirror of
https://github.com/mempool/mempool.git
synced 2025-02-24 22:58:30 +01:00
Merge branch 'master' into hunicus/doc-nav-fix
This commit is contained in:
commit
a61106377c
95 changed files with 2550 additions and 816 deletions
18
.github/workflows/cypress.yml
vendored
18
.github/workflows/cypress.yml
vendored
|
@ -2,7 +2,7 @@ name: Cypress Tests
|
|||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, review_requested, synchronize ]
|
||||
types: [opened, review_requested, synchronize]
|
||||
jobs:
|
||||
cypress:
|
||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||
|
@ -24,36 +24,36 @@ jobs:
|
|||
- module: "bisq"
|
||||
spec: |
|
||||
cypress/e2e/bisq/bisq.spec.ts
|
||||
|
||||
|
||||
name: E2E tests for ${{ matrix.module }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: ${{ matrix.module }}
|
||||
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.15.0
|
||||
cache: 'npm'
|
||||
cache: "npm"
|
||||
cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json
|
||||
|
||||
- name: Chrome browser tests (${{ matrix.module }})
|
||||
uses: cypress-io/github-action@v4
|
||||
uses: cypress-io/github-action@v5
|
||||
with:
|
||||
tag: ${{ github.event_name }}
|
||||
working-directory: ${{ matrix.module }}/frontend
|
||||
build: npm run config:defaults:${{ matrix.module }}
|
||||
start: npm run start:local-staging
|
||||
wait-on: 'http://localhost:4200'
|
||||
wait-on: "http://localhost:4200"
|
||||
wait-on-timeout: 120
|
||||
record: true
|
||||
parallel: true
|
||||
spec: ${{ matrix.spec }}
|
||||
group: Tests on Chrome (${{ matrix.module }})
|
||||
browser: "chrome"
|
||||
ci-build-id: '${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}'
|
||||
ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}"
|
||||
env:
|
||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
|
|
10
.github/workflows/on-tag.yml
vendored
10
.github/workflows/on-tag.yml
vendored
|
@ -31,7 +31,7 @@ jobs:
|
|||
run: |
|
||||
sudo swapoff /mnt/swapfile
|
||||
sudo rm -v /mnt/swapfile
|
||||
sudo fallocate -l 10G /mnt/swapfile
|
||||
sudo fallocate -l 13G /mnt/swapfile
|
||||
sudo chmod 600 /mnt/swapfile
|
||||
sudo mkswap /mnt/swapfile
|
||||
sudo swapon /mnt/swapfile
|
||||
|
@ -68,24 +68,24 @@ jobs:
|
|||
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@e2f20e631ae6d7dd3b768f56a5d2af784dd54791 # v2.5.0
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Init repo for Dockerization
|
||||
run: docker/init.sh "$TAG"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@e81a89b1732b9c48d79cd809d8d81d79c4647a18 # v2.1.0
|
||||
uses: docker/setup-qemu-action@v2
|
||||
id: qemu
|
||||
|
||||
- name: Setup Docker buildx action
|
||||
uses: docker/setup-buildx-action@8c0edbc76e98fa90f69d9a2c020dcb50019dc325 # v2.2.1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
id: buildx
|
||||
|
||||
- name: Available platforms
|
||||
run: echo ${{ steps.buildx.outputs.platforms }}
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 # v3.0.11
|
||||
uses: actions/cache@v3
|
||||
id: cache
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
"POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master",
|
||||
"ADVANCED_GBT_AUDIT": false,
|
||||
"ADVANCED_GBT_MEMPOOL": false,
|
||||
"TRANSACTION_INDEXING": false
|
||||
"CPFP_INDEXING": false
|
||||
},
|
||||
"CORE_RPC": {
|
||||
"HOST": "127.0.0.1",
|
||||
|
|
|
@ -26,9 +26,9 @@
|
|||
"INDEXING_BLOCKS_AMOUNT": 14,
|
||||
"POOLS_JSON_TREE_URL": "__POOLS_JSON_TREE_URL__",
|
||||
"POOLS_JSON_URL": "__POOLS_JSON_URL__",
|
||||
"ADVANCED_GBT_AUDIT": "__ADVANCED_GBT_AUDIT__",
|
||||
"ADVANCED_GBT_MEMPOOL": "__ADVANCED_GBT_MEMPOOL__",
|
||||
"TRANSACTION_INDEXING": "__TRANSACTION_INDEXING__"
|
||||
"ADVANCED_GBT_AUDIT": "__MEMPOOL_ADVANCED_GBT_AUDIT__",
|
||||
"ADVANCED_GBT_MEMPOOL": "__MEMPOOL_ADVANCED_GBT_MEMPOOL__",
|
||||
"CPFP_INDEXING": "__MEMPOOL_CPFP_INDEXING__"
|
||||
},
|
||||
"CORE_RPC": {
|
||||
"HOST": "__CORE_RPC_HOST__",
|
||||
|
|
|
@ -40,7 +40,7 @@ describe('Mempool Backend Config', () => {
|
|||
POOLS_JSON_URL: 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json',
|
||||
ADVANCED_GBT_AUDIT: false,
|
||||
ADVANCED_GBT_MEMPOOL: false,
|
||||
TRANSACTION_INDEXING: false,
|
||||
CPFP_INDEXING: false,
|
||||
});
|
||||
|
||||
expect(config.ELECTRUM).toStrictEqual({ HOST: '127.0.0.1', PORT: 3306, TLS_ENABLED: true });
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import config from '../config';
|
||||
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||
import { Common } from './common';
|
||||
import { TransactionExtended, MempoolBlockWithTransactions, AuditScore } from '../mempool.interfaces';
|
||||
import blocksRepository from '../repositories/BlocksRepository';
|
||||
import blocksAuditsRepository from '../repositories/BlocksAuditsRepository';
|
||||
import blocks from '../api/blocks';
|
||||
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
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import fs from 'fs';
|
|||
import path from 'path';
|
||||
import os from 'os';
|
||||
import { IBackendInfo } from '../mempool.interfaces';
|
||||
import config from '../config';
|
||||
|
||||
class BackendInfo {
|
||||
private backendInfo: IBackendInfo;
|
||||
|
@ -22,7 +23,8 @@ class BackendInfo {
|
|||
this.backendInfo = {
|
||||
hostname: os.hostname(),
|
||||
version: versionInfo.version,
|
||||
gitCommit: versionInfo.gitCommit
|
||||
gitCommit: versionInfo.gitCommit,
|
||||
lightning: config.LIGHTNING.ENABLED
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -17,4 +17,6 @@ function bitcoinApiFactory(): AbstractBitcoinApi {
|
|||
}
|
||||
}
|
||||
|
||||
export const bitcoinCoreApi = new BitcoinApi(bitcoinClient);
|
||||
|
||||
export default bitcoinApiFactory();
|
||||
|
|
|
@ -402,7 +402,8 @@ class BitcoinRoutes {
|
|||
private async getLegacyBlocks(req: Request, res: Response) {
|
||||
try {
|
||||
const returnBlocks: IEsploraApi.Block[] = [];
|
||||
const fromHeight = parseInt(req.params.height, 10) || blocks.getCurrentBlockHeight();
|
||||
const tip = blocks.getCurrentBlockHeight();
|
||||
const fromHeight = Math.min(parseInt(req.params.height, 10) || tip, tip);
|
||||
|
||||
// Check if block height exist in local cache to skip the hash lookup
|
||||
const blockByHeight = blocks.getBlocks().find((b) => b.height === fromHeight);
|
||||
|
|
|
@ -22,12 +22,10 @@ import poolsParser from './pools-parser';
|
|||
import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository';
|
||||
import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository';
|
||||
import cpfpRepository from '../repositories/CpfpRepository';
|
||||
import transactionRepository from '../repositories/TransactionRepository';
|
||||
import mining from './mining/mining';
|
||||
import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository';
|
||||
import PricesRepository from '../repositories/PricesRepository';
|
||||
import priceUpdater from '../tasks/price-updater';
|
||||
import { Block } from 'bitcoinjs-lib';
|
||||
|
||||
class Blocks {
|
||||
private blocks: BlockExtended[] = [];
|
||||
|
@ -101,12 +99,23 @@ class Blocks {
|
|||
transactions.push(tx);
|
||||
transactionsFetched++;
|
||||
} catch (e) {
|
||||
if (i === 0) {
|
||||
const msg = `Cannot fetch coinbase tx ${txIds[i]}. Reason: ` + (e instanceof Error ? e.message : e);
|
||||
logger.err(msg);
|
||||
throw new Error(msg);
|
||||
} else {
|
||||
logger.err(`Cannot fetch tx ${txIds[i]}. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
try {
|
||||
if (config.MEMPOOL.BACKEND === 'esplora') {
|
||||
// Try again with core
|
||||
const tx = await transactionUtils.$getTransactionExtended(txIds[i], false, false, true);
|
||||
transactions.push(tx);
|
||||
transactionsFetched++;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} catch (e) {
|
||||
if (i === 0) {
|
||||
const msg = `Cannot fetch coinbase tx ${txIds[i]}. Reason: ` + (e instanceof Error ? e.message : e);
|
||||
logger.err(msg);
|
||||
throw new Error(msg);
|
||||
} else {
|
||||
logger.err(`Cannot fetch tx ${txIds[i]}. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -296,7 +305,7 @@ class Blocks {
|
|||
const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt));
|
||||
const blockPerSeconds = Math.max(1, indexedThisRun / elapsedSeconds);
|
||||
const progress = Math.round(totalIndexed / indexedBlocks.length * 10000) / 100;
|
||||
logger.debug(`Indexing block summary for #${block.height} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexedBlocks.length} (${progress}%) | elapsed: ${runningFor} seconds`);
|
||||
logger.debug(`Indexing block summary for #${block.height} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexedBlocks.length} (${progress}%) | elapsed: ${runningFor} seconds`, logger.tags.mining);
|
||||
timer = new Date().getTime() / 1000;
|
||||
indexedThisRun = 0;
|
||||
}
|
||||
|
@ -309,12 +318,12 @@ class Blocks {
|
|||
newlyIndexed++;
|
||||
}
|
||||
if (newlyIndexed > 0) {
|
||||
logger.notice(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`);
|
||||
logger.notice(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`, logger.tags.mining);
|
||||
} else {
|
||||
logger.debug(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`);
|
||||
logger.debug(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`, logger.tags.mining);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err(`Blocks summaries indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`);
|
||||
logger.err(`Blocks summaries indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`, logger.tags.mining);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -329,9 +338,10 @@ class Blocks {
|
|||
|
||||
try {
|
||||
// Get all indexed block hash
|
||||
const unindexedBlocks = await blocksRepository.$getCPFPUnindexedBlocks();
|
||||
const unindexedBlockHeights = await blocksRepository.$getCPFPUnindexedBlocks();
|
||||
logger.info(`Indexing cpfp data for ${unindexedBlockHeights.length} blocks`);
|
||||
|
||||
if (!unindexedBlocks?.length) {
|
||||
if (!unindexedBlockHeights?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -340,30 +350,26 @@ class Blocks {
|
|||
let countThisRun = 0;
|
||||
let timer = new Date().getTime() / 1000;
|
||||
const startedAt = new Date().getTime() / 1000;
|
||||
|
||||
for (const block of unindexedBlocks) {
|
||||
for (const height of unindexedBlockHeights) {
|
||||
// Logging
|
||||
const hash = await bitcoinApi.$getBlockHash(height);
|
||||
const elapsedSeconds = Math.max(1, new Date().getTime() / 1000 - timer);
|
||||
if (elapsedSeconds > 5) {
|
||||
const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt));
|
||||
const blockPerSeconds = Math.max(1, countThisRun / elapsedSeconds);
|
||||
const progress = Math.round(count / unindexedBlocks.length * 10000) / 100;
|
||||
logger.debug(`Indexing cpfp clusters for #${block.height} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${count}/${unindexedBlocks.length} (${progress}%) | elapsed: ${runningFor} seconds`);
|
||||
const blockPerSeconds = (countThisRun / elapsedSeconds);
|
||||
const progress = Math.round(count / unindexedBlockHeights.length * 10000) / 100;
|
||||
logger.debug(`Indexing cpfp clusters for #${height} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${count}/${unindexedBlockHeights.length} (${progress}%) | elapsed: ${runningFor} seconds`);
|
||||
timer = new Date().getTime() / 1000;
|
||||
countThisRun = 0;
|
||||
}
|
||||
|
||||
await this.$indexCPFP(block.hash, block.height); // Calculate and save CPFP data for transactions in this block
|
||||
await this.$indexCPFP(hash, height); // Calculate and save CPFP data for transactions in this block
|
||||
|
||||
// Logging
|
||||
count++;
|
||||
countThisRun++;
|
||||
}
|
||||
if (count > 0) {
|
||||
logger.notice(`CPFP indexing completed: indexed ${count} blocks`);
|
||||
} else {
|
||||
logger.debug(`CPFP indexing completed: indexed ${count} blocks`);
|
||||
}
|
||||
logger.notice(`CPFP indexing completed: indexed ${count} blocks`);
|
||||
} catch (e) {
|
||||
logger.err(`CPFP indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`);
|
||||
throw e;
|
||||
|
@ -385,7 +391,7 @@ class Blocks {
|
|||
|
||||
const lastBlockToIndex = Math.max(0, currentBlockHeight - indexingBlockAmount + 1);
|
||||
|
||||
logger.debug(`Indexing blocks from #${currentBlockHeight} to #${lastBlockToIndex}`);
|
||||
logger.debug(`Indexing blocks from #${currentBlockHeight} to #${lastBlockToIndex}`, logger.tags.mining);
|
||||
loadingIndicators.setProgress('block-indexing', 0);
|
||||
|
||||
const chunkSize = 10000;
|
||||
|
@ -405,7 +411,7 @@ class Blocks {
|
|||
continue;
|
||||
}
|
||||
|
||||
logger.info(`Indexing ${missingBlockHeights.length} blocks from #${currentBlockHeight} to #${endBlock}`);
|
||||
logger.info(`Indexing ${missingBlockHeights.length} blocks from #${currentBlockHeight} to #${endBlock}`, logger.tags.mining);
|
||||
|
||||
for (const blockHeight of missingBlockHeights) {
|
||||
if (blockHeight < lastBlockToIndex) {
|
||||
|
@ -418,7 +424,7 @@ class Blocks {
|
|||
const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt));
|
||||
const blockPerSeconds = Math.max(1, indexedThisRun / elapsedSeconds);
|
||||
const progress = Math.round(totalIndexed / indexingBlockAmount * 10000) / 100;
|
||||
logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexingBlockAmount} (${progress}%) | elapsed: ${runningFor} seconds`);
|
||||
logger.debug(`Indexing block #${blockHeight} | ~${blockPerSeconds.toFixed(2)} blocks/sec | total: ${totalIndexed}/${indexingBlockAmount} (${progress}%) | elapsed: ${runningFor} seconds`, logger.tags.mining);
|
||||
timer = new Date().getTime() / 1000;
|
||||
indexedThisRun = 0;
|
||||
loadingIndicators.setProgress('block-indexing', progress, false);
|
||||
|
@ -435,13 +441,13 @@ class Blocks {
|
|||
currentBlockHeight -= chunkSize;
|
||||
}
|
||||
if (newlyIndexed > 0) {
|
||||
logger.notice(`Block indexing completed: indexed ${newlyIndexed} blocks`);
|
||||
logger.notice(`Block indexing completed: indexed ${newlyIndexed} blocks`, logger.tags.mining);
|
||||
} else {
|
||||
logger.debug(`Block indexing completed: indexed ${newlyIndexed} blocks`);
|
||||
logger.debug(`Block indexing completed: indexed ${newlyIndexed} blocks`, logger.tags.mining);
|
||||
}
|
||||
loadingIndicators.setProgress('block-indexing', 100);
|
||||
} catch (e) {
|
||||
logger.err('Block indexing failed. Trying again in 10 seconds. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
logger.err('Block indexing failed. Trying again in 10 seconds. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining);
|
||||
loadingIndicators.setProgress('block-indexing', 100);
|
||||
throw e;
|
||||
}
|
||||
|
@ -519,7 +525,7 @@ class Blocks {
|
|||
for (let i = 10; i >= 0; --i) {
|
||||
const newBlock = await this.$indexBlock(lastBlock['height'] - i);
|
||||
await this.$getStrippedBlockTransactions(newBlock.id, true, true);
|
||||
if (config.MEMPOOL.TRANSACTION_INDEXING) {
|
||||
if (config.MEMPOOL.CPFP_INDEXING) {
|
||||
await this.$indexCPFP(newBlock.id, lastBlock['height'] - i);
|
||||
}
|
||||
}
|
||||
|
@ -537,7 +543,7 @@ class Blocks {
|
|||
priceId: lastestPriceId,
|
||||
}]);
|
||||
} else {
|
||||
logger.info(`Cannot save block price for ${blockExtended.height} because the price updater hasnt completed yet. Trying again in 10 seconds.`)
|
||||
logger.info(`Cannot save block price for ${blockExtended.height} because the price updater hasnt completed yet. Trying again in 10 seconds.`, logger.tags.mining);
|
||||
setTimeout(() => {
|
||||
indexer.runSingleTask('blocksPrices');
|
||||
}, 10000);
|
||||
|
@ -547,7 +553,7 @@ class Blocks {
|
|||
if (Common.blocksSummariesIndexingEnabled() === true) {
|
||||
await this.$getStrippedBlockTransactions(blockExtended.id, true);
|
||||
}
|
||||
if (config.MEMPOOL.TRANSACTION_INDEXING) {
|
||||
if (config.MEMPOOL.CPFP_INDEXING) {
|
||||
this.$indexCPFP(blockExtended.id, this.currentBlockHeight);
|
||||
}
|
||||
}
|
||||
|
@ -677,7 +683,12 @@ class Blocks {
|
|||
}
|
||||
|
||||
public async $getBlocks(fromHeight?: number, limit: number = 15): Promise<BlockExtended[]> {
|
||||
let currentHeight = fromHeight !== undefined ? fromHeight : await blocksRepository.$mostRecentBlockHeight();
|
||||
|
||||
let currentHeight = fromHeight !== undefined ? fromHeight : this.currentBlockHeight;
|
||||
if (currentHeight > this.currentBlockHeight) {
|
||||
limit -= currentHeight - this.currentBlockHeight;
|
||||
currentHeight = this.currentBlockHeight;
|
||||
}
|
||||
const returnBlocks: BlockExtended[] = [];
|
||||
|
||||
if (currentHeight < 0) {
|
||||
|
@ -741,32 +752,15 @@ class Blocks {
|
|||
}
|
||||
|
||||
public async $indexCPFP(hash: string, height: number): Promise<void> {
|
||||
let transactions;
|
||||
if (false/*Common.blocksSummariesIndexingEnabled()*/) {
|
||||
transactions = await this.$getStrippedBlockTransactions(hash);
|
||||
const rawBlock = await bitcoinApi.$getRawBlock(hash);
|
||||
const block = Block.fromBuffer(rawBlock);
|
||||
const txMap = {};
|
||||
for (const tx of block.transactions || []) {
|
||||
txMap[tx.getId()] = tx;
|
||||
}
|
||||
for (const tx of transactions) {
|
||||
if (txMap[tx.txid]?.ins) {
|
||||
tx.vin = txMap[tx.txid].ins.map(vin => {
|
||||
return {
|
||||
txid: vin.hash
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const block = await bitcoinClient.getBlock(hash, 2);
|
||||
transactions = block.tx.map(tx => {
|
||||
tx.vsize = tx.weight / 4;
|
||||
return tx;
|
||||
});
|
||||
}
|
||||
|
||||
const block = await bitcoinClient.getBlock(hash, 2);
|
||||
const transactions = block.tx.map(tx => {
|
||||
tx.vsize = tx.weight / 4;
|
||||
tx.fee *= 100_000_000;
|
||||
return tx;
|
||||
});
|
||||
|
||||
const clusters: any[] = [];
|
||||
|
||||
let cluster: TransactionStripped[] = [];
|
||||
let ancestors: { [txid: string]: boolean } = {};
|
||||
for (let i = transactions.length - 1; i >= 0; i--) {
|
||||
|
@ -778,12 +772,14 @@ class Blocks {
|
|||
totalFee += tx?.fee || 0;
|
||||
totalVSize += tx.vsize;
|
||||
});
|
||||
const effectiveFeePerVsize = (totalFee * 100_000_000) / totalVSize;
|
||||
const effectiveFeePerVsize = totalFee / totalVSize;
|
||||
if (cluster.length > 1) {
|
||||
await cpfpRepository.$saveCluster(height, cluster.map(tx => { return { txid: tx.txid, weight: tx.vsize * 4, fee: (tx.fee || 0) * 100_000_000 }; }), effectiveFeePerVsize);
|
||||
for (const tx of cluster) {
|
||||
await transactionRepository.$setCluster(tx.txid, cluster[0].txid);
|
||||
}
|
||||
clusters.push({
|
||||
root: cluster[0].txid,
|
||||
height,
|
||||
txs: cluster.map(tx => { return { txid: tx.txid, weight: tx.vsize * 4, fee: tx.fee || 0 }; }),
|
||||
effectiveFeePerVsize,
|
||||
});
|
||||
}
|
||||
cluster = [];
|
||||
ancestors = {};
|
||||
|
@ -793,7 +789,10 @@ class Blocks {
|
|||
ancestors[vin.txid] = true;
|
||||
});
|
||||
}
|
||||
await blocksRepository.$setCPFPIndexed(hash);
|
||||
const result = await cpfpRepository.$batchSaveClusters(clusters);
|
||||
if (!result) {
|
||||
await cpfpRepository.$insertProgressMarker(height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -190,7 +190,7 @@ export class Common {
|
|||
static cpfpIndexingEnabled(): boolean {
|
||||
return (
|
||||
Common.indexingEnabled() &&
|
||||
config.MEMPOOL.TRANSACTION_INDEXING === true
|
||||
config.MEMPOOL.CPFP_INDEXING === true
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,12 @@ import config from '../config';
|
|||
import DB from '../database';
|
||||
import logger from '../logger';
|
||||
import { Common } from './common';
|
||||
import blocksRepository from '../repositories/BlocksRepository';
|
||||
import cpfpRepository from '../repositories/CpfpRepository';
|
||||
import { RowDataPacket } from 'mysql2';
|
||||
|
||||
class DatabaseMigration {
|
||||
private static currentVersion = 49;
|
||||
private static currentVersion = 52;
|
||||
private queryTimeout = 3600_000;
|
||||
private statisticsAddedIndexed = false;
|
||||
private uniqueLogs: string[] = [];
|
||||
|
@ -442,6 +445,29 @@ class DatabaseMigration {
|
|||
await this.$executeQuery('TRUNCATE TABLE `blocks_audits`');
|
||||
await this.updateToSchemaVersion(49);
|
||||
}
|
||||
|
||||
if (databaseSchemaVersion < 50) {
|
||||
await this.$executeQuery('ALTER TABLE `blocks` DROP COLUMN `cpfp_indexed`');
|
||||
await this.updateToSchemaVersion(50);
|
||||
}
|
||||
|
||||
if (databaseSchemaVersion < 51) {
|
||||
await this.$executeQuery('ALTER TABLE `cpfp_clusters` ADD INDEX `height` (`height`)');
|
||||
await this.updateToSchemaVersion(51);
|
||||
}
|
||||
|
||||
if (databaseSchemaVersion < 52) {
|
||||
await this.$executeQuery(this.getCreateCompactCPFPTableQuery(), await this.$checkIfTableExists('compact_cpfp_clusters'));
|
||||
await this.$executeQuery(this.getCreateCompactTransactionsTableQuery(), await this.$checkIfTableExists('compact_transactions'));
|
||||
try {
|
||||
await this.$convertCompactCpfpTables();
|
||||
await this.$executeQuery('DROP TABLE IF EXISTS `cpfp_clusters`');
|
||||
await this.$executeQuery('DROP TABLE IF EXISTS `transactions`');
|
||||
await this.updateToSchemaVersion(52);
|
||||
} catch(e) {
|
||||
logger.warn('' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -913,6 +939,25 @@ class DatabaseMigration {
|
|||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
|
||||
}
|
||||
|
||||
private getCreateCompactCPFPTableQuery(): string {
|
||||
return `CREATE TABLE IF NOT EXISTS compact_cpfp_clusters (
|
||||
root binary(32) NOT NULL,
|
||||
height int(10) NOT NULL,
|
||||
txs BLOB DEFAULT NULL,
|
||||
fee_rate float unsigned,
|
||||
PRIMARY KEY (root),
|
||||
INDEX (height)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
|
||||
}
|
||||
|
||||
private getCreateCompactTransactionsTableQuery(): string {
|
||||
return `CREATE TABLE IF NOT EXISTS compact_transactions (
|
||||
txid binary(32) NOT NULL,
|
||||
cluster binary(32) DEFAULT NULL,
|
||||
PRIMARY KEY (txid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
|
||||
}
|
||||
|
||||
public async $truncateIndexedData(tables: string[]) {
|
||||
const allowedTables = ['blocks', 'hashrates', 'prices'];
|
||||
|
||||
|
@ -933,6 +978,49 @@ class DatabaseMigration {
|
|||
logger.warn(`Unable to erase indexed data`);
|
||||
}
|
||||
}
|
||||
|
||||
private async $convertCompactCpfpTables(): Promise<void> {
|
||||
try {
|
||||
const batchSize = 250;
|
||||
const maxHeight = await blocksRepository.$mostRecentBlockHeight() || 0;
|
||||
const [minHeightRows]: any = await DB.query(`SELECT MIN(height) AS minHeight from cpfp_clusters`);
|
||||
const minHeight = (minHeightRows.length && minHeightRows[0].minHeight != null) ? minHeightRows[0].minHeight : maxHeight;
|
||||
let height = maxHeight;
|
||||
|
||||
// Logging
|
||||
let timer = new Date().getTime() / 1000;
|
||||
const startedAt = new Date().getTime() / 1000;
|
||||
|
||||
while (height > minHeight) {
|
||||
const [rows] = await DB.query(
|
||||
`
|
||||
SELECT * from cpfp_clusters
|
||||
WHERE height <= ? AND height > ?
|
||||
ORDER BY height
|
||||
`,
|
||||
[height, height - batchSize]
|
||||
) as RowDataPacket[][];
|
||||
if (rows?.length) {
|
||||
await cpfpRepository.$batchSaveClusters(rows.map(row => {
|
||||
return {
|
||||
root: row.root,
|
||||
height: row.height,
|
||||
txs: JSON.parse(row.txs),
|
||||
effectiveFeePerVsize: row.fee_rate,
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
const elapsed = new Date().getTime() / 1000 - timer;
|
||||
const runningFor = new Date().getTime() / 1000 - startedAt;
|
||||
logger.debug(`Migrated cpfp data from block ${height} to ${height - batchSize} in ${elapsed.toFixed(2)} seconds | total elapsed: ${runningFor.toFixed(2)} seconds`);
|
||||
timer = new Date().getTime() / 1000;
|
||||
height -= batchSize;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn(`Failed to migrate cpfp transaction data`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new DatabaseMigration();
|
||||
|
|
|
@ -670,9 +670,7 @@ class ChannelsApi {
|
|||
AND status != 2
|
||||
`);
|
||||
if (result[0].changedRows ?? 0 > 0) {
|
||||
logger.info(`Marked ${result[0].changedRows} channels as inactive because they are not in the graph`);
|
||||
} else {
|
||||
logger.debug(`Marked ${result[0].changedRows} channels as inactive because they are not in the graph`);
|
||||
logger.debug(`Marked ${result[0].changedRows} channels as inactive because they are not in the graph`, logger.tags.ln);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('$setChannelsInactive() error: ' + (e instanceof Error ? e.message : e));
|
||||
|
|
|
@ -685,9 +685,7 @@ class NodesApi {
|
|||
)
|
||||
`);
|
||||
if (result[0].changedRows ?? 0 > 0) {
|
||||
logger.info(`Marked ${result[0].changedRows} nodes as inactive because they are not in the graph`);
|
||||
} else {
|
||||
logger.debug(`Marked ${result[0].changedRows} nodes as inactive because they are not in the graph`);
|
||||
logger.debug(`Marked ${result[0].changedRows} nodes as inactive because they are not in the graph`, logger.tags.ln);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('$setNodesInactive() error: ' + (e instanceof Error ? e.message : e));
|
||||
|
|
|
@ -41,13 +41,70 @@ class NodesRoutes {
|
|||
let nodes: any[] = [];
|
||||
switch (config.MEMPOOL.NETWORK) {
|
||||
case 'testnet':
|
||||
nodesList = ['032c7c7819276c4f706a04df1a0f1e10a5495994a7be4c1d3d28ca766e5a2b957b', '025a7e38c2834dd843591a4d23d5f09cdeb77ddca85f673c2d944a14220ff14cf7', '0395e2731a1673ef21d7a16a727c4fc4d4c35a861c428ce2c819c53d2b81c8bd55', '032ab2028c0b614c6d87824e2373529652fd7e4221b4c70cc4da7c7005c49afcf0', '029001b22fe70b48bee12d014df91982eb85ff1bd404ec772d5c83c4ee3e88d2c3', '0212e2848d79f928411da5f2ff0a8c95ec6ccb5a09d2031b6f71e91309dcde63af', '03e871a2229523d34f76e6311ff197cfe7f26c2fbec13554b93a46f4e710c47dab', '032202ec98d976b0e928bd1d91924e8bd3eab07231fc39feb3737b010071073df8', '02fa7c5a948d03d563a9f36940c2205a814e594d17c0042ced242c71a857d72605', '039c14fdec2d958e3d14cebf657451bbd9e039196615785e82c917f274e3fb2205', '033589bbcb233ffc416cefd5437c7f37e9d7cb7942d405e39e72c4c846d9b37f18', '029293110441c6e2eacb57e1255bf6ef05c41a6a676fe474922d33c19f98a7d584'];
|
||||
nodesList = [
|
||||
'032c7c7819276c4f706a04df1a0f1e10a5495994a7be4c1d3d28ca766e5a2b957b',
|
||||
'025a7e38c2834dd843591a4d23d5f09cdeb77ddca85f673c2d944a14220ff14cf7',
|
||||
'0395e2731a1673ef21d7a16a727c4fc4d4c35a861c428ce2c819c53d2b81c8bd55',
|
||||
'032ab2028c0b614c6d87824e2373529652fd7e4221b4c70cc4da7c7005c49afcf0',
|
||||
'029001b22fe70b48bee12d014df91982eb85ff1bd404ec772d5c83c4ee3e88d2c3',
|
||||
'0212e2848d79f928411da5f2ff0a8c95ec6ccb5a09d2031b6f71e91309dcde63af',
|
||||
'03e871a2229523d34f76e6311ff197cfe7f26c2fbec13554b93a46f4e710c47dab',
|
||||
'032202ec98d976b0e928bd1d91924e8bd3eab07231fc39feb3737b010071073df8',
|
||||
'02fa7c5a948d03d563a9f36940c2205a814e594d17c0042ced242c71a857d72605',
|
||||
'039c14fdec2d958e3d14cebf657451bbd9e039196615785e82c917f274e3fb2205',
|
||||
'033589bbcb233ffc416cefd5437c7f37e9d7cb7942d405e39e72c4c846d9b37f18',
|
||||
'029293110441c6e2eacb57e1255bf6ef05c41a6a676fe474922d33c19f98a7d584',
|
||||
'0235ad0b56ed8c42c4354444c24e971c05e769ec0b5fb0ccea42880095dc02ea2c',
|
||||
'029700819a37afea630f80e6cc461f3fd3c4ace2598a21cfbbe64d1c78d0ee69a5',
|
||||
'02c2d8b2dbf87c7894af2f1d321290e2fe6db5446cd35323987cee98f06e2e0075',
|
||||
'030b0ca1ea7b1075716d2a555630e6fd47ef11bc7391fe68963ec06cf370a5e382',
|
||||
'031adb9eb2d66693f85fa31a4adca0319ba68219f3ad5f9a2ef9b34a6b40755fa1',
|
||||
'02ccd07faa47eda810ecf5591ccf5ca50f6c1034d0d175052898d32a00b9bae24f',
|
||||
];
|
||||
break;
|
||||
case 'signet':
|
||||
nodesList = ['03ddab321b760433cbf561b615ef62ac7d318630c5f51d523aaf5395b90b751956', '033d92c7bfd213ef1b34c90e985fb5dc77f9ec2409d391492484e57a44c4aca1de', '02ad010dda54253c1eb9efe38b0760657a3b43ecad62198c359c051c9d99d45781', '025196512905b8a3f1597428b867bec63ec9a95e5089eb7dc7e63e2d2691669029', '027c625aa1fbe3768db68ebcb05b53b6dc0ce68b7b54b8900d326d167363e684fe', '03f1629af3101fcc56b7aac2667016be84e3defbf3d0c8719f836c9b41c9a57a43', '02dfb81e2f7a3c4c9e8a51b70ef82b4a24549cc2fab1f5b2fd636501774a918991', '02d01ccf832944c68f10d39006093769c5b8bda886d561b128534e313d729fdb34', '02499ed23027d4698a6904ff4ec1b6085a61f10b9a6937f90438f9947e38e8ea86', '038310e3a786340f2bd7770704c7ccfe560fd163d9a1c99d67894597419d12cbf7', '03e5e9d879b72c7d67ecd483bae023bd33e695bb32b981a4021260f7b9d62bc761', '028d16e1a0ace4c0c0a421536d8d32ce484dfe6e2f726b7b0e7c30f12a195f8cc7'];
|
||||
nodesList = [
|
||||
'03ddab321b760433cbf561b615ef62ac7d318630c5f51d523aaf5395b90b751956',
|
||||
'033d92c7bfd213ef1b34c90e985fb5dc77f9ec2409d391492484e57a44c4aca1de',
|
||||
'02ad010dda54253c1eb9efe38b0760657a3b43ecad62198c359c051c9d99d45781',
|
||||
'025196512905b8a3f1597428b867bec63ec9a95e5089eb7dc7e63e2d2691669029',
|
||||
'027c625aa1fbe3768db68ebcb05b53b6dc0ce68b7b54b8900d326d167363e684fe',
|
||||
'03f1629af3101fcc56b7aac2667016be84e3defbf3d0c8719f836c9b41c9a57a43',
|
||||
'02dfb81e2f7a3c4c9e8a51b70ef82b4a24549cc2fab1f5b2fd636501774a918991',
|
||||
'02d01ccf832944c68f10d39006093769c5b8bda886d561b128534e313d729fdb34',
|
||||
'02499ed23027d4698a6904ff4ec1b6085a61f10b9a6937f90438f9947e38e8ea86',
|
||||
'038310e3a786340f2bd7770704c7ccfe560fd163d9a1c99d67894597419d12cbf7',
|
||||
'03e5e9d879b72c7d67ecd483bae023bd33e695bb32b981a4021260f7b9d62bc761',
|
||||
'028d16e1a0ace4c0c0a421536d8d32ce484dfe6e2f726b7b0e7c30f12a195f8cc7',
|
||||
'02ff690d06c187ab994bf83c5a2114fe5bf50112c2c817af0f788f736be9fa2070',
|
||||
'02a9f570c51a2526a5ee85802e88f9281bed771eb66a0c8a7d898430dd5d0eae45',
|
||||
'038c3de773255d3bd7a50e31e58d423baac5c90826a74d75e64b74c95475de1097',
|
||||
'0242c7f7d315095f37ad1421ae0a2fc967d4cbe65b61b079c5395a769436959853',
|
||||
'02a909e70eb03742f12666ebb1f56ac42a5fbaab0c0e8b5b1df4aa9f10f8a09240',
|
||||
'03a26efa12489803c07f3ac2f1dba63812e38f0f6e866ce3ebb34df7de1f458cd2',
|
||||
];
|
||||
break;
|
||||
default:
|
||||
nodesList = ['03fbc17549ec667bccf397ababbcb4cdc0e3394345e4773079ab2774612ec9be61', '03da9a8623241ccf95f19cd645c6cecd4019ac91570e976eb0a128bebbc4d8a437', '03ca5340cf85cb2e7cf076e489f785410838de174e40be62723e8a60972ad75144', '0238bd27f02d67d6c51e269692bc8c9a32357a00e7777cba7f4f1f18a2a700b108', '03f983dcabed6baa1eab5b56c8b2e8fdc846ab3fd931155377897335e85a9fa57c', '03e399589533581e48796e29a825839a010036a61b20744fda929d6709fcbffcc5', '021f5288b5f72c42cd0d8801086af7ce09a816d8ee9a4c47a4b436399b26cb601a', '032b01b7585f781420cd4148841a82831ba37fa952342052cec16750852d4f2dd9', '02848036488d4b8fb1f1c4064261ec36151f43b085f0b51bd239ade3ddfc940c34', '02b6b1640fe029e304c216951af9fbefdb23b0bdc9baaf327540d31b6107841fdf', '03694289827203a5b3156d753071ddd5bf92e371f5a462943f9555eef6d2d6606c', '0283d850db7c3e8ea7cc9c4abc7afaab12bbdf72b677dcba1d608350d2537d7d43'];
|
||||
nodesList = [
|
||||
'03fbc17549ec667bccf397ababbcb4cdc0e3394345e4773079ab2774612ec9be61',
|
||||
'03da9a8623241ccf95f19cd645c6cecd4019ac91570e976eb0a128bebbc4d8a437',
|
||||
'03ca5340cf85cb2e7cf076e489f785410838de174e40be62723e8a60972ad75144',
|
||||
'0238bd27f02d67d6c51e269692bc8c9a32357a00e7777cba7f4f1f18a2a700b108',
|
||||
'03f983dcabed6baa1eab5b56c8b2e8fdc846ab3fd931155377897335e85a9fa57c',
|
||||
'03e399589533581e48796e29a825839a010036a61b20744fda929d6709fcbffcc5',
|
||||
'021f5288b5f72c42cd0d8801086af7ce09a816d8ee9a4c47a4b436399b26cb601a',
|
||||
'032b01b7585f781420cd4148841a82831ba37fa952342052cec16750852d4f2dd9',
|
||||
'02848036488d4b8fb1f1c4064261ec36151f43b085f0b51bd239ade3ddfc940c34',
|
||||
'02b6b1640fe029e304c216951af9fbefdb23b0bdc9baaf327540d31b6107841fdf',
|
||||
'03694289827203a5b3156d753071ddd5bf92e371f5a462943f9555eef6d2d6606c',
|
||||
'0283d850db7c3e8ea7cc9c4abc7afaab12bbdf72b677dcba1d608350d2537d7d43',
|
||||
'02521287789f851268a39c9eccc9d6180d2c614315b583c9e6ae0addbd6d79df06',
|
||||
'0258c2a7b7f8af2585b4411b1ec945f70988f30412bb1df179de941f14d0b1bc3e',
|
||||
'03c3389ff1a896f84d921ed01a19fc99c6724ce8dc4b960cd3b7b2362b62cd60d7',
|
||||
'038d118996b3eaa15dcd317b32a539c9ecfdd7698f204acf8a087336af655a9192',
|
||||
'02a928903d93d78877dacc3642b696128a3636e9566dd42d2d132325b2c8891c09',
|
||||
'0328cd17f3a9d3d90b532ade0d1a67e05eb8a51835b3dce0a2e38eac04b5a62a57',
|
||||
];
|
||||
}
|
||||
|
||||
for (let pubKey of nodesList) {
|
||||
|
|
|
@ -141,13 +141,13 @@ export default class CLightningClient extends EventEmitter implements AbstractLi
|
|||
// main data directory provided, default to using the bitcoin mainnet subdirectory
|
||||
// to be removed in v0.2.0
|
||||
else if (fExists(rpcPath, 'bitcoin', 'lightning-rpc')) {
|
||||
logger.warn(`[CLightningClient] ${rpcPath}/lightning-rpc is missing, using the bitcoin mainnet subdirectory at ${rpcPath}/bitcoin instead.`)
|
||||
logger.warn(`[CLightningClient] specifying the main lightning data directory is deprecated, please specify the network directory explicitly.\n`)
|
||||
logger.warn(`${rpcPath}/lightning-rpc is missing, using the bitcoin mainnet subdirectory at ${rpcPath}/bitcoin instead.`, logger.tags.ln)
|
||||
logger.warn(`specifying the main lightning data directory is deprecated, please specify the network directory explicitly.\n`, logger.tags.ln)
|
||||
rpcPath = path.join(rpcPath, 'bitcoin', 'lightning-rpc')
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`[CLightningClient] Connecting to ${rpcPath}`);
|
||||
logger.debug(`Connecting to ${rpcPath}`, logger.tags.ln);
|
||||
|
||||
super();
|
||||
this.rpcPath = rpcPath;
|
||||
|
@ -172,19 +172,19 @@ export default class CLightningClient extends EventEmitter implements AbstractLi
|
|||
|
||||
this.clientConnectionPromise = new Promise<void>(resolve => {
|
||||
_self.client.on('connect', () => {
|
||||
logger.info(`[CLightningClient] Lightning client connected`);
|
||||
logger.info(`CLightning client connected`, logger.tags.ln);
|
||||
_self.reconnectWait = 1;
|
||||
resolve();
|
||||
});
|
||||
|
||||
_self.client.on('end', () => {
|
||||
logger.err('[CLightningClient] Lightning client connection closed, reconnecting');
|
||||
logger.err(`CLightning client connection closed, reconnecting`, logger.tags.ln);
|
||||
_self.increaseWaitTime();
|
||||
_self.reconnect();
|
||||
});
|
||||
|
||||
_self.client.on('error', error => {
|
||||
logger.err(`[CLightningClient] Lightning client connection error: ${error}`);
|
||||
logger.err(`CLightning client connection error: ${error}`, logger.tags.ln);
|
||||
_self.increaseWaitTime();
|
||||
_self.reconnect();
|
||||
});
|
||||
|
@ -196,7 +196,6 @@ export default class CLightningClient extends EventEmitter implements AbstractLi
|
|||
return;
|
||||
}
|
||||
const data = JSON.parse(line);
|
||||
// logger.debug(`[CLightningClient] #${data.id} <-- ${JSON.stringify(data.error || data.result)}`);
|
||||
_self.emit('res:' + data.id, data);
|
||||
});
|
||||
}
|
||||
|
@ -217,7 +216,7 @@ export default class CLightningClient extends EventEmitter implements AbstractLi
|
|||
}
|
||||
|
||||
this.reconnectTimeout = setTimeout(() => {
|
||||
logger.debug('[CLightningClient] Trying to reconnect...');
|
||||
logger.debug(`Trying to reconnect...`, logger.tags.ln);
|
||||
|
||||
_self.client.connect(_self.rpcPath);
|
||||
_self.reconnectTimeout = null;
|
||||
|
@ -235,7 +234,6 @@ export default class CLightningClient extends EventEmitter implements AbstractLi
|
|||
id: '' + callInt
|
||||
};
|
||||
|
||||
// logger.debug(`[CLightningClient] #${callInt} --> ${method} ${args}`);
|
||||
|
||||
// Wait for the client to connect
|
||||
return this.clientConnectionPromise
|
||||
|
|
|
@ -2,6 +2,7 @@ import { ILightningApi } from '../lightning-api.interface';
|
|||
import FundingTxFetcher from '../../../tasks/lightning/sync-tasks/funding-tx-fetcher';
|
||||
import logger from '../../../logger';
|
||||
import { Common } from '../../common';
|
||||
import config from '../../../config';
|
||||
|
||||
/**
|
||||
* Convert a clightning "listnode" entry to a lnd node entry
|
||||
|
@ -40,7 +41,7 @@ export function convertNode(clNode: any): ILightningApi.Node {
|
|||
* Convert clightning "listchannels" response to lnd "describegraph.edges" format
|
||||
*/
|
||||
export async function convertAndmergeBidirectionalChannels(clChannels: any[]): Promise<ILightningApi.Channel[]> {
|
||||
logger.info('Converting clightning nodes and channels to lnd graph format');
|
||||
logger.debug(`Converting clightning nodes and channels to lnd graph format`, logger.tags.ln);
|
||||
|
||||
let loggerTimer = new Date().getTime() / 1000;
|
||||
let channelProcessed = 0;
|
||||
|
@ -62,8 +63,8 @@ export async function convertAndmergeBidirectionalChannels(clChannels: any[]): P
|
|||
}
|
||||
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - loggerTimer);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Building complete channels from clightning output. Channels processed: ${channelProcessed + 1} of ${clChannels.length}`);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.info(`Building complete channels from clightning output. Channels processed: ${channelProcessed + 1} of ${clChannels.length}`, logger.tags.ln);
|
||||
loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
|
||||
|
@ -76,7 +77,7 @@ export async function convertAndmergeBidirectionalChannels(clChannels: any[]): P
|
|||
consolidatedChannelList.push(await buildIncompleteChannel(clChannelsDict[short_channel_id]));
|
||||
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - loggerTimer);
|
||||
if (elapsedSeconds > 10) {
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.info(`Building partial channels from clightning output. Channels processed: ${channelProcessed + 1} of ${keys.length}`);
|
||||
loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,14 @@
|
|||
import logger from '../logger';
|
||||
import { MempoolBlock, TransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta } from '../mempool.interfaces';
|
||||
import { MempoolBlock, TransactionExtended, ThreadTransaction, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor } from '../mempool.interfaces';
|
||||
import { Common } from './common';
|
||||
import config from '../config';
|
||||
import { StaticPool } from 'node-worker-threads-pool';
|
||||
import { Worker } from 'worker_threads';
|
||||
import path from 'path';
|
||||
|
||||
class MempoolBlocks {
|
||||
private mempoolBlocks: MempoolBlockWithTransactions[] = [];
|
||||
private mempoolBlockDeltas: MempoolBlockDelta[] = [];
|
||||
private makeTemplatesPool = new StaticPool({
|
||||
size: 1,
|
||||
task: path.resolve(__dirname, './tx-selection-worker.js'),
|
||||
});
|
||||
private txSelectionWorker: Worker | null = null;
|
||||
|
||||
constructor() {}
|
||||
|
||||
|
@ -146,27 +143,159 @@ class MempoolBlocks {
|
|||
return mempoolBlockDeltas;
|
||||
}
|
||||
|
||||
public async makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, blockLimit: number, weightLimit: number | null = null, condenseRest = false): Promise<void> {
|
||||
const { mempool, blocks } = await this.makeTemplatesPool.exec({ mempool: newMempool, blockLimit, weightLimit, condenseRest });
|
||||
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, blocks);
|
||||
|
||||
// copy CPFP info across to main thread's mempool
|
||||
Object.keys(newMempool).forEach((txid) => {
|
||||
if (newMempool[txid] && mempool[txid]) {
|
||||
newMempool[txid].effectiveFeePerVsize = mempool[txid].effectiveFeePerVsize;
|
||||
newMempool[txid].ancestors = mempool[txid].ancestors;
|
||||
newMempool[txid].descendants = mempool[txid].descendants;
|
||||
newMempool[txid].bestDescendant = mempool[txid].bestDescendant;
|
||||
newMempool[txid].cpfpChecked = mempool[txid].cpfpChecked;
|
||||
}
|
||||
public async makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }): Promise<void> {
|
||||
// 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 } = {};
|
||||
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),
|
||||
};
|
||||
});
|
||||
|
||||
this.mempoolBlocks = blocks;
|
||||
// (re)initialize tx selection worker thread
|
||||
if (!this.txSelectionWorker) {
|
||||
this.txSelectionWorker = new Worker(path.resolve(__dirname, './tx-selection-worker.js'));
|
||||
// if the thread throws an unexpected error, or exits for any other reason,
|
||||
// reset worker state so that it will be re-initialized on the next run
|
||||
this.txSelectionWorker.once('error', () => {
|
||||
this.txSelectionWorker = null;
|
||||
});
|
||||
this.txSelectionWorker.once('exit', () => {
|
||||
this.txSelectionWorker = null;
|
||||
});
|
||||
}
|
||||
|
||||
// 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) => {
|
||||
threadErrorListener = reject;
|
||||
this.txSelectionWorker?.once('message', (result): void => {
|
||||
resolve(result);
|
||||
});
|
||||
this.txSelectionWorker?.once('error', reject);
|
||||
});
|
||||
this.txSelectionWorker.postMessage({ type: 'set', mempool: strippedMempool });
|
||||
const { blocks, clusters } = await workerResultPromise;
|
||||
|
||||
this.processBlockTemplates(newMempool, blocks, clusters);
|
||||
|
||||
// clean up thread error listener
|
||||
this.txSelectionWorker?.removeListener('error', threadErrorListener);
|
||||
} catch (e) {
|
||||
logger.err('makeBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
public async updateBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, added: TransactionExtended[], removed: string[]): Promise<void> {
|
||||
if (!this.txSelectionWorker) {
|
||||
// need to reset the worker
|
||||
return this.makeBlockTemplates(newMempool);
|
||||
}
|
||||
// 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 => {
|
||||
return {
|
||||
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),
|
||||
};
|
||||
});
|
||||
|
||||
// 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) => {
|
||||
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.processBlockTemplates(newMempool, blocks, clusters);
|
||||
|
||||
// clean up thread error listener
|
||||
this.txSelectionWorker?.removeListener('error', threadErrorListener);
|
||||
} catch (e) {
|
||||
logger.err('updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
|
||||
}
|
||||
}
|
||||
|
||||
private processBlockTemplates(mempool, blocks, clusters): void {
|
||||
// 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;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 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), undefined, undefined, blockIndex);
|
||||
});
|
||||
|
||||
const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, mempoolBlocks);
|
||||
|
||||
this.mempoolBlocks = mempoolBlocks;
|
||||
this.mempoolBlockDeltas = deltas;
|
||||
}
|
||||
|
||||
private dataToMempoolBlocks(transactions: TransactionExtended[],
|
||||
blockSize: number, blockWeight: number, blocksIndex: number): MempoolBlockWithTransactions {
|
||||
blockSize: number | undefined, blockWeight: number | undefined, blocksIndex: number): MempoolBlockWithTransactions {
|
||||
let totalSize = blockSize || 0;
|
||||
let totalWeight = blockWeight || 0;
|
||||
if (blockSize === undefined && blockWeight === undefined) {
|
||||
totalSize = 0;
|
||||
totalWeight = 0;
|
||||
transactions.forEach(tx => {
|
||||
totalSize += tx.size;
|
||||
totalWeight += tx.weight;
|
||||
});
|
||||
}
|
||||
let rangeLength = 4;
|
||||
if (blocksIndex === 0) {
|
||||
rangeLength = 8;
|
||||
|
@ -177,8 +306,8 @@ class MempoolBlocks {
|
|||
rangeLength = 8;
|
||||
}
|
||||
return {
|
||||
blockSize: blockSize,
|
||||
blockVSize: blockWeight / 4,
|
||||
blockSize: totalSize,
|
||||
blockVSize: totalWeight / 4,
|
||||
nTx: transactions.length,
|
||||
totalFees: transactions.reduce((acc, cur) => acc + cur.fee, 0),
|
||||
medianFee: Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
|
||||
|
|
|
@ -21,7 +21,7 @@ class Mempool {
|
|||
private mempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
||||
deletedTransactions: TransactionExtended[]) => void) | undefined;
|
||||
private asyncMempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
||||
deletedTransactions: TransactionExtended[]) => void) | undefined;
|
||||
deletedTransactions: TransactionExtended[]) => Promise<void>) | undefined;
|
||||
|
||||
private txPerSecondArray: number[] = [];
|
||||
private txPerSecond: number = 0;
|
||||
|
|
|
@ -265,9 +265,9 @@ class Mining {
|
|||
}
|
||||
await HashratesRepository.$setLatestRun('last_weekly_hashrates_indexing', new Date().getUTCDate());
|
||||
if (newlyIndexed > 0) {
|
||||
logger.notice(`Weekly mining pools hashrates indexing completed: indexed ${newlyIndexed}`);
|
||||
logger.notice(`Weekly mining pools hashrates indexing completed: indexed ${newlyIndexed}`, logger.tags.mining);
|
||||
} else {
|
||||
logger.debug(`Weekly mining pools hashrates indexing completed: indexed ${newlyIndexed}`);
|
||||
logger.debug(`Weekly mining pools hashrates indexing completed: indexed ${newlyIndexed}`, logger.tags.mining);
|
||||
}
|
||||
loadingIndicators.setProgress('weekly-hashrate-indexing', 100);
|
||||
} catch (e) {
|
||||
|
@ -370,14 +370,14 @@ class Mining {
|
|||
|
||||
await HashratesRepository.$setLatestRun('last_hashrates_indexing', new Date().getUTCDate());
|
||||
if (newlyIndexed > 0) {
|
||||
logger.notice(`Daily network hashrate indexing completed: indexed ${newlyIndexed} days`);
|
||||
logger.notice(`Daily network hashrate indexing completed: indexed ${newlyIndexed} days`, logger.tags.mining);
|
||||
} else {
|
||||
logger.debug(`Daily network hashrate indexing completed: indexed ${newlyIndexed} days`);
|
||||
logger.debug(`Daily network hashrate indexing completed: indexed ${newlyIndexed} days`, logger.tags.mining);
|
||||
}
|
||||
loadingIndicators.setProgress('daily-hashrate-indexing', 100);
|
||||
} catch (e) {
|
||||
loadingIndicators.setProgress('daily-hashrate-indexing', 100);
|
||||
logger.err(`Daily network hashrate indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`);
|
||||
logger.err(`Daily network hashrate indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`, logger.tags.mining);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -449,9 +449,9 @@ class Mining {
|
|||
}
|
||||
|
||||
if (totalIndexed > 0) {
|
||||
logger.notice(`Indexed ${totalIndexed} difficulty adjustments`);
|
||||
logger.notice(`Indexed ${totalIndexed} difficulty adjustments`, logger.tags.mining);
|
||||
} else {
|
||||
logger.debug(`Indexed ${totalIndexed} difficulty adjustments`);
|
||||
logger.debug(`Indexed ${totalIndexed} difficulty adjustments`, logger.tags.mining);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,7 @@ class PoolsParser {
|
|||
poolNames.push(poolsDuplicated[i].name);
|
||||
}
|
||||
}
|
||||
logger.debug(`Found ${poolNames.length} unique mining pools`);
|
||||
logger.debug(`Found ${poolNames.length} unique mining pools`, logger.tags.mining);
|
||||
|
||||
// Get existing pools from the db
|
||||
let existingPools;
|
||||
|
@ -72,7 +72,7 @@ class PoolsParser {
|
|||
existingPools = [];
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('Cannot get existing pools from the database, skipping pools.json import');
|
||||
logger.err('Cannot get existing pools from the database, skipping pools.json import', logger.tags.mining);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -99,7 +99,7 @@ class PoolsParser {
|
|||
slug = poolsJson['slugs'][poolNames[i]];
|
||||
} catch (e) {
|
||||
if (this.slugWarnFlag === false) {
|
||||
logger.warn(`pools.json does not seem to contain the 'slugs' object`);
|
||||
logger.warn(`pools.json does not seem to contain the 'slugs' object`, logger.tags.mining);
|
||||
this.slugWarnFlag = true;
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ class PoolsParser {
|
|||
if (slug === undefined) {
|
||||
// Only keep alphanumerical
|
||||
slug = poolNames[i].replace(/[^a-z0-9]/gi, '').toLowerCase();
|
||||
logger.warn(`No slug found for '${poolNames[i]}', generating it => '${slug}'`);
|
||||
logger.warn(`No slug found for '${poolNames[i]}', generating it => '${slug}'`, logger.tags.mining);
|
||||
}
|
||||
|
||||
const poolObj = {
|
||||
|
@ -143,9 +143,9 @@ class PoolsParser {
|
|||
'addresses': allAddresses,
|
||||
'slug': slug
|
||||
});
|
||||
logger.debug(`Rename '${poolToRename[0].name}' mining pool to ${poolObj.name}`);
|
||||
logger.debug(`Rename '${poolToRename[0].name}' mining pool to ${poolObj.name}`, logger.tags.mining);
|
||||
} else {
|
||||
logger.debug(`Add '${finalPoolName}' mining pool`);
|
||||
logger.debug(`Add '${finalPoolName}' mining pool`, logger.tags.mining);
|
||||
finalPoolDataAdd.push(poolObj);
|
||||
}
|
||||
}
|
||||
|
@ -160,14 +160,14 @@ class PoolsParser {
|
|||
}
|
||||
|
||||
if (config.DATABASE.ENABLED === false) { // Don't run db operations
|
||||
logger.info('Mining pools.json import completed (no database)');
|
||||
logger.info('Mining pools.json import completed (no database)', logger.tags.mining);
|
||||
return;
|
||||
}
|
||||
|
||||
if (finalPoolDataAdd.length > 0 || finalPoolDataUpdate.length > 0 ||
|
||||
finalPoolDataRename.length > 0
|
||||
) {
|
||||
logger.debug(`Update pools table now`);
|
||||
logger.debug(`Update pools table now`, logger.tags.mining);
|
||||
|
||||
// Add new mining pools into the database
|
||||
let queryAdd: string = 'INSERT INTO pools(name, link, regexes, addresses, slug) VALUES ';
|
||||
|
@ -217,9 +217,9 @@ class PoolsParser {
|
|||
await DB.query({ sql: query, timeout: 120000 });
|
||||
}
|
||||
await this.insertUnknownPool();
|
||||
logger.info('Mining pools.json import completed');
|
||||
logger.info('Mining pools.json import completed', logger.tags.mining);
|
||||
} catch (e) {
|
||||
logger.err(`Cannot import pools in the database`);
|
||||
logger.err(`Cannot import pools in the database`, logger.tags.mining);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ class PoolsParser {
|
|||
try {
|
||||
await this.insertUnknownPool();
|
||||
} catch (e) {
|
||||
logger.err(`Cannot insert unknown pool in the database`);
|
||||
logger.err(`Cannot insert unknown pool in the database`, logger.tags.mining);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -252,7 +252,7 @@ class PoolsParser {
|
|||
`);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('Unable to insert "Unknown" mining pool');
|
||||
logger.err('Unable to insert "Unknown" mining pool', logger.tags.mining);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,17 +272,17 @@ class PoolsParser {
|
|||
for (const updatedPool of finalPoolDataUpdate) {
|
||||
const [pool]: any[] = await DB.query(`SELECT id, name from pools where slug = "${updatedPool.slug}"`);
|
||||
if (pool.length > 0) {
|
||||
logger.notice(`Deleting blocks from ${pool[0].name} mining pool for future re-indexing`);
|
||||
logger.notice(`Deleting blocks from ${pool[0].name} mining pool for future re-indexing`, logger.tags.mining);
|
||||
await DB.query(`DELETE FROM blocks WHERE pool_id = ${pool[0].id}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore early days of Bitcoin as there were not mining pool yet
|
||||
logger.notice('Deleting blocks with unknown mining pool from height 130635 for future re-indexing');
|
||||
logger.notice(`Deleting blocks with unknown mining pool from height 130635 for future re-indexing`, logger.tags.mining);
|
||||
const [unknownPool] = await DB.query(`SELECT id from pools where slug = "unknown"`);
|
||||
await DB.query(`DELETE FROM blocks WHERE pool_id = ${unknownPool[0].id} AND height > 130635`);
|
||||
|
||||
logger.notice('Truncating hashrates for future re-indexing');
|
||||
logger.notice(`Truncating hashrates for future re-indexing`, logger.tags.mining);
|
||||
await DB.query(`DELETE FROM hashrates`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||
import { TransactionExtended, TransactionMinerInfo } from '../mempool.interfaces';
|
||||
import { IEsploraApi } from './bitcoin/esplora-api.interface';
|
||||
import config from '../config';
|
||||
import { Common } from './common';
|
||||
import bitcoinApi, { bitcoinCoreApi } from './bitcoin/bitcoin-api-factory';
|
||||
|
||||
class TransactionUtils {
|
||||
constructor() { }
|
||||
|
@ -21,8 +20,19 @@ class TransactionUtils {
|
|||
};
|
||||
}
|
||||
|
||||
public async $getTransactionExtended(txId: string, addPrevouts = false, lazyPrevouts = false): Promise<TransactionExtended> {
|
||||
const transaction: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(txId, false, addPrevouts, lazyPrevouts);
|
||||
/**
|
||||
* @param txId
|
||||
* @param addPrevouts
|
||||
* @param lazyPrevouts
|
||||
* @param forceCore - See https://github.com/mempool/mempool/issues/2904
|
||||
*/
|
||||
public async $getTransactionExtended(txId: string, addPrevouts = false, lazyPrevouts = false, forceCore = false): Promise<TransactionExtended> {
|
||||
let transaction: IEsploraApi.Transaction;
|
||||
if (forceCore === true) {
|
||||
transaction = await bitcoinCoreApi.$getRawTransaction(txId, true);
|
||||
} else {
|
||||
transaction = await bitcoinApi.$getRawTransaction(txId, false, addPrevouts, lazyPrevouts);
|
||||
}
|
||||
return this.extendTransaction(transaction);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,30 @@
|
|||
import config from '../config';
|
||||
import logger from '../logger';
|
||||
import { TransactionExtended, MempoolBlockWithTransactions, AuditTransaction } from '../mempool.interfaces';
|
||||
import { ThreadTransaction, MempoolBlockWithTransactions, AuditTransaction } from '../mempool.interfaces';
|
||||
import { PairingHeap } from '../utils/pairing-heap';
|
||||
import { Common } from './common';
|
||||
import { parentPort } from 'worker_threads';
|
||||
|
||||
let mempool: { [txid: string]: ThreadTransaction } = {};
|
||||
|
||||
if (parentPort) {
|
||||
parentPort.on('message', (params: { mempool: { [txid: string]: TransactionExtended }, blockLimit: number, weightLimit: number | null, condenseRest: boolean}) => {
|
||||
const { mempool, blocks } = makeBlockTemplates(params);
|
||||
parentPort.on('message', (params) => {
|
||||
if (params.type === 'set') {
|
||||
mempool = params.mempool;
|
||||
} else if (params.type === 'update') {
|
||||
params.added.forEach(tx => {
|
||||
mempool[tx.txid] = tx;
|
||||
});
|
||||
params.removed.forEach(txid => {
|
||||
delete mempool[txid];
|
||||
});
|
||||
}
|
||||
|
||||
const { blocks, clusters } = makeBlockTemplates(mempool);
|
||||
|
||||
// return the result to main thread.
|
||||
if (parentPort) {
|
||||
parentPort.postMessage({ mempool, blocks });
|
||||
parentPort.postMessage({ blocks, clusters });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -19,35 +32,24 @@ 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)
|
||||
*
|
||||
* blockLimit: number of blocks to build in total.
|
||||
* weightLimit: maximum weight of transactions to consider using the selection algorithm.
|
||||
* if weightLimit is significantly lower than the mempool size, results may start to diverge from getBlockTemplate
|
||||
* condenseRest: whether to ignore excess transactions or append them to the final block.
|
||||
*/
|
||||
function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }: { mempool: { [txid: string]: TransactionExtended }, blockLimit: number, weightLimit?: number | null, condenseRest?: boolean | null })
|
||||
: { mempool: { [txid: string]: TransactionExtended }, blocks: MempoolBlockWithTransactions[] } {
|
||||
function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
|
||||
: { blocks: ThreadTransaction[][], clusters: { [root: string]: string[] } } {
|
||||
const start = Date.now();
|
||||
const auditPool: { [txid: string]: AuditTransaction } = {};
|
||||
const mempoolArray: AuditTransaction[] = [];
|
||||
const restOfArray: TransactionExtended[] = [];
|
||||
const restOfArray: ThreadTransaction[] = [];
|
||||
const cpfpClusters: { [root: string]: string[] } = {};
|
||||
|
||||
let weight = 0;
|
||||
const maxWeight = weightLimit ? Math.max(4_000_000 * blockLimit, weightLimit) : Infinity;
|
||||
// grab the top feerate txs up to maxWeight
|
||||
Object.values(mempool).sort((a, b) => b.feePerVsize - a.feePerVsize).forEach(tx => {
|
||||
weight += tx.weight;
|
||||
if (weight >= maxWeight) {
|
||||
restOfArray.push(tx);
|
||||
return;
|
||||
}
|
||||
// initializing everything up front helps V8 optimize property access later
|
||||
auditPool[tx.txid] = {
|
||||
txid: tx.txid,
|
||||
fee: tx.fee,
|
||||
size: tx.size,
|
||||
weight: tx.weight,
|
||||
feePerVsize: tx.feePerVsize,
|
||||
effectiveFeePerVsize: tx.feePerVsize,
|
||||
vin: tx.vin,
|
||||
relativesSet: false,
|
||||
ancestorMap: new Map<string, AuditTransaction>(),
|
||||
|
@ -74,7 +76,7 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
|||
|
||||
// Build blocks by greedily choosing the highest feerate package
|
||||
// (i.e. the package rooted in the transaction with the best ancestor score)
|
||||
const blocks: MempoolBlockWithTransactions[] = [];
|
||||
const blocks: ThreadTransaction[][] = [];
|
||||
let blockWeight = 4000;
|
||||
let blockSize = 0;
|
||||
let transactions: AuditTransaction[] = [];
|
||||
|
@ -82,7 +84,7 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
|||
let overflow: AuditTransaction[] = [];
|
||||
let failures = 0;
|
||||
let top = 0;
|
||||
while ((top < mempoolArray.length || !modified.isEmpty()) && (condenseRest || blocks.length < blockLimit)) {
|
||||
while ((top < mempoolArray.length || !modified.isEmpty())) {
|
||||
// skip invalid transactions
|
||||
while (top < mempoolArray.length && (mempoolArray[top].used || mempoolArray[top].modified)) {
|
||||
top++;
|
||||
|
@ -107,9 +109,13 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
|||
// Check if the package fits into this block
|
||||
if (blockWeight + nextTx.ancestorWeight < config.MEMPOOL.BLOCK_WEIGHT_UNITS) {
|
||||
const ancestors: AuditTransaction[] = Array.from(nextTx.ancestorMap.values());
|
||||
const descendants: AuditTransaction[] = [];
|
||||
// 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);
|
||||
isCluster = true;
|
||||
}
|
||||
const effectiveFeeRate = nextTx.ancestorFee / (nextTx.ancestorWeight / 4);
|
||||
const used: AuditTransaction[] = [];
|
||||
while (sortedTxSet.length) {
|
||||
|
@ -119,21 +125,9 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
|||
ancestor.usedBy = nextTx.txid;
|
||||
// update original copy of this tx with effective fee rate & relatives data
|
||||
mempoolTx.effectiveFeePerVsize = effectiveFeeRate;
|
||||
mempoolTx.ancestors = sortedTxSet.map((a) => {
|
||||
return {
|
||||
txid: a.txid,
|
||||
fee: a.fee,
|
||||
weight: a.weight,
|
||||
};
|
||||
}).reverse();
|
||||
mempoolTx.descendants = descendants.map((a) => {
|
||||
return {
|
||||
txid: a.txid,
|
||||
fee: a.fee,
|
||||
weight: a.weight,
|
||||
};
|
||||
});
|
||||
descendants.push(ancestor);
|
||||
if (isCluster) {
|
||||
mempoolTx.cpfpRoot = nextTx.txid;
|
||||
}
|
||||
mempoolTx.cpfpChecked = true;
|
||||
transactions.push(ancestor);
|
||||
blockSize += ancestor.size;
|
||||
|
@ -159,10 +153,10 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
|||
// this block is full
|
||||
const exceededPackageTries = failures > 1000 && blockWeight > (config.MEMPOOL.BLOCK_WEIGHT_UNITS - 4000);
|
||||
const queueEmpty = top >= mempoolArray.length && modified.isEmpty();
|
||||
if ((exceededPackageTries || queueEmpty) && (!condenseRest || blocks.length < blockLimit - 1)) {
|
||||
if ((exceededPackageTries || queueEmpty) && blocks.length < 7) {
|
||||
// construct this block
|
||||
if (transactions.length) {
|
||||
blocks.push(dataToMempoolBlocks(transactions.map(t => mempool[t.txid]), blockSize, blockWeight, blocks.length));
|
||||
blocks.push(transactions.map(t => mempool[t.txid]));
|
||||
}
|
||||
// reset for the next block
|
||||
transactions = [];
|
||||
|
@ -181,55 +175,40 @@ function makeBlockTemplates({ mempool, blockLimit, weightLimit, condenseRest }:
|
|||
overflow = [];
|
||||
}
|
||||
}
|
||||
if (condenseRest) {
|
||||
// pack any leftover transactions into the last block
|
||||
for (const tx of overflow) {
|
||||
if (!tx || tx?.used) {
|
||||
continue;
|
||||
}
|
||||
blockWeight += tx.weight;
|
||||
blockSize += tx.size;
|
||||
const mempoolTx = mempool[tx.txid];
|
||||
// update original copy of this tx with effective fee rate & relatives data
|
||||
mempoolTx.effectiveFeePerVsize = tx.score;
|
||||
mempoolTx.ancestors = (Array.from(tx.ancestorMap?.values()) as AuditTransaction[]).map((a) => {
|
||||
return {
|
||||
txid: a.txid,
|
||||
fee: a.fee,
|
||||
weight: a.weight,
|
||||
};
|
||||
});
|
||||
mempoolTx.bestDescendant = null;
|
||||
mempoolTx.cpfpChecked = true;
|
||||
transactions.push(tx);
|
||||
tx.used = true;
|
||||
// pack any leftover transactions into the last block
|
||||
for (const tx of overflow) {
|
||||
if (!tx || tx?.used) {
|
||||
continue;
|
||||
}
|
||||
const blockTransactions = transactions.map(t => mempool[t.txid]);
|
||||
restOfArray.forEach(tx => {
|
||||
blockWeight += tx.weight;
|
||||
blockSize += tx.size;
|
||||
tx.effectiveFeePerVsize = tx.feePerVsize;
|
||||
tx.cpfpChecked = false;
|
||||
tx.ancestors = [];
|
||||
tx.bestDescendant = null;
|
||||
blockTransactions.push(tx);
|
||||
});
|
||||
if (blockTransactions.length) {
|
||||
blocks.push(dataToMempoolBlocks(blockTransactions, blockSize, blockWeight, blocks.length));
|
||||
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;
|
||||
}
|
||||
transactions = [];
|
||||
} else if (transactions.length) {
|
||||
blocks.push(dataToMempoolBlocks(transactions.map(t => mempool[t.txid]), blockSize, blockWeight, blocks.length));
|
||||
mempoolTx.cpfpChecked = true;
|
||||
transactions.push(tx);
|
||||
tx.used = true;
|
||||
}
|
||||
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);
|
||||
}
|
||||
transactions = [];
|
||||
|
||||
const end = Date.now();
|
||||
const time = end - start;
|
||||
logger.debug('Mempool templates calculated in ' + time / 1000 + ' seconds');
|
||||
|
||||
return {
|
||||
mempool,
|
||||
blocks
|
||||
};
|
||||
return { blocks, clusters: cpfpClusters };
|
||||
}
|
||||
|
||||
// traverse in-mempool ancestors
|
||||
|
@ -239,9 +218,9 @@ function setRelatives(
|
|||
mempool: { [txid: string]: AuditTransaction },
|
||||
): void {
|
||||
for (const parent of tx.vin) {
|
||||
const parentTx = mempool[parent.txid];
|
||||
if (parentTx && !tx.ancestorMap?.has(parent.txid)) {
|
||||
tx.ancestorMap.set(parent.txid, parentTx);
|
||||
const parentTx = mempool[parent];
|
||||
if (parentTx && !tx.ancestorMap?.has(parent)) {
|
||||
tx.ancestorMap.set(parent, parentTx);
|
||||
parentTx.children.add(tx);
|
||||
// visit each node only once
|
||||
if (!parentTx.relativesSet) {
|
||||
|
@ -312,27 +291,4 @@ function updateDescendants(
|
|||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dataToMempoolBlocks(transactions: TransactionExtended[],
|
||||
blockSize: number, blockWeight: number, blocksIndex: number): MempoolBlockWithTransactions {
|
||||
let rangeLength = 4;
|
||||
if (blocksIndex === 0) {
|
||||
rangeLength = 8;
|
||||
}
|
||||
if (transactions.length > 4000) {
|
||||
rangeLength = 6;
|
||||
} else if (transactions.length > 10000) {
|
||||
rangeLength = 8;
|
||||
}
|
||||
return {
|
||||
blockSize: blockSize,
|
||||
blockVSize: blockWeight / 4,
|
||||
nTx: transactions.length,
|
||||
totalFees: transactions.reduce((acc, cur) => acc + cur.fee, 0),
|
||||
medianFee: Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
|
||||
feeRange: Common.getFeesInRange(transactions, rangeLength),
|
||||
transactionIds: transactions.map((tx) => tx.txid),
|
||||
transactions: transactions.map((tx) => Common.stripTransaction(tx)),
|
||||
};
|
||||
}
|
|
@ -251,7 +251,7 @@ class WebsocketHandler {
|
|||
}
|
||||
|
||||
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
||||
await mempoolBlocks.makeBlockTemplates(newMempool, 8, null, true);
|
||||
await mempoolBlocks.updateBlockTemplates(newMempool, newTransactions, deletedTransactions.map(tx => tx.txid));
|
||||
} else {
|
||||
mempoolBlocks.updateMempoolBlocks(newMempool);
|
||||
}
|
||||
|
@ -419,7 +419,7 @@ class WebsocketHandler {
|
|||
const _memPool = memPool.getMempool();
|
||||
|
||||
if (config.MEMPOOL.ADVANCED_GBT_AUDIT) {
|
||||
await mempoolBlocks.makeBlockTemplates(_memPool, 2);
|
||||
await mempoolBlocks.makeBlockTemplates(_memPool);
|
||||
} else {
|
||||
mempoolBlocks.updateMempoolBlocks(_memPool);
|
||||
}
|
||||
|
@ -462,13 +462,15 @@ 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);
|
||||
}
|
||||
|
||||
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
|
||||
await mempoolBlocks.makeBlockTemplates(_memPool, 8, null, true);
|
||||
await mempoolBlocks.updateBlockTemplates(_memPool, [], removed);
|
||||
} else {
|
||||
mempoolBlocks.updateMempoolBlocks(_memPool);
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ interface IConfig {
|
|||
POOLS_JSON_TREE_URL: string,
|
||||
ADVANCED_GBT_AUDIT: boolean;
|
||||
ADVANCED_GBT_MEMPOOL: boolean;
|
||||
TRANSACTION_INDEXING: boolean;
|
||||
CPFP_INDEXING: boolean;
|
||||
};
|
||||
ESPLORA: {
|
||||
REST_API_URL: string;
|
||||
|
@ -152,7 +152,7 @@ const defaults: IConfig = {
|
|||
'POOLS_JSON_TREE_URL': 'https://api.github.com/repos/mempool/mining-pools/git/trees/master',
|
||||
'ADVANCED_GBT_AUDIT': false,
|
||||
'ADVANCED_GBT_MEMPOOL': false,
|
||||
'TRANSACTION_INDEXING': false,
|
||||
'CPFP_INDEXING': false,
|
||||
},
|
||||
'ESPLORA': {
|
||||
'REST_API_URL': 'http://127.0.0.1:3000',
|
||||
|
|
|
@ -32,22 +32,27 @@ class Logger {
|
|||
local7: 23
|
||||
};
|
||||
|
||||
public tags = {
|
||||
mining: 'Mining',
|
||||
ln: 'Lightning',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
public emerg: ((msg: string) => void);
|
||||
public emerg: ((msg: string, tag?: string) => void);
|
||||
// @ts-ignore
|
||||
public alert: ((msg: string) => void);
|
||||
public alert: ((msg: string, tag?: string) => void);
|
||||
// @ts-ignore
|
||||
public crit: ((msg: string) => void);
|
||||
public crit: ((msg: string, tag?: string) => void);
|
||||
// @ts-ignore
|
||||
public err: ((msg: string) => void);
|
||||
public err: ((msg: string, tag?: string) => void);
|
||||
// @ts-ignore
|
||||
public warn: ((msg: string) => void);
|
||||
public warn: ((msg: string, tag?: string) => void);
|
||||
// @ts-ignore
|
||||
public notice: ((msg: string) => void);
|
||||
public notice: ((msg: string, tag?: string) => void);
|
||||
// @ts-ignore
|
||||
public info: ((msg: string) => void);
|
||||
public info: ((msg: string, tag?: string) => void);
|
||||
// @ts-ignore
|
||||
public debug: ((msg: string) => void);
|
||||
public debug: ((msg: string, tag?: string) => void);
|
||||
|
||||
private name = 'mempool';
|
||||
private client: dgram.Socket;
|
||||
|
@ -66,8 +71,8 @@ class Logger {
|
|||
|
||||
private addprio(prio): void {
|
||||
this[prio] = (function(_this) {
|
||||
return function(msg) {
|
||||
return _this.msg(prio, msg);
|
||||
return function(msg, tag?: string) {
|
||||
return _this.msg(prio, msg, tag);
|
||||
};
|
||||
})(this);
|
||||
}
|
||||
|
@ -85,7 +90,7 @@ class Logger {
|
|||
return '';
|
||||
}
|
||||
|
||||
private msg(priority, msg) {
|
||||
private msg(priority, msg, tag?: string) {
|
||||
let consolemsg, prionum, syslogmsg;
|
||||
if (typeof msg === 'string' && msg.length > 0) {
|
||||
while (msg[msg.length - 1].charCodeAt(0) === 10) {
|
||||
|
@ -94,10 +99,10 @@ class Logger {
|
|||
}
|
||||
const network = this.network ? ' <' + this.network + '>' : '';
|
||||
prionum = Logger.priorities[priority] || Logger.priorities.info;
|
||||
consolemsg = `${this.ts()} [${process.pid}] ${priority.toUpperCase()}:${network} ${msg}`;
|
||||
consolemsg = `${this.ts()} [${process.pid}] ${priority.toUpperCase()}:${network} ${tag ? '[' + tag + '] ' : ''}${msg}`;
|
||||
|
||||
if (config.SYSLOG.ENABLED && Logger.priorities[priority] <= Logger.priorities[config.SYSLOG.MIN_PRIORITY]) {
|
||||
syslogmsg = `<${(Logger.facilities[config.SYSLOG.FACILITY] * 8 + prionum)}> ${this.name}[${process.pid}]: ${priority.toUpperCase()}${network} ${msg}`;
|
||||
syslogmsg = `<${(Logger.facilities[config.SYSLOG.FACILITY] * 8 + prionum)}> ${this.name}[${process.pid}]: ${priority.toUpperCase()}${network} ${tag ? '[' + tag + '] ' : ''}${msg}`;
|
||||
this.syslog(syslogmsg);
|
||||
}
|
||||
if (Logger.priorities[priority] > Logger.priorities[config.MEMPOOL.STDOUT_LOG_MIN_PRIORITY]) {
|
||||
|
|
|
@ -81,10 +81,10 @@ export interface TransactionExtended extends IEsploraApi.Transaction {
|
|||
export interface AuditTransaction {
|
||||
txid: string;
|
||||
fee: number;
|
||||
size: number;
|
||||
weight: number;
|
||||
feePerVsize: number;
|
||||
vin: IEsploraApi.Vin[];
|
||||
effectiveFeePerVsize: number;
|
||||
vin: string[];
|
||||
relativesSet: boolean;
|
||||
ancestorMap: Map<string, AuditTransaction>;
|
||||
children: Set<AuditTransaction>;
|
||||
|
@ -96,6 +96,17 @@ export interface AuditTransaction {
|
|||
modifiedNode: HeapNode<AuditTransaction>;
|
||||
}
|
||||
|
||||
export interface ThreadTransaction {
|
||||
txid: string;
|
||||
fee: number;
|
||||
weight: number;
|
||||
feePerVsize: number;
|
||||
effectiveFeePerVsize?: number;
|
||||
vin: string[];
|
||||
cpfpRoot?: string;
|
||||
cpfpChecked?: boolean;
|
||||
}
|
||||
|
||||
export interface Ancestor {
|
||||
txid: string;
|
||||
weight: number;
|
||||
|
@ -263,6 +274,7 @@ export interface IBackendInfo {
|
|||
hostname: string;
|
||||
gitCommit: string;
|
||||
version: string;
|
||||
lightning: boolean;
|
||||
}
|
||||
|
||||
export interface IDifficultyAdjustment {
|
||||
|
@ -326,4 +338,4 @@ export interface IOldestNodes {
|
|||
updatedAt?: number,
|
||||
city?: any,
|
||||
country?: any,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import HashratesRepository from './HashratesRepository';
|
|||
import { escape } from 'mysql2';
|
||||
import BlocksSummariesRepository from './BlocksSummariesRepository';
|
||||
import DifficultyAdjustmentsRepository from './DifficultyAdjustmentsRepository';
|
||||
import bitcoinClient from '../api/bitcoin/bitcoin-client';
|
||||
import config from '../config';
|
||||
|
||||
class BlocksRepository {
|
||||
/**
|
||||
|
@ -667,16 +669,32 @@ class BlocksRepository {
|
|||
*/
|
||||
public async $getCPFPUnindexedBlocks(): Promise<any[]> {
|
||||
try {
|
||||
const [rows]: any = await DB.query(`SELECT height, hash FROM blocks WHERE cpfp_indexed = 0 ORDER BY height DESC`);
|
||||
return rows;
|
||||
const blockchainInfo = await bitcoinClient.getBlockchainInfo();
|
||||
const currentBlockHeight = blockchainInfo.blocks;
|
||||
let indexingBlockAmount = Math.min(config.MEMPOOL.INDEXING_BLOCKS_AMOUNT, currentBlockHeight);
|
||||
if (indexingBlockAmount <= -1) {
|
||||
indexingBlockAmount = currentBlockHeight + 1;
|
||||
}
|
||||
const minHeight = Math.max(0, currentBlockHeight - indexingBlockAmount + 1);
|
||||
|
||||
const [rows]: any[] = await DB.query(`
|
||||
SELECT height
|
||||
FROM compact_cpfp_clusters
|
||||
WHERE height <= ? AND height >= ?
|
||||
ORDER BY height DESC;
|
||||
`, [currentBlockHeight, minHeight]);
|
||||
|
||||
const indexedHeights = {};
|
||||
rows.forEach((row) => { indexedHeights[row.height] = true; });
|
||||
const allHeights: number[] = Array.from(Array(currentBlockHeight - minHeight + 1).keys(), n => n + minHeight).reverse();
|
||||
const unindexedHeights = allHeights.filter(x => !indexedHeights[x]);
|
||||
|
||||
return unindexedHeights;
|
||||
} catch (e) {
|
||||
logger.err('Cannot fetch CPFP unindexed blocks. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async $setCPFPIndexed(hash: string): Promise<void> {
|
||||
await DB.query(`UPDATE blocks SET cpfp_indexed = 1 WHERE hash = ?`, [hash]);
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,34 +1,151 @@
|
|||
import cluster, { Cluster } from 'cluster';
|
||||
import { RowDataPacket } from 'mysql2';
|
||||
import DB from '../database';
|
||||
import logger from '../logger';
|
||||
import { Ancestor } from '../mempool.interfaces';
|
||||
import transactionRepository from '../repositories/TransactionRepository';
|
||||
|
||||
class CpfpRepository {
|
||||
public async $saveCluster(height: number, txs: Ancestor[], effectiveFeePerVsize: number): Promise<void> {
|
||||
public async $saveCluster(clusterRoot: string, height: number, txs: Ancestor[], effectiveFeePerVsize: number): Promise<boolean> {
|
||||
if (!txs[0]) {
|
||||
return false;
|
||||
}
|
||||
// skip clusters of transactions with the same fees
|
||||
const roundedEffectiveFee = Math.round(effectiveFeePerVsize * 100) / 100;
|
||||
const equalFee = txs.reduce((acc, tx) => {
|
||||
return (acc && Math.round(((tx.fee || 0) / (tx.weight / 4)) * 100) / 100 === roundedEffectiveFee);
|
||||
}, true);
|
||||
if (equalFee) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const txsJson = JSON.stringify(txs);
|
||||
const packedTxs = Buffer.from(this.pack(txs));
|
||||
await DB.query(
|
||||
`
|
||||
INSERT INTO cpfp_clusters(root, height, txs, fee_rate)
|
||||
VALUE (?, ?, ?, ?)
|
||||
INSERT INTO compact_cpfp_clusters(root, height, txs, fee_rate)
|
||||
VALUE (UNHEX(?), ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
height = ?,
|
||||
txs = ?,
|
||||
fee_rate = ?
|
||||
`,
|
||||
[txs[0].txid, height, txsJson, effectiveFeePerVsize, height, txsJson, effectiveFeePerVsize, height]
|
||||
[clusterRoot, height, packedTxs, effectiveFeePerVsize, height, packedTxs, effectiveFeePerVsize]
|
||||
);
|
||||
const maxChunk = 10;
|
||||
let chunkIndex = 0;
|
||||
while (chunkIndex < txs.length) {
|
||||
const chunk = txs.slice(chunkIndex, chunkIndex + maxChunk).map(tx => {
|
||||
return { txid: tx.txid, cluster: clusterRoot };
|
||||
});
|
||||
await transactionRepository.$batchSetCluster(chunk);
|
||||
chunkIndex += maxChunk;
|
||||
}
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
logger.err(`Cannot save cpfp cluster into db. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async $batchSaveClusters(clusters: { root: string, height: number, txs: any, effectiveFeePerVsize: number}[]): Promise<boolean> {
|
||||
try {
|
||||
const clusterValues: any[] = [];
|
||||
const txs: any[] = [];
|
||||
|
||||
for (const cluster of clusters) {
|
||||
if (cluster.txs?.length > 1) {
|
||||
const roundedEffectiveFee = Math.round(cluster.effectiveFeePerVsize * 100) / 100;
|
||||
const equalFee = cluster.txs.reduce((acc, tx) => {
|
||||
return (acc && Math.round(((tx.fee || 0) / (tx.weight / 4)) * 100) / 100 === roundedEffectiveFee);
|
||||
}, true);
|
||||
if (!equalFee) {
|
||||
clusterValues.push([
|
||||
cluster.root,
|
||||
cluster.height,
|
||||
Buffer.from(this.pack(cluster.txs)),
|
||||
cluster.effectiveFeePerVsize
|
||||
]);
|
||||
for (const tx of cluster.txs) {
|
||||
txs.push({ txid: tx.txid, cluster: cluster.root });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!clusterValues.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const maxChunk = 100;
|
||||
let chunkIndex = 0;
|
||||
// insert transactions in batches of up to 100 rows
|
||||
while (chunkIndex < txs.length) {
|
||||
const chunk = txs.slice(chunkIndex, chunkIndex + maxChunk);
|
||||
await transactionRepository.$batchSetCluster(chunk);
|
||||
chunkIndex += maxChunk;
|
||||
}
|
||||
|
||||
chunkIndex = 0;
|
||||
// insert clusters in batches of up to 100 rows
|
||||
while (chunkIndex < clusterValues.length) {
|
||||
const chunk = clusterValues.slice(chunkIndex, chunkIndex + maxChunk);
|
||||
let query = `
|
||||
INSERT IGNORE INTO compact_cpfp_clusters(root, height, txs, fee_rate)
|
||||
VALUES
|
||||
`;
|
||||
query += chunk.map(chunk => {
|
||||
return (' (UNHEX(?), ?, ?, ?)');
|
||||
}) + ';';
|
||||
const values = chunk.flat();
|
||||
await DB.query(
|
||||
query,
|
||||
values
|
||||
);
|
||||
chunkIndex += maxChunk;
|
||||
}
|
||||
return true;
|
||||
} catch (e: any) {
|
||||
logger.err(`Cannot save cpfp clusters into db. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async $getCluster(clusterRoot: string): Promise<Cluster> {
|
||||
const [clusterRows]: any = await DB.query(
|
||||
`
|
||||
SELECT *
|
||||
FROM compact_cpfp_clusters
|
||||
WHERE root = UNHEX(?)
|
||||
`,
|
||||
[clusterRoot]
|
||||
);
|
||||
const cluster = clusterRows[0];
|
||||
cluster.txs = this.unpack(cluster.txs);
|
||||
return cluster;
|
||||
}
|
||||
|
||||
public async $deleteClustersFrom(height: number): Promise<void> {
|
||||
logger.info(`Delete newer cpfp clusters from height ${height} from the database`);
|
||||
try {
|
||||
const [rows] = await DB.query(
|
||||
`
|
||||
SELECT txs, height, root from compact_cpfp_clusters
|
||||
WHERE height >= ?
|
||||
`,
|
||||
[height]
|
||||
) as RowDataPacket[][];
|
||||
if (rows?.length) {
|
||||
for (let clusterToDelete of rows) {
|
||||
const txs = this.unpack(clusterToDelete.txs);
|
||||
for (let tx of txs) {
|
||||
await transactionRepository.$removeTransaction(tx.txid);
|
||||
}
|
||||
}
|
||||
}
|
||||
await DB.query(
|
||||
`
|
||||
DELETE from cpfp_clusters
|
||||
DELETE from compact_cpfp_clusters
|
||||
WHERE height >= ?
|
||||
`,
|
||||
[height]
|
||||
|
@ -38,6 +155,70 @@ class CpfpRepository {
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// insert a dummy row to mark that we've indexed as far as this block
|
||||
public async $insertProgressMarker(height: number): Promise<void> {
|
||||
try {
|
||||
const [rows]: any = await DB.query(
|
||||
`
|
||||
SELECT root
|
||||
FROM compact_cpfp_clusters
|
||||
WHERE height = ?
|
||||
`,
|
||||
[height]
|
||||
);
|
||||
if (!rows?.length) {
|
||||
const rootBuffer = Buffer.alloc(32);
|
||||
rootBuffer.writeInt32LE(height);
|
||||
await DB.query(
|
||||
`
|
||||
INSERT INTO compact_cpfp_clusters(root, height, fee_rate)
|
||||
VALUE (?, ?, ?)
|
||||
`,
|
||||
[rootBuffer, height, 0]
|
||||
);
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.err(`Cannot insert cpfp progress marker. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public pack(txs: Ancestor[]): ArrayBuffer {
|
||||
const buf = new ArrayBuffer(44 * txs.length);
|
||||
const view = new DataView(buf);
|
||||
txs.forEach((tx, i) => {
|
||||
const offset = i * 44;
|
||||
for (let x = 0; x < 32; x++) {
|
||||
// store txid in little-endian
|
||||
view.setUint8(offset + (31 - x), parseInt(tx.txid.slice(x * 2, (x * 2) + 2), 16));
|
||||
}
|
||||
view.setUint32(offset + 32, tx.weight);
|
||||
view.setBigUint64(offset + 36, BigInt(Math.round(tx.fee)));
|
||||
});
|
||||
return buf;
|
||||
}
|
||||
|
||||
public unpack(buf: Buffer): Ancestor[] {
|
||||
if (!buf) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const arrayBuffer = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
|
||||
const txs: Ancestor[] = [];
|
||||
const view = new DataView(arrayBuffer);
|
||||
for (let offset = 0; offset < arrayBuffer.byteLength; offset += 44) {
|
||||
const txid = Array.from(new Uint8Array(arrayBuffer, offset, 32)).reverse().map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
const weight = view.getUint32(offset + 32);
|
||||
const fee = Number(view.getBigUint64(offset + 36));
|
||||
txs.push({
|
||||
txid,
|
||||
weight,
|
||||
fee
|
||||
});
|
||||
}
|
||||
return txs;
|
||||
}
|
||||
}
|
||||
|
||||
export default new CpfpRepository();
|
|
@ -1,6 +1,7 @@
|
|||
import DB from '../database';
|
||||
import logger from '../logger';
|
||||
import { Ancestor, CpfpInfo } from '../mempool.interfaces';
|
||||
import cpfpRepository from './CpfpRepository';
|
||||
|
||||
interface CpfpSummary {
|
||||
txid: string;
|
||||
|
@ -12,20 +13,20 @@ interface CpfpSummary {
|
|||
}
|
||||
|
||||
class TransactionRepository {
|
||||
public async $setCluster(txid: string, cluster: string): Promise<void> {
|
||||
public async $setCluster(txid: string, clusterRoot: string): Promise<void> {
|
||||
try {
|
||||
await DB.query(
|
||||
`
|
||||
INSERT INTO transactions
|
||||
INSERT INTO compact_transactions
|
||||
(
|
||||
txid,
|
||||
cluster
|
||||
)
|
||||
VALUE (?, ?)
|
||||
VALUE (UNHEX(?), UNHEX(?))
|
||||
ON DUPLICATE KEY UPDATE
|
||||
cluster = ?
|
||||
cluster = UNHEX(?)
|
||||
;`,
|
||||
[txid, cluster, cluster]
|
||||
[txid, clusterRoot, clusterRoot]
|
||||
);
|
||||
} catch (e: any) {
|
||||
logger.err(`Cannot save transaction cpfp cluster into db. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
|
@ -33,18 +34,45 @@ class TransactionRepository {
|
|||
}
|
||||
}
|
||||
|
||||
public async $getCpfpInfo(txid: string): Promise<CpfpInfo | void> {
|
||||
public async $batchSetCluster(txs): Promise<void> {
|
||||
try {
|
||||
let query = `
|
||||
SELECT *
|
||||
FROM transactions
|
||||
LEFT JOIN cpfp_clusters AS cluster ON cluster.root = transactions.cluster
|
||||
WHERE transactions.txid = ?
|
||||
INSERT IGNORE INTO compact_transactions
|
||||
(
|
||||
txid,
|
||||
cluster
|
||||
)
|
||||
VALUES
|
||||
`;
|
||||
const [rows]: any = await DB.query(query, [txid]);
|
||||
if (rows.length) {
|
||||
rows[0].txs = JSON.parse(rows[0].txs) as Ancestor[];
|
||||
return this.convertCpfp(rows[0]);
|
||||
query += txs.map(tx => {
|
||||
return (' (UNHEX(?), UNHEX(?))');
|
||||
}) + ';';
|
||||
const values = txs.map(tx => [tx.txid, tx.cluster]).flat();
|
||||
await DB.query(
|
||||
query,
|
||||
values
|
||||
);
|
||||
} catch (e: any) {
|
||||
logger.err(`Cannot save cpfp transactions into db. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public async $getCpfpInfo(txid: string): Promise<CpfpInfo | void> {
|
||||
try {
|
||||
const [txRows]: any = await DB.query(
|
||||
`
|
||||
SELECT HEX(txid) as id, HEX(cluster) as root
|
||||
FROM compact_transactions
|
||||
WHERE txid = UNHEX(?)
|
||||
`,
|
||||
[txid]
|
||||
);
|
||||
if (txRows.length && txRows[0].root != null) {
|
||||
const txid = txRows[0].id.toLowerCase();
|
||||
const clusterId = txRows[0].root.toLowerCase();
|
||||
const cluster = await cpfpRepository.$getCluster(clusterId);
|
||||
return this.convertCpfp(txid, cluster);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('Cannot get transaction cpfp info from db. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
|
@ -52,12 +80,23 @@ class TransactionRepository {
|
|||
}
|
||||
}
|
||||
|
||||
private convertCpfp(cpfp: CpfpSummary): CpfpInfo {
|
||||
public async $removeTransaction(txid: string): Promise<void> {
|
||||
await DB.query(
|
||||
`
|
||||
DELETE FROM compact_transactions
|
||||
WHERE txid = UNHEX(?)
|
||||
`,
|
||||
[txid]
|
||||
);
|
||||
}
|
||||
|
||||
private convertCpfp(txid, cluster): CpfpInfo {
|
||||
const descendants: Ancestor[] = [];
|
||||
const ancestors: Ancestor[] = [];
|
||||
let matched = false;
|
||||
for (const tx of cpfp.txs) {
|
||||
if (tx.txid === cpfp.txid) {
|
||||
|
||||
for (const tx of cluster.txs) {
|
||||
if (tx.txid === txid) {
|
||||
matched = true;
|
||||
} else if (!matched) {
|
||||
descendants.push(tx);
|
||||
|
@ -68,7 +107,6 @@ class TransactionRepository {
|
|||
return {
|
||||
descendants,
|
||||
ancestors,
|
||||
effectiveFeePerVsize: cpfp.fee_rate
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ class NetworkSyncService {
|
|||
constructor() {}
|
||||
|
||||
public async $startService(): Promise<void> {
|
||||
logger.info('Starting lightning network sync service');
|
||||
logger.info(`Starting lightning network sync service`, logger.tags.ln);
|
||||
|
||||
this.loggerTimer = new Date().getTime() / 1000;
|
||||
|
||||
|
@ -33,11 +33,11 @@ class NetworkSyncService {
|
|||
private async $runTasks(): Promise<void> {
|
||||
const taskStartTime = Date.now();
|
||||
try {
|
||||
logger.info(`Updating nodes and channels`);
|
||||
logger.debug(`Updating nodes and channels`, logger.tags.ln);
|
||||
|
||||
const networkGraph = await lightningApi.$getNetworkGraph();
|
||||
if (networkGraph.nodes.length === 0 || networkGraph.edges.length === 0) {
|
||||
logger.info(`LN Network graph is empty, retrying in 10 seconds`);
|
||||
logger.info(`LN Network graph is empty, retrying in 10 seconds`, logger.tags.ln);
|
||||
setTimeout(() => { this.$runTasks(); }, 10000);
|
||||
return;
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ class NetworkSyncService {
|
|||
}
|
||||
|
||||
} catch (e) {
|
||||
logger.err('$runTasks() error: ' + (e instanceof Error ? e.message : e));
|
||||
logger.err(`$runTasks() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
}
|
||||
|
||||
setTimeout(() => { this.$runTasks(); }, Math.max(1, (1000 * config.LIGHTNING.GRAPH_REFRESH_INTERVAL) - (Date.now() - taskStartTime)));
|
||||
|
@ -79,8 +79,8 @@ class NetworkSyncService {
|
|||
++progress;
|
||||
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Updating node ${progress}/${nodes.length}`);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.debug(`Updating node ${progress}/${nodes.length}`, logger.tags.ln);
|
||||
this.loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ class NetworkSyncService {
|
|||
deletedRecords += await NodeRecordsRepository.$deleteUnusedRecords(node.pub_key, customRecordTypes);
|
||||
}
|
||||
}
|
||||
logger.info(`${progress} nodes updated. ${deletedSockets} sockets deleted. ${deletedRecords} custom records deleted.`);
|
||||
logger.debug(`${progress} nodes updated. ${deletedSockets} sockets deleted. ${deletedRecords} custom records deleted.`);
|
||||
|
||||
// If a channel if not present in the graph, mark it as inactive
|
||||
await nodesApi.$setNodesInactive(graphNodesPubkeys);
|
||||
|
@ -138,18 +138,18 @@ class NetworkSyncService {
|
|||
++progress;
|
||||
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Updating channel ${progress}/${channels.length}`);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.debug(`Updating channel ${progress}/${channels.length}`, logger.tags.ln);
|
||||
this.loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`${progress} channels updated`);
|
||||
logger.debug(`${progress} channels updated`, logger.tags.ln);
|
||||
|
||||
// If a channel if not present in the graph, mark it as inactive
|
||||
await channelsApi.$setChannelsInactive(graphChannelsIds);
|
||||
} catch (e) {
|
||||
logger.err(`Cannot update channel list. Reason: ${(e instanceof Error ? e.message : e)}`);
|
||||
logger.err(` Cannot update channel list. Reason: ${(e instanceof Error ? e.message : e)}`, logger.tags.ln);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,26 +184,28 @@ class NetworkSyncService {
|
|||
if (lowest < node.first_seen) {
|
||||
const query = `UPDATE nodes SET first_seen = FROM_UNIXTIME(?) WHERE public_key = ?`;
|
||||
const params = [lowest, node.public_key];
|
||||
++updated;
|
||||
await DB.query(query, params);
|
||||
}
|
||||
++progress;
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Updating node first seen date ${progress}/${nodes.length}`);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.debug(`Updating node first seen date ${progress}/${nodes.length}`, logger.tags.ln);
|
||||
this.loggerTimer = new Date().getTime() / 1000;
|
||||
++updated;
|
||||
}
|
||||
}
|
||||
logger.info(`Updated ${updated} node first seen dates`);
|
||||
if (updated > 0) {
|
||||
logger.debug(`Updated ${updated} node first seen dates`, logger.tags.ln);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('$updateNodeFirstSeen() error: ' + (e instanceof Error ? e.message : e));
|
||||
logger.err(`$updateNodeFirstSeen() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
}
|
||||
}
|
||||
|
||||
private async $lookUpCreationDateFromChain(): Promise<void> {
|
||||
let progress = 0;
|
||||
|
||||
logger.info(`Running channel creation date lookup`);
|
||||
logger.debug(`Running channel creation date lookup`, logger.tags.ln);
|
||||
try {
|
||||
const channels = await channelsApi.$getChannelsWithoutCreatedDate();
|
||||
for (const channel of channels) {
|
||||
|
@ -214,14 +216,17 @@ class NetworkSyncService {
|
|||
);
|
||||
++progress;
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Updating channel creation date ${progress}/${channels.length}`);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.debug(`Updating channel creation date ${progress}/${channels.length}`, logger.tags.ln);
|
||||
this.loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
}
|
||||
logger.info(`Updated ${channels.length} channels' creation date`);
|
||||
|
||||
if (channels.length > 0) {
|
||||
logger.debug(`Updated ${channels.length} channels' creation date`, logger.tags.ln);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('$lookUpCreationDateFromChain() error: ' + (e instanceof Error ? e.message : e));
|
||||
logger.err(`$lookUpCreationDateFromChain() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,7 +235,7 @@ class NetworkSyncService {
|
|||
* mark that channel as inactive
|
||||
*/
|
||||
private async $deactivateChannelsWithoutActiveNodes(): Promise<void> {
|
||||
logger.info(`Find channels which nodes are offline`);
|
||||
logger.debug(`Find channels which nodes are offline`, logger.tags.ln);
|
||||
|
||||
try {
|
||||
const result = await DB.query<ResultSetHeader>(`
|
||||
|
@ -253,12 +258,10 @@ class NetworkSyncService {
|
|||
`);
|
||||
|
||||
if (result[0].changedRows ?? 0 > 0) {
|
||||
logger.info(`Marked ${result[0].changedRows} channels as inactive because they are not linked to any active node`);
|
||||
} else {
|
||||
logger.debug(`Marked ${result[0].changedRows} channels as inactive because they are not linked to any active node`);
|
||||
logger.debug(`Marked ${result[0].changedRows} channels as inactive because they are not linked to any active node`, logger.tags.ln);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('$deactivateChannelsWithoutActiveNodes() error: ' + (e instanceof Error ? e.message : e));
|
||||
logger.err(`$deactivateChannelsWithoutActiveNodes() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -277,13 +280,13 @@ class NetworkSyncService {
|
|||
} else {
|
||||
log += ` for the first time`;
|
||||
}
|
||||
logger.info(log);
|
||||
logger.info(`${log}`, logger.tags.ln);
|
||||
|
||||
const channels = await channelsApi.$getChannelsByStatus([0, 1]);
|
||||
for (const channel of channels) {
|
||||
const spendingTx = await bitcoinApi.$getOutspend(channel.transaction_id, channel.transaction_vout);
|
||||
if (spendingTx.spent === true && spendingTx.status?.confirmed === true) {
|
||||
logger.debug('Marking channel: ' + channel.id + ' as closed.');
|
||||
logger.debug(`Marking channel: ${channel.id} as closed.`, logger.tags.ln);
|
||||
await DB.query(`UPDATE channels SET status = 2, closing_date = FROM_UNIXTIME(?) WHERE id = ?`,
|
||||
[spendingTx.status.block_time, channel.id]);
|
||||
if (spendingTx.txid && !channel.closing_transaction_id) {
|
||||
|
@ -293,16 +296,16 @@ class NetworkSyncService {
|
|||
|
||||
++progress;
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - this.loggerTimer);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Checking if channel has been closed ${progress}/${channels.length}`);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.info(`Checking if channel has been closed ${progress}/${channels.length}`, logger.tags.ln);
|
||||
this.loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
this.closedChannelsScanBlock = blocks.getCurrentBlockHeight();
|
||||
logger.info(`Closed channels scan completed at block ${this.closedChannelsScanBlock}`);
|
||||
logger.debug(`Closed channels scan completed at block ${this.closedChannelsScanBlock}`, logger.tags.ln);
|
||||
} catch (e) {
|
||||
logger.err('$scanForClosedChannels() error: ' + (e instanceof Error ? e.message : e));
|
||||
logger.err(`$scanForClosedChannels() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Common } from '../../api/common';
|
|||
|
||||
class LightningStatsUpdater {
|
||||
public async $startService(): Promise<void> {
|
||||
logger.info('Starting Lightning Stats service');
|
||||
logger.info(`Starting Lightning Stats service`, logger.tags.ln);
|
||||
|
||||
await this.$runTasks();
|
||||
LightningStatsImporter.$run();
|
||||
|
@ -27,7 +27,7 @@ class LightningStatsUpdater {
|
|||
const networkGraph = await lightningApi.$getNetworkGraph();
|
||||
await LightningStatsImporter.computeNetworkStats(date.getTime() / 1000, networkGraph);
|
||||
|
||||
logger.info(`Updated latest network stats`);
|
||||
logger.debug(`Updated latest network stats`, logger.tags.ln);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,10 @@ class FundingTxFetcher {
|
|||
try {
|
||||
this.fundingTxCache = JSON.parse(await fsPromises.readFile(CACHE_FILE_NAME, 'utf-8'));
|
||||
} catch (e) {
|
||||
logger.err(`Unable to parse channels funding txs disk cache. Starting from scratch`);
|
||||
logger.err(`Unable to parse channels funding txs disk cache. Starting from scratch`, logger.tags.ln);
|
||||
this.fundingTxCache = {};
|
||||
}
|
||||
logger.debug(`Imported ${Object.keys(this.fundingTxCache).length} funding tx amount from the disk cache`);
|
||||
logger.debug(`Imported ${Object.keys(this.fundingTxCache).length} funding tx amount from the disk cache`, logger.tags.ln);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,26 +44,27 @@ class FundingTxFetcher {
|
|||
++channelProcessed;
|
||||
|
||||
let elapsedSeconds = Math.round((new Date().getTime() / 1000) - loggerTimer);
|
||||
if (elapsedSeconds > 10) {
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
elapsedSeconds = Math.round((new Date().getTime() / 1000) - globalTimer);
|
||||
logger.info(`Indexing channels funding tx ${channelProcessed + 1} of ${channelIds.length} ` +
|
||||
`(${Math.floor(channelProcessed / channelIds.length * 10000) / 100}%) | ` +
|
||||
`elapsed: ${elapsedSeconds} seconds`
|
||||
`elapsed: ${elapsedSeconds} seconds`,
|
||||
logger.tags.ln
|
||||
);
|
||||
loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
|
||||
elapsedSeconds = Math.round((new Date().getTime() / 1000) - cacheTimer);
|
||||
if (elapsedSeconds > 60) {
|
||||
logger.debug(`Saving ${Object.keys(this.fundingTxCache).length} funding txs cache into disk`);
|
||||
logger.debug(`Saving ${Object.keys(this.fundingTxCache).length} funding txs cache into disk`, logger.tags.ln);
|
||||
fsPromises.writeFile(CACHE_FILE_NAME, JSON.stringify(this.fundingTxCache));
|
||||
cacheTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.channelNewlyProcessed > 0) {
|
||||
logger.info(`Indexed ${this.channelNewlyProcessed} additional channels funding tx`);
|
||||
logger.debug(`Saving ${Object.keys(this.fundingTxCache).length} funding txs cache into disk`);
|
||||
logger.info(`Indexed ${this.channelNewlyProcessed} additional channels funding tx`, logger.tags.ln);
|
||||
logger.debug(`Saving ${Object.keys(this.fundingTxCache).length} funding txs cache into disk`, logger.tags.ln);
|
||||
fsPromises.writeFile(CACHE_FILE_NAME, JSON.stringify(this.fundingTxCache));
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ export async function $lookupNodeLocation(): Promise<void> {
|
|||
let nodesUpdated = 0;
|
||||
let geoNamesInserted = 0;
|
||||
|
||||
logger.info(`Running node location updater using Maxmind`);
|
||||
logger.debug(`Running node location updater using Maxmind`, logger.tags.ln);
|
||||
try {
|
||||
const nodes = await nodesApi.$getAllNodes();
|
||||
const lookupCity = await maxmind.open<CityResponse>(config.MAXMIND.GEOLITE2_CITY);
|
||||
|
@ -152,8 +152,8 @@ export async function $lookupNodeLocation(): Promise<void> {
|
|||
|
||||
++progress;
|
||||
const elapsedSeconds = Math.round((new Date().getTime() / 1000) - loggerTimer);
|
||||
if (elapsedSeconds > 10) {
|
||||
logger.info(`Updating node location data ${progress}/${nodes.length}`);
|
||||
if (elapsedSeconds > config.LIGHTNING.LOGGER_UPDATE_INTERVAL) {
|
||||
logger.debug(`Updating node location data ${progress}/${nodes.length}`);
|
||||
loggerTimer = new Date().getTime() / 1000;
|
||||
}
|
||||
}
|
||||
|
@ -161,9 +161,7 @@ export async function $lookupNodeLocation(): Promise<void> {
|
|||
}
|
||||
|
||||
if (nodesUpdated > 0) {
|
||||
logger.info(`${nodesUpdated} nodes maxmind data updated, ${geoNamesInserted} geo names inserted`);
|
||||
} else {
|
||||
logger.debug(`${nodesUpdated} nodes maxmind data updated, ${geoNamesInserted} geo names inserted`);
|
||||
logger.debug(`${nodesUpdated} nodes maxmind data updated, ${geoNamesInserted} geo names inserted`, logger.tags.ln);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err('$lookupNodeLocation() error: ' + (e instanceof Error ? e.message : e));
|
||||
|
|
|
@ -8,7 +8,6 @@ import { isIP } from 'net';
|
|||
import { Common } from '../../../api/common';
|
||||
import channelsApi from '../../../api/explorer/channels.api';
|
||||
import nodesApi from '../../../api/explorer/nodes.api';
|
||||
import { ResultSetHeader } from 'mysql2';
|
||||
|
||||
const fsPromises = promises;
|
||||
|
||||
|
@ -17,7 +16,7 @@ class LightningStatsImporter {
|
|||
|
||||
async $run(): Promise<void> {
|
||||
const [channels]: any[] = await DB.query('SELECT short_id from channels;');
|
||||
logger.info('Caching funding txs for currently existing channels');
|
||||
logger.info(`Caching funding txs for currently existing channels`, logger.tags.ln);
|
||||
await fundingTxFetcher.$fetchChannelsFundingTxs(channels.map(channel => channel.short_id));
|
||||
|
||||
if (config.MEMPOOL.NETWORK !== 'mainnet' || config.DATABASE.ENABLED === false) {
|
||||
|
@ -108,7 +107,7 @@ class LightningStatsImporter {
|
|||
|
||||
const tx = await fundingTxFetcher.$fetchChannelOpenTx(short_id);
|
||||
if (!tx) {
|
||||
logger.err(`Unable to fetch funding tx for channel ${short_id}. Capacity and creation date is unknown. Skipping channel.`);
|
||||
logger.err(`Unable to fetch funding tx for channel ${short_id}. Capacity and creation date is unknown. Skipping channel.`, logger.tags.ln);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -310,13 +309,18 @@ class LightningStatsImporter {
|
|||
* Import topology files LN historical data into the database
|
||||
*/
|
||||
async $importHistoricalLightningStats(): Promise<void> {
|
||||
if (!config.LIGHTNING.TOPOLOGY_FOLDER) {
|
||||
logger.info(`Lightning topology folder is not set. Not importing historical LN stats`);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug('Run the historical importer');
|
||||
try {
|
||||
let fileList: string[] = [];
|
||||
try {
|
||||
fileList = await fsPromises.readdir(this.topologiesFolder);
|
||||
} catch (e) {
|
||||
logger.err(`Unable to open topology folder at ${this.topologiesFolder}`);
|
||||
logger.err(`Unable to open topology folder at ${this.topologiesFolder}`, logger.tags.ln);
|
||||
throw e;
|
||||
}
|
||||
// Insert history from the most recent to the oldest
|
||||
|
@ -354,7 +358,7 @@ class LightningStatsImporter {
|
|||
continue;
|
||||
}
|
||||
|
||||
logger.debug(`Reading ${this.topologiesFolder}/${filename}`);
|
||||
logger.debug(`Reading ${this.topologiesFolder}/${filename}`, logger.tags.ln);
|
||||
let fileContent = '';
|
||||
try {
|
||||
fileContent = await fsPromises.readFile(`${this.topologiesFolder}/${filename}`, 'utf8');
|
||||
|
@ -363,7 +367,7 @@ class LightningStatsImporter {
|
|||
totalProcessed++;
|
||||
continue;
|
||||
}
|
||||
logger.err(`Unable to open ${this.topologiesFolder}/${filename}`);
|
||||
logger.err(`Unable to open ${this.topologiesFolder}/${filename}`, logger.tags.ln);
|
||||
totalProcessed++;
|
||||
continue;
|
||||
}
|
||||
|
@ -373,7 +377,7 @@ class LightningStatsImporter {
|
|||
graph = JSON.parse(fileContent);
|
||||
graph = await this.cleanupTopology(graph);
|
||||
} catch (e) {
|
||||
logger.debug(`Invalid topology file ${this.topologiesFolder}/${filename}, cannot parse the content. Reason: ${e instanceof Error ? e.message : e}`);
|
||||
logger.debug(`Invalid topology file ${this.topologiesFolder}/${filename}, cannot parse the content. Reason: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
totalProcessed++;
|
||||
continue;
|
||||
}
|
||||
|
@ -385,20 +389,20 @@ class LightningStatsImporter {
|
|||
}
|
||||
|
||||
if (!logStarted) {
|
||||
logger.info(`Founds a topology file that we did not import. Importing historical lightning stats now.`);
|
||||
logger.info(`Founds a topology file that we did not import. Importing historical lightning stats now.`, logger.tags.ln);
|
||||
logStarted = true;
|
||||
}
|
||||
|
||||
const datestr = `${new Date(timestamp * 1000).toUTCString()} (${timestamp})`;
|
||||
logger.debug(`${datestr}: Found ${graph.nodes.length} nodes and ${graph.edges.length} channels`);
|
||||
logger.debug(`${datestr}: Found ${graph.nodes.length} nodes and ${graph.edges.length} channels`, logger.tags.ln);
|
||||
|
||||
totalProcessed++;
|
||||
|
||||
if (processed > 10) {
|
||||
logger.info(`Generating LN network stats for ${datestr}. Processed ${totalProcessed}/${fileList.length} files`);
|
||||
logger.info(`Generating LN network stats for ${datestr}. Processed ${totalProcessed}/${fileList.length} files`, logger.tags.ln);
|
||||
processed = 0;
|
||||
} else {
|
||||
logger.debug(`Generating LN network stats for ${datestr}. Processed ${totalProcessed}/${fileList.length} files`);
|
||||
logger.debug(`Generating LN network stats for ${datestr}. Processed ${totalProcessed}/${fileList.length} files`, logger.tags.ln);
|
||||
}
|
||||
await fundingTxFetcher.$fetchChannelsFundingTxs(graph.edges.map(channel => channel.channel_id.slice(0, -2)));
|
||||
const stat = await this.computeNetworkStats(timestamp, graph, true);
|
||||
|
@ -407,10 +411,10 @@ class LightningStatsImporter {
|
|||
}
|
||||
|
||||
if (totalProcessed > 0) {
|
||||
logger.info(`Lightning network stats historical import completed`);
|
||||
logger.notice(`Lightning network stats historical import completed`, logger.tags.ln);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err(`Lightning network stats historical failed. Reason: ${e instanceof Error ? e.message : e}`);
|
||||
logger.err(`Lightning network stats historical failed. Reason: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,9 +32,9 @@ class PoolsUpdater {
|
|||
this.lastRun = now;
|
||||
|
||||
if (config.SOCKS5PROXY.ENABLED) {
|
||||
logger.info(`Updating latest mining pools from ${this.poolsUrl} over the Tor network`);
|
||||
logger.info(`Updating latest mining pools from ${this.poolsUrl} over the Tor network`, logger.tags.mining);
|
||||
} else {
|
||||
logger.info(`Updating latest mining pools from ${this.poolsUrl} over clearnet`);
|
||||
logger.info(`Updating latest mining pools from ${this.poolsUrl} over clearnet`, logger.tags.mining);
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -53,9 +53,9 @@ class PoolsUpdater {
|
|||
}
|
||||
|
||||
if (this.currentSha === undefined) {
|
||||
logger.info(`Downloading pools.json for the first time from ${this.poolsUrl}`);
|
||||
logger.info(`Downloading pools.json for the first time from ${this.poolsUrl}`, logger.tags.mining);
|
||||
} else {
|
||||
logger.warn(`Pools.json is outdated, fetch latest from ${this.poolsUrl}`);
|
||||
logger.warn(`Pools.json is outdated, fetch latest from ${this.poolsUrl}`, logger.tags.mining);
|
||||
}
|
||||
const poolsJson = await this.query(this.poolsUrl);
|
||||
if (poolsJson === undefined) {
|
||||
|
@ -63,11 +63,11 @@ class PoolsUpdater {
|
|||
}
|
||||
await poolsParser.migratePoolsJson(poolsJson);
|
||||
await this.updateDBSha(githubSha);
|
||||
logger.notice('PoolsUpdater completed');
|
||||
logger.notice(`PoolsUpdater completed`, logger.tags.mining);
|
||||
|
||||
} catch (e) {
|
||||
this.lastRun = now - (oneWeek - oneDay); // Try again in 24h instead of waiting next week
|
||||
logger.err('PoolsUpdater failed. Will try again in 24h. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
logger.err(`PoolsUpdater failed. Will try again in 24h. Reason: ${e instanceof Error ? e.message : e}`, logger.tags.mining);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,7 +81,7 @@ class PoolsUpdater {
|
|||
await DB.query('DELETE FROM state where name="pools_json_sha"');
|
||||
await DB.query(`INSERT INTO state VALUES('pools_json_sha', NULL, '${githubSha}')`);
|
||||
} catch (e) {
|
||||
logger.err('Cannot save github pools.json sha into the db. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
logger.err('Cannot save github pools.json sha into the db. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ class PoolsUpdater {
|
|||
const [rows]: any[] = await DB.query('SELECT string FROM state WHERE name="pools_json_sha"');
|
||||
return (rows.length > 0 ? rows[0].string : undefined);
|
||||
} catch (e) {
|
||||
logger.err('Cannot fetch pools.json sha from db. Reason: ' + (e instanceof Error ? e.message : e));
|
||||
logger.err('Cannot fetch pools.json sha from db. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ class PoolsUpdater {
|
|||
}
|
||||
}
|
||||
|
||||
logger.err(`Cannot find "pools.json" in git tree (${this.treeUrl})`);
|
||||
logger.err(`Cannot find "pools.json" in git tree (${this.treeUrl})`, logger.tags.mining);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ class KrakenApi implements PriceFeed {
|
|||
}
|
||||
|
||||
if (Object.keys(priceHistory).length > 0) {
|
||||
logger.notice(`Inserted ${Object.keys(priceHistory).length} Kraken EUR, USD, GBP, JPY, CAD, CHF and AUD weekly price history into db`);
|
||||
logger.notice(`Inserted ${Object.keys(priceHistory).length} Kraken EUR, USD, GBP, JPY, CAD, CHF and AUD weekly price history into db`, logger.tags.mining);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ class PriceUpdater {
|
|||
await this.$updatePrice();
|
||||
}
|
||||
} catch (e) {
|
||||
logger.err(`Cannot save BTC prices in db. Reason: ${e instanceof Error ? e.message : e}`);
|
||||
logger.err(`Cannot save BTC prices in db. Reason: ${e instanceof Error ? e.message : e}`, logger.tags.mining);
|
||||
}
|
||||
|
||||
this.running = false;
|
||||
|
@ -115,14 +115,14 @@ class PriceUpdater {
|
|||
if (price > 0) {
|
||||
prices.push(price);
|
||||
}
|
||||
logger.debug(`${feed.name} BTC/${currency} price: ${price}`);
|
||||
logger.debug(`${feed.name} BTC/${currency} price: ${price}`, logger.tags.mining);
|
||||
} catch (e) {
|
||||
logger.debug(`Could not fetch BTC/${currency} price at ${feed.name}. Reason: ${(e instanceof Error ? e.message : e)}`);
|
||||
logger.debug(`Could not fetch BTC/${currency} price at ${feed.name}. Reason: ${(e instanceof Error ? e.message : e)}`, logger.tags.mining);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prices.length === 1) {
|
||||
logger.debug(`Only ${prices.length} feed available for BTC/${currency} price`);
|
||||
logger.debug(`Only ${prices.length} feed available for BTC/${currency} price`, logger.tags.mining);
|
||||
}
|
||||
|
||||
// Compute average price, non weighted
|
||||
|
@ -175,9 +175,9 @@ class PriceUpdater {
|
|||
++insertedCount;
|
||||
}
|
||||
if (insertedCount > 0) {
|
||||
logger.notice(`Inserted ${insertedCount} MtGox USD weekly price history into db`);
|
||||
logger.notice(`Inserted ${insertedCount} MtGox USD weekly price history into db`, logger.tags.mining);
|
||||
} else {
|
||||
logger.debug(`Inserted ${insertedCount} MtGox USD weekly price history into db`);
|
||||
logger.debug(`Inserted ${insertedCount} MtGox USD weekly price history into db`, logger.tags.mining);
|
||||
}
|
||||
|
||||
// Insert Kraken weekly prices
|
||||
|
@ -198,7 +198,7 @@ class PriceUpdater {
|
|||
private async $insertMissingRecentPrices(type: 'hour' | 'day'): Promise<void> {
|
||||
const existingPriceTimes = await PricesRepository.$getPricesTimes();
|
||||
|
||||
logger.info(`Fetching ${type === 'day' ? 'dai' : 'hour'}ly price history from exchanges and saving missing ones into the database, this may take a while`);
|
||||
logger.info(`Fetching ${type === 'day' ? 'dai' : 'hour'}ly price history from exchanges and saving missing ones into the database`, logger.tags.mining);
|
||||
|
||||
const historicalPrices: PriceHistory[] = [];
|
||||
|
||||
|
@ -207,7 +207,7 @@ class PriceUpdater {
|
|||
try {
|
||||
historicalPrices.push(await feed.$fetchRecentPrice(this.currencies, type));
|
||||
} catch (e) {
|
||||
logger.err(`Cannot fetch hourly historical price from ${feed.name}. Ignoring this feed. Reason: ${e instanceof Error ? e.message : e}`);
|
||||
logger.err(`Cannot fetch hourly historical price from ${feed.name}. Ignoring this feed. Reason: ${e instanceof Error ? e.message : e}`, logger.tags.mining);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,9 +252,9 @@ class PriceUpdater {
|
|||
}
|
||||
|
||||
if (totalInserted > 0) {
|
||||
logger.notice(`Inserted ${totalInserted} ${type === 'day' ? 'dai' : 'hour'}ly historical prices into the db`);
|
||||
logger.notice(`Inserted ${totalInserted} ${type === 'day' ? 'dai' : 'hour'}ly historical prices into the db`, logger.tags.mining);
|
||||
} else {
|
||||
logger.debug(`Inserted ${totalInserted} ${type === 'day' ? 'dai' : 'hour'}ly historical prices into the db`);
|
||||
logger.debug(`Inserted ${totalInserted} ${type === 'day' ? 'dai' : 'hour'}ly historical prices into the db`, logger.tags.mining);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,12 +100,18 @@ Below we list all settings from `mempool-config.json` and the corresponding over
|
|||
"BLOCK_WEIGHT_UNITS": 4000000,
|
||||
"INITIAL_BLOCKS_AMOUNT": 8,
|
||||
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
||||
"BLOCKS_SUMMARIES_INDEXING": false,
|
||||
"PRICE_FEED_UPDATE_INTERVAL": 600,
|
||||
"USE_SECOND_NODE_FOR_MINFEE": false,
|
||||
"EXTERNAL_ASSETS": ["https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json"],
|
||||
"STDOUT_LOG_MIN_PRIORITY": "info",
|
||||
"INDEXING_BLOCKS_AMOUNT": false,
|
||||
"AUTOMATIC_BLOCK_REINDEXING": false,
|
||||
"POOLS_JSON_URL": "https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json",
|
||||
"POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master"
|
||||
"POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master",
|
||||
"ADVANCED_GBT_AUDIT": false,
|
||||
"ADVANCED_GBT_MEMPOOL": false,
|
||||
"CPFP_INDEXING": false,
|
||||
},
|
||||
```
|
||||
|
||||
|
@ -125,15 +131,25 @@ Corresponding `docker-compose.yml` overrides:
|
|||
MEMPOOL_BLOCK_WEIGHT_UNITS: ""
|
||||
MEMPOOL_INITIAL_BLOCKS_AMOUNT: ""
|
||||
MEMPOOL_MEMPOOL_BLOCKS_AMOUNT: ""
|
||||
MEMPOOL_BLOCKS_SUMMARIES_INDEXING: ""
|
||||
MEMPOOL_PRICE_FEED_UPDATE_INTERVAL: ""
|
||||
MEMPOOL_USE_SECOND_NODE_FOR_MINFEE: ""
|
||||
MEMPOOL_EXTERNAL_ASSETS: ""
|
||||
MEMPOOL_STDOUT_LOG_MIN_PRIORITY: ""
|
||||
MEMPOOL_INDEXING_BLOCKS_AMOUNT: ""
|
||||
MEMPOOL_AUTOMATIC_BLOCK_REINDEXING: ""
|
||||
MEMPOOL_POOLS_JSON_URL: ""
|
||||
MEMPOOL_POOLS_JSON_TREE_URL: ""
|
||||
MEMPOOL_ADVANCED_GBT_AUDIT: ""
|
||||
MEMPOOL_ADVANCED_GBT_MEMPOOL: ""
|
||||
MEMPOOL_CPFP_INDEXING: ""
|
||||
...
|
||||
```
|
||||
|
||||
`ADVANCED_GBT_AUDIT` AND `ADVANCED_GBT_MEMPOOL` enable a more accurate (but slower) block prediction algorithm for the block audit feature and the projected mempool-blocks respectively.
|
||||
|
||||
`CPFP_INDEXING` enables indexing CPFP (Child Pays For Parent) information for the last `INDEXING_BLOCKS_AMOUNT` blocks.
|
||||
|
||||
<br/>
|
||||
|
||||
`mempool-config.json`:
|
||||
|
|
|
@ -22,7 +22,10 @@
|
|||
"STDOUT_LOG_MIN_PRIORITY": "__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__",
|
||||
"INDEXING_BLOCKS_AMOUNT": __MEMPOOL_INDEXING_BLOCKS_AMOUNT__,
|
||||
"BLOCKS_SUMMARIES_INDEXING": __MEMPOOL_BLOCKS_SUMMARIES_INDEXING__,
|
||||
"AUTOMATIC_BLOCK_REINDEXING": __MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__
|
||||
"AUTOMATIC_BLOCK_REINDEXING": __MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__,
|
||||
"ADVANCED_GBT_AUDIT": __MEMPOOL_ADVANCED_GBT_AUDIT__,
|
||||
"ADVANCED_GBT_MEMPOOL": __MEMPOOL_ADVANCED_GBT_MEMPOOL__,
|
||||
"CPFP_INDEXING": __MEMPOOL_CPFP_INDEXING__
|
||||
},
|
||||
"CORE_RPC": {
|
||||
"HOST": "__CORE_RPC_HOST__",
|
||||
|
|
|
@ -27,6 +27,9 @@ __MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=false}
|
|||
__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__=${MEMPOOL_AUTOMATIC_BLOCK_REINDEXING:=false}
|
||||
__MEMPOOL_POOLS_JSON_URL__=${MEMPOOL_POOLS_JSON_URL:=https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json}
|
||||
__MEMPOOL_POOLS_JSON_TREE_URL__=${MEMPOOL_POOLS_JSON_TREE_URL:=https://api.github.com/repos/mempool/mining-pools/git/trees/master}
|
||||
__MEMPOOL_ADVANCED_GBT_AUDIT__=${MEMPOOL_ADVANCED_GBT_AUDIT:=false}
|
||||
__MEMPOOL_ADVANCED_GBT_MEMPOOL__=${MEMPOOL_ADVANCED_GBT_MEMPOOL:=false}
|
||||
__MEMPOOL_CPFP_INDEXING__=${MEMPOOL_CPFP_INDEXING:=false}
|
||||
|
||||
# CORE_RPC
|
||||
__CORE_RPC_HOST__=${CORE_RPC_HOST:=127.0.0.1}
|
||||
|
@ -136,6 +139,8 @@ sed -i "s/__MEMPOOL_INDEXING_BLOCKS_AMOUNT__/${__MEMPOOL_INDEXING_BLOCKS_AMOUNT_
|
|||
sed -i "s/__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__/${__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__}/g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_POOLS_JSON_URL__!${__MEMPOOL_POOLS_JSON_URL__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_POOLS_JSON_TREE_URL__!${__MEMPOOL_POOLS_JSON_TREE_URL__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_ADVANCED_GBT_MEMPOOL__!${__MEMPOOL_ADVANCED_GBT_MEMPOOL__}!g" mempool-config.json
|
||||
sed -i "s!__MEMPOOL_CPFP_INDEXING__!${__MEMPOOL_CPFP_INDEXING__}!g" mempool-config.json
|
||||
|
||||
sed -i "s/__CORE_RPC_HOST__/${__CORE_RPC_HOST__}/g" mempool-config.json
|
||||
sed -i "s/__CORE_RPC_PORT__/${__CORE_RPC_PORT__}/g" mempool-config.json
|
||||
|
|
|
@ -31,6 +31,9 @@ __LIQUID_WEBSITE_URL__=${LIQUID_WEBSITE_URL:=https://liquid.network}
|
|||
__BISQ_WEBSITE_URL__=${BISQ_WEBSITE_URL:=https://bisq.markets}
|
||||
__MINING_DASHBOARD__=${MINING_DASHBOARD:=true}
|
||||
__LIGHTNING__=${LIGHTNING:=false}
|
||||
__MAINNET_BLOCK_AUDIT_START_HEIGHT__=${MAINNET_BLOCK_AUDIT_START_HEIGHT:=0}
|
||||
__TESTNET_BLOCK_AUDIT_START_HEIGHT__=${TESTNET_BLOCK_AUDIT_START_HEIGHT:=0}
|
||||
__SIGNET_BLOCK_AUDIT_START_HEIGHT__=${SIGNET_BLOCK_AUDIT_START_HEIGHT:=0}
|
||||
|
||||
# Export as environment variables to be used by envsubst
|
||||
export __TESTNET_ENABLED__
|
||||
|
@ -52,6 +55,9 @@ export __LIQUID_WEBSITE_URL__
|
|||
export __BISQ_WEBSITE_URL__
|
||||
export __MINING_DASHBOARD__
|
||||
export __LIGHTNING__
|
||||
export __MAINNET_BLOCK_AUDIT_START_HEIGHT__
|
||||
export __TESTNET_BLOCK_AUDIT_START_HEIGHT__
|
||||
export __SIGNET_BLOCK_AUDIT_START_HEIGHT__
|
||||
|
||||
folder=$(find /var/www/mempool -name "config.js" | xargs dirname)
|
||||
echo ${folder}
|
||||
|
|
|
@ -127,7 +127,7 @@ https://www.transifex.com/mempool/mempool/dashboard/
|
|||
* Thai @Gusb3ll
|
||||
* Turkish @stackmore
|
||||
* Ukrainian @volbil
|
||||
* Vietnamese @bitcoin_vietnam
|
||||
* Vietnamese @BitcoinvnNews
|
||||
* Chinese @wdljt
|
||||
* Russian @TonyCrusoe @Bitconan
|
||||
* Romanian @mirceavesa
|
||||
|
|
|
@ -137,6 +137,10 @@
|
|||
"hi": {
|
||||
"translation": "src/locale/messages.hi.xlf",
|
||||
"baseHref": "/hi/"
|
||||
},
|
||||
"lt": {
|
||||
"translation": "src/locale/messages.lt.xlf",
|
||||
"baseHref": "/lt/"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -7,7 +7,6 @@ describe('Liquid', () => {
|
|||
cy.intercept('/liquid/api/blocks/').as('blocks');
|
||||
cy.intercept('/liquid/api/tx/**/outspends').as('outspends');
|
||||
cy.intercept('/liquid/api/block/**/txs/**').as('block-txs');
|
||||
cy.intercept('/resources/pools.json').as('pools');
|
||||
|
||||
Cypress.Commands.add('waitForBlockData', () => {
|
||||
cy.wait('@socket');
|
||||
|
|
|
@ -7,7 +7,6 @@ describe('Liquid Testnet', () => {
|
|||
cy.intercept('/liquidtestnet/api/blocks/').as('blocks');
|
||||
cy.intercept('/liquidtestnet/api/tx/**/outspends').as('outspends');
|
||||
cy.intercept('/liquidtestnet/api/block/**/txs/**').as('block-txs');
|
||||
cy.intercept('/resources/pools.json').as('pools');
|
||||
|
||||
Cypress.Commands.add('waitForBlockData', () => {
|
||||
cy.wait('@socket');
|
||||
|
|
|
@ -41,7 +41,6 @@ describe('Mainnet', () => {
|
|||
// cy.intercept('/api/v1/block/*/summary').as('block-summary');
|
||||
// cy.intercept('/api/v1/outspends/*').as('outspends');
|
||||
// cy.intercept('/api/tx/*/outspends').as('tx-outspends');
|
||||
// cy.intercept('/resources/pools.json').as('pools');
|
||||
|
||||
// Search Auto Complete
|
||||
cy.intercept('/api/address-prefix/1wiz').as('search-1wiz');
|
||||
|
|
30
frontend/package-lock.json
generated
30
frontend/package-lock.json
generated
|
@ -57,8 +57,8 @@
|
|||
"typescript": "~4.6.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@cypress/schematic": "~2.3.0",
|
||||
"cypress": "^11.2.0",
|
||||
"@cypress/schematic": "^2.4.0",
|
||||
"cypress": "^12.1.0",
|
||||
"cypress-fail-on-console-error": "~4.0.2",
|
||||
"cypress-wait-until": "^1.7.2",
|
||||
"mock-socket": "~9.1.5",
|
||||
|
@ -3225,9 +3225,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@cypress/schematic": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.3.0.tgz",
|
||||
"integrity": "sha512-LBKX20MUUYF2Xu+1+KpVbLCoMvt2Osa80yQfonduVsLJ/p8JxtLHqufuf/ryJp9Gm9R5sDfk/YhHL+rB7a+gsg==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.4.0.tgz",
|
||||
"integrity": "sha512-aor8hQ+gMXqx/ASdo7CUGo/sMEWwwfSRsLr99rM2GjvW+pZnCKKTnRG4UPf8Ro9SevLJj7KRZAZWxa5MAkJzZA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@angular-devkit/architect": "^0.1402.1",
|
||||
|
@ -7019,9 +7019,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"node_modules/cypress": {
|
||||
"version": "11.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-11.2.0.tgz",
|
||||
"integrity": "sha512-u61UGwtu7lpsNWLUma/FKNOsrjcI6wleNmda/TyKHe0dOBcVjbCPlp1N6uwFZ0doXev7f/91YDpU9bqDCFeBLA==",
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-12.1.0.tgz",
|
||||
"integrity": "sha512-7fz8N84uhN1+ePNDsfQvoWEl4P3/VGKKmAg+bJQFY4onhA37Ys+6oBkGbNdwGeC7n2QqibNVPhk8x3YuQLwzfw==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
|
@ -7072,7 +7072,7 @@
|
|||
"cypress": "bin/cypress"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
"node": "^14.0.0 || ^16.0.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cypress-fail-on-console-error": {
|
||||
|
@ -19345,9 +19345,9 @@
|
|||
}
|
||||
},
|
||||
"@cypress/schematic": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.3.0.tgz",
|
||||
"integrity": "sha512-LBKX20MUUYF2Xu+1+KpVbLCoMvt2Osa80yQfonduVsLJ/p8JxtLHqufuf/ryJp9Gm9R5sDfk/YhHL+rB7a+gsg==",
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.4.0.tgz",
|
||||
"integrity": "sha512-aor8hQ+gMXqx/ASdo7CUGo/sMEWwwfSRsLr99rM2GjvW+pZnCKKTnRG4UPf8Ro9SevLJj7KRZAZWxa5MAkJzZA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@angular-devkit/architect": "^0.1402.1",
|
||||
|
@ -22282,9 +22282,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"cypress": {
|
||||
"version": "11.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-11.2.0.tgz",
|
||||
"integrity": "sha512-u61UGwtu7lpsNWLUma/FKNOsrjcI6wleNmda/TyKHe0dOBcVjbCPlp1N6uwFZ0doXev7f/91YDpU9bqDCFeBLA==",
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-12.1.0.tgz",
|
||||
"integrity": "sha512-7fz8N84uhN1+ePNDsfQvoWEl4P3/VGKKmAg+bJQFY4onhA37Ys+6oBkGbNdwGeC7n2QqibNVPhk8x3YuQLwzfw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@cypress/request": "^2.88.10",
|
||||
|
|
|
@ -109,8 +109,8 @@
|
|||
"typescript": "~4.6.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@cypress/schematic": "~2.3.0",
|
||||
"cypress": "^11.2.0",
|
||||
"@cypress/schematic": "^2.4.0",
|
||||
"cypress": "^12.1.0",
|
||||
"cypress-fail-on-console-error": "~4.0.2",
|
||||
"cypress-wait-until": "^1.7.2",
|
||||
"mock-socket": "~9.1.5",
|
||||
|
|
|
@ -76,7 +76,7 @@ PROXY_CONFIG = [
|
|||
|
||||
if (configContent && configContent.BASE_MODULE == "liquid") {
|
||||
PROXY_CONFIG.push({
|
||||
context: ['/resources/pools.json',
|
||||
context: [
|
||||
'/resources/assets.json', '/resources/assets.minimal.json',
|
||||
'/resources/assets-testnet.json', '/resources/assets-testnet.minimal.json'],
|
||||
target: "https://liquid.network",
|
||||
|
@ -85,7 +85,7 @@ if (configContent && configContent.BASE_MODULE == "liquid") {
|
|||
});
|
||||
} else {
|
||||
PROXY_CONFIG.push({
|
||||
context: ['/resources/pools.json', '/resources/assets.json', '/resources/assets.minimal.json', '/resources/worldmap.json'],
|
||||
context: ['/resources/assets.json', '/resources/assets.minimal.json', '/resources/worldmap.json'],
|
||||
target: "https://mempool.space",
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
|
|
|
@ -120,7 +120,7 @@ export const languages: Language[] = [
|
|||
{ code: 'he', name: 'עברית' }, // Hebrew
|
||||
{ code: 'ka', name: 'ქართული' }, // Georgian
|
||||
// { code: 'lv', name: 'Latviešu' }, // Latvian
|
||||
// { code: 'lt', name: 'Lietuvių' }, // Lithuanian
|
||||
{ code: 'lt', name: 'Lietuvių' }, // Lithuanian
|
||||
{ code: 'hu', name: 'Magyar' }, // Hungarian
|
||||
{ code: 'mk', name: 'Македонски' }, // Macedonian
|
||||
// { code: 'ms', name: 'Bahasa Melayu' }, // Malay
|
||||
|
|
|
@ -6,6 +6,7 @@ import { AppRoutingModule } from './app-routing.module';
|
|||
import { AppComponent } from './components/app/app.component';
|
||||
import { ElectrsApiService } from './services/electrs-api.service';
|
||||
import { StateService } from './services/state.service';
|
||||
import { CacheService } from './services/cache.service';
|
||||
import { EnterpriseService } from './services/enterprise.service';
|
||||
import { WebsocketService } from './services/websocket.service';
|
||||
import { AudioService } from './services/audio.service';
|
||||
|
@ -23,6 +24,7 @@ import { AppPreloadingStrategy } from './app.preloading-strategy';
|
|||
const providers = [
|
||||
ElectrsApiService,
|
||||
StateService,
|
||||
CacheService,
|
||||
WebsocketService,
|
||||
AudioService,
|
||||
SeoService,
|
||||
|
|
|
@ -36,7 +36,9 @@ export class AddressLabelsComponent implements OnChanges {
|
|||
|
||||
handleChannel() {
|
||||
const type = this.vout ? 'open' : 'close';
|
||||
this.label = `Channel ${type}: ${this.channel.node_left.alias} <> ${this.channel.node_right.alias}`;
|
||||
const leftNodeName = this.channel.node_left.alias || this.channel.node_left.public_key.substring(0, 10);
|
||||
const rightNodeName = this.channel.node_right.alias || this.channel.node_right.public_key.substring(0, 10);
|
||||
this.label = `Channel ${type}: ${leftNodeName} <> ${rightNodeName}`;
|
||||
}
|
||||
|
||||
handleVin() {
|
||||
|
|
|
@ -42,6 +42,10 @@ export class AppComponent implements OnInit {
|
|||
if (event.target instanceof HTMLInputElement) {
|
||||
return;
|
||||
}
|
||||
// prevent arrow key horizontal scrolling
|
||||
if(["ArrowLeft","ArrowRight"].indexOf(event.code) > -1) {
|
||||
event.preventDefault();
|
||||
}
|
||||
this.stateService.keyNavigation$.next(event);
|
||||
}
|
||||
|
||||
|
|
|
@ -77,6 +77,8 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
|
|||
cancelAnimationFrame(this.animationFrameRequest);
|
||||
clearTimeout(this.animationHeartBeat);
|
||||
}
|
||||
this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost);
|
||||
this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored);
|
||||
}
|
||||
|
||||
clear(direction): void {
|
||||
|
|
|
@ -53,13 +53,13 @@
|
|||
<td i18n="block.miner">Miner</td>
|
||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a [attr.data-cy]="'block-details-miner-badge'" placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block?.extras.pool.slug]" class="badge"
|
||||
[class]="block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
[class]="!block?.extras.pool.name || block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block?.extras.pool.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||
<span [attr.data-cy]="'block-details-miner-badge'" placement="bottom" class="badge"
|
||||
[class]="block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
[class]="!block?.extras.pool.name || block?.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block?.extras.pool.name }}
|
||||
</span>
|
||||
</td>
|
||||
|
|
|
@ -56,3 +56,7 @@
|
|||
::ng-deep .symbol {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
transition: none;
|
||||
}
|
||||
|
|
|
@ -32,10 +32,10 @@
|
|||
|
||||
<div class="box" *ngIf="!error">
|
||||
<div class="row">
|
||||
<ng-template [ngIf]="!isLoadingBlock">
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<ng-container *ngIf="!isLoadingBlock; else skeletonRows">
|
||||
<tr>
|
||||
<td class="td-width" i18n="block.hash">Hash</td>
|
||||
<td>‎<a [routerLink]="['/block/' | relativeUrl, block.id]" title="{{ block.id }}">{{ block.id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="block.id"></app-clipboard></td>
|
||||
|
@ -54,83 +54,28 @@
|
|||
<td i18n="block.weight">Weight</td>
|
||||
<td [innerHTML]="'‎' + (block.weight | wuBytes: 2)"></td>
|
||||
</tr>
|
||||
<tr *ngIf="auditEnabled">
|
||||
<tr *ngIf="!auditDataMissing && indexingAvailable">
|
||||
<td i18n="block.health">Block health</td>
|
||||
<td>
|
||||
<span *ngIf="blockAudit?.matchRate != null">{{ blockAudit.matchRate }}%</span>
|
||||
<span *ngIf="blockAudit?.matchRate === null" i18n="unknown">Unknown</span>
|
||||
<span
|
||||
class="health-badge badge"
|
||||
[class.badge-success]="blockAudit?.matchRate >= 99"
|
||||
[class.badge-warning]="blockAudit?.matchRate >= 75 && blockAudit?.matchRate < 99"
|
||||
[class.badge-danger]="blockAudit?.matchRate < 75"
|
||||
*ngIf="blockAudit?.matchRate != null; else nullHealth"
|
||||
>{{ blockAudit?.matchRate }}%</span>
|
||||
<ng-template #nullHealth>
|
||||
<ng-container *ngIf="!isLoadingAudit; else loadingHealth">
|
||||
<span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #loadingHealth>
|
||||
<span class="skeleton-loader" style="max-width: 60px"></span>
|
||||
</ng-template>
|
||||
</td>
|
||||
</tr>
|
||||
<ng-container *ngIf="webGlEnabled && (auditDataMissing || !indexingAvailable)">
|
||||
<tr *ngIf="isMobile && auditEnabled"></tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="mempool-block.fee-span">Fee span</td>
|
||||
<td><span>{{ block.extras.feeRange[0] | number:'1.0-0' }} - {{ block.extras.feeRange[block.extras.feeRange.length - 1] | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="block?.extras?.medianFee != undefined">
|
||||
<td class="td-width" i18n="block.median-fee">Median fee</td>
|
||||
<td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
|
||||
</tr>
|
||||
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
||||
<tr>
|
||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
||||
<td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees">
|
||||
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="block.extras.totalFees" digitsInfo="1.0-0"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
<ng-template #liquidTotalFees>
|
||||
<td>
|
||||
<app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <app-fiat
|
||||
[value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>
|
||||
</td>
|
||||
</ng-template>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
||||
<td>
|
||||
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template #loadingFees>
|
||||
<tr>
|
||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
||||
<td style="width: 75%;"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.miner">Miner</td>
|
||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge"
|
||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block.extras.pool.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||
<span placement="bottom" class="badge"
|
||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block.extras.pool.name }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="isLoadingBlock">
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
</ng-container>
|
||||
<ng-template #skeletonRows>
|
||||
<tr>
|
||||
<td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
|
@ -143,114 +88,18 @@
|
|||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<tr *ngIf="!auditDataMissing && indexingAvailable">
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<ng-container *ngIf="webGlEnabled && (!indexingAvailable || auditDataMissing)">
|
||||
<tr *ngIf="isMobile && !auditEnabled"></tr>
|
||||
<tr>
|
||||
<td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped" *ngIf="!isLoadingBlock && (!auditDataMissing || indexingAvailable && !webGlEnabled)">
|
||||
<tbody>
|
||||
<tr *ngIf="isMobile && auditEnabled"></tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="mempool-block.fee-span">Fee span</td>
|
||||
<td><span>{{ block.extras.feeRange[0] | number:'1.0-0' }} - {{ block.extras.feeRange[block.extras.feeRange.length - 1] | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="block?.extras?.medianFee != undefined">
|
||||
<td class="td-width" i18n="block.median-fee">Median fee</td>
|
||||
<td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
|
||||
</tr>
|
||||
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
||||
<tr>
|
||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
||||
<td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees">
|
||||
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="block.extras.totalFees" digitsInfo="1.0-0"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
<ng-template #liquidTotalFees>
|
||||
<td>
|
||||
<app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <app-fiat
|
||||
[value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>
|
||||
</td>
|
||||
</ng-template>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
||||
<td>
|
||||
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template #loadingFees>
|
||||
<tr>
|
||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
||||
<td style="width: 75%;"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.miner">Miner</td>
|
||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge"
|
||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block.extras.pool.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||
<span placement="bottom" class="badge"
|
||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block.extras.pool.name }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<ng-container *ngIf="isMobile || (webGlEnabled && (auditDataMissing || !indexingAvailable)); then restOfTable;"></ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="table table-borderless table-striped" *ngIf="isLoadingBlock && !auditDataMissing && (indexingAvailable || !webGlEnabled)">
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped" *ngIf="!isMobile && !(webGlEnabled && (auditDataMissing || !indexingAvailable))">
|
||||
<tbody>
|
||||
<tr *ngIf="isMobile && !auditEnabled"></tr>
|
||||
<tr>
|
||||
<td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<ng-container *ngTemplateOutlet="restOfTable"></ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="col-sm chart-container" *ngIf="webGlEnabled && (!indexingAvailable || auditDataMissing)">
|
||||
|
@ -263,11 +112,93 @@
|
|||
[flip]="false"
|
||||
(txClickEvent)="onTxClick($event)"
|
||||
></app-block-overview-graph>
|
||||
<ng-container *ngTemplateOutlet="emptyBlockInfo"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #restOfTable>
|
||||
<ng-container *ngIf="!isLoadingBlock; else loadingRest">
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="mempool-block.fee-span">Fee span</td>
|
||||
<td><span>{{ block.extras.feeRange[0] | number:'1.0-0' }} - {{ block.extras.feeRange[block.extras.feeRange.length - 1] | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="block?.extras?.medianFee != undefined">
|
||||
<td class="td-width" i18n="block.median-fee">Median fee</td>
|
||||
<td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
|
||||
</tr>
|
||||
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
||||
<tr>
|
||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
||||
<td *ngIf="network !== 'liquid' && network !== 'liquidtestnet'; else liquidTotalFees">
|
||||
<app-amount [satoshis]="block.extras.totalFees" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="block.extras.totalFees" digitsInfo="1.0-0"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
<ng-template #liquidTotalFees>
|
||||
<td>
|
||||
<app-amount [satoshis]="fees * 100000000" digitsInfo="1.2-2" [noFiat]="true"></app-amount> <app-fiat
|
||||
[value]="fees * 100000000" digitsInfo="1.2-2"></app-fiat>
|
||||
</td>
|
||||
</ng-template>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
||||
<td>
|
||||
<app-amount [satoshis]="block.extras.reward" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="(blockSubsidy + fees) * 100000000" digitsInfo="1.0-0"></app-fiat>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template #loadingFees>
|
||||
<tr>
|
||||
<td i18n="block.total-fees|Total fees in a block">Total fees</td>
|
||||
<td style="width: 75%;"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.subsidy-and-fees|Total subsidy and fees in a block">Subsidy + fees:</td>
|
||||
<td><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td i18n="block.miner">Miner</td>
|
||||
<td *ngIf="stateService.env.MINING_DASHBOARD">
|
||||
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge"
|
||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block.extras.pool.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
|
||||
<span placement="bottom" class="badge"
|
||||
[class]="block.extras.pool.name === 'Unknown' ? 'badge-secondary' : 'badge-primary'">
|
||||
{{ block.extras.pool.name }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<ng-template #loadingRest>
|
||||
<tr>
|
||||
<td class="td-width" colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
<tr *ngIf="network !== 'liquid' && network !== 'liquidtestnet'">
|
||||
<td colspan="2"><span class="skeleton-loader"></span></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
<span id="overview"></span>
|
||||
|
||||
<br>
|
||||
|
@ -283,15 +214,21 @@
|
|||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<h3 class="block-subtitle" *ngIf="!isMobile" i18n="block.projected-block">Projected Block</h3>
|
||||
<app-block-overview-graph #blockGraphProjected [isLoading]="isLoadingOverview" [resolution]="75"
|
||||
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx"
|
||||
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="!isMobile && !auditEnabled"></app-block-overview-graph>
|
||||
<div class="block-graph-wrapper">
|
||||
<app-block-overview-graph #blockGraphProjected [isLoading]="isLoadingOverview" [resolution]="75"
|
||||
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx"
|
||||
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="!isMobile && !auditEnabled"></app-block-overview-graph>
|
||||
<ng-container *ngIf="!isMobile || mode !== 'actual'; else emptyBlockInfo"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm" *ngIf="!isMobile">
|
||||
<h3 class="block-subtitle" *ngIf="!isMobile" i18n="block.actual-block">Actual Block</h3>
|
||||
<app-block-overview-graph #blockGraphActual [isLoading]="isLoadingOverview" [resolution]="75"
|
||||
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined"
|
||||
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="isMobile && !auditEnabled"></app-block-overview-graph>
|
||||
<div class="block-graph-wrapper">
|
||||
<app-block-overview-graph #blockGraphActual [isLoading]="isLoadingOverview" [resolution]="75"
|
||||
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined"
|
||||
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="isMobile && !auditEnabled"></app-block-overview-graph>
|
||||
<ng-container *ngTemplateOutlet="emptyBlockInfo"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -413,5 +350,17 @@
|
|||
|
||||
</div>
|
||||
|
||||
<ng-template #emptyBlockInfo>
|
||||
<a
|
||||
*ngIf="network === '' && block && block.height > 100000 && block.tx_count <= 1"
|
||||
class="info-bubble-link badge badge-primary"
|
||||
[routerLink]="['/docs/faq/' | relativeUrl]"
|
||||
fragment="why-empty-blocks"
|
||||
>
|
||||
<fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon>
|
||||
<span i18n="block.empty-block-explanation">Why is this block empty?</span>
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
|
|
@ -202,4 +202,24 @@ h1 {
|
|||
&.active, &:hover {
|
||||
border-color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.block-graph-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.info-bubble-link {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 2em;
|
||||
left: 50%;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
padding: 0.5em 1em;
|
||||
font-size: 80%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
.ng-fa-icon {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
|
@ -43,6 +43,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||
strippedTransactions: TransactionStripped[];
|
||||
overviewTransitionDirection: string;
|
||||
isLoadingOverview = true;
|
||||
isLoadingAudit = true;
|
||||
error: any;
|
||||
blockSubsidy: number;
|
||||
fees: number;
|
||||
|
@ -137,7 +138,6 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||
this.page = 1;
|
||||
this.error = undefined;
|
||||
this.fees = undefined;
|
||||
this.stateService.markBlock$.next({});
|
||||
this.auditDataMissing = false;
|
||||
|
||||
if (history.state.data && history.state.data.blockHeight) {
|
||||
|
@ -297,13 +297,18 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||
this.auditSubscription = block$.pipe(
|
||||
startWith(null),
|
||||
pairwise(),
|
||||
switchMap(([prevBlock, block]) => this.apiService.getBlockAudit$(block.id)
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
this.overviewError = err;
|
||||
return of([]);
|
||||
})
|
||||
)
|
||||
switchMap(([prevBlock, block]) => {
|
||||
this.isLoadingAudit = true;
|
||||
this.blockAudit = null;
|
||||
return this.apiService.getBlockAudit$(block.id)
|
||||
.pipe(
|
||||
catchError((err) => {
|
||||
this.overviewError = err;
|
||||
this.isLoadingAudit = false;
|
||||
return of([]);
|
||||
})
|
||||
);
|
||||
}
|
||||
),
|
||||
filter((response) => response != null),
|
||||
map((response) => {
|
||||
|
@ -375,12 +380,14 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||
console.log(err);
|
||||
this.error = err;
|
||||
this.isLoadingOverview = false;
|
||||
this.isLoadingAudit = false;
|
||||
return of(null);
|
||||
}),
|
||||
).subscribe((blockAudit) => {
|
||||
this.blockAudit = blockAudit;
|
||||
this.setupBlockGraphs();
|
||||
this.isLoadingOverview = false;
|
||||
this.isLoadingAudit = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,36 +1,55 @@
|
|||
<div class="blocks-container blockchain-blocks-container" [class.time-ltr]="timeLtr" *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
|
||||
<div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn" >
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i" class="text-center bitcoin-block mined-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]" [class.blink-bg]="(specialBlocks[block.height] !== undefined)">
|
||||
<a draggable="false" [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }"
|
||||
class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}"> </a>
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + '-height'" class="block-height">
|
||||
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
|
||||
<div class="blocks-container blockchain-blocks-container" [class.time-ltr]="timeLtr" [style.left]="static ? (offset || 0) + 'px' : null" *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
|
||||
<div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn">
|
||||
<ng-container *ngIf="block && !block.loading && !block.placeholder; else placeholderBlock">
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i" class="text-center bitcoin-block mined-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]" [class.blink-bg]="(specialBlocks[block.height] !== undefined)">
|
||||
<a draggable="false" [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }"
|
||||
class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}"> </a>
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + '-height'" class="block-height">
|
||||
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
|
||||
</div>
|
||||
<div class="block-body">
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + '-fees'" class="fees">
|
||||
~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
||||
</div>
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + '-fee-span'" class="fee-span" *ngIf="block?.extras?.feeRange">
|
||||
{{ block?.extras?.feeRange?.[1] | number:feeRounding }} - {{ block?.extras?.feeRange[block?.extras?.feeRange?.length - 1] | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
||||
</div>
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + '-fee-span'" class="fee-span" *ngIf="!block?.extras?.feeRange">
|
||||
|
||||
</div>
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + '-total-fees'" *ngIf="showMiningInfo" class="block-size">
|
||||
<app-amount [satoshis]="block.extras?.totalFees ?? 0" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||
</div>
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + 'block-size'" *ngIf="!showMiningInfo" class="block-size" [innerHTML]="'‎' + (block.size | bytes: 2)"></div>
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + '-transactions'" class="transaction-count">
|
||||
<ng-container *ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container>
|
||||
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
|
||||
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
|
||||
</div>
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + '-time'" class="time-difference"><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since></div>
|
||||
</div>
|
||||
<div class="animated" [class]="showMiningInfo ? 'show' : 'hide'" *ngIf="block.extras?.pool != undefined">
|
||||
<a [attr.data-cy]="'bitcoin-block-' + i + '-pool'" class="badge badge-primary" [routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
|
||||
{{ block.extras.pool.name}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block-body">
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + '-fees'" class="fees">
|
||||
~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
||||
</ng-container>
|
||||
<ng-template #placeholderBlock>
|
||||
<ng-container *ngIf="block && block.placeholder; else loadingBlock">
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i" class="text-center bitcoin-block mined-block placeholder-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]">
|
||||
|
||||
</div>
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + '-fee-span'" class="fee-span">
|
||||
{{ block?.extras?.feeRange[1] | number:feeRounding }} - {{ block?.extras?.feeRange[block?.extras?.feeRange.length - 1] | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #loadingBlock>
|
||||
<ng-container *ngIf="block && block.loading">
|
||||
<div class="flashing">
|
||||
<div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]"></div>
|
||||
</div>
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + '-total-fees'" *ngIf="showMiningInfo" class="block-size">
|
||||
<app-amount [satoshis]="block.extras?.totalFees ?? 0" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
|
||||
</div>
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + 'block-size'" *ngIf="!showMiningInfo" class="block-size" [innerHTML]="'‎' + (block.size | bytes: 2)"></div>
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + '-transactions'" class="transaction-count">
|
||||
<ng-container *ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container>
|
||||
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
|
||||
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
|
||||
</div>
|
||||
<div [attr.data-cy]="'bitcoin-block-' + i + '-time'" class="time-difference"><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since></div>
|
||||
</div>
|
||||
<div class="animated" [class]="showMiningInfo ? 'show' : 'hide'" *ngIf="block.extras?.pool != undefined">
|
||||
<a [attr.data-cy]="'bitcoin-block-' + i + '-pool'" class="badge badge-primary" [routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
|
||||
{{ block.extras.pool.name}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div [hidden]="!arrowVisible" id="arrow-up" [style.transition]="transition" [ngStyle]="{'left': arrowLeftPx + 'px' }"></div>
|
||||
<div [hidden]="!arrowVisible" id="arrow-up" [style.transition]="arrowTransition" [ngStyle]="{'left': arrowLeftPx + 'px' }"></div>
|
||||
</div>
|
||||
|
||||
<ng-template #loadingBlocksTemplate>
|
||||
|
|
|
@ -25,6 +25,10 @@
|
|||
transition: background 2s, left 2s, transform 1s;
|
||||
}
|
||||
|
||||
.mined-block.placeholder-block {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
.block-size {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
|
@ -96,6 +100,16 @@
|
|||
transform-origin: top;
|
||||
}
|
||||
|
||||
.bitcoin-block.placeholder-block::after {
|
||||
content: none;
|
||||
background: 0;
|
||||
}
|
||||
|
||||
.bitcoin-block.placeholder-block::before {
|
||||
content: none;
|
||||
background: 0;
|
||||
}
|
||||
|
||||
.black-background {
|
||||
background-color: #11131f;
|
||||
z-index: 100;
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { specialBlocks } from '../../app.constants';
|
||||
import { BlockExtended } from '../../interfaces/node-api.interface';
|
||||
import { Location } from '@angular/common';
|
||||
import { config } from 'process';
|
||||
import { CacheService } from 'src/app/services/cache.service';
|
||||
|
||||
interface BlockchainBlock extends BlockExtended {
|
||||
placeholder?: boolean;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-blockchain-blocks',
|
||||
|
@ -12,13 +18,19 @@ import { config } from 'process';
|
|||
styleUrls: ['./blockchain-blocks.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||
export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() static: boolean = false;
|
||||
@Input() offset: number = 0;
|
||||
@Input() height: number = 0;
|
||||
@Input() count: number = 8;
|
||||
|
||||
specialBlocks = specialBlocks;
|
||||
network = '';
|
||||
blocks: BlockExtended[] = [];
|
||||
blocks: BlockchainBlock[] = [];
|
||||
emptyBlocks: BlockExtended[] = this.mountEmptyBlocks();
|
||||
markHeight: number;
|
||||
blocksSubscription: Subscription;
|
||||
blockPageSubscription: Subscription;
|
||||
networkSubscription: Subscription;
|
||||
tabHiddenSubscription: Subscription;
|
||||
markBlockSubscription: Subscription;
|
||||
|
@ -31,7 +43,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||
arrowVisible = false;
|
||||
arrowLeftPx = 30;
|
||||
blocksFilled = false;
|
||||
transition = '1s';
|
||||
arrowTransition = '1s';
|
||||
showMiningInfo = false;
|
||||
timeLtrSubscription: Subscription;
|
||||
timeLtr: boolean;
|
||||
|
@ -47,6 +59,7 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
public cacheService: CacheService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private location: Location,
|
||||
) {
|
||||
|
@ -75,44 +88,52 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||
this.loadingBlocks$ = this.stateService.isLoadingWebSocket$;
|
||||
this.networkSubscription = this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
this.tabHiddenSubscription = this.stateService.isTabHidden$.subscribe((tabHidden) => this.tabHidden = tabHidden);
|
||||
this.blocksSubscription = this.stateService.blocks$
|
||||
.subscribe(([block, txConfirmed]) => {
|
||||
if (this.blocks.some((b) => b.height === block.height)) {
|
||||
return;
|
||||
}
|
||||
if (!this.static) {
|
||||
this.blocksSubscription = this.stateService.blocks$
|
||||
.subscribe(([block, txConfirmed]) => {
|
||||
if (this.blocks.some((b) => b.height === block.height)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.blocks.length && block.height !== this.blocks[0].height + 1) {
|
||||
this.blocks = [];
|
||||
this.blocksFilled = false;
|
||||
}
|
||||
if (this.blocks.length && block.height !== this.blocks[0].height + 1) {
|
||||
this.blocks = [];
|
||||
this.blocksFilled = false;
|
||||
}
|
||||
|
||||
this.blocks.unshift(block);
|
||||
this.blocks = this.blocks.slice(0, this.stateService.env.KEEP_BLOCKS_AMOUNT);
|
||||
this.blocks.unshift(block);
|
||||
this.blocks = this.blocks.slice(0, this.stateService.env.KEEP_BLOCKS_AMOUNT);
|
||||
|
||||
if (this.blocksFilled && !this.tabHidden && block.extras) {
|
||||
block.extras.stage = block.extras.matchRate >= 66 ? 1 : 2;
|
||||
}
|
||||
if (txConfirmed) {
|
||||
this.markHeight = block.height;
|
||||
this.moveArrowToPosition(true, true);
|
||||
} else {
|
||||
this.moveArrowToPosition(true, false);
|
||||
}
|
||||
|
||||
if (txConfirmed) {
|
||||
this.markHeight = block.height;
|
||||
this.moveArrowToPosition(true, true);
|
||||
} else {
|
||||
this.moveArrowToPosition(true, false);
|
||||
}
|
||||
|
||||
this.blockStyles = [];
|
||||
this.blocks.forEach((b) => this.blockStyles.push(this.getStyleForBlock(b)));
|
||||
setTimeout(() => {
|
||||
this.blockStyles = [];
|
||||
this.blocks.forEach((b) => this.blockStyles.push(this.getStyleForBlock(b)));
|
||||
this.cd.markForCheck();
|
||||
}, 50);
|
||||
if (this.blocksFilled) {
|
||||
this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i, i ? -155 : -205)));
|
||||
setTimeout(() => {
|
||||
this.blockStyles = [];
|
||||
this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i)));
|
||||
this.cd.markForCheck();
|
||||
}, 50);
|
||||
} else {
|
||||
this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i)));
|
||||
}
|
||||
|
||||
if (this.blocks.length === this.stateService.env.KEEP_BLOCKS_AMOUNT) {
|
||||
this.blocksFilled = true;
|
||||
if (this.blocks.length === this.stateService.env.KEEP_BLOCKS_AMOUNT) {
|
||||
this.blocksFilled = true;
|
||||
}
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
} else {
|
||||
this.blockPageSubscription = this.cacheService.loadedBlocks$.subscribe((block) => {
|
||||
if (block.height <= this.height && block.height > this.height - this.count) {
|
||||
this.onBlockLoaded(block);
|
||||
}
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
this.markBlockSubscription = this.stateService.markBlock$
|
||||
.subscribe((state) => {
|
||||
|
@ -123,10 +144,26 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||
this.moveArrowToPosition(false);
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
if (this.static) {
|
||||
this.updateStaticBlocks();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (this.static) {
|
||||
const animateSlide = changes.height && (changes.height.currentValue === changes.height.previousValue + 1);
|
||||
this.updateStaticBlocks(animateSlide);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.blocksSubscription.unsubscribe();
|
||||
if (this.blocksSubscription) {
|
||||
this.blocksSubscription.unsubscribe();
|
||||
}
|
||||
if (this.blockPageSubscription) {
|
||||
this.blockPageSubscription.unsubscribe();
|
||||
}
|
||||
this.networkSubscription.unsubscribe();
|
||||
this.tabHiddenSubscription.unsubscribe();
|
||||
this.markBlockSubscription.unsubscribe();
|
||||
|
@ -142,13 +179,13 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||
const blockindex = this.blocks.findIndex((b) => b.height === this.markHeight);
|
||||
if (blockindex > -1) {
|
||||
if (!animate) {
|
||||
this.transition = 'inherit';
|
||||
this.arrowTransition = 'inherit';
|
||||
}
|
||||
this.arrowVisible = true;
|
||||
if (newBlockFromLeft) {
|
||||
this.arrowLeftPx = blockindex * 155 + 30 - 205;
|
||||
setTimeout(() => {
|
||||
this.transition = '2s';
|
||||
this.arrowTransition = '2s';
|
||||
this.arrowLeftPx = blockindex * 155 + 30;
|
||||
this.cd.markForCheck();
|
||||
}, 50);
|
||||
|
@ -156,45 +193,117 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||
this.arrowLeftPx = blockindex * 155 + 30;
|
||||
if (!animate) {
|
||||
setTimeout(() => {
|
||||
this.transition = '2s';
|
||||
this.arrowTransition = '2s';
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.arrowVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
trackByBlocksFn(index: number, item: BlockExtended) {
|
||||
trackByBlocksFn(index: number, item: BlockchainBlock) {
|
||||
return item.height;
|
||||
}
|
||||
|
||||
getStyleForBlock(block: BlockExtended) {
|
||||
updateStaticBlocks(animateSlide: boolean = false) {
|
||||
// reset blocks
|
||||
this.blocks = [];
|
||||
this.blockStyles = [];
|
||||
while (this.blocks.length < this.count) {
|
||||
const height = this.height - this.blocks.length;
|
||||
let block;
|
||||
if (height >= 0) {
|
||||
this.cacheService.loadBlock(height);
|
||||
block = this.cacheService.getCachedBlock(height) || null;
|
||||
}
|
||||
this.blocks.push(block || {
|
||||
placeholder: height < 0,
|
||||
loading: height >= 0,
|
||||
id: '',
|
||||
height,
|
||||
version: 0,
|
||||
timestamp: 0,
|
||||
bits: 0,
|
||||
nonce: 0,
|
||||
difficulty: 0,
|
||||
merkle_root: '',
|
||||
tx_count: 0,
|
||||
size: 0,
|
||||
weight: 0,
|
||||
previousblockhash: '',
|
||||
});
|
||||
}
|
||||
this.blocks = this.blocks.slice(0, this.count);
|
||||
this.blockStyles = [];
|
||||
this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i, animateSlide ? -155 : 0)));
|
||||
this.cd.markForCheck();
|
||||
if (animateSlide) {
|
||||
// animate blocks slide right
|
||||
setTimeout(() => {
|
||||
this.blockStyles = [];
|
||||
this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i)));
|
||||
this.cd.markForCheck();
|
||||
}, 50);
|
||||
this.moveArrowToPosition(true, true);
|
||||
} else {
|
||||
this.moveArrowToPosition(false, false);
|
||||
}
|
||||
}
|
||||
|
||||
onBlockLoaded(block: BlockExtended) {
|
||||
const blockIndex = this.height - block.height;
|
||||
if (blockIndex >= 0 && blockIndex < this.blocks.length) {
|
||||
this.blocks[blockIndex] = block;
|
||||
this.blockStyles[blockIndex] = this.getStyleForBlock(block, blockIndex);
|
||||
}
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
getStyleForBlock(block: BlockchainBlock, index: number, animateEnterFrom: number = 0) {
|
||||
if (!block || block.placeholder) {
|
||||
return this.getStyleForPlaceholderBlock(index, animateEnterFrom);
|
||||
} else if (block.loading) {
|
||||
return this.getStyleForLoadingBlock(index, animateEnterFrom);
|
||||
}
|
||||
const greenBackgroundHeight = 100 - (block.weight / this.stateService.env.BLOCK_WEIGHT_UNITS) * 100;
|
||||
let addLeft = 0;
|
||||
|
||||
if (block?.extras?.stage === 1) {
|
||||
block.extras.stage = 2;
|
||||
addLeft = -205;
|
||||
if (animateEnterFrom) {
|
||||
addLeft = animateEnterFrom || 0;
|
||||
}
|
||||
|
||||
return {
|
||||
left: addLeft + 155 * this.blocks.indexOf(block) + 'px',
|
||||
left: addLeft + 155 * index + 'px',
|
||||
background: `repeating-linear-gradient(
|
||||
#2d3348,
|
||||
#2d3348 ${greenBackgroundHeight}%,
|
||||
${this.gradientColors[this.network][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
||||
${this.gradientColors[this.network][1]} 100%
|
||||
)`,
|
||||
transition: animateEnterFrom ? 'background 2s, transform 1s' : null,
|
||||
};
|
||||
}
|
||||
|
||||
getStyleForEmptyBlock(block: BlockExtended) {
|
||||
let addLeft = 0;
|
||||
getStyleForLoadingBlock(index: number, animateEnterFrom: number = 0) {
|
||||
const addLeft = animateEnterFrom || 0;
|
||||
|
||||
if (block?.extras?.stage === 1) {
|
||||
block.extras.stage = 2;
|
||||
addLeft = -205;
|
||||
}
|
||||
return {
|
||||
left: addLeft + (155 * index) + 'px',
|
||||
background: "#2d3348",
|
||||
};
|
||||
}
|
||||
|
||||
getStyleForPlaceholderBlock(index: number, animateEnterFrom: number = 0) {
|
||||
const addLeft = animateEnterFrom || 0;
|
||||
return {
|
||||
left: addLeft + (155 * index) + 'px',
|
||||
};
|
||||
}
|
||||
|
||||
getStyleForEmptyBlock(block: BlockExtended, animateEnterFrom: number = 0) {
|
||||
const addLeft = animateEnterFrom || 0;
|
||||
|
||||
return {
|
||||
left: addLeft + 155 * this.emptyBlocks.indexOf(block) + 'px',
|
||||
|
@ -219,7 +328,6 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
|||
weight: 0,
|
||||
previousblockhash: '',
|
||||
matchRate: 0,
|
||||
stage: 0,
|
||||
});
|
||||
}
|
||||
return emptyBlocks;
|
||||
|
|
|
@ -2,10 +2,14 @@
|
|||
<div class="position-container" [ngClass]="network ? network : ''">
|
||||
<span>
|
||||
<div class="blocks-wrapper">
|
||||
<app-mempool-blocks></app-mempool-blocks>
|
||||
<app-blockchain-blocks></app-blockchain-blocks>
|
||||
<div class="scroll-spacer" *ngIf="minScrollWidth" [style.left]="minScrollWidth + 'px'"></div>
|
||||
<app-mempool-blocks [hidden]="pageIndex > 0"></app-mempool-blocks>
|
||||
<app-blockchain-blocks [hidden]="pageIndex > 0"></app-blockchain-blocks>
|
||||
<ng-container *ngFor="let page of pages; trackBy: trackByPageFn">
|
||||
<app-blockchain-blocks [static]="true" [offset]="page.offset" [height]="page.height" [count]="blocksPerPage"></app-blockchain-blocks>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div id="divider">
|
||||
<div id="divider" [hidden]="pageIndex > 0">
|
||||
<button class="time-toggle" (click)="toggleTimeDirection()"><fa-icon [icon]="['fas', 'exchange-alt']" [fixedWidth]="true"></fa-icon></button>
|
||||
</div>
|
||||
</span>
|
||||
|
|
|
@ -72,6 +72,15 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.scroll-spacer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loading-block {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
|
||||
|
@ -9,6 +9,11 @@ import { StateService } from '../../services/state.service';
|
|||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class BlockchainComponent implements OnInit, OnDestroy {
|
||||
@Input() pages: any[] = [];
|
||||
@Input() pageIndex: number;
|
||||
@Input() blocksPerPage: number = 8;
|
||||
@Input() minScrollWidth: number = 0;
|
||||
|
||||
network: string;
|
||||
timeLtrSubscription: Subscription;
|
||||
timeLtr: boolean = this.stateService.timeLtr.value;
|
||||
|
@ -29,6 +34,10 @@ export class BlockchainComponent implements OnInit, OnDestroy {
|
|||
this.timeLtrSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
trackByPageFn(index: number, item: { index: number }) {
|
||||
return item.index;
|
||||
}
|
||||
|
||||
toggleTimeDirection() {
|
||||
this.ltrTransitionEnabled = true;
|
||||
this.stateService.timeLtr.next(!this.timeLtr);
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
i18n-ngbTooltip="mining.pool-name" ngbTooltip="Pool" placement="bottom" #miningpool [disableTooltip]="!isEllipsisActive(miningpool)">Pool</th>
|
||||
<th class="timestamp" i18n="latest-blocks.timestamp" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">Timestamp</th>
|
||||
<th class="mined" i18n="latest-blocks.mined" *ngIf="!widget" [class]="indexingAvailable ? '' : 'legacy'">Mined</th>
|
||||
<th *ngIf="indexingAvailable" class="health text-left" i18n="latest-blocks.health" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
|
||||
<th *ngIf="indexingAvailable" class="health text-right" i18n="latest-blocks.health" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
|
||||
i18n-ngbTooltip="latest-blocks.health" ngbTooltip="Health" placement="bottom" #health [disableTooltip]="!isEllipsisActive(health)">Health</th>
|
||||
<th *ngIf="indexingAvailable" class="reward text-right" i18n="latest-blocks.reward" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}"
|
||||
i18n-ngbTooltip="latest-blocks.reward" ngbTooltip="Reward" placement="bottom" #reward [disableTooltip]="!isEllipsisActive(reward)">Reward</th>
|
||||
|
@ -27,7 +27,7 @@
|
|||
<tbody *ngIf="blocks$ | async as blocks; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||
<tr *ngFor="let block of blocks; let i= index; trackBy: trackByBlock">
|
||||
<td class="text-left" [class]="widget ? 'widget' : ''">
|
||||
<a [routerLink]="['/block' | relativeUrl, block.id]">{{ block.height }}</a>
|
||||
<a [routerLink]="['/block' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
|
||||
</td>
|
||||
<td *ngIf="indexingAvailable" class="pool text-left" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<div class="tooltip-custom">
|
||||
|
@ -46,16 +46,23 @@
|
|||
<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since>
|
||||
</td>
|
||||
<td *ngIf="indexingAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<a class="clear-link" [routerLink]="auditScores[block.id] != null ? ['/block/' | relativeUrl, block.id] : null">
|
||||
<div class="progress progress-health">
|
||||
<div class="progress-bar progress-bar-health" role="progressbar"
|
||||
[ngStyle]="{'width': (100 - (auditScores[block.id] || 0)) + '%' }"></div>
|
||||
<div class="progress-text">
|
||||
<span *ngIf="auditScores[block.id] != null;">{{ auditScores[block.id] }}%</span>
|
||||
<span *ngIf="auditScores[block.id] == null">~</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
class="health-badge badge"
|
||||
[class.badge-success]="auditScores[block.id] >= 99"
|
||||
[class.badge-warning]="auditScores[block.id] >= 75 && auditScores[block.id] < 99"
|
||||
[class.badge-danger]="auditScores[block.id] < 75"
|
||||
[routerLink]="auditScores[block.id] != null ? ['/block/' | relativeUrl, block.id] : null"
|
||||
[state]="{ data: { block: block } }"
|
||||
*ngIf="auditScores[block.id] != null; else nullHealth"
|
||||
>{{ auditScores[block.id] }}%</a>
|
||||
<ng-template #nullHealth>
|
||||
<ng-container *ngIf="!loadingScores; else loadingHealth">
|
||||
<span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template #loadingHealth>
|
||||
<span class="skeleton-loader" style="max-width: 60px"></span>
|
||||
</ng-template>
|
||||
</td>
|
||||
<td *ngIf="indexingAvailable" class="reward text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
|
||||
<app-amount [satoshis]="block.extras.reward" [noFiat]="true" digitsInfo="1.2-2"></app-amount>
|
||||
|
|
|
@ -23,6 +23,7 @@ export class BlocksList implements OnInit, OnDestroy {
|
|||
|
||||
indexingAvailable = false;
|
||||
isLoading = true;
|
||||
loadingScores = true;
|
||||
fromBlockHeight = undefined;
|
||||
paginationMaxSize: number;
|
||||
page = 1;
|
||||
|
@ -113,6 +114,7 @@ export class BlocksList implements OnInit, OnDestroy {
|
|||
if (this.indexingAvailable) {
|
||||
this.auditScoreSubscription = this.fromHeightSubject.pipe(
|
||||
switchMap((fromBlockHeight) => {
|
||||
this.loadingScores = true;
|
||||
return this.apiService.getBlockAuditScores$(this.page === 1 ? undefined : fromBlockHeight)
|
||||
.pipe(
|
||||
catchError(() => {
|
||||
|
@ -124,6 +126,7 @@ export class BlocksList implements OnInit, OnDestroy {
|
|||
Object.values(scores).forEach(score => {
|
||||
this.auditScores[score.hash] = score?.matchRate != null ? score.matchRate : null;
|
||||
});
|
||||
this.loadingScores = false;
|
||||
});
|
||||
|
||||
this.latestScoreSubscription = this.stateService.blocks$.pipe(
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<app-search-results #searchResults [hidden]="dropdownHidden" [results]="typeAhead$ | async" (selectedResult)="selectedResult($event)"></app-search-results>
|
||||
</div>
|
||||
<div>
|
||||
<button [disabled]="isSearching" type="submit" class="btn btn-block btn-primary">
|
||||
<button [disabled]="isSearching" type="submit" class="btn btn-block btn-purple">
|
||||
<fa-icon *ngIf="!(isTypeaheading$ | async) else searchLoading" [icon]="['fas', 'search']" [fixedWidth]="true" i18n-title="search-form.search-title" title="Search"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -43,9 +43,6 @@ form {
|
|||
@media (min-width: 1200px) {
|
||||
min-width: 300px;
|
||||
}
|
||||
input {
|
||||
border: 0px;
|
||||
}
|
||||
.btn {
|
||||
width: 100px;
|
||||
}
|
||||
|
|
|
@ -11,8 +11,9 @@
|
|||
<div id="blockchain-container" [dir]="timeLtr ? 'rtl' : 'ltr'" #blockchainContainer
|
||||
(mousedown)="onMouseDown($event)"
|
||||
(dragstart)="onDragStart($event)"
|
||||
(scroll)="onScroll($event)"
|
||||
>
|
||||
<app-blockchain></app-blockchain>
|
||||
<app-blockchain [pageIndex]="pageIndex" [pages]="pages" [blocksPerPage]="blocksPerPage" [minScrollWidth]="minScrollWidth"></app-blockchain>
|
||||
</div>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
|
|
@ -19,16 +19,51 @@ export class StartComponent implements OnInit, OnDestroy {
|
|||
blockchainScrollLeftInit: number;
|
||||
timeLtrSubscription: Subscription;
|
||||
timeLtr: boolean = this.stateService.timeLtr.value;
|
||||
chainTipSubscription: Subscription;
|
||||
chainTip: number = -1;
|
||||
markBlockSubscription: Subscription;
|
||||
@ViewChild('blockchainContainer') blockchainContainer: ElementRef;
|
||||
|
||||
isMobile: boolean = false;
|
||||
blockWidth = 155;
|
||||
blocksPerPage: number = 1;
|
||||
pageWidth: number;
|
||||
firstPageWidth: number;
|
||||
minScrollWidth: number;
|
||||
pageIndex: number = 0;
|
||||
pages: any[] = [];
|
||||
pendingMark: number | void = null;
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.firstPageWidth = 40 + (this.blockWidth * this.stateService.env.KEEP_BLOCKS_AMOUNT);
|
||||
this.onResize();
|
||||
this.updatePages();
|
||||
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
|
||||
this.timeLtr = !!ltr;
|
||||
});
|
||||
this.chainTipSubscription = this.stateService.chainTip$.subscribe((height) => {
|
||||
this.chainTip = height;
|
||||
this.updatePages();
|
||||
if (this.pendingMark != null) {
|
||||
this.scrollToBlock(this.pendingMark);
|
||||
this.pendingMark = null;
|
||||
}
|
||||
});
|
||||
this.markBlockSubscription = this.stateService.markBlock$.subscribe((mark) => {
|
||||
if (mark?.blockHeight != null) {
|
||||
if (this.chainTip >=0) {
|
||||
if (!this.blockInViewport(mark.blockHeight)) {
|
||||
this.scrollToBlock(mark.blockHeight);
|
||||
}
|
||||
} else {
|
||||
this.pendingMark = mark.blockHeight;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.stateService.blocks$
|
||||
.subscribe((blocks: any) => {
|
||||
if (this.stateService.network !== '') {
|
||||
|
@ -55,6 +90,34 @@ export class StartComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(): void {
|
||||
this.isMobile = window.innerWidth <= 767.98;
|
||||
let firstVisibleBlock;
|
||||
let offset;
|
||||
if (this.blockchainContainer?.nativeElement != null) {
|
||||
this.pages.forEach(page => {
|
||||
const left = page.offset - this.getConvertedScrollOffset();
|
||||
const right = left + this.pageWidth;
|
||||
if (left <= 0 && right > 0) {
|
||||
const blockIndex = Math.max(0, Math.floor(left / -this.blockWidth));
|
||||
firstVisibleBlock = page.height - blockIndex;
|
||||
offset = left + (blockIndex * this.blockWidth);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.blocksPerPage = Math.ceil(window.innerWidth / this.blockWidth);
|
||||
this.pageWidth = this.blocksPerPage * this.blockWidth;
|
||||
this.minScrollWidth = this.firstPageWidth + (this.pageWidth * 2);
|
||||
|
||||
if (firstVisibleBlock != null) {
|
||||
this.scrollToBlock(firstVisibleBlock, offset);
|
||||
} else {
|
||||
this.updatePages();
|
||||
}
|
||||
}
|
||||
|
||||
onMouseDown(event: MouseEvent) {
|
||||
this.mouseDragStartX = event.clientX;
|
||||
this.blockchainScrollLeftInit = this.blockchainContainer.nativeElement.scrollLeft;
|
||||
|
@ -70,7 +133,7 @@ export class StartComponent implements OnInit, OnDestroy {
|
|||
if (this.mouseDragStartX != null) {
|
||||
this.stateService.setBlockScrollingInProgress(true);
|
||||
this.blockchainContainer.nativeElement.scrollLeft =
|
||||
this.blockchainScrollLeftInit + this.mouseDragStartX - event.clientX
|
||||
this.blockchainScrollLeftInit + this.mouseDragStartX - event.clientX;
|
||||
}
|
||||
}
|
||||
@HostListener('document:mouseup', [])
|
||||
|
@ -79,7 +142,149 @@ export class StartComponent implements OnInit, OnDestroy {
|
|||
this.stateService.setBlockScrollingInProgress(false);
|
||||
}
|
||||
|
||||
onScroll(e) {
|
||||
const middlePage = this.pageIndex === 0 ? this.pages[0] : this.pages[1];
|
||||
// compensate for css transform
|
||||
const translation = (this.isMobile ? window.innerWidth * 0.95 : window.innerWidth * 0.5);
|
||||
const backThreshold = middlePage.offset + (this.pageWidth * 0.5) + translation;
|
||||
const forwardThreshold = middlePage.offset - (this.pageWidth * 0.5) + translation;
|
||||
const scrollLeft = this.getConvertedScrollOffset();
|
||||
if (scrollLeft > backThreshold) {
|
||||
if (this.shiftPagesBack()) {
|
||||
this.addConvertedScrollOffset(-this.pageWidth);
|
||||
this.blockchainScrollLeftInit -= this.pageWidth;
|
||||
}
|
||||
} else if (scrollLeft < forwardThreshold) {
|
||||
if (this.shiftPagesForward()) {
|
||||
this.addConvertedScrollOffset(this.pageWidth);
|
||||
this.blockchainScrollLeftInit += this.pageWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scrollToBlock(height, blockOffset = 0) {
|
||||
if (!this.blockchainContainer?.nativeElement) {
|
||||
setTimeout(() => { this.scrollToBlock(height, blockOffset); }, 50);
|
||||
return;
|
||||
}
|
||||
const targetHeight = this.isMobile ? height - 1 : height;
|
||||
const viewingPageIndex = this.getPageIndexOf(targetHeight);
|
||||
const pages = [];
|
||||
this.pageIndex = Math.max(viewingPageIndex - 1, 0);
|
||||
let viewingPage = this.getPageAt(viewingPageIndex);
|
||||
const isLastPage = viewingPage.height < this.blocksPerPage;
|
||||
if (isLastPage) {
|
||||
this.pageIndex = Math.max(viewingPageIndex - 2, 0);
|
||||
viewingPage = this.getPageAt(viewingPageIndex);
|
||||
}
|
||||
const left = viewingPage.offset - this.getConvertedScrollOffset();
|
||||
const blockIndex = viewingPage.height - targetHeight;
|
||||
const targetOffset = (this.blockWidth * blockIndex) + left;
|
||||
let deltaOffset = targetOffset - blockOffset;
|
||||
|
||||
if (isLastPage) {
|
||||
pages.push(this.getPageAt(viewingPageIndex - 2));
|
||||
}
|
||||
if (viewingPageIndex > 1) {
|
||||
pages.push(this.getPageAt(viewingPageIndex - 1));
|
||||
}
|
||||
if (viewingPageIndex > 0) {
|
||||
pages.push(viewingPage);
|
||||
}
|
||||
if (!isLastPage) {
|
||||
pages.push(this.getPageAt(viewingPageIndex + 1));
|
||||
}
|
||||
if (viewingPageIndex === 0) {
|
||||
pages.push(this.getPageAt(viewingPageIndex + 2));
|
||||
}
|
||||
|
||||
this.pages = pages;
|
||||
this.addConvertedScrollOffset(deltaOffset);
|
||||
}
|
||||
|
||||
updatePages() {
|
||||
const pages = [];
|
||||
if (this.pageIndex > 0) {
|
||||
pages.push(this.getPageAt(this.pageIndex));
|
||||
}
|
||||
pages.push(this.getPageAt(this.pageIndex + 1));
|
||||
pages.push(this.getPageAt(this.pageIndex + 2));
|
||||
this.pages = pages;
|
||||
}
|
||||
|
||||
shiftPagesBack(): boolean {
|
||||
const nextPage = this.getPageAt(this.pageIndex + 3);
|
||||
if (nextPage.height >= 0) {
|
||||
this.pageIndex++;
|
||||
this.pages.forEach(page => page.offset -= this.pageWidth);
|
||||
if (this.pageIndex !== 1) {
|
||||
this.pages.shift();
|
||||
}
|
||||
this.pages.push(this.getPageAt(this.pageIndex + 2));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
shiftPagesForward(): boolean {
|
||||
if (this.pageIndex > 0) {
|
||||
this.pageIndex--;
|
||||
this.pages.forEach(page => page.offset += this.pageWidth);
|
||||
this.pages.pop();
|
||||
if (this.pageIndex) {
|
||||
this.pages.unshift(this.getPageAt(this.pageIndex));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getPageAt(index: number) {
|
||||
const height = this.chainTip - 8 - ((index - 1) * this.blocksPerPage)
|
||||
return {
|
||||
offset: this.firstPageWidth + (this.pageWidth * (index - 1 - this.pageIndex)),
|
||||
height: height,
|
||||
depth: this.chainTip - height,
|
||||
index: index,
|
||||
};
|
||||
}
|
||||
|
||||
getPageIndexOf(height: number): number {
|
||||
const delta = this.chainTip - 8 - height;
|
||||
return Math.max(0, Math.floor(delta / this.blocksPerPage) + 1);
|
||||
}
|
||||
|
||||
blockInViewport(height: number): boolean {
|
||||
const firstHeight = this.pages[0].height;
|
||||
const translation = (this.isMobile ? window.innerWidth * 0.95 : window.innerWidth * 0.5);
|
||||
const firstX = this.pages[0].offset - this.getConvertedScrollOffset() + translation;
|
||||
const xPos = firstX + ((firstHeight - height) * 155);
|
||||
return xPos > -55 && xPos < (window.innerWidth - 100);
|
||||
}
|
||||
|
||||
getConvertedScrollOffset(): number {
|
||||
if (this.timeLtr) {
|
||||
return -this.blockchainContainer?.nativeElement?.scrollLeft || 0;
|
||||
} else {
|
||||
return this.blockchainContainer?.nativeElement?.scrollLeft || 0;
|
||||
}
|
||||
}
|
||||
|
||||
addConvertedScrollOffset(offset: number): void {
|
||||
if (!this.blockchainContainer?.nativeElement) {
|
||||
return;
|
||||
}
|
||||
if (this.timeLtr) {
|
||||
this.blockchainContainer.nativeElement.scrollLeft -= offset;
|
||||
} else {
|
||||
this.blockchainContainer.nativeElement.scrollLeft += offset;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.timeLtrSubscription.unsubscribe();
|
||||
this.chainTipSubscription.unsubscribe();
|
||||
this.markBlockSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
import { Transaction, Vout } from '../../interfaces/electrs.interface';
|
||||
import { of, merge, Subscription, Observable, Subject, from } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { CacheService } from '../../services/cache.service';
|
||||
import { OpenGraphService } from '../../services/opengraph.service';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
import { SeoService } from '../../services/seo.service';
|
||||
|
@ -45,6 +46,7 @@ export class TransactionPreviewComponent implements OnInit, OnDestroy {
|
|||
private route: ActivatedRoute,
|
||||
private electrsApiService: ElectrsApiService,
|
||||
private stateService: StateService,
|
||||
private cacheService: CacheService,
|
||||
private apiService: ApiService,
|
||||
private seoService: SeoService,
|
||||
private openGraphService: OpenGraphService,
|
||||
|
@ -97,7 +99,7 @@ export class TransactionPreviewComponent implements OnInit, OnDestroy {
|
|||
}),
|
||||
switchMap(() => {
|
||||
let transactionObservable$: Observable<Transaction>;
|
||||
const cached = this.stateService.getTxFromCache(this.txId);
|
||||
const cached = this.cacheService.getTxFromCache(this.txId);
|
||||
if (cached && cached.fee !== -1) {
|
||||
transactionObservable$ = of(cached);
|
||||
} else {
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
import { Transaction } from '../../interfaces/electrs.interface';
|
||||
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from, throwError } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { CacheService } from '../../services/cache.service';
|
||||
import { WebsocketService } from '../../services/websocket.service';
|
||||
import { AudioService } from '../../services/audio.service';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
|
@ -74,6 +75,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
private relativeUrlPipe: RelativeUrlPipe,
|
||||
private electrsApiService: ElectrsApiService,
|
||||
private stateService: StateService,
|
||||
private cacheService: CacheService,
|
||||
private websocketService: WebsocketService,
|
||||
private audioService: AudioService,
|
||||
private apiService: ApiService,
|
||||
|
@ -131,26 +133,20 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.cpfpInfo = null;
|
||||
return;
|
||||
}
|
||||
if (cpfpInfo.effectiveFeePerVsize) {
|
||||
this.tx.effectiveFeePerVsize = cpfpInfo.effectiveFeePerVsize;
|
||||
} else {
|
||||
const lowerFeeParents = cpfpInfo.ancestors.filter(
|
||||
(parent) => parent.fee / (parent.weight / 4) < this.tx.feePerVsize
|
||||
);
|
||||
let totalWeight =
|
||||
this.tx.weight +
|
||||
lowerFeeParents.reduce((prev, val) => prev + val.weight, 0);
|
||||
let totalFees =
|
||||
this.tx.fee +
|
||||
lowerFeeParents.reduce((prev, val) => prev + val.fee, 0);
|
||||
|
||||
if (cpfpInfo?.bestDescendant) {
|
||||
totalWeight += cpfpInfo?.bestDescendant.weight;
|
||||
totalFees += cpfpInfo?.bestDescendant.fee;
|
||||
}
|
||||
|
||||
this.tx.effectiveFeePerVsize = totalFees / (totalWeight / 4);
|
||||
// merge ancestors/descendants
|
||||
const relatives = [...(cpfpInfo.ancestors || []), ...(cpfpInfo.descendants || [])];
|
||||
if (cpfpInfo.bestDescendant && !cpfpInfo.descendants?.length) {
|
||||
relatives.push(cpfpInfo.bestDescendant);
|
||||
}
|
||||
let totalWeight =
|
||||
this.tx.weight +
|
||||
relatives.reduce((prev, val) => prev + val.weight, 0);
|
||||
let totalFees =
|
||||
this.tx.fee +
|
||||
relatives.reduce((prev, val) => prev + val.fee, 0);
|
||||
|
||||
this.tx.effectiveFeePerVsize = totalFees / (totalWeight / 4);
|
||||
|
||||
if (!this.tx.status.confirmed) {
|
||||
this.stateService.markBlock$.next({
|
||||
txFeePerVSize: this.tx.effectiveFeePerVsize,
|
||||
|
@ -203,7 +199,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
}),
|
||||
switchMap(() => {
|
||||
let transactionObservable$: Observable<Transaction>;
|
||||
const cached = this.stateService.getTxFromCache(this.txId);
|
||||
const cached = this.cacheService.getTxFromCache(this.txId);
|
||||
if (cached && cached.fee !== -1) {
|
||||
transactionObservable$ = of(cached);
|
||||
} else {
|
||||
|
@ -302,7 +298,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.waitingForTransaction = false;
|
||||
}
|
||||
this.rbfTransaction = rbfTransaction;
|
||||
this.stateService.setTxCache([this.rbfTransaction]);
|
||||
this.cacheService.setTxCache([this.rbfTransaction]);
|
||||
});
|
||||
|
||||
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { CacheService } from '../../services/cache.service';
|
||||
import { Observable, ReplaySubject, BehaviorSubject, merge, Subscription } from 'rxjs';
|
||||
import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.interface';
|
||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||
|
@ -44,6 +45,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
private cacheService: CacheService,
|
||||
private electrsApiService: ElectrsApiService,
|
||||
private apiService: ApiService,
|
||||
private assetsService: AssetsService,
|
||||
|
@ -91,6 +93,9 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||
filter(() => this.stateService.env.LIGHTNING),
|
||||
switchMap((txIds) => this.apiService.getChannelByTxIds$(txIds)),
|
||||
tap((channels) => {
|
||||
if (!this.transactions) {
|
||||
return;
|
||||
}
|
||||
const transactions = this.transactions.filter((tx) => !tx._channels);
|
||||
channels.forEach((channel, i) => {
|
||||
transactions[i]._channels = channel;
|
||||
|
@ -120,7 +125,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
|||
}
|
||||
|
||||
this.transactionsLength = this.transactions.length;
|
||||
this.stateService.setTxCache(this.transactions);
|
||||
this.cacheService.setTxCache(this.transactions);
|
||||
|
||||
this.transactions.forEach((tx) => {
|
||||
tx['@voutLimit'] = true;
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
<stop offset="100%" stop-color="transparent" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path [attr.d]="middle.path" class="line middle" [style]="middle.style"/>
|
||||
<path *ngIf="hasLine" [attr.d]="middle.path" class="line middle" [style]="middle.style"/>
|
||||
<ng-container *ngFor="let input of inputs; let i = index">
|
||||
<path *ngIf="connectors && !inputData[i].coinbase && !inputData[i].pegin"
|
||||
[attr.d]="input.connectorPath"
|
||||
|
@ -106,7 +106,7 @@
|
|||
(pointerout)="onBlur($event, 'input', i);"
|
||||
(click)="onClick($event, 'input', inputData[i].index);"
|
||||
/>
|
||||
<path
|
||||
<path *ngIf="!input.zeroValue"
|
||||
[attr.d]="input.path"
|
||||
class="line {{input.class}}"
|
||||
[class.highlight]="inputIndex != null && inputData[i].index === inputIndex"
|
||||
|
@ -116,6 +116,16 @@
|
|||
(pointerout)="onBlur($event, 'input', i);"
|
||||
(click)="onClick($event, 'input', inputData[i].index);"
|
||||
/>
|
||||
<path *ngIf="input.zeroValue"
|
||||
[attr.d]="input.path"
|
||||
class="line {{input.class}} zerovalue"
|
||||
[class.highlight]="inputIndex != null && inputData[i].index === inputIndex"
|
||||
[class.zerovalue]="input.zeroValue"
|
||||
[style]="input.style"
|
||||
(pointerover)="onHover($event, 'input', i);"
|
||||
(pointerout)="onBlur($event, 'input', i);"
|
||||
(click)="onClick($event, 'input', inputData[i].index);"
|
||||
/>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let output of outputs; let i = index">
|
||||
<path *ngIf="connectors && outspends[outputData[i].index]?.spent"
|
||||
|
|
|
@ -68,6 +68,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||
outspends: Outspend[] = [];
|
||||
zeroValueWidth = 60;
|
||||
zeroValueThickness = 20;
|
||||
hasLine: boolean;
|
||||
|
||||
outspendsSubscription: Subscription;
|
||||
refreshOutspends$: ReplaySubject<string> = new ReplaySubject();
|
||||
|
@ -162,7 +163,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||
let truncatedInputs = this.tx.vin.map((v, i) => {
|
||||
return {
|
||||
type: 'input',
|
||||
value: v?.prevout?.value,
|
||||
value: v?.prevout?.value || (v?.is_coinbase && !totalValue ? 0 : undefined),
|
||||
txid: v.txid,
|
||||
vout: v.vout,
|
||||
address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(),
|
||||
|
@ -198,6 +199,9 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||
path: `M ${(this.width / 2) - this.midWidth} ${(this.height / 2) + 0.5} L ${(this.width / 2) + this.midWidth} ${(this.height / 2) + 0.5}`,
|
||||
style: `stroke-width: ${this.combinedWeight + 1}; stroke: ${this.gradient[1]}`
|
||||
};
|
||||
|
||||
this.hasLine = this.inputs.reduce((line, put) => line || !put.zeroValue, false)
|
||||
&& this.outputs.reduce((line, put) => line || !put.zeroValue, false);
|
||||
}
|
||||
|
||||
calcTotalValue(tx: Transaction): number {
|
||||
|
@ -278,6 +282,9 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
|||
lineParams.forEach((line, i) => {
|
||||
if (xputs[i].value === 0) {
|
||||
line.outerY = lastOuter + (this.zeroValueThickness / 2);
|
||||
if (xputs.length === 1) {
|
||||
line.outerY = (this.height / 2);
|
||||
}
|
||||
lastOuter += this.zeroValueThickness + spacing;
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ export interface CpfpInfo {
|
|||
ancestors: Ancestor[];
|
||||
descendants?: Ancestor[];
|
||||
bestDescendant?: BestDescendant | null;
|
||||
effectiveFeePerVsize?: number;
|
||||
}
|
||||
|
||||
export interface DifficultyAdjustment {
|
||||
|
@ -122,8 +121,6 @@ export interface BlockExtension {
|
|||
name: string;
|
||||
slug: string;
|
||||
}
|
||||
|
||||
stage?: number; // Frontend only
|
||||
}
|
||||
|
||||
export interface BlockExtended extends Block {
|
||||
|
|
|
@ -118,7 +118,7 @@ export class NodesNetworksChartComponent implements OnInit {
|
|||
color: 'grey',
|
||||
fontSize: 15
|
||||
},
|
||||
text: $localize`Indexing in progess`,
|
||||
text: $localize`Indexing in progress`,
|
||||
left: 'center',
|
||||
top: 'center',
|
||||
};
|
||||
|
|
|
@ -109,7 +109,7 @@ export class LightningStatisticsChartComponent implements OnInit {
|
|||
color: 'grey',
|
||||
fontSize: 15
|
||||
},
|
||||
text: $localize`Indexing in progess`,
|
||||
text: $localize`Indexing in progress`,
|
||||
left: 'center',
|
||||
top: 'center'
|
||||
};
|
||||
|
|
105
frontend/src/app/services/cache.service.ts
Normal file
105
frontend/src/app/services/cache.service.ts
Normal file
|
@ -0,0 +1,105 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { firstValueFrom, Subject, Subscription} from 'rxjs';
|
||||
import { Transaction } from '../interfaces/electrs.interface';
|
||||
import { BlockExtended } from '../interfaces/node-api.interface';
|
||||
import { StateService } from './state.service';
|
||||
import { ApiService } from './api.service';
|
||||
|
||||
const BLOCK_CACHE_SIZE = 500;
|
||||
const KEEP_RECENT_BLOCKS = 50;
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CacheService {
|
||||
loadedBlocks$ = new Subject<BlockExtended>();
|
||||
tip: number = 0;
|
||||
|
||||
txCache: { [txid: string]: Transaction } = {};
|
||||
|
||||
blockCache: { [height: number]: BlockExtended } = {};
|
||||
blockLoading: { [height: number]: boolean } = {};
|
||||
copiesInBlockQueue: { [height: number]: number } = {};
|
||||
blockPriorities: number[] = [];
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
private apiService: ApiService,
|
||||
) {
|
||||
this.stateService.blocks$.subscribe(([block]) => {
|
||||
this.addBlockToCache(block);
|
||||
this.clearBlocks();
|
||||
});
|
||||
this.stateService.chainTip$.subscribe((height) => {
|
||||
this.tip = height;
|
||||
});
|
||||
}
|
||||
|
||||
setTxCache(transactions) {
|
||||
this.txCache = {};
|
||||
transactions.forEach(tx => {
|
||||
this.txCache[tx.txid] = tx;
|
||||
});
|
||||
}
|
||||
|
||||
getTxFromCache(txid) {
|
||||
if (this.txCache && this.txCache[txid]) {
|
||||
return this.txCache[txid];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
addBlockToCache(block: BlockExtended) {
|
||||
this.blockCache[block.height] = block;
|
||||
this.bumpBlockPriority(block.height);
|
||||
}
|
||||
|
||||
async loadBlock(height) {
|
||||
if (!this.blockCache[height] && !this.blockLoading[height]) {
|
||||
const chunkSize = 10;
|
||||
const maxHeight = Math.ceil(height / chunkSize) * chunkSize;
|
||||
for (let i = 0; i < chunkSize; i++) {
|
||||
this.blockLoading[maxHeight - i] = true;
|
||||
}
|
||||
const result = await firstValueFrom(this.apiService.getBlocks$(maxHeight));
|
||||
for (let i = 0; i < chunkSize; i++) {
|
||||
delete this.blockLoading[maxHeight - i];
|
||||
}
|
||||
if (result && result.length) {
|
||||
result.forEach(block => {
|
||||
this.addBlockToCache(block);
|
||||
this.loadedBlocks$.next(block);
|
||||
});
|
||||
}
|
||||
this.clearBlocks();
|
||||
} else {
|
||||
this.bumpBlockPriority(height);
|
||||
}
|
||||
}
|
||||
|
||||
// increase the priority of a block, to delay removal
|
||||
bumpBlockPriority(height) {
|
||||
this.blockPriorities.push(height);
|
||||
this.copiesInBlockQueue[height] = (this.copiesInBlockQueue[height] || 0) + 1;
|
||||
}
|
||||
|
||||
// remove lowest priority blocks from the cache
|
||||
clearBlocks() {
|
||||
while (Object.keys(this.blockCache).length > (BLOCK_CACHE_SIZE + KEEP_RECENT_BLOCKS) && this.blockPriorities.length > KEEP_RECENT_BLOCKS) {
|
||||
const height = this.blockPriorities.shift();
|
||||
if (this.copiesInBlockQueue[height] > 1) {
|
||||
this.copiesInBlockQueue[height]--;
|
||||
} else if ((this.tip - height) < KEEP_RECENT_BLOCKS) {
|
||||
this.bumpBlockPriority(height);
|
||||
} else {
|
||||
delete this.blockCache[height];
|
||||
delete this.copiesInBlockQueue[height];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCachedBlock(height) {
|
||||
return this.blockCache[height];
|
||||
}
|
||||
}
|
|
@ -104,6 +104,7 @@ export class StateService {
|
|||
backendInfo$ = new ReplaySubject<IBackendInfo>(1);
|
||||
loadingIndicators$ = new ReplaySubject<ILoadingIndicators>(1);
|
||||
recommendedFees$ = new ReplaySubject<Recommendedfees>(1);
|
||||
chainTip$ = new ReplaySubject<number>(-1);
|
||||
|
||||
live2Chart$ = new Subject<OptimizedMempoolStats>();
|
||||
|
||||
|
@ -111,15 +112,13 @@ export class StateService {
|
|||
connectionState$ = new BehaviorSubject<0 | 1 | 2>(2);
|
||||
isTabHidden$: Observable<boolean>;
|
||||
|
||||
markBlock$ = new ReplaySubject<MarkBlockState>();
|
||||
markBlock$ = new BehaviorSubject<MarkBlockState>({});
|
||||
keyNavigation$ = new Subject<KeyboardEvent>();
|
||||
|
||||
blockScrolling$: Subject<boolean> = new Subject<boolean>();
|
||||
timeLtr: BehaviorSubject<boolean>;
|
||||
hideFlow: BehaviorSubject<boolean>;
|
||||
|
||||
txCache: { [txid: string]: Transaction } = {};
|
||||
|
||||
constructor(
|
||||
@Inject(PLATFORM_ID) private platformId: any,
|
||||
@Inject(LOCALE_ID) private locale: string,
|
||||
|
@ -274,18 +273,15 @@ export class StateService {
|
|||
return this.network === 'liquid' || this.network === 'liquidtestnet';
|
||||
}
|
||||
|
||||
setTxCache(transactions) {
|
||||
this.txCache = {};
|
||||
transactions.forEach(tx => {
|
||||
this.txCache[tx.txid] = tx;
|
||||
});
|
||||
resetChainTip() {
|
||||
this.latestBlockHeight = -1;
|
||||
this.chainTip$.next(-1);
|
||||
}
|
||||
|
||||
getTxFromCache(txid) {
|
||||
if (this.txCache && this.txCache[txid]) {
|
||||
return this.txCache[txid];
|
||||
} else {
|
||||
return null;
|
||||
|
||||
updateChainTip(height) {
|
||||
if (height > this.latestBlockHeight) {
|
||||
this.latestBlockHeight = height;
|
||||
this.chainTip$.next(height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ export class WebsocketService {
|
|||
clearTimeout(this.onlineCheckTimeout);
|
||||
clearTimeout(this.onlineCheckTimeoutTwo);
|
||||
|
||||
this.stateService.latestBlockHeight = -1;
|
||||
this.stateService.resetChainTip();
|
||||
|
||||
this.websocketSubject.complete();
|
||||
this.subscription.unsubscribe();
|
||||
|
@ -224,12 +224,14 @@ export class WebsocketService {
|
|||
handleResponse(response: WebsocketResponse) {
|
||||
if (response.blocks && response.blocks.length) {
|
||||
const blocks = response.blocks;
|
||||
let maxHeight = 0;
|
||||
blocks.forEach((block: BlockExtended) => {
|
||||
if (block.height > this.stateService.latestBlockHeight) {
|
||||
this.stateService.latestBlockHeight = block.height;
|
||||
maxHeight = Math.max(maxHeight, block.height);
|
||||
this.stateService.blocks$.next([block, false]);
|
||||
}
|
||||
});
|
||||
this.stateService.updateChainTip(maxHeight);
|
||||
}
|
||||
|
||||
if (response.tx) {
|
||||
|
@ -238,7 +240,7 @@ export class WebsocketService {
|
|||
|
||||
if (response.block) {
|
||||
if (response.block.height > this.stateService.latestBlockHeight) {
|
||||
this.stateService.latestBlockHeight = response.block.height;
|
||||
this.stateService.updateChainTip(response.block.height);
|
||||
this.stateService.blocks$.next([response.block, !!response.txConfirmed]);
|
||||
}
|
||||
|
||||
|
|
|
@ -1471,6 +1471,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="1405c5f1a9834338ff13442c550927ab7144fdc8" datatype="html">
|
||||
<source>Community Integrations</source>
|
||||
<target>Intégrations communautaires</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/about/about.component.html</context>
|
||||
<context context-type="linenumber">191,193</context>
|
||||
|
@ -1544,6 +1545,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="address-label.multisig" datatype="html">
|
||||
<source>Multisig <x id="multisigM" equiv-text="ms.m"/> of <x id="multisigN" equiv-text="ms.n"/></source>
|
||||
<target>Multi-signature <x id="multisigM" equiv-text="ms.m"/> de <x id="multisigN" equiv-text="ms.n"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/address-labels/address-labels.component.ts</context>
|
||||
<context context-type="linenumber">105</context>
|
||||
|
@ -2027,6 +2029,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4718502b32e47c66db00ac7cf4f7f058acb6e849" datatype="html">
|
||||
<source>Block </source>
|
||||
<target>Bloc</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-audit/block-audit.component.html</context>
|
||||
<context context-type="linenumber">7,9</context>
|
||||
|
@ -2035,6 +2038,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="adb216f561e9101b11a8288cc7da44afa1ab33b8" datatype="html">
|
||||
<source>Template vs Mined</source>
|
||||
<target>Modèle vs Miné</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-audit/block-audit.component.html</context>
|
||||
<context context-type="linenumber">11,17</context>
|
||||
|
@ -2121,6 +2125,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="e9503a2605cf5d41273a7d5ad653873f28f8f1a3" datatype="html">
|
||||
<source>Match rate</source>
|
||||
<target>Taux de correspondance</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-audit/block-audit.component.html</context>
|
||||
<context context-type="linenumber">64,67</context>
|
||||
|
@ -2129,6 +2134,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="c97ea3cbb05527c76b0011820ca76e8e00632ca1" datatype="html">
|
||||
<source>Missing txs</source>
|
||||
<target>Txs manquantes</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-audit/block-audit.component.html</context>
|
||||
<context context-type="linenumber">68,71</context>
|
||||
|
@ -2137,6 +2143,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="94d8765805623dd51e562e6e4bb669a944bbe516" datatype="html">
|
||||
<source>Added txs</source>
|
||||
<target>Txs ajoutées</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-audit/block-audit.component.html</context>
|
||||
<context context-type="linenumber">72,75</context>
|
||||
|
@ -2145,6 +2152,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="da5d7e263a3c6dc585d04844074afaa7229b88a0" datatype="html">
|
||||
<source>Missing</source>
|
||||
<target>Manquantes</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-audit/block-audit.component.html</context>
|
||||
<context context-type="linenumber">84,85</context>
|
||||
|
@ -2153,6 +2161,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="80e3b490720757978c99a7b5af3885faf202b955" datatype="html">
|
||||
<source>Added</source>
|
||||
<target>Ajoutées</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-audit/block-audit.component.html</context>
|
||||
<context context-type="linenumber">86,92</context>
|
||||
|
@ -2470,6 +2479,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="80065834848189518" datatype="html">
|
||||
<source>No data to display yet. Try again later.</source>
|
||||
<target>Aucune donnée à afficher pour le moment. Réessayez plus tard.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-prediction-graph/block-prediction-graph.component.ts</context>
|
||||
<context context-type="linenumber">108,103</context>
|
||||
|
@ -2523,6 +2533,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7f5d0c10614e8a34f0e2dad33a0568277c50cf69" datatype="html">
|
||||
<source>Block</source>
|
||||
<target>Bloc</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block/block-preview.component.html</context>
|
||||
<context context-type="linenumber">3,7</context>
|
||||
|
@ -2535,6 +2546,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="445c243f1d1cd2c7f0df430cdb2fa25639600396" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="</ng-template> </h1> <div class="blockhash" *ngIf="block"/></source>
|
||||
<target> <x id="INTERPOLATION" equiv-text="</ng-template> </h1> <div class="blockhash" *ngIf="block"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block/block-preview.component.html</context>
|
||||
<context context-type="linenumber">11,12</context>
|
||||
|
@ -3250,6 +3262,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="5115edb23059f4dcfe6ce0a979e40962a149c35c" datatype="html">
|
||||
<source>Hashrate & Difficulty</source>
|
||||
<target>Taux de hachage & difficulté</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">15,16</context>
|
||||
|
@ -3258,6 +3271,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="a3382a60534e9fff5c3a0fcde39bab356afc4a59" datatype="html">
|
||||
<source>Lightning</source>
|
||||
<target>Lightning</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">31</context>
|
||||
|
@ -3266,6 +3280,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="b420668a91f8ebaf6e6409c4ba87f1d45961d2bd" datatype="html">
|
||||
<source>Lightning Nodes Per Network</source>
|
||||
<target>Nœuds Lightning par réseau</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">34</context>
|
||||
|
@ -3286,6 +3301,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="ea8db27e6db64f8b940711948c001a1100e5fe9f" datatype="html">
|
||||
<source>Lightning Network Capacity</source>
|
||||
<target>Capacité du réseau Lightning</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">36</context>
|
||||
|
@ -3306,6 +3322,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="8573a1576789bd2c4faeaed23037c4917812c6cf" datatype="html">
|
||||
<source>Lightning Nodes Per ISP</source>
|
||||
<target>Nœuds Lightning par FAI</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">38</context>
|
||||
|
@ -3318,6 +3335,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="9d3ad4c6623870d96b65fb7a708fed6ce7c20044" datatype="html">
|
||||
<source>Lightning Nodes Per Country</source>
|
||||
<target>Nœuds Lightning par pays</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">40</context>
|
||||
|
@ -3334,6 +3352,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="af8560ca50882114be16c951650f83bca73161a7" datatype="html">
|
||||
<source>Lightning Nodes World Map</source>
|
||||
<target>Carte du monde des nœuds Lightning</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">42</context>
|
||||
|
@ -3350,6 +3369,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="b482ceceb39c7a045cb2ab2c64f7091d21e63d44" datatype="html">
|
||||
<source>Lightning Nodes Channels World Map</source>
|
||||
<target>Carte du monde des canaux Lightning</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">44</context>
|
||||
|
@ -3470,6 +3490,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="142e923d3b04186ac6ba23387265d22a2fa404e0" datatype="html">
|
||||
<source>Lightning Explorer</source>
|
||||
<target>Explorateur Lightning</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/master-page/master-page.component.html</context>
|
||||
<context context-type="linenumber">44,45</context>
|
||||
|
@ -3482,6 +3503,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7cbedd89f60daafaf0e56363900d666a4e02ffb1" datatype="html">
|
||||
<source>beta</source>
|
||||
<target>bêta</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/master-page/master-page.component.html</context>
|
||||
<context context-type="linenumber">45,48</context>
|
||||
|
@ -3737,6 +3759,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="2158ea60725d3a97aed6f0f00aa7df48d7bb42ff" datatype="html">
|
||||
<source>mining pool</source>
|
||||
<target>Pool de minage</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/pool/pool-preview.component.html</context>
|
||||
<context context-type="linenumber">3,5</context>
|
||||
|
@ -4067,6 +4090,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7deec1c1520f06170e1f8e8ddfbe4532312f638f" datatype="html">
|
||||
<source>Explore the full Bitcoin ecosystem</source>
|
||||
<target>Explorez tout l'écosystème Bitcoin</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/search-form/search-form.component.html</context>
|
||||
<context context-type="linenumber">4,6</context>
|
||||
|
@ -4427,6 +4451,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="53fbdc20554c4e68ae509f652b38ab80021c0739" datatype="html">
|
||||
<source>Flow</source>
|
||||
<target>Flux</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/transaction/transaction.component.html</context>
|
||||
<context context-type="linenumber">195,198</context>
|
||||
|
@ -4440,6 +4465,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="d0fd8887b50687cfc0fc1f6569f6fd6c5db4ffc0" datatype="html">
|
||||
<source>Hide diagram</source>
|
||||
<target>Masquer le diagramme</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/transaction/transaction.component.html</context>
|
||||
<context context-type="linenumber">198,203</context>
|
||||
|
@ -4448,6 +4474,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="f0c5f6f270e70cbe063b5368fcf48f9afc1abd9b" datatype="html">
|
||||
<source>Show more</source>
|
||||
<target>Montrer plus</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/transaction/transaction.component.html</context>
|
||||
<context context-type="linenumber">219,221</context>
|
||||
|
@ -4456,6 +4483,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="5403a767248e304199592271bba3366d2ca3f903" datatype="html">
|
||||
<source>Show less</source>
|
||||
<target>Montrer moins</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/transaction/transaction.component.html</context>
|
||||
<context context-type="linenumber">221,227</context>
|
||||
|
@ -4464,6 +4492,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="e7aa6db8df12d0158df972b6abfc65a8478b2b7d" datatype="html">
|
||||
<source>Show diagram</source>
|
||||
<target>Afficher le diagramme</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/transaction/transaction.component.html</context>
|
||||
<context context-type="linenumber">241,242</context>
|
||||
|
@ -4657,6 +4686,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="ac0c4b49e44c42db35ddf590fb5f78375a891b01" datatype="html">
|
||||
<source>other inputs</source>
|
||||
<target>autres entrées</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html</context>
|
||||
<context context-type="linenumber">12</context>
|
||||
|
@ -4665,6 +4695,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="8900247c0476fea8fcbee57a72a1d1da5ddd40ff" datatype="html">
|
||||
<source>other outputs</source>
|
||||
<target>autres sorties</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
|
@ -4673,6 +4704,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="3e242f213dd1a0754aad9164aa80887d67708500" datatype="html">
|
||||
<source>Input</source>
|
||||
<target>Entrées</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html</context>
|
||||
<context context-type="linenumber">43</context>
|
||||
|
@ -4681,6 +4713,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7a080851e25a41e898776a9c90cf8dbe81027f9a" datatype="html">
|
||||
<source>Output</source>
|
||||
<target>Sorties</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html</context>
|
||||
<context context-type="linenumber">44</context>
|
||||
|
@ -4689,6 +4722,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>Cette transaction a permis d'économiser <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedSegwitGains * 100 | number: '1.0-0' }}"/> % sur les frais en utilisant SegWit natif</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>
|
||||
|
@ -4715,6 +4749,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>Cette transaction a permis d'économiser <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedSegwitGains * 100 | number: '1.0-0' }}"/> % sur les frais en utilisant SegWit et pourrait économiser <x id="INTERPOLATION_1" equiv-text="{{ segwitGains.potentialSegwitGains * 100 | number : '1.0-0' }}"/> % de plus en passant entièrement à SegWit natif</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>
|
||||
|
@ -4723,6 +4758,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>Cette transaction pourrait économiser <x id="INTERPOLATION" equiv-text="{{ segwitGains.potentialSegwitGains * 100 | number : '1.0-0' }}"/> % sur les frais en passant à SegWit natif ou <x id="INTERPOLATION_1" equiv-text="{{ segwitGains.potentialP2shSegwitGains * 100 | number: '1.0-0' }}"/> % en passant à SegWit-P2SH</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>
|
||||
|
@ -4731,6 +4767,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>Cette transaction utilise Taproot et a ainsi économisé au moins <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedTaprootGains * 100 | number: '1.0-0' }}"/> % sur les frais</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>
|
||||
|
@ -4739,6 +4776,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="65d5167c4947e3ad81758d238a7ac7e893c261f0" datatype="html">
|
||||
<source>Taproot</source>
|
||||
<target>Taproot</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>
|
||||
|
@ -4760,6 +4798,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>Cette transaction utilise Taproot et a déjà économisé au moins <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedTaprootGains * 100 | number: '1.0-0' }}"/> % sur les frais, mais pourrait économiser <x id="INTERPOLATION_1" equiv-text="{{ segwitGains.potentialTaprootGains * 100 | number: '1.0-0' }}"/> % supplémentaires en utilisant pleinement Taproot</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>
|
||||
|
@ -4768,6 +4807,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>Cette transaction pourrait économiser <x id="INTERPOLATION" equiv-text="{{ segwitGains.potentialTaprootGains * 100 | number: '1.0-0' }}"/> % sur les frais en utilisant Taproot</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>
|
||||
|
@ -4785,6 +4825,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="07883574bb93ea23b764861f56a525bdaf907513" datatype="html">
|
||||
<source>This transaction supports Replace-By-Fee (RBF) allowing fee bumping</source>
|
||||
<target>Cette transaction prend en charge le Replace-By-Fee/remplacement par frais (RBF), permettant une augmentation des frais</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-features/tx-features.component.html</context>
|
||||
<context context-type="linenumber">25</context>
|
||||
|
@ -5021,6 +5062,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4ca458fe3274d1c79640b052d57cf3b900b650b6" datatype="html">
|
||||
<source>Base fee</source>
|
||||
<target>Frais de base</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">30</context>
|
||||
|
@ -5033,6 +5075,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="6acd06bd5a3af583cd46c6d9f7954d7a2b44095e" datatype="html">
|
||||
<source>mSats</source>
|
||||
<target>mSats</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">36</context>
|
||||
|
@ -5049,6 +5092,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="fb2137ba0df55f21a9d6b6ad08d56d74ad852e0e" datatype="html">
|
||||
<source>This channel supports zero base fee routing</source>
|
||||
<target>Ce canal prend en charge le routage sans frais de base</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">45</context>
|
||||
|
@ -5057,6 +5101,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="3ec76ccfe1fdcbfd7ea152392f6472d93d4e8cab" datatype="html">
|
||||
<source>Zero base fee</source>
|
||||
<target>Aucun frais de base</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">46</context>
|
||||
|
@ -5065,6 +5110,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="b5e42e06ea8a4012a38eef209104bbd9dd1a0fc0" datatype="html">
|
||||
<source>This channel does not support zero base fee routing</source>
|
||||
<target>Ce canal ne prend pas en charge le routage sans frais de base</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">51</context>
|
||||
|
@ -5073,6 +5119,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="09a1bc9c4198e87e9e974a51e86b181429b480d3" datatype="html">
|
||||
<source>Non-zero base fee</source>
|
||||
<target>Frais de base non nuls</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">52</context>
|
||||
|
@ -5081,6 +5128,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="055060668d0b9902c37abfb6168a08a36eba4496" datatype="html">
|
||||
<source>Min HTLC</source>
|
||||
<target>HTLC min.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">58</context>
|
||||
|
@ -5089,6 +5137,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="c3d94c1a5aef6211f4a902027bd08540d7222b0d" datatype="html">
|
||||
<source>Max HTLC</source>
|
||||
<target>HTLC max.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">64</context>
|
||||
|
@ -5097,6 +5146,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="9fe79011b50c2ca1f9b7db7066046631bfc6b3cb" datatype="html">
|
||||
<source>Timelock delta</source>
|
||||
<target>Timelock delta</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">70</context>
|
||||
|
@ -5105,6 +5155,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="205c1b86ac1cc419c4d0cca51fdde418c4ffdc20" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> channels</source>
|
||||
<target> <x id="INTERPOLATION" equiv-text="{{ i }}"/> canaux</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">80</context>
|
||||
|
@ -5117,6 +5168,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="3bbd25c289760a3ba57e30df5ad5fe8977ab25a5" datatype="html">
|
||||
<source>lightning channel</source>
|
||||
<target>canal Lightning</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-preview.component.html</context>
|
||||
<context context-type="linenumber">3,5</context>
|
||||
|
@ -5125,6 +5177,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="43c4133c7a0263d2e33dd4c2e74d40784b2e4b1c" datatype="html">
|
||||
<source>Inactive</source>
|
||||
<target>Inactif</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-preview.component.html</context>
|
||||
<context context-type="linenumber">10,11</context>
|
||||
|
@ -5141,6 +5194,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="b36e1450940b7f6028d8587568c7d669b53f7a06" datatype="html">
|
||||
<source>Active</source>
|
||||
<target>Actif</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-preview.component.html</context>
|
||||
<context context-type="linenumber">11,12</context>
|
||||
|
@ -5157,6 +5211,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4804b8e78964cee9e5c85f31fd982639b97780b2" datatype="html">
|
||||
<source>Closed</source>
|
||||
<target>Fermé</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-preview.component.html</context>
|
||||
<context context-type="linenumber">12,14</context>
|
||||
|
@ -5177,6 +5232,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="1b051734b0ee9021991c91b3ed4e81c244322462" datatype="html">
|
||||
<source>Created</source>
|
||||
<target>Établi</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-preview.component.html</context>
|
||||
<context context-type="linenumber">23,26</context>
|
||||
|
@ -5189,6 +5245,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="ce9dfdc6dccb28dc75a78c704e09dc18fb02dcfa" datatype="html">
|
||||
<source>Capacity</source>
|
||||
<target>Capacité</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-preview.component.html</context>
|
||||
<context context-type="linenumber">27,28</context>
|
||||
|
@ -5241,6 +5298,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="8fd0077b032e360ece45c4fd655f85b2400dcb83" datatype="html">
|
||||
<source>ppm</source>
|
||||
<target>ppm</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>
|
||||
|
@ -5261,6 +5319,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="13142ad9637003749d667393aaae7a286d1eba5b" datatype="html">
|
||||
<source>Lightning channel</source>
|
||||
<target>Canal Lightning</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel.component.html</context>
|
||||
<context context-type="linenumber">2,5</context>
|
||||
|
@ -5273,6 +5332,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="8dad9f60ff582b632a864f22c7466327793c3f09" datatype="html">
|
||||
<source>Last update</source>
|
||||
<target>Dernière mise à jour</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel.component.html</context>
|
||||
<context context-type="linenumber">33,34</context>
|
||||
|
@ -5305,6 +5365,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="0c134c6787c6b763446c096ea5233ace6fd9116d" datatype="html">
|
||||
<source>Closing date</source>
|
||||
<target>Date de clôture</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel.component.html</context>
|
||||
<context context-type="linenumber">37,38</context>
|
||||
|
@ -5317,6 +5378,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="cdd2ea2e12437df848ec474ac15af48859bd09a0" datatype="html">
|
||||
<source>Opening transaction</source>
|
||||
<target>Transaction d'ouverture</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel.component.html</context>
|
||||
<context context-type="linenumber">73,74</context>
|
||||
|
@ -5325,6 +5387,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="50411064ac48e15659d1985b414ae91af0c8cd36" datatype="html">
|
||||
<source>Closing transaction</source>
|
||||
<target>Transaction de clôture</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel.component.html</context>
|
||||
<context context-type="linenumber">82,84</context>
|
||||
|
@ -5333,6 +5396,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="6008566722612122663" datatype="html">
|
||||
<source>Channel: <x id="PH" equiv-text="value.short_id"/></source>
|
||||
<target>Canal : <x id="PH" equiv-text="value.short_id"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel.component.ts</context>
|
||||
<context context-type="linenumber">37</context>
|
||||
|
@ -5340,6 +5404,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="c9039b1b13b3ef165b66b3c5d79f810ab1ebb050" datatype="html">
|
||||
<source>Open</source>
|
||||
<target>Ouvert</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-list/channels-list.component.html</context>
|
||||
<context context-type="linenumber">5,7</context>
|
||||
|
@ -5348,6 +5413,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="a43e63c25599408ef14b33c80dd523021b21f846" datatype="html">
|
||||
<source>No channels to display</source>
|
||||
<target>Aucun canal à afficher</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-list/channels-list.component.html</context>
|
||||
<context context-type="linenumber">29,35</context>
|
||||
|
@ -5356,6 +5422,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="fbaaeb297e70b9a800acf841b9d26c19d60651ef" datatype="html">
|
||||
<source>Alias</source>
|
||||
<target>Alias</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-list/channels-list.component.html</context>
|
||||
<context context-type="linenumber">35,37</context>
|
||||
|
@ -5392,6 +5459,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="81b97b8ea996ad1e4f9fca8415021850214884b1" datatype="html">
|
||||
<source>Status</source>
|
||||
<target>Statut</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-list/channels-list.component.html</context>
|
||||
<context context-type="linenumber">37,38</context>
|
||||
|
@ -5400,6 +5468,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="0cd107458dce99721e72971d426a5a3106074331" datatype="html">
|
||||
<source>Channel ID</source>
|
||||
<target>ID du canal</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-list/channels-list.component.html</context>
|
||||
<context context-type="linenumber">41,45</context>
|
||||
|
@ -5408,6 +5477,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="e4b2d9e6a2ab9e6ca34027ec03beaac42b7badd4" datatype="html">
|
||||
<source>sats</source>
|
||||
<target>sats</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-list/channels-list.component.html</context>
|
||||
<context context-type="linenumber">61,65</context>
|
||||
|
@ -5460,6 +5530,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="ab456546aa39de3328fcfdf077f410b5ff1aa773" datatype="html">
|
||||
<source>Avg Capacity</source>
|
||||
<target>Capacité moy</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">13,15</context>
|
||||
|
@ -5472,6 +5543,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="f68705670e611f13da1a43e90f9c97d8761dd9ef" datatype="html">
|
||||
<source>Avg Fee Rate</source>
|
||||
<target>Taux de frais moy</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">26,28</context>
|
||||
|
@ -5484,6 +5556,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>Le taux de frais moyen facturé par les nœuds de routage, en ignorant les taux de frais > 0,5 % ou 5 000 ppm</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>
|
||||
|
@ -5492,6 +5565,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="140fb39368f210ec945417f3eb23bf9564396e5c" datatype="html">
|
||||
<source>Avg Base Fee</source>
|
||||
<target>Frais de base moy</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">41,43</context>
|
||||
|
@ -5504,6 +5578,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="0a46218f4a7b17b6445460898d75ab78e7e7979b" datatype="html">
|
||||
<source>The average base fee charged by routing nodes, ignoring base fees > 5000ppm</source>
|
||||
<target>Frais de base moyens facturés par les nœuds de routage, sans tenir compte des frais de base > 5 000 ppm</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>
|
||||
|
@ -5512,6 +5587,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="2e72b276a3c5cc2ec27b4c8189639ba2fe62b6cb" datatype="html">
|
||||
<source>Med Capacity</source>
|
||||
<target>Capacité med</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">59,61</context>
|
||||
|
@ -5520,6 +5596,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="2c1c39e28735f607d62dbf3272eb792451c265a5" datatype="html">
|
||||
<source>Med Fee Rate</source>
|
||||
<target>Taux de frais med</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">72,74</context>
|
||||
|
@ -5528,6 +5605,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>Le taux de frais médian facturé par les nœuds de routage, en ignorant les taux de frais > 0,5 % ou 5 000 ppm</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>
|
||||
|
@ -5536,6 +5614,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="a541dbcef4908bf2e767e77d7a09cc62450e8e56" datatype="html">
|
||||
<source>Med Base Fee</source>
|
||||
<target>Frais de base med</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-statistics/channels-statistics.component.html</context>
|
||||
<context context-type="linenumber">87,89</context>
|
||||
|
@ -5544,6 +5623,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="b8539025268617abfcab1c3f2a2c60cd8d7485fb" datatype="html">
|
||||
<source>The median base fee charged by routing nodes, ignoring base fees > 5000ppm</source>
|
||||
<target>Frais de base médians facturés par les nœuds de routage, sans tenir compte des frais de base > 5 000 ppm</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>
|
||||
|
@ -5552,6 +5632,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="de1c07e9943fc284461bb8fb4860faecf52a1568" datatype="html">
|
||||
<source>Lightning node group</source>
|
||||
<target>Groupe de nœuds Lightning</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/group/group-preview.component.html</context>
|
||||
<context context-type="linenumber">3,5</context>
|
||||
|
@ -5564,6 +5645,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="6e2329529b1953198c7dfa0edb260554310bc636" datatype="html">
|
||||
<source>Nodes</source>
|
||||
<target>Nœuds</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/group/group-preview.component.html</context>
|
||||
<context context-type="linenumber">25,29</context>
|
||||
|
@ -5604,6 +5686,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="14a12efce56ffe89f839e50320bcf47e4e9ca4e4" datatype="html">
|
||||
<source>Liquidity</source>
|
||||
<target>Liquidité</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/group/group-preview.component.html</context>
|
||||
<context context-type="linenumber">29,31</context>
|
||||
|
@ -5640,6 +5723,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19" datatype="html">
|
||||
<source>Channels</source>
|
||||
<target>Canaux</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/group/group-preview.component.html</context>
|
||||
<context context-type="linenumber">40,43</context>
|
||||
|
@ -5700,6 +5784,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="e4706894b195010f6814e54bf6570c729d69aaca" datatype="html">
|
||||
<source>Average size</source>
|
||||
<target>Taille moyenne</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/group/group-preview.component.html</context>
|
||||
<context context-type="linenumber">44,46</context>
|
||||
|
@ -5712,6 +5797,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="ed31c09fd77c36238c13d83635f3fe5294c733d2" datatype="html">
|
||||
<source>Location</source>
|
||||
<target>Emplacement</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/group/group.component.html</context>
|
||||
<context context-type="linenumber">74,77</context>
|
||||
|
@ -5752,6 +5838,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="29c05e9a540827cdfa8e3b2e5e2f27aeb478916c" datatype="html">
|
||||
<source>Network Statistics</source>
|
||||
<target>Statistiques du réseau</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/lightning-dashboard/lightning-dashboard.component.html</context>
|
||||
<context context-type="linenumber">10</context>
|
||||
|
@ -5760,6 +5847,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="066e05b9a5db60850d907783fde6913e2e47cd5b" datatype="html">
|
||||
<source>Channels Statistics</source>
|
||||
<target>Statistiques des canaux</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/lightning-dashboard/lightning-dashboard.component.html</context>
|
||||
<context context-type="linenumber">24</context>
|
||||
|
@ -5768,6 +5856,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="0f33aeb084ac4d83cb0fe6f72648a8585b1b5e88" datatype="html">
|
||||
<source>Lightning Network History</source>
|
||||
<target>Historique du réseau Lightning</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/lightning-dashboard/lightning-dashboard.component.html</context>
|
||||
<context context-type="linenumber">49</context>
|
||||
|
@ -5776,6 +5865,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="2d9883d230a47fbbb2ec969e32a186597ea27405" datatype="html">
|
||||
<source>Liquidity Ranking</source>
|
||||
<target>Classement par liquidité</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>
|
||||
|
@ -5792,6 +5882,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="c50bf442cf99f6fc5f8b687c460f33234b879869" datatype="html">
|
||||
<source>Connectivity Ranking</source>
|
||||
<target>Classement par connectivité</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>
|
||||
|
@ -5804,6 +5895,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="027f48063a5512e5c26b6ca88f7d7734e2d333a7" datatype="html">
|
||||
<source>Percentage change past week</source>
|
||||
<target>Variation sur une semaine</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>
|
||||
|
@ -5820,6 +5912,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="be6ebbb11d55adb8e821d503f8e10ccf43ed8b00" datatype="html">
|
||||
<source>Lightning node</source>
|
||||
<target>Nœud Lightning</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node-preview.component.html</context>
|
||||
<context context-type="linenumber">3,5</context>
|
||||
|
@ -5836,6 +5929,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="af15c87bfed273bc095ba572cf27e3aaffc33b22" datatype="html">
|
||||
<source>Active capacity</source>
|
||||
<target>Capacité active</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node-preview.component.html</context>
|
||||
<context context-type="linenumber">20,22</context>
|
||||
|
@ -5848,6 +5942,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="52ffa66bd0399a49d5aa8d6f8fa077a6e8db09c0" datatype="html">
|
||||
<source>Active channels</source>
|
||||
<target>Canaux actifs</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node-preview.component.html</context>
|
||||
<context context-type="linenumber">26,30</context>
|
||||
|
@ -5860,6 +5955,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="a43f25a9ac40e8e2441ff0be7a36b8e5d15534df" datatype="html">
|
||||
<source>Country</source>
|
||||
<target>Pays</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node-preview.component.html</context>
|
||||
<context context-type="linenumber">44,47</context>
|
||||
|
@ -5868,6 +5964,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="674378571ab7e72a386f27fd3281558bae821d9d" datatype="html">
|
||||
<source>No node found for public key "<x id="INTERPOLATION" equiv-text="{{ node.public_key | shortenString : 12}}"/>"</source>
|
||||
<target>Aucun nœud trouvé pour la clé publique &quot; <x id="INTERPOLATION" equiv-text="{{ node.public_key | shortenString : 12}}"/> &quot;</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">17,19</context>
|
||||
|
@ -5876,6 +5973,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="43b48b9c15083a164b401bf3775a4b99f3917699" datatype="html">
|
||||
<source>Average channel size</source>
|
||||
<target>Taille moyenne du canal</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">40,43</context>
|
||||
|
@ -5884,6 +5982,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="e5d8bb389c702588877f039d72178f219453a72d" datatype="html">
|
||||
<source>Unknown</source>
|
||||
<target>Inconnue</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">52,56</context>
|
||||
|
@ -5904,6 +6003,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="8fa4d523f7b91df4390120b85ed0406138273e1a" datatype="html">
|
||||
<source>Color</source>
|
||||
<target>Couleur</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">75,77</context>
|
||||
|
@ -5912,6 +6012,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="5b9904cb31f6f28314443f6385dc5facab7ea851" datatype="html">
|
||||
<source>ISP</source>
|
||||
<target>FAI</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">82,83</context>
|
||||
|
@ -5924,6 +6025,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="86d9619247d148019e5599707c39a36e880a2d23" datatype="html">
|
||||
<source>Exclusively on Tor</source>
|
||||
<target>Exclusivement sur Tor</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">88,90</context>
|
||||
|
@ -5932,6 +6034,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="b371db1a7ab2167dc8dd91b48ea929d71bb4ef4c" datatype="html">
|
||||
<source>Open channels</source>
|
||||
<target>Canaux ouverts</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">145,148</context>
|
||||
|
@ -5940,6 +6043,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="a2dff531c3d7477178553f579e0ec7c3ac7a6f30" datatype="html">
|
||||
<source>Closed channels</source>
|
||||
<target>Canaux fermés</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">149,152</context>
|
||||
|
@ -5948,6 +6052,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="2519445964020754921" datatype="html">
|
||||
<source>Node: <x id="PH" equiv-text="node.alias"/></source>
|
||||
<target>Nœud : <x id="PH" equiv-text="node.alias"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.ts</context>
|
||||
<context context-type="linenumber">42</context>
|
||||
|
@ -5955,6 +6060,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7cac1c3013423d82d5149a5854d709bd08411430" datatype="html">
|
||||
<source>(Tor nodes excluded)</source>
|
||||
<target>(Nœuds Tor exclus)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-channels-map/nodes-channels-map.component.html</context>
|
||||
<context context-type="linenumber">8,11</context>
|
||||
|
@ -5975,6 +6081,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="8199511328474154549" datatype="html">
|
||||
<source>Lightning Nodes Channels World Map</source>
|
||||
<target>Carte du monde des canaux Lightning</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>
|
||||
|
@ -5982,6 +6089,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4390631969351833104" datatype="html">
|
||||
<source>No geolocation data available</source>
|
||||
<target>Aucune donnée de géolocalisation disponible</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">218,213</context>
|
||||
|
@ -5989,6 +6097,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="a4d393ee035f4225083c22cc3909b26a05a87528" datatype="html">
|
||||
<source>Active channels map</source>
|
||||
<target>Carte des canaux actifs</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-channels/node-channels.component.html</context>
|
||||
<context context-type="linenumber">2,3</context>
|
||||
|
@ -5997,6 +6106,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4635698809727522638" datatype="html">
|
||||
<source>Indexing in progess</source>
|
||||
<target>Indexation en cours</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">121,116</context>
|
||||
|
@ -6008,6 +6118,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="1055322764280599360" datatype="html">
|
||||
<source>Reachable on Clearnet Only</source>
|
||||
<target>Accessible uniquement sur Clearnet</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>
|
||||
|
@ -6019,6 +6130,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="2760682261176173881" datatype="html">
|
||||
<source>Reachable on Clearnet and Darknet</source>
|
||||
<target>Accessible sur Clearnet et 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">185,182</context>
|
||||
|
@ -6030,6 +6142,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="1191036460161514668" datatype="html">
|
||||
<source>Reachable on Darknet Only</source>
|
||||
<target>Accessible uniquement sur 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">206,203</context>
|
||||
|
@ -6041,6 +6154,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9" datatype="html">
|
||||
<source>Share</source>
|
||||
<target>Part</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html</context>
|
||||
<context context-type="linenumber">29,31</context>
|
||||
|
@ -6053,6 +6167,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="5222540403093176126" datatype="html">
|
||||
<source><x id="PH" equiv-text="country.count.toString()"/> nodes</source>
|
||||
<target> <x id="PH" equiv-text="country.count.toString()"/> nœuds</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">103,102</context>
|
||||
|
@ -6068,6 +6183,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7032954508645880700" datatype="html">
|
||||
<source><x id="PH" equiv-text="this.amountShortenerPipe.transform(country.capacity / 100000000, 2)"/> BTC capacity</source>
|
||||
<target>Capacité de <x id="PH" equiv-text="this.amountShortenerPipe.transform(country.capacity / 100000000, 2)"/> BTC</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,102</context>
|
||||
|
@ -6075,6 +6191,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7ede3edfacd291eb9db08e11845d9efdf197f417" datatype="html">
|
||||
<source>Lightning nodes in <x id="INTERPOLATION" equiv-text="{{ country?.name }}"/></source>
|
||||
<target>Nœuds Lightning: <x id="INTERPOLATION" equiv-text="{{ country?.name }}"/></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">3,4</context>
|
||||
|
@ -6083,6 +6200,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4498ec29c37744fef46809ebc3db67c5fb789917" datatype="html">
|
||||
<source>ISP Count</source>
|
||||
<target>Nombre de FAI</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>
|
||||
|
@ -6091,6 +6209,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="90a6a964ba53464578003e3b4b2873ef5d2132a1" datatype="html">
|
||||
<source>Top ISP</source>
|
||||
<target>FAI les plus utilisés</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>
|
||||
|
@ -6099,6 +6218,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7246059109648045954" datatype="html">
|
||||
<source>Lightning nodes in <x id="PH" equiv-text="response.country.en"/></source>
|
||||
<target>Nœuds Lightning: <x id="PH" equiv-text="response.country.en"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-country/nodes-per-country.component.ts</context>
|
||||
<context context-type="linenumber">35</context>
|
||||
|
@ -6106,6 +6226,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="6b4442323c695a8211357c7e4486dd620c443822" datatype="html">
|
||||
<source>Clearnet Capacity</source>
|
||||
<target>Capacité Clearnet</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">6,8</context>
|
||||
|
@ -6118,6 +6239,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>Liquidité qui circule sur les nœuds annonçant au moins une adresse IP clearnet</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>
|
||||
|
@ -6126,6 +6248,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="462d2233ddacc9869eb28e09b3b12f1d85556937" datatype="html">
|
||||
<source>Unknown Capacity</source>
|
||||
<target>Capacité inconnue</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">13,15</context>
|
||||
|
@ -6138,6 +6261,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="26fb07e8754b87bba4bf12c5137ffa77dac389a8" datatype="html">
|
||||
<source>How much liquidity is running on nodes which ISP was not identifiable</source>
|
||||
<target>Liquidité qui circule sur les nœuds dont le FAI n'était pas identifiable</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>
|
||||
|
@ -6146,6 +6270,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="df3728b721159d25e68f5daf44aaab7fa25f1415" datatype="html">
|
||||
<source>Tor Capacity</source>
|
||||
<target>Capacité Tor</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">20,22</context>
|
||||
|
@ -6158,6 +6283,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="23549ef4e1f846f06abcf07ceecb115945a0cf61" datatype="html">
|
||||
<source>How much liquidity is running on nodes advertising only Tor addresses</source>
|
||||
<target>Liquidité qui circule sur les nœuds annonçant uniquement les adresses Tor</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>
|
||||
|
@ -6166,6 +6292,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="e008f2a76179fdcd7110b41ca624131f91075949" datatype="html">
|
||||
<source>Top 100 ISPs hosting LN nodes</source>
|
||||
<target>Top 100 des FAI hébergeant des nœuds LN</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>
|
||||
|
@ -6174,6 +6301,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="3627306100664959238" datatype="html">
|
||||
<source><x id="PH" equiv-text="this.amountShortenerPipe.transform(isp[2] / 100000000, 2)"/> BTC</source>
|
||||
<target> <x id="PH" equiv-text="this.amountShortenerPipe.transform(isp[2] / 100000000, 2)"/> BTC</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts</context>
|
||||
<context context-type="linenumber">158,156</context>
|
||||
|
@ -6185,6 +6313,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="c18497e4f0db0d0ad0c71ba294295f42b3d312c9" datatype="html">
|
||||
<source>Lightning ISP</source>
|
||||
<target>FAI Lightning</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>
|
||||
|
@ -6193,6 +6322,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="41074627e075a9b1bd8197c474ea68a2b8276e54" datatype="html">
|
||||
<source>Top country</source>
|
||||
<target>Meilleur pays</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">39,41</context>
|
||||
|
@ -6205,6 +6335,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="5fad6872a652d922ad8822f4016e104b9a8cc113" datatype="html">
|
||||
<source>Top node</source>
|
||||
<target>Meilleur nœud</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">45,48</context>
|
||||
|
@ -6213,6 +6344,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>Nœuds Lightning sur le FAI : <x id="PH" equiv-text="response.isp"/> [AS <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>
|
||||
|
@ -6224,6 +6356,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="d82f436f033a7d81680b8430275f94dda530151c" datatype="html">
|
||||
<source>Lightning nodes on ISP: <x id="INTERPOLATION" equiv-text="{{ isp?.name }}"/></source>
|
||||
<target>Nœuds Lightning sur le FAI : <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>
|
||||
|
@ -6232,6 +6365,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>
|
||||
|
@ -6240,6 +6374,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="5b727d251b06e9959cf24a90250a480d425339de" datatype="html">
|
||||
<source>Top 100 oldest lightning nodes</source>
|
||||
<target>Top 100 des nœuds Lightning les plus anciens</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html</context>
|
||||
<context context-type="linenumber">3,7</context>
|
||||
|
@ -6248,6 +6383,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4157312397261844620" datatype="html">
|
||||
<source>Oldest lightning nodes</source>
|
||||
<target>Noeuds Lightning les plus anciens</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.ts</context>
|
||||
<context context-type="linenumber">27</context>
|
||||
|
@ -6255,6 +6391,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="71bb1ed9da9ebb92cf35925bc6fe0a8fbc325625" datatype="html">
|
||||
<source>Top 100 nodes liquidity ranking</source>
|
||||
<target>Classement des 100 meilleurs nœuds par liquidité</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html</context>
|
||||
<context context-type="linenumber">3,7</context>
|
||||
|
@ -6263,6 +6400,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="99786bd2106b708e4514d0121964affb19bee636" datatype="html">
|
||||
<source>Top 100 nodes connectivity ranking</source>
|
||||
<target>Classement des 100 meilleurs nœuds par connectivité</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html</context>
|
||||
<context context-type="linenumber">3,7</context>
|
||||
|
@ -6271,6 +6409,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="47a30fc5a836252f8fe03e2949756b150684d934" datatype="html">
|
||||
<source>Oldest nodes</source>
|
||||
<target>Nœuds les plus anciens</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html</context>
|
||||
<context context-type="linenumber">36</context>
|
||||
|
@ -6279,6 +6418,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4034215342842066505" datatype="html">
|
||||
<source>Top lightning nodes</source>
|
||||
<target>Top nœuds Lightning</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.ts</context>
|
||||
<context context-type="linenumber">22</context>
|
||||
|
@ -6286,6 +6426,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="af1176facd00a0580509fb2900ab0cf7f9b39ae7" datatype="html">
|
||||
<source>Indexing in progress</source>
|
||||
<target>Indexation en cours</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/statistics-chart/lightning-statistics-chart.component.html</context>
|
||||
<context context-type="linenumber">52,55</context>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1471,6 +1471,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="1405c5f1a9834338ff13442c550927ab7144fdc8" datatype="html">
|
||||
<source>Community Integrations</source>
|
||||
<target>Интеграции c сообществом</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/about/about.component.html</context>
|
||||
<context context-type="linenumber">191,193</context>
|
||||
|
@ -1544,6 +1545,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="address-label.multisig" datatype="html">
|
||||
<source>Multisig <x id="multisigM" equiv-text="ms.m"/> of <x id="multisigN" equiv-text="ms.n"/></source>
|
||||
<target>Мультиподпись <x id="multisigM" equiv-text="ms.m"/> из <x id="multisigN" equiv-text="ms.n"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/address-labels/address-labels.component.ts</context>
|
||||
<context context-type="linenumber">105</context>
|
||||
|
@ -2027,6 +2029,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4718502b32e47c66db00ac7cf4f7f058acb6e849" datatype="html">
|
||||
<source>Block </source>
|
||||
<target>Блок</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-audit/block-audit.component.html</context>
|
||||
<context context-type="linenumber">7,9</context>
|
||||
|
@ -2121,6 +2124,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="e9503a2605cf5d41273a7d5ad653873f28f8f1a3" datatype="html">
|
||||
<source>Match rate</source>
|
||||
<target>Коэффициент соответствия</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-audit/block-audit.component.html</context>
|
||||
<context context-type="linenumber">64,67</context>
|
||||
|
@ -2129,6 +2133,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="c97ea3cbb05527c76b0011820ca76e8e00632ca1" datatype="html">
|
||||
<source>Missing txs</source>
|
||||
<target>Отсутствующие транзакции</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-audit/block-audit.component.html</context>
|
||||
<context context-type="linenumber">68,71</context>
|
||||
|
@ -2137,6 +2142,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="94d8765805623dd51e562e6e4bb669a944bbe516" datatype="html">
|
||||
<source>Added txs</source>
|
||||
<target>Добавленные транзакции</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-audit/block-audit.component.html</context>
|
||||
<context context-type="linenumber">72,75</context>
|
||||
|
@ -2145,6 +2151,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="da5d7e263a3c6dc585d04844074afaa7229b88a0" datatype="html">
|
||||
<source>Missing</source>
|
||||
<target>Отсутствующие</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-audit/block-audit.component.html</context>
|
||||
<context context-type="linenumber">84,85</context>
|
||||
|
@ -2153,6 +2160,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="80e3b490720757978c99a7b5af3885faf202b955" datatype="html">
|
||||
<source>Added</source>
|
||||
<target>Добавленные</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-audit/block-audit.component.html</context>
|
||||
<context context-type="linenumber">86,92</context>
|
||||
|
@ -2470,6 +2478,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="80065834848189518" datatype="html">
|
||||
<source>No data to display yet. Try again later.</source>
|
||||
<target>Пока нет данных для отображения. Попробуйте позже.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block-prediction-graph/block-prediction-graph.component.ts</context>
|
||||
<context context-type="linenumber">108,103</context>
|
||||
|
@ -2523,6 +2532,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7f5d0c10614e8a34f0e2dad33a0568277c50cf69" datatype="html">
|
||||
<source>Block</source>
|
||||
<target>Блок</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block/block-preview.component.html</context>
|
||||
<context context-type="linenumber">3,7</context>
|
||||
|
@ -2535,6 +2545,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="445c243f1d1cd2c7f0df430cdb2fa25639600396" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="</ng-template> </h1> <div class="blockhash" *ngIf="block"/></source>
|
||||
<target> <x id="INTERPOLATION" equiv-text="</ng-template> </h1> <div class="blockhash" *ngIf="block"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/block/block-preview.component.html</context>
|
||||
<context context-type="linenumber">11,12</context>
|
||||
|
@ -3250,6 +3261,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="5115edb23059f4dcfe6ce0a979e40962a149c35c" datatype="html">
|
||||
<source>Hashrate & Difficulty</source>
|
||||
<target>Хэшрейт и сложность</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">15,16</context>
|
||||
|
@ -3258,6 +3270,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="a3382a60534e9fff5c3a0fcde39bab356afc4a59" datatype="html">
|
||||
<source>Lightning</source>
|
||||
<target>Лайтнинг</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">31</context>
|
||||
|
@ -3266,6 +3279,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="b420668a91f8ebaf6e6409c4ba87f1d45961d2bd" datatype="html">
|
||||
<source>Lightning Nodes Per Network</source>
|
||||
<target>Узлы Лайтнинг на сеть</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">34</context>
|
||||
|
@ -3286,6 +3300,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="ea8db27e6db64f8b940711948c001a1100e5fe9f" datatype="html">
|
||||
<source>Lightning Network Capacity</source>
|
||||
<target>Объем сети Лайтнинг</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">36</context>
|
||||
|
@ -3306,6 +3321,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="8573a1576789bd2c4faeaed23037c4917812c6cf" datatype="html">
|
||||
<source>Lightning Nodes Per ISP</source>
|
||||
<target>Узлов Лайтнинг на провайдера</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">38</context>
|
||||
|
@ -3318,6 +3334,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="9d3ad4c6623870d96b65fb7a708fed6ce7c20044" datatype="html">
|
||||
<source>Lightning Nodes Per Country</source>
|
||||
<target>Узлов Лайтнинг на страну</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">40</context>
|
||||
|
@ -3334,6 +3351,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="af8560ca50882114be16c951650f83bca73161a7" datatype="html">
|
||||
<source>Lightning Nodes World Map</source>
|
||||
<target>Мировая карта узлов Лайтнинг</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">42</context>
|
||||
|
@ -3350,6 +3368,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="b482ceceb39c7a045cb2ab2c64f7091d21e63d44" datatype="html">
|
||||
<source>Lightning Nodes Channels World Map</source>
|
||||
<target>Мировая карта лайтнинг-каналов</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/graphs/graphs.component.html</context>
|
||||
<context context-type="linenumber">44</context>
|
||||
|
@ -3470,6 +3489,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,45</context>
|
||||
|
@ -3482,6 +3502,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7cbedd89f60daafaf0e56363900d666a4e02ffb1" datatype="html">
|
||||
<source>beta</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">45,48</context>
|
||||
|
@ -3737,6 +3758,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="2158ea60725d3a97aed6f0f00aa7df48d7bb42ff" datatype="html">
|
||||
<source>mining pool</source>
|
||||
<target>майнинг-пул</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/pool/pool-preview.component.html</context>
|
||||
<context context-type="linenumber">3,5</context>
|
||||
|
@ -4067,6 +4089,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,6</context>
|
||||
|
@ -4440,6 +4463,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="d0fd8887b50687cfc0fc1f6569f6fd6c5db4ffc0" datatype="html">
|
||||
<source>Hide diagram</source>
|
||||
<target>Скрыть диаграмму</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/transaction/transaction.component.html</context>
|
||||
<context context-type="linenumber">198,203</context>
|
||||
|
@ -4448,6 +4472,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="f0c5f6f270e70cbe063b5368fcf48f9afc1abd9b" datatype="html">
|
||||
<source>Show more</source>
|
||||
<target>Показать больше</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/transaction/transaction.component.html</context>
|
||||
<context context-type="linenumber">219,221</context>
|
||||
|
@ -4456,6 +4481,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="5403a767248e304199592271bba3366d2ca3f903" datatype="html">
|
||||
<source>Show less</source>
|
||||
<target>Показывай меньше</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/transaction/transaction.component.html</context>
|
||||
<context context-type="linenumber">221,227</context>
|
||||
|
@ -4464,6 +4490,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="e7aa6db8df12d0158df972b6abfc65a8478b2b7d" datatype="html">
|
||||
<source>Show diagram</source>
|
||||
<target>Показать схему</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/transaction/transaction.component.html</context>
|
||||
<context context-type="linenumber">241,242</context>
|
||||
|
@ -4657,6 +4684,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="ac0c4b49e44c42db35ddf590fb5f78375a891b01" datatype="html">
|
||||
<source>other inputs</source>
|
||||
<target>другие входы</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html</context>
|
||||
<context context-type="linenumber">12</context>
|
||||
|
@ -4665,6 +4693,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="8900247c0476fea8fcbee57a72a1d1da5ddd40ff" datatype="html">
|
||||
<source>other outputs</source>
|
||||
<target>другие выходы</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html</context>
|
||||
<context context-type="linenumber">13</context>
|
||||
|
@ -4673,6 +4702,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="3e242f213dd1a0754aad9164aa80887d67708500" datatype="html">
|
||||
<source>Input</source>
|
||||
<target>Вход</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html</context>
|
||||
<context context-type="linenumber">43</context>
|
||||
|
@ -4681,6 +4711,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7a080851e25a41e898776a9c90cf8dbe81027f9a" datatype="html">
|
||||
<source>Output</source>
|
||||
<target>Выход</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component.html</context>
|
||||
<context context-type="linenumber">44</context>
|
||||
|
@ -4689,6 +4720,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>Эта транзакция сэкономила <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedSegwitGains * 100 | number: '1.0-0' }}"/> % на комиссиях за счет использования нативного SegWit.</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>
|
||||
|
@ -4715,6 +4747,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>Эта транзакция сэкономила <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedSegwitGains * 100 | number: '1.0-0' }}"/> % на комиссиях за счет использования SegWit и может сэкономить еще <x id="INTERPOLATION_1" equiv-text="{{ segwitGains.potentialSegwitGains * 100 | number : '1.0-0' }}"/> % за счет полного перехода на нативный SegWit.</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>
|
||||
|
@ -4723,6 +4756,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>Эта транзакция могла сэкономить <x id="INTERPOLATION" equiv-text="{{ segwitGains.potentialSegwitGains * 100 | number : '1.0-0' }}"/> % на комиссиях за счет перехода на нативный SegWit или <x id="INTERPOLATION_1" equiv-text="{{ segwitGains.potentialP2shSegwitGains * 100 | number: '1.0-0' }}"/> % за счет обновления до SegWit-P2SH.</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>
|
||||
|
@ -4731,6 +4765,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>Эта транзакция использует Taproot и, таким образом, сэкономила как минимум <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedTaprootGains * 100 | number: '1.0-0' }}"/> % на комиссиях.</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>
|
||||
|
@ -4739,6 +4774,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="65d5167c4947e3ad81758d238a7ac7e893c261f0" datatype="html">
|
||||
<source>Taproot</source>
|
||||
<target>Taproot</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>
|
||||
|
@ -4760,6 +4796,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>Эта транзакция использует Taproot и уже сэкономила как минимум <x id="INTERPOLATION" equiv-text="{{ segwitGains.realizedTaprootGains * 100 | number: '1.0-0' }}"/> % на комиссиях, но может сэкономить дополнительно <x id="INTERPOLATION_1" equiv-text="{{ segwitGains.potentialTaprootGains * 100 | number: '1.0-0' }}"/> % за счет использования Taproot в полной мере.</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>
|
||||
|
@ -4768,6 +4805,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>Эта транзакция может сэкономить <x id="INTERPOLATION" equiv-text="{{ segwitGains.potentialTaprootGains * 100 | number: '1.0-0' }}"/> % на комиссиях с помощью Taproot.</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>
|
||||
|
@ -4785,6 +4823,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="07883574bb93ea23b764861f56a525bdaf907513" datatype="html">
|
||||
<source>This transaction supports Replace-By-Fee (RBF) allowing fee bumping</source>
|
||||
<target>Эта транзакция поддерживает функцию Replace-By-Fee (RBF), что позволяет повышать комиссию.</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/components/tx-features/tx-features.component.html</context>
|
||||
<context context-type="linenumber">25</context>
|
||||
|
@ -5021,6 +5060,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4ca458fe3274d1c79640b052d57cf3b900b650b6" datatype="html">
|
||||
<source>Base fee</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">30</context>
|
||||
|
@ -5033,6 +5073,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">36</context>
|
||||
|
@ -5049,6 +5090,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="fb2137ba0df55f21a9d6b6ad08d56d74ad852e0e" datatype="html">
|
||||
<source>This channel supports zero base fee routing</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">45</context>
|
||||
|
@ -5057,6 +5099,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="3ec76ccfe1fdcbfd7ea152392f6472d93d4e8cab" datatype="html">
|
||||
<source>Zero base fee</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">46</context>
|
||||
|
@ -5065,6 +5108,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="b5e42e06ea8a4012a38eef209104bbd9dd1a0fc0" datatype="html">
|
||||
<source>This channel does not support zero base fee routing</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">51</context>
|
||||
|
@ -5073,6 +5117,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="09a1bc9c4198e87e9e974a51e86b181429b480d3" datatype="html">
|
||||
<source>Non-zero base fee</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">52</context>
|
||||
|
@ -5081,6 +5126,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">58</context>
|
||||
|
@ -5089,6 +5135,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">64</context>
|
||||
|
@ -5105,6 +5152,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="205c1b86ac1cc419c4d0cca51fdde418c4ffdc20" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ i }}"/> channels</source>
|
||||
<target> <x id="INTERPOLATION" equiv-text="{{ i }}"/> каналов</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-box/channel-box.component.html</context>
|
||||
<context context-type="linenumber">80</context>
|
||||
|
@ -5117,6 +5165,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="3bbd25c289760a3ba57e30df5ad5fe8977ab25a5" datatype="html">
|
||||
<source>lightning channel</source>
|
||||
<target>лайтнинг-канал</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-preview.component.html</context>
|
||||
<context context-type="linenumber">3,5</context>
|
||||
|
@ -5125,6 +5174,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="43c4133c7a0263d2e33dd4c2e74d40784b2e4b1c" datatype="html">
|
||||
<source>Inactive</source>
|
||||
<target>Неактивный</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-preview.component.html</context>
|
||||
<context context-type="linenumber">10,11</context>
|
||||
|
@ -5141,6 +5191,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="b36e1450940b7f6028d8587568c7d669b53f7a06" datatype="html">
|
||||
<source>Active</source>
|
||||
<target>Активный</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-preview.component.html</context>
|
||||
<context context-type="linenumber">11,12</context>
|
||||
|
@ -5157,6 +5208,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4804b8e78964cee9e5c85f31fd982639b97780b2" datatype="html">
|
||||
<source>Closed</source>
|
||||
<target>Закрыт</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-preview.component.html</context>
|
||||
<context context-type="linenumber">12,14</context>
|
||||
|
@ -5177,6 +5229,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="1b051734b0ee9021991c91b3ed4e81c244322462" datatype="html">
|
||||
<source>Created</source>
|
||||
<target>Создан</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-preview.component.html</context>
|
||||
<context context-type="linenumber">23,26</context>
|
||||
|
@ -5189,6 +5242,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="ce9dfdc6dccb28dc75a78c704e09dc18fb02dcfa" datatype="html">
|
||||
<source>Capacity</source>
|
||||
<target>Объем</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel-preview.component.html</context>
|
||||
<context context-type="linenumber">27,28</context>
|
||||
|
@ -5261,6 +5315,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="13142ad9637003749d667393aaae7a286d1eba5b" datatype="html">
|
||||
<source>Lightning channel</source>
|
||||
<target>Лайтнинг-канал</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel.component.html</context>
|
||||
<context context-type="linenumber">2,5</context>
|
||||
|
@ -5273,6 +5328,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="8dad9f60ff582b632a864f22c7466327793c3f09" datatype="html">
|
||||
<source>Last update</source>
|
||||
<target>Последнее обновление</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel.component.html</context>
|
||||
<context context-type="linenumber">33,34</context>
|
||||
|
@ -5305,6 +5361,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="0c134c6787c6b763446c096ea5233ace6fd9116d" datatype="html">
|
||||
<source>Closing date</source>
|
||||
<target>Дата закрытия</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel.component.html</context>
|
||||
<context context-type="linenumber">37,38</context>
|
||||
|
@ -5317,6 +5374,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="cdd2ea2e12437df848ec474ac15af48859bd09a0" datatype="html">
|
||||
<source>Opening transaction</source>
|
||||
<target>Транзакция открытия</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel.component.html</context>
|
||||
<context context-type="linenumber">73,74</context>
|
||||
|
@ -5325,6 +5383,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="50411064ac48e15659d1985b414ae91af0c8cd36" datatype="html">
|
||||
<source>Closing transaction</source>
|
||||
<target>Транзакция закрытия</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel.component.html</context>
|
||||
<context context-type="linenumber">82,84</context>
|
||||
|
@ -5333,6 +5392,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="6008566722612122663" datatype="html">
|
||||
<source>Channel: <x id="PH" equiv-text="value.short_id"/></source>
|
||||
<target>Канал: <x id="PH" equiv-text="value.short_id"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channel/channel.component.ts</context>
|
||||
<context context-type="linenumber">37</context>
|
||||
|
@ -5348,6 +5408,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="a43e63c25599408ef14b33c80dd523021b21f846" datatype="html">
|
||||
<source>No channels to display</source>
|
||||
<target>Нет каналов для отображения</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-list/channels-list.component.html</context>
|
||||
<context context-type="linenumber">29,35</context>
|
||||
|
@ -5356,6 +5417,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="fbaaeb297e70b9a800acf841b9d26c19d60651ef" datatype="html">
|
||||
<source>Alias</source>
|
||||
<target>Псевдоним</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-list/channels-list.component.html</context>
|
||||
<context context-type="linenumber">35,37</context>
|
||||
|
@ -5392,6 +5454,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="81b97b8ea996ad1e4f9fca8415021850214884b1" datatype="html">
|
||||
<source>Status</source>
|
||||
<target>Статус</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-list/channels-list.component.html</context>
|
||||
<context context-type="linenumber">37,38</context>
|
||||
|
@ -5400,6 +5463,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="0cd107458dce99721e72971d426a5a3106074331" datatype="html">
|
||||
<source>Channel ID</source>
|
||||
<target>ID канала</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-list/channels-list.component.html</context>
|
||||
<context context-type="linenumber">41,45</context>
|
||||
|
@ -5408,6 +5472,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="e4b2d9e6a2ab9e6ca34027ec03beaac42b7badd4" datatype="html">
|
||||
<source>sats</source>
|
||||
<target>сат</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/channels-list/channels-list.component.html</context>
|
||||
<context context-type="linenumber">61,65</context>
|
||||
|
@ -5460,6 +5525,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="ab456546aa39de3328fcfdf077f410b5ff1aa773" datatype="html">
|
||||
<source>Avg Capacity</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">13,15</context>
|
||||
|
@ -5472,6 +5538,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="f68705670e611f13da1a43e90f9c97d8761dd9ef" datatype="html">
|
||||
<source>Avg Fee Rate</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">26,28</context>
|
||||
|
@ -5484,6 +5551,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 ppm.</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>
|
||||
|
@ -5492,6 +5560,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="140fb39368f210ec945417f3eb23bf9564396e5c" datatype="html">
|
||||
<source>Avg Base Fee</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">41,43</context>
|
||||
|
@ -5504,6 +5573,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 ppm</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>
|
||||
|
@ -5512,6 +5582,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="2e72b276a3c5cc2ec27b4c8189639ba2fe62b6cb" datatype="html">
|
||||
<source>Med Capacity</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">59,61</context>
|
||||
|
@ -5520,6 +5591,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="2c1c39e28735f607d62dbf3272eb792451c265a5" datatype="html">
|
||||
<source>Med Fee Rate</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">72,74</context>
|
||||
|
@ -5528,6 +5600,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 ppm.</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>
|
||||
|
@ -5536,6 +5609,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="a541dbcef4908bf2e767e77d7a09cc62450e8e56" datatype="html">
|
||||
<source>Med Base Fee</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">87,89</context>
|
||||
|
@ -5544,6 +5618,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 ppm</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>
|
||||
|
@ -5552,6 +5627,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="de1c07e9943fc284461bb8fb4860faecf52a1568" datatype="html">
|
||||
<source>Lightning node group</source>
|
||||
<target>Группа лайтнинг-узлов </target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/group/group-preview.component.html</context>
|
||||
<context context-type="linenumber">3,5</context>
|
||||
|
@ -5564,6 +5640,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="6e2329529b1953198c7dfa0edb260554310bc636" datatype="html">
|
||||
<source>Nodes</source>
|
||||
<target>Узлы</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/group/group-preview.component.html</context>
|
||||
<context context-type="linenumber">25,29</context>
|
||||
|
@ -5604,6 +5681,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="14a12efce56ffe89f839e50320bcf47e4e9ca4e4" datatype="html">
|
||||
<source>Liquidity</source>
|
||||
<target>Ликвидность</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/group/group-preview.component.html</context>
|
||||
<context context-type="linenumber">29,31</context>
|
||||
|
@ -5640,6 +5718,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="807cf11e6ac1cde912496f764c176bdfdd6b7e19" datatype="html">
|
||||
<source>Channels</source>
|
||||
<target>Каналы</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/group/group-preview.component.html</context>
|
||||
<context context-type="linenumber">40,43</context>
|
||||
|
@ -5700,6 +5779,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="e4706894b195010f6814e54bf6570c729d69aaca" datatype="html">
|
||||
<source>Average size</source>
|
||||
<target>Средний размер</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/group/group-preview.component.html</context>
|
||||
<context context-type="linenumber">44,46</context>
|
||||
|
@ -5712,6 +5792,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="ed31c09fd77c36238c13d83635f3fe5294c733d2" datatype="html">
|
||||
<source>Location</source>
|
||||
<target>Расположение</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/group/group.component.html</context>
|
||||
<context context-type="linenumber">74,77</context>
|
||||
|
@ -5752,6 +5833,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="29c05e9a540827cdfa8e3b2e5e2f27aeb478916c" datatype="html">
|
||||
<source>Network Statistics</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">10</context>
|
||||
|
@ -5760,6 +5842,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="066e05b9a5db60850d907783fde6913e2e47cd5b" datatype="html">
|
||||
<source>Channels Statistics</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">24</context>
|
||||
|
@ -5768,6 +5851,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="0f33aeb084ac4d83cb0fe6f72648a8585b1b5e88" datatype="html">
|
||||
<source>Lightning Network History</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">49</context>
|
||||
|
@ -5776,6 +5860,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>
|
||||
|
@ -5792,6 +5877,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>
|
||||
|
@ -5804,6 +5890,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="027f48063a5512e5c26b6ca88f7d7734e2d333a7" datatype="html">
|
||||
<source>Percentage change past week</source>
|
||||
<target>Процентное изменение за последнюю неделю</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>
|
||||
|
@ -5820,6 +5907,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="be6ebbb11d55adb8e821d503f8e10ccf43ed8b00" datatype="html">
|
||||
<source>Lightning node</source>
|
||||
<target>Лайтнинг-узел</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node-preview.component.html</context>
|
||||
<context context-type="linenumber">3,5</context>
|
||||
|
@ -5836,6 +5924,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="af15c87bfed273bc095ba572cf27e3aaffc33b22" datatype="html">
|
||||
<source>Active capacity</source>
|
||||
<target>Активная емкость</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node-preview.component.html</context>
|
||||
<context context-type="linenumber">20,22</context>
|
||||
|
@ -5848,6 +5937,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="52ffa66bd0399a49d5aa8d6f8fa077a6e8db09c0" datatype="html">
|
||||
<source>Active channels</source>
|
||||
<target>Активные каналы</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node-preview.component.html</context>
|
||||
<context context-type="linenumber">26,30</context>
|
||||
|
@ -5860,6 +5950,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="a43f25a9ac40e8e2441ff0be7a36b8e5d15534df" datatype="html">
|
||||
<source>Country</source>
|
||||
<target>Страна</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node-preview.component.html</context>
|
||||
<context context-type="linenumber">44,47</context>
|
||||
|
@ -5868,6 +5959,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="674378571ab7e72a386f27fd3281558bae821d9d" datatype="html">
|
||||
<source>No node found for public key "<x id="INTERPOLATION" equiv-text="{{ node.public_key | shortenString : 12}}"/>"</source>
|
||||
<target>Не найден узел для открытого ключа &quot; <x id="INTERPOLATION" equiv-text="{{ node.public_key | shortenString : 12}}"/> &quot;</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">17,19</context>
|
||||
|
@ -5876,6 +5968,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="43b48b9c15083a164b401bf3775a4b99f3917699" datatype="html">
|
||||
<source>Average channel size</source>
|
||||
<target>Средний размер канала</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">40,43</context>
|
||||
|
@ -5884,6 +5977,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="e5d8bb389c702588877f039d72178f219453a72d" datatype="html">
|
||||
<source>Unknown</source>
|
||||
<target>Неизвестно</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">52,56</context>
|
||||
|
@ -5904,6 +5998,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="8fa4d523f7b91df4390120b85ed0406138273e1a" datatype="html">
|
||||
<source>Color</source>
|
||||
<target>Цвет</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">75,77</context>
|
||||
|
@ -5912,6 +6007,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="5b9904cb31f6f28314443f6385dc5facab7ea851" datatype="html">
|
||||
<source>ISP</source>
|
||||
<target>Интернет-провайдер</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">82,83</context>
|
||||
|
@ -5924,6 +6020,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="86d9619247d148019e5599707c39a36e880a2d23" datatype="html">
|
||||
<source>Exclusively on Tor</source>
|
||||
<target>Исключительно Tor</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">88,90</context>
|
||||
|
@ -5932,6 +6029,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="b371db1a7ab2167dc8dd91b48ea929d71bb4ef4c" datatype="html">
|
||||
<source>Open channels</source>
|
||||
<target>Открытые каналы</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">145,148</context>
|
||||
|
@ -5940,6 +6038,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="a2dff531c3d7477178553f579e0ec7c3ac7a6f30" datatype="html">
|
||||
<source>Closed channels</source>
|
||||
<target>Закрытые каналы</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.html</context>
|
||||
<context context-type="linenumber">149,152</context>
|
||||
|
@ -5948,6 +6047,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="2519445964020754921" datatype="html">
|
||||
<source>Node: <x id="PH" equiv-text="node.alias"/></source>
|
||||
<target>Узел: <x id="PH" equiv-text="node.alias"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/node/node.component.ts</context>
|
||||
<context context-type="linenumber">42</context>
|
||||
|
@ -5955,6 +6055,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7cac1c3013423d82d5149a5854d709bd08411430" datatype="html">
|
||||
<source>(Tor nodes excluded)</source>
|
||||
<target>(узлы Tor исключены)</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-channels-map/nodes-channels-map.component.html</context>
|
||||
<context context-type="linenumber">8,11</context>
|
||||
|
@ -5975,6 +6076,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>
|
||||
|
@ -5982,6 +6084,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4390631969351833104" datatype="html">
|
||||
<source>No geolocation data available</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">218,213</context>
|
||||
|
@ -5989,6 +6092,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="a4d393ee035f4225083c22cc3909b26a05a87528" datatype="html">
|
||||
<source>Active channels map</source>
|
||||
<target>Карта активных каналов</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-channels/node-channels.component.html</context>
|
||||
<context context-type="linenumber">2,3</context>
|
||||
|
@ -5997,6 +6101,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4635698809727522638" datatype="html">
|
||||
<source>Indexing in progess</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">121,116</context>
|
||||
|
@ -6008,6 +6113,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="1055322764280599360" datatype="html">
|
||||
<source>Reachable on Clearnet Only</source>
|
||||
<target>Доступно только в Clearnet</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>
|
||||
|
@ -6019,6 +6125,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="2760682261176173881" datatype="html">
|
||||
<source>Reachable on Clearnet and Darknet</source>
|
||||
<target>Доступно в Clearnet и 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">185,182</context>
|
||||
|
@ -6030,6 +6137,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="1191036460161514668" datatype="html">
|
||||
<source>Reachable on Darknet Only</source>
|
||||
<target>Доступно только в 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">206,203</context>
|
||||
|
@ -6041,6 +6149,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9" datatype="html">
|
||||
<source>Share</source>
|
||||
<target>Поделиться</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.html</context>
|
||||
<context context-type="linenumber">29,31</context>
|
||||
|
@ -6053,6 +6162,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="5222540403093176126" datatype="html">
|
||||
<source><x id="PH" equiv-text="country.count.toString()"/> nodes</source>
|
||||
<target> узлы <x id="PH" equiv-text="country.count.toString()"/></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">103,102</context>
|
||||
|
@ -6068,6 +6178,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7032954508645880700" datatype="html">
|
||||
<source><x id="PH" equiv-text="this.amountShortenerPipe.transform(country.capacity / 100000000, 2)"/> BTC capacity</source>
|
||||
<target> <x id="PH" equiv-text="this.amountShortenerPipe.transform(country.capacity / 100000000, 2)"/> Емкость BTC</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,102</context>
|
||||
|
@ -6075,6 +6186,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7ede3edfacd291eb9db08e11845d9efdf197f417" datatype="html">
|
||||
<source>Lightning nodes in <x id="INTERPOLATION" equiv-text="{{ country?.name }}"/></source>
|
||||
<target>лайтнинг-злы в <x id="INTERPOLATION" equiv-text="{{ country?.name }}"/></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">3,4</context>
|
||||
|
@ -6083,6 +6195,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>
|
||||
|
@ -6091,6 +6204,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>
|
||||
|
@ -6099,6 +6213,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="7246059109648045954" datatype="html">
|
||||
<source>Lightning nodes in <x id="PH" equiv-text="response.country.en"/></source>
|
||||
<target>лайтнинг-узлы в <x id="PH" equiv-text="response.country.en"/></target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-country/nodes-per-country.component.ts</context>
|
||||
<context context-type="linenumber">35</context>
|
||||
|
@ -6106,6 +6221,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="6b4442323c695a8211357c7e4486dd620c443822" datatype="html">
|
||||
<source>Clearnet Capacity</source>
|
||||
<target>Емкость Clearnet</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">6,8</context>
|
||||
|
@ -6118,6 +6234,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-адрес на Clearnet</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>
|
||||
|
@ -6126,6 +6243,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="462d2233ddacc9869eb28e09b3b12f1d85556937" datatype="html">
|
||||
<source>Unknown Capacity</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">13,15</context>
|
||||
|
@ -6138,6 +6256,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>
|
||||
|
@ -6146,6 +6265,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="df3728b721159d25e68f5daf44aaab7fa25f1415" datatype="html">
|
||||
<source>Tor Capacity</source>
|
||||
<target>Емкость Tor</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">20,22</context>
|
||||
|
@ -6158,6 +6278,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="23549ef4e1f846f06abcf07ceecb115945a0cf61" datatype="html">
|
||||
<source>How much liquidity is running on nodes advertising only Tor addresses</source>
|
||||
<target>Сколько ликвидности в узлах, сообщающих только адреса Tor</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>
|
||||
|
@ -6166,6 +6287,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="e008f2a76179fdcd7110b41ca624131f91075949" datatype="html">
|
||||
<source>Top 100 ISPs hosting LN nodes</source>
|
||||
<target>Топ-100 интернет-провайдеров, размещающих узлы LN</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>
|
||||
|
@ -6174,6 +6296,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="3627306100664959238" datatype="html">
|
||||
<source><x id="PH" equiv-text="this.amountShortenerPipe.transform(isp[2] / 100000000, 2)"/> BTC</source>
|
||||
<target> <x id="PH" equiv-text="this.amountShortenerPipe.transform(isp[2] / 100000000, 2)"/> BTC</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts</context>
|
||||
<context context-type="linenumber">158,156</context>
|
||||
|
@ -6185,6 +6308,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>
|
||||
|
@ -6193,6 +6317,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="41074627e075a9b1bd8197c474ea68a2b8276e54" datatype="html">
|
||||
<source>Top country</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">39,41</context>
|
||||
|
@ -6205,6 +6330,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="5fad6872a652d922ad8822f4016e104b9a8cc113" datatype="html">
|
||||
<source>Top node</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">45,48</context>
|
||||
|
@ -6213,6 +6339,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"/> [AS <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>
|
||||
|
@ -6224,6 +6351,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>
|
||||
|
@ -6232,6 +6360,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>
|
||||
|
@ -6240,6 +6369,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="5b727d251b06e9959cf24a90250a480d425339de" datatype="html">
|
||||
<source>Top 100 oldest lightning nodes</source>
|
||||
<target>Топ-100 самых старых узлов Lightning</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.html</context>
|
||||
<context context-type="linenumber">3,7</context>
|
||||
|
@ -6248,6 +6378,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4157312397261844620" datatype="html">
|
||||
<source>Oldest lightning nodes</source>
|
||||
<target>Самые старые лайтнинг-узлы</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-ranking/oldest-nodes/oldest-nodes.component.ts</context>
|
||||
<context context-type="linenumber">27</context>
|
||||
|
@ -6255,6 +6386,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="71bb1ed9da9ebb92cf35925bc6fe0a8fbc325625" datatype="html">
|
||||
<source>Top 100 nodes liquidity ranking</source>
|
||||
<target>Топ-100 узлов по ликвидности</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-ranking/top-nodes-per-capacity/top-nodes-per-capacity.component.html</context>
|
||||
<context context-type="linenumber">3,7</context>
|
||||
|
@ -6263,6 +6395,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="99786bd2106b708e4514d0121964affb19bee636" datatype="html">
|
||||
<source>Top 100 nodes connectivity ranking</source>
|
||||
<target>Топ-100 узлов по подключению</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-ranking/top-nodes-per-channels/top-nodes-per-channels.component.html</context>
|
||||
<context context-type="linenumber">3,7</context>
|
||||
|
@ -6271,6 +6404,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="47a30fc5a836252f8fe03e2949756b150684d934" datatype="html">
|
||||
<source>Oldest nodes</source>
|
||||
<target>Самые старые узлы</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.html</context>
|
||||
<context context-type="linenumber">36</context>
|
||||
|
@ -6279,6 +6413,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="4034215342842066505" datatype="html">
|
||||
<source>Top lightning nodes</source>
|
||||
<target>Топ лайтнинг-узлы</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.ts</context>
|
||||
<context context-type="linenumber">22</context>
|
||||
|
@ -6286,6 +6421,7 @@
|
|||
</trans-unit>
|
||||
<trans-unit id="af1176facd00a0580509fb2900ab0cf7f9b39ae7" datatype="html">
|
||||
<source>Indexing in progress</source>
|
||||
<target>Выполняется индексирование</target>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/lightning/statistics-chart/lightning-statistics-chart.component.html</context>
|
||||
<context context-type="linenumber">52,55</context>
|
||||
|
|
|
@ -115,10 +115,38 @@ body {
|
|||
}
|
||||
|
||||
.form-control {
|
||||
color: #495057;
|
||||
color: #fff;
|
||||
background-color: #2d3348;
|
||||
border: 1px solid rgba(17, 19, 31, 0.2);
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
color: #000;
|
||||
color: #fff;
|
||||
background-color: #2d3348;
|
||||
}
|
||||
|
||||
.btn-purple {
|
||||
background-color: #653b9c;
|
||||
border-color: #653b9c;
|
||||
}
|
||||
|
||||
.btn-purple:not(:disabled):not(.disabled):active, .btn-purple:not(:disabled):not(.disabled).active, .show > .btn-purple.dropdown-toggle {
|
||||
color: #fff;
|
||||
background-color: #4d2d77;
|
||||
border-color: #472a6e;
|
||||
}
|
||||
|
||||
.btn-purple:focus, .btn-purple.focus {
|
||||
color: #fff;
|
||||
background-color: #533180;
|
||||
border-color: #4d2d77;
|
||||
box-shadow: 0 0 0 0.2rem rgb(124 88 171 / 50%);
|
||||
}
|
||||
|
||||
.btn-purple:hover {
|
||||
color: #fff;
|
||||
background-color: #533180;
|
||||
border-color: #4d2d77;
|
||||
}
|
||||
|
||||
.form-control.form-control-secondary {
|
||||
|
|
|
@ -54,9 +54,13 @@ function downloadMiningPoolLogos() {
|
|||
|
||||
response.on('end', () => {
|
||||
let response_body = Buffer.concat(chunks_of_data);
|
||||
const poolLogos = JSON.parse(response_body.toString());
|
||||
for (const poolLogo of poolLogos) {
|
||||
download(`${PATH}/mining-pools/${poolLogo.name}`, poolLogo.download_url);
|
||||
try {
|
||||
const poolLogos = JSON.parse(response_body.toString());
|
||||
for (const poolLogo of poolLogos) {
|
||||
download(`${PATH}/mining-pools/${poolLogo.name}`, poolLogo.download_url);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Unable to download mining pool logos. Trying again at next restart. Reason: ${e instanceof Error ? e.message : e}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -66,7 +70,6 @@ function downloadMiningPoolLogos() {
|
|||
})
|
||||
}
|
||||
|
||||
const poolsJsonUrl = 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json';
|
||||
let assetsJsonUrl = 'https://raw.githubusercontent.com/mempool/asset_registry_db/master/index.json';
|
||||
let assetsMinimalJsonUrl = 'https://raw.githubusercontent.com/mempool/asset_registry_db/master/index.minimal.json';
|
||||
|
||||
|
@ -82,8 +85,6 @@ console.log('Downloading assets');
|
|||
download(PATH + 'assets.json', assetsJsonUrl);
|
||||
console.log('Downloading assets minimal');
|
||||
download(PATH + 'assets.minimal.json', assetsMinimalJsonUrl);
|
||||
console.log('Downloading mining pools info');
|
||||
download(PATH + 'pools.json', poolsJsonUrl);
|
||||
console.log('Downloading testnet assets');
|
||||
download(PATH + 'assets-testnet.json', testnetAssetsJsonUrl);
|
||||
console.log('Downloading testnet assets minimal');
|
||||
|
|
|
@ -106,6 +106,7 @@ http {
|
|||
~*^vi vi;
|
||||
~*^zh zh;
|
||||
~*^hi hi;
|
||||
~*^lt lt;
|
||||
}
|
||||
|
||||
map $cookie_lang $lang {
|
||||
|
@ -141,6 +142,7 @@ http {
|
|||
~*^vi vi;
|
||||
~*^zh zh;
|
||||
~*^hi hi;
|
||||
~*^lt lt;
|
||||
}
|
||||
|
||||
server {
|
||||
|
|
|
@ -36,6 +36,12 @@ zmqpubrawtx=tcp://127.0.0.1:8335
|
|||
#addnode=[2401:b140:2::92:204]:8333
|
||||
#addnode=[2401:b140:2::92:205]:8333
|
||||
#addnode=[2401:b140:2::92:206]:8333
|
||||
#addnode=[2401:b140:3::92:201]:8333
|
||||
#addnode=[2401:b140:3::92:202]:8333
|
||||
#addnode=[2401:b140:3::92:203]:8333
|
||||
#addnode=[2401:b140:3::92:204]:8333
|
||||
#addnode=[2401:b140:3::92:205]:8333
|
||||
#addnode=[2401:b140:3::92:206]:8333
|
||||
|
||||
[test]
|
||||
daemon=1
|
||||
|
@ -57,6 +63,12 @@ zmqpubrawtx=tcp://127.0.0.1:18335
|
|||
#addnode=[2401:b140:2::92:204]:18333
|
||||
#addnode=[2401:b140:2::92:205]:18333
|
||||
#addnode=[2401:b140:2::92:206]:18333
|
||||
#addnode=[2401:b140:3::92:201]:18333
|
||||
#addnode=[2401:b140:3::92:202]:18333
|
||||
#addnode=[2401:b140:3::92:203]:18333
|
||||
#addnode=[2401:b140:3::92:204]:18333
|
||||
#addnode=[2401:b140:3::92:205]:18333
|
||||
#addnode=[2401:b140:3::92:206]:18333
|
||||
|
||||
[signet]
|
||||
daemon=1
|
||||
|
@ -78,3 +90,9 @@ zmqpubrawtx=tcp://127.0.0.1:38335
|
|||
#addnode=[2401:b140:2::92:204]:38333
|
||||
#addnode=[2401:b140:2::92:205]:38333
|
||||
#addnode=[2401:b140:2::92:206]:38333
|
||||
#addnode=[2401:b140:3::92:201]:38333
|
||||
#addnode=[2401:b140:3::92:202]:38333
|
||||
#addnode=[2401:b140:3::92:203]:38333
|
||||
#addnode=[2401:b140:3::92:204]:38333
|
||||
#addnode=[2401:b140:3::92:205]:38333
|
||||
#addnode=[2401:b140:3::92:206]:38333
|
||||
|
|
|
@ -251,6 +251,7 @@ MEMPOOL_BISQ_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
|||
MEMPOOL_HOME=/mempool
|
||||
MEMPOOL_USER=mempool
|
||||
MEMPOOL_GROUP=mempool
|
||||
MEMPOOL_MYSQL_CREDENTIALS="${MEMPOOL_HOME}/.mysql_credentials"
|
||||
# name of Tor hidden service in torrc
|
||||
MEMPOOL_TOR_HS=mempool
|
||||
|
||||
|
@ -1009,6 +1010,7 @@ osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_
|
|||
osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-build-all upgrade
|
||||
osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-kill-all stop
|
||||
osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-start-all start
|
||||
osSudo "${MEMPOOL_USER}" ln -s mempool/production/mempool-reset-all reset
|
||||
|
||||
|
||||
case $OS in
|
||||
|
@ -1303,9 +1305,9 @@ if [ "${ELEMENTS_ELECTRS_INSTALL}" = ON ];then
|
|||
osSudo "${ELEMENTS_USER}" sh -c "cd ${ELEMENTS_ELECTRS_HOME} && cargo run --release --features liquid --bin electrs -- --network liquid --version" || true
|
||||
fi
|
||||
|
||||
#####################################
|
||||
# Core Lightning for Bitcoin Mainnet #
|
||||
#####################################
|
||||
##############################
|
||||
# Core Lightning for Bitcoin #
|
||||
##############################
|
||||
|
||||
echo "[*] Installing Core Lightning"
|
||||
case $OS in
|
||||
|
@ -1319,20 +1321,28 @@ case $OS in
|
|||
osSudo "${ROOT_USER}" mkdir -p "${CLN_HOME}/.lightning/{bitcoin,signet,testnet}"
|
||||
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning" "${CLN_HOME}/.lightning/{bitcoin,signet,testnet}"
|
||||
osSudo "${ROOT_USER}" chown -R "${CLN_USER}:${CLN_GROUP}" "${CLN_HOME}"
|
||||
echo "[*] Creating symlink to .bitcoin folder"
|
||||
osSudo "${CLN_USER}" ln -s "${BITCOIN_HOME}/.bitcoin" "${CLN_HOME}/.bitcoin"
|
||||
|
||||
echo "[*] Installing Core Lightning package"
|
||||
osPackageInstall ${CLN_PKG}
|
||||
|
||||
echo "[*] Installing Core Lightning mainnet Cronjob"
|
||||
crontab_cln+='@reboot sleep 60 ; screen -dmS main lightningd --alias `hostname` --fee-base 0 --bitcoin-datadir /bitcoin\n'
|
||||
crontab_cln+='@reboot sleep 90 ; screen -dmS tes lightningd --alias `hostname` --fee-base 0 --bitcoin-datadir /bitcoin --network testnet\n'
|
||||
crontab_cln+='@reboot sleep 120 ; screen -dmS sig lightningd --alias `hostname` --fee-base 0 --bitcoin-datadir /bitcoin --network signet\n'
|
||||
public_ipv4=$( ifconfig | grep 'inet ' | awk -F ' ' '{ print $2 }' | grep -v '^103\.165\.192\.' | grep -v '^127\.0\.0\.1' )
|
||||
public_ipv6=$( ifconfig | grep 'inet6' | awk -F ' ' '{ print $2 }' | grep -v '^2001:df6:7280::' | grep -v '^fe80::' | grep -v '^::1' )
|
||||
|
||||
crontab_cln+="@reboot sleep 60 ; screen -dmS main lightningd --rpc-file-mode 0660 --alias `hostname` --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6\n"
|
||||
crontab_cln+="@reboot sleep 90 ; screen -dmS tes lightningd --rpc-file-mode 0660 --alias `hostname` --network testnet --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6\n"
|
||||
crontab_cln+="@reboot sleep 120 ; screen -dmS sig lightningd --rpc-file-mode 0660 --alias `hostname` --network signet --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6 \n"
|
||||
crontab_cln+="@reboot sleep 180 ; /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\n"
|
||||
crontab_cln+="1 * * * * /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\n"
|
||||
echo "${crontab_cln}" | crontab -u "${CLN_USER}" -
|
||||
;;
|
||||
Debian)
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
#####################
|
||||
# Bisq installation #
|
||||
#####################
|
||||
|
@ -1861,7 +1871,7 @@ grant all on mempool_bisq.* to '${MEMPOOL_BISQ_USER}'@'localhost' identified by
|
|||
_EOF_
|
||||
|
||||
echo "[*] save MySQL credentials"
|
||||
cat > ${MEMPOOL_HOME}/mysql_credentials << _EOF_
|
||||
cat > "${MEMPOOL_MYSQL_CREDENTIALS}" << _EOF_
|
||||
declare -x MEMPOOL_MAINNET_USER="${MEMPOOL_MAINNET_USER}"
|
||||
declare -x MEMPOOL_MAINNET_PASS="${MEMPOOL_MAINNET_PASS}"
|
||||
declare -x MEMPOOL_TESTNET_USER="${MEMPOOL_TESTNET_USER}"
|
||||
|
@ -1881,6 +1891,7 @@ declare -x MEMPOOL_LIQUIDTESTNET_PASS="${MEMPOOL_LIQUIDTESTNET_PASS}"
|
|||
declare -x MEMPOOL_BISQ_USER="${MEMPOOL_BISQ_USER}"
|
||||
declare -x MEMPOOL_BISQ_PASS="${MEMPOOL_BISQ_PASS}"
|
||||
_EOF_
|
||||
chown "${MEMPOOL_USER}:${MEMPOOL_GROUP}" "${MEMPOOL_MYSQL_CREDENTIALS}"
|
||||
|
||||
##### nginx
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ ELEMENTS_RPC_USER=$(grep '^rpcuser' /elements/elements.conf | cut -d '=' -f2)
|
|||
ELEMENTS_RPC_PASS=$(grep '^rpcpassword' /elements/elements.conf | cut -d '=' -f2)
|
||||
|
||||
# get mysql credentials
|
||||
MYSQL_CRED_FILE=${HOME}/mempool/mysql_credentials
|
||||
MYSQL_CRED_FILE=${HOME}/.mysql_credentials
|
||||
if [ -f "${MYSQL_CRED_FILE}" ];then
|
||||
. ${MYSQL_CRED_FILE}
|
||||
fi
|
||||
|
|
|
@ -9,5 +9,6 @@
|
|||
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
|
||||
"LIQUID_WEBSITE_URL": "https://liquid.network",
|
||||
"BISQ_WEBSITE_URL": "https://bisq.markets",
|
||||
"ITEMS_PER_PAGE": 25
|
||||
"ITEMS_PER_PAGE": 25,
|
||||
"LIGHTNING": true
|
||||
}
|
||||
|
|
3
production/mempool-reset-all
Executable file
3
production/mempool-reset-all
Executable file
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env zsh
|
||||
rm $HOME/*/backend/mempool-config.json
|
||||
rm $HOME/*/frontend/mempool-frontend-config.json
|
|
@ -31,6 +31,7 @@ map $http_accept_language $header_lang {
|
|||
~*^uk uk;
|
||||
~*^vi vi;
|
||||
~*^zh zh;
|
||||
~*^lt lt;
|
||||
}
|
||||
map $cookie_lang $lang {
|
||||
default $header_lang;
|
||||
|
@ -65,4 +66,5 @@ map $cookie_lang $lang {
|
|||
~*^uk uk;
|
||||
~*^vi vi;
|
||||
~*^zh zh;
|
||||
~*^lt lt;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue