Merge branch 'master' into breathe-effect-framerate
This commit is contained in:
commit
f9dfbf94ef
42
backend/package-lock.json
generated
42
backend/package-lock.json
generated
@ -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": {
|
||||
|
@ -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",
|
||||
|
@ -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() {
|
||||
|
@ -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'];
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
51
backend/src/repositories/BlocksAuditsRepository.ts
Normal file
51
backend/src/repositories/BlocksAuditsRepository.ts
Normal 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();
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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++;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -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": {
|
||||
|
@ -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
|
||||
});
|
||||
};
|
2772
frontend/package-lock.json
generated
2772
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
@ -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();
|
||||
});
|
||||
});
|
@ -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!');
|
||||
});
|
||||
});
|
@ -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;
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ export class BlockFeeRatesGraphComponent implements OnInit {
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
formatter: function (data) {
|
||||
formatter: function(data) {
|
||||
if (data.length <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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>
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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>‎<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>
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
@ -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>
|
||||
|
@ -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,7 +277,7 @@ export class HashrateChartComponent implements OnInit {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: $localize`::Difficulty`,
|
||||
name: $localize`:@@25148835d92465353fc5fe8897c27d5369978e5a:Difficulty`,
|
||||
inactiveColor: 'rgb(110, 112, 121)',
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
@ -284,7 +285,7 @@ export class HashrateChartComponent implements OnInit {
|
||||
icon: 'roundRect',
|
||||
},
|
||||
{
|
||||
name: $localize`Hashrate` + ` (MA${this.maResolution})`,
|
||||
name: $localize`Hashrate (MA)`,
|
||||
inactiveColor: 'rgb(110, 112, 121)',
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
@ -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() {
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
@ -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 »</a></div>
|
||||
<div class="mt-1"><a [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]" fragment="1y" i18n="dashboard.view-more">View more »</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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">
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
@ -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();
|
||||
});
|
||||
});
|
@ -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>
|
||||
|
@ -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",
|
||||
|
@ -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>
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
@ -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,
|
||||
|
@ -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,
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -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}`);
|
||||
}
|
||||
|
@ -1,8 +0,0 @@
|
||||
import { AbsolutePipe } from './absolute.pipe';
|
||||
|
||||
describe('AbsolutePipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new AbsolutePipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
@ -1,8 +0,0 @@
|
||||
import { AsmStylerPipe } from './asm-styler.pipe';
|
||||
|
||||
describe('OpcodesStylerPipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new AsmStylerPipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
@ -1,8 +0,0 @@
|
||||
import { FeeRoundingPipe } from './fee-rounding.pipe';
|
||||
|
||||
describe('FeeRoundingPipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new FeeRoundingPipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
@ -1,8 +0,0 @@
|
||||
import { Hex2asciiPipe } from './hex2ascii.pipe';
|
||||
|
||||
describe('Hex2asciiPipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new Hex2asciiPipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
@ -1,8 +0,0 @@
|
||||
import { RelativeUrlPipe } from './relative-url.pipe';
|
||||
|
||||
describe('RelativeUrlPipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new RelativeUrlPipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
@ -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;
|
||||
|
@ -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);
|
@ -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"
|
||||
]
|
||||
}
|
@ -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}"
|
||||
|
@ -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}"
|
||||
|
@ -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}"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
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
|
||||
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
|
||||
|
22
production/linux/bitcoin-minfee.service
Normal file
22
production/linux/bitcoin-minfee.service
Normal 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
|
@ -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}"
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
20
production/nginx/location-api-v1-lightning.conf
Normal file
20
production/nginx/location-api-v1-lightning.conf
Normal 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;
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
21
production/nginx/location-signet-api-v1-lightning.conf
Normal file
21
production/nginx/location-signet-api-v1-lightning.conf
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
21
production/nginx/location-testnet-api-v1-lightning.conf
Normal file
21
production/nginx/location-testnet-api-v1-lightning.conf
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/local/bin/zsh
|
||||
#!/usr/bin/env zsh
|
||||
PROTO=https
|
||||
HOSTNAME=mempool.ninja
|
||||
URL_BASE=${PROTO}://${HOSTNAME}
|
||||
|
Loading…
x
Reference in New Issue
Block a user