Merge branch 'master' into nymkappa/bugfix/cleanup-mining-states
This commit is contained in:
commit
622929831e
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
backend/src/api/database-migration.ts @wiz @softsimon
|
5
.github/workflows/cypress.yml
vendored
5
.github/workflows/cypress.yml
vendored
@ -1,8 +1,11 @@
|
|||||||
name: Cypress Tests
|
name: Cypress Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, review_requested, synchronize]
|
types: [opened, synchronize]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cypress:
|
cypress:
|
||||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||||
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"editor.tabSize": 2,
|
"editor.tabSize": 2,
|
||||||
|
"typescript.preferences.importModuleSpecifier": "relative",
|
||||||
"typescript.tsdk": "./backend/node_modules/typescript/lib"
|
"typescript.tsdk": "./backend/node_modules/typescript/lib"
|
||||||
}
|
}
|
@ -1,5 +1,7 @@
|
|||||||
# The Mempool Open Source Project™ [](https://dashboard.cypress.io/projects/ry4br7/runs)
|
# The Mempool Open Source Project™ [](https://dashboard.cypress.io/projects/ry4br7/runs)
|
||||||
|
|
||||||
|
https://user-images.githubusercontent.com/232186/222445818-234aa6c9-c233-4c52-b3f0-e32b8232893b.mp4
|
||||||
|
|
||||||
Mempool is the fully-featured mempool visualizer, explorer, and API service running at [mempool.space](https://mempool.space/).
|
Mempool is the fully-featured mempool visualizer, explorer, and API service running at [mempool.space](https://mempool.space/).
|
||||||
|
|
||||||
It is an open-source project developed and operated for the benefit of the Bitcoin community, with a focus on the emerging transaction fee market that is evolving Bitcoin into a multi-layer ecosystem.
|
It is an open-source project developed and operated for the benefit of the Bitcoin community, with a focus on the emerging transaction fee market that is evolving Bitcoin into a multi-layer ecosystem.
|
||||||
|
@ -172,4 +172,35 @@ export namespace IBitcoinApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BlockStats {
|
||||||
|
"avgfee": number;
|
||||||
|
"avgfeerate": number;
|
||||||
|
"avgtxsize": number;
|
||||||
|
"blockhash": string;
|
||||||
|
"feerate_percentiles": [number, number, number, number, number];
|
||||||
|
"height": number;
|
||||||
|
"ins": number;
|
||||||
|
"maxfee": number;
|
||||||
|
"maxfeerate": number;
|
||||||
|
"maxtxsize": number;
|
||||||
|
"medianfee": number;
|
||||||
|
"mediantime": number;
|
||||||
|
"mediantxsize": number;
|
||||||
|
"minfee": number;
|
||||||
|
"minfeerate": number;
|
||||||
|
"mintxsize": number;
|
||||||
|
"outs": number;
|
||||||
|
"subsidy": number;
|
||||||
|
"swtotal_size": number;
|
||||||
|
"swtotal_weight": number;
|
||||||
|
"swtxs": number;
|
||||||
|
"time": number;
|
||||||
|
"total_out": number;
|
||||||
|
"total_size": number;
|
||||||
|
"total_weight": number;
|
||||||
|
"totalfee": number;
|
||||||
|
"txs": number;
|
||||||
|
"utxo_increase": number;
|
||||||
|
"utxo_size_inc": number;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
size: block.size,
|
size: block.size,
|
||||||
weight: block.weight,
|
weight: block.weight,
|
||||||
previousblockhash: block.previousblockhash,
|
previousblockhash: block.previousblockhash,
|
||||||
medianTime: block.mediantime,
|
mediantime: block.mediantime,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,7 +217,15 @@ class BitcoinRoutes {
|
|||||||
res.json(cpfpInfo);
|
res.json(cpfpInfo);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
const cpfpInfo = await transactionRepository.$getCpfpInfo(req.params.txId);
|
let cpfpInfo;
|
||||||
|
if (config.DATABASE.ENABLED) {
|
||||||
|
cpfpInfo = await transactionRepository.$getCpfpInfo(req.params.txId);
|
||||||
|
} else {
|
||||||
|
res.json({
|
||||||
|
ancestors: []
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (cpfpInfo) {
|
if (cpfpInfo) {
|
||||||
res.json(cpfpInfo);
|
res.json(cpfpInfo);
|
||||||
return;
|
return;
|
||||||
|
@ -88,7 +88,7 @@ export namespace IEsploraApi {
|
|||||||
size: number;
|
size: number;
|
||||||
weight: number;
|
weight: number;
|
||||||
previousblockhash: string;
|
previousblockhash: string;
|
||||||
medianTime?: number;
|
mediantime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Address {
|
export interface Address {
|
||||||
|
@ -2,7 +2,7 @@ import config from '../config';
|
|||||||
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import memPool from './mempool';
|
import memPool from './mempool';
|
||||||
import { BlockExtended, BlockSummary, PoolTag, TransactionExtended, TransactionStripped, TransactionMinerInfo } from '../mempool.interfaces';
|
import { BlockExtended, BlockExtension, BlockSummary, PoolTag, TransactionExtended, TransactionStripped, TransactionMinerInfo } from '../mempool.interfaces';
|
||||||
import { Common } from './common';
|
import { Common } from './common';
|
||||||
import diskCache from './disk-cache';
|
import diskCache from './disk-cache';
|
||||||
import transactionUtils from './transaction-utils';
|
import transactionUtils from './transaction-utils';
|
||||||
@ -13,7 +13,6 @@ import poolsRepository from '../repositories/PoolsRepository';
|
|||||||
import blocksRepository from '../repositories/BlocksRepository';
|
import blocksRepository from '../repositories/BlocksRepository';
|
||||||
import loadingIndicators from './loading-indicators';
|
import loadingIndicators from './loading-indicators';
|
||||||
import BitcoinApi from './bitcoin/bitcoin-api';
|
import BitcoinApi from './bitcoin/bitcoin-api';
|
||||||
import { prepareBlock } from '../utils/blocks-utils';
|
|
||||||
import BlocksRepository from '../repositories/BlocksRepository';
|
import BlocksRepository from '../repositories/BlocksRepository';
|
||||||
import HashratesRepository from '../repositories/HashratesRepository';
|
import HashratesRepository from '../repositories/HashratesRepository';
|
||||||
import indexer from '../indexer';
|
import indexer from '../indexer';
|
||||||
@ -143,7 +142,7 @@ class Blocks {
|
|||||||
* @param block
|
* @param block
|
||||||
* @returns BlockSummary
|
* @returns BlockSummary
|
||||||
*/
|
*/
|
||||||
private summarizeBlock(block: IBitcoinApi.VerboseBlock): BlockSummary {
|
public summarizeBlock(block: IBitcoinApi.VerboseBlock): BlockSummary {
|
||||||
const stripped = block.tx.map((tx) => {
|
const stripped = block.tx.map((tx) => {
|
||||||
return {
|
return {
|
||||||
txid: tx.txid,
|
txid: tx.txid,
|
||||||
@ -166,80 +165,81 @@ class Blocks {
|
|||||||
* @returns BlockExtended
|
* @returns BlockExtended
|
||||||
*/
|
*/
|
||||||
private async $getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): Promise<BlockExtended> {
|
private async $getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): Promise<BlockExtended> {
|
||||||
const blk: BlockExtended = Object.assign({ extras: {} }, block);
|
const coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
|
||||||
blk.extras.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
|
|
||||||
blk.extras.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
|
const blk: Partial<BlockExtended> = Object.assign({}, block);
|
||||||
blk.extras.coinbaseRaw = blk.extras.coinbaseTx.vin[0].scriptsig;
|
const extras: Partial<BlockExtension> = {};
|
||||||
blk.extras.usd = priceUpdater.latestPrices.USD;
|
|
||||||
blk.extras.medianTimestamp = block.medianTime;
|
extras.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
|
||||||
blk.extras.orphans = chainTips.getOrphanedBlocksAtHeight(blk.height);
|
extras.coinbaseRaw = coinbaseTx.vin[0].scriptsig;
|
||||||
|
extras.orphans = chainTips.getOrphanedBlocksAtHeight(blk.height);
|
||||||
|
|
||||||
if (block.height === 0) {
|
if (block.height === 0) {
|
||||||
blk.extras.medianFee = 0; // 50th percentiles
|
extras.medianFee = 0; // 50th percentiles
|
||||||
blk.extras.feeRange = [0, 0, 0, 0, 0, 0, 0];
|
extras.feeRange = [0, 0, 0, 0, 0, 0, 0];
|
||||||
blk.extras.totalFees = 0;
|
extras.totalFees = 0;
|
||||||
blk.extras.avgFee = 0;
|
extras.avgFee = 0;
|
||||||
blk.extras.avgFeeRate = 0;
|
extras.avgFeeRate = 0;
|
||||||
blk.extras.utxoSetChange = 0;
|
extras.utxoSetChange = 0;
|
||||||
blk.extras.avgTxSize = 0;
|
extras.avgTxSize = 0;
|
||||||
blk.extras.totalInputs = 0;
|
extras.totalInputs = 0;
|
||||||
blk.extras.totalOutputs = 1;
|
extras.totalOutputs = 1;
|
||||||
blk.extras.totalOutputAmt = 0;
|
extras.totalOutputAmt = 0;
|
||||||
blk.extras.segwitTotalTxs = 0;
|
extras.segwitTotalTxs = 0;
|
||||||
blk.extras.segwitTotalSize = 0;
|
extras.segwitTotalSize = 0;
|
||||||
blk.extras.segwitTotalWeight = 0;
|
extras.segwitTotalWeight = 0;
|
||||||
} else {
|
} else {
|
||||||
const stats = await bitcoinClient.getBlockStats(block.id);
|
const stats: IBitcoinApi.BlockStats = await bitcoinClient.getBlockStats(block.id);
|
||||||
blk.extras.medianFee = stats.feerate_percentiles[2]; // 50th percentiles
|
extras.medianFee = stats.feerate_percentiles[2]; // 50th percentiles
|
||||||
blk.extras.feeRange = [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat();
|
extras.feeRange = [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat();
|
||||||
blk.extras.totalFees = stats.totalfee;
|
extras.totalFees = stats.totalfee;
|
||||||
blk.extras.avgFee = stats.avgfee;
|
extras.avgFee = stats.avgfee;
|
||||||
blk.extras.avgFeeRate = stats.avgfeerate;
|
extras.avgFeeRate = stats.avgfeerate;
|
||||||
blk.extras.utxoSetChange = stats.utxo_increase;
|
extras.utxoSetChange = stats.utxo_increase;
|
||||||
blk.extras.avgTxSize = Math.round(stats.total_size / stats.txs * 100) * 0.01;
|
extras.avgTxSize = Math.round(stats.total_size / stats.txs * 100) * 0.01;
|
||||||
blk.extras.totalInputs = stats.ins;
|
extras.totalInputs = stats.ins;
|
||||||
blk.extras.totalOutputs = stats.outs;
|
extras.totalOutputs = stats.outs;
|
||||||
blk.extras.totalOutputAmt = stats.total_out;
|
extras.totalOutputAmt = stats.total_out;
|
||||||
blk.extras.segwitTotalTxs = stats.swtxs;
|
extras.segwitTotalTxs = stats.swtxs;
|
||||||
blk.extras.segwitTotalSize = stats.swtotal_size;
|
extras.segwitTotalSize = stats.swtotal_size;
|
||||||
blk.extras.segwitTotalWeight = stats.swtotal_weight;
|
extras.segwitTotalWeight = stats.swtotal_weight;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Common.blocksSummariesIndexingEnabled()) {
|
if (Common.blocksSummariesIndexingEnabled()) {
|
||||||
blk.extras.feePercentiles = await BlocksSummariesRepository.$getFeePercentilesByBlockId(block.id);
|
extras.feePercentiles = await BlocksSummariesRepository.$getFeePercentilesByBlockId(block.id);
|
||||||
if (blk.extras.feePercentiles !== null) {
|
if (extras.feePercentiles !== null) {
|
||||||
blk.extras.medianFeeAmt = blk.extras.feePercentiles[3];
|
extras.medianFeeAmt = extras.feePercentiles[3];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
blk.extras.virtualSize = block.weight / 4.0;
|
extras.virtualSize = block.weight / 4.0;
|
||||||
if (blk.extras.coinbaseTx.vout.length > 0) {
|
if (coinbaseTx?.vout.length > 0) {
|
||||||
blk.extras.coinbaseAddress = blk.extras.coinbaseTx.vout[0].scriptpubkey_address ?? null;
|
extras.coinbaseAddress = coinbaseTx.vout[0].scriptpubkey_address ?? null;
|
||||||
blk.extras.coinbaseSignature = blk.extras.coinbaseTx.vout[0].scriptpubkey_asm ?? null;
|
extras.coinbaseSignature = coinbaseTx.vout[0].scriptpubkey_asm ?? null;
|
||||||
blk.extras.coinbaseSignatureAscii = transactionUtils.hex2ascii(blk.extras.coinbaseTx.vin[0].scriptsig) ?? null;
|
extras.coinbaseSignatureAscii = transactionUtils.hex2ascii(coinbaseTx.vin[0].scriptsig) ?? null;
|
||||||
} else {
|
} else {
|
||||||
blk.extras.coinbaseAddress = null;
|
extras.coinbaseAddress = null;
|
||||||
blk.extras.coinbaseSignature = null;
|
extras.coinbaseSignature = null;
|
||||||
blk.extras.coinbaseSignatureAscii = null;
|
extras.coinbaseSignatureAscii = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const header = await bitcoinClient.getBlockHeader(block.id, false);
|
const header = await bitcoinClient.getBlockHeader(block.id, false);
|
||||||
blk.extras.header = header;
|
extras.header = header;
|
||||||
|
|
||||||
const coinStatsIndex = indexer.isCoreIndexReady('coinstatsindex');
|
const coinStatsIndex = indexer.isCoreIndexReady('coinstatsindex');
|
||||||
if (coinStatsIndex !== null && coinStatsIndex.best_block_height >= block.height) {
|
if (coinStatsIndex !== null && coinStatsIndex.best_block_height >= block.height) {
|
||||||
const txoutset = await bitcoinClient.getTxoutSetinfo('none', block.height);
|
const txoutset = await bitcoinClient.getTxoutSetinfo('none', block.height);
|
||||||
blk.extras.utxoSetSize = txoutset.txouts,
|
extras.utxoSetSize = txoutset.txouts,
|
||||||
blk.extras.totalInputAmt = Math.round(txoutset.block_info.prevout_spent * 100000000);
|
extras.totalInputAmt = Math.round(txoutset.block_info.prevout_spent * 100000000);
|
||||||
} else {
|
} else {
|
||||||
blk.extras.utxoSetSize = null;
|
extras.utxoSetSize = null;
|
||||||
blk.extras.totalInputAmt = null;
|
extras.totalInputAmt = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) {
|
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) {
|
||||||
let pool: PoolTag;
|
let pool: PoolTag;
|
||||||
if (blk.extras?.coinbaseTx !== undefined) {
|
if (coinbaseTx !== undefined) {
|
||||||
pool = await this.$findBlockMiner(blk.extras?.coinbaseTx);
|
pool = await this.$findBlockMiner(coinbaseTx);
|
||||||
} else {
|
} else {
|
||||||
if (config.DATABASE.ENABLED === true) {
|
if (config.DATABASE.ENABLED === true) {
|
||||||
pool = await poolsRepository.$getUnknownPool();
|
pool = await poolsRepository.$getUnknownPool();
|
||||||
@ -252,22 +252,24 @@ class Blocks {
|
|||||||
logger.warn(`Cannot assign pool to block ${blk.height} and 'unknown' pool does not exist. ` +
|
logger.warn(`Cannot assign pool to block ${blk.height} and 'unknown' pool does not exist. ` +
|
||||||
`Check your "pools" table entries`);
|
`Check your "pools" table entries`);
|
||||||
} else {
|
} else {
|
||||||
blk.extras.pool = {
|
extras.pool = {
|
||||||
id: pool.id,
|
id: pool.uniqueId,
|
||||||
name: pool.name,
|
name: pool.name,
|
||||||
slug: pool.slug,
|
slug: pool.slug,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extras.matchRate = null;
|
||||||
if (config.MEMPOOL.AUDIT) {
|
if (config.MEMPOOL.AUDIT) {
|
||||||
const auditScore = await BlocksAuditsRepository.$getBlockAuditScore(block.id);
|
const auditScore = await BlocksAuditsRepository.$getBlockAuditScore(block.id);
|
||||||
if (auditScore != null) {
|
if (auditScore != null) {
|
||||||
blk.extras.matchRate = auditScore.matchRate;
|
extras.matchRate = auditScore.matchRate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return blk;
|
blk.extras = <BlockExtension>extras;
|
||||||
|
return <BlockExtended>blk;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -293,15 +295,18 @@ class Blocks {
|
|||||||
} else {
|
} else {
|
||||||
pools = poolsParser.miningPools;
|
pools = poolsParser.miningPools;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < pools.length; ++i) {
|
for (let i = 0; i < pools.length; ++i) {
|
||||||
if (address !== undefined) {
|
if (address !== undefined) {
|
||||||
const addresses: string[] = JSON.parse(pools[i].addresses);
|
const addresses: string[] = typeof pools[i].addresses === 'string' ?
|
||||||
|
JSON.parse(pools[i].addresses) : pools[i].addresses;
|
||||||
if (addresses.indexOf(address) !== -1) {
|
if (addresses.indexOf(address) !== -1) {
|
||||||
return pools[i];
|
return pools[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const regexes: string[] = JSON.parse(pools[i].regexes);
|
const regexes: string[] = typeof pools[i].regexes === 'string' ?
|
||||||
|
JSON.parse(pools[i].regexes) : pools[i].regexes;
|
||||||
for (let y = 0; y < regexes.length; ++y) {
|
for (let y = 0; y < regexes.length; ++y) {
|
||||||
const regex = new RegExp(regexes[y], 'i');
|
const regex = new RegExp(regexes[y], 'i');
|
||||||
const match = asciiScriptSig.match(regex);
|
const match = asciiScriptSig.match(regex);
|
||||||
@ -479,7 +484,7 @@ class Blocks {
|
|||||||
loadingIndicators.setProgress('block-indexing', progress, false);
|
loadingIndicators.setProgress('block-indexing', progress, false);
|
||||||
}
|
}
|
||||||
const blockHash = await bitcoinApi.$getBlockHash(blockHeight);
|
const blockHash = await bitcoinApi.$getBlockHash(blockHeight);
|
||||||
const block = BitcoinApi.convertBlock(await bitcoinClient.getBlock(blockHash));
|
const block: IEsploraApi.Block = await bitcoinApi.$getBlock(blockHash);
|
||||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true, true);
|
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true, true);
|
||||||
const blockExtended = await this.$getBlockExtended(block, transactions);
|
const blockExtended = await this.$getBlockExtended(block, transactions);
|
||||||
|
|
||||||
@ -527,13 +532,13 @@ class Blocks {
|
|||||||
if (blockchainInfo.blocks === blockchainInfo.headers) {
|
if (blockchainInfo.blocks === blockchainInfo.headers) {
|
||||||
const heightDiff = blockHeightTip % 2016;
|
const heightDiff = blockHeightTip % 2016;
|
||||||
const blockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff);
|
const blockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff);
|
||||||
const block = BitcoinApi.convertBlock(await bitcoinClient.getBlock(blockHash));
|
const block: IEsploraApi.Block = await bitcoinApi.$getBlock(blockHash);
|
||||||
this.lastDifficultyAdjustmentTime = block.timestamp;
|
this.lastDifficultyAdjustmentTime = block.timestamp;
|
||||||
this.currentDifficulty = block.difficulty;
|
this.currentDifficulty = block.difficulty;
|
||||||
|
|
||||||
if (blockHeightTip >= 2016) {
|
if (blockHeightTip >= 2016) {
|
||||||
const previousPeriodBlockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff - 2016);
|
const previousPeriodBlockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff - 2016);
|
||||||
const previousPeriodBlock = await bitcoinClient.getBlock(previousPeriodBlockHash)
|
const previousPeriodBlock: IEsploraApi.Block = await bitcoinApi.$getBlock(previousPeriodBlockHash);
|
||||||
this.previousDifficultyRetarget = (block.difficulty - previousPeriodBlock.difficulty) / previousPeriodBlock.difficulty * 100;
|
this.previousDifficultyRetarget = (block.difficulty - previousPeriodBlock.difficulty) / previousPeriodBlock.difficulty * 100;
|
||||||
logger.debug(`Initial difficulty adjustment data set.`);
|
logger.debug(`Initial difficulty adjustment data set.`);
|
||||||
}
|
}
|
||||||
@ -565,18 +570,18 @@ class Blocks {
|
|||||||
if (Common.indexingEnabled()) {
|
if (Common.indexingEnabled()) {
|
||||||
if (!fastForwarded) {
|
if (!fastForwarded) {
|
||||||
const lastBlock = await blocksRepository.$getBlockByHeight(blockExtended.height - 1);
|
const lastBlock = await blocksRepository.$getBlockByHeight(blockExtended.height - 1);
|
||||||
if (lastBlock !== null && blockExtended.previousblockhash !== lastBlock['hash']) {
|
if (lastBlock !== null && blockExtended.previousblockhash !== lastBlock.id) {
|
||||||
logger.warn(`Chain divergence detected at block ${lastBlock['height']}, re-indexing most recent data`);
|
logger.warn(`Chain divergence detected at block ${lastBlock.height}, re-indexing most recent data`);
|
||||||
// We assume there won't be a reorg with more than 10 block depth
|
// We assume there won't be a reorg with more than 10 block depth
|
||||||
await BlocksRepository.$deleteBlocksFrom(lastBlock['height'] - 10);
|
await BlocksRepository.$deleteBlocksFrom(lastBlock.height - 10);
|
||||||
await HashratesRepository.$deleteLastEntries();
|
await HashratesRepository.$deleteLastEntries();
|
||||||
await BlocksSummariesRepository.$deleteBlocksFrom(lastBlock['height'] - 10);
|
await BlocksSummariesRepository.$deleteBlocksFrom(lastBlock.height - 10);
|
||||||
await cpfpRepository.$deleteClustersFrom(lastBlock['height'] - 10);
|
await cpfpRepository.$deleteClustersFrom(lastBlock.height - 10);
|
||||||
for (let i = 10; i >= 0; --i) {
|
for (let i = 10; i >= 0; --i) {
|
||||||
const newBlock = await this.$indexBlock(lastBlock['height'] - i);
|
const newBlock = await this.$indexBlock(lastBlock.height - i);
|
||||||
await this.$getStrippedBlockTransactions(newBlock.id, true, true);
|
await this.$getStrippedBlockTransactions(newBlock.id, true, true);
|
||||||
if (config.MEMPOOL.CPFP_INDEXING) {
|
if (config.MEMPOOL.CPFP_INDEXING) {
|
||||||
await this.$indexCPFP(newBlock.id, lastBlock['height'] - i);
|
await this.$indexCPFP(newBlock.id, lastBlock.height - i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await mining.$indexDifficultyAdjustments();
|
await mining.$indexDifficultyAdjustments();
|
||||||
@ -652,12 +657,12 @@ class Blocks {
|
|||||||
if (Common.indexingEnabled()) {
|
if (Common.indexingEnabled()) {
|
||||||
const dbBlock = await blocksRepository.$getBlockByHeight(height);
|
const dbBlock = await blocksRepository.$getBlockByHeight(height);
|
||||||
if (dbBlock !== null) {
|
if (dbBlock !== null) {
|
||||||
return prepareBlock(dbBlock);
|
return dbBlock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const blockHash = await bitcoinApi.$getBlockHash(height);
|
const blockHash = await bitcoinApi.$getBlockHash(height);
|
||||||
const block = BitcoinApi.convertBlock(await bitcoinClient.getBlock(blockHash));
|
const block: IEsploraApi.Block = await bitcoinApi.$getBlock(blockHash);
|
||||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true);
|
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true);
|
||||||
const blockExtended = await this.$getBlockExtended(block, transactions);
|
const blockExtended = await this.$getBlockExtended(block, transactions);
|
||||||
|
|
||||||
@ -665,11 +670,11 @@ class Blocks {
|
|||||||
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
||||||
}
|
}
|
||||||
|
|
||||||
return prepareBlock(blockExtended);
|
return blockExtended;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Index a block by hash if it's missing from the database. Returns the block after indexing
|
* Get one block by its hash
|
||||||
*/
|
*/
|
||||||
public async $getBlock(hash: string): Promise<BlockExtended | IEsploraApi.Block> {
|
public async $getBlock(hash: string): Promise<BlockExtended | IEsploraApi.Block> {
|
||||||
// Check the memory cache
|
// Check the memory cache
|
||||||
@ -678,31 +683,14 @@ class Blocks {
|
|||||||
return blockByHash;
|
return blockByHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block has already been indexed
|
// Not Bitcoin network, return the block as it from the bitcoin backend
|
||||||
if (Common.indexingEnabled()) {
|
|
||||||
const dbBlock = await blocksRepository.$getBlockByHash(hash);
|
|
||||||
if (dbBlock != null) {
|
|
||||||
return prepareBlock(dbBlock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not Bitcoin network, return the block as it
|
|
||||||
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) {
|
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) {
|
||||||
return await bitcoinApi.$getBlock(hash);
|
return await bitcoinApi.$getBlock(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
let block = await bitcoinClient.getBlock(hash);
|
|
||||||
block = prepareBlock(block);
|
|
||||||
|
|
||||||
// Bitcoin network, add our custom data on top
|
// Bitcoin network, add our custom data on top
|
||||||
const transactions = await this.$getTransactionsExtended(hash, block.height, true);
|
const block: IEsploraApi.Block = await bitcoinApi.$getBlock(hash);
|
||||||
const blockExtended = await this.$getBlockExtended(block, transactions);
|
return await this.$indexBlock(block.height);
|
||||||
if (Common.indexingEnabled()) {
|
|
||||||
delete(blockExtended['coinbaseTx']);
|
|
||||||
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
|
||||||
}
|
|
||||||
|
|
||||||
return blockExtended;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $getStrippedBlockTransactions(hash: string, skipMemoryCache = false,
|
public async $getStrippedBlockTransactions(hash: string, skipMemoryCache = false,
|
||||||
@ -736,6 +724,18 @@ class Blocks {
|
|||||||
return summary.transactions;
|
return summary.transactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get 15 blocks
|
||||||
|
*
|
||||||
|
* Internally this function uses two methods to get the blocks, and
|
||||||
|
* the method is automatically selected:
|
||||||
|
* - Using previous block hash links
|
||||||
|
* - Using block height
|
||||||
|
*
|
||||||
|
* @param fromHeight
|
||||||
|
* @param limit
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
public async $getBlocks(fromHeight?: number, limit: number = 15): Promise<BlockExtended[]> {
|
public async $getBlocks(fromHeight?: number, limit: number = 15): Promise<BlockExtended[]> {
|
||||||
let currentHeight = fromHeight !== undefined ? fromHeight : this.currentBlockHeight;
|
let currentHeight = fromHeight !== undefined ? fromHeight : this.currentBlockHeight;
|
||||||
if (currentHeight > this.currentBlockHeight) {
|
if (currentHeight > this.currentBlockHeight) {
|
||||||
@ -748,27 +748,15 @@ class Blocks {
|
|||||||
return returnBlocks;
|
return returnBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if block height exist in local cache to skip the hash lookup
|
|
||||||
const blockByHeight = this.getBlocks().find((b) => b.height === currentHeight);
|
|
||||||
let startFromHash: string | null = null;
|
|
||||||
if (blockByHeight) {
|
|
||||||
startFromHash = blockByHeight.id;
|
|
||||||
} else if (!Common.indexingEnabled()) {
|
|
||||||
startFromHash = await bitcoinApi.$getBlockHash(currentHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
let nextHash = startFromHash;
|
|
||||||
for (let i = 0; i < limit && currentHeight >= 0; i++) {
|
for (let i = 0; i < limit && currentHeight >= 0; i++) {
|
||||||
let block = this.getBlocks().find((b) => b.height === currentHeight);
|
let block = this.getBlocks().find((b) => b.height === currentHeight);
|
||||||
if (block) {
|
if (block) {
|
||||||
|
// Using the memory cache (find by height)
|
||||||
returnBlocks.push(block);
|
returnBlocks.push(block);
|
||||||
} else if (Common.indexingEnabled()) {
|
} else {
|
||||||
|
// Using indexing (find by height, index on the fly, save in database)
|
||||||
block = await this.$indexBlock(currentHeight);
|
block = await this.$indexBlock(currentHeight);
|
||||||
returnBlocks.push(block);
|
returnBlocks.push(block);
|
||||||
} else if (nextHash != null) {
|
|
||||||
block = await this.$indexBlock(currentHeight);
|
|
||||||
nextHash = block.previousblockhash;
|
|
||||||
returnBlocks.push(block);
|
|
||||||
}
|
}
|
||||||
currentHeight--;
|
currentHeight--;
|
||||||
}
|
}
|
||||||
@ -790,7 +778,7 @@ class Blocks {
|
|||||||
const blocks: any[] = [];
|
const blocks: any[] = [];
|
||||||
|
|
||||||
while (fromHeight <= toHeight) {
|
while (fromHeight <= toHeight) {
|
||||||
let block: any = await blocksRepository.$getBlockByHeight(fromHeight);
|
let block: BlockExtended | null = await blocksRepository.$getBlockByHeight(fromHeight);
|
||||||
if (!block) {
|
if (!block) {
|
||||||
await this.$indexBlock(fromHeight);
|
await this.$indexBlock(fromHeight);
|
||||||
block = await blocksRepository.$getBlockByHeight(fromHeight);
|
block = await blocksRepository.$getBlockByHeight(fromHeight);
|
||||||
@ -803,11 +791,11 @@ class Blocks {
|
|||||||
const cleanBlock: any = {
|
const cleanBlock: any = {
|
||||||
height: block.height ?? null,
|
height: block.height ?? null,
|
||||||
hash: block.id ?? null,
|
hash: block.id ?? null,
|
||||||
timestamp: block.blockTimestamp ?? null,
|
timestamp: block.timestamp ?? null,
|
||||||
median_timestamp: block.medianTime ?? null,
|
median_timestamp: block.mediantime ?? null,
|
||||||
previous_block_hash: block.previousblockhash ?? null,
|
previous_block_hash: block.previousblockhash ?? null,
|
||||||
difficulty: block.difficulty ?? null,
|
difficulty: block.difficulty ?? null,
|
||||||
header: block.header ?? null,
|
header: block.extras.header ?? null,
|
||||||
version: block.version ?? null,
|
version: block.version ?? null,
|
||||||
bits: block.bits ?? null,
|
bits: block.bits ?? null,
|
||||||
nonce: block.nonce ?? null,
|
nonce: block.nonce ?? null,
|
||||||
@ -815,29 +803,30 @@ class Blocks {
|
|||||||
weight: block.weight ?? null,
|
weight: block.weight ?? null,
|
||||||
tx_count: block.tx_count ?? null,
|
tx_count: block.tx_count ?? null,
|
||||||
merkle_root: block.merkle_root ?? null,
|
merkle_root: block.merkle_root ?? null,
|
||||||
reward: block.reward ?? null,
|
reward: block.extras.reward ?? null,
|
||||||
total_fee_amt: block.fees ?? null,
|
total_fee_amt: block.extras.totalFees ?? null,
|
||||||
avg_fee_amt: block.avg_fee ?? null,
|
avg_fee_amt: block.extras.avgFee ?? null,
|
||||||
median_fee_amt: block.median_fee_amt ?? null,
|
median_fee_amt: block.extras.medianFeeAmt ?? null,
|
||||||
fee_amt_percentiles: block.fee_percentiles ?? null,
|
fee_amt_percentiles: block.extras.feePercentiles ?? null,
|
||||||
avg_fee_rate: block.avg_fee_rate ?? null,
|
avg_fee_rate: block.extras.avgFeeRate ?? null,
|
||||||
median_fee_rate: block.median_fee ?? null,
|
median_fee_rate: block.extras.medianFee ?? null,
|
||||||
fee_rate_percentiles: block.fee_span ?? null,
|
fee_rate_percentiles: block.extras.feeRange ?? null,
|
||||||
total_inputs: block.total_inputs ?? null,
|
total_inputs: block.extras.totalInputs ?? null,
|
||||||
total_input_amt: block.total_input_amt ?? null,
|
total_input_amt: block.extras.totalInputAmt ?? null,
|
||||||
total_outputs: block.total_outputs ?? null,
|
total_outputs: block.extras.totalOutputs ?? null,
|
||||||
total_output_amt: block.total_output_amt ?? null,
|
total_output_amt: block.extras.totalOutputAmt ?? null,
|
||||||
segwit_total_txs: block.segwit_total_txs ?? null,
|
segwit_total_txs: block.extras.segwitTotalTxs ?? null,
|
||||||
segwit_total_size: block.segwit_total_size ?? null,
|
segwit_total_size: block.extras.segwitTotalSize ?? null,
|
||||||
segwit_total_weight: block.segwit_total_weight ?? null,
|
segwit_total_weight: block.extras.segwitTotalWeight ?? null,
|
||||||
avg_tx_size: block.avg_tx_size ?? null,
|
avg_tx_size: block.extras.avgTxSize ?? null,
|
||||||
utxoset_change: block.utxoset_change ?? null,
|
utxoset_change: block.extras.utxoSetChange ?? null,
|
||||||
utxoset_size: block.utxoset_size ?? null,
|
utxoset_size: block.extras.utxoSetSize ?? null,
|
||||||
coinbase_raw: block.coinbase_raw ?? null,
|
coinbase_raw: block.extras.coinbaseRaw ?? null,
|
||||||
coinbase_address: block.coinbase_address ?? null,
|
coinbase_address: block.extras.coinbaseAddress ?? null,
|
||||||
coinbase_signature: block.coinbase_signature ?? null,
|
coinbase_signature: block.extras.coinbaseSignature ?? null,
|
||||||
coinbase_signature_ascii: block.coinbase_signature_ascii ?? null,
|
coinbase_signature_ascii: block.extras.coinbaseSignatureAscii ?? null,
|
||||||
pool_slug: block.pool_slug ?? null,
|
pool_slug: block.extras.pool.slug ?? null,
|
||||||
|
pool_id: block.extras.pool.id ?? null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Common.blocksSummariesIndexingEnabled() && cleanBlock.fee_amt_percentiles === null) {
|
if (Common.blocksSummariesIndexingEnabled() && cleanBlock.fee_amt_percentiles === null) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import logger from "../logger";
|
import logger from '../logger';
|
||||||
import bitcoinClient from "./bitcoin/bitcoin-client";
|
import bitcoinClient from './bitcoin/bitcoin-client';
|
||||||
|
|
||||||
export interface ChainTip {
|
export interface ChainTip {
|
||||||
height: number;
|
height: number;
|
||||||
@ -43,7 +43,11 @@ class ChainTips {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getOrphanedBlocksAtHeight(height: number): OrphanedBlock[] {
|
public getOrphanedBlocksAtHeight(height: number | undefined): OrphanedBlock[] {
|
||||||
|
if (height === undefined) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const orphans: OrphanedBlock[] = [];
|
const orphans: OrphanedBlock[] = [];
|
||||||
for (const block of this.orphanedBlocks) {
|
for (const block of this.orphanedBlocks) {
|
||||||
if (block.height === height) {
|
if (block.height === height) {
|
||||||
|
@ -501,7 +501,7 @@ class DatabaseMigration {
|
|||||||
await this.updateToSchemaVersion(56);
|
await this.updateToSchemaVersion(56);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (databaseSchemaVersion < 57) {
|
if (databaseSchemaVersion < 57 && isBitcoin === true) {
|
||||||
await this.$executeQuery(`ALTER TABLE nodes MODIFY updated_at datetime NULL`);
|
await this.$executeQuery(`ALTER TABLE nodes MODIFY updated_at datetime NULL`);
|
||||||
await this.updateToSchemaVersion(57);
|
await this.updateToSchemaVersion(57);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import { TransactionExtended } from '../mempool.interfaces';
|
|||||||
import { Common } from './common';
|
import { Common } from './common';
|
||||||
|
|
||||||
class DiskCache {
|
class DiskCache {
|
||||||
private cacheSchemaVersion = 2;
|
private cacheSchemaVersion = 3;
|
||||||
|
|
||||||
private static FILE_NAME = config.MEMPOOL.CACHE_DIR + '/cache.json';
|
private static FILE_NAME = config.MEMPOOL.CACHE_DIR + '/cache.json';
|
||||||
private static FILE_NAMES = config.MEMPOOL.CACHE_DIR + '/cache{number}.json';
|
private static FILE_NAMES = config.MEMPOOL.CACHE_DIR + '/cache{number}.json';
|
||||||
@ -62,9 +62,24 @@ class DiskCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wipeCache() {
|
wipeCache() {
|
||||||
fs.unlinkSync(DiskCache.FILE_NAME);
|
logger.notice(`Wipping nodejs backend cache/cache*.json files`);
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(DiskCache.FILE_NAME);
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e?.code !== 'ENOENT') {
|
||||||
|
logger.err(`Cannot wipe cache file ${DiskCache.FILE_NAME}. Exception ${JSON.stringify(e)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 1; i < DiskCache.CHUNK_FILES; i++) {
|
for (let i = 1; i < DiskCache.CHUNK_FILES; i++) {
|
||||||
fs.unlinkSync(DiskCache.FILE_NAMES.replace('{number}', i.toString()));
|
const filename = DiskCache.FILE_NAMES.replace('{number}', i.toString());
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(filename);
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e?.code !== 'ENOENT') {
|
||||||
|
logger.err(`Cannot wipe cache file ${filename}. Exception ${JSON.stringify(e)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,14 +97,14 @@ class MempoolBlocks {
|
|||||||
blockSize += tx.size;
|
blockSize += tx.size;
|
||||||
transactions.push(tx);
|
transactions.push(tx);
|
||||||
} else {
|
} else {
|
||||||
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, mempoolBlocks.length));
|
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, mempoolBlocks.length));
|
||||||
blockWeight = tx.weight;
|
blockWeight = tx.weight;
|
||||||
blockSize = tx.size;
|
blockSize = tx.size;
|
||||||
transactions = [tx];
|
transactions = [tx];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (transactions.length) {
|
if (transactions.length) {
|
||||||
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, mempoolBlocks.length));
|
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, mempoolBlocks.length));
|
||||||
}
|
}
|
||||||
|
|
||||||
return mempoolBlocks;
|
return mempoolBlocks;
|
||||||
@ -281,7 +281,7 @@ class MempoolBlocks {
|
|||||||
const mempoolBlocks = blocks.map((transactions, blockIndex) => {
|
const mempoolBlocks = blocks.map((transactions, blockIndex) => {
|
||||||
return this.dataToMempoolBlocks(transactions.map(tx => {
|
return this.dataToMempoolBlocks(transactions.map(tx => {
|
||||||
return mempool[tx.txid] || null;
|
return mempool[tx.txid] || null;
|
||||||
}).filter(tx => !!tx), undefined, undefined, blockIndex);
|
}).filter(tx => !!tx), blockIndex);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (saveResults) {
|
if (saveResults) {
|
||||||
@ -293,18 +293,17 @@ class MempoolBlocks {
|
|||||||
return mempoolBlocks;
|
return mempoolBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
private dataToMempoolBlocks(transactions: TransactionExtended[],
|
private dataToMempoolBlocks(transactions: TransactionExtended[], blocksIndex: number): MempoolBlockWithTransactions {
|
||||||
blockSize: number | undefined, blockWeight: number | undefined, blocksIndex: number): MempoolBlockWithTransactions {
|
let totalSize = 0;
|
||||||
let totalSize = blockSize || 0;
|
let totalWeight = 0;
|
||||||
let totalWeight = blockWeight || 0;
|
const fitTransactions: TransactionExtended[] = [];
|
||||||
if (blockSize === undefined && blockWeight === undefined) {
|
transactions.forEach(tx => {
|
||||||
totalSize = 0;
|
totalSize += tx.size;
|
||||||
totalWeight = 0;
|
totalWeight += tx.weight;
|
||||||
transactions.forEach(tx => {
|
if ((totalWeight + tx.weight) <= config.MEMPOOL.BLOCK_WEIGHT_UNITS * 1.2) {
|
||||||
totalSize += tx.size;
|
fitTransactions.push(tx);
|
||||||
totalWeight += tx.weight;
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
let rangeLength = 4;
|
let rangeLength = 4;
|
||||||
if (blocksIndex === 0) {
|
if (blocksIndex === 0) {
|
||||||
rangeLength = 8;
|
rangeLength = 8;
|
||||||
@ -322,7 +321,7 @@ class MempoolBlocks {
|
|||||||
medianFee: Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
|
medianFee: Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
|
||||||
feeRange: Common.getFeesInRange(transactions, rangeLength),
|
feeRange: Common.getFeesInRange(transactions, rangeLength),
|
||||||
transactionIds: transactions.map((tx) => tx.txid),
|
transactionIds: transactions.map((tx) => tx.txid),
|
||||||
transactions: transactions.map((tx) => Common.stripTransaction(tx)),
|
transactions: fitTransactions.map((tx) => Common.stripTransaction(tx)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,8 @@ import DifficultyAdjustmentsRepository from '../../repositories/DifficultyAdjust
|
|||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository';
|
import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository';
|
||||||
import PricesRepository from '../../repositories/PricesRepository';
|
import PricesRepository from '../../repositories/PricesRepository';
|
||||||
|
import bitcoinApiFactory from '../bitcoin/bitcoin-api-factory';
|
||||||
|
import { IEsploraApi } from '../bitcoin/esplora-api.interface';
|
||||||
|
|
||||||
class Mining {
|
class Mining {
|
||||||
private blocksPriceIndexingRunning = false;
|
private blocksPriceIndexingRunning = false;
|
||||||
@ -189,8 +191,8 @@ class Mining {
|
|||||||
try {
|
try {
|
||||||
const oldestConsecutiveBlockTimestamp = 1000 * (await BlocksRepository.$getOldestConsecutiveBlock()).timestamp;
|
const oldestConsecutiveBlockTimestamp = 1000 * (await BlocksRepository.$getOldestConsecutiveBlock()).timestamp;
|
||||||
|
|
||||||
const genesisBlock = await bitcoinClient.getBlock(await bitcoinClient.getBlockHash(0));
|
const genesisBlock: IEsploraApi.Block = await bitcoinApiFactory.$getBlock(await bitcoinClient.getBlockHash(0));
|
||||||
const genesisTimestamp = genesisBlock.time * 1000;
|
const genesisTimestamp = genesisBlock.timestamp * 1000;
|
||||||
|
|
||||||
const indexedTimestamp = await HashratesRepository.$getWeeklyHashrateTimestamps();
|
const indexedTimestamp = await HashratesRepository.$getWeeklyHashrateTimestamps();
|
||||||
const hashrates: any[] = [];
|
const hashrates: any[] = [];
|
||||||
@ -292,8 +294,8 @@ class Mining {
|
|||||||
const oldestConsecutiveBlockTimestamp = 1000 * (await BlocksRepository.$getOldestConsecutiveBlock()).timestamp;
|
const oldestConsecutiveBlockTimestamp = 1000 * (await BlocksRepository.$getOldestConsecutiveBlock()).timestamp;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const genesisBlock = await bitcoinClient.getBlock(await bitcoinClient.getBlockHash(0));
|
const genesisBlock: IEsploraApi.Block = await bitcoinApiFactory.$getBlock(await bitcoinClient.getBlockHash(0));
|
||||||
const genesisTimestamp = genesisBlock.time * 1000;
|
const genesisTimestamp = genesisBlock.timestamp * 1000;
|
||||||
const indexedTimestamp = (await HashratesRepository.$getRawNetworkDailyHashrate(null)).map(hashrate => hashrate.timestamp);
|
const indexedTimestamp = (await HashratesRepository.$getRawNetworkDailyHashrate(null)).map(hashrate => hashrate.timestamp);
|
||||||
const lastMidnight = this.getDateMidnight(new Date());
|
const lastMidnight = this.getDateMidnight(new Date());
|
||||||
let toTimestamp = Math.round(lastMidnight.getTime());
|
let toTimestamp = Math.round(lastMidnight.getTime());
|
||||||
@ -394,13 +396,13 @@ class Mining {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const blocks: any = await BlocksRepository.$getBlocksDifficulty();
|
const blocks: any = await BlocksRepository.$getBlocksDifficulty();
|
||||||
const genesisBlock = await bitcoinClient.getBlock(await bitcoinClient.getBlockHash(0));
|
const genesisBlock: IEsploraApi.Block = await bitcoinApiFactory.$getBlock(await bitcoinClient.getBlockHash(0));
|
||||||
let currentDifficulty = genesisBlock.difficulty;
|
let currentDifficulty = genesisBlock.difficulty;
|
||||||
let totalIndexed = 0;
|
let totalIndexed = 0;
|
||||||
|
|
||||||
if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT === -1 && indexedHeights[0] !== true) {
|
if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT === -1 && indexedHeights[0] !== true) {
|
||||||
await DifficultyAdjustmentsRepository.$saveAdjustments({
|
await DifficultyAdjustmentsRepository.$saveAdjustments({
|
||||||
time: genesisBlock.time,
|
time: genesisBlock.timestamp,
|
||||||
height: 0,
|
height: 0,
|
||||||
difficulty: currentDifficulty,
|
difficulty: currentDifficulty,
|
||||||
adjustment: 0.0,
|
adjustment: 0.0,
|
||||||
|
@ -3,10 +3,12 @@ import logger from '../logger';
|
|||||||
import config from '../config';
|
import config from '../config';
|
||||||
import PoolsRepository from '../repositories/PoolsRepository';
|
import PoolsRepository from '../repositories/PoolsRepository';
|
||||||
import { PoolTag } from '../mempool.interfaces';
|
import { PoolTag } from '../mempool.interfaces';
|
||||||
|
import diskCache from './disk-cache';
|
||||||
|
|
||||||
class PoolsParser {
|
class PoolsParser {
|
||||||
miningPools: any[] = [];
|
miningPools: any[] = [];
|
||||||
unknownPool: any = {
|
unknownPool: any = {
|
||||||
|
'id': 0,
|
||||||
'name': 'Unknown',
|
'name': 'Unknown',
|
||||||
'link': 'https://learnmeabitcoin.com/technical/coinbase-transaction',
|
'link': 'https://learnmeabitcoin.com/technical/coinbase-transaction',
|
||||||
'regexes': '[]',
|
'regexes': '[]',
|
||||||
@ -26,6 +28,7 @@ class PoolsParser {
|
|||||||
public setMiningPools(pools): void {
|
public setMiningPools(pools): void {
|
||||||
for (const pool of pools) {
|
for (const pool of pools) {
|
||||||
pool.regexes = pool.tags;
|
pool.regexes = pool.tags;
|
||||||
|
pool.slug = pool.name.replace(/[^a-z0-9]/gi, '').toLowerCase();
|
||||||
delete(pool.tags);
|
delete(pool.tags);
|
||||||
}
|
}
|
||||||
this.miningPools = pools;
|
this.miningPools = pools;
|
||||||
@ -36,6 +39,10 @@ class PoolsParser {
|
|||||||
* @param pools
|
* @param pools
|
||||||
*/
|
*/
|
||||||
public async migratePoolsJson(): Promise<void> {
|
public async migratePoolsJson(): Promise<void> {
|
||||||
|
// We also need to wipe the backend cache to make sure we don't serve blocks with
|
||||||
|
// the wrong mining pool (usually happen with unknown blocks)
|
||||||
|
diskCache.wipeCache();
|
||||||
|
|
||||||
await this.$insertUnknownPool();
|
await this.$insertUnknownPool();
|
||||||
|
|
||||||
for (const pool of this.miningPools) {
|
for (const pool of this.miningPools) {
|
||||||
|
@ -110,6 +110,7 @@ class Server {
|
|||||||
|
|
||||||
this.setUpWebsocketHandling();
|
this.setUpWebsocketHandling();
|
||||||
|
|
||||||
|
await poolsUpdater.updatePoolsJson(); // Needs to be done before loading the disk cache because we sometimes wipe it
|
||||||
await syncAssets.syncAssets$();
|
await syncAssets.syncAssets$();
|
||||||
if (config.MEMPOOL.ENABLED) {
|
if (config.MEMPOOL.ENABLED) {
|
||||||
diskCache.loadMempoolCache();
|
diskCache.loadMempoolCache();
|
||||||
@ -168,7 +169,6 @@ class Server {
|
|||||||
logger.debug(msg);
|
logger.debug(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await poolsUpdater.updatePoolsJson();
|
|
||||||
await blocks.$updateBlocks();
|
await blocks.$updateBlocks();
|
||||||
await memPool.$updateMempool();
|
await memPool.$updateMempool();
|
||||||
indexer.$run();
|
indexer.$run();
|
||||||
@ -176,7 +176,14 @@ class Server {
|
|||||||
setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS);
|
setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS);
|
||||||
this.currentBackendRetryInterval = 5;
|
this.currentBackendRetryInterval = 5;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
const loggerMsg = `runMainLoop error: ${(e instanceof Error ? e.message : e)}. Retrying in ${this.currentBackendRetryInterval} sec.`;
|
let loggerMsg = `Exception in runMainUpdateLoop(). Retrying in ${this.currentBackendRetryInterval} sec.`;
|
||||||
|
loggerMsg += ` Reason: ${(e instanceof Error ? e.message : e)}.`;
|
||||||
|
if (e?.stack) {
|
||||||
|
loggerMsg += ` Stack trace: ${e.stack}`;
|
||||||
|
}
|
||||||
|
// When we get a first Exception, only `logger.debug` it and retry after 5 seconds
|
||||||
|
// From the second Exception, `logger.warn` the Exception and increase the retry delay
|
||||||
|
// Maximum retry delay is 60 seconds
|
||||||
if (this.currentBackendRetryInterval > 5) {
|
if (this.currentBackendRetryInterval > 5) {
|
||||||
logger.warn(loggerMsg);
|
logger.warn(loggerMsg);
|
||||||
mempool.setOutOfSync();
|
mempool.setOutOfSync();
|
||||||
@ -196,8 +203,8 @@ class Server {
|
|||||||
try {
|
try {
|
||||||
await fundingTxFetcher.$init();
|
await fundingTxFetcher.$init();
|
||||||
await networkSyncService.$startService();
|
await networkSyncService.$startService();
|
||||||
await forensicsService.$startService();
|
|
||||||
await lightningStatsUpdater.$startService();
|
await lightningStatsUpdater.$startService();
|
||||||
|
await forensicsService.$startService();
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
logger.err(`Nodejs lightning backend crashed. Restarting in 1 minute. Reason: ${(e instanceof Error ? e.message : e)}`);
|
logger.err(`Nodejs lightning backend crashed. Restarting in 1 minute. Reason: ${(e instanceof Error ? e.message : e)}`);
|
||||||
await Common.sleep$(1000 * 60);
|
await Common.sleep$(1000 * 60);
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { IEsploraApi } from './api/bitcoin/esplora-api.interface';
|
import { IEsploraApi } from './api/bitcoin/esplora-api.interface';
|
||||||
import { OrphanedBlock } from './api/chain-tips';
|
import { OrphanedBlock } from './api/chain-tips';
|
||||||
import { HeapNode } from "./utils/pairing-heap";
|
import { HeapNode } from './utils/pairing-heap';
|
||||||
|
|
||||||
export interface PoolTag {
|
export interface PoolTag {
|
||||||
id: number; // mysql row id
|
id: number;
|
||||||
|
uniqueId: number;
|
||||||
name: string;
|
name: string;
|
||||||
link: string;
|
link: string;
|
||||||
regexes: string; // JSON array
|
regexes: string; // JSON array
|
||||||
@ -147,44 +148,44 @@ export interface TransactionStripped {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface BlockExtension {
|
export interface BlockExtension {
|
||||||
totalFees?: number;
|
totalFees: number;
|
||||||
medianFee?: number;
|
medianFee: number; // median fee rate
|
||||||
feeRange?: number[];
|
feeRange: number[]; // fee rate percentiles
|
||||||
reward?: number;
|
reward: number;
|
||||||
coinbaseTx?: TransactionMinerInfo;
|
matchRate: number | null;
|
||||||
matchRate?: number;
|
pool: {
|
||||||
pool?: {
|
id: number; // Note - This is the `unique_id`, not to mix with the auto increment `id`
|
||||||
id: number;
|
|
||||||
name: string;
|
name: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
avgFee?: number;
|
avgFee: number;
|
||||||
avgFeeRate?: number;
|
avgFeeRate: number;
|
||||||
coinbaseRaw?: string;
|
coinbaseRaw: string;
|
||||||
usd?: number | null;
|
orphans: OrphanedBlock[] | null;
|
||||||
medianTimestamp?: number;
|
coinbaseAddress: string | null;
|
||||||
blockTime?: number;
|
coinbaseSignature: string | null;
|
||||||
orphans?: OrphanedBlock[] | null;
|
coinbaseSignatureAscii: string | null;
|
||||||
coinbaseAddress?: string | null;
|
virtualSize: number;
|
||||||
coinbaseSignature?: string | null;
|
avgTxSize: number;
|
||||||
coinbaseSignatureAscii?: string | null;
|
totalInputs: number;
|
||||||
virtualSize?: number;
|
totalOutputs: number;
|
||||||
avgTxSize?: number;
|
totalOutputAmt: number;
|
||||||
totalInputs?: number;
|
medianFeeAmt: number | null; // median fee in sats
|
||||||
totalOutputs?: number;
|
feePercentiles: number[] | null, // fee percentiles in sats
|
||||||
totalOutputAmt?: number;
|
segwitTotalTxs: number;
|
||||||
medianFeeAmt?: number | null;
|
segwitTotalSize: number;
|
||||||
feePercentiles?: number[] | null,
|
segwitTotalWeight: number;
|
||||||
segwitTotalTxs?: number;
|
header: string;
|
||||||
segwitTotalSize?: number;
|
utxoSetChange: number;
|
||||||
segwitTotalWeight?: number;
|
|
||||||
header?: string;
|
|
||||||
utxoSetChange?: number;
|
|
||||||
// Requires coinstatsindex, will be set to NULL otherwise
|
// Requires coinstatsindex, will be set to NULL otherwise
|
||||||
utxoSetSize?: number | null;
|
utxoSetSize: number | null;
|
||||||
totalInputAmt?: number | null;
|
totalInputAmt: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: Everything that is added in here will be automatically returned through
|
||||||
|
* /api/v1/block and /api/v1/blocks APIs
|
||||||
|
*/
|
||||||
export interface BlockExtended extends IEsploraApi.Block {
|
export interface BlockExtended extends IEsploraApi.Block {
|
||||||
extras: BlockExtension;
|
extras: BlockExtension;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { BlockExtended, BlockPrice } from '../mempool.interfaces';
|
import { BlockExtended, BlockExtension, BlockPrice } from '../mempool.interfaces';
|
||||||
import DB from '../database';
|
import DB from '../database';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import { Common } from '../api/common';
|
import { Common } from '../api/common';
|
||||||
import { prepareBlock } from '../utils/blocks-utils';
|
|
||||||
import PoolsRepository from './PoolsRepository';
|
import PoolsRepository from './PoolsRepository';
|
||||||
import HashratesRepository from './HashratesRepository';
|
import HashratesRepository from './HashratesRepository';
|
||||||
import { escape } from 'mysql2';
|
import { escape } from 'mysql2';
|
||||||
@ -10,6 +9,51 @@ import BlocksSummariesRepository from './BlocksSummariesRepository';
|
|||||||
import DifficultyAdjustmentsRepository from './DifficultyAdjustmentsRepository';
|
import DifficultyAdjustmentsRepository from './DifficultyAdjustmentsRepository';
|
||||||
import bitcoinClient from '../api/bitcoin/bitcoin-client';
|
import bitcoinClient from '../api/bitcoin/bitcoin-client';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
|
import chainTips from '../api/chain-tips';
|
||||||
|
import blocks from '../api/blocks';
|
||||||
|
import BlocksAuditsRepository from './BlocksAuditsRepository';
|
||||||
|
|
||||||
|
const BLOCK_DB_FIELDS = `
|
||||||
|
blocks.hash AS id,
|
||||||
|
blocks.height,
|
||||||
|
blocks.version,
|
||||||
|
UNIX_TIMESTAMP(blocks.blockTimestamp) AS timestamp,
|
||||||
|
blocks.bits,
|
||||||
|
blocks.nonce,
|
||||||
|
blocks.difficulty,
|
||||||
|
blocks.merkle_root,
|
||||||
|
blocks.tx_count,
|
||||||
|
blocks.size,
|
||||||
|
blocks.weight,
|
||||||
|
blocks.previous_block_hash AS previousblockhash,
|
||||||
|
UNIX_TIMESTAMP(blocks.median_timestamp) AS mediantime,
|
||||||
|
blocks.fees AS totalFees,
|
||||||
|
blocks.median_fee AS medianFee,
|
||||||
|
blocks.fee_span AS feeRange,
|
||||||
|
blocks.reward,
|
||||||
|
pools.unique_id AS poolId,
|
||||||
|
pools.name AS poolName,
|
||||||
|
pools.slug AS poolSlug,
|
||||||
|
blocks.avg_fee AS avgFee,
|
||||||
|
blocks.avg_fee_rate AS avgFeeRate,
|
||||||
|
blocks.coinbase_raw AS coinbaseRaw,
|
||||||
|
blocks.coinbase_address AS coinbaseAddress,
|
||||||
|
blocks.coinbase_signature AS coinbaseSignature,
|
||||||
|
blocks.coinbase_signature_ascii AS coinbaseSignatureAscii,
|
||||||
|
blocks.avg_tx_size AS avgTxSize,
|
||||||
|
blocks.total_inputs AS totalInputs,
|
||||||
|
blocks.total_outputs AS totalOutputs,
|
||||||
|
blocks.total_output_amt AS totalOutputAmt,
|
||||||
|
blocks.median_fee_amt AS medianFeeAmt,
|
||||||
|
blocks.fee_percentiles AS feePercentiles,
|
||||||
|
blocks.segwit_total_txs AS segwitTotalTxs,
|
||||||
|
blocks.segwit_total_size AS segwitTotalSize,
|
||||||
|
blocks.segwit_total_weight AS segwitTotalWeight,
|
||||||
|
blocks.header,
|
||||||
|
blocks.utxoset_change AS utxoSetChange,
|
||||||
|
blocks.utxoset_size AS utxoSetSize,
|
||||||
|
blocks.total_input_amt AS totalInputAmts
|
||||||
|
`;
|
||||||
|
|
||||||
class BlocksRepository {
|
class BlocksRepository {
|
||||||
/**
|
/**
|
||||||
@ -44,6 +88,11 @@ class BlocksRepository {
|
|||||||
?, ?
|
?, ?
|
||||||
)`;
|
)`;
|
||||||
|
|
||||||
|
const poolDbId = await PoolsRepository.$getPoolByUniqueId(block.extras.pool.id);
|
||||||
|
if (!poolDbId) {
|
||||||
|
throw Error(`Could not find a mining pool with the unique_id = ${block.extras.pool.id}. This error should never be printed.`);
|
||||||
|
}
|
||||||
|
|
||||||
const params: any[] = [
|
const params: any[] = [
|
||||||
block.height,
|
block.height,
|
||||||
block.id,
|
block.id,
|
||||||
@ -53,7 +102,7 @@ class BlocksRepository {
|
|||||||
block.tx_count,
|
block.tx_count,
|
||||||
block.extras.coinbaseRaw,
|
block.extras.coinbaseRaw,
|
||||||
block.difficulty,
|
block.difficulty,
|
||||||
block.extras.pool?.id, // Should always be set to something
|
poolDbId.id,
|
||||||
block.extras.totalFees,
|
block.extras.totalFees,
|
||||||
JSON.stringify(block.extras.feeRange),
|
JSON.stringify(block.extras.feeRange),
|
||||||
block.extras.medianFee,
|
block.extras.medianFee,
|
||||||
@ -65,7 +114,7 @@ class BlocksRepository {
|
|||||||
block.previousblockhash,
|
block.previousblockhash,
|
||||||
block.extras.avgFee,
|
block.extras.avgFee,
|
||||||
block.extras.avgFeeRate,
|
block.extras.avgFeeRate,
|
||||||
block.extras.medianTimestamp,
|
block.mediantime,
|
||||||
block.extras.header,
|
block.extras.header,
|
||||||
block.extras.coinbaseAddress,
|
block.extras.coinbaseAddress,
|
||||||
truncatedCoinbaseSignature,
|
truncatedCoinbaseSignature,
|
||||||
@ -87,9 +136,9 @@ class BlocksRepository {
|
|||||||
await DB.query(query, params);
|
await DB.query(query, params);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
||||||
logger.debug(`$saveBlockInDatabase() - Block ${block.height} has already been indexed, ignoring`);
|
logger.debug(`$saveBlockInDatabase() - Block ${block.height} has already been indexed, ignoring`, logger.tags.mining);
|
||||||
} else {
|
} else {
|
||||||
logger.err('Cannot save indexed block into db. Reason: ' + (e instanceof Error ? e.message : e));
|
logger.err('Cannot save indexed block into db. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -307,34 +356,17 @@ class BlocksRepository {
|
|||||||
/**
|
/**
|
||||||
* Get blocks mined by a specific mining pool
|
* Get blocks mined by a specific mining pool
|
||||||
*/
|
*/
|
||||||
public async $getBlocksByPool(slug: string, startHeight?: number): Promise<object[]> {
|
public async $getBlocksByPool(slug: string, startHeight?: number): Promise<BlockExtended[]> {
|
||||||
const pool = await PoolsRepository.$getPool(slug);
|
const pool = await PoolsRepository.$getPool(slug);
|
||||||
if (!pool) {
|
if (!pool) {
|
||||||
throw new Error('This mining pool does not exist ' + escape(slug));
|
throw new Error('This mining pool does not exist ' + escape(slug));
|
||||||
}
|
}
|
||||||
|
|
||||||
const params: any[] = [];
|
const params: any[] = [];
|
||||||
let query = ` SELECT
|
let query = `
|
||||||
blocks.height,
|
SELECT ${BLOCK_DB_FIELDS}
|
||||||
hash as id,
|
|
||||||
UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp,
|
|
||||||
size,
|
|
||||||
weight,
|
|
||||||
tx_count,
|
|
||||||
coinbase_raw,
|
|
||||||
difficulty,
|
|
||||||
fees,
|
|
||||||
fee_span,
|
|
||||||
median_fee,
|
|
||||||
reward,
|
|
||||||
version,
|
|
||||||
bits,
|
|
||||||
nonce,
|
|
||||||
merkle_root,
|
|
||||||
previous_block_hash as previousblockhash,
|
|
||||||
avg_fee,
|
|
||||||
avg_fee_rate
|
|
||||||
FROM blocks
|
FROM blocks
|
||||||
|
JOIN pools ON blocks.pool_id = pools.id
|
||||||
WHERE pool_id = ?`;
|
WHERE pool_id = ?`;
|
||||||
params.push(pool.id);
|
params.push(pool.id);
|
||||||
|
|
||||||
@ -347,11 +379,11 @@ class BlocksRepository {
|
|||||||
LIMIT 10`;
|
LIMIT 10`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [rows] = await DB.query(query, params);
|
const [rows]: any[] = await DB.query(query, params);
|
||||||
|
|
||||||
const blocks: BlockExtended[] = [];
|
const blocks: BlockExtended[] = [];
|
||||||
for (const block of <object[]>rows) {
|
for (const block of rows) {
|
||||||
blocks.push(prepareBlock(block));
|
blocks.push(await this.formatDbBlockIntoExtendedBlock(block));
|
||||||
}
|
}
|
||||||
|
|
||||||
return blocks;
|
return blocks;
|
||||||
@ -364,32 +396,21 @@ class BlocksRepository {
|
|||||||
/**
|
/**
|
||||||
* Get one block by height
|
* Get one block by height
|
||||||
*/
|
*/
|
||||||
public async $getBlockByHeight(height: number): Promise<object | null> {
|
public async $getBlockByHeight(height: number): Promise<BlockExtended | null> {
|
||||||
try {
|
try {
|
||||||
const [rows]: any[] = await DB.query(`SELECT
|
const [rows]: any[] = await DB.query(`
|
||||||
blocks.*,
|
SELECT ${BLOCK_DB_FIELDS}
|
||||||
hash as id,
|
|
||||||
UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp,
|
|
||||||
UNIX_TIMESTAMP(blocks.median_timestamp) as medianTime,
|
|
||||||
pools.id as pool_id,
|
|
||||||
pools.name as pool_name,
|
|
||||||
pools.link as pool_link,
|
|
||||||
pools.slug as pool_slug,
|
|
||||||
pools.addresses as pool_addresses,
|
|
||||||
pools.regexes as pool_regexes,
|
|
||||||
previous_block_hash as previousblockhash
|
|
||||||
FROM blocks
|
FROM blocks
|
||||||
JOIN pools ON blocks.pool_id = pools.id
|
JOIN pools ON blocks.pool_id = pools.id
|
||||||
WHERE blocks.height = ${height}
|
WHERE blocks.height = ?`,
|
||||||
`);
|
[height]
|
||||||
|
);
|
||||||
|
|
||||||
if (rows.length <= 0) {
|
if (rows.length <= 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
rows[0].fee_span = JSON.parse(rows[0].fee_span);
|
return await this.formatDbBlockIntoExtendedBlock(rows[0]);
|
||||||
rows[0].fee_percentiles = JSON.parse(rows[0].fee_percentiles);
|
|
||||||
return rows[0];
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err(`Cannot get indexed block ${height}. Reason: ` + (e instanceof Error ? e.message : e));
|
logger.err(`Cannot get indexed block ${height}. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
throw e;
|
throw e;
|
||||||
@ -402,10 +423,7 @@ class BlocksRepository {
|
|||||||
public async $getBlockByHash(hash: string): Promise<object | null> {
|
public async $getBlockByHash(hash: string): Promise<object | null> {
|
||||||
try {
|
try {
|
||||||
const query = `
|
const query = `
|
||||||
SELECT *, blocks.height, UNIX_TIMESTAMP(blocks.blockTimestamp) as blockTimestamp, hash as id,
|
SELECT ${BLOCK_DB_FIELDS}
|
||||||
pools.id as pool_id, pools.name as pool_name, pools.link as pool_link, pools.slug as pool_slug,
|
|
||||||
pools.addresses as pool_addresses, pools.regexes as pool_regexes,
|
|
||||||
previous_block_hash as previousblockhash
|
|
||||||
FROM blocks
|
FROM blocks
|
||||||
JOIN pools ON blocks.pool_id = pools.id
|
JOIN pools ON blocks.pool_id = pools.id
|
||||||
WHERE hash = ?;
|
WHERE hash = ?;
|
||||||
@ -415,9 +433,8 @@ class BlocksRepository {
|
|||||||
if (rows.length <= 0) {
|
if (rows.length <= 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
rows[0].fee_span = JSON.parse(rows[0].fee_span);
|
return await this.formatDbBlockIntoExtendedBlock(rows[0]);
|
||||||
return rows[0];
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err(`Cannot get indexed block ${hash}. Reason: ` + (e instanceof Error ? e.message : e));
|
logger.err(`Cannot get indexed block ${hash}. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
throw e;
|
throw e;
|
||||||
@ -508,8 +525,15 @@ class BlocksRepository {
|
|||||||
public async $validateChain(): Promise<boolean> {
|
public async $validateChain(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const start = new Date().getTime();
|
const start = new Date().getTime();
|
||||||
const [blocks]: any[] = await DB.query(`SELECT height, hash, previous_block_hash,
|
const [blocks]: any[] = await DB.query(`
|
||||||
UNIX_TIMESTAMP(blockTimestamp) as timestamp FROM blocks ORDER BY height`);
|
SELECT
|
||||||
|
height,
|
||||||
|
hash,
|
||||||
|
previous_block_hash,
|
||||||
|
UNIX_TIMESTAMP(blockTimestamp) AS timestamp
|
||||||
|
FROM blocks
|
||||||
|
ORDER BY height
|
||||||
|
`);
|
||||||
|
|
||||||
let partialMsg = false;
|
let partialMsg = false;
|
||||||
let idx = 1;
|
let idx = 1;
|
||||||
@ -833,6 +857,95 @@ class BlocksRepository {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a mysql row block into a BlockExtended. Note that you
|
||||||
|
* must provide the correct field into dbBlk object param
|
||||||
|
*
|
||||||
|
* @param dbBlk
|
||||||
|
*/
|
||||||
|
private async formatDbBlockIntoExtendedBlock(dbBlk: any): Promise<BlockExtended> {
|
||||||
|
const blk: Partial<BlockExtended> = {};
|
||||||
|
const extras: Partial<BlockExtension> = {};
|
||||||
|
|
||||||
|
// IEsploraApi.Block
|
||||||
|
blk.id = dbBlk.id;
|
||||||
|
blk.height = dbBlk.height;
|
||||||
|
blk.version = dbBlk.version;
|
||||||
|
blk.timestamp = dbBlk.timestamp;
|
||||||
|
blk.bits = dbBlk.bits;
|
||||||
|
blk.nonce = dbBlk.nonce;
|
||||||
|
blk.difficulty = dbBlk.difficulty;
|
||||||
|
blk.merkle_root = dbBlk.merkle_root;
|
||||||
|
blk.tx_count = dbBlk.tx_count;
|
||||||
|
blk.size = dbBlk.size;
|
||||||
|
blk.weight = dbBlk.weight;
|
||||||
|
blk.previousblockhash = dbBlk.previousblockhash;
|
||||||
|
blk.mediantime = dbBlk.mediantime;
|
||||||
|
|
||||||
|
// BlockExtension
|
||||||
|
extras.totalFees = dbBlk.totalFees;
|
||||||
|
extras.medianFee = dbBlk.medianFee;
|
||||||
|
extras.feeRange = JSON.parse(dbBlk.feeRange);
|
||||||
|
extras.reward = dbBlk.reward;
|
||||||
|
extras.pool = {
|
||||||
|
id: dbBlk.poolId,
|
||||||
|
name: dbBlk.poolName,
|
||||||
|
slug: dbBlk.poolSlug,
|
||||||
|
};
|
||||||
|
extras.avgFee = dbBlk.avgFee;
|
||||||
|
extras.avgFeeRate = dbBlk.avgFeeRate;
|
||||||
|
extras.coinbaseRaw = dbBlk.coinbaseRaw;
|
||||||
|
extras.coinbaseAddress = dbBlk.coinbaseAddress;
|
||||||
|
extras.coinbaseSignature = dbBlk.coinbaseSignature;
|
||||||
|
extras.coinbaseSignatureAscii = dbBlk.coinbaseSignatureAscii;
|
||||||
|
extras.avgTxSize = dbBlk.avgTxSize;
|
||||||
|
extras.totalInputs = dbBlk.totalInputs;
|
||||||
|
extras.totalOutputs = dbBlk.totalOutputs;
|
||||||
|
extras.totalOutputAmt = dbBlk.totalOutputAmt;
|
||||||
|
extras.medianFeeAmt = dbBlk.medianFeeAmt;
|
||||||
|
extras.feePercentiles = JSON.parse(dbBlk.feePercentiles);
|
||||||
|
extras.segwitTotalTxs = dbBlk.segwitTotalTxs;
|
||||||
|
extras.segwitTotalSize = dbBlk.segwitTotalSize;
|
||||||
|
extras.segwitTotalWeight = dbBlk.segwitTotalWeight;
|
||||||
|
extras.header = dbBlk.header,
|
||||||
|
extras.utxoSetChange = dbBlk.utxoSetChange;
|
||||||
|
extras.utxoSetSize = dbBlk.utxoSetSize;
|
||||||
|
extras.totalInputAmt = dbBlk.totalInputAmt;
|
||||||
|
extras.virtualSize = dbBlk.weight / 4.0;
|
||||||
|
|
||||||
|
// Re-org can happen after indexing so we need to always get the
|
||||||
|
// latest state from core
|
||||||
|
extras.orphans = chainTips.getOrphanedBlocksAtHeight(dbBlk.height);
|
||||||
|
|
||||||
|
// Match rate is not part of the blocks table, but it is part of APIs so we must include it
|
||||||
|
extras.matchRate = null;
|
||||||
|
if (config.MEMPOOL.AUDIT) {
|
||||||
|
const auditScore = await BlocksAuditsRepository.$getBlockAuditScore(dbBlk.id);
|
||||||
|
if (auditScore != null) {
|
||||||
|
extras.matchRate = auditScore.matchRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're missing block summary related field, check if we can populate them on the fly now
|
||||||
|
if (Common.blocksSummariesIndexingEnabled() &&
|
||||||
|
(extras.medianFeeAmt === null || extras.feePercentiles === null))
|
||||||
|
{
|
||||||
|
extras.feePercentiles = await BlocksSummariesRepository.$getFeePercentilesByBlockId(dbBlk.id);
|
||||||
|
if (extras.feePercentiles === null) {
|
||||||
|
const block = await bitcoinClient.getBlock(dbBlk.id, 2);
|
||||||
|
const summary = blocks.summarizeBlock(block);
|
||||||
|
await BlocksSummariesRepository.$saveSummary({ height: block.height, mined: summary });
|
||||||
|
extras.feePercentiles = await BlocksSummariesRepository.$getFeePercentilesByBlockId(dbBlk.id);
|
||||||
|
}
|
||||||
|
if (extras.feePercentiles !== null) {
|
||||||
|
extras.medianFeeAmt = extras.feePercentiles[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blk.extras = <BlockExtension>extras;
|
||||||
|
return <BlockExtended>blk;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new BlocksRepository();
|
export default new BlocksRepository();
|
||||||
|
@ -10,7 +10,7 @@ class PoolsRepository {
|
|||||||
* Get all pools tagging info
|
* Get all pools tagging info
|
||||||
*/
|
*/
|
||||||
public async $getPools(): Promise<PoolTag[]> {
|
public async $getPools(): Promise<PoolTag[]> {
|
||||||
const [rows] = await DB.query('SELECT id, name, addresses, regexes, slug FROM pools;');
|
const [rows] = await DB.query('SELECT id, unique_id as uniqueId, name, addresses, regexes, slug FROM pools');
|
||||||
return <PoolTag[]>rows;
|
return <PoolTag[]>rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,10 +18,10 @@ class PoolsRepository {
|
|||||||
* Get unknown pool tagging info
|
* Get unknown pool tagging info
|
||||||
*/
|
*/
|
||||||
public async $getUnknownPool(): Promise<PoolTag> {
|
public async $getUnknownPool(): Promise<PoolTag> {
|
||||||
let [rows]: any[] = await DB.query('SELECT id, name, slug FROM pools where name = "Unknown"');
|
let [rows]: any[] = await DB.query('SELECT id, unique_id as uniqueId, name, slug FROM pools where name = "Unknown"');
|
||||||
if (rows && rows.length === 0 && config.DATABASE.ENABLED) {
|
if (rows && rows.length === 0 && config.DATABASE.ENABLED) {
|
||||||
await poolsParser.$insertUnknownPool();
|
await poolsParser.$insertUnknownPool();
|
||||||
[rows] = await DB.query('SELECT id, name, slug FROM pools where name = "Unknown"');
|
[rows] = await DB.query('SELECT id, unique_id as uniqueId, name, slug FROM pools where name = "Unknown"');
|
||||||
}
|
}
|
||||||
return <PoolTag>rows[0];
|
return <PoolTag>rows[0];
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ export const MAX_PRICES = {
|
|||||||
|
|
||||||
class PricesRepository {
|
class PricesRepository {
|
||||||
public async $savePrices(time: number, prices: IConversionRates): Promise<void> {
|
public async $savePrices(time: number, prices: IConversionRates): Promise<void> {
|
||||||
if (prices.USD === 0) {
|
if (prices.USD === -1) {
|
||||||
// Some historical price entries have no USD prices, so we just ignore them to avoid future UX issues
|
// Some historical price entries have no USD prices, so we just ignore them to avoid future UX issues
|
||||||
// As of today there are only 4 (on 2013-09-05, 2013-0909, 2013-09-12 and 2013-09-26) so that's fine
|
// As of today there are only 4 (on 2013-09-05, 2013-0909, 2013-09-12 and 2013-09-26) so that's fine
|
||||||
return;
|
return;
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
import { BlockExtended } from '../mempool.interfaces';
|
|
||||||
|
|
||||||
export function prepareBlock(block: any): BlockExtended {
|
|
||||||
return <BlockExtended>{
|
|
||||||
id: block.id ?? block.hash, // hash for indexed block
|
|
||||||
timestamp: block.timestamp ?? block.time ?? block.blockTimestamp, // blockTimestamp for indexed block
|
|
||||||
height: block.height,
|
|
||||||
version: block.version,
|
|
||||||
bits: (typeof block.bits === 'string' ? parseInt(block.bits, 16): block.bits),
|
|
||||||
nonce: block.nonce,
|
|
||||||
difficulty: block.difficulty,
|
|
||||||
merkle_root: block.merkle_root ?? block.merkleroot,
|
|
||||||
tx_count: block.tx_count ?? block.nTx,
|
|
||||||
size: block.size,
|
|
||||||
weight: block.weight,
|
|
||||||
previousblockhash: block.previousblockhash,
|
|
||||||
extras: {
|
|
||||||
coinbaseRaw: block.coinbase_raw ?? block.extras?.coinbaseRaw,
|
|
||||||
medianFee: block.medianFee ?? block.median_fee ?? block.extras?.medianFee,
|
|
||||||
feeRange: block.feeRange ?? block?.extras?.feeRange ?? block.fee_span,
|
|
||||||
reward: block.reward ?? block?.extras?.reward,
|
|
||||||
totalFees: block.totalFees ?? block?.fees ?? block?.extras?.totalFees,
|
|
||||||
avgFee: block?.extras?.avgFee ?? block.avg_fee,
|
|
||||||
avgFeeRate: block?.avgFeeRate ?? block.avg_fee_rate,
|
|
||||||
pool: block?.extras?.pool ?? (block?.pool_id ? {
|
|
||||||
id: block.pool_id,
|
|
||||||
name: block.pool_name,
|
|
||||||
slug: block.pool_slug,
|
|
||||||
} : undefined),
|
|
||||||
usd: block?.extras?.usd ?? block.usd ?? null,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -35,6 +35,7 @@ __AUDIT__=${AUDIT:=false}
|
|||||||
__MAINNET_BLOCK_AUDIT_START_HEIGHT__=${MAINNET_BLOCK_AUDIT_START_HEIGHT:=0}
|
__MAINNET_BLOCK_AUDIT_START_HEIGHT__=${MAINNET_BLOCK_AUDIT_START_HEIGHT:=0}
|
||||||
__TESTNET_BLOCK_AUDIT_START_HEIGHT__=${TESTNET_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}
|
__SIGNET_BLOCK_AUDIT_START_HEIGHT__=${SIGNET_BLOCK_AUDIT_START_HEIGHT:=0}
|
||||||
|
__HISTORICAL_PRICE__=${HISTORICAL_PRICE:=true}
|
||||||
|
|
||||||
# Export as environment variables to be used by envsubst
|
# Export as environment variables to be used by envsubst
|
||||||
export __TESTNET_ENABLED__
|
export __TESTNET_ENABLED__
|
||||||
@ -60,6 +61,7 @@ export __AUDIT__
|
|||||||
export __MAINNET_BLOCK_AUDIT_START_HEIGHT__
|
export __MAINNET_BLOCK_AUDIT_START_HEIGHT__
|
||||||
export __TESTNET_BLOCK_AUDIT_START_HEIGHT__
|
export __TESTNET_BLOCK_AUDIT_START_HEIGHT__
|
||||||
export __SIGNET_BLOCK_AUDIT_START_HEIGHT__
|
export __SIGNET_BLOCK_AUDIT_START_HEIGHT__
|
||||||
|
export __HISTORICAL_PRICE__
|
||||||
|
|
||||||
folder=$(find /var/www/mempool -name "config.js" | xargs dirname)
|
folder=$(find /var/www/mempool -name "config.js" | xargs dirname)
|
||||||
echo ${folder}
|
echo ${folder}
|
||||||
|
1
frontend/.gitignore
vendored
1
frontend/.gitignore
vendored
@ -54,6 +54,7 @@ src/resources/assets-testnet.json
|
|||||||
src/resources/assets-testnet.minimal.json
|
src/resources/assets-testnet.minimal.json
|
||||||
src/resources/pools.json
|
src/resources/pools.json
|
||||||
src/resources/mining-pools/*
|
src/resources/mining-pools/*
|
||||||
|
src/resources/*.mp4
|
||||||
|
|
||||||
# environment config
|
# environment config
|
||||||
mempool-frontend-config.json
|
mempool-frontend-config.json
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { defineConfig } from 'cypress'
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
projectId: 'ry4br7',
|
projectId: 'ry4br7',
|
||||||
@ -12,12 +12,18 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
chromeWebSecurity: false,
|
chromeWebSecurity: false,
|
||||||
e2e: {
|
e2e: {
|
||||||
// We've imported your old cypress plugins here.
|
setupNodeEvents(on: any, config: any) {
|
||||||
// You may want to clean this up later by importing these.
|
const fs = require('fs');
|
||||||
setupNodeEvents(on, config) {
|
const CONFIG_FILE = 'mempool-frontend-config.json';
|
||||||
return require('./cypress/plugins/index.js')(on, config)
|
if (fs.existsSync(CONFIG_FILE)) {
|
||||||
|
let contents = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
||||||
|
config.env.BASE_MODULE = contents.BASE_MODULE ? contents.BASE_MODULE : 'mempool';
|
||||||
|
} else {
|
||||||
|
config.env.BASE_MODULE = 'mempool';
|
||||||
|
}
|
||||||
|
return config;
|
||||||
},
|
},
|
||||||
baseUrl: 'http://localhost:4200',
|
baseUrl: 'http://localhost:4200',
|
||||||
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
describe('Bisq', () => {
|
describe('Bisq', () => {
|
||||||
const baseModule = Cypress.env("BASE_MODULE");
|
const baseModule = Cypress.env('BASE_MODULE');
|
||||||
const basePath = '';
|
const basePath = '';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -20,7 +20,7 @@ describe('Bisq', () => {
|
|||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("transactions", () => {
|
describe('transactions', () => {
|
||||||
it('loads the transactions screen', () => {
|
it('loads the transactions screen', () => {
|
||||||
cy.visit(`${basePath}`);
|
cy.visit(`${basePath}`);
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
@ -30,9 +30,9 @@ describe('Bisq', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const filters = [
|
const filters = [
|
||||||
"Asset listing fee", "Blind vote", "Compensation request",
|
'Asset listing fee', 'Blind vote', 'Compensation request',
|
||||||
"Genesis", "Irregular", "Lockup", "Pay trade fee", "Proof of burn",
|
'Genesis', 'Irregular', 'Lockup', 'Pay trade fee', 'Proof of burn',
|
||||||
"Proposal", "Reimbursement request", "Transfer BSQ", "Unlock", "Vote reveal"
|
'Proposal', 'Reimbursement request', 'Transfer BSQ', 'Unlock', 'Vote reveal'
|
||||||
];
|
];
|
||||||
filters.forEach((filter) => {
|
filters.forEach((filter) => {
|
||||||
it.only(`filters the transaction screen by ${filter}`, () => {
|
it.only(`filters the transaction screen by ${filter}`, () => {
|
||||||
@ -49,7 +49,7 @@ describe('Bisq', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("filters using multiple criteria", () => {
|
it('filters using multiple criteria', () => {
|
||||||
const filters = ['Proposal', 'Lockup', 'Unlock'];
|
const filters = ['Proposal', 'Lockup', 'Unlock'];
|
||||||
cy.visit(`${basePath}/transactions`);
|
cy.visit(`${basePath}/transactions`);
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
describe('Liquid', () => {
|
describe('Liquid', () => {
|
||||||
const baseModule = Cypress.env("BASE_MODULE");
|
const baseModule = Cypress.env('BASE_MODULE');
|
||||||
const basePath = '';
|
const basePath = '';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
describe('Liquid Testnet', () => {
|
describe('Liquid Testnet', () => {
|
||||||
const baseModule = Cypress.env("BASE_MODULE");
|
const baseModule = Cypress.env('BASE_MODULE');
|
||||||
const basePath = '/testnet';
|
const basePath = '/testnet';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { emitMempoolInfo, dropWebSocket } from "../../support/websocket";
|
import { emitMempoolInfo, dropWebSocket } from '../../support/websocket';
|
||||||
|
|
||||||
const baseModule = Cypress.env("BASE_MODULE");
|
const baseModule = Cypress.env('BASE_MODULE');
|
||||||
|
|
||||||
|
|
||||||
//Credit: https://github.com/bahmutov/cypress-examples/blob/6cedb17f83a3bb03ded13cf1d6a3f0656ca2cdf5/docs/recipes/overlapping-elements.md
|
//Credit: https://github.com/bahmutov/cypress-examples/blob/6cedb17f83a3bb03ded13cf1d6a3f0656ca2cdf5/docs/recipes/overlapping-elements.md
|
||||||
@ -339,14 +339,14 @@ describe('Mainnet', () => {
|
|||||||
cy.visit('/');
|
cy.visit('/');
|
||||||
cy.waitForSkeletonGone();
|
cy.waitForSkeletonGone();
|
||||||
|
|
||||||
cy.changeNetwork("testnet");
|
cy.changeNetwork('testnet');
|
||||||
cy.changeNetwork("signet");
|
cy.changeNetwork('signet');
|
||||||
cy.changeNetwork("mainnet");
|
cy.changeNetwork('mainnet');
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('loads the dashboard with the skeleton blocks', () => {
|
it.skip('loads the dashboard with the skeleton blocks', () => {
|
||||||
cy.mockMempoolSocket();
|
cy.mockMempoolSocket();
|
||||||
cy.visit("/");
|
cy.visit('/');
|
||||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
||||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
|
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
|
||||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
|
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const baseModule = Cypress.env("BASE_MODULE");
|
const baseModule = Cypress.env('BASE_MODULE');
|
||||||
|
|
||||||
describe('Mainnet - Mining Features', () => {
|
describe('Mainnet - Mining Features', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { emitMempoolInfo } from "../../support/websocket";
|
import { emitMempoolInfo } from '../../support/websocket';
|
||||||
|
|
||||||
const baseModule = Cypress.env("BASE_MODULE");
|
const baseModule = Cypress.env('BASE_MODULE');
|
||||||
|
|
||||||
describe('Signet', () => {
|
describe('Signet', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -25,7 +25,7 @@ describe('Signet', () => {
|
|||||||
|
|
||||||
it.skip('loads the dashboard with the skeleton blocks', () => {
|
it.skip('loads the dashboard with the skeleton blocks', () => {
|
||||||
cy.mockMempoolSocket();
|
cy.mockMempoolSocket();
|
||||||
cy.visit("/signet");
|
cy.visit('/signet');
|
||||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
||||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
|
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
|
||||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
|
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
|
||||||
@ -35,7 +35,7 @@ describe('Signet', () => {
|
|||||||
|
|
||||||
emitMempoolInfo({
|
emitMempoolInfo({
|
||||||
'params': {
|
'params': {
|
||||||
"network": "signet"
|
'network': 'signet'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { confirmAddress, emitMempoolInfo, sendWsMock, showNewTx, startTrackingAddress } from "../../support/websocket";
|
import { emitMempoolInfo } from '../../support/websocket';
|
||||||
|
|
||||||
const baseModule = Cypress.env("BASE_MODULE");
|
const baseModule = Cypress.env('BASE_MODULE');
|
||||||
|
|
||||||
describe('Testnet', () => {
|
describe('Testnet', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -25,7 +25,7 @@ describe('Testnet', () => {
|
|||||||
|
|
||||||
it.skip('loads the dashboard with the skeleton blocks', () => {
|
it.skip('loads the dashboard with the skeleton blocks', () => {
|
||||||
cy.mockMempoolSocket();
|
cy.mockMempoolSocket();
|
||||||
cy.visit("/testnet");
|
cy.visit('/testnet');
|
||||||
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
cy.get(':nth-child(1) > #bitcoin-block-0').should('be.visible');
|
||||||
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
|
cy.get(':nth-child(2) > #bitcoin-block-0').should('be.visible');
|
||||||
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
|
cy.get(':nth-child(3) > #bitcoin-block-0').should('be.visible');
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
const CONFIG_FILE = 'mempool-frontend-config.json';
|
|
||||||
|
|
||||||
module.exports = (on, config) => {
|
|
||||||
if (fs.existsSync(CONFIG_FILE)) {
|
|
||||||
let contents = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
||||||
config.env.BASE_MODULE = contents.BASE_MODULE ? contents.BASE_MODULE : 'mempool';
|
|
||||||
} else {
|
|
||||||
config.env.BASE_MODULE = 'mempool';
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
@ -21,5 +21,6 @@
|
|||||||
"MAINNET_BLOCK_AUDIT_START_HEIGHT": 0,
|
"MAINNET_BLOCK_AUDIT_START_HEIGHT": 0,
|
||||||
"TESTNET_BLOCK_AUDIT_START_HEIGHT": 0,
|
"TESTNET_BLOCK_AUDIT_START_HEIGHT": 0,
|
||||||
"SIGNET_BLOCK_AUDIT_START_HEIGHT": 0,
|
"SIGNET_BLOCK_AUDIT_START_HEIGHT": 0,
|
||||||
"LIGHTNING": false
|
"LIGHTNING": false,
|
||||||
|
"HISTORICAL_PRICE": true
|
||||||
}
|
}
|
||||||
|
44
frontend/package-lock.json
generated
44
frontend/package-lock.json
generated
@ -58,7 +58,7 @@
|
|||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@cypress/schematic": "^2.4.0",
|
"@cypress/schematic": "^2.4.0",
|
||||||
"cypress": "^12.3.0",
|
"cypress": "^12.7.0",
|
||||||
"cypress-fail-on-console-error": "~4.0.2",
|
"cypress-fail-on-console-error": "~4.0.2",
|
||||||
"cypress-wait-until": "^1.7.2",
|
"cypress-wait-until": "^1.7.2",
|
||||||
"mock-socket": "~9.1.5",
|
"mock-socket": "~9.1.5",
|
||||||
@ -7010,9 +7010,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/cypress": {
|
"node_modules/cypress": {
|
||||||
"version": "12.3.0",
|
"version": "12.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-12.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-12.7.0.tgz",
|
||||||
"integrity": "sha512-ZQNebibi6NBt51TRxRMYKeFvIiQZ01t50HSy7z/JMgRVqBUey3cdjog5MYEbzG6Ktti5ckDt1tfcC47lmFwXkw==",
|
"integrity": "sha512-7rq+nmhzz0u6yabCFyPtADU2OOrYt6pvUau9qV7xyifJ/hnsaw/vkr0tnLlcuuQKUAOC1v1M1e4Z0zG7S0IAvA==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -7033,7 +7033,7 @@
|
|||||||
"commander": "^5.1.0",
|
"commander": "^5.1.0",
|
||||||
"common-tags": "^1.8.0",
|
"common-tags": "^1.8.0",
|
||||||
"dayjs": "^1.10.4",
|
"dayjs": "^1.10.4",
|
||||||
"debug": "^4.3.2",
|
"debug": "^4.3.4",
|
||||||
"enquirer": "^2.3.6",
|
"enquirer": "^2.3.6",
|
||||||
"eventemitter2": "6.4.7",
|
"eventemitter2": "6.4.7",
|
||||||
"execa": "4.1.0",
|
"execa": "4.1.0",
|
||||||
@ -7159,6 +7159,23 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cypress/node_modules/debug": {
|
||||||
|
"version": "4.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cypress/node_modules/execa": {
|
"node_modules/cypress/node_modules/execa": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
|
||||||
@ -22276,9 +22293,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"cypress": {
|
"cypress": {
|
||||||
"version": "12.3.0",
|
"version": "12.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-12.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-12.7.0.tgz",
|
||||||
"integrity": "sha512-ZQNebibi6NBt51TRxRMYKeFvIiQZ01t50HSy7z/JMgRVqBUey3cdjog5MYEbzG6Ktti5ckDt1tfcC47lmFwXkw==",
|
"integrity": "sha512-7rq+nmhzz0u6yabCFyPtADU2OOrYt6pvUau9qV7xyifJ/hnsaw/vkr0tnLlcuuQKUAOC1v1M1e4Z0zG7S0IAvA==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@cypress/request": "^2.88.10",
|
"@cypress/request": "^2.88.10",
|
||||||
@ -22298,7 +22315,7 @@
|
|||||||
"commander": "^5.1.0",
|
"commander": "^5.1.0",
|
||||||
"common-tags": "^1.8.0",
|
"common-tags": "^1.8.0",
|
||||||
"dayjs": "^1.10.4",
|
"dayjs": "^1.10.4",
|
||||||
"debug": "^4.3.2",
|
"debug": "^4.3.4",
|
||||||
"enquirer": "^2.3.6",
|
"enquirer": "^2.3.6",
|
||||||
"eventemitter2": "6.4.7",
|
"eventemitter2": "6.4.7",
|
||||||
"execa": "4.1.0",
|
"execa": "4.1.0",
|
||||||
@ -22382,6 +22399,15 @@
|
|||||||
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
|
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"debug": {
|
||||||
|
"version": "4.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"ms": "2.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"execa": {
|
"execa": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
|
||||||
|
@ -110,7 +110,7 @@
|
|||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@cypress/schematic": "^2.4.0",
|
"@cypress/schematic": "^2.4.0",
|
||||||
"cypress": "^12.3.0",
|
"cypress": "^12.7.0",
|
||||||
"cypress-fail-on-console-error": "~4.0.2",
|
"cypress-fail-on-console-error": "~4.0.2",
|
||||||
"cypress-wait-until": "^1.7.2",
|
"cypress-wait-until": "^1.7.2",
|
||||||
"mock-socket": "~9.1.5",
|
"mock-socket": "~9.1.5",
|
||||||
@ -119,4 +119,4 @@
|
|||||||
"scarfSettings": {
|
"scarfSettings": {
|
||||||
"enabled": false
|
"enabled": false
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -13,19 +13,9 @@
|
|||||||
<p i18n>Our mempool and blockchain explorer for the Bitcoin community, focusing on the transaction fee market and multi-layer ecosystem, completely self-hosted without any trusted third-parties.</p>
|
<p i18n>Our mempool and blockchain explorer for the Bitcoin community, focusing on the transaction fee market and multi-layer ecosystem, completely self-hosted without any trusted third-parties.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="social-icons">
|
<video src="/resources/mempool-promo.mp4" poster="/resources/mempool-promo.jpg" controls loop playsinline [autoplay]="true" [muted]="true"></video>
|
||||||
<a target="_blank" href="https://github.com/mempool/mempool">
|
|
||||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="github" class="svg-inline--fa fa-github fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>
|
|
||||||
</a>
|
|
||||||
<a target="_blank" href="https://twitter.com/mempool">
|
|
||||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="twitter" class="svg-inline--fa fa-twitter fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path></svg>
|
|
||||||
</a>
|
|
||||||
<a target="_blank" href="https://matrix.to/#/#mempool:bitcoin.kyoto">
|
|
||||||
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="matrix" class="svg-inline--fa fa-matrix fa-w-16 fa-4x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1536 1792"><path fill="currentColor" d="M40.467 163.152v1465.696H145.92V1664H0V128h145.92v35.152zm450.757 464.64v74.14h2.069c19.79-28.356 43.717-50.215 71.483-65.575 27.765-15.656 59.963-23.336 96-23.336 34.56 0 66.165 6.795 94.818 20.086 28.652 13.293 50.216 37.22 65.28 70.893 16.246-23.926 38.4-45.194 66.166-63.507 27.766-18.314 60.848-27.472 98.954-27.472 28.948 0 55.828 3.545 80.64 10.635 24.812 7.088 45.785 18.314 63.508 33.968 17.722 15.656 31.31 35.742 41.354 60.85 9.747 25.107 14.768 55.236 14.768 90.683v366.573h-150.35V865.28c0-18.314-.59-35.741-2.068-51.987-1.476-16.247-5.316-30.426-11.52-42.24-6.499-12.112-15.656-21.563-28.062-28.653-12.405-7.088-29.242-10.634-50.214-10.634-21.268 0-38.4 4.135-51.397 12.112-12.997 8.27-23.336 18.608-30.72 31.901-7.386 12.997-12.407 27.765-14.77 44.602-2.363 16.542-3.84 33.379-3.84 50.216v305.133H692.971v-307.2c0-16.247-.294-32.197-1.18-48.149-.591-15.95-3.84-30.424-9.157-44.011-5.317-13.293-14.178-24.223-26.585-32.197-12.406-7.976-30.425-12.112-54.646-12.112-7.088 0-16.542 1.478-28.062 4.726-11.52 3.25-23.04 9.157-33.968 18.02-10.93 8.86-20.383 21.563-28.063 38.103-7.68 16.543-11.52 38.4-11.52 65.28v317.834H349.44V627.792zm1004.309 1001.056V163.152H1390.08V128H1536v1536h-145.92v-35.152z"/></svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="enterprise-sponsor">
|
<div class="enterprise-sponsor" id="enterprise-sponsors">
|
||||||
<h3 i18n="about.sponsors.enterprise.withRocket">Enterprise Sponsors 🚀</h3>
|
<h3 i18n="about.sponsors.enterprise.withRocket">Enterprise Sponsors 🚀</h3>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<a href="https://spiral.xyz/" target="_blank" title="Spiral">
|
<a href="https://spiral.xyz/" target="_blank" title="Spiral">
|
||||||
@ -173,7 +163,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="community-sponsor">
|
<div class="community-sponsor" id="community-sponsors">
|
||||||
<h3 i18n="about.sponsors.withHeart">Community Sponsors ❤️</h3>
|
<h3 i18n="about.sponsors.withHeart">Community Sponsors ❤️</h3>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
@ -187,7 +177,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="community-integrations-sponsor">
|
<div class="community-integrations-sponsor" id="community-integrations">
|
||||||
<h3 i18n="about.community-integrations">Community Integrations</h3>
|
<h3 i18n="about.community-integrations">Community Integrations</h3>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<a href="https://github.com/getumbrel/umbrel" target="_blank" title="Umbrel">
|
<a href="https://github.com/getumbrel/umbrel" target="_blank" title="Umbrel">
|
||||||
@ -281,7 +271,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="alliances">
|
<div class="alliances" id="community-alliances">
|
||||||
<h3 i18n="about.alliances">Community Alliances</h3>
|
<h3 i18n="about.alliances">Community Alliances</h3>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<a href="https://liquid.net/" title="Liquid Network">
|
<a href="https://liquid.net/" title="Liquid Network">
|
||||||
@ -297,7 +287,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-container *ngIf="translators$ | async | keyvalue as translators else loadingSponsors">
|
<ng-container *ngIf="translators$ | async | keyvalue as translators else loadingSponsors">
|
||||||
<div class="project-translators">
|
<div class="project-translators" id="project-translators">
|
||||||
<h3 i18n="about.translators">Project Translators</h3>
|
<h3 i18n="about.translators">Project Translators</h3>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<ng-template ngFor let-translator [ngForOf]="translators">
|
<ng-template ngFor let-translator [ngForOf]="translators">
|
||||||
@ -311,7 +301,7 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="allContributors$ | async as contributors else loadingSponsors">
|
<ng-container *ngIf="allContributors$ | async as contributors else loadingSponsors">
|
||||||
<div class="contributors">
|
<div class="contributors" id="project-contributors">
|
||||||
<h3 i18n="about.contributors">Project Contributors</h3>
|
<h3 i18n="about.contributors">Project Contributors</h3>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<ng-template ngFor let-contributor [ngForOf]="contributors.regular">
|
<ng-template ngFor let-contributor [ngForOf]="contributors.regular">
|
||||||
@ -323,7 +313,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="maintainers" *ngIf="contributors.core.length">
|
<div class="maintainers" *ngIf="contributors.core.length" id="project-members">
|
||||||
<h3 i18n="about.project_members">Project Members</h3>
|
<h3 i18n="about.project_members">Project Members</h3>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<ng-template ngFor let-contributor [ngForOf]="contributors.core">
|
<ng-template ngFor let-contributor [ngForOf]="contributors.core">
|
||||||
@ -336,7 +326,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<div class="maintainers">
|
<div class="maintainers" id="project-maintainers">
|
||||||
<h3 i18n="about.maintainers">Project Maintainers</h3>
|
<h3 i18n="about.maintainers">Project Maintainers</h3>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<a href="https://twitter.com/softsimon_" target="_blank" title="softsimon">
|
<a href="https://twitter.com/softsimon_" target="_blank" title="softsimon">
|
||||||
@ -383,6 +373,17 @@
|
|||||||
<div class="footer-links">
|
<div class="footer-links">
|
||||||
<a href="/3rdpartylicenses.txt">Third-party Licenses</a>
|
<a href="/3rdpartylicenses.txt">Third-party Licenses</a>
|
||||||
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
||||||
|
<div class="social-icons">
|
||||||
|
<a target="_blank" href="https://github.com/mempool/mempool">
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="github" class="svg-inline--fa fa-github fa-w-16 fa-2x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path fill="currentColor" d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>
|
||||||
|
</a>
|
||||||
|
<a target="_blank" href="https://twitter.com/mempool">
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="twitter" class="svg-inline--fa fa-twitter fa-w-16 fa-2x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path></svg>
|
||||||
|
</a>
|
||||||
|
<a target="_blank" href="https://matrix.to/#/#mempool:bitcoin.kyoto">
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fab" data-icon="matrix" class="svg-inline--fa fa-matrix fa-w-16 fa-2x" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1536 1792"><path fill="currentColor" d="M40.467 163.152v1465.696H145.92V1664H0V128h145.92v35.152zm450.757 464.64v74.14h2.069c19.79-28.356 43.717-50.215 71.483-65.575 27.765-15.656 59.963-23.336 96-23.336 34.56 0 66.165 6.795 94.818 20.086 28.652 13.293 50.216 37.22 65.28 70.893 16.246-23.926 38.4-45.194 66.166-63.507 27.766-18.314 60.848-27.472 98.954-27.472 28.948 0 55.828 3.545 80.64 10.635 24.812 7.088 45.785 18.314 63.508 33.968 17.722 15.656 31.31 35.742 41.354 60.85 9.747 25.107 14.768 55.236 14.768 90.683v366.573h-150.35V865.28c0-18.314-.59-35.741-2.068-51.987-1.476-16.247-5.316-30.426-11.52-42.24-6.499-12.112-15.656-21.563-28.062-28.653-12.405-7.088-29.242-10.634-50.214-10.634-21.268 0-38.4 4.135-51.397 12.112-12.997 8.27-23.336 18.608-30.72 31.901-7.386 12.997-12.407 27.765-14.77 44.602-2.363 16.542-3.84 33.379-3.84 50.216v305.133H692.971v-307.2c0-16.247-.294-32.197-1.18-48.149-.591-15.95-3.84-30.424-9.157-44.011-5.317-13.293-14.178-24.223-26.585-32.197-12.406-7.976-30.425-12.112-54.646-12.112-7.088 0-16.542 1.478-28.062 4.726-11.52 3.25-23.04 9.157-33.968 18.02-10.93 8.86-20.383 21.563-28.063 38.103-7.68 16.543-11.52 38.4-11.52 65.28v317.834H349.44V627.792zm1004.309 1001.056V163.152H1390.08V128H1536v1536h-145.92v-35.152z"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer-version" *ngIf="officialMempoolSpace">
|
<div class="footer-version" *ngIf="officialMempoolSpace">
|
||||||
|
@ -34,6 +34,13 @@
|
|||||||
padding: 10px 15px 15px;
|
padding: 10px 15px 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
width: 640px;
|
||||||
|
height: 360px;
|
||||||
|
max-width: 90%;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.social-icons {
|
.social-icons {
|
||||||
a {
|
a {
|
||||||
margin: auto 10px;
|
margin: auto 10px;
|
||||||
@ -46,6 +53,7 @@
|
|||||||
.maintainers {
|
.maintainers {
|
||||||
margin-top: 68px;
|
margin-top: 68px;
|
||||||
margin-bottom: 68px;
|
margin-bottom: 68px;
|
||||||
|
scroll-margin: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maintainers {
|
.maintainers {
|
||||||
@ -117,6 +125,7 @@
|
|||||||
.project-translators,
|
.project-translators,
|
||||||
.community-integrations-sponsor,
|
.community-integrations-sponsor,
|
||||||
.maintainers {
|
.maintainers {
|
||||||
|
scroll-margin: 30px;
|
||||||
.wrapper {
|
.wrapper {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
a {
|
a {
|
||||||
@ -186,6 +195,11 @@
|
|||||||
margin: 20px auto 30px;
|
margin: 20px auto 30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.social-icons {
|
||||||
|
a {
|
||||||
|
margin: 45px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-version {
|
.footer-version {
|
||||||
|
@ -5,9 +5,10 @@ import { StateService } from '../../services/state.service';
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { IBackendInfo } from '../../interfaces/websocket.interface';
|
import { IBackendInfo } from '../../interfaces/websocket.interface';
|
||||||
import { Router } from '@angular/router';
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
import { map } from 'rxjs/operators';
|
import { map, tap } from 'rxjs/operators';
|
||||||
import { ITranslators } from '../../interfaces/node-api.interface';
|
import { ITranslators } from '../../interfaces/node-api.interface';
|
||||||
|
import { DOCUMENT } from '@angular/common';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-about',
|
selector: 'app-about',
|
||||||
@ -31,7 +32,9 @@ export class AboutComponent implements OnInit {
|
|||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
private route: ActivatedRoute,
|
||||||
@Inject(LOCALE_ID) public locale: string,
|
@Inject(LOCALE_ID) public locale: string,
|
||||||
|
@Inject(DOCUMENT) private document: Document,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -39,17 +42,21 @@ export class AboutComponent implements OnInit {
|
|||||||
this.seoService.setTitle($localize`:@@004b222ff9ef9dd4771b777950ca1d0e4cd4348a:About`);
|
this.seoService.setTitle($localize`:@@004b222ff9ef9dd4771b777950ca1d0e4cd4348a:About`);
|
||||||
this.websocketService.want(['blocks']);
|
this.websocketService.want(['blocks']);
|
||||||
|
|
||||||
this.sponsors$ = this.apiService.getDonation$();
|
this.sponsors$ = this.apiService.getDonation$()
|
||||||
|
.pipe(
|
||||||
|
tap(() => this.goToAnchor())
|
||||||
|
);
|
||||||
this.translators$ = this.apiService.getTranslators$()
|
this.translators$ = this.apiService.getTranslators$()
|
||||||
.pipe(
|
.pipe(
|
||||||
map((translators) => {
|
map((translators) => {
|
||||||
for (const t in translators) {
|
for (const t in translators) {
|
||||||
if (translators[t] === '') {
|
if (translators[t] === '') {
|
||||||
delete translators[t]
|
delete translators[t];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return translators;
|
return translators;
|
||||||
})
|
}),
|
||||||
|
tap(() => this.goToAnchor())
|
||||||
);
|
);
|
||||||
this.allContributors$ = this.apiService.getContributor$().pipe(
|
this.allContributors$ = this.apiService.getContributor$().pipe(
|
||||||
map((contributors) => {
|
map((contributors) => {
|
||||||
@ -57,9 +64,24 @@ export class AboutComponent implements OnInit {
|
|||||||
regular: contributors.filter((user) => !user.core_constributor),
|
regular: contributors.filter((user) => !user.core_constributor),
|
||||||
core: contributors.filter((user) => user.core_constributor),
|
core: contributors.filter((user) => user.core_constributor),
|
||||||
};
|
};
|
||||||
})
|
}),
|
||||||
|
tap(() => this.goToAnchor())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.goToAnchor();
|
||||||
|
}
|
||||||
|
|
||||||
|
goToAnchor() {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.route.snapshot.fragment) {
|
||||||
|
if (this.document.getElementById(this.route.snapshot.fragment)) {
|
||||||
|
this.document.getElementById(this.route.snapshot.fragment).scrollIntoView({behavior: 'smooth'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
|
||||||
sponsor(): void {
|
sponsor(): void {
|
||||||
if (this.officialMempoolSpace && this.stateService.env.BASE_MODULE === 'mempool') {
|
if (this.officialMempoolSpace && this.stateService.env.BASE_MODULE === 'mempool') {
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="td-width" i18n="dashboard.latest-transactions.amount">Amount</td>
|
<td class="td-width" i18n="dashboard.latest-transactions.amount">Amount</td>
|
||||||
<td><app-amount [blockConversion]="blockConversion" [satoshis]="value"></app-amount></td>
|
<td><app-amount [blockConversion]="blockConversion" [satoshis]="value" [noFiat]="true"></app-amount></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
|
<td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
<div class="blockchain-wrapper" [class.time-ltr]="timeLtr" [class.time-rtl]="!timeLtr">
|
<div class="blockchain-wrapper" [class.time-ltr]="timeLtr" [class.time-rtl]="!timeLtr">
|
||||||
<div id="blockchain-container" [dir]="timeLtr ? 'rtl' : 'ltr'" #blockchainContainer
|
<div id="blockchain-container" [dir]="timeLtr ? 'rtl' : 'ltr'" #blockchainContainer
|
||||||
(mousedown)="onMouseDown($event)"
|
(mousedown)="onMouseDown($event)"
|
||||||
|
(pointerdown)="onPointerDown($event)"
|
||||||
|
(touchmove)="onTouchMove($event)"
|
||||||
(dragstart)="onDragStart($event)"
|
(dragstart)="onDragStart($event)"
|
||||||
(scroll)="onScroll($event)"
|
(scroll)="onScroll($event)"
|
||||||
>
|
>
|
||||||
|
@ -27,6 +27,7 @@ export class StartComponent implements OnInit, OnDestroy {
|
|||||||
@ViewChild('blockchainContainer') blockchainContainer: ElementRef;
|
@ViewChild('blockchainContainer') blockchainContainer: ElementRef;
|
||||||
|
|
||||||
isMobile: boolean = false;
|
isMobile: boolean = false;
|
||||||
|
isiOS: boolean = false;
|
||||||
blockWidth = 155;
|
blockWidth = 155;
|
||||||
dynamicBlocksAmount: number = 8;
|
dynamicBlocksAmount: number = 8;
|
||||||
blockCount: number = 0;
|
blockCount: number = 0;
|
||||||
@ -40,7 +41,9 @@ export class StartComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
) { }
|
) {
|
||||||
|
this.isiOS = ['iPhone','iPod','iPad'].includes((navigator as any)?.userAgentData?.platform || navigator.platform);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.firstPageWidth = 40 + (this.blockWidth * this.dynamicBlocksAmount);
|
this.firstPageWidth = 40 + (this.blockWidth * this.dynamicBlocksAmount);
|
||||||
@ -135,9 +138,21 @@ export class StartComponent implements OnInit, OnDestroy {
|
|||||||
this.mouseDragStartX = event.clientX;
|
this.mouseDragStartX = event.clientX;
|
||||||
this.blockchainScrollLeftInit = this.blockchainContainer.nativeElement.scrollLeft;
|
this.blockchainScrollLeftInit = this.blockchainContainer.nativeElement.scrollLeft;
|
||||||
}
|
}
|
||||||
|
onPointerDown(event: PointerEvent) {
|
||||||
|
if (this.isiOS) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.onMouseDown(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
onDragStart(event: MouseEvent) { // Ignore Firefox annoying default drag behavior
|
onDragStart(event: MouseEvent) { // Ignore Firefox annoying default drag behavior
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
onTouchMove(event: TouchEvent) {
|
||||||
|
// disable native scrolling on iOS
|
||||||
|
if (this.isiOS) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We're catching the whole page event here because we still want to scroll blocks
|
// We're catching the whole page event here because we still want to scroll blocks
|
||||||
// even if the mouse leave the blockchain blocks container. Same idea for mouseup below.
|
// even if the mouse leave the blockchain blocks container. Same idea for mouseup below.
|
||||||
@ -154,6 +169,20 @@ export class StartComponent implements OnInit, OnDestroy {
|
|||||||
this.mouseDragStartX = null;
|
this.mouseDragStartX = null;
|
||||||
this.stateService.setBlockScrollingInProgress(false);
|
this.stateService.setBlockScrollingInProgress(false);
|
||||||
}
|
}
|
||||||
|
@HostListener('document:pointermove', ['$event'])
|
||||||
|
onPointerMove(event: PointerEvent): void {
|
||||||
|
if (this.isiOS) {
|
||||||
|
this.onMouseMove(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@HostListener('document:pointerup', [])
|
||||||
|
@HostListener('document:pointercancel', [])
|
||||||
|
onPointerUp() {
|
||||||
|
if (this.isiOS) {
|
||||||
|
this.onMouseUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
onScroll(e) {
|
onScroll(e) {
|
||||||
const middlePage = this.pageIndex === 0 ? this.pages[0] : this.pages[1];
|
const middlePage = this.pageIndex === 0 ? this.pages[0] : this.pages[1];
|
||||||
|
@ -2907,6 +2907,219 @@ export const restApiDocsData = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
...
|
...
|
||||||
|
]`
|
||||||
|
},
|
||||||
|
codeSampleLiquid: emptyCodeSample,
|
||||||
|
codeSampleLiquidTestnet: emptyCodeSample,
|
||||||
|
codeSampleBisq: emptyCodeSample,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "endpoint",
|
||||||
|
category: "blocks",
|
||||||
|
httpRequestMethod: "GET",
|
||||||
|
fragment: "get-blocks-bulk",
|
||||||
|
title: "GET Blocks (Bulk)",
|
||||||
|
description: {
|
||||||
|
default: "<p>Returns details on the range of blocks between <code>:minHeight</code> and <code>:maxHeight</code>, inclusive, up to 10 blocks. If <code>:maxHeight</code> is not specified, it defaults to the current tip.</p><p>To return data for more than 10 blocks, consider becoming an <a href='/enterprise'>enterprise sponsor</a>.</p>"
|
||||||
|
},
|
||||||
|
urlString: "/v1/blocks-bulk/:minHeight[/:maxHeight]",
|
||||||
|
showConditions: bitcoinNetworks,
|
||||||
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
|
codeExample: {
|
||||||
|
default: {
|
||||||
|
codeTemplate: {
|
||||||
|
curl: `/api/v1/blocks-bulk/%{1}/%{2}`,
|
||||||
|
commonJS: ``,
|
||||||
|
},
|
||||||
|
codeSampleMainnet: {
|
||||||
|
esModule: [],
|
||||||
|
commonJS: [],
|
||||||
|
curl: [100000,100000],
|
||||||
|
response: `[
|
||||||
|
{
|
||||||
|
"height": 100000,
|
||||||
|
"hash": "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506",
|
||||||
|
"timestamp": 1293623863,
|
||||||
|
"median_timestamp": 1293622620,
|
||||||
|
"previous_block_hash": "000000000002d01c1fccc21636b607dfd930d31d01c3a62104612a1719011250",
|
||||||
|
"difficulty": 14484.1623612254,
|
||||||
|
"header": "0100000050120119172a610421a6c3011dd330d9df07b63616c2cc1f1cd00200000000006657a9252aacd5c0b2940996ecff952228c3067cc38d4885efb5a4ac4247e9f337221b4d4c86041b0f2b5710",
|
||||||
|
"version": 1,
|
||||||
|
"bits": 453281356,
|
||||||
|
"nonce": 274148111,
|
||||||
|
"size": 957,
|
||||||
|
"weight": 3828,
|
||||||
|
"tx_count": 4,
|
||||||
|
"merkle_root": "f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766",
|
||||||
|
"reward": 5000000000,
|
||||||
|
"total_fee_amt": 0,
|
||||||
|
"avg_fee_amt": 0,
|
||||||
|
"median_fee_amt": 0,
|
||||||
|
"fee_amt_percentiles": {
|
||||||
|
"min": 0,
|
||||||
|
"perc_10": 0,
|
||||||
|
"perc_25": 0,
|
||||||
|
"perc_50": 0,
|
||||||
|
"perc_75": 0,
|
||||||
|
"perc_90": 0,
|
||||||
|
"max": 0
|
||||||
|
},
|
||||||
|
"avg_fee_rate": 0,
|
||||||
|
"median_fee_rate": 0,
|
||||||
|
"fee_rate_percentiles": {
|
||||||
|
"min": 0,
|
||||||
|
"perc_10": 0,
|
||||||
|
"perc_25": 0,
|
||||||
|
"perc_50": 0,
|
||||||
|
"perc_75": 0,
|
||||||
|
"perc_90": 0,
|
||||||
|
"max": 0
|
||||||
|
},
|
||||||
|
"total_inputs": 3,
|
||||||
|
"total_input_amt": 5301000000,
|
||||||
|
"total_outputs": 6,
|
||||||
|
"total_output_amt": 5301000000,
|
||||||
|
"segwit_total_txs": 0,
|
||||||
|
"segwit_total_size": 0,
|
||||||
|
"segwit_total_weight": 0,
|
||||||
|
"avg_tx_size": 185.25,
|
||||||
|
"utxoset_change": 3,
|
||||||
|
"utxoset_size": 71888,
|
||||||
|
"coinbase_raw": "044c86041b020602",
|
||||||
|
"coinbase_address": null,
|
||||||
|
"coinbase_signature": "OP_PUSHBYTES_65 041b0e8c2567c12536aa13357b79a073dc4444acb83c4ec7a0e2f99dd7457516c5817242da796924ca4e99947d087fedf9ce467cb9f7c6287078f801df276fdf84 OP_CHECKSIG",
|
||||||
|
"coinbase_signature_ascii": "\u0004L<34>\u0004\u001b\u0002\u0006\u0002",
|
||||||
|
"pool_slug": "unknown",
|
||||||
|
"orphans": []
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
},
|
||||||
|
codeSampleTestnet: {
|
||||||
|
esModule: [],
|
||||||
|
commonJS: [],
|
||||||
|
curl: [100000,100000],
|
||||||
|
response: `[
|
||||||
|
{
|
||||||
|
"height": 100000,
|
||||||
|
"hash": "00000000009e2958c15ff9290d571bf9459e93b19765c6801ddeccadbb160a1e",
|
||||||
|
"timestamp": 1376123972,
|
||||||
|
"median_timestamp": 1677396660,
|
||||||
|
"previous_block_hash": "000000004956cc2edd1a8caa05eacfa3c69f4c490bfc9ace820257834115ab35",
|
||||||
|
"difficulty": 271.7576739288896,
|
||||||
|
"header": "0200000035ab154183570282ce9afc0b494c9fc6a3cfea05aa8c1add2ecc56490000000038ba3d78e4500a5a7570dbe61960398add4410d278b21cd9708e6d9743f374d544fc055227f1001c29c1ea3b",
|
||||||
|
"version": 2,
|
||||||
|
"bits": 469823783,
|
||||||
|
"nonce": 1005240617,
|
||||||
|
"size": 221,
|
||||||
|
"weight": 884,
|
||||||
|
"tx_count": 1,
|
||||||
|
"merkle_root": "d574f343976d8e70d91cb278d21044dd8a396019e6db70755a0a50e4783dba38",
|
||||||
|
"reward": 5000000000,
|
||||||
|
"total_fee_amt": 0,
|
||||||
|
"avg_fee_amt": 0,
|
||||||
|
"median_fee_amt": 0,
|
||||||
|
"fee_amt_percentiles": {
|
||||||
|
"min": 0,
|
||||||
|
"perc_10": 0,
|
||||||
|
"perc_25": 0,
|
||||||
|
"perc_50": 0,
|
||||||
|
"perc_75": 0,
|
||||||
|
"perc_90": 0,
|
||||||
|
"max": 0
|
||||||
|
},
|
||||||
|
"avg_fee_rate": 0,
|
||||||
|
"median_fee_rate": 0,
|
||||||
|
"fee_rate_percentiles": {
|
||||||
|
"min": 0,
|
||||||
|
"perc_10": 0,
|
||||||
|
"perc_25": 0,
|
||||||
|
"perc_50": 0,
|
||||||
|
"perc_75": 0,
|
||||||
|
"perc_90": 0,
|
||||||
|
"max": 0
|
||||||
|
},
|
||||||
|
"total_inputs": 0,
|
||||||
|
"total_input_amt": null,
|
||||||
|
"total_outputs": 1,
|
||||||
|
"total_output_amt": 0,
|
||||||
|
"segwit_total_txs": 0,
|
||||||
|
"segwit_total_size": 0,
|
||||||
|
"segwit_total_weight": 0,
|
||||||
|
"avg_tx_size": 0,
|
||||||
|
"utxoset_change": 1,
|
||||||
|
"utxoset_size": null,
|
||||||
|
"coinbase_raw": "03a08601000427f1001c046a510100522cfabe6d6d0000000000000000000068692066726f6d20706f6f6c7365727665726aac1eeeed88",
|
||||||
|
"coinbase_address": "mtkbaiLiUH3fvGJeSzuN3kUgmJzqinLejJ",
|
||||||
|
"coinbase_signature": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 912e2b234f941f30b18afbb4fa46171214bf66c8 OP_EQUALVERIFY OP_CHECKSIG",
|
||||||
|
"coinbase_signature_ascii": "\u0003 <20>\u0001\u0000\u0004'ñ\u0000\u001c\u0004jQ\u0001\u0000R,ú¾mm\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000hi from poolserverj¬\u001eîí<C3AE>",
|
||||||
|
"pool_slug": "unknown",
|
||||||
|
"orphans": []
|
||||||
|
}
|
||||||
|
]`
|
||||||
|
},
|
||||||
|
codeSampleSignet: {
|
||||||
|
esModule: [],
|
||||||
|
commonJS: [],
|
||||||
|
curl: [100000,100000],
|
||||||
|
response: `[
|
||||||
|
{
|
||||||
|
"height": 100000,
|
||||||
|
"hash": "0000008753108390007b3f5c26e5d924191567e147876b84489b0c0cf133a0bf",
|
||||||
|
"timestamp": 1658421183,
|
||||||
|
"median_timestamp": 1658418056,
|
||||||
|
"previous_block_hash": "000000b962a13c3dd3f81917bc8646a0c98224adcd5124026d4fdfcb76a76d30",
|
||||||
|
"difficulty": 0.002781447610743506,
|
||||||
|
"header": "00000020306da776cbdf4f6d022451cdad2482c9a04686bc1719f8d33d3ca162b90000001367fb15320ebb1932fd589f8f38866b692ca8a4ad6100a4bc732d212916d0efbf7fd9628567011e47662d00",
|
||||||
|
"version": 536870912,
|
||||||
|
"bits": 503408517,
|
||||||
|
"nonce": 2975303,
|
||||||
|
"size": 343,
|
||||||
|
"weight": 1264,
|
||||||
|
"tx_count": 1,
|
||||||
|
"merkle_root": "efd01629212d73bca40061ada4a82c696b86388f9f58fd3219bb0e3215fb6713",
|
||||||
|
"reward": 5000000000,
|
||||||
|
"total_fee_amt": 0,
|
||||||
|
"avg_fee_amt": 0,
|
||||||
|
"median_fee_amt": 0,
|
||||||
|
"fee_amt_percentiles": {
|
||||||
|
"min": 0,
|
||||||
|
"perc_10": 0,
|
||||||
|
"perc_25": 0,
|
||||||
|
"perc_50": 0,
|
||||||
|
"perc_75": 0,
|
||||||
|
"perc_90": 0,
|
||||||
|
"max": 0
|
||||||
|
},
|
||||||
|
"avg_fee_rate": 0,
|
||||||
|
"median_fee_rate": 0,
|
||||||
|
"fee_rate_percentiles": {
|
||||||
|
"min": 0,
|
||||||
|
"perc_10": 0,
|
||||||
|
"perc_25": 0,
|
||||||
|
"perc_50": 0,
|
||||||
|
"perc_75": 0,
|
||||||
|
"perc_90": 0,
|
||||||
|
"max": 0
|
||||||
|
},
|
||||||
|
"total_inputs": 0,
|
||||||
|
"total_input_amt": null,
|
||||||
|
"total_outputs": 2,
|
||||||
|
"total_output_amt": 0,
|
||||||
|
"segwit_total_txs": 0,
|
||||||
|
"segwit_total_size": 0,
|
||||||
|
"segwit_total_weight": 0,
|
||||||
|
"avg_tx_size": 0,
|
||||||
|
"utxoset_change": 2,
|
||||||
|
"utxoset_size": null,
|
||||||
|
"coinbase_raw": "03a08601",
|
||||||
|
"coinbase_address": "tb1psfjl80vk0yp3agcq6ylueas29rau00mfq90mhejerpgccg33xhasd9gjyd",
|
||||||
|
"coinbase_signature": "OP_PUSHNUM_1 OP_PUSHBYTES_32 8265f3bd9679031ea300d13fccf60a28fbc7bf69015fbbe65918518c223135fb",
|
||||||
|
"coinbase_signature_ascii": "\u0003 <20>\u0001",
|
||||||
|
"pool_slug": "unknown",
|
||||||
|
"orphans": []
|
||||||
|
}
|
||||||
]`
|
]`
|
||||||
},
|
},
|
||||||
codeSampleLiquid: emptyCodeSample,
|
codeSampleLiquid: emptyCodeSample,
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
<div class="doc-content">
|
<div class="doc-content">
|
||||||
|
|
||||||
<p class="doc-welcome-note">Below is a reference for the {{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} <ng-container i18n="api-docs.title">REST API service</ng-container>.</p>
|
<p class="doc-welcome-note">Below is a reference for the {{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} <ng-container i18n="api-docs.title">REST API service</ng-container>.</p>
|
||||||
<p class="doc-welcome-note api-note" *ngIf="officialMempoolInstance">Note that we enforce rate limits. If you exceed these limits, you will get a polite error encouraging you to run your own Mempool instance. If you repeatedly exceed the limits, you may be banned from accessing the service altogether.</p>
|
<p class="doc-welcome-note api-note" *ngIf="officialMempoolInstance">Note that we enforce rate limits. If you exceed these limits, you will get an HTTP 429 error. If you repeatedly exceed the limits, you may be banned from accessing the service altogether. Consider an <a [routerLink]="['/enterprise']">enterprise sponsorship</a> if you need higher API limits.</p>
|
||||||
|
|
||||||
<div class="doc-item-container" *ngFor="let item of restDocs">
|
<div class="doc-item-container" *ngFor="let item of restDocs">
|
||||||
<h3 *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )">{{ item.title }}</h3>
|
<h3 *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )">{{ item.title }}</h3>
|
||||||
|
@ -32,7 +32,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li [ngbNavItem]="3" *ngIf="showElectrsTab" role="presentation">
|
<li [ngbNavItem]="3" *ngIf="showElectrsTab" role="presentation" class="hide-on-mobile">
|
||||||
<a ngbNavLink [routerLink]="['/docs/api/electrs' | relativeUrl]" role="tab">API - Electrum RPC</a>
|
<a ngbNavLink [routerLink]="['/docs/api/electrs' | relativeUrl]" role="tab">API - Electrum RPC</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
|
|
||||||
|
@ -7,3 +7,9 @@
|
|||||||
#footer {
|
#footer {
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.hide-on-mobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -114,7 +114,6 @@ export interface BlockExtension {
|
|||||||
medianFee?: number;
|
medianFee?: number;
|
||||||
feeRange?: number[];
|
feeRange?: number[];
|
||||||
reward?: number;
|
reward?: number;
|
||||||
coinbaseTx?: Transaction;
|
|
||||||
coinbaseRaw?: string;
|
coinbaseRaw?: string;
|
||||||
matchRate?: number;
|
matchRate?: number;
|
||||||
pool?: {
|
pool?: {
|
||||||
|
@ -161,28 +161,7 @@ export class NodesNetworksChartComponent implements OnInit {
|
|||||||
{
|
{
|
||||||
zlevel: 1,
|
zlevel: 1,
|
||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
name: $localize`Reachable on Clearnet Only`,
|
name: $localize`Clearnet and Darknet`,
|
||||||
showSymbol: false,
|
|
||||||
symbol: 'none',
|
|
||||||
data: data.clearnet_nodes,
|
|
||||||
type: 'line',
|
|
||||||
lineStyle: {
|
|
||||||
width: 2,
|
|
||||||
},
|
|
||||||
areaStyle: {
|
|
||||||
opacity: 0.5,
|
|
||||||
},
|
|
||||||
stack: 'Total',
|
|
||||||
color: new graphic.LinearGradient(0, 0.75, 0, 1, [
|
|
||||||
{ offset: 0, color: '#FFB300' },
|
|
||||||
{ offset: 1, color: '#FFB300AA' },
|
|
||||||
]),
|
|
||||||
smooth: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
zlevel: 1,
|
|
||||||
yAxisIndex: 0,
|
|
||||||
name: $localize`Reachable on Clearnet and Darknet`,
|
|
||||||
showSymbol: false,
|
showSymbol: false,
|
||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
data: data.clearnet_tor_nodes,
|
data: data.clearnet_tor_nodes,
|
||||||
@ -203,7 +182,28 @@ export class NodesNetworksChartComponent implements OnInit {
|
|||||||
{
|
{
|
||||||
zlevel: 1,
|
zlevel: 1,
|
||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
name: $localize`Reachable on Darknet Only`,
|
name: $localize`Clearnet (IPv4, IPv6)`,
|
||||||
|
showSymbol: false,
|
||||||
|
symbol: 'none',
|
||||||
|
data: data.clearnet_nodes,
|
||||||
|
type: 'line',
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
stack: 'Total',
|
||||||
|
color: new graphic.LinearGradient(0, 0.75, 0, 1, [
|
||||||
|
{ offset: 0, color: '#FFB300' },
|
||||||
|
{ offset: 1, color: '#FFB300AA' },
|
||||||
|
]),
|
||||||
|
smooth: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
zlevel: 1,
|
||||||
|
yAxisIndex: 0,
|
||||||
|
name: $localize`Darknet Only (Tor, I2P, cjdns)`,
|
||||||
showSymbol: false,
|
showSymbol: false,
|
||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
data: data.tor_nodes,
|
data: data.tor_nodes,
|
||||||
@ -284,7 +284,7 @@ export class NodesNetworksChartComponent implements OnInit {
|
|||||||
padding: 10,
|
padding: 10,
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
name: $localize`Reachable on Darknet Only`,
|
name: $localize`Darknet Only (Tor, I2P, cjdns)`,
|
||||||
inactiveColor: 'rgb(110, 112, 121)',
|
inactiveColor: 'rgb(110, 112, 121)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: 'white',
|
color: 'white',
|
||||||
@ -292,7 +292,7 @@ export class NodesNetworksChartComponent implements OnInit {
|
|||||||
icon: 'roundRect',
|
icon: 'roundRect',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: $localize`Reachable on Clearnet and Darknet`,
|
name: $localize`Clearnet (IPv4, IPv6)`,
|
||||||
inactiveColor: 'rgb(110, 112, 121)',
|
inactiveColor: 'rgb(110, 112, 121)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: 'white',
|
color: 'white',
|
||||||
@ -300,7 +300,7 @@ export class NodesNetworksChartComponent implements OnInit {
|
|||||||
icon: 'roundRect',
|
icon: 'roundRect',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: $localize`Reachable on Clearnet Only`,
|
name: $localize`Clearnet and Darknet`,
|
||||||
inactiveColor: 'rgb(110, 112, 121)',
|
inactiveColor: 'rgb(110, 112, 121)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: 'white',
|
color: 'white',
|
||||||
@ -317,9 +317,9 @@ export class NodesNetworksChartComponent implements OnInit {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
selected: this.widget ? undefined : JSON.parse(this.storageService.getValue('nodes_networks_legend')) ?? {
|
selected: this.widget ? undefined : JSON.parse(this.storageService.getValue('nodes_networks_legend')) ?? {
|
||||||
'$localize`Reachable on Darknet Only`': true,
|
'$localize`Darknet Only (Tor, I2P, cjdns)`': true,
|
||||||
'$localize`Reachable on Clearnet Only`': true,
|
'$localize`Clearnet (IPv4, IPv6)`': true,
|
||||||
'$localize`Reachable on Clearnet and Darknet`': true,
|
'$localize`Clearnet and Darknet`': true,
|
||||||
'$localize`:@@e5d8bb389c702588877f039d72178f219453a72d:Unknown`': true,
|
'$localize`:@@e5d8bb389c702588877f039d72178f219453a72d:Unknown`': true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||||
import { map, Observable } from 'rxjs';
|
import { map, Observable } from 'rxjs';
|
||||||
import { StateService } from 'src/app/services/state.service';
|
|
||||||
import { INodesRanking, ITopNodesPerCapacity } from '../../../interfaces/node-api.interface';
|
import { INodesRanking, ITopNodesPerCapacity } from '../../../interfaces/node-api.interface';
|
||||||
import { SeoService } from '../../../services/seo.service';
|
import { SeoService } from '../../../services/seo.service';
|
||||||
|
import { StateService } from '../../../services/state.service';
|
||||||
import { GeolocationData } from '../../../shared/components/geolocation/geolocation.component';
|
import { GeolocationData } from '../../../shared/components/geolocation/geolocation.component';
|
||||||
import { LightningApiService } from '../../lightning-api.service';
|
import { LightningApiService } from '../../lightning-api.service';
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||||
import { map, Observable } from 'rxjs';
|
import { map, Observable } from 'rxjs';
|
||||||
import { StateService } from 'src/app/services/state.service';
|
|
||||||
import { INodesRanking, ITopNodesPerChannels } from '../../../interfaces/node-api.interface';
|
import { INodesRanking, ITopNodesPerChannels } from '../../../interfaces/node-api.interface';
|
||||||
|
import { StateService } from '../../../services/state.service';
|
||||||
import { GeolocationData } from '../../../shared/components/geolocation/geolocation.component';
|
import { GeolocationData } from '../../../shared/components/geolocation/geolocation.component';
|
||||||
import { LightningApiService } from '../../lightning-api.service';
|
import { LightningApiService } from '../../lightning-api.service';
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ export class PriceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getBlockPrice$(blockTimestamp: number, singlePrice = false): Observable<Price | undefined> {
|
getBlockPrice$(blockTimestamp: number, singlePrice = false): Observable<Price | undefined> {
|
||||||
if (this.stateService.env.BASE_MODULE !== 'mempool') {
|
if (this.stateService.env.BASE_MODULE !== 'mempool' || !this.stateService.env.HISTORICAL_PRICE) {
|
||||||
return of(undefined);
|
return of(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ export interface Env {
|
|||||||
MAINNET_BLOCK_AUDIT_START_HEIGHT: number;
|
MAINNET_BLOCK_AUDIT_START_HEIGHT: number;
|
||||||
TESTNET_BLOCK_AUDIT_START_HEIGHT: number;
|
TESTNET_BLOCK_AUDIT_START_HEIGHT: number;
|
||||||
SIGNET_BLOCK_AUDIT_START_HEIGHT: number;
|
SIGNET_BLOCK_AUDIT_START_HEIGHT: number;
|
||||||
|
HISTORICAL_PRICE: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultEnv: Env = {
|
const defaultEnv: Env = {
|
||||||
@ -72,6 +73,7 @@ const defaultEnv: Env = {
|
|||||||
'MAINNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
'MAINNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
||||||
'TESTNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
'TESTNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
||||||
'SIGNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
'SIGNET_BLOCK_AUDIT_START_HEIGHT': 0,
|
||||||
|
'HISTORICAL_PRICE': true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
BIN
frontend/src/resources/mempool-promo.jpg
Normal file
BIN
frontend/src/resources/mempool-promo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
@ -51,7 +51,7 @@ function downloadMiningPoolLogos() {
|
|||||||
response.on('data', (fragments) => {
|
response.on('data', (fragments) => {
|
||||||
chunks_of_data.push(fragments);
|
chunks_of_data.push(fragments);
|
||||||
});
|
});
|
||||||
|
|
||||||
response.on('end', () => {
|
response.on('end', () => {
|
||||||
let response_body = Buffer.concat(chunks_of_data);
|
let response_body = Buffer.concat(chunks_of_data);
|
||||||
try {
|
try {
|
||||||
@ -63,7 +63,7 @@ function downloadMiningPoolLogos() {
|
|||||||
console.error(`Unable to download mining pool logos. Trying again at next restart. Reason: ${e instanceof Error ? e.message : e}`);
|
console.error(`Unable to download mining pool logos. Trying again at next restart. Reason: ${e instanceof Error ? e.message : e}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
response.on('error', (error) => {
|
response.on('error', (error) => {
|
||||||
throw new Error(error);
|
throw new Error(error);
|
||||||
});
|
});
|
||||||
@ -81,6 +81,9 @@ if (configContent.BASE_MODULE && configContent.BASE_MODULE === 'liquid') {
|
|||||||
const testnetAssetsJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.json';
|
const testnetAssetsJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.json';
|
||||||
const testnetAssetsMinimalJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.minimal.json';
|
const testnetAssetsMinimalJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.minimal.json';
|
||||||
|
|
||||||
|
const promoVideo = PATH + 'mempool-promo.mp4';
|
||||||
|
const promoVideoUrl = 'https://raw.githubusercontent.com/mempool/mempool-promo/master/promo.mp4';
|
||||||
|
|
||||||
console.log('Downloading assets');
|
console.log('Downloading assets');
|
||||||
download(PATH + 'assets.json', assetsJsonUrl);
|
download(PATH + 'assets.json', assetsJsonUrl);
|
||||||
console.log('Downloading assets minimal');
|
console.log('Downloading assets minimal');
|
||||||
@ -89,5 +92,9 @@ console.log('Downloading testnet assets');
|
|||||||
download(PATH + 'assets-testnet.json', testnetAssetsJsonUrl);
|
download(PATH + 'assets-testnet.json', testnetAssetsJsonUrl);
|
||||||
console.log('Downloading testnet assets minimal');
|
console.log('Downloading testnet assets minimal');
|
||||||
download(PATH + 'assets-testnet.minimal.json', testnetAssetsMinimalJsonUrl);
|
download(PATH + 'assets-testnet.minimal.json', testnetAssetsMinimalJsonUrl);
|
||||||
|
if (!fs.existsSync(promoVideo)) {
|
||||||
|
console.log('Downloading promo video');
|
||||||
|
download(promoVideo, promoVideoUrl);
|
||||||
|
}
|
||||||
console.log('Downloading mining pool logos');
|
console.log('Downloading mining pool logos');
|
||||||
downloadMiningPoolLogos();
|
downloadMiningPoolLogos();
|
||||||
|
@ -38,7 +38,7 @@ update_repo()
|
|||||||
cd "$HOME/${site}" || exit 1
|
cd "$HOME/${site}" || exit 1
|
||||||
|
|
||||||
git fetch origin || exit 1
|
git fetch origin || exit 1
|
||||||
for remote in origin hunicus mononaut;do
|
for remote in origin;do
|
||||||
git remote add "${remote}" "https://github.com/${remote}/mempool" >/dev/null 2>&1
|
git remote add "${remote}" "https://github.com/${remote}/mempool" >/dev/null 2>&1
|
||||||
git fetch "${remote}" || exit 1
|
git fetch "${remote}" || exit 1
|
||||||
done
|
done
|
||||||
|
Loading…
x
Reference in New Issue
Block a user