Merge branch 'master' into breathe-effect-framerate

This commit is contained in:
wiz 2022-07-06 23:45:29 +02:00 committed by GitHub
commit f9dfbf94ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 2716 additions and 2794 deletions

View File

@ -17,9 +17,9 @@
"express": "^4.18.0",
"mysql2": "2.3.3",
"node-worker-threads-pool": "^1.5.1",
"socks-proxy-agent": "^6.2.0",
"typescript": "~4.7.2",
"ws": "~8.7.0"
"socks-proxy-agent": "~7.0.0",
"typescript": "~4.7.4",
"ws": "~8.8.0"
},
"devDependencies": {
"@types/compression": "^1.7.2",
@ -2734,9 +2734,9 @@
}
},
"node_modules/socks-proxy-agent": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.0.tgz",
"integrity": "sha512-wWqJhjb32Q6GsrUqzuFkukxb/zzide5quXYcMVpIjxalDBBYy2nqKCFQ/9+Ie4dvOYSQdOk3hUlZSdzZOd3zMQ==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz",
"integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==",
"dependencies": {
"agent-base": "^6.0.2",
"debug": "^4.3.3",
@ -2950,9 +2950,9 @@
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g=="
},
"node_modules/typescript": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz",
"integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==",
"version": "4.7.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@ -3052,9 +3052,9 @@
"dev": true
},
"node_modules/ws": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.7.0.tgz",
"integrity": "sha512-c2gsP0PRwcLFzUiA8Mkr37/MI7ilIlHQxaEAtd0uNMbVMoy8puJyafRlm0bV9MbGSabUPeLrRRaqIBcFcA2Pqg==",
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz",
"integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==",
"engines": {
"node": ">=10.0.0"
},
@ -5098,9 +5098,9 @@
}
},
"socks-proxy-agent": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.0.tgz",
"integrity": "sha512-wWqJhjb32Q6GsrUqzuFkukxb/zzide5quXYcMVpIjxalDBBYy2nqKCFQ/9+Ie4dvOYSQdOk3hUlZSdzZOd3zMQ==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz",
"integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==",
"requires": {
"agent-base": "^6.0.2",
"debug": "^4.3.3",
@ -5256,9 +5256,9 @@
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g=="
},
"typescript": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.2.tgz",
"integrity": "sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A=="
"version": "4.7.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ=="
},
"unpipe": {
"version": "1.0.0",
@ -5333,9 +5333,9 @@
"dev": true
},
"ws": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.7.0.tgz",
"integrity": "sha512-c2gsP0PRwcLFzUiA8Mkr37/MI7ilIlHQxaEAtd0uNMbVMoy8puJyafRlm0bV9MbGSabUPeLrRRaqIBcFcA2Pqg==",
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz",
"integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==",
"requires": {}
},
"yallist": {

View File

@ -37,9 +37,9 @@
"express": "^4.18.0",
"mysql2": "2.3.3",
"node-worker-threads-pool": "^1.5.1",
"socks-proxy-agent": "^6.2.0",
"typescript": "~4.7.2",
"ws": "~8.7.0"
"socks-proxy-agent": "~7.0.0",
"typescript": "~4.7.4",
"ws": "~8.8.0"
},
"devDependencies": {
"@types/compression": "^1.7.2",

View File

@ -295,7 +295,8 @@ class Blocks {
}
logger.notice(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`);
} catch (e) {
logger.err(`Blocks summaries indexing failed. Reason: ${(e instanceof Error ? e.message : e)}`);
logger.err(`Blocks summaries indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`);
throw e;
}
}
@ -367,18 +368,12 @@ class Blocks {
logger.notice(`Block indexing completed: indexed ${newlyIndexed} blocks`);
loadingIndicators.setProgress('block-indexing', 100);
} catch (e) {
logger.err('Block indexing failed. Trying again later. Reason: ' + (e instanceof Error ? e.message : e));
logger.err('Block indexing failed. Trying again in 10 seconds. Reason: ' + (e instanceof Error ? e.message : e));
loadingIndicators.setProgress('block-indexing', 100);
return false;
throw e;
}
const chainValid = await BlocksRepository.$validateChain();
if (!chainValid) {
indexer.reindex();
return false;
}
return true;
return await BlocksRepository.$validateChain();
}
public async $updateBlocks() {

View File

@ -4,7 +4,7 @@ import logger from '../logger';
import { Common } from './common';
class DatabaseMigration {
private static currentVersion = 22;
private static currentVersion = 24;
private queryTimeout = 120000;
private statisticsAddedIndexed = false;
private uniqueLogs: string[] = [];
@ -231,6 +231,23 @@ class DatabaseMigration {
await this.$executeQuery('DROP TABLE IF EXISTS `difficulty_adjustments`');
await this.$executeQuery(this.getCreateDifficultyAdjustmentsTableQuery(), await this.$checkIfTableExists('difficulty_adjustments'));
}
if (databaseSchemaVersion < 23) {
await this.$executeQuery('TRUNCATE `prices`');
await this.$executeQuery('ALTER TABLE `prices` DROP `avg_prices`');
await this.$executeQuery('ALTER TABLE `prices` ADD `USD` float DEFAULT "0"');
await this.$executeQuery('ALTER TABLE `prices` ADD `EUR` float DEFAULT "0"');
await this.$executeQuery('ALTER TABLE `prices` ADD `GBP` float DEFAULT "0"');
await this.$executeQuery('ALTER TABLE `prices` ADD `CAD` float DEFAULT "0"');
await this.$executeQuery('ALTER TABLE `prices` ADD `CHF` float DEFAULT "0"');
await this.$executeQuery('ALTER TABLE `prices` ADD `AUD` float DEFAULT "0"');
await this.$executeQuery('ALTER TABLE `prices` ADD `JPY` float DEFAULT "0"');
}
if (databaseSchemaVersion < 24 && isBitcoin == true) {
await this.$executeQuery('DROP TABLE IF EXISTS `blocks_audits`');
await this.$executeQuery(this.getCreateBlocksAuditsTableQuery(), await this.$checkIfTableExists('blocks_audits'));
}
} catch (e) {
throw e;
}
@ -555,6 +572,19 @@ class DatabaseMigration {
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
}
private getCreateBlocksAuditsTableQuery(): string {
return `CREATE TABLE IF NOT EXISTS blocks_audits (
time timestamp NOT NULL,
hash varchar(65) NOT NULL,
height int(10) unsigned NOT NULL,
missing_txs JSON NOT NULL,
added_txs JSON NOT NULL,
match_rate float unsigned NOT NULL,
PRIMARY KEY (hash),
INDEX (height)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
}
public async $truncateIndexedData(tables: string[]) {
const allowedTables = ['blocks', 'hashrates', 'prices'];

View File

@ -7,12 +7,25 @@ import logger from '../logger';
import { Common } from './common';
import loadingIndicators from './loading-indicators';
import { escape } from 'mysql2';
import indexer from '../indexer';
import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository';
import config from '../config';
import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository';
class Mining {
constructor() {
}
/**
* Get historical block predictions match rate
*/
public async $getBlockPredictionsHistory(interval: string | null = null): Promise<any> {
return await BlocksAuditsRepository.$getBlockPredictionsHistory(
this.getTimeRange(interval),
Common.getSqlInterval(interval)
);
}
/**
* Get historical block total fee
*/
@ -263,6 +276,7 @@ class Mining {
loadingIndicators.setProgress('weekly-hashrate-indexing', 100);
} catch (e) {
loadingIndicators.setProgress('weekly-hashrate-indexing', 100);
logger.err(`Weekly mining pools hashrates indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`);
throw e;
}
}
@ -302,7 +316,7 @@ class Mining {
while (toTimestamp > genesisTimestamp) {
const fromTimestamp = toTimestamp - 86400000;
// Skip already indexed weeks
// Skip already indexed days
if (indexedTimestamp.includes(toTimestamp / 1000)) {
toTimestamp -= 86400000;
++totalIndexed;
@ -313,7 +327,7 @@ class Mining {
// we are currently indexing has complete data)
const blockStatsPreviousDay: any = await BlocksRepository.$blockCountBetweenTimestamp(
null, (fromTimestamp - 86400000) / 1000, (toTimestamp - 86400000) / 1000);
if (blockStatsPreviousDay.blockCount === 0) { // We are done indexing
if (blockStatsPreviousDay.blockCount === 0 && config.MEMPOOL.NETWORK === 'mainnet') { // We are done indexing
break;
}
@ -357,9 +371,10 @@ class Mining {
// Add genesis block manually
if (toTimestamp <= genesisTimestamp && !indexedTimestamp.includes(genesisTimestamp)) {
hashrates.push({
hashrateTimestamp: genesisTimestamp,
hashrateTimestamp: genesisTimestamp / 1000,
avgHashrate: await bitcoinClient.getNetworkHashPs(1, 1),
poolId: null,
poolId: 0,
share: 1,
type: 'daily',
});
}
@ -374,6 +389,7 @@ class Mining {
loadingIndicators.setProgress('daily-hashrate-indexing', 100);
} catch (e) {
loadingIndicators.setProgress('daily-hashrate-indexing', 100);
logger.err(`Daily network hashrate indexing failed. Trying again in 10 seconds. Reason: ${(e instanceof Error ? e.message : e)}`);
throw e;
}
}
@ -393,6 +409,15 @@ class Mining {
let currentDifficulty = 0;
let totalIndexed = 0;
if (indexedHeights[0] === false) {
await DifficultyAdjustmentsRepository.$saveAdjustments({
time: 1231006505,
height: 0,
difficulty: 1.0,
adjustment: 0.0,
});
}
for (const block of blocks) {
if (block.difficulty !== currentDifficulty) {
if (block.height === 0 || indexedHeights[block.height] === true) { // Already indexed

View File

@ -16,6 +16,7 @@ import transactionUtils from './transaction-utils';
import rbfCache from './rbf-cache';
import difficultyAdjustment from './difficulty-adjustment';
import feeApi from './fee-api';
import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository';
class WebsocketHandler {
private wss: WebSocket.Server | undefined;
@ -416,17 +417,40 @@ class WebsocketHandler {
if (_mempoolBlocks[0]) {
const matches: string[] = [];
const added: string[] = [];
const missing: string[] = [];
for (const txId of txIds) {
if (_mempoolBlocks[0].transactionIds.indexOf(txId) > -1) {
matches.push(txId);
} else {
added.push(txId);
}
delete _memPool[txId];
}
matchRate = Math.round((matches.length / (txIds.length - 1)) * 100);
for (const txId of _mempoolBlocks[0].transactionIds) {
if (matches.includes(txId) || added.includes(txId)) {
continue;
}
missing.push(txId);
}
matchRate = Math.round((Math.max(0, matches.length - missing.length - added.length) / txIds.length * 100) * 100) / 100;
mempoolBlocks.updateMempoolBlocks(_memPool);
mBlocks = mempoolBlocks.getMempoolBlocks();
mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas();
if (Common.indexingEnabled()) {
BlocksAuditsRepository.$saveAudit({
time: block.timestamp,
height: block.height,
hash: block.id,
addedTxs: added,
missingTxs: missing,
matchRate: matchRate,
});
}
}
if (block.extras) {

View File

@ -28,6 +28,7 @@ import { Common } from './api/common';
import poolsUpdater from './tasks/pools-updater';
import indexer from './indexer';
import priceUpdater from './tasks/price-updater';
import BlocksAuditsRepository from './repositories/BlocksAuditsRepository';
class Server {
private wss: WebSocket.Server | undefined;
@ -285,12 +286,14 @@ class Server {
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug', routes.$getPool)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/pools/:interval', routes.$getPoolsHistoricalHashrate)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/:interval', routes.$getHistoricalHashrate)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments', routes.$getDifficultyAdjustments)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/reward-stats/:blockCount', routes.$getRewardStats)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fees/:interval', routes.$getHistoricalBlockFees)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/rewards/:interval', routes.$getHistoricalBlockRewards)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fee-rates/:interval', routes.$getHistoricalBlockFeeRates)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/sizes-weights/:interval', routes.$getHistoricalBlockSizeAndWeight)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments/:interval', routes.$getDifficultyAdjustments)
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/predictions/:interval', routes.$getHistoricalBlockPrediction)
;
}

View File

@ -39,6 +39,8 @@ class Indexer {
const chainValid = await blocks.$generateBlockDatabase();
if (chainValid === false) {
// Chain of block hash was invalid, so we need to reindex. Stop here and continue at the next iteration
logger.warn(`The chain of block hash is invalid, re-indexing invalid data in 10 seconds.`);
setTimeout(() => this.reindex(), 10000);
this.indexerRunning = false;
return;
}
@ -49,8 +51,9 @@ class Indexer {
await mining.$generatePoolHashrateHistory();
await blocks.$generateBlocksSummariesDatabase();
} catch (e) {
this.reindex();
logger.err(`Indexer failed, trying again later. Reason: ` + (e instanceof Error ? e.message : e));
this.indexerRunning = false;
logger.err(`Indexer failed, trying again in 10 seconds. Reason: ` + (e instanceof Error ? e.message : e));
setTimeout(() => this.reindex(), 10000);
}
this.indexerRunning = false;
@ -62,6 +65,7 @@ class Indexer {
await HashratesRepository.$setLatestRun('last_weekly_hashrates_indexing', 0);
} catch (e) {
logger.err(`Cannot reset hashrate indexing timestamps. Reason: ` + (e instanceof Error ? e.message : e));
throw e;
}
}
}

View File

@ -22,6 +22,15 @@ export interface PoolStats extends PoolInfo {
emptyBlocks: number;
}
export interface BlockAudit {
time: number,
height: number,
hash: string,
missingTxs: string[],
addedTxs: string[],
matchRate: number,
}
export interface MempoolBlock {
blockSize: number;
blockVSize: number;

View File

@ -0,0 +1,51 @@
import DB from '../database';
import logger from '../logger';
import { BlockAudit } from '../mempool.interfaces';
class BlocksAuditRepositories {
public async $saveAudit(audit: BlockAudit): Promise<void> {
try {
await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, match_rate)
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs),
JSON.stringify(audit.addedTxs), audit.matchRate]);
} catch (e: any) {
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
logger.debug(`Cannot save block audit for block ${audit.hash} because it has already been indexed, ignoring`);
} else {
logger.err(`Cannot save block audit into db. Reason: ` + (e instanceof Error ? e.message : e));
throw e;
}
}
}
public async $getBlockPredictionsHistory(div: number, interval: string | null): Promise<any> {
try {
let query = `SELECT UNIX_TIMESTAMP(time) as time, height, match_rate FROM blocks_audits`;
if (interval !== null) {
query += ` WHERE time BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
}
query += ` GROUP BY UNIX_TIMESTAMP(time) DIV ${div} ORDER BY height`;
const [rows] = await DB.query(query);
return rows;
} catch (e: any) {
logger.err(`Cannot fetch block prediction history. Reason: ` + (e instanceof Error ? e.message : e));
throw e;
}
}
public async $getPredictionsCount(): Promise<number> {
try {
const [rows] = await DB.query(`SELECT count(hash) as count FROM blocks_audits`);
return rows[0].count;
} catch (e: any) {
logger.err(`Cannot fetch block prediction history. Reason: ` + (e instanceof Error ? e.message : e));
throw e;
}
}
}
export default new BlocksAuditRepositories();

View File

@ -436,7 +436,7 @@ class BlocksRepository {
}
if (blocks[idx].previous_block_hash !== blocks[idx - 1].hash) {
logger.warn(`Chain divergence detected at block ${blocks[idx - 1].height}, re-indexing newer blocks and hashrates`);
logger.warn(`Chain divergence detected at block ${blocks[idx - 1].height}`);
await this.$deleteBlocksFrom(blocks[idx - 1].height);
await BlocksSummariesRepository.$deleteBlocksFrom(blocks[idx - 1].height);
await HashratesRepository.$deleteHashratesFromTimestamp(blocks[idx - 1].timestamp - 604800);

View File

@ -1,4 +1,5 @@
import { Common } from '../api/common';
import config from '../config';
import DB from '../database';
import logger from '../logger';
import { IndexedDifficultyAdjustment } from '../mempool.interfaces';
@ -31,13 +32,19 @@ class DifficultyAdjustmentsRepository {
public async $getAdjustments(interval: string | null, descOrder: boolean = false): Promise<IndexedDifficultyAdjustment[]> {
interval = Common.getSqlInterval(interval);
let query = `SELECT UNIX_TIMESTAMP(time) as time, height, difficulty, adjustment
let query = `SELECT
CAST(AVG(UNIX_TIMESTAMP(time)) as INT) as time,
CAST(AVG(height) AS INT) as height,
CAST(AVG(difficulty) as DOUBLE) as difficulty,
CAST(AVG(adjustment) as DOUBLE) as adjustment
FROM difficulty_adjustments`;
if (interval) {
query += ` WHERE time BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
}
query += ` GROUP BY UNIX_TIMESTAMP(time) DIV ${86400}`;
if (descOrder === true) {
query += ` ORDER BY time DESC`;
} else {

View File

@ -1,5 +1,6 @@
import { escape } from 'mysql2';
import { Common } from '../api/common';
import config from '../config';
import DB from '../database';
import logger from '../logger';
import PoolsRepository from './PoolsRepository';
@ -32,7 +33,9 @@ class HashratesRepository {
public async $getNetworkDailyHashrate(interval: string | null): Promise<any[]> {
interval = Common.getSqlInterval(interval);
let query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp, avg_hashrate as avgHashrate
let query = `SELECT
CAST(AVG(UNIX_TIMESTAMP(hashrate_timestamp)) as INT) as timestamp,
CAST(AVG(avg_hashrate) as DOUBLE) as avgHashrate
FROM hashrates`;
if (interval) {
@ -42,6 +45,7 @@ class HashratesRepository {
query += ` WHERE hashrates.type = 'daily'`;
}
query += ` GROUP BY UNIX_TIMESTAMP(hashrate_timestamp) DIV ${86400}`;
query += ` ORDER by hashrate_timestamp`;
try {
@ -75,6 +79,9 @@ class HashratesRepository {
interval = Common.getSqlInterval(interval);
const topPoolsId = (await PoolsRepository.$getPoolsInfo('1w')).map((pool) => pool.poolId);
if (topPoolsId.length === 0) {
return [];
}
let query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp, avg_hashrate as avgHashrate, share, pools.name as poolName
FROM hashrates

View File

@ -5,7 +5,11 @@ import { Prices } from '../tasks/price-updater';
class PricesRepository {
public async $savePrices(time: number, prices: Prices): Promise<void> {
try {
await DB.query(`INSERT INTO prices(time, avg_prices) VALUE (FROM_UNIXTIME(?), ?)`, [time, JSON.stringify(prices)]);
await DB.query(`
INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY)
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ? )`,
[time, prices.USD, prices.EUR, prices.GBP, prices.CAD, prices.CHF, prices.AUD, prices.JPY]
);
} catch (e: any) {
logger.err(`Cannot save exchange rate into db. Reason: ` + (e instanceof Error ? e.message : e));
throw e;

View File

@ -27,6 +27,7 @@ import BlocksRepository from './repositories/BlocksRepository';
import HashratesRepository from './repositories/HashratesRepository';
import difficultyAdjustment from './api/difficulty-adjustment';
import DifficultyAdjustmentsRepository from './repositories/DifficultyAdjustmentsRepository';
import BlocksAuditsRepository from './repositories/BlocksAuditsRepository';
class Routes {
constructor() {}
@ -743,6 +744,20 @@ class Routes {
}
}
public async $getHistoricalBlockPrediction(req: Request, res: Response) {
try {
const blockPredictions = await mining.$getBlockPredictionsHistory(req.params.interval);
const blockCount = await BlocksAuditsRepository.$getPredictionsCount();
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.header('X-total-count', blockCount.toString());
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
res.json(blockPredictions.map(prediction => [prediction.time, prediction.height, prediction.match_rate]));
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
}
}
public async getBlock(req: Request, res: Response) {
try {
const block = await blocks.$getBlock(req.params.hash);

View File

@ -87,7 +87,7 @@ class KrakenApi implements PriceFeed {
}
if (Object.keys(priceHistory).length > 0) {
logger.info(`Inserted ${Object.keys(priceHistory).length} Kraken EUR, USD, GBP, JPY, CAD, CHF and AUD weekly price history into db`);
logger.notice(`Inserted ${Object.keys(priceHistory).length} Kraken EUR, USD, GBP, JPY, CAD, CHF and AUD weekly price history into db`);
}
}
}

View File

@ -176,7 +176,7 @@ class PriceUpdater {
++insertedCount;
}
if (insertedCount > 0) {
logger.info(`Inserted ${insertedCount} MtGox USD weekly price history into db`);
logger.notice(`Inserted ${insertedCount} MtGox USD weekly price history into db`);
}
// Insert Kraken weekly prices
@ -205,23 +205,23 @@ class PriceUpdater {
try {
historicalPrices.push(await feed.$fetchRecentHourlyPrice(this.currencies));
} catch (e) {
logger.info(`Cannot fetch hourly historical price from ${feed.name}. Ignoring this feed. Reason: ${e instanceof Error ? e.message : e}`);
logger.err(`Cannot fetch hourly historical price from ${feed.name}. Ignoring this feed. Reason: ${e instanceof Error ? e.message : e}`);
}
}
// Group them by timestamp and currency, for example
// grouped[123456789]['USD'] = [1, 2, 3, 4];
let grouped: Object = {};
const grouped: Object = {};
for (const historicalEntry of historicalPrices) {
for (const time in historicalEntry) {
if (existingPriceTimes.includes(parseInt(time, 10))) {
continue;
}
if (grouped[time] == undefined) {
if (grouped[time] === undefined) {
grouped[time] = {
USD: [], EUR: [], GBP: [], CAD: [], CHF: [], AUD: [], JPY: []
}
};
}
for (const currency of this.currencies) {
@ -238,13 +238,20 @@ class PriceUpdater {
for (const time in grouped) {
const prices: Prices = this.getEmptyPricesObj();
for (const currency in grouped[time]) {
prices[currency] = Math.round((grouped[time][currency].reduce((partialSum, a) => partialSum + a, 0)) / grouped[time][currency].length);
if (grouped[time][currency].length === 0) {
continue;
}
prices[currency] = Math.round((grouped[time][currency].reduce(
(partialSum, a) => partialSum + a, 0)
) / grouped[time][currency].length);
}
await PricesRepository.$savePrices(parseInt(time, 10), prices);
++totalInserted;
}
logger.info(`Inserted ${totalInserted} hourly historical prices into the db`);
if (totalInserted > 0) {
logger.notice(`Inserted ${totalInserted} hourly historical prices into the db`);
}
}
}

View File

@ -50,10 +50,14 @@ export async function query(path): Promise<object | undefined> {
}
return data.data;
} catch (e) {
logger.err(`Could not connect to ${path}. Reason: ` + (e instanceof Error ? e.message : e));
logger.warn(`Could not connect to ${path} (Attempt ${retry + 1}/${config.MEMPOOL.EXTERNAL_MAX_RETRY}). Reason: ` + (e instanceof Error ? e.message : e));
retry++;
}
await setDelay(config.MEMPOOL.EXTERNAL_RETRY_INTERVAL);
if (retry < config.MEMPOOL.EXTERNAL_MAX_RETRY) {
await setDelay(config.MEMPOOL.EXTERNAL_RETRY_INTERVAL);
}
}
logger.err(`Could not connect to ${path}. All ${config.MEMPOOL.EXTERNAL_MAX_RETRY} attempts failed`);
return undefined;
}

View File

@ -248,23 +248,6 @@
"browserTarget": "mempool:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/resources"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
},
"e2e": {
"builder": "@cypress/schematic:cypress",
"options": {

View File

@ -1,32 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/mempool'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

File diff suppressed because it is too large Load Diff

View File

@ -78,7 +78,6 @@
"@fortawesome/fontawesome-common-types": "~6.1.1",
"@fortawesome/fontawesome-svg-core": "~6.1.1",
"@fortawesome/free-solid-svg-icons": "~6.1.1",
"@juggle/resize-observer": "^3.3.1",
"@mempool/mempool.js": "2.3.0",
"@ng-bootstrap/ng-bootstrap": "^11.0.0",
"@nguniversal/express-engine": "~13.1.1",
@ -89,7 +88,7 @@
"domino": "^2.1.6",
"echarts": "~5.3.2",
"express": "^4.17.1",
"lightweight-charts": "^3.3.0",
"lightweight-charts": "~3.8.0",
"ngx-bootrap-multiselect": "^2.0.0",
"ngx-echarts": "8.0.1",
"ngx-infinite-scroll": "^10.0.1",
@ -105,31 +104,23 @@
"@angular/language-service": "~13.3.10",
"@nguniversal/builders": "~13.1.1",
"@types/express": "^4.17.0",
"@types/jasmine": "~4.0.3",
"@types/jasminewd2": "~2.0.10",
"@types/node": "^12.11.1",
"@typescript-eslint/eslint-plugin": "^5.30.5",
"@typescript-eslint/parser": "^5.30.5",
"codelyzer": "~6.0.2",
"eslint": "^8.19.0",
"http-proxy-middleware": "^1.0.5",
"jasmine-core": "~4.1.0",
"jasmine-spec-reporter": "~7.0.0",
"karma": "~6.3.19",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.0.3",
"karma-jasmine": "~5.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"http-proxy-middleware": "~2.0.6",
"ts-node": "~10.8.1",
"typescript": "~4.6.4"
},
"optionalDependencies": {
"@cypress/schematic": "^1.3.0",
"@cypress/schematic": "~2.0.0",
"cypress": "^10.0.2",
"cypress-fail-on-console-error": "^2.1.3",
"cypress-fail-on-console-error": "~2.1.4",
"cypress-wait-until": "^1.7.1",
"mock-socket": "^9.0.3",
"start-server-and-test": "^1.12.6"
"mock-socket": "~9.1.4",
"start-server-and-test": "~1.14.0"
},
"scarfSettings": {
"enabled": false
}
}

View File

@ -171,7 +171,7 @@ let routes: Routes = [
{
path: 'block',
component: StartComponent,
children: [
children: [
{
path: ':id',
component: BlockComponent
@ -258,7 +258,7 @@ let routes: Routes = [
{
path: 'block',
component: StartComponent,
children: [
children: [
{
path: ':id',
component: BlockComponent
@ -361,7 +361,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
{
path: 'block',
component: StartComponent,
children: [
children: [
{
path: ':id',
component: BlockComponent
@ -465,7 +465,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
{
path: 'block',
component: StartComponent,
children: [
children: [
{
path: ':id',
component: BlockComponent

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AddressLabelsComponent } from './address-labels.component';
describe('AddressLabelsComponent', () => {
let component: AddressLabelsComponent;
let fixture: ComponentFixture<AddressLabelsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AddressLabelsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AddressLabelsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AmountComponent } from './amount.component';
describe('AmountComponent', () => {
let component: AmountComponent;
let fixture: ComponentFixture<AmountComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AmountComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AmountComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,35 +0,0 @@
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
});
it(`should have as title 'mempool'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('mempool');
});
it('should render title in a h1 tag', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to mempool!');
});
});

View File

@ -79,57 +79,3 @@
}
}
}
.pool-distribution {
min-height: 56px;
display: block;
@media (min-width: 485px) {
display: flex;
flex-direction: row;
}
h5 {
margin-bottom: 10px;
}
.item {
width: 50%;
display: inline-block;
margin: 0px auto 20px;
&:nth-child(2) {
order: 2;
@media (min-width: 485px) {
order: 3;
}
}
&:nth-child(3) {
order: 3;
@media (min-width: 485px) {
order: 2;
display: block;
}
@media (min-width: 768px) {
display: none;
}
@media (min-width: 992px) {
display: block;
}
}
.card-title {
font-size: 1rem;
color: #4a68b9;
}
.card-text {
font-size: 18px;
span {
color: #ffffff66;
font-size: 12px;
}
}
}
}
.skeleton-loader {
width: 100%;
display: block;
max-width: 80px;
margin: 15px auto 3px;
}

View File

@ -174,7 +174,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
align: 'left',
},
borderColor: '#000',
formatter: function (data) {
formatter: function(data) {
if (data.length <= 0) {
return '';
}

View File

@ -79,57 +79,3 @@
}
}
}
.pool-distribution {
min-height: 56px;
display: block;
@media (min-width: 485px) {
display: flex;
flex-direction: row;
}
h5 {
margin-bottom: 10px;
}
.item {
width: 50%;
display: inline-block;
margin: 0px auto 20px;
&:nth-child(2) {
order: 2;
@media (min-width: 485px) {
order: 3;
}
}
&:nth-child(3) {
order: 3;
@media (min-width: 485px) {
order: 2;
display: block;
}
@media (min-width: 768px) {
display: none;
}
@media (min-width: 992px) {
display: block;
}
}
.card-title {
font-size: 1rem;
color: #4a68b9;
}
.card-text {
font-size: 18px;
span {
color: #ffffff66;
font-size: 12px;
}
}
}
}
.skeleton-loader {
width: 100%;
display: block;
max-width: 80px;
margin: 15px auto 3px;
}

View File

@ -0,0 +1,52 @@
<app-indexing-progress></app-indexing-progress>
<div class="full-container">
<div class="card-header mb-0 mb-md-4">
<span i18n="mining.block-prediction-accuracy">Block Predictions Accuracy</span>
<button class="btn" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
</button>
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 144">
<input ngbButton type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-predictions' | relativeUrl]"> 24h
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 432">
<input ngbButton type="radio" [value]="'3d'" fragment="3d" [routerLink]="['/graphs/mining/block-predictions' | relativeUrl]"> 3D
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 1008">
<input ngbButton type="radio" [value]="'1w'" fragment="1w" [routerLink]="['/graphs/mining/block-predictions' | relativeUrl]"> 1W
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 4320">
<input ngbButton type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-predictions' | relativeUrl]"> 1M
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 12960">
<input ngbButton type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/mining/block-predictions' | relativeUrl]"> 3M
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 25920">
<input ngbButton type="radio" [value]="'6m'" fragment="6m" [routerLink]="['/graphs/mining/block-predictions' | relativeUrl]"> 6M
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 52560">
<input ngbButton type="radio" [value]="'1y'" fragment="1y" [routerLink]="['/graphs/mining/block-predictions' | relativeUrl]"> 1Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 105120">
<input ngbButton type="radio" [value]="'2y'" fragment="2y" [routerLink]="['/graphs/mining/block-predictions' | relativeUrl]"> 2Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 157680">
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-predictions' | relativeUrl]"> 3Y
</label>
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount > 157680">
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-predictions' | relativeUrl]"> ALL
</label>
</div>
</form>
</div>
<div class="chart" echarts [initOpts]="chartInitOptions" [options]="chartOptions"
(chartInit)="onChartInit($event)">
</div>
<div class="text-center loadingGraphs" *ngIf="isLoading">
<div class="spinner-border text-light"></div>
</div>
</div>

View File

@ -0,0 +1,81 @@
.card-header {
border-bottom: 0;
font-size: 18px;
@media (min-width: 465px) {
font-size: 20px;
}
}
.main-title {
position: relative;
color: #ffffff91;
margin-top: -13px;
font-size: 10px;
text-transform: uppercase;
font-weight: 500;
text-align: center;
padding-bottom: 3px;
}
.full-container {
padding: 0px 15px;
width: 100%;
min-height: 500px;
height: calc(100% - 150px);
@media (max-width: 992px) {
height: 100%;
padding-bottom: 100px;
};
}
.chart {
width: 100%;
height: 100%;
padding-bottom: 20px;
padding-right: 10px;
@media (max-width: 992px) {
padding-bottom: 25px;
}
@media (max-width: 829px) {
padding-bottom: 50px;
}
@media (max-width: 767px) {
padding-bottom: 25px;
}
@media (max-width: 629px) {
padding-bottom: 55px;
}
@media (max-width: 567px) {
padding-bottom: 55px;
}
}
.chart-widget {
width: 100%;
height: 100%;
max-height: 270px;
}
.formRadioGroup {
margin-top: 6px;
display: flex;
flex-direction: column;
@media (min-width: 991px) {
position: relative;
top: -65px;
}
@media (min-width: 830px) and (max-width: 991px) {
position: relative;
top: 0px;
}
@media (min-width: 830px) {
flex-direction: row;
float: right;
margin-top: 0px;
}
.btn-sm {
font-size: 9px;
@media (min-width: 830px) {
font-size: 14px;
}
}
}

View File

@ -0,0 +1,289 @@
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, OnInit } from '@angular/core';
import { EChartsOption } from 'echarts';
import { Observable } from 'rxjs';
import { map, share, startWith, switchMap, tap } from 'rxjs/operators';
import { ApiService } from 'src/app/services/api.service';
import { SeoService } from 'src/app/services/seo.service';
import { formatNumber } from '@angular/common';
import { FormBuilder, FormGroup } from '@angular/forms';
import { download, formatterXAxis, formatterXAxisLabel, formatterXAxisTimeCategory } from 'src/app/shared/graphs.utils';
import { StorageService } from 'src/app/services/storage.service';
import { ActivatedRoute, Router } from '@angular/router';
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
import { StateService } from 'src/app/services/state.service';
@Component({
selector: 'app-block-predictions-graph',
templateUrl: './block-predictions-graph.component.html',
styleUrls: ['./block-predictions-graph.component.scss'],
styles: [`
.loadingGraphs {
position: absolute;
top: 50%;
left: calc(50% - 15px);
z-index: 100;
}
`],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BlockPredictionsGraphComponent implements OnInit {
@Input() right: number | string = 45;
@Input() left: number | string = 75;
miningWindowPreference: string;
radioGroupForm: FormGroup;
chartOptions: EChartsOption = {};
chartInitOptions = {
renderer: 'svg',
};
statsObservable$: Observable<any>;
isLoading = true;
formatNumber = formatNumber;
timespan = '';
chartInstance: any = undefined;
constructor(
@Inject(LOCALE_ID) public locale: string,
private seoService: SeoService,
private apiService: ApiService,
private formBuilder: FormBuilder,
private storageService: StorageService,
private zone: NgZone,
private route: ActivatedRoute,
private stateService: StateService,
private router: Router,
) {
this.radioGroupForm = this.formBuilder.group({ dateSpan: '1y' });
this.radioGroupForm.controls.dateSpan.setValue('1y');
}
ngOnInit(): void {
this.seoService.setTitle($localize`Block predictions accuracy`);
this.miningWindowPreference = '24h';//this.miningService.getDefaultTimespan('24h');
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
this.route
.fragment
.subscribe((fragment) => {
if (['24h', '3d', '1w', '1m', '3m', '6m', '1y', '2y', '3y', 'all'].indexOf(fragment) > -1) {
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
}
});
this.statsObservable$ = this.radioGroupForm.get('dateSpan').valueChanges
.pipe(
startWith(this.radioGroupForm.controls.dateSpan.value),
switchMap((timespan) => {
this.storageService.setValue('miningWindowPreference', timespan);
this.timespan = timespan;
this.isLoading = true;
return this.apiService.getHistoricalBlockPrediction$(timespan)
.pipe(
tap((response) => {
this.prepareChartOptions(response.body);
this.isLoading = false;
}),
map((response) => {
return {
blockCount: parseInt(response.headers.get('x-total-count'), 10),
};
}),
);
}),
share()
);
}
prepareChartOptions(data) {
this.chartOptions = {
animation: false,
grid: {
top: 30,
bottom: 80,
right: this.right,
left: this.left,
},
tooltip: {
show: !this.isMobile(),
trigger: 'axis',
axisPointer: {
type: 'line'
},
backgroundColor: 'rgba(17, 19, 31, 1)',
borderRadius: 4,
shadowColor: 'rgba(0, 0, 0, 0.5)',
textStyle: {
color: '#b1b1b1',
align: 'left',
},
borderColor: '#000',
formatter: (ticks) => {
let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10) * 1000)}</b><br>`;
tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data.value, this.locale, '1.2-2')}%<br>`;
if (['24h', '3d'].includes(this.timespan)) {
tooltip += `<small>` + $localize`At block: ${ticks[0].data.block}` + `</small>`;
} else {
tooltip += `<small>` + $localize`Around block: ${ticks[0].data.block}` + `</small>`;
}
return tooltip;
}
},
xAxis: {
name: formatterXAxisLabel(this.locale, this.timespan),
nameLocation: 'middle',
nameTextStyle: {
padding: [10, 0, 0, 0],
},
type: 'category',
boundaryGap: false,
axisLine: { onZero: true },
axisLabel: {
formatter: val => formatterXAxisTimeCategory(this.locale, this.timespan, parseInt(val, 10)),
align: 'center',
fontSize: 11,
lineHeight: 12,
hideOverlap: true,
padding: [0, 5],
},
data: data.map(prediction => prediction[0])
},
yAxis: [
{
type: 'value',
axisLabel: {
color: 'rgb(110, 112, 121)',
formatter: (val) => {
return `${val}%`;
}
},
splitLine: {
lineStyle: {
type: 'dotted',
color: '#ffffff66',
opacity: 0.25,
}
},
},
],
series: [
{
zlevel: 0,
name: $localize`Match rate`,
data: data.map(prediction => ({
value: prediction[2],
block: prediction[1],
itemStyle: {
color: this.getPredictionColor(prediction[2])
}
})),
type: 'bar',
barWidth: '90%',
},
],
dataZoom: [{
type: 'inside',
realtime: true,
zoomLock: true,
maxSpan: 100,
minSpan: 5,
moveOnMouseMove: false,
}, {
showDetail: false,
show: true,
type: 'slider',
brushSelect: false,
realtime: true,
left: 20,
right: 15,
selectedDataBackground: {
lineStyle: {
color: '#fff',
opacity: 0.45,
},
areaStyle: {
opacity: 0,
}
},
}],
};
}
colorGradient(fadeFraction, rgbColor1, rgbColor2, rgbColor3) {
let color1 = rgbColor1;
let color2 = rgbColor2;
let fade = fadeFraction;
// Do we have 3 colors for the gradient? Need to adjust the params.
if (rgbColor3) {
fade = fade * 2;
// Find which interval to use and adjust the fade percentage
if (fade >= 1) {
fade -= 1;
color1 = rgbColor2;
color2 = rgbColor3;
}
}
const diffRed = color2.red - color1.red;
const diffGreen = color2.green - color1.green;
const diffBlue = color2.blue - color1.blue;
const gradient = {
red: Math.floor(color1.red + (diffRed * fade)),
green: Math.floor(color1.green + (diffGreen * fade)),
blue: Math.floor(color1.blue + (diffBlue * fade)),
};
return 'rgb(' + gradient.red + ',' + gradient.green + ',' + gradient.blue + ')';
}
getPredictionColor(matchRate) {
return this.colorGradient(
Math.pow((100 - matchRate) / 100, 0.5),
{red: 67, green: 171, blue: 71},
{red: 253, green: 216, blue: 53},
{red: 244, green: 0, blue: 0},
);
}
onChartInit(ec) {
this.chartInstance = ec;
this.chartInstance.on('click', (e) => {
this.zone.run(() => {
if (['24h', '3d'].includes(this.timespan)) {
const url = new RelativeUrlPipe(this.stateService).transform(`/block/${e.data.block}`);
this.router.navigate([url]);
}
});
});
}
isMobile() {
return (window.innerWidth <= 767.98);
}
onSaveChart() {
// @ts-ignore
const prevBottom = this.chartOptions.grid.bottom;
const now = new Date();
// @ts-ignore
this.chartOptions.grid.bottom = 40;
this.chartOptions.backgroundColor = '#11131f';
this.chartInstance.setOption(this.chartOptions);
download(this.chartInstance.getDataURL({
pixelRatio: 2,
excludeComponents: ['dataZoom'],
}), `block-fees-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
// @ts-ignore
this.chartOptions.grid.bottom = prevBottom;
this.chartOptions.backgroundColor = 'none';
this.chartInstance.setOption(this.chartOptions);
}
}

View File

@ -79,57 +79,3 @@
}
}
}
.pool-distribution {
min-height: 56px;
display: block;
@media (min-width: 485px) {
display: flex;
flex-direction: row;
}
h5 {
margin-bottom: 10px;
}
.item {
width: 50%;
display: inline-block;
margin: 0px auto 20px;
&:nth-child(2) {
order: 2;
@media (min-width: 485px) {
order: 3;
}
}
&:nth-child(3) {
order: 3;
@media (min-width: 485px) {
order: 2;
display: block;
}
@media (min-width: 768px) {
display: none;
}
@media (min-width: 992px) {
display: block;
}
}
.card-title {
font-size: 1rem;
color: #4a68b9;
}
.card-text {
font-size: 18px;
span {
color: #ffffff66;
font-size: 12px;
}
}
}
}
.skeleton-loader {
width: 100%;
display: block;
max-width: 80px;
margin: 15px auto 3px;
}

View File

@ -79,57 +79,3 @@
}
}
}
.pool-distribution {
min-height: 56px;
display: block;
@media (min-width: 485px) {
display: flex;
flex-direction: row;
}
h5 {
margin-bottom: 10px;
}
.item {
width: 50%;
display: inline-block;
margin: 0px auto 20px;
&:nth-child(2) {
order: 2;
@media (min-width: 485px) {
order: 3;
}
}
&:nth-child(3) {
order: 3;
@media (min-width: 485px) {
order: 2;
display: block;
}
@media (min-width: 768px) {
display: none;
}
@media (min-width: 992px) {
display: block;
}
}
.card-title {
font-size: 1rem;
color: #4a68b9;
}
.card-text {
font-size: 18px;
span {
color: #ffffff66;
font-size: 12px;
}
}
}
}
.skeleton-loader {
width: 100%;
display: block;
max-width: 80px;
margin: 15px auto 3px;
}

View File

@ -50,7 +50,7 @@
<tbody>
<tr>
<td class="td-width" i18n="block.hash">Hash</td>
<td><a [routerLink]="['/block/' | relativeUrl, block.id]" title="{{ block.id }}">{{ block.id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="block.id"></app-clipboard></td>
<td>&lrm;<a [routerLink]="['/block/' | relativeUrl, block.id]" title="{{ block.id }}">{{ block.id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="block.id"></app-clipboard></td>
</tr>
<tr>
<td i18n="block.timestamp">Timestamp</td>
@ -358,7 +358,7 @@
<div class="text-center">
<span i18n="error.general-loading-data">Error loading data.</span>
<br><br>
<i>{{ error.code }}: {{ error.error }}</i>
<i>{{ error.status }}: {{ error.error }}</i>
</div>
</ng-template>

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ClipboardComponent } from './clipboard.component';
describe('ClipboardComponent', () => {
let component: ClipboardComponent;
let fixture: ComponentFixture<ClipboardComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ClipboardComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ClipboardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,4 +1,4 @@
<div *ngIf="stateService.env.MINING_DASHBOARD" class="mb-3 d-flex menu" style="padding: 0px 35px;">
<div *ngIf="stateService.env.MINING_DASHBOARD" class="mb-3 d-inline-flex menu" style="padding: 0px 35px;">
<a routerLinkActive="active" class="btn btn-primary w-50 mr-1"
[routerLink]="['/graphs/mempool' | relativeUrl]">Mempool</a>
<div ngbDropdown class="w-50">
@ -18,6 +18,8 @@
[routerLink]="['/graphs/mining/block-rewards' | relativeUrl]" i18n="mining.block-rewards">Block Rewards</a>
<a class="dropdown-item" routerLinkActive="active"
[routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]" i18n="mining.block-sizes-weights">Block Sizes and Weights</a>
<a class="dropdown-item" routerLinkActive="active"
[routerLink]="['/graphs/mining/block-predictions' | relativeUrl]" i18n="mining.block-prediction-accuracy">Blocks Predictions Accuracy</a>
</div>
</div>
</div>

View File

@ -11,6 +11,7 @@ import { StorageService } from 'src/app/services/storage.service';
import { MiningService } from 'src/app/services/mining.service';
import { download } from 'src/app/shared/graphs.utils';
import { ActivatedRoute } from '@angular/router';
import { StateService } from 'src/app/services/state.service';
@Component({
selector: 'app-hashrate-chart',
@ -47,7 +48,7 @@ export class HashrateChartComponent implements OnInit {
formatNumber = formatNumber;
timespan = '';
chartInstance: any = undefined;
maResolution: number = 30;
network = '';
constructor(
@Inject(LOCALE_ID) public locale: string,
@ -57,17 +58,20 @@ export class HashrateChartComponent implements OnInit {
private storageService: StorageService,
private miningService: MiningService,
private route: ActivatedRoute,
private stateService: StateService
) {
}
ngOnInit(): void {
this.stateService.networkChanged$.subscribe((network) => this.network = network);
let firstRun = true;
if (this.widget) {
this.miningWindowPreference = '1y';
} else {
this.seoService.setTitle($localize`:@@3510fc6daa1d975f331e3a717bdf1a34efa06dff:Hashrate & Difficulty`);
this.miningWindowPreference = this.miningService.getDefaultTimespan('1m');
this.miningWindowPreference = this.miningService.getDefaultTimespan('3m');
}
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
@ -124,17 +128,14 @@ export class HashrateChartComponent implements OnInit {
++diffIndex;
}
this.maResolution = 30;
if (["3m", "6m"].includes(this.timespan)) {
this.maResolution = 7;
}
let maResolution = 15;
const hashrateMa = [];
for (let i = this.maResolution - 1; i < data.hashrates.length; ++i) {
for (let i = maResolution - 1; i < data.hashrates.length; ++i) {
let avg = 0;
for (let y = this.maResolution - 1; y >= 0; --y) {
for (let y = maResolution - 1; y >= 0; --y) {
avg += data.hashrates[i - y].avgHashrate;
}
avg /= this.maResolution;
avg /= maResolution;
hashrateMa.push([data.hashrates[i].timestamp * 1000, avg]);
}
@ -276,17 +277,17 @@ export class HashrateChartComponent implements OnInit {
},
},
{
name: $localize`::Difficulty`,
name: $localize`:@@25148835d92465353fc5fe8897c27d5369978e5a:Difficulty`,
inactiveColor: 'rgb(110, 112, 121)',
textStyle: {
textStyle: {
color: 'white',
},
icon: 'roundRect',
},
{
name: $localize`Hashrate` + ` (MA${this.maResolution})`,
name: $localize`Hashrate (MA)`,
inactiveColor: 'rgb(110, 112, 121)',
textStyle: {
textStyle: {
color: 'white',
},
icon: 'roundRect',
@ -295,11 +296,18 @@ export class HashrateChartComponent implements OnInit {
},
},
],
selected: JSON.parse(this.storageService.getValue('hashrate_difficulty_legend')) ?? {
'$localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`': true,
'$localize`::Difficulty`': this.network === '',
'$localize`Hashrate (MA)`': true,
},
},
yAxis: data.hashrates.length === 0 ? undefined : [
{
min: (value) => {
return value.min * 0.9;
const selectedPowerOfTen: any = selectPowerOfTen(value.min);
const newMin = Math.floor(value.min / selectedPowerOfTen.divider / 10);
return newMin * selectedPowerOfTen.divider * 10;
},
type: 'value',
axisLabel: {
@ -363,7 +371,7 @@ export class HashrateChartComponent implements OnInit {
},
{
zlevel: 2,
name: $localize`Hashrate` + ` (MA${this.maResolution})`,
name: $localize`Hashrate (MA)`,
showSymbol: false,
symbol: 'none',
data: data.hashrateMa,
@ -404,6 +412,10 @@ export class HashrateChartComponent implements OnInit {
onChartInit(ec) {
this.chartInstance = ec;
this.chartInstance.on('legendselectchanged', (e) => {
this.storageService.setValue('hashrate_difficulty_legend', JSON.stringify(e.selected));
});
}
isMobile() {

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MempoolBlockComponent } from './mempool-block.component';
describe('MempoolBlockComponent', () => {
let component: MempoolBlockComponent;
let fixture: ComponentFixture<MempoolBlockComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MempoolBlockComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MempoolBlockComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -40,7 +40,7 @@
<div class="card">
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2">
<app-hashrate-chart [widget]="true"></app-hashrate-chart>
<div class="mt-1"><a [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]" i18n="dashboard.view-more">View more &raquo;</a></div>
<div class="mt-1"><a [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]" fragment="1y" i18n="dashboard.view-more">View more &raquo;</a></div>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
<div class="container-xl">
<h1 i18n="shared.broadcast-transaction|Broadcast Transaction">Broadcast Transaction</h1>
<h1 class="text-left" i18n="shared.broadcast-transaction|Broadcast Transaction">Broadcast Transaction</h1>
<form [formGroup]="pushTxForm" (submit)="pushTxForm.valid && postTx()" novalidate>
<div class="mb-3">

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { QrcodeComponent } from './qrcode.component';
describe('QrcodeComponent', () => {
let component: QrcodeComponent;
let fixture: ComponentFixture<QrcodeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ QrcodeComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(QrcodeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -24,7 +24,7 @@ export class QrcodeComponent implements AfterViewInit {
return;
}
const opts: QRCode.QRCodeRenderersOptions = {
errorCorrectionLevel: 'H',
errorCorrectionLevel: 'L',
margin: 0,
color: {
dark: '#000',
@ -38,7 +38,11 @@ export class QrcodeComponent implements AfterViewInit {
}
const address = this.data;
if (this.data.indexOf('bc1') === 0 || this.data.indexOf('tb1') === 0) {
if (
this.data.indexOf('bc1') === 0 ||
this.data.indexOf('tb1') === 0 ||
this.data.indexOf('bcrt1') === 0
) {
address.toUpperCase();
}

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SearchFormComponent } from './search-form.component';
describe('SearchFormComponent', () => {
let component: SearchFormComponent;
let fixture: ComponentFixture<SearchFormComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SearchFormComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SearchFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { StartComponent } from './start.component';
describe('StartComponent', () => {
let component: StartComponent;
let fixture: ComponentFixture<StartComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ StartComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(StartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -66,9 +66,9 @@
</ng-template>
</ng-template>
<ng-template #defaultAddress>
<a *ngIf="vin.prevout.scriptpubkey_address; else vinScriptPubkeyType" [routerLink]="['/address/' | relativeUrl, vin.prevout.scriptpubkey_address]" title="{{ vin.prevout.scriptpubkey_address }}">
<a class="shortable-address" *ngIf="vin.prevout.scriptpubkey_address; else vinScriptPubkeyType" [routerLink]="['/address/' | relativeUrl, vin.prevout.scriptpubkey_address]" title="{{ vin.prevout.scriptpubkey_address }}">
<span class="d-block d-lg-none">{{ vin.prevout.scriptpubkey_address | shortenString : 16 }}</span>
<span class="d-none d-lg-flex justify-content-start">
<span class="d-none d-lg-inline-flex justify-content-start">
<span class="addr-left flex-grow-1" [style]="vin.prevout.scriptpubkey_address.length > 40 ? 'max-width: 235px' : ''">{{ vin.prevout.scriptpubkey_address }}</span>
<span *ngIf="vin.prevout.scriptpubkey_address.length > 40" class="addr-right">{{ vin.prevout.scriptpubkey_address | capAddress: 40: 10 }}</span>
</span>
@ -164,9 +164,9 @@
'highlight': vout.scriptpubkey_address === this.address && this.address !== ''
}">
<td>
<a *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
<a class="shortable-address" *ngIf="vout.scriptpubkey_address; else scriptpubkey_type" [routerLink]="['/address/' | relativeUrl, vout.scriptpubkey_address]" title="{{ vout.scriptpubkey_address }}">
<span class="d-block d-lg-none">{{ vout.scriptpubkey_address | shortenString : 16 }}</span>
<span class="d-none d-lg-flex justify-content-start">
<span class="d-none d-lg-inline-flex justify-content-start">
<span class="addr-left flex-grow-1" [style]="vout.scriptpubkey_address.length > 40 ? 'max-width: 235px' : ''">{{ vout.scriptpubkey_address }}</span>
<span *ngIf="vout.scriptpubkey_address.length > 40" class="addr-right">{{ vout.scriptpubkey_address | capAddress: 40: 10 }}</span>
</span>

View File

@ -6017,6 +6017,20 @@ export const faqData = [
fragment: "what-are-mining-pools",
title: "What are mining pools?",
},
{
type: "endpoint",
category: "basics",
showConditions: bitcoinNetworks,
fragment: "what-are-vb-wu",
title: "What are virtual bytes (vB) and weight units (WU)?",
},
{
type: "endpoint",
category: "basics",
showConditions: bitcoinNetworks,
fragment: "what-is-svb",
title: "What is sat/vB?",
},
{
type: "endpoint",
category: "basics",

View File

@ -134,6 +134,19 @@
Mining pools are groups of miners that combine their computational power in order to increase the probability of finding new blocks.
</ng-template>
<ng-template type="what-are-vb-wu">
<p>Virtual bytes (vB) and weight units (WU) are used to measure the size of transactions and blocks on the Bitcoin network.</p>
<p>A Bitcoin transaction's size in the blockchain is <i>not</i> determined how much bitcoin it transfers—instead, a transaction's size is determined by technical factors such as how many inputs and outputs it has, how many signatures it has, and the format it uses (legacy, SegWit, etc). Since space in the Bitcoin blockchain is limited, bigger transactions pay more in mining fees than smaller transactions.</p>
<p>Block sizes are limited to 4,000,000 WU (or 1,000,000 vB since 1 vB = 4 WU).</p>
<p>Transaction sizes and block sizes used to be measured in plain bytes, but virtual bytes and weight units were devised to maintain backward compatibility after the SegWit upgrade in 2017. See <a href="https://programmingbitcoin.com/understanding-segwit-block-size" target="_blank">this post</a> for more details.</p>
</ng-template>
<ng-template type="what-is-svb">
<p>The priority of a pending Bitcoin transaction is determined by its feerate. Feerates are measured in sat/vB.</p>
<p>Using a higher sat/vB feerate for a Bitcoin transaction will generally result in quicker confirmation than using a lower feerate. But feerates change all the time, so it's important to check suggested feerates right before making a transaction to <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="why-is-transaction-stuck-in-mempool">avoid it from getting stuck</a>.</p>
<p>There are feerate estimates on the top of <a [routerLink]="['/' | relativeUrl]">the main dashboard</a> you can use as a guide. See <a [routerLink]="['/docs/faq' | relativeUrl]" fragment="looking-up-fee-estimates">this FAQ</a> for more on picking the right feerate.</p>
</ng-template>
<ng-template type="what-is-full-mempool">
<p>When a Bitcoin transaction is made, it is stored in a Bitcoin node's mempool before it is confirmed into a block. When the rate of incoming transactions exceeds the rate transactions are confirmed, the mempool grows in size.</p><p>The default maximum size of a Bitcoin node's mempool is 300MB, so when there are 300MB of transactions in the mempool, we say it's "full".</p>
</ng-template>

View File

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FiatComponent } from './fiat.component';
describe('FiatComponent', () => {
let component: FiatComponent;
let fixture: ComponentFixture<FiatComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FiatComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FiatComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -22,6 +22,7 @@ import { DashboardComponent } from '../dashboard/dashboard.component';
import { MiningDashboardComponent } from '../components/mining-dashboard/mining-dashboard.component';
import { HashrateChartComponent } from '../components/hashrate-chart/hashrate-chart.component';
import { HashrateChartPoolsComponent } from '../components/hashrates-chart-pools/hashrate-chart-pools.component';
import { BlockPredictionsGraphComponent } from '../components/block-predictions-graph/block-predictions-graph.component';
import { CommonModule } from '@angular/common';
@NgModule({
@ -47,6 +48,7 @@ import { CommonModule } from '@angular/common';
LbtcPegsGraphComponent,
HashrateChartComponent,
HashrateChartPoolsComponent,
BlockPredictionsGraphComponent,
],
imports: [
CommonModule,

View File

@ -1,5 +1,6 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { BlockPredictionsGraphComponent } from '../components/block-predictions-graph/block-predictions-graph.component';
import { BlockFeeRatesGraphComponent } from '../components/block-fee-rates-graph/block-fee-rates-graph.component';
import { BlockFeesGraphComponent } from '../components/block-fees-graph/block-fees-graph.component';
import { BlockRewardsGraphComponent } from '../components/block-rewards-graph/block-rewards-graph.component';
@ -92,6 +93,10 @@ const routes: Routes = [
path: '',
redirectTo: 'mempool',
},
{
path: 'mining/block-predictions',
component: BlockPredictionsGraphComponent,
},
]
},
{

View File

@ -221,6 +221,13 @@ export class ApiService {
);
}
getHistoricalBlockPrediction$(interval: string | undefined) : Observable<any> {
return this.httpClient.get<any[]>(
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/predictions` +
(interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
);
}
getRewardStats$(blockCount: number = 144): Observable<RewardStats> {
return this.httpClient.get<RewardStats>(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/reward-stats/${blockCount}`);
}

View File

@ -1,8 +0,0 @@
import { AbsolutePipe } from './absolute.pipe';
describe('AbsolutePipe', () => {
it('create an instance', () => {
const pipe = new AbsolutePipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -1,8 +0,0 @@
import { AsmStylerPipe } from './asm-styler.pipe';
describe('OpcodesStylerPipe', () => {
it('create an instance', () => {
const pipe = new AsmStylerPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -1,8 +0,0 @@
import { FeeRoundingPipe } from './fee-rounding.pipe';
describe('FeeRoundingPipe', () => {
it('create an instance', () => {
const pipe = new FeeRoundingPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -1,8 +0,0 @@
import { Hex2asciiPipe } from './hex2ascii.pipe';
describe('Hex2asciiPipe', () => {
it('create an instance', () => {
const pipe = new Hex2asciiPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -1,8 +0,0 @@
import { RelativeUrlPipe } from './relative-url.pipe';
describe('RelativeUrlPipe', () => {
it('create an instance', () => {
const pipe = new RelativeUrlPipe();
expect(pipe).toBeTruthy();
});
});

View File

@ -852,6 +852,40 @@ th {
}
}
.fee-progress-bar {
@extend .fee-progress-bar;
&.priority {
@media (767px < width < 992px), (width < 576px) {
width: 100%;
}
width: 75%;
border-radius: 10px 0px 0px 10px !important;
}
}
.fees-wrapper-tooltip-chart {
@extend .fees-wrapper-tooltip-chart;
.title {
direction: rtl;
}
}
.btn-link {
padding: 0.1rem 0.5rem 0.25rem 0 !important;
}
.shortable-address {
direction: ltr;
}
.lastest-blocks-table {
@extend .lastest-blocks-table;
.table-cell-mined {
@extend .table-cell-mined;
text-align: right !important;
}
}
.mempool-graph {
@extend .mempool-graph;
direction: ltr;

View File

@ -1,22 +0,0 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(), {
teardown: { destroyAfterEach: false }
}
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@ -1,158 +0,0 @@
{
"extends": "tslint:recommended",
"rules": {
"align": {
"options": [
"parameters",
"statements"
]
},
"array-type": false,
"forin": false,
"arrow-parens": false,
"arrow-return-shorthand": true,
"curly": true,
"no-bitwise": false,
"deprecation": {
"severity": "warning"
},
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"eofline": true,
"import-spacing": true,
"indent": {
"options": [
"spaces"
]
},
"object-literal-shorthand": false,
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"import-blacklist": [
true,
"rxjs/Rx"
],
"interface-name": false,
"max-classes-per-file": false,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-consecutive-blank-lines": false,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-empty": false,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-switch-case-fall-through": true,
"no-var-requires": false,
"object-literal-key-quotes": [
false,
"as-needed"
],
"object-literal-sort-keys": false,
"ordered-imports": false,
"quotemark": [
true,
"single"
],
"semicolon": {
"options": [
"always"
]
},
"space-before-function-paren": {
"options": {
"anonymous": "never",
"asyncArrow": "always",
"constructor": "never",
"method": "never",
"named": "never"
}
},
"trailing-comma": false,
"no-conflicting-lifecycle": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"typedef-whitespace": {
"options": [
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
]
},
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true
, "variable-name": {
"options": [
"ban-keywords",
"check-format",
"allow-pascal-case"
]
},
"whitespace": {
"options": [
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type",
"check-typecast"
]
}
},
"rulesDirectory": [
"codelyzer"
]
}

View File

@ -1,4 +1,4 @@
#!/usr/local/bin/zsh
#!/usr/bin/env zsh
cd "${HOME}/electrs"
#source "${HOME}/.cargo/env"
#export PATH="${HOME}/.cargo/bin:${PATH}"

View File

@ -1,4 +1,4 @@
#!/usr/local/bin/zsh
#!/usr/bin/env zsh
cd "${HOME}/electrs"
#source "${HOME}/.cargo/env"
#export PATH="${HOME}/.cargo/bin:${PATH}"

View File

@ -1,4 +1,4 @@
#!/usr/local/bin/zsh
#!/usr/bin/env zsh
cd "${HOME}/electrs"
#source "${HOME}/.cargo/env"
#export PATH="${HOME}/.cargo/bin:${PATH}"

View File

@ -1,4 +1,4 @@
#!/usr/local/bin/zsh
#!/usr/bin/env zsh
cd "${HOME}/electrs"
#source $HOME/.cargo/env
#export PATH=$HOME/.cargo/bin:$PATH

View File

@ -6,11 +6,13 @@ case `uname -s` in
FreeBSD)
OS=FreeBSD
NPROC=$(sysctl hw.ncpu | awk '{print $2}')
;;
Linux)
if [ "$(grep -Ei 'debian|buntu|mint' /etc/*release)" ]; then
OS=Debian
NPROC=$(nproc --all)
else
echo "Your distribution of Linux is not yet supported by this installation script"
exit 1
@ -39,6 +41,7 @@ ELEMENTS_INSTALL=ON
# configure 4 network instances
BITCOIN_MAINNET_ENABLE=ON
BITCOIN_MAINNET_MINFEE_ENABLE=ON
BITCOIN_TESTNET_ENABLE=ON
BITCOIN_SIGNET_ENABLE=ON
BISQ_MAINNET_ENABLE=ON
@ -682,6 +685,7 @@ $CUT >$input <<-EOF
Tor:Enable Tor v3 HS Onion:ON
Certbot:Enable HTTPS using Certbot:ON
Mainnet:Enable Bitcoin Mainnet:ON
Mainnet-Minfee:Enable Bitcoin Mainnet Minfee:ON
Testnet:Enable Bitcoin Testnet:ON
Liquid:Enable Elements Liquid:ON
Bisq:Enable Bisq:ON
@ -725,6 +729,12 @@ else
BITCOIN_MAINNET_ENABLE=OFF
fi
if grep Mainnet-Minfee $tempfile >/dev/null 2>&1;then
BITCOIN_MAINNET_MINFEE_ENABLE=ON
else
BITCOIN_MAINNET_MINFEE_ENABLE=OFF
fi
if grep Testnet $tempfile >/dev/null 2>&1;then
BITCOIN_TESTNET_ENABLE=ON
else
@ -964,7 +974,7 @@ if [ "${BITCOIN_INSTALL}" = ON ];then
echo "[*] Building Bitcoin from source repo"
osSudo "${BITCOIN_USER}" sh -c "cd ${BITCOIN_REPO_NAME} && ./autogen.sh --quiet"
osSudo "${BITCOIN_USER}" sh -c "cd ${BITCOIN_REPO_NAME} && MAKE=gmake CC=cc CXX=c++ CPPFLAGS=-I/usr/local/include ./configure --with-gui=no --disable-wallet --disable-tests"
osSudo "${BITCOIN_USER}" sh -c "cd ${BITCOIN_REPO_NAME} && gmake -j48"
osSudo "${BITCOIN_USER}" sh -c "cd ${BITCOIN_REPO_NAME} && gmake -j${NPROC}"
echo "[*] Installing Bitcoin binaries into OS"
osSudo "${ROOT_USER}" sh -c "cd ${BITCOIN_HOME}/${BITCOIN_REPO_NAME} && gmake install"
@ -1009,7 +1019,7 @@ if [ "${ELEMENTS_INSTALL}" = ON ];then
echo "[*] Building Elements from source repo"
osSudo "${ELEMENTS_USER}" sh -c "cd ${ELEMENTS_REPO_NAME} && ./autogen.sh --quiet"
osSudo "${ELEMENTS_USER}" sh -c "cd ${ELEMENTS_REPO_NAME} && MAKE=gmake CC=cc CXX=c++ CPPFLAGS=-I/usr/local/include ./configure --with-gui=no --disable-wallet --disable-tests"
osSudo "${ELEMENTS_USER}" sh -c "cd ${ELEMENTS_REPO_NAME} && gmake -j48"
osSudo "${ELEMENTS_USER}" sh -c "cd ${ELEMENTS_REPO_NAME} && gmake -j${NPROC}"
echo "[*] Installing Elements binaries into OS"
osSudo "${ROOT_USER}" sh -c "cd ${ELEMENTS_HOME}/${ELEMENTS_REPO_NAME} && gmake install"
@ -1048,7 +1058,7 @@ case $OS in
;;
Debian)
echo "[*] Installing Rust from rustup.rs"
osSudo "${BITCOIN_USER}" sh -c "cd ${BITCOIN_ELECTRS_HOME} && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"
osSudo "${BITCOIN_USER}" sh -c "cd ${BITCOIN_ELECTRS_HOME} && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y"
;;
esac
@ -1095,15 +1105,6 @@ echo "[*] Cloning Liquid Asset Registry testnet repo from ${LIQUIDTESTNET_ASSET_
osSudo "${ELEMENTS_USER}" git config --global advice.detachedHead false
osSudo "${ELEMENTS_USER}" git clone "${LIQUIDTESTNET_ASSET_REGISTRY_DB_URL}" "${ELEMENTS_HOME}/${LIQUIDTESTNET_ASSET_REGISTRY_DB_NAME}"
case $OS in
FreeBSD)
;;
Debian)
echo "[*] Installing Rust from rustup.rs"
osSudo "${BITCOIN_USER}" sh -c "cd ${BITCOIN_ELECTRS_HOME} && curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"
;;
esac
echo "[*] Building Liquid Electrs release binary"
osSudo "${ELEMENTS_USER}" sh -c "cd ${ELEMENTS_ELECTRS_HOME} && cargo run --release --features liquid --bin electrs -- --network liquid --version" || true
@ -1214,6 +1215,24 @@ if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
esac
fi
#######################################
# Bitcoin instance for Mainnet Minfee #
#######################################
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
echo "[*] Installing Bitcoin Minfee service"
case $OS in
FreeBSD)
echo "[*] FIXME: Bitcoin Minfee service must be installed manually on FreeBSD"
;;
Debian)
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/linux/bitcoin-minfee.service" "${DEBIAN_SERVICE_HOME}"
;;
esac
fi
################################
# Bitcoin instance for Testnet #
################################
@ -1277,9 +1296,16 @@ if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-mainnet" "${BITCOIN_ELECTRS_HOME}"
echo "[*] Installing Bitcoin crontab"
# FIXME: must only crontab enabled daemons
osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.crontab"
osSudo "${ROOT_USER}" crontab -u "${MINFEE_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/minfee.crontab"
case $OS in
FreeBSD)
echo [*] FIXME: must only crontab enabled daemons
osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.crontab"
osSudo "${ROOT_USER}" crontab -u "${MINFEE_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/minfee.crontab"
;;
Debian)
(crontab -l ; echo "@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
;;
esac
echo "[*] Configuring Bitcoin Mainnet RPC credentials in electrs start script"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
@ -1295,6 +1321,13 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
echo "[*] Installing Bitcoin Testnet electrs start script"
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-testnet" "${BITCOIN_ELECTRS_HOME}"
case $OS in
Debian)
echo "[*] Installing Bitcoin-testnet crontab"
(crontab -l ; echo "@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/electrs-start-testnet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
;;
esac
echo "[*] Configuring Bitcoin Testnet RPC credentials in electrs start script"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
@ -1309,6 +1342,13 @@ if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
echo "[*] Installing Bitcoin Signet electrs start script"
osSudo "${ROOT_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-signet" "${BITCOIN_ELECTRS_HOME}"
case $OS in
Debian)
echo "[*] Installing Bitcoin-signet crontab"
(crontab -l ; echo "@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/electrs-start-signet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
;;
esac
echo "[*] Configuring Bitcoin Signet RPC credentials in electrs start script"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
@ -1324,8 +1364,15 @@ if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" install -c -o "${ELEMENTS_USER}" -g "${ELEMENTS_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-liquid" "${ELEMENTS_ELECTRS_HOME}"
echo "[*] Installing Elements crontab"
# FIXME: must only crontab enabled daemons
osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/elements.crontab"
case $OS in
FreeBSD)
echo [*] FIXME: must only crontab enabled daemons
osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/elements.crontab"
;;
Debian)
(crontab -l ; echo "6 * * * * cd $HOME/asset_registry_db && git pull origin master >/dev/null 2>&1") | osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" -
;;
esac
echo "[*] Configuring Elements Liquid RPC credentials in electrs start script"
osSudo "${ROOT_USER}" sed -i.orig "s/__ELEMENTS_RPC_USER__/${ELEMENTS_RPC_USER}/" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquid"
@ -1341,6 +1388,13 @@ if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
echo "[*] Installing Elements Liquid Testnet electrs start script"
osSudo "${ROOT_USER}" install -c -o "${ELEMENTS_USER}" -g "${ELEMENTS_GROUP}" -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/electrs-start-liquidtestnet" "${ELEMENTS_ELECTRS_HOME}"
case $OS in
Debian)
echo "[*] Installing Elements-testnet crontab"
(crontab -l ; echo "6 * * * * cd $HOME/asset_registry_testnet_db && git pull origin master >/dev/null 2>&1") | osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" -
;;
esac
echo "[*] Installing Elements Liquid Testnet RPC credentials"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${ELEMENTS_HOME}/elements.conf"
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${ELEMENTS_HOME}/elements.conf"
@ -1480,23 +1534,21 @@ case $OS in
fi
if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" systemctl enable bitcoin.service
osSudo "${ROOT_USER}" systemctl enable mempool.service
fi
if [ "${BITCOIN_MAINNET_MINFEE_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" systemctl enable bitcoin-minfee.service
fi
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" systemctl enable bitcoin-testnet.service
osSudo "${ROOT_USER}" systemctl enable mempool-testnet.service
fi
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" systemctl enable bitcoin-signet.service
osSudo "${ROOT_USER}" systemctl enable mempool-signet.service
fi
if [ "${BISQ_MAINNET_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" systemctl enable bisq.service
osSudo "${ROOT_USER}" systemctl enable mempool-bisq.service
fi
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" systemctl enable liquid.service
osSudo "${ROOT_USER}" systemctl enable mempool-liquid.service
fi
;;
esac

View File

@ -0,0 +1,22 @@
[Unit]
Description=Bitcoind-minfee
After=network.target
[Service]
ExecStart=/usr/local/bin/bitcoind -daemon -printtoconsole -pid=/minfee/bitcoind-minfee.pid
ExecStop=/usr/local/bin/bitcoin-cli stop
Type=forking
PIDFile=/minfee/bitcoind.pid
Restart=on-failure
User=minfee
Group=minfee
PrivateTmp=true
ProtectSystem=full
NoNewPrivileges=true
PrivateDevices=true
[Install]
WantedBy=multi-user.target

View File

@ -1,4 +1,4 @@
#!/usr/local/bin/zsh
#!/usr/bin/env zsh
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:$HOME/bin
HOSTNAME=$(hostname)
LOCATION=$(hostname|cut -d . -f2)
@ -16,10 +16,13 @@ if [ -f "${LOCKFILE}" ];then
exit 1
fi
trap "rv=\$?; rm -rf "${LOCKFILE}"; exit \$rv" INT TERM EXIT
# on exit, remove lockfile but preserve exit code
trap "rv=\$?; rm -f "${LOCKFILE}"; exit \$rv" INT TERM EXIT
# create lockfile
touch "${LOCKFILE}"
# notify logged in users
echo "Upgrading mempool to ${REF}" | wall
update_repo()
@ -84,25 +87,48 @@ ship_frontend()
rsync -av "./dist/mempool/browser/" "${HOME}/public_html/${site}/" || exit 1
}
# load nvm if necessary
export NVM_DIR="${HOME}/.nvm"
source "${NVM_DIR}/nvm.sh"
for target in mainnet testnet signet liquid liquidtestnet bisq;do
update_repo "${target}"
# what to look for
frontends=(mainnet liquid bisq)
backends=(mainnet testnet signet liquid liquidtestnet bisq)
frontend_repos=()
backend_repos=()
# find which frontend repos we have
for repo in $frontends;do
[ -d "${repo}" ] && frontend_repos+="${repo}"
done
for target in mainnet testnet signet liquid liquidtestnet bisq;do
build_backend "${target}"
# find which backend repos we have
for repo in $backends;do
[ -d "${repo}" ] && backend_repos+="${repo}"
[ -d "${repo}-lightning" ] && backend_repos+="${repo}-lightning"
done
for target in mainnet liquid bisq;do
build_frontend "${target}"
# update all repos
for repo in $backend_repos;do
update_repo "${repo}"
done
# build backends
for repo in $backend_repos;do
build_backend "${repo}"
done
# build frontends
for repo in $frontend_repos;do
build_frontend "${repo}"
done
# ship frontend dist folders to public_html
for target in mainnet liquid bisq;do
ship_frontend "${target}"
done
# notify everyone
echo "${HOSTNAME} updated to \`${REF}\` @ \`${HASH}\`" | /usr/local/bin/keybase chat send --nonblock --channel general mempool.dev
echo "${HOSTNAME} updated to \`${REF}\` @ \`${HASH}\`" | /usr/local/bin/keybase chat send --nonblock --channel general "mempool.ops.${LOCATION}"

View File

@ -1,8 +1,8 @@
#!/usr/local/bin/zsh
#!/usr/bin/env zsh
export NVM_DIR="$HOME/.nvm"
source "$NVM_DIR/nvm.sh"
for site in mainnet liquid testnet bisq signet liquidtestnet
do
for site in mainnet mainnet-lightning testnet testnet-lightning signet signet-lightning bisq liquid liquidtestnet;do
cd "${HOME}/${site}/backend/" && \
screen -dmS "${site}" sh -c 'while true;do npm run start-production;sleep 1;done'
done

View File

@ -4,8 +4,6 @@ tcp_nopush on;
tcp_nodelay on;
server_tokens off;
server_name_in_redirect off;
include /usr/local/etc/nginx/mime.types;
default_type application/octet-stream;
# default logs
access_log /var/log/nginx/access.log;

View File

@ -0,0 +1,20 @@
# route lightning API endpoints to lightning backend
location /api/v1/lightning {
try_files /dev/null @mempool-api-v1-lightning;
}
location @mempool-api-v1-lightning {
proxy_pass $mempoolMainnetLightning;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 10s;
proxy_redirect off;
expires 10s;
}

View File

@ -1,26 +1,51 @@
location /api/v1/statistics {
try_files /dev/null @mempool-api-v1-warmcache;
}
location /api/v1/mining {
try_files /dev/null @mempool-api-v1-warmcache;
}
location /api/v1/block/ {
try_files /dev/null @mempool-api-v1-forevercache;
}
location /api/v1 {
try_files /dev/null @mempool-api-v1-coldcache;
}
location /api/block/ {
rewrite ^/api/(.*) /$1 break;
try_files /dev/null @electrs-api-forevercache;
}
location /api/ {
rewrite ^/api/(.*) /$1 break;
try_files /dev/null @electrs-api-nocache;
###########
# mempool #
###########
# websocket has special HTTP headers
location /api/v1/ws {
try_files /dev/null @mempool-api-v1-websocket;
}
location @mempool-api-v1-forevercache {
proxy_pass $mempoolBackend;
# warm cache mining and mempool API responses
location /api/v1/statistics {
try_files /dev/null @mempool-api-v1-cache-warm;
}
location /api/v1/mining {
try_files /dev/null @mempool-api-v1-cache-warm;
}
# it's ok to cache blockchain data "forever", so we do 30d
location /api/v1/block/ {
try_files /dev/null @mempool-api-v1-cache-forever;
}
# everything else gets "normal" cache
location /api/v1 {
try_files /dev/null @mempool-api-v1-cache-normal;
}
###########
# esplora #
###########
# it's ok to cache blockchain data "forever", so we do 30d
location /api/block/ {
rewrite ^/api/(.*) /$1 break;
try_files /dev/null @esplora-api-cache-forever;
}
# other API responses cannot be cached
location /api/ {
rewrite ^/api/(.*) /$1 break;
try_files /dev/null @esplora-api-cache-disabled;
}
###########
# routing #
###########
location @mempool-api-v1-websocket {
proxy_pass $mempoolMainnet;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
@ -29,8 +54,16 @@ location @mempool-api-v1-forevercache {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-Proto $scheme;
}
location @mempool-api-v1-cache-forever {
proxy_pass $mempoolMainnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
@ -40,18 +73,14 @@ location @mempool-api-v1-forevercache {
expires 30d;
}
location @mempool-api-v1-warmcache {
proxy_pass $mempoolBackend;
proxy_http_version 1.1;
location @mempool-api-v1-cache-warm {
proxy_pass $mempoolMainnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
@ -59,18 +88,14 @@ location @mempool-api-v1-warmcache {
proxy_redirect off;
}
location @mempool-api-v1-coldcache {
proxy_pass $mempoolBackend;
proxy_http_version 1.1;
location @mempool-api-v1-cache-normal {
proxy_pass $mempoolMainnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_cache api;
proxy_cache_valid 200 10s;
proxy_redirect off;
@ -78,54 +103,42 @@ location @mempool-api-v1-coldcache {
expires 10s;
}
location @mempool-api-v1-nocache {
proxy_pass $mempoolBackend;
proxy_http_version 1.1;
location @mempool-api-v1-cache-disabled {
proxy_pass $mempoolMainnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_redirect off;
proxy_buffering off;
expires -1;
}
location @electrs-api-nocache {
proxy_pass $electrsBackend;
proxy_http_version 1.1;
location @esplora-api-cache-disabled {
proxy_pass $esploraMainnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_redirect off;
proxy_buffering off;
expires -1;
}
location @electrs-api-forevercache {
proxy_pass $electrsBackend;
proxy_http_version 1.1;
location @esplora-api-cache-forever {
proxy_pass $esploraMainnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;

View File

@ -1,12 +1,150 @@
###########
# mempool #
###########
# websocket has special HTTP headers
location /liquid/api/v1/ws {
proxy_pass http://mempool-liquid-mainnet/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
rewrite ^/liquid/(.*) /$1 break;
try_files /dev/null @mempool-liquid-api-v1-websocket;
}
# warm cache mempool API responses
location /liquid/api/v1/statistics {
rewrite ^/liquid/(.*) /$1 break;
try_files /dev/null @mempool-liquid-api-v1-cache-warm;
}
# it's ok to cache blockchain data "forever", so we do 30d
location /liquid/api/v1/block/ {
rewrite ^/liquid/(.*) /$1 break;
try_files /dev/null @mempool-liquid-api-v1-cache-forever;
}
# everything else gets "normal" cache
location /liquid/api/v1 {
proxy_pass http://mempool-liquid-mainnet/api/v1;
rewrite ^/liquid/(.*) /$1 break;
try_files /dev/null @mempool-liquid-api-v1-cache-normal;
}
###########
# esplora #
###########
# it's ok to cache blockchain data "forever", so we do 30d
location /liquid/api/block/ {
rewrite ^/liquid/api/(.*) /$1 break;
try_files /dev/null @esplora-liquid-api-cache-forever;
}
# other API responses cannot be cached
location /liquid/api/ {
proxy_pass http://electrs-liquid-mainnet/;
rewrite ^/liquid/api/(.*) /$1 break;
try_files /dev/null @esplora-liquid-api-cache-disabled;
}
###########
# routing #
###########
location @mempool-liquid-api-v1-websocket {
proxy_pass $mempoolMainnet;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-Proto $scheme;
}
location @mempool-liquid-api-v1-cache-forever {
proxy_pass $mempoolMainnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 30d;
proxy_redirect off;
expires 30d;
}
location @mempool-liquid-api-v1-cache-warm {
proxy_pass $mempoolMainnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 10s;
proxy_redirect off;
}
location @mempool-liquid-api-v1-cache-normal {
proxy_pass $mempoolMainnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache api;
proxy_cache_valid 200 10s;
proxy_redirect off;
expires 10s;
}
location @mempool-liquid-api-v1-cache-disabled {
proxy_pass $mempoolMainnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_buffering off;
expires -1;
}
location @esplora-liquid-api-cache-disabled {
proxy_pass $esploraMainnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_buffering off;
expires -1;
}
location @esplora-liquid-api-cache-forever {
proxy_pass $esploraMainnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 30d;
proxy_redirect off;
expires 30d;
}

View File

@ -1,12 +1,154 @@
###########
# mempool #
###########
# websocket has special HTTP headers
location /liquidtestnet/api/v1/ws {
proxy_pass http://mempool-liquid-testnet/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
rewrite ^/liquidtestnet/(.*) /$1 break;
try_files /dev/null @mempool-liquidtestnet-api-v1-websocket;
}
# warm cache mining and mempool API responses
location /liquidtestnet/api/v1/statistics {
rewrite ^/liquidtestnet/(.*) /$1 break;
try_files /dev/null @mempool-liquidtestnet-api-v1-cache-warm;
}
location /liquidtestnet/api/v1/mining {
rewrite ^/liquidtestnet/(.*) /$1 break;
try_files /dev/null @mempool-liquidtestnet-api-v1-cache-warm;
}
# it's ok to cache blockchain data "forever", so we do 30d
location /liquidtestnet/api/v1/block/ {
rewrite ^/liquidtestnet/(.*) /$1 break;
try_files /dev/null @mempool-liquidtestnet-api-v1-cache-forever;
}
# everything else gets "normal" cache
location /liquidtestnet/api/v1 {
proxy_pass http://mempool-liquid-testnet/api/v1;
rewrite ^/liquidtestnet/(.*) /$1 break;
try_files /dev/null @mempool-liquidtestnet-api-v1-cache-normal;
}
###########
# esplora #
###########
# it's ok to cache blockchain data "forever", so we do 30d
location /liquidtestnet/api/block/ {
rewrite ^/liquidtestnet/api/(.*) /$1 break;
try_files /dev/null @esplora-liquidtestnet-api-cache-forever;
}
# other API responses cannot be cached
location /liquidtestnet/api/ {
proxy_pass http://electrs-liquid-testnet/;
rewrite ^/liquidtestnet/api/(.*) /$1 break;
try_files /dev/null @esplora-liquidtestnet-api-cache-disabled;
}
###########
# routing #
###########
location @mempool-liquidtestnet-api-v1-websocket {
proxy_pass $mempoolTestnet;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-Proto $scheme;
}
location @mempool-liquidtestnet-api-v1-cache-forever {
proxy_pass $mempoolTestnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 30d;
proxy_redirect off;
expires 30d;
}
location @mempool-liquidtestnet-api-v1-cache-warm {
proxy_pass $mempoolTestnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 10s;
proxy_redirect off;
}
location @mempool-liquidtestnet-api-v1-cache-normal {
proxy_pass $mempoolTestnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache api;
proxy_cache_valid 200 10s;
proxy_redirect off;
expires 10s;
}
location @mempool-liquidtestnet-api-v1-cache-disabled {
proxy_pass $mempoolTestnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_buffering off;
expires -1;
}
location @esplora-liquidtestnet-api-cache-disabled {
proxy_pass $esploraTestnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_buffering off;
expires -1;
}
location @esplora-liquidtestnet-api-cache-forever {
proxy_pass $esploraTestnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 30d;
proxy_redirect off;
expires 30d;
}

View File

@ -0,0 +1,21 @@
# route lightning API endpoints to lightning backend
location /signet/api/v1/lightning {
rewrite ^/signet/(.*) /$1 break;
try_files /dev/null @mempool-signet-api-v1-lightning;
}
location @mempool-signet-api-v1-lightning {
proxy_pass $mempoolSignetLightning;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 10s;
proxy_redirect off;
expires 10s;
}

View File

@ -1,12 +1,154 @@
###########
# mempool #
###########
# websocket has special HTTP headers
location /signet/api/v1/ws {
proxy_pass http://mempool-bitcoin-signet/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
rewrite ^/signet/(.*) /$1 break;
try_files /dev/null @mempool-signet-api-v1-websocket;
}
# warm cache mining and mempool API responses
location /signet/api/v1/statistics {
rewrite ^/signet/(.*) /$1 break;
try_files /dev/null @mempool-signet-api-v1-cache-warm;
}
location /signet/api/v1/mining {
rewrite ^/signet/(.*) /$1 break;
try_files /dev/null @mempool-signet-api-v1-cache-warm;
}
# it's ok to cache blockchain data "forever", so we do 30d
location /signet/api/v1/block/ {
rewrite ^/signet/(.*) /$1 break;
try_files /dev/null @mempool-signet-api-v1-cache-forever;
}
# everything else gets "normal" cache
location /signet/api/v1 {
proxy_pass http://mempool-bitcoin-signet/api/v1;
rewrite ^/signet/(.*) /$1 break;
try_files /dev/null @mempool-signet-api-v1-cache-normal;
}
###########
# esplora #
###########
# it's ok to cache blockchain data "forever", so we do 30d
location /signet/api/block/ {
rewrite ^/signet/api/(.*) /$1 break;
try_files /dev/null @esplora-signet-api-cache-forever;
}
# other API responses cannot be cached
location /signet/api/ {
proxy_pass http://electrs-bitcoin-signet/;
rewrite ^/signet/api/(.*) /$1 break;
try_files /dev/null @esplora-signet-api-cache-disabled;
}
###########
# routing #
###########
location @mempool-signet-api-v1-websocket {
proxy_pass $mempoolSignet;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-Proto $scheme;
}
location @mempool-signet-api-v1-cache-forever {
proxy_pass $mempoolSignet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 30d;
proxy_redirect off;
expires 30d;
}
location @mempool-signet-api-v1-cache-warm {
proxy_pass $mempoolSignet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 10s;
proxy_redirect off;
}
location @mempool-signet-api-v1-cache-normal {
proxy_pass $mempoolSignet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache api;
proxy_cache_valid 200 10s;
proxy_redirect off;
expires 10s;
}
location @mempool-signet-api-v1-cache-disabled {
proxy_pass $mempoolSignet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_buffering off;
expires -1;
}
location @esplora-signet-api-cache-disabled {
proxy_pass $esploraSignet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_buffering off;
expires -1;
}
location @esplora-signet-api-cache-forever {
proxy_pass $esploraSignet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 30d;
proxy_redirect off;
expires 30d;
}

View File

@ -0,0 +1,21 @@
# route lightning API endpoints to lightning backend
location /testnet/api/v1/lightning {
rewrite ^/testnet/(.*) /$1 break;
try_files /dev/null @mempool-testnet-api-v1-lightning;
}
location @mempool-testnet-api-v1-lightning {
proxy_pass $mempoolSignetLightning;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 10s;
proxy_redirect off;
expires 10s;
}

View File

@ -1,12 +1,154 @@
###########
# mempool #
###########
# websocket has special HTTP headers
location /testnet/api/v1/ws {
proxy_pass http://mempool-bitcoin-testnet/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
rewrite ^/testnet/(.*) /$1 break;
try_files /dev/null @mempool-testnet-api-v1-websocket;
}
# warm cache mining and mempool API responses
location /testnet/api/v1/statistics {
rewrite ^/testnet/(.*) /$1 break;
try_files /dev/null @mempool-testnet-api-v1-cache-warm;
}
location /testnet/api/v1/mining {
rewrite ^/testnet/(.*) /$1 break;
try_files /dev/null @mempool-testnet-api-v1-cache-warm;
}
# it's ok to cache blockchain data "forever", so we do 30d
location /testnet/api/v1/block/ {
rewrite ^/testnet/(.*) /$1 break;
try_files /dev/null @mempool-testnet-api-v1-cache-forever;
}
# everything else gets "normal" cache
location /testnet/api/v1 {
proxy_pass http://mempool-bitcoin-testnet/api/v1;
rewrite ^/testnet/(.*) /$1 break;
try_files /dev/null @mempool-testnet-api-v1-cache-normal;
}
###########
# esplora #
###########
# it's ok to cache blockchain data "forever", so we do 30d
location /testnet/api/block/ {
rewrite ^/testnet/api/(.*) /$1 break;
try_files /dev/null @esplora-testnet-api-cache-forever;
}
# other API responses cannot be cached
location /testnet/api/ {
proxy_pass http://electrs-bitcoin-testnet/;
rewrite ^/testnet/api/(.*) /$1 break;
try_files /dev/null @esplora-testnet-api-cache-disabled;
}
###########
# routing #
###########
location @mempool-testnet-api-v1-websocket {
proxy_pass $mempoolTestnet;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header X-Forwarded-Proto $scheme;
}
location @mempool-testnet-api-v1-cache-forever {
proxy_pass $mempoolTestnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 30d;
proxy_redirect off;
expires 30d;
}
location @mempool-testnet-api-v1-cache-warm {
proxy_pass $mempoolTestnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 10s;
proxy_redirect off;
}
location @mempool-testnet-api-v1-cache-normal {
proxy_pass $mempoolTestnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache api;
proxy_cache_valid 200 10s;
proxy_redirect off;
expires 10s;
}
location @mempool-testnet-api-v1-cache-disabled {
proxy_pass $mempoolTestnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_buffering off;
expires -1;
}
location @esplora-testnet-api-cache-disabled {
proxy_pass $esploraTestnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_buffering off;
expires -1;
}
location @esplora-testnet-api-cache-forever {
proxy_pass $esploraTestnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_background_update on;
proxy_cache_use_stale updating;
proxy_cache api;
proxy_cache_valid 200 30d;
proxy_redirect off;
expires 30d;
}

View File

@ -1,4 +1,3 @@
# FreeBSD configuration
user nobody;
pid /var/run/nginx.pid;
@ -14,17 +13,21 @@ http {
# DNS servers for on-demand recursive resolver
resolver 8.8.8.8;
# include default mime types
include /usr/local/etc/nginx/mime.types;
default_type application/octet-stream;
# HTTP basic configuration
include mempool/production/nginx/http-basic.conf;
include mempool/production/nginx/http-proxy-cache.conf;
include mempool/production/nginx/http-language.conf;
# mempool backend configuration
# mempool configuration
include mempool/production/nginx/upstream-mempool.conf;
# electrs backend configuration
include mempool/production/nginx/upstream-electrs.conf;
include mempool/production/nginx/server-electrs.conf;
# esplora configuration
include mempool/production/nginx/upstream-esplora.conf;
include mempool/production/nginx/server-esplora.conf;
# MEMPOOL.NINJA
server {
@ -36,11 +39,18 @@ http {
# for services from mempool.space like contributors on about page
set $mempoolSpaceServices "https://mempool.space";
# for mempool/backend daemon, see upstream-mempool.conf
set $mempoolBackend "http://mempool-bitcoin-mainnet";
# for mempool daemons, see upstream-mempool.conf
set $mempoolMainnet "http://mempool-bitcoin-mainnet";
set $mempoolMainnetLightning "http://mempool-bitcoin-mainnet-lightning";
set $mempoolTestnet "http://mempool-bitcoin-testnet";
set $mempoolTestnetLightning "http://mempool-bitcoin-testnet-lightning";
set $mempoolSignet "http://mempool-bitcoin-signet";
set $mempoolSignetLightning "http://mempool-bitcoin-signet-lightning";
# for blockstream/electrs daemon, see upstream-electrs.conf
set $electrsBackend "http://electrs-bitcoin-mainnet";
# for blockstream/esplora daemons, see upstream-esplora.conf
set $esploraMainnet "http://esplora-bitcoin-mainnet";
set $esploraTestnet "http://esplora-bitcoin-testnet";
set $esploraSignet "http://esplora-bitcoin-signet";
# tor v3
listen 127.0.0.1:81;
@ -70,11 +80,11 @@ http {
# for services from mempool.space like contributors on about page
set $mempoolSpaceServices "https://mempool.space";
# for mempool/backend daemon, see upstream-mempool.conf
set $mempoolBackend "http://mempool-bisq-mainnet";
# for mempool daemons, see upstream-mempool.conf
set $mempoolBisq "http://mempool-bitcoin-bisq";
# for blockstream/electrs daemon, see upstream-electrs.conf
set $electrsBackend "http://electrs-bitcoin-mainnet";
# for blockstream/esplora daemon, see upstream-esplora.conf
set $esploraMainnet "http://esplora-bitcoin-mainnet";
# tor v3
listen 127.0.0.1:82;
@ -104,11 +114,13 @@ http {
# for services from mempool.space like contributors on about page
set $mempoolSpaceServices "https://mempool.space";
# for mempool/backend daemon, see upstream-mempool.conf
set $mempoolBackend "http://mempool-liquid-mainnet";
# for mempool daemons, see upstream-mempool.conf
set $mempoolMainnet "http://mempool-liquid-mainnet";
set $mempoolTestnet "http://mempool-liquid-testnet";
# for blockstream/electrs daemon, see upstream-electrs.conf
set $electrsBackend "http://electrs-liquid-mainnet";
# for blockstream/esplora daemon, see upstream-esplora.conf
set $esploraMainnet "http://esplora-liquid-mainnet";
set $esploraTestnet "http://esplora-liquid-testnet";
# tor v3
listen 127.0.0.1:83;

View File

@ -4,80 +4,98 @@ include mempool/production/nginx/location-api-v1-services.conf;
proxy_cache markets;
proxy_cache_valid 200 30s;
# route electrs APIs to electrs
# route esplora APIs to esplora
location /api/tx/ {
proxy_pass http://electrs-bitcoin-mainnet/tx/;
rewrite ^/api/(.*) /$1 break;
try_files /dev/null @esplora-api-cache-disabled;
}
# rewrite APIs to match what backend expects
location /api/currencies {
rewrite ^/api/(.*) /api/v1/bisq/markets/$1 break;
try_files $uri $uri/ @mempool-bisq;
try_files /dev/null @mempool-bisq;
}
location /api/depth {
rewrite ^/api/(.*) /api/v1/bisq/markets/$1 break;
try_files $uri $uri/ @mempool-bisq;
try_files /dev/null @mempool-bisq;
}
location /api/hloc {
rewrite ^/api/(.*) /api/v1/bisq/markets/$1 break;
try_files $uri $uri/ @mempool-bisq;
try_files /dev/null @mempool-bisq;
}
location /api/offers {
rewrite ^/api/(.*) /api/v1/bisq/markets/$1 break;
try_files $uri $uri/ @mempool-bisq;
try_files /dev/null @mempool-bisq;
}
location /api/ticker {
rewrite ^/api/(.*) /api/v1/bisq/markets/$1 break;
try_files $uri $uri/ @mempool-bisq;
try_files /dev/null @mempool-bisq;
}
location /api/trades {
rewrite ^/api/(.*) /api/v1/bisq/markets/$1 break;
try_files $uri $uri/ @mempool-bisq;
try_files /dev/null @mempool-bisq;
}
location /api/volumes {
rewrite ^/api/(.*) /api/v1/bisq/markets/$1 break;
try_files $uri $uri/ @mempool-bisq;
try_files /dev/null @mempool-bisq;
}
location /api/markets {
rewrite ^/api/(.*) /api/v1/bisq/markets/$1 break;
try_files $uri $uri/ @mempool-bisq;
try_files /dev/null @mempool-bisq;
}
location /api/v1 {
rewrite ^/api/v1/(.*) /api/v1/bisq/$1 break;
try_files $uri $uri/ @mempool-bisq;
try_files /dev/null @mempool-bisq;
}
location /api {
rewrite ^/api/(.*) /api/v1/bisq/$1 break;
try_files $uri $uri/ @mempool-bisq;
try_files /dev/null @mempool-bisq;
}
location /api/v1/ws {
rewrite ^/api/(.*) /api/v1/bisq/$1 break;
try_files /dev/null @mempool-bisq-websocket;
}
location /bisq/api/v1/ws {
rewrite ^/bisq/api/v1/(.*) /api/v1/bisq/$1 break;
try_files /dev/null @mempool-bisq-websocket;
}
location /bisq/api/v1 {
rewrite ^/bisq/api/v1/(.*) /api/v1/bisq/$1 break;
try_files $uri $uri/ @mempool-bisq;
try_files /dev/null @mempool-bisq;
}
location /bisq/api {
rewrite ^/bisq/api/(.*) /api/v1/bisq/$1 break;
try_files $uri $uri/ @mempool-bisq;
rewrite ^/bisq/api/(.*) /api/v1/bisq/$1 break;
try_files /dev/null @mempool-bisq;
}
# special handling for websocket
location /api/v1/ws {
proxy_pass http://mempool-bitcoin-bisq/;
location @mempool-bisq-websocket {
proxy_pass $mempoolBisq;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location @mempool-bisq {
proxy_pass http://mempool-bitcoin-bisq;
proxy_http_version 1.1;
proxy_pass $mempoolBisq;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_cache_bypass $http_upgrade;
proxy_redirect off;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
}
location @esplora-api-cache-disabled {
proxy_pass $esploraMainnet;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
proxy_buffering off;
expires -1;
}

View File

@ -2,34 +2,34 @@ server {
listen 127.0.0.1:4000;
access_log /dev/null;
location / {
proxy_pass http://electrs-bitcoin-mainnet;
proxy_pass http://esplora-bitcoin-mainnet;
}
}
server {
listen 127.0.0.1:4001;
access_log /dev/null;
location / {
proxy_pass http://electrs-liquid-mainnet;
proxy_pass http://esplora-liquid-mainnet;
}
}
server {
listen 127.0.0.1:4002;
access_log /dev/null;
location / {
proxy_pass http://electrs-bitcoin-testnet;
proxy_pass http://esplora-bitcoin-testnet;
}
}
server {
listen 127.0.0.1:4003;
access_log /dev/null;
location / {
proxy_pass http://electrs-bitcoin-signet;
proxy_pass http://esplora-bitcoin-signet;
}
}
server {
listen 127.0.0.1:4004;
access_log /dev/null;
location / {
proxy_pass http://electrs-liquid-testnet;
proxy_pass http://esplora-liquid-testnet;
}
}

View File

@ -1,6 +1,9 @@
include mempool/production/nginx/server-common.conf;
include mempool/production/nginx/location-redirects.conf;
include mempool/production/nginx/location-api-v1-services.conf;
include mempool/production/nginx/location-api-v1-lightning.conf;
include mempool/production/nginx/location-api.conf;
include mempool/production/nginx/location-testnet-api.conf;
include mempool/production/nginx/location-testnet-api-v1-lightning.conf;
include mempool/production/nginx/location-signet-api.conf;
include mempool/production/nginx/location-signet-api-v1-lightning.conf;

View File

@ -1,15 +1,15 @@
upstream electrs-bitcoin-mainnet {
upstream esplora-bitcoin-mainnet {
server [::1]:3000 fail_timeout=10s max_fails=10 weight=99999;
}
upstream electrs-liquid-mainnet {
upstream esplora-liquid-mainnet {
server [::1]:3001 fail_timeout=10s max_fails=10 weight=99999;
}
upstream electrs-bitcoin-testnet {
upstream esplora-bitcoin-testnet {
server [::1]:3002 fail_timeout=10s max_fails=10 weight=99999;
}
upstream electrs-bitcoin-signet {
upstream esplora-bitcoin-signet {
server [::1]:3003 fail_timeout=10s max_fails=10 weight=99999;
}
upstream electrs-liquid-testnet {
upstream esplora-liquid-testnet {
server [::1]:3004 fail_timeout=10s max_fails=10 weight=99999;
}

View File

@ -16,3 +16,12 @@ upstream mempool-bitcoin-signet {
upstream mempool-liquid-testnet {
server 127.0.0.1:8994 fail_timeout=10s max_fails=10 weight=99999;
}
upstream mempool-bitcoin-mainnet-lightning {
server 127.0.0.1:8993 fail_timeout=10s max_fails=10 weight=99999;
}
upstream mempool-bitcoin-testnet-lightning {
server 127.0.0.1:8992 fail_timeout=10s max_fails=10 weight=99999;
}
upstream mempool-bitcoin-signet-lightning {
server 127.0.0.1:8991 fail_timeout=10s max_fails=10 weight=99999;
}

View File

@ -1,4 +1,4 @@
#!/usr/local/bin/zsh
#!/usr/bin/env zsh
PROTO=https
HOSTNAME=mempool.ninja
URL_BASE=${PROTO}://${HOSTNAME}