Merge branch 'master' into qrcodes
This commit is contained in:
commit
98db8b1b25
74
.github/workflows/ci.yml
vendored
74
.github/workflows/ci.yml
vendored
@ -1,48 +1,88 @@
|
|||||||
name: CI Pipeline for the Backend and Frontend
|
name: CI Pipeline for the Backend and Frontend
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
env:
|
env:
|
||||||
NODE_VERSION: 16.15.0
|
NODE_VERSION: 16.15.0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_backend:
|
backend:
|
||||||
name: Build backend
|
strategy:
|
||||||
runs-on: ubuntu-latest
|
matrix:
|
||||||
|
flavor: ['dev', 'prod']
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
|
||||||
|
name: Backend (${{ matrix.flavor }})
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
path: ${{ matrix.flavor }}
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
- name: Install
|
|
||||||
|
- name: Install
|
||||||
|
if: ${{ matrix.flavor == 'dev'}}
|
||||||
|
run: npm install
|
||||||
|
working-directory: ${{ matrix.flavor }}/backend
|
||||||
|
|
||||||
|
- name: Install (Prod dependencies only)
|
||||||
|
if: ${{ matrix.flavor == 'prod'}}
|
||||||
run: npm install --prod
|
run: npm install --prod
|
||||||
working-directory: backend
|
working-directory: ${{ matrix.flavor }}/backend
|
||||||
# - name: Lint
|
|
||||||
# run: npm run lint
|
- name: Lint
|
||||||
|
if: ${{ matrix.flavor == 'dev'}}
|
||||||
|
run: npm run lint
|
||||||
|
working-directory: ${{ matrix.flavor }}/backend
|
||||||
|
|
||||||
# - name: Test
|
# - name: Test
|
||||||
# run: npm run test
|
# run: npm run test
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
working-directory: backend
|
working-directory: ${{ matrix.flavor }}/backend
|
||||||
build_frontend:
|
|
||||||
name: Build frontend
|
frontend:
|
||||||
runs-on: ubuntu-latest
|
strategy:
|
||||||
|
matrix:
|
||||||
|
flavor: ['dev', 'prod']
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
|
||||||
|
name: Frontend (${{ matrix.flavor }})
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
path: ${{ matrix.flavor }}
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
- name: Install
|
|
||||||
run: npm install --prod
|
- name: Install (Prod dependencies only)
|
||||||
working-directory: frontend
|
run: npm install
|
||||||
# - name: Lint
|
if: ${{ matrix.flavor == 'prod'}}
|
||||||
# run: npm run lint
|
working-directory: ${{ matrix.flavor }}/frontend
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
if: ${{ matrix.flavor == 'dev'}}
|
||||||
|
run: npm install
|
||||||
|
working-directory: ${{ matrix.flavor }}/frontend
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: npm run lint
|
||||||
|
working-directory: ${{ matrix.flavor }}/frontend
|
||||||
|
|
||||||
# - name: Test
|
# - name: Test
|
||||||
# run: npm run test
|
# run: npm run test
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
working-directory: frontend
|
working-directory: ${{ matrix.flavor }}/frontend
|
||||||
|
2
backend/.eslintignore
Normal file
2
backend/.eslintignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
32
backend/.eslintrc
Normal file
32
backend/.eslintrc
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint"
|
||||||
|
],
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/ban-ts-comment": 1,
|
||||||
|
"@typescript-eslint/ban-types": 1,
|
||||||
|
"@typescript-eslint/no-empty-function": 1,
|
||||||
|
"@typescript-eslint/no-explicit-any": 1,
|
||||||
|
"@typescript-eslint/no-inferrable-types": 1,
|
||||||
|
"@typescript-eslint/no-namespace": 1,
|
||||||
|
"@typescript-eslint/no-this-alias": 1,
|
||||||
|
"@typescript-eslint/no-var-requires": 1,
|
||||||
|
"no-console": 1,
|
||||||
|
"no-constant-condition": 1,
|
||||||
|
"no-dupe-else-if": 1,
|
||||||
|
"no-empty": 1,
|
||||||
|
"no-prototype-builtins": 1,
|
||||||
|
"no-self-assign": 1,
|
||||||
|
"no-useless-catch": 1,
|
||||||
|
"no-var": 1,
|
||||||
|
"prefer-const": 1,
|
||||||
|
"prefer-rest-params": 1
|
||||||
|
}
|
||||||
|
}
|
2633
backend/package-lock.json
generated
2633
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -24,7 +24,9 @@
|
|||||||
"build": "npm run tsc",
|
"build": "npm run tsc",
|
||||||
"start": "node --max-old-space-size=2048 dist/index.js",
|
"start": "node --max-old-space-size=2048 dist/index.js",
|
||||||
"start-production": "node --max-old-space-size=4096 dist/index.js",
|
"start-production": "node --max-old-space-size=4096 dist/index.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"lint": "./node_modules/.bin/eslint . --ext .ts",
|
||||||
|
"lint:fix": "./node_modules/.bin/eslint . --ext .ts --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mempool/electrum-client": "^1.1.7",
|
"@mempool/electrum-client": "^1.1.7",
|
||||||
@ -35,14 +37,17 @@
|
|||||||
"express": "^4.18.0",
|
"express": "^4.18.0",
|
||||||
"mysql2": "2.3.3",
|
"mysql2": "2.3.3",
|
||||||
"node-worker-threads-pool": "^1.5.1",
|
"node-worker-threads-pool": "^1.5.1",
|
||||||
"socks-proxy-agent": "^6.2.0",
|
"socks-proxy-agent": "~7.0.0",
|
||||||
"typescript": "~4.7.2",
|
"typescript": "~4.7.4",
|
||||||
"ws": "~8.7.0"
|
"ws": "~8.8.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/compression": "^1.7.2",
|
"@types/compression": "^1.7.2",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/ws": "~8.5.3",
|
"@types/ws": "~8.5.3",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
||||||
|
"@typescript-eslint/parser": "^5.30.5",
|
||||||
|
"eslint": "^8.19.0",
|
||||||
"tslint": "^6.1.0"
|
"tslint": "^6.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ export interface AbstractBitcoinApi {
|
|||||||
$getRawMempool(): Promise<IEsploraApi.Transaction['txid'][]>;
|
$getRawMempool(): Promise<IEsploraApi.Transaction['txid'][]>;
|
||||||
$getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean, lazyPrevouts?: boolean): Promise<IEsploraApi.Transaction>;
|
$getRawTransaction(txId: string, skipConversion?: boolean, addPrevout?: boolean, lazyPrevouts?: boolean): Promise<IEsploraApi.Transaction>;
|
||||||
$getBlockHeightTip(): Promise<number>;
|
$getBlockHeightTip(): Promise<number>;
|
||||||
|
$getBlockHashTip(): Promise<string>;
|
||||||
$getTxIdsForBlock(hash: string): Promise<string[]>;
|
$getTxIdsForBlock(hash: string): Promise<string[]>;
|
||||||
$getBlockHash(height: number): Promise<string>;
|
$getBlockHash(height: number): Promise<string>;
|
||||||
$getBlockHeader(hash: string): Promise<string>;
|
$getBlockHeader(hash: string): Promise<string>;
|
||||||
|
@ -64,6 +64,13 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$getBlockHashTip(): Promise<string> {
|
||||||
|
return this.bitcoindClient.getChainTips()
|
||||||
|
.then((result: IBitcoinApi.ChainTips[]) => {
|
||||||
|
return result.find(tip => tip.status === 'active')!.hash;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$getTxIdsForBlock(hash: string): Promise<string[]> {
|
$getTxIdsForBlock(hash: string): Promise<string[]> {
|
||||||
return this.bitcoindClient.getBlock(hash, 1)
|
return this.bitcoindClient.getBlock(hash, 1)
|
||||||
.then((rpcBlock: IBitcoinApi.Block) => rpcBlock.tx);
|
.then((rpcBlock: IBitcoinApi.Block) => rpcBlock.tx);
|
||||||
|
@ -25,6 +25,11 @@ class ElectrsApi implements AbstractBitcoinApi {
|
|||||||
.then((response) => response.data);
|
.then((response) => response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$getBlockHashTip(): Promise<string> {
|
||||||
|
return axios.get<string>(config.ESPLORA.REST_API_URL + '/blocks/tip/hash', this.axiosConfig)
|
||||||
|
.then((response) => response.data);
|
||||||
|
}
|
||||||
|
|
||||||
$getTxIdsForBlock(hash: string): Promise<string[]> {
|
$getTxIdsForBlock(hash: string): Promise<string[]> {
|
||||||
return axios.get<string[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids', this.axiosConfig)
|
return axios.get<string[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids', this.axiosConfig)
|
||||||
.then((response) => response.data);
|
.then((response) => response.data);
|
||||||
|
@ -19,6 +19,9 @@ import HashratesRepository from '../repositories/HashratesRepository';
|
|||||||
import indexer from '../indexer';
|
import indexer from '../indexer';
|
||||||
import poolsParser from './pools-parser';
|
import poolsParser from './pools-parser';
|
||||||
import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository';
|
import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository';
|
||||||
|
import mining from './mining';
|
||||||
|
import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository';
|
||||||
|
import difficultyAdjustment from './difficulty-adjustment';
|
||||||
|
|
||||||
class Blocks {
|
class Blocks {
|
||||||
private blocks: BlockExtended[] = [];
|
private blocks: BlockExtended[] = [];
|
||||||
@ -292,7 +295,8 @@ class Blocks {
|
|||||||
}
|
}
|
||||||
logger.notice(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`);
|
logger.notice(`Blocks summaries indexing completed: indexed ${newlyIndexed} blocks`);
|
||||||
} catch (e) {
|
} 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,12 +304,8 @@ class Blocks {
|
|||||||
* [INDEXING] Index all blocks metadata for the mining dashboard
|
* [INDEXING] Index all blocks metadata for the mining dashboard
|
||||||
*/
|
*/
|
||||||
public async $generateBlockDatabase(): Promise<boolean> {
|
public async $generateBlockDatabase(): Promise<boolean> {
|
||||||
const blockchainInfo = await bitcoinClient.getBlockchainInfo();
|
|
||||||
if (blockchainInfo.blocks !== blockchainInfo.headers) { // Wait for node to sync
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const blockchainInfo = await bitcoinClient.getBlockchainInfo();
|
||||||
let currentBlockHeight = blockchainInfo.blocks;
|
let currentBlockHeight = blockchainInfo.blocks;
|
||||||
|
|
||||||
let indexingBlockAmount = Math.min(config.MEMPOOL.INDEXING_BLOCKS_AMOUNT, blockchainInfo.blocks);
|
let indexingBlockAmount = Math.min(config.MEMPOOL.INDEXING_BLOCKS_AMOUNT, blockchainInfo.blocks);
|
||||||
@ -368,18 +368,12 @@ class Blocks {
|
|||||||
logger.notice(`Block indexing completed: indexed ${newlyIndexed} blocks`);
|
logger.notice(`Block indexing completed: indexed ${newlyIndexed} blocks`);
|
||||||
loadingIndicators.setProgress('block-indexing', 100);
|
loadingIndicators.setProgress('block-indexing', 100);
|
||||||
} catch (e) {
|
} 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);
|
loadingIndicators.setProgress('block-indexing', 100);
|
||||||
return false;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
const chainValid = await BlocksRepository.$validateChain();
|
return await BlocksRepository.$validateChain();
|
||||||
if (!chainValid) {
|
|
||||||
indexer.reindex();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $updateBlocks() {
|
public async $updateBlocks() {
|
||||||
@ -449,7 +443,10 @@ class Blocks {
|
|||||||
const newBlock = await this.$indexBlock(lastBlock['height'] - i);
|
const newBlock = await this.$indexBlock(lastBlock['height'] - i);
|
||||||
await this.$getStrippedBlockTransactions(newBlock.id, true, true);
|
await this.$getStrippedBlockTransactions(newBlock.id, true, true);
|
||||||
}
|
}
|
||||||
logger.info(`Re-indexed 10 blocks and summaries`);
|
await mining.$indexDifficultyAdjustments();
|
||||||
|
await DifficultyAdjustmentsRepository.$deleteLastAdjustment();
|
||||||
|
logger.info(`Re-indexed 10 blocks and summaries. Also re-indexed the last difficulty adjustments. Will re-index latest hashrates in a few seconds.`);
|
||||||
|
indexer.reindex();
|
||||||
}
|
}
|
||||||
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
await blocksRepository.$saveBlockInDatabase(blockExtended);
|
||||||
|
|
||||||
@ -461,6 +458,15 @@ class Blocks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (block.height % 2016 === 0) {
|
if (block.height % 2016 === 0) {
|
||||||
|
if (Common.indexingEnabled()) {
|
||||||
|
await DifficultyAdjustmentsRepository.$saveAdjustments({
|
||||||
|
time: block.timestamp,
|
||||||
|
height: block.height,
|
||||||
|
difficulty: block.difficulty,
|
||||||
|
adjustment: Math.round((block.difficulty / this.currentDifficulty) * 1000000) / 1000000, // Remove float point noise
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.previousDifficultyRetarget = (block.difficulty - this.currentDifficulty) / this.currentDifficulty * 100;
|
this.previousDifficultyRetarget = (block.difficulty - this.currentDifficulty) / this.currentDifficulty * 100;
|
||||||
this.lastDifficultyAdjustmentTime = block.timestamp;
|
this.lastDifficultyAdjustmentTime = block.timestamp;
|
||||||
this.currentDifficulty = block.difficulty;
|
this.currentDifficulty = block.difficulty;
|
||||||
|
@ -4,7 +4,7 @@ import logger from '../logger';
|
|||||||
import { Common } from './common';
|
import { Common } from './common';
|
||||||
|
|
||||||
class DatabaseMigration {
|
class DatabaseMigration {
|
||||||
private static currentVersion = 21;
|
private static currentVersion = 24;
|
||||||
private queryTimeout = 120000;
|
private queryTimeout = 120000;
|
||||||
private statisticsAddedIndexed = false;
|
private statisticsAddedIndexed = false;
|
||||||
private uniqueLogs: string[] = [];
|
private uniqueLogs: string[] = [];
|
||||||
@ -226,6 +226,28 @@ class DatabaseMigration {
|
|||||||
await this.$executeQuery('DROP TABLE IF EXISTS `rates`');
|
await this.$executeQuery('DROP TABLE IF EXISTS `rates`');
|
||||||
await this.$executeQuery(this.getCreatePricesTableQuery(), await this.$checkIfTableExists('prices'));
|
await this.$executeQuery(this.getCreatePricesTableQuery(), await this.$checkIfTableExists('prices'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 22 && isBitcoin === true) {
|
||||||
|
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) {
|
} catch (e) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
@ -513,7 +535,7 @@ class DatabaseMigration {
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getCreateRatesTableQuery(): string {
|
private getCreateRatesTableQuery(): string { // This table has been replaced by the prices table
|
||||||
return `CREATE TABLE IF NOT EXISTS rates (
|
return `CREATE TABLE IF NOT EXISTS rates (
|
||||||
height int(10) unsigned NOT NULL,
|
height int(10) unsigned NOT NULL,
|
||||||
bisq_rates JSON NOT NULL,
|
bisq_rates JSON NOT NULL,
|
||||||
@ -539,6 +561,30 @@ class DatabaseMigration {
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getCreateDifficultyAdjustmentsTableQuery(): string {
|
||||||
|
return `CREATE TABLE IF NOT EXISTS difficulty_adjustments (
|
||||||
|
time timestamp NOT NULL,
|
||||||
|
height int(10) unsigned NOT NULL,
|
||||||
|
difficulty double unsigned NOT NULL,
|
||||||
|
adjustment float NOT NULL,
|
||||||
|
PRIMARY KEY (height),
|
||||||
|
INDEX (time)
|
||||||
|
) 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[]) {
|
public async $truncateIndexedData(tables: string[]) {
|
||||||
const allowedTables = ['blocks', 'hashrates', 'prices'];
|
const allowedTables = ['blocks', 'hashrates', 'prices'];
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { PoolInfo, PoolStats, RewardStats } from '../mempool.interfaces';
|
import { IndexedDifficultyAdjustment, PoolInfo, PoolStats, RewardStats } from '../mempool.interfaces';
|
||||||
import BlocksRepository from '../repositories/BlocksRepository';
|
import BlocksRepository from '../repositories/BlocksRepository';
|
||||||
import PoolsRepository from '../repositories/PoolsRepository';
|
import PoolsRepository from '../repositories/PoolsRepository';
|
||||||
import HashratesRepository from '../repositories/HashratesRepository';
|
import HashratesRepository from '../repositories/HashratesRepository';
|
||||||
@ -7,11 +7,25 @@ import logger from '../logger';
|
|||||||
import { Common } from './common';
|
import { Common } from './common';
|
||||||
import loadingIndicators from './loading-indicators';
|
import loadingIndicators from './loading-indicators';
|
||||||
import { escape } from 'mysql2';
|
import { escape } from 'mysql2';
|
||||||
|
import indexer from '../indexer';
|
||||||
|
import DifficultyAdjustmentsRepository from '../repositories/DifficultyAdjustmentsRepository';
|
||||||
|
import config from '../config';
|
||||||
|
import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository';
|
||||||
|
|
||||||
class Mining {
|
class Mining {
|
||||||
constructor() {
|
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
|
* Get historical block total fee
|
||||||
*/
|
*/
|
||||||
@ -262,6 +276,7 @@ class Mining {
|
|||||||
loadingIndicators.setProgress('weekly-hashrate-indexing', 100);
|
loadingIndicators.setProgress('weekly-hashrate-indexing', 100);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
loadingIndicators.setProgress('weekly-hashrate-indexing', 100);
|
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;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -301,7 +316,7 @@ class Mining {
|
|||||||
while (toTimestamp > genesisTimestamp) {
|
while (toTimestamp > genesisTimestamp) {
|
||||||
const fromTimestamp = toTimestamp - 86400000;
|
const fromTimestamp = toTimestamp - 86400000;
|
||||||
|
|
||||||
// Skip already indexed weeks
|
// Skip already indexed days
|
||||||
if (indexedTimestamp.includes(toTimestamp / 1000)) {
|
if (indexedTimestamp.includes(toTimestamp / 1000)) {
|
||||||
toTimestamp -= 86400000;
|
toTimestamp -= 86400000;
|
||||||
++totalIndexed;
|
++totalIndexed;
|
||||||
@ -312,7 +327,7 @@ class Mining {
|
|||||||
// we are currently indexing has complete data)
|
// we are currently indexing has complete data)
|
||||||
const blockStatsPreviousDay: any = await BlocksRepository.$blockCountBetweenTimestamp(
|
const blockStatsPreviousDay: any = await BlocksRepository.$blockCountBetweenTimestamp(
|
||||||
null, (fromTimestamp - 86400000) / 1000, (toTimestamp - 86400000) / 1000);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,9 +371,10 @@ class Mining {
|
|||||||
// Add genesis block manually
|
// Add genesis block manually
|
||||||
if (toTimestamp <= genesisTimestamp && !indexedTimestamp.includes(genesisTimestamp)) {
|
if (toTimestamp <= genesisTimestamp && !indexedTimestamp.includes(genesisTimestamp)) {
|
||||||
hashrates.push({
|
hashrates.push({
|
||||||
hashrateTimestamp: genesisTimestamp,
|
hashrateTimestamp: genesisTimestamp / 1000,
|
||||||
avgHashrate: await bitcoinClient.getNetworkHashPs(1, 1),
|
avgHashrate: await bitcoinClient.getNetworkHashPs(1, 1),
|
||||||
poolId: null,
|
poolId: 0,
|
||||||
|
share: 1,
|
||||||
type: 'daily',
|
type: 'daily',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -373,10 +389,62 @@ class Mining {
|
|||||||
loadingIndicators.setProgress('daily-hashrate-indexing', 100);
|
loadingIndicators.setProgress('daily-hashrate-indexing', 100);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
loadingIndicators.setProgress('daily-hashrate-indexing', 100);
|
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;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index difficulty adjustments
|
||||||
|
*/
|
||||||
|
public async $indexDifficultyAdjustments(): Promise<void> {
|
||||||
|
const indexedHeightsArray = await DifficultyAdjustmentsRepository.$getAdjustmentsHeights();
|
||||||
|
const indexedHeights = {};
|
||||||
|
for (const height of indexedHeightsArray) {
|
||||||
|
indexedHeights[height] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blocks: any = await BlocksRepository.$getBlocksDifficulty();
|
||||||
|
|
||||||
|
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
|
||||||
|
currentDifficulty = block.difficulty;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let adjustment = block.difficulty / Math.max(1, currentDifficulty);
|
||||||
|
adjustment = Math.round(adjustment * 1000000) / 1000000; // Remove float point noise
|
||||||
|
|
||||||
|
await DifficultyAdjustmentsRepository.$saveAdjustments({
|
||||||
|
time: block.time,
|
||||||
|
height: block.height,
|
||||||
|
difficulty: block.difficulty,
|
||||||
|
adjustment: adjustment,
|
||||||
|
});
|
||||||
|
|
||||||
|
totalIndexed++;
|
||||||
|
currentDifficulty = block.difficulty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalIndexed > 0) {
|
||||||
|
logger.notice(`Indexed ${totalIndexed} difficulty adjustments`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getDateMidnight(date: Date): Date {
|
private getDateMidnight(date: Date): Date {
|
||||||
date.setUTCHours(0);
|
date.setUTCHours(0);
|
||||||
date.setUTCMinutes(0);
|
date.setUTCMinutes(0);
|
||||||
|
@ -16,6 +16,7 @@ import transactionUtils from './transaction-utils';
|
|||||||
import rbfCache from './rbf-cache';
|
import rbfCache from './rbf-cache';
|
||||||
import difficultyAdjustment from './difficulty-adjustment';
|
import difficultyAdjustment from './difficulty-adjustment';
|
||||||
import feeApi from './fee-api';
|
import feeApi from './fee-api';
|
||||||
|
import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository';
|
||||||
|
|
||||||
class WebsocketHandler {
|
class WebsocketHandler {
|
||||||
private wss: WebSocket.Server | undefined;
|
private wss: WebSocket.Server | undefined;
|
||||||
@ -416,17 +417,40 @@ class WebsocketHandler {
|
|||||||
|
|
||||||
if (_mempoolBlocks[0]) {
|
if (_mempoolBlocks[0]) {
|
||||||
const matches: string[] = [];
|
const matches: string[] = [];
|
||||||
|
const added: string[] = [];
|
||||||
|
const missing: string[] = [];
|
||||||
|
|
||||||
for (const txId of txIds) {
|
for (const txId of txIds) {
|
||||||
if (_mempoolBlocks[0].transactionIds.indexOf(txId) > -1) {
|
if (_mempoolBlocks[0].transactionIds.indexOf(txId) > -1) {
|
||||||
matches.push(txId);
|
matches.push(txId);
|
||||||
|
} else {
|
||||||
|
added.push(txId);
|
||||||
}
|
}
|
||||||
delete _memPool[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);
|
mempoolBlocks.updateMempoolBlocks(_memPool);
|
||||||
mBlocks = mempoolBlocks.getMempoolBlocks();
|
mBlocks = mempoolBlocks.getMempoolBlocks();
|
||||||
mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas();
|
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) {
|
if (block.extras) {
|
||||||
|
@ -28,6 +28,7 @@ import { Common } from './api/common';
|
|||||||
import poolsUpdater from './tasks/pools-updater';
|
import poolsUpdater from './tasks/pools-updater';
|
||||||
import indexer from './indexer';
|
import indexer from './indexer';
|
||||||
import priceUpdater from './tasks/price-updater';
|
import priceUpdater from './tasks/price-updater';
|
||||||
|
import BlocksAuditsRepository from './repositories/BlocksAuditsRepository';
|
||||||
|
|
||||||
class Server {
|
class Server {
|
||||||
private wss: WebSocket.Server | undefined;
|
private wss: WebSocket.Server | undefined;
|
||||||
@ -285,11 +286,14 @@ class Server {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:slug', routes.$getPool)
|
.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/pools/:interval', routes.$getPoolsHistoricalHashrate)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/hashrate/:interval', routes.$getHistoricalHashrate)
|
.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/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/fees/:interval', routes.$getHistoricalBlockFees)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/rewards/:interval', routes.$getHistoricalBlockRewards)
|
.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/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/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)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,6 +336,7 @@ class Server {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/outspends', routes.getTransactionOutspends)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'tx/:txId/outspends', routes.getTransactionOutspends)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/header', routes.getBlockHeader)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/header', routes.getBlockHeader)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks/tip/height', routes.getBlockTipHeight)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks/tip/height', routes.getBlockTipHeight)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'blocks/tip/hash', routes.getBlockTipHash)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txs', routes.getBlockTransactions)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txs', routes.getBlockTransactions)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txs/:index', routes.getBlockTransactions)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txs/:index', routes.getBlockTransactions)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txids', routes.getTxIdsForBlock)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'block/:hash/txids', routes.getTxIdsForBlock)
|
||||||
|
@ -4,6 +4,7 @@ import mempool from './api/mempool';
|
|||||||
import mining from './api/mining';
|
import mining from './api/mining';
|
||||||
import logger from './logger';
|
import logger from './logger';
|
||||||
import HashratesRepository from './repositories/HashratesRepository';
|
import HashratesRepository from './repositories/HashratesRepository';
|
||||||
|
import bitcoinClient from './api/bitcoin/bitcoin-client';
|
||||||
|
|
||||||
class Indexer {
|
class Indexer {
|
||||||
runIndexer = true;
|
runIndexer = true;
|
||||||
@ -25,6 +26,12 @@ class Indexer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do not attempt to index anything unless Bitcoin Core is fully synced
|
||||||
|
const blockchainInfo = await bitcoinClient.getBlockchainInfo();
|
||||||
|
if (blockchainInfo.blocks !== blockchainInfo.headers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.runIndexer = false;
|
this.runIndexer = false;
|
||||||
this.indexerRunning = true;
|
this.indexerRunning = true;
|
||||||
|
|
||||||
@ -32,17 +39,21 @@ class Indexer {
|
|||||||
const chainValid = await blocks.$generateBlockDatabase();
|
const chainValid = await blocks.$generateBlockDatabase();
|
||||||
if (chainValid === false) {
|
if (chainValid === false) {
|
||||||
// Chain of block hash was invalid, so we need to reindex. Stop here and continue at the next iteration
|
// 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;
|
this.indexerRunning = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await mining.$indexDifficultyAdjustments();
|
||||||
await this.$resetHashratesIndexingState();
|
await this.$resetHashratesIndexingState();
|
||||||
await mining.$generateNetworkHashrateHistory();
|
await mining.$generateNetworkHashrateHistory();
|
||||||
await mining.$generatePoolHashrateHistory();
|
await mining.$generatePoolHashrateHistory();
|
||||||
await blocks.$generateBlocksSummariesDatabase();
|
await blocks.$generateBlocksSummariesDatabase();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.reindex();
|
this.indexerRunning = false;
|
||||||
logger.err(`Indexer failed, trying again later. Reason: ` + (e instanceof Error ? e.message : e));
|
logger.err(`Indexer failed, trying again in 10 seconds. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
|
setTimeout(() => this.reindex(), 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.indexerRunning = false;
|
this.indexerRunning = false;
|
||||||
@ -54,6 +65,7 @@ class Indexer {
|
|||||||
await HashratesRepository.$setLatestRun('last_weekly_hashrates_indexing', 0);
|
await HashratesRepository.$setLatestRun('last_weekly_hashrates_indexing', 0);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err(`Cannot reset hashrate indexing timestamps. Reason: ` + (e instanceof Error ? e.message : 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;
|
emptyBlocks: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BlockAudit {
|
||||||
|
time: number,
|
||||||
|
height: number,
|
||||||
|
hash: string,
|
||||||
|
missingTxs: string[],
|
||||||
|
addedTxs: string[],
|
||||||
|
matchRate: number,
|
||||||
|
}
|
||||||
|
|
||||||
export interface MempoolBlock {
|
export interface MempoolBlock {
|
||||||
blockSize: number;
|
blockSize: number;
|
||||||
blockVSize: number;
|
blockVSize: number;
|
||||||
@ -224,6 +233,13 @@ export interface IDifficultyAdjustment {
|
|||||||
timeOffset: number;
|
timeOffset: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IndexedDifficultyAdjustment {
|
||||||
|
time: number; // UNIX timestamp
|
||||||
|
height: number; // Block height
|
||||||
|
difficulty: number;
|
||||||
|
adjustment: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RewardStats {
|
export interface RewardStats {
|
||||||
totalReward: number;
|
totalReward: number;
|
||||||
totalFee: number;
|
totalFee: 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();
|
||||||
|
|
@ -7,6 +7,7 @@ import PoolsRepository from './PoolsRepository';
|
|||||||
import HashratesRepository from './HashratesRepository';
|
import HashratesRepository from './HashratesRepository';
|
||||||
import { escape } from 'mysql2';
|
import { escape } from 'mysql2';
|
||||||
import BlocksSummariesRepository from './BlocksSummariesRepository';
|
import BlocksSummariesRepository from './BlocksSummariesRepository';
|
||||||
|
import DifficultyAdjustmentsRepository from './DifficultyAdjustmentsRepository';
|
||||||
|
|
||||||
class BlocksRepository {
|
class BlocksRepository {
|
||||||
/**
|
/**
|
||||||
@ -381,48 +382,9 @@ class BlocksRepository {
|
|||||||
/**
|
/**
|
||||||
* Return blocks difficulty
|
* Return blocks difficulty
|
||||||
*/
|
*/
|
||||||
public async $getBlocksDifficulty(interval: string | null): Promise<object[]> {
|
public async $getBlocksDifficulty(): Promise<object[]> {
|
||||||
interval = Common.getSqlInterval(interval);
|
|
||||||
|
|
||||||
// :D ... Yeah don't ask me about this one https://stackoverflow.com/a/40303162
|
|
||||||
// Basically, using temporary user defined fields, we are able to extract all
|
|
||||||
// difficulty adjustments from the blocks tables.
|
|
||||||
// This allow use to avoid indexing it in another table.
|
|
||||||
let query = `
|
|
||||||
SELECT
|
|
||||||
*
|
|
||||||
FROM
|
|
||||||
(
|
|
||||||
SELECT
|
|
||||||
UNIX_TIMESTAMP(blockTimestamp) as timestamp, difficulty, height,
|
|
||||||
IF(@prevStatus = YT.difficulty, @rn := @rn + 1,
|
|
||||||
IF(@prevStatus := YT.difficulty, @rn := 1, @rn := 1)
|
|
||||||
) AS rn
|
|
||||||
FROM blocks YT
|
|
||||||
CROSS JOIN
|
|
||||||
(
|
|
||||||
SELECT @prevStatus := -1, @rn := 1
|
|
||||||
) AS var
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (interval) {
|
|
||||||
query += ` WHERE blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
|
|
||||||
}
|
|
||||||
|
|
||||||
query += `
|
|
||||||
ORDER BY YT.height
|
|
||||||
) AS t
|
|
||||||
WHERE t.rn = 1
|
|
||||||
ORDER BY t.height
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [rows]: any[] = await DB.query(query);
|
const [rows]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(blockTimestamp) as time, height, difficulty FROM blocks`);
|
||||||
|
|
||||||
for (const row of rows) {
|
|
||||||
delete row['rn'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return rows;
|
return rows;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err('Cannot generate difficulty history. Reason: ' + (e instanceof Error ? e.message : e));
|
logger.err('Cannot generate difficulty history. Reason: ' + (e instanceof Error ? e.message : e));
|
||||||
@ -452,26 +414,6 @@ class BlocksRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Check if the last 10 blocks chain is valid
|
|
||||||
*/
|
|
||||||
public async $validateRecentBlocks(): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const [lastBlocks]: any[] = await DB.query(`SELECT height, hash, previous_block_hash FROM blocks ORDER BY height DESC LIMIT 10`);
|
|
||||||
|
|
||||||
for (let i = 0; i < lastBlocks.length - 1; ++i) {
|
|
||||||
if (lastBlocks[i].previous_block_hash !== lastBlocks[i + 1].hash) {
|
|
||||||
logger.warn(`Chain divergence detected at block ${lastBlocks[i].height}, re-indexing most recent data`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return true; // Don't do anything if there is a db error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the chain of block hash is valid and delete data from the stale branch if needed
|
* Check if the chain of block hash is valid and delete data from the stale branch if needed
|
||||||
*/
|
*/
|
||||||
@ -494,10 +436,11 @@ class BlocksRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (blocks[idx].previous_block_hash !== blocks[idx - 1].hash) {
|
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 this.$deleteBlocksFrom(blocks[idx - 1].height);
|
||||||
await BlocksSummariesRepository.$deleteBlocksFrom(blocks[idx - 1].height);
|
await BlocksSummariesRepository.$deleteBlocksFrom(blocks[idx - 1].height);
|
||||||
await HashratesRepository.$deleteHashratesFromTimestamp(blocks[idx - 1].timestamp - 604800);
|
await HashratesRepository.$deleteHashratesFromTimestamp(blocks[idx - 1].timestamp - 604800);
|
||||||
|
await DifficultyAdjustmentsRepository.$deleteAdjustementsFromHeight(blocks[idx - 1].height);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
++idx;
|
++idx;
|
||||||
|
95
backend/src/repositories/DifficultyAdjustmentsRepository.ts
Normal file
95
backend/src/repositories/DifficultyAdjustmentsRepository.ts
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import { Common } from '../api/common';
|
||||||
|
import config from '../config';
|
||||||
|
import DB from '../database';
|
||||||
|
import logger from '../logger';
|
||||||
|
import { IndexedDifficultyAdjustment } from '../mempool.interfaces';
|
||||||
|
|
||||||
|
class DifficultyAdjustmentsRepository {
|
||||||
|
public async $saveAdjustments(adjustment: IndexedDifficultyAdjustment): Promise<void> {
|
||||||
|
if (adjustment.height === 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query = `INSERT INTO difficulty_adjustments(time, height, difficulty, adjustment) VALUE (FROM_UNIXTIME(?), ?, ?, ?)`;
|
||||||
|
const params: any[] = [
|
||||||
|
adjustment.time,
|
||||||
|
adjustment.height,
|
||||||
|
adjustment.difficulty,
|
||||||
|
adjustment.adjustment,
|
||||||
|
];
|
||||||
|
await DB.query(query, params);
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
||||||
|
logger.debug(`Cannot save difficulty adjustment at block ${adjustment.height}, already indexed, ignoring`);
|
||||||
|
} else {
|
||||||
|
logger.err(`Cannot save difficulty adjustment at block ${adjustment.height}. Reason: ${e instanceof Error ? e.message : e}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $getAdjustments(interval: string | null, descOrder: boolean = false): Promise<IndexedDifficultyAdjustment[]> {
|
||||||
|
interval = Common.getSqlInterval(interval);
|
||||||
|
|
||||||
|
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 {
|
||||||
|
query += ` ORDER BY time`;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [rows] = await DB.query(query);
|
||||||
|
return rows as IndexedDifficultyAdjustment[];
|
||||||
|
} catch (e) {
|
||||||
|
logger.err(`Cannot get difficulty adjustments from the database. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $getAdjustmentsHeights(): Promise<number[]> {
|
||||||
|
try {
|
||||||
|
const [rows]: any[] = await DB.query(`SELECT height FROM difficulty_adjustments`);
|
||||||
|
return rows.map(block => block.height);
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.err(`Cannot get difficulty adjustment block heights. Reason: ${e instanceof Error ? e.message : e}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $deleteAdjustementsFromHeight(height: number): Promise<void> {
|
||||||
|
try {
|
||||||
|
logger.info(`Delete newer difficulty adjustments from height ${height} from the database`);
|
||||||
|
await DB.query(`DELETE FROM difficulty_adjustments WHERE height >= ?`, [height]);
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.err(`Cannot delete difficulty adjustments from the database. Reason: ${e instanceof Error ? e.message : e}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $deleteLastAdjustment(): Promise<void> {
|
||||||
|
try {
|
||||||
|
logger.info(`Delete last difficulty adjustment from the database`);
|
||||||
|
await DB.query(`DELETE FROM difficulty_adjustments ORDER BY time LIMIT 1`);
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.err(`Cannot delete last difficulty adjustment from the database. Reason: ${e instanceof Error ? e.message : e}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new DifficultyAdjustmentsRepository();
|
||||||
|
|
@ -1,5 +1,6 @@
|
|||||||
import { escape } from 'mysql2';
|
import { escape } from 'mysql2';
|
||||||
import { Common } from '../api/common';
|
import { Common } from '../api/common';
|
||||||
|
import config from '../config';
|
||||||
import DB from '../database';
|
import DB from '../database';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import PoolsRepository from './PoolsRepository';
|
import PoolsRepository from './PoolsRepository';
|
||||||
@ -32,7 +33,9 @@ class HashratesRepository {
|
|||||||
public async $getNetworkDailyHashrate(interval: string | null): Promise<any[]> {
|
public async $getNetworkDailyHashrate(interval: string | null): Promise<any[]> {
|
||||||
interval = Common.getSqlInterval(interval);
|
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`;
|
FROM hashrates`;
|
||||||
|
|
||||||
if (interval) {
|
if (interval) {
|
||||||
@ -42,6 +45,7 @@ class HashratesRepository {
|
|||||||
query += ` WHERE hashrates.type = 'daily'`;
|
query += ` WHERE hashrates.type = 'daily'`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query += ` GROUP BY UNIX_TIMESTAMP(hashrate_timestamp) DIV ${86400}`;
|
||||||
query += ` ORDER by hashrate_timestamp`;
|
query += ` ORDER by hashrate_timestamp`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -75,6 +79,9 @@ class HashratesRepository {
|
|||||||
interval = Common.getSqlInterval(interval);
|
interval = Common.getSqlInterval(interval);
|
||||||
|
|
||||||
const topPoolsId = (await PoolsRepository.$getPoolsInfo('1w')).map((pool) => pool.poolId);
|
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
|
let query = `SELECT UNIX_TIMESTAMP(hashrate_timestamp) as timestamp, avg_hashrate as avgHashrate, share, pools.name as poolName
|
||||||
FROM hashrates
|
FROM hashrates
|
||||||
|
@ -5,7 +5,11 @@ import { Prices } from '../tasks/price-updater';
|
|||||||
class PricesRepository {
|
class PricesRepository {
|
||||||
public async $savePrices(time: number, prices: Prices): Promise<void> {
|
public async $savePrices(time: number, prices: Prices): Promise<void> {
|
||||||
try {
|
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) {
|
} catch (e: any) {
|
||||||
logger.err(`Cannot save exchange rate into db. Reason: ` + (e instanceof Error ? e.message : e));
|
logger.err(`Cannot save exchange rate into db. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
throw e;
|
throw e;
|
||||||
|
@ -26,6 +26,8 @@ import mining from './api/mining';
|
|||||||
import BlocksRepository from './repositories/BlocksRepository';
|
import BlocksRepository from './repositories/BlocksRepository';
|
||||||
import HashratesRepository from './repositories/HashratesRepository';
|
import HashratesRepository from './repositories/HashratesRepository';
|
||||||
import difficultyAdjustment from './api/difficulty-adjustment';
|
import difficultyAdjustment from './api/difficulty-adjustment';
|
||||||
|
import DifficultyAdjustmentsRepository from './repositories/DifficultyAdjustmentsRepository';
|
||||||
|
import BlocksAuditsRepository from './repositories/BlocksAuditsRepository';
|
||||||
|
|
||||||
class Routes {
|
class Routes {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
@ -653,7 +655,7 @@ class Routes {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const hashrates = await HashratesRepository.$getNetworkDailyHashrate(req.params.interval);
|
const hashrates = await HashratesRepository.$getNetworkDailyHashrate(req.params.interval);
|
||||||
const difficulty = await BlocksRepository.$getBlocksDifficulty(req.params.interval);
|
const difficulty = await DifficultyAdjustmentsRepository.$getAdjustments(req.params.interval, false);
|
||||||
const blockCount = await BlocksRepository.$blockCount(null, null);
|
const blockCount = await BlocksRepository.$blockCount(null, null);
|
||||||
res.header('Pragma', 'public');
|
res.header('Pragma', 'public');
|
||||||
res.header('Cache-control', 'public');
|
res.header('Cache-control', 'public');
|
||||||
@ -730,6 +732,32 @@ class Routes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async $getDifficultyAdjustments(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const difficulty = await DifficultyAdjustmentsRepository.$getAdjustments(req.params.interval, true);
|
||||||
|
res.header('Pragma', 'public');
|
||||||
|
res.header('Cache-control', 'public');
|
||||||
|
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
|
||||||
|
res.json(difficulty.map(adj => [adj.time, adj.height, adj.difficulty, adj.adjustment]));
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
public async getBlock(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const block = await blocks.$getBlock(req.params.hash);
|
const block = await blocks.$getBlock(req.params.hash);
|
||||||
@ -925,6 +953,16 @@ class Routes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getBlockTipHash(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const result = await bitcoinApi.$getBlockHashTip();
|
||||||
|
res.setHeader('content-type', 'text/plain');
|
||||||
|
res.send(result);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async getTxIdsForBlock(req: Request, res: Response) {
|
public async getTxIdsForBlock(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const result = await bitcoinApi.$getTxIdsForBlock(req.params.hash);
|
const result = await bitcoinApi.$getTxIdsForBlock(req.params.hash);
|
||||||
|
@ -87,7 +87,7 @@ class KrakenApi implements PriceFeed {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(priceHistory).length > 0) {
|
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;
|
++insertedCount;
|
||||||
}
|
}
|
||||||
if (insertedCount > 0) {
|
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
|
// Insert Kraken weekly prices
|
||||||
@ -205,23 +205,23 @@ class PriceUpdater {
|
|||||||
try {
|
try {
|
||||||
historicalPrices.push(await feed.$fetchRecentHourlyPrice(this.currencies));
|
historicalPrices.push(await feed.$fetchRecentHourlyPrice(this.currencies));
|
||||||
} catch (e) {
|
} 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
|
// Group them by timestamp and currency, for example
|
||||||
// grouped[123456789]['USD'] = [1, 2, 3, 4];
|
// grouped[123456789]['USD'] = [1, 2, 3, 4];
|
||||||
let grouped: Object = {};
|
const grouped: Object = {};
|
||||||
for (const historicalEntry of historicalPrices) {
|
for (const historicalEntry of historicalPrices) {
|
||||||
for (const time in historicalEntry) {
|
for (const time in historicalEntry) {
|
||||||
if (existingPriceTimes.includes(parseInt(time, 10))) {
|
if (existingPriceTimes.includes(parseInt(time, 10))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (grouped[time] == undefined) {
|
if (grouped[time] === undefined) {
|
||||||
grouped[time] = {
|
grouped[time] = {
|
||||||
USD: [], EUR: [], GBP: [], CAD: [], CHF: [], AUD: [], JPY: []
|
USD: [], EUR: [], GBP: [], CAD: [], CHF: [], AUD: [], JPY: []
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const currency of this.currencies) {
|
for (const currency of this.currencies) {
|
||||||
@ -238,13 +238,20 @@ class PriceUpdater {
|
|||||||
for (const time in grouped) {
|
for (const time in grouped) {
|
||||||
const prices: Prices = this.getEmptyPricesObj();
|
const prices: Prices = this.getEmptyPricesObj();
|
||||||
for (const currency in grouped[time]) {
|
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);
|
await PricesRepository.$savePrices(parseInt(time, 10), prices);
|
||||||
++totalInserted;
|
++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;
|
return data.data;
|
||||||
} catch (e) {
|
} 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++;
|
retry++;
|
||||||
}
|
}
|
||||||
await setDelay(config.MEMPOOL.EXTERNAL_RETRY_INTERVAL);
|
if (retry < config.MEMPOOL.EXTERNAL_MAX_RETRY) {
|
||||||
|
await setDelay(config.MEMPOOL.EXTERNAL_RETRY_INTERVAL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.err(`Could not connect to ${path}. All ${config.MEMPOOL.EXTERNAL_MAX_RETRY} attempts failed`);
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
3
frontend/.eslintignore
Normal file
3
frontend/.eslintignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
frontend
|
34
frontend/.eslintrc
Normal file
34
frontend/.eslintrc
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint"
|
||||||
|
],
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/eslint-recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/ban-ts-comment": 1,
|
||||||
|
"@typescript-eslint/ban-types": 1,
|
||||||
|
"@typescript-eslint/no-empty-function": 1,
|
||||||
|
"@typescript-eslint/no-explicit-any": 1,
|
||||||
|
"@typescript-eslint/no-inferrable-types": 1,
|
||||||
|
"@typescript-eslint/no-namespace": 1,
|
||||||
|
"@typescript-eslint/no-this-alias": 1,
|
||||||
|
"@typescript-eslint/no-var-requires": 1,
|
||||||
|
"no-case-declarations": 1,
|
||||||
|
"no-console": 1,
|
||||||
|
"no-constant-condition": 1,
|
||||||
|
"no-dupe-else-if": 1,
|
||||||
|
"no-empty": 1,
|
||||||
|
"no-extra-boolean-cast": 1,
|
||||||
|
"no-prototype-builtins": 1,
|
||||||
|
"no-self-assign": 1,
|
||||||
|
"no-useless-catch": 1,
|
||||||
|
"no-var": 1,
|
||||||
|
"prefer-const": 1,
|
||||||
|
"prefer-rest-params": 1
|
||||||
|
}
|
||||||
|
}
|
@ -248,23 +248,6 @@
|
|||||||
"browserTarget": "mempool:build"
|
"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": {
|
"e2e": {
|
||||||
"builder": "@cypress/schematic:cypress",
|
"builder": "@cypress/schematic:cypress",
|
||||||
"options": {
|
"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
|
|
||||||
});
|
|
||||||
};
|
|
4414
frontend/package-lock.json
generated
4414
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -42,7 +42,8 @@
|
|||||||
"build-mempool-bisq-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-bisq.js --standalone bisqJS > ./dist/mempool/browser/en-US/bisq.js",
|
"build-mempool-bisq-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-bisq.js --standalone bisqJS > ./dist/mempool/browser/en-US/bisq.js",
|
||||||
"build-mempool-liquid-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-liquid.js --standalone liquidJS > ./dist/mempool/browser/en-US/liquid.js",
|
"build-mempool-liquid-js": "browserify -p tinyify ./node_modules/@mempool/mempool.js/lib/index-liquid.js --standalone liquidJS > ./dist/mempool/browser/en-US/liquid.js",
|
||||||
"test": "npm run ng -- test",
|
"test": "npm run ng -- test",
|
||||||
"lint": "npm run ng -- lint",
|
"lint": "./node_modules/.bin/eslint . --ext .ts",
|
||||||
|
"lint:fix": "./node_modules/.bin/eslint . --ext .ts --fix",
|
||||||
"e2e": "npm run generate-config && npm run ng -- e2e",
|
"e2e": "npm run generate-config && npm run ng -- e2e",
|
||||||
"e2e:ci": "npm run cypress:run:ci",
|
"e2e:ci": "npm run cypress:run:ci",
|
||||||
"config:defaults:mempool": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true LIQUID_TESTNET_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=mempool BLOCK_WEIGHT_UNITS=4000000 && npm run generate-config",
|
"config:defaults:mempool": "node update-config.js TESTNET_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true LIQUID_TESTNET_ENABLED=true BISQ_ENABLED=true ITEMS_PER_PAGE=25 BASE_MODULE=mempool BLOCK_WEIGHT_UNITS=4000000 && npm run generate-config",
|
||||||
@ -77,7 +78,6 @@
|
|||||||
"@fortawesome/fontawesome-common-types": "~6.1.1",
|
"@fortawesome/fontawesome-common-types": "~6.1.1",
|
||||||
"@fortawesome/fontawesome-svg-core": "~6.1.1",
|
"@fortawesome/fontawesome-svg-core": "~6.1.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "~6.1.1",
|
"@fortawesome/free-solid-svg-icons": "~6.1.1",
|
||||||
"@juggle/resize-observer": "^3.3.1",
|
|
||||||
"@mempool/mempool.js": "2.3.0",
|
"@mempool/mempool.js": "2.3.0",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^11.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^11.0.0",
|
||||||
"@nguniversal/express-engine": "~13.1.1",
|
"@nguniversal/express-engine": "~13.1.1",
|
||||||
@ -88,7 +88,7 @@
|
|||||||
"domino": "^2.1.6",
|
"domino": "^2.1.6",
|
||||||
"echarts": "~5.3.2",
|
"echarts": "~5.3.2",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"lightweight-charts": "^3.3.0",
|
"lightweight-charts": "~3.8.0",
|
||||||
"ngx-bootrap-multiselect": "^2.0.0",
|
"ngx-bootrap-multiselect": "^2.0.0",
|
||||||
"ngx-echarts": "8.0.1",
|
"ngx-echarts": "8.0.1",
|
||||||
"ngx-infinite-scroll": "^10.0.1",
|
"ngx-infinite-scroll": "^10.0.1",
|
||||||
@ -104,28 +104,23 @@
|
|||||||
"@angular/language-service": "~13.3.10",
|
"@angular/language-service": "~13.3.10",
|
||||||
"@nguniversal/builders": "~13.1.1",
|
"@nguniversal/builders": "~13.1.1",
|
||||||
"@types/express": "^4.17.0",
|
"@types/express": "^4.17.0",
|
||||||
"@types/jasmine": "~4.0.3",
|
|
||||||
"@types/jasminewd2": "~2.0.10",
|
|
||||||
"@types/node": "^12.11.1",
|
"@types/node": "^12.11.1",
|
||||||
"codelyzer": "~6.0.2",
|
"@typescript-eslint/eslint-plugin": "^5.30.5",
|
||||||
"http-proxy-middleware": "^1.0.5",
|
"@typescript-eslint/parser": "^5.30.5",
|
||||||
"jasmine-core": "~4.1.0",
|
"eslint": "^8.19.0",
|
||||||
"jasmine-spec-reporter": "~7.0.0",
|
"http-proxy-middleware": "~2.0.6",
|
||||||
"karma": "~6.3.19",
|
"ts-node": "~10.8.1",
|
||||||
"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",
|
|
||||||
"typescript": "~4.6.4"
|
"typescript": "~4.6.4"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@cypress/schematic": "^1.3.0",
|
"@cypress/schematic": "~2.0.0",
|
||||||
"cypress": "^10.0.2",
|
"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",
|
"cypress-wait-until": "^1.7.1",
|
||||||
"mock-socket": "^9.0.3",
|
"mock-socket": "~9.1.4",
|
||||||
"start-server-and-test": "^1.12.6"
|
"start-server-and-test": "~1.14.0"
|
||||||
|
},
|
||||||
|
"scarfSettings": {
|
||||||
|
"enabled": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,7 @@ let routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'block',
|
path: 'block',
|
||||||
component: StartComponent,
|
component: StartComponent,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: ':id',
|
path: ':id',
|
||||||
component: BlockComponent
|
component: BlockComponent
|
||||||
@ -258,7 +258,7 @@ let routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'block',
|
path: 'block',
|
||||||
component: StartComponent,
|
component: StartComponent,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: ':id',
|
path: ':id',
|
||||||
component: BlockComponent
|
component: BlockComponent
|
||||||
@ -361,7 +361,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
|||||||
{
|
{
|
||||||
path: 'block',
|
path: 'block',
|
||||||
component: StartComponent,
|
component: StartComponent,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: ':id',
|
path: ':id',
|
||||||
component: BlockComponent
|
component: BlockComponent
|
||||||
@ -465,7 +465,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
|
|||||||
{
|
{
|
||||||
path: 'block',
|
path: 'block',
|
||||||
component: StartComponent,
|
component: StartComponent,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: ':id',
|
path: ':id',
|
||||||
component: BlockComponent
|
component: BlockComponent
|
||||||
|
@ -31,7 +31,7 @@ export class BisqTransfersComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
ngOnChanges() {
|
||||||
this.totalOutput = this.tx.outputs.filter((output) => output.isVerified).reduce((acc, output) => acc + output.bsqAmount, 0);;
|
this.totalOutput = this.tx.outputs.filter((output) => output.isVerified).reduce((acc, output) => acc + output.bsqAmount, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
switchCurrency() {
|
switchCurrency() {
|
||||||
|
@ -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();
|
|
||||||
});
|
|
||||||
});
|
|
@ -130,7 +130,7 @@
|
|||||||
<span i18n="address.error.loading-address-data">Error loading address data.</span>
|
<span i18n="address.error.loading-address-data">Error loading address data.</span>
|
||||||
<br>
|
<br>
|
||||||
<ng-template #displayServerError><i class="small">({{ error.error }})</i></ng-template>
|
<ng-template #displayServerError><i class="small">({{ error.error }})</i></ng-template>
|
||||||
<ng-template [ngIf]="error.status === 413 || error.status === 405" [ngIfElse]="displayServerError">
|
<ng-template [ngIf]="error.status === 413 || error.status === 405 || error.status === 504" [ngIfElse]="displayServerError">
|
||||||
<ng-container i18n="Electrum server limit exceeded error">
|
<ng-container i18n="Electrum server limit exceeded error">
|
||||||
<i>There many transactions on this address, more than your backend can handle. See more on <a href="/docs/faq#address-lookup-issues">setting up a stronger backend</a>.</i>
|
<i>There many transactions on this address, more than your backend can handle. See more on <a href="/docs/faq#address-lookup-issues">setting up a stronger backend</a>.</i>
|
||||||
<br><br>
|
<br><br>
|
||||||
|
@ -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!');
|
|
||||||
});
|
|
||||||
});
|
|
@ -35,7 +35,7 @@
|
|||||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 157680">
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 157680">
|
||||||
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-fee-rates' | relativeUrl]"> 3Y
|
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-fee-rates' | relativeUrl]"> 3Y
|
||||||
</label>
|
</label>
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount > 157680">
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-fee-rates' | relativeUrl]"> ALL
|
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-fee-rates' | relativeUrl]"> ALL
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
formatter: function (data) {
|
formatter: function(data) {
|
||||||
if (data.length <= 0) {
|
if (data.length <= 0) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 157680">
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 157680">
|
||||||
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-fees' | relativeUrl]"> 3Y
|
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-fees' | relativeUrl]"> 3Y
|
||||||
</label>
|
</label>
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount > 157680">
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-fees' | relativeUrl]"> ALL
|
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-fees' | relativeUrl]"> ALL
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -36,7 +36,7 @@
|
|||||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 157680">
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 157680">
|
||||||
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]"> 3Y
|
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]"> 3Y
|
||||||
</label>
|
</label>
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount > 157680">
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]"> ALL
|
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-rewards' | relativeUrl]"> ALL
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 157680">
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 157680">
|
||||||
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]"> 3Y
|
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]"> 3Y
|
||||||
</label>
|
</label>
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount > 157680">
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]"> ALL
|
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]"> ALL
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="td-width" i18n="block.hash">Hash</td>
|
<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>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="block.timestamp">Timestamp</td>
|
<td i18n="block.timestamp">Timestamp</td>
|
||||||
@ -358,7 +358,7 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<span i18n="error.general-loading-data">Error loading data.</span>
|
<span i18n="error.general-loading-data">Error loading data.</span>
|
||||||
<br><br>
|
<br><br>
|
||||||
<i>{{ error.code }}: {{ error.error }}</i>
|
<i>{{ error.status }}: {{ error.error }}</i>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</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();
|
|
||||||
});
|
|
||||||
});
|
|
@ -9,7 +9,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody *ngIf="(hashrateObservable$ | async) as data">
|
<tbody *ngIf="(hashrateObservable$ | async) as data">
|
||||||
<tr *ngFor="let diffChange of data.difficulty">
|
<tr *ngFor="let diffChange of data">
|
||||||
<td class="d-none d-md-block"><a [routerLink]="['/block' | relativeUrl, diffChange.height]">{{ diffChange.height
|
<td class="d-none d-md-block"><a [routerLink]="['/block' | relativeUrl, diffChange.height]">{{ diffChange.height
|
||||||
}}</a></td>
|
}}</a></td>
|
||||||
<td class="text-left">
|
<td class="text-left">
|
||||||
@ -17,7 +17,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="text-right">{{ diffChange.difficultyShorten }}</td>
|
<td class="text-right">{{ diffChange.difficultyShorten }}</td>
|
||||||
<td class="text-right" [style]="diffChange.change >= 0 ? 'color: #42B747' : 'color: #B74242'">
|
<td class="text-right" [style]="diffChange.change >= 0 ? 'color: #42B747' : 'color: #B74242'">
|
||||||
{{ diffChange.change >= 0 ? '+' : '' }}{{ diffChange.change | amountShortener }}%
|
{{ diffChange.change >= 0 ? '+' : '' }}{{ diffChange.change | amountShortener: 2 }}%
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -30,27 +30,24 @@ export class DifficultyAdjustmentsTable implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.hashrateObservable$ = this.apiService.getHistoricalHashrate$('1y')
|
this.hashrateObservable$ = this.apiService.getDifficultyAdjustments$('3m')
|
||||||
.pipe(
|
.pipe(
|
||||||
map((response) => {
|
map((response) => {
|
||||||
const data = response.body;
|
const data = response.body;
|
||||||
const tableData = [];
|
const tableData = [];
|
||||||
for (let i = data.difficulty.length - 1; i > 0; --i) {
|
for (const adjustment of data) {
|
||||||
const selectedPowerOfTen: any = selectPowerOfTen(data.difficulty[i].difficulty);
|
const selectedPowerOfTen: any = selectPowerOfTen(adjustment[2]);
|
||||||
const change = (data.difficulty[i].difficulty / data.difficulty[i - 1].difficulty - 1) * 100;
|
tableData.push({
|
||||||
|
height: adjustment[1],
|
||||||
tableData.push(Object.assign(data.difficulty[i], {
|
timestamp: adjustment[0],
|
||||||
change: Math.round(change * 100) / 100,
|
change: (adjustment[3] - 1) * 100,
|
||||||
difficultyShorten: formatNumber(
|
difficultyShorten: formatNumber(
|
||||||
data.difficulty[i].difficulty / selectedPowerOfTen.divider,
|
adjustment[2] / selectedPowerOfTen.divider,
|
||||||
this.locale, '1.2-2') + selectedPowerOfTen.unit
|
this.locale, '1.2-2') + selectedPowerOfTen.unit
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
|
return tableData.slice(0, 6);
|
||||||
return {
|
|
||||||
difficulty: tableData.slice(0, 6),
|
|
||||||
};
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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"
|
<a routerLinkActive="active" class="btn btn-primary w-50 mr-1"
|
||||||
[routerLink]="['/graphs/mempool' | relativeUrl]">Mempool</a>
|
[routerLink]="['/graphs/mempool' | relativeUrl]">Mempool</a>
|
||||||
<div ngbDropdown class="w-50">
|
<div ngbDropdown class="w-50">
|
||||||
@ -18,6 +18,8 @@
|
|||||||
[routerLink]="['/graphs/mining/block-rewards' | relativeUrl]" i18n="mining.block-rewards">Block Rewards</a>
|
[routerLink]="['/graphs/mining/block-rewards' | relativeUrl]" i18n="mining.block-rewards">Block Rewards</a>
|
||||||
<a class="dropdown-item" routerLinkActive="active"
|
<a class="dropdown-item" routerLinkActive="active"
|
||||||
[routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]" i18n="mining.block-sizes-weights">Block Sizes and Weights</a>
|
[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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 157680">
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 157680">
|
||||||
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]"> 3Y
|
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]"> 3Y
|
||||||
</label>
|
</label>
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount > 157680">
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]"> ALL
|
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/hashrate-difficulty' | relativeUrl]"> ALL
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,6 +11,7 @@ import { StorageService } from 'src/app/services/storage.service';
|
|||||||
import { MiningService } from 'src/app/services/mining.service';
|
import { MiningService } from 'src/app/services/mining.service';
|
||||||
import { download } from 'src/app/shared/graphs.utils';
|
import { download } from 'src/app/shared/graphs.utils';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { StateService } from 'src/app/services/state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-hashrate-chart',
|
selector: 'app-hashrate-chart',
|
||||||
@ -47,7 +48,7 @@ export class HashrateChartComponent implements OnInit {
|
|||||||
formatNumber = formatNumber;
|
formatNumber = formatNumber;
|
||||||
timespan = '';
|
timespan = '';
|
||||||
chartInstance: any = undefined;
|
chartInstance: any = undefined;
|
||||||
maResolution: number = 30;
|
network = '';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(LOCALE_ID) public locale: string,
|
@Inject(LOCALE_ID) public locale: string,
|
||||||
@ -57,17 +58,20 @@ export class HashrateChartComponent implements OnInit {
|
|||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
private miningService: MiningService,
|
private miningService: MiningService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
private stateService: StateService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||||
|
|
||||||
let firstRun = true;
|
let firstRun = true;
|
||||||
|
|
||||||
if (this.widget) {
|
if (this.widget) {
|
||||||
this.miningWindowPreference = '1y';
|
this.miningWindowPreference = '1y';
|
||||||
} else {
|
} else {
|
||||||
this.seoService.setTitle($localize`:@@3510fc6daa1d975f331e3a717bdf1a34efa06dff:Hashrate & Difficulty`);
|
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 = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||||
@ -95,6 +99,7 @@ export class HashrateChartComponent implements OnInit {
|
|||||||
.pipe(
|
.pipe(
|
||||||
tap((response) => {
|
tap((response) => {
|
||||||
const data = response.body;
|
const data = response.body;
|
||||||
|
|
||||||
// We generate duplicated data point so the tooltip works nicely
|
// We generate duplicated data point so the tooltip works nicely
|
||||||
const diffFixed = [];
|
const diffFixed = [];
|
||||||
let diffIndex = 1;
|
let diffIndex = 1;
|
||||||
@ -112,7 +117,7 @@ export class HashrateChartComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (hashIndex < data.hashrates.length && diffIndex < data.difficulty.length &&
|
while (hashIndex < data.hashrates.length && diffIndex < data.difficulty.length &&
|
||||||
data.hashrates[hashIndex].timestamp <= data.difficulty[diffIndex].timestamp
|
data.hashrates[hashIndex].timestamp <= data.difficulty[diffIndex].time
|
||||||
) {
|
) {
|
||||||
diffFixed.push({
|
diffFixed.push({
|
||||||
timestamp: data.hashrates[hashIndex].timestamp,
|
timestamp: data.hashrates[hashIndex].timestamp,
|
||||||
@ -123,17 +128,14 @@ export class HashrateChartComponent implements OnInit {
|
|||||||
++diffIndex;
|
++diffIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.maResolution = 30;
|
let maResolution = 15;
|
||||||
if (["3m", "6m"].includes(this.timespan)) {
|
|
||||||
this.maResolution = 7;
|
|
||||||
}
|
|
||||||
const hashrateMa = [];
|
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;
|
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 += data.hashrates[i - y].avgHashrate;
|
||||||
}
|
}
|
||||||
avg /= this.maResolution;
|
avg /= maResolution;
|
||||||
hashrateMa.push([data.hashrates[i].timestamp * 1000, avg]);
|
hashrateMa.push([data.hashrates[i].timestamp * 1000, avg]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,17 +277,17 @@ export class HashrateChartComponent implements OnInit {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: $localize`::Difficulty`,
|
name: $localize`:@@25148835d92465353fc5fe8897c27d5369978e5a:Difficulty`,
|
||||||
inactiveColor: 'rgb(110, 112, 121)',
|
inactiveColor: 'rgb(110, 112, 121)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: 'white',
|
color: 'white',
|
||||||
},
|
},
|
||||||
icon: 'roundRect',
|
icon: 'roundRect',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: $localize`Hashrate` + ` (MA${this.maResolution})`,
|
name: $localize`Hashrate (MA)`,
|
||||||
inactiveColor: 'rgb(110, 112, 121)',
|
inactiveColor: 'rgb(110, 112, 121)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: 'white',
|
color: 'white',
|
||||||
},
|
},
|
||||||
icon: 'roundRect',
|
icon: 'roundRect',
|
||||||
@ -294,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 : [
|
yAxis: data.hashrates.length === 0 ? undefined : [
|
||||||
{
|
{
|
||||||
min: (value) => {
|
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',
|
type: 'value',
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
@ -362,7 +371,7 @@ export class HashrateChartComponent implements OnInit {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
zlevel: 2,
|
zlevel: 2,
|
||||||
name: $localize`Hashrate` + ` (MA${this.maResolution})`,
|
name: $localize`Hashrate (MA)`,
|
||||||
showSymbol: false,
|
showSymbol: false,
|
||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
data: data.hashrateMa,
|
data: data.hashrateMa,
|
||||||
@ -403,6 +412,10 @@ export class HashrateChartComponent implements OnInit {
|
|||||||
|
|
||||||
onChartInit(ec) {
|
onChartInit(ec) {
|
||||||
this.chartInstance = ec;
|
this.chartInstance = ec;
|
||||||
|
|
||||||
|
this.chartInstance.on('legendselectchanged', (e) => {
|
||||||
|
this.storageService.setValue('hashrate_difficulty_legend', JSON.stringify(e.selected));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isMobile() {
|
isMobile() {
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 157680">
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount >= 157680">
|
||||||
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/pools-dominance' | relativeUrl]"> 3Y
|
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/pools-dominance' | relativeUrl]"> 3Y
|
||||||
</label>
|
</label>
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.blockCount > 157680">
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/pools-dominance' | relativeUrl]"> ALL
|
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/pools-dominance' | relativeUrl]"> ALL
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -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">
|
||||||
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2">
|
<div class="card-body pl-lg-3 pr-lg-3 pl-2 pr-2">
|
||||||
<app-hashrate-chart [widget]="true"></app-hashrate-chart>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,7 +66,7 @@
|
|||||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.totalBlockCount >= 157680">
|
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.totalBlockCount >= 157680">
|
||||||
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/pools' | relativeUrl]"> 3Y
|
<input ngbButton type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/pools' | relativeUrl]"> 3Y
|
||||||
</label>
|
</label>
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm" *ngIf="stats.totalBlockCount > 157680">
|
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||||
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/pools' | relativeUrl]"><span i18n>All</span>
|
<input ngbButton type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/pools' | relativeUrl]"><span i18n>All</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<div class="container-xl">
|
<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>
|
<form [formGroup]="pushTxForm" (submit)="pushTxForm.valid && postTx()" novalidate>
|
||||||
<div class="mb-3">
|
<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();
|
|
||||||
});
|
|
||||||
});
|
|
@ -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>
|
</ng-template>
|
||||||
<ng-template #defaultAddress>
|
<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-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 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 *ngIf="vin.prevout.scriptpubkey_address.length > 40" class="addr-right">{{ vin.prevout.scriptpubkey_address | capAddress: 40: 10 }}</span>
|
||||||
</span>
|
</span>
|
||||||
@ -164,9 +164,9 @@
|
|||||||
'highlight': vout.scriptpubkey_address === this.address && this.address !== ''
|
'highlight': vout.scriptpubkey_address === this.address && this.address !== ''
|
||||||
}">
|
}">
|
||||||
<td>
|
<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-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 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 *ngIf="vout.scriptpubkey_address.length > 40" class="addr-right">{{ vout.scriptpubkey_address | capAddress: 40: 10 }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
@ -6017,6 +6017,20 @@ export const faqData = [
|
|||||||
fragment: "what-are-mining-pools",
|
fragment: "what-are-mining-pools",
|
||||||
title: "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",
|
type: "endpoint",
|
||||||
category: "basics",
|
category: "basics",
|
||||||
|
@ -17,15 +17,15 @@ export class ApiDocsNavComponent implements OnInit {
|
|||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if( this.whichTab === 'rest' ) {
|
if (this.whichTab === 'rest') {
|
||||||
this.tabData = restApiDocsData;
|
this.tabData = restApiDocsData;
|
||||||
} else if( this.whichTab = 'faq' ) {
|
} else if (this.whichTab === 'faq') {
|
||||||
this.tabData = faqData;
|
this.tabData = faqData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
navLinkClick( event ) {
|
navLinkClick(event) {
|
||||||
this.navLinkClickEvent.emit( event );
|
this.navLinkClickEvent.emit(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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.
|
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>
|
||||||
|
|
||||||
|
<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">
|
<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>
|
<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>
|
</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 { MiningDashboardComponent } from '../components/mining-dashboard/mining-dashboard.component';
|
||||||
import { HashrateChartComponent } from '../components/hashrate-chart/hashrate-chart.component';
|
import { HashrateChartComponent } from '../components/hashrate-chart/hashrate-chart.component';
|
||||||
import { HashrateChartPoolsComponent } from '../components/hashrates-chart-pools/hashrate-chart-pools.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';
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -47,6 +48,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
LbtcPegsGraphComponent,
|
LbtcPegsGraphComponent,
|
||||||
HashrateChartComponent,
|
HashrateChartComponent,
|
||||||
HashrateChartPoolsComponent,
|
HashrateChartPoolsComponent,
|
||||||
|
BlockPredictionsGraphComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
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 { BlockFeeRatesGraphComponent } from '../components/block-fee-rates-graph/block-fee-rates-graph.component';
|
||||||
import { BlockFeesGraphComponent } from '../components/block-fees-graph/block-fees-graph.component';
|
import { BlockFeesGraphComponent } from '../components/block-fees-graph/block-fees-graph.component';
|
||||||
import { BlockRewardsGraphComponent } from '../components/block-rewards-graph/block-rewards-graph.component';
|
import { BlockRewardsGraphComponent } from '../components/block-rewards-graph/block-rewards-graph.component';
|
||||||
@ -92,6 +93,10 @@ const routes: Routes = [
|
|||||||
path: '',
|
path: '',
|
||||||
redirectTo: 'mempool',
|
redirectTo: 'mempool',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'mining/block-predictions',
|
||||||
|
component: BlockPredictionsGraphComponent,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -140,7 +140,7 @@ export class ApiService {
|
|||||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pools` +
|
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pools` +
|
||||||
(interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
|
(interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPoolStats$(slug: string): Observable<PoolStat> {
|
getPoolStats$(slug: string): Observable<PoolStat> {
|
||||||
return this.httpClient.get<PoolStat>(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}`);
|
return this.httpClient.get<PoolStat>(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/pool/${slug}`);
|
||||||
@ -172,6 +172,13 @@ export class ApiService {
|
|||||||
return this.httpClient.get<TransactionStripped[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash + '/summary');
|
return this.httpClient.get<TransactionStripped[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/block/' + hash + '/summary');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDifficultyAdjustments$(interval: string | undefined): Observable<any> {
|
||||||
|
return this.httpClient.get<any[]>(
|
||||||
|
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty-adjustments` +
|
||||||
|
(interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getHistoricalHashrate$(interval: string | undefined): Observable<any> {
|
getHistoricalHashrate$(interval: string | undefined): Observable<any> {
|
||||||
return this.httpClient.get<any[]>(
|
return this.httpClient.get<any[]>(
|
||||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/hashrate` +
|
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/hashrate` +
|
||||||
@ -214,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> {
|
getRewardStats$(blockCount: number = 144): Observable<RewardStats> {
|
||||||
return this.httpClient.get<RewardStats>(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/reward-stats/${blockCount}`);
|
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 {
|
.mempool-graph {
|
||||||
@extend .mempool-graph;
|
@extend .mempool-graph;
|
||||||
direction: ltr;
|
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"
|
cd "${HOME}/electrs"
|
||||||
#source "${HOME}/.cargo/env"
|
#source "${HOME}/.cargo/env"
|
||||||
#export PATH="${HOME}/.cargo/bin:${PATH}"
|
#export PATH="${HOME}/.cargo/bin:${PATH}"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/usr/local/bin/zsh
|
#!/usr/bin/env zsh
|
||||||
cd "${HOME}/electrs"
|
cd "${HOME}/electrs"
|
||||||
#source "${HOME}/.cargo/env"
|
#source "${HOME}/.cargo/env"
|
||||||
#export PATH="${HOME}/.cargo/bin:${PATH}"
|
#export PATH="${HOME}/.cargo/bin:${PATH}"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/usr/local/bin/zsh
|
#!/usr/bin/env zsh
|
||||||
cd "${HOME}/electrs"
|
cd "${HOME}/electrs"
|
||||||
#source "${HOME}/.cargo/env"
|
#source "${HOME}/.cargo/env"
|
||||||
#export PATH="${HOME}/.cargo/bin:${PATH}"
|
#export PATH="${HOME}/.cargo/bin:${PATH}"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/usr/local/bin/zsh
|
#!/usr/bin/env zsh
|
||||||
cd "${HOME}/electrs"
|
cd "${HOME}/electrs"
|
||||||
#source $HOME/.cargo/env
|
#source $HOME/.cargo/env
|
||||||
#export PATH=$HOME/.cargo/bin:$PATH
|
#export PATH=$HOME/.cargo/bin:$PATH
|
||||||
|
@ -6,11 +6,13 @@ case `uname -s` in
|
|||||||
|
|
||||||
FreeBSD)
|
FreeBSD)
|
||||||
OS=FreeBSD
|
OS=FreeBSD
|
||||||
|
NPROC=$(sysctl hw.ncpu | awk '{print $2}')
|
||||||
;;
|
;;
|
||||||
|
|
||||||
Linux)
|
Linux)
|
||||||
if [ "$(grep -Ei 'debian|buntu|mint' /etc/*release)" ]; then
|
if [ "$(grep -Ei 'debian|buntu|mint' /etc/*release)" ]; then
|
||||||
OS=Debian
|
OS=Debian
|
||||||
|
NPROC=$(nproc --all)
|
||||||
else
|
else
|
||||||
echo "Your distribution of Linux is not yet supported by this installation script"
|
echo "Your distribution of Linux is not yet supported by this installation script"
|
||||||
exit 1
|
exit 1
|
||||||
@ -39,6 +41,7 @@ ELEMENTS_INSTALL=ON
|
|||||||
|
|
||||||
# configure 4 network instances
|
# configure 4 network instances
|
||||||
BITCOIN_MAINNET_ENABLE=ON
|
BITCOIN_MAINNET_ENABLE=ON
|
||||||
|
BITCOIN_MAINNET_MINFEE_ENABLE=ON
|
||||||
BITCOIN_TESTNET_ENABLE=ON
|
BITCOIN_TESTNET_ENABLE=ON
|
||||||
BITCOIN_SIGNET_ENABLE=ON
|
BITCOIN_SIGNET_ENABLE=ON
|
||||||
BISQ_MAINNET_ENABLE=ON
|
BISQ_MAINNET_ENABLE=ON
|
||||||
@ -191,9 +194,9 @@ case $OS in
|
|||||||
TOR_CONFIGURATION=/etc/tor/torrc
|
TOR_CONFIGURATION=/etc/tor/torrc
|
||||||
TOR_RESOURCES=/var/lib/tor
|
TOR_RESOURCES=/var/lib/tor
|
||||||
TOR_PKG=tor
|
TOR_PKG=tor
|
||||||
TOR_USER=tor-debian
|
TOR_USER=debian-tor
|
||||||
TOR_GROUP=tor-debian
|
TOR_GROUP=debian-tor
|
||||||
CERTBOT_PKG=python-certbot
|
CERTBOT_PKG=python3-certbot-nginx
|
||||||
NGINX_CONFIGURATION=/etc/nginx/nginx.conf
|
NGINX_CONFIGURATION=/etc/nginx/nginx.conf
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@ -320,12 +323,12 @@ LIQUIDTESTNET_ASSET_REGISTRY_DB_NAME=asset_registry_testnet_db
|
|||||||
|
|
||||||
# packages needed for mempool ecosystem
|
# packages needed for mempool ecosystem
|
||||||
DEBIAN_PKG=()
|
DEBIAN_PKG=()
|
||||||
DEBIAN_PKG+=(zsh vim curl screen openssl python3 dialog)
|
DEBIAN_PKG+=(zsh vim curl screen openssl python3 dialog cron)
|
||||||
DEBIAN_PKG+=(build-essential git git-lfs clang cmake jq)
|
DEBIAN_PKG+=(build-essential git git-lfs clang cmake jq)
|
||||||
DEBIAN_PKG+=(autotools-dev autoconf automake pkg-config bsdmainutils)
|
DEBIAN_PKG+=(autotools-dev autoconf automake pkg-config bsdmainutils)
|
||||||
DEBIAN_PKG+=(libevent-dev libdb-dev libssl-dev libtool-dev autotools-dev)
|
DEBIAN_PKG+=(libevent-dev libdb-dev libssl-dev libtool autotools-dev)
|
||||||
DEBIAN_PKG+=(libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev)
|
DEBIAN_PKG+=(libboost-system-dev libboost-filesystem-dev libboost-chrono-dev libboost-test-dev libboost-thread-dev)
|
||||||
DEBIAN_PKG+=(nodejs npm mariadb-server nginx-core python-certbot-nginx rsync ufw)
|
DEBIAN_PKG+=(nodejs npm mariadb-server nginx-core python3-certbot-nginx rsync ufw)
|
||||||
|
|
||||||
# packages needed for mempool ecosystem
|
# packages needed for mempool ecosystem
|
||||||
FREEBSD_PKG=()
|
FREEBSD_PKG=()
|
||||||
@ -555,7 +558,6 @@ zfsCreateFilesystems()
|
|||||||
ext4CreateDir()
|
ext4CreateDir()
|
||||||
{
|
{
|
||||||
mkdir -p "/backup" "${ELEMENTS_HOME}" "${BITCOIN_HOME}" "${MINFEE_HOME}" "${ELECTRS_HOME}" "${MEMPOOL_HOME}" "${MYSQL_HOME}" "${BITCOIN_ELECTRS_HOME}" "${ELEMENTS_HOME}/liquidv1" "${ELEMENTS_ELECTRS_HOME}"
|
mkdir -p "/backup" "${ELEMENTS_HOME}" "${BITCOIN_HOME}" "${MINFEE_HOME}" "${ELECTRS_HOME}" "${MEMPOOL_HOME}" "${MYSQL_HOME}" "${BITCOIN_ELECTRS_HOME}" "${ELEMENTS_HOME}/liquidv1" "${ELEMENTS_ELECTRS_HOME}"
|
||||||
exit
|
|
||||||
# Bitcoin Mainnet
|
# Bitcoin Mainnet
|
||||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
|
||||||
for folder in chainstate indexes blocks
|
for folder in chainstate indexes blocks
|
||||||
@ -683,6 +685,7 @@ $CUT >$input <<-EOF
|
|||||||
Tor:Enable Tor v3 HS Onion:ON
|
Tor:Enable Tor v3 HS Onion:ON
|
||||||
Certbot:Enable HTTPS using Certbot:ON
|
Certbot:Enable HTTPS using Certbot:ON
|
||||||
Mainnet:Enable Bitcoin Mainnet:ON
|
Mainnet:Enable Bitcoin Mainnet:ON
|
||||||
|
Mainnet-Minfee:Enable Bitcoin Mainnet Minfee:ON
|
||||||
Testnet:Enable Bitcoin Testnet:ON
|
Testnet:Enable Bitcoin Testnet:ON
|
||||||
Liquid:Enable Elements Liquid:ON
|
Liquid:Enable Elements Liquid:ON
|
||||||
Bisq:Enable Bisq:ON
|
Bisq:Enable Bisq:ON
|
||||||
@ -726,6 +729,12 @@ else
|
|||||||
BITCOIN_MAINNET_ENABLE=OFF
|
BITCOIN_MAINNET_ENABLE=OFF
|
||||||
fi
|
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
|
if grep Testnet $tempfile >/dev/null 2>&1;then
|
||||||
BITCOIN_TESTNET_ENABLE=ON
|
BITCOIN_TESTNET_ENABLE=ON
|
||||||
else
|
else
|
||||||
@ -965,7 +974,7 @@ if [ "${BITCOIN_INSTALL}" = ON ];then
|
|||||||
echo "[*] Building Bitcoin from source repo"
|
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} && ./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} && 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"
|
echo "[*] Installing Bitcoin binaries into OS"
|
||||||
osSudo "${ROOT_USER}" sh -c "cd ${BITCOIN_HOME}/${BITCOIN_REPO_NAME} && gmake install"
|
osSudo "${ROOT_USER}" sh -c "cd ${BITCOIN_HOME}/${BITCOIN_REPO_NAME} && gmake install"
|
||||||
@ -977,10 +986,10 @@ if [ "${BITCOIN_INSTALL}" = ON ];then
|
|||||||
osSudo "${ROOT_USER}" install -c -o "${MINFEE_USER}" -g "${MINFEE_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.minfee.conf" "${MINFEE_HOME}/bitcoin.conf"
|
osSudo "${ROOT_USER}" install -c -o "${MINFEE_USER}" -g "${MINFEE_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.minfee.conf" "${MINFEE_HOME}/bitcoin.conf"
|
||||||
|
|
||||||
echo "[*] Installing Bitcoin RPC credentials"
|
echo "[*] Installing Bitcoin RPC credentials"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_HOME}/bitcoin.conf"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_HOME}/bitcoin.conf"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_HOME}/bitcoin.conf"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_HOME}/bitcoin.conf"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${MINFEE_HOME}/bitcoin.conf"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${MINFEE_HOME}/bitcoin.conf"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${MINFEE_HOME}/bitcoin.conf"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${MINFEE_HOME}/bitcoin.conf"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
@ -1010,7 +1019,7 @@ if [ "${ELEMENTS_INSTALL}" = ON ];then
|
|||||||
echo "[*] Building Elements from source repo"
|
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} && ./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} && 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"
|
echo "[*] Installing Elements binaries into OS"
|
||||||
osSudo "${ROOT_USER}" sh -c "cd ${ELEMENTS_HOME}/${ELEMENTS_REPO_NAME} && gmake install"
|
osSudo "${ROOT_USER}" sh -c "cd ${ELEMENTS_HOME}/${ELEMENTS_REPO_NAME} && gmake install"
|
||||||
@ -1019,10 +1028,10 @@ if [ "${ELEMENTS_INSTALL}" = ON ];then
|
|||||||
osSudo "${ROOT_USER}" install -c -o "${ELEMENTS_USER}" -g "${ELEMENTS_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/elements.conf" "${ELEMENTS_HOME}/elements.conf"
|
osSudo "${ROOT_USER}" install -c -o "${ELEMENTS_USER}" -g "${ELEMENTS_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/elements.conf" "${ELEMENTS_HOME}/elements.conf"
|
||||||
|
|
||||||
echo "[*] Configuring Elements Liquid RPC credentials in elements.conf"
|
echo "[*] Configuring Elements Liquid RPC credentials in elements.conf"
|
||||||
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_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"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${ELEMENTS_HOME}/elements.conf"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__ELEMENTS_RPC_USER__/${ELEMENTS_RPC_USER}/" "${ELEMENTS_HOME}/elements.conf"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__ELEMENTS_RPC_USER__/${ELEMENTS_RPC_USER}/" "${ELEMENTS_HOME}/elements.conf"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__ELEMENTS_RPC_PASS__/${ELEMENTS_RPC_PASS}/" "${ELEMENTS_HOME}/elements.conf"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__ELEMENTS_RPC_PASS__/${ELEMENTS_RPC_PASS}/" "${ELEMENTS_HOME}/elements.conf"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
###################################
|
###################################
|
||||||
@ -1049,17 +1058,23 @@ case $OS in
|
|||||||
;;
|
;;
|
||||||
Debian)
|
Debian)
|
||||||
echo "[*] Installing Rust from rustup.rs"
|
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
|
esac
|
||||||
|
|
||||||
echo "[*] Building Bitcoin Electrs release binary"
|
echo "[*] Building Bitcoin Electrs release binary"
|
||||||
osSudo "${BITCOIN_USER}" sh -c "cd ${BITCOIN_ELECTRS_HOME} && cargo run --release --bin electrs -- --version" || true
|
osSudo "${BITCOIN_USER}" sh -c "cd ${BITCOIN_ELECTRS_HOME} && cargo run --release --bin electrs -- --version" || true
|
||||||
|
|
||||||
echo "[*] Patching Bitcoin Electrs code for FreeBSD"
|
case $OS in
|
||||||
osSudo "${BITCOIN_USER}" sh -c "cd \"${BITCOIN_HOME}/.cargo/registry/src/github.com-1ecc6299db9ec823/sysconf-0.3.4\" && patch -p1 < \"${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/freebsd/sysconf.patch\""
|
FreeBSD)
|
||||||
osSudo "${BITCOIN_USER}" sh -c "cd \"${BITCOIN_ELECTRS_HOME}/src/new_index/\" && sed -i .bak -e s/Snappy/None/ db.rs && rm db.rs.bak"
|
echo "[*] Patching Bitcoin Electrs code for FreeBSD"
|
||||||
osSudo "${BITCOIN_USER}" sh -c "cd \"${BITCOIN_ELECTRS_HOME}/src/bin/\" && sed -i .bak -e 's/from_secs(5)/from_secs(1)/' electrs.rs && rm electrs.rs.bak"
|
osSudo "${BITCOIN_USER}" sh -c "cd \"${BITCOIN_HOME}/.cargo/registry/src/github.com-1ecc6299db9ec823/sysconf-0.3.4\" && patch -p1 < \"${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/freebsd/sysconf.patch\""
|
||||||
|
osSudo "${BITCOIN_USER}" sh -c "cd \"${BITCOIN_ELECTRS_HOME}/src/new_index/\" && sed -i.bak -e s/Snappy/None/ db.rs && rm db.rs.bak"
|
||||||
|
osSudo "${BITCOIN_USER}" sh -c "cd \"${BITCOIN_ELECTRS_HOME}/src/bin/\" && sed -i.bak -e 's/from_secs(5)/from_secs(1)/' electrs.rs && rm electrs.rs.bak"
|
||||||
|
;;
|
||||||
|
Debian)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
echo "[*] Building Bitcoin Electrs release binary"
|
echo "[*] Building Bitcoin Electrs release binary"
|
||||||
osSudo "${BITCOIN_USER}" sh -c "cd ${BITCOIN_ELECTRS_HOME} && cargo run --release --bin electrs -- --version"
|
osSudo "${BITCOIN_USER}" sh -c "cd ${BITCOIN_ELECTRS_HOME} && cargo run --release --bin electrs -- --version"
|
||||||
@ -1090,20 +1105,17 @@ echo "[*] Cloning Liquid Asset Registry testnet repo from ${LIQUIDTESTNET_ASSET_
|
|||||||
osSudo "${ELEMENTS_USER}" git config --global advice.detachedHead false
|
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}"
|
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"
|
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
|
osSudo "${ELEMENTS_USER}" sh -c "cd ${ELEMENTS_ELECTRS_HOME} && cargo run --release --features liquid --bin electrs -- --network liquid --version" || true
|
||||||
|
|
||||||
echo "[*] Patching Liquid Electrs code for FreeBSD"
|
case $OS in
|
||||||
osSudo "${ELEMENTS_USER}" sh -c "cd \"${ELEMENTS_HOME}/.cargo/registry/src/github.com-1ecc6299db9ec823/sysconf-0.3.4\" && patch -p1 < \"${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/freebsd/sysconf.patch\""
|
FreeBSD)
|
||||||
|
echo "[*] Patching Liquid Electrs code for FreeBSD"
|
||||||
|
osSudo "${ELEMENTS_USER}" sh -c "cd \"${ELEMENTS_HOME}/.cargo/registry/src/github.com-1ecc6299db9ec823/sysconf-0.3.4\" && patch -p1 < \"${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/freebsd/sysconf.patch\""
|
||||||
|
;;
|
||||||
|
Debian)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
echo "[*] Building Liquid Electrs release binary"
|
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
|
osSudo "${ELEMENTS_USER}" sh -c "cd ${ELEMENTS_ELECTRS_HOME} && cargo run --release --features liquid --bin electrs -- --network liquid --version" || true
|
||||||
@ -1135,8 +1147,8 @@ if [ "${BISQ_INSTALL}" = ON ];then
|
|||||||
echo "[*] Cloning Bisq top-level repo"
|
echo "[*] Cloning Bisq top-level repo"
|
||||||
osSudo "${BISQ_USER}" git clone --branch "${BISQ_REPO_BRANCH}" "${BISQ_REPO_URL}" "${BISQ_HOME}/${BISQ_REPO_NAME}"
|
osSudo "${BISQ_USER}" git clone --branch "${BISQ_REPO_BRANCH}" "${BISQ_REPO_URL}" "${BISQ_HOME}/${BISQ_REPO_NAME}"
|
||||||
|
|
||||||
echo "[*] Installing OpenJDK 10.0.2 from Bisq install_java.sh script"
|
echo "[*] Installing OpenJDK from Bisq install_java_linux.sh script"
|
||||||
osSudo "${ROOT_USER}" "${BISQ_HOME}/${BISQ_REPO_NAME}/scripts/install_java.sh"
|
osSudo "${ROOT_USER}" "${BISQ_HOME}/${BISQ_REPO_NAME}/scripts/install_java_linux.sh"
|
||||||
|
|
||||||
echo "[*] Checking out Bisq ${BISQ_LATEST_RELEASE}"
|
echo "[*] Checking out Bisq ${BISQ_LATEST_RELEASE}"
|
||||||
osSudo "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && git checkout ${BISQ_LATEST_RELEASE}"
|
osSudo "${BISQ_USER}" sh -c "cd ${BISQ_HOME}/${BISQ_REPO_NAME} && git checkout ${BISQ_LATEST_RELEASE}"
|
||||||
@ -1159,26 +1171,26 @@ if [ "${BISQ_INSTALL}" = ON ];then
|
|||||||
|
|
||||||
Debian)
|
Debian)
|
||||||
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${BISQ_HOME}/${BISQ_REPO_NAME}/seednode/bisq.service" "${DEBIAN_SERVICE_HOME}/bisq.service"
|
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${BISQ_HOME}/${BISQ_REPO_NAME}/seednode/bisq.service" "${DEBIAN_SERVICE_HOME}/bisq.service"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/#Requires=bitcoin.service/Requires=bitcoin.service/" "${DEBIAN_SERVICE_HOME}/bisq.service"
|
osSudo "${ROOT_USER}" sed -i.orig "s/#Requires=bitcoin.service/Requires=bitcoin.service/" "${DEBIAN_SERVICE_HOME}/bisq.service"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/#BindsTo=bitcoin.service/BindsTo=bitcoin.service/" "${DEBIAN_SERVICE_HOME}/bisq.service"
|
osSudo "${ROOT_USER}" sed -i.orig "s/#BindsTo=bitcoin.service/BindsTo=bitcoin.service/" "${DEBIAN_SERVICE_HOME}/bisq.service"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__BISQ_REPO_NAME__/${BISQ_REPO_NAME}/" "${DEBIAN_SERVICE_HOME}/bisq.service"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BISQ_REPO_NAME__/${BISQ_REPO_NAME}/" "${DEBIAN_SERVICE_HOME}/bisq.service"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s!__BISQ_HOME__!${BISQ_HOME}!" "${DEBIAN_SERVICE_HOME}/bisq.service"
|
osSudo "${ROOT_USER}" sed -i.orig "s!__BISQ_HOME__!${BISQ_HOME}!" "${DEBIAN_SERVICE_HOME}/bisq.service"
|
||||||
|
|
||||||
echo "[*] Installing Bisq environment file"
|
echo "[*] Installing Bisq environment file"
|
||||||
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${BISQ_HOME}/${BISQ_REPO_NAME}/seednode/bisq.env" "${DEBIAN_ENV_HOME}/bisq.env"
|
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${BISQ_HOME}/${BISQ_REPO_NAME}/seednode/bisq.env" "${DEBIAN_ENV_HOME}/bisq.env"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s!__BISQ_APP_NAME__!${BISQ_APP_NAME}!" "${DEBIAN_ENV_HOME}/bisq.env"
|
osSudo "${ROOT_USER}" sed -i.orig "s!__BISQ_APP_NAME__!${BISQ_APP_NAME}!" "${DEBIAN_ENV_HOME}/bisq.env"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s!__BISQ_HOME__!${BISQ_HOME}!" "${DEBIAN_ENV_HOME}/bisq.env"
|
osSudo "${ROOT_USER}" sed -i.orig "s!__BISQ_HOME__!${BISQ_HOME}!" "${DEBIAN_ENV_HOME}/bisq.env"
|
||||||
|
|
||||||
echo "[*] Configuring Bisq environment file with Bitcoin RPC credentials"
|
echo "[*] Configuring Bisq environment file with Bitcoin RPC credentials"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__BITCOIN_P2P_HOST__/${BITCOIN_MAINNET_P2P_HOST}/" "${DEBIAN_ENV_HOME}/bisq.env"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_P2P_HOST__/${BITCOIN_MAINNET_P2P_HOST}/" "${DEBIAN_ENV_HOME}/bisq.env"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__BITCOIN_P2P_PORT__/${BITCOIN_MAINNET_P2P_PORT}/" "${DEBIAN_ENV_HOME}/bisq.env"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_P2P_PORT__/${BITCOIN_MAINNET_P2P_PORT}/" "${DEBIAN_ENV_HOME}/bisq.env"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__BITCOIN_RPC_HOST__/${BITCOIN_MAINNET_RPC_HOST}/" "${DEBIAN_ENV_HOME}/bisq.env"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_HOST__/${BITCOIN_MAINNET_RPC_HOST}/" "${DEBIAN_ENV_HOME}/bisq.env"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__BITCOIN_RPC_PORT__/${BITCOIN_MAINNET_RPC_PORT}/" "${DEBIAN_ENV_HOME}/bisq.env"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PORT__/${BITCOIN_MAINNET_RPC_PORT}/" "${DEBIAN_ENV_HOME}/bisq.env"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${DEBIAN_ENV_HOME}/bisq.env"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${DEBIAN_ENV_HOME}/bisq.env"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${DEBIAN_ENV_HOME}/bisq.env"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${DEBIAN_ENV_HOME}/bisq.env"
|
||||||
|
|
||||||
#echo "[*] Updating Bitcoin configuration for Bisq"
|
#echo "[*] Updating Bitcoin configuration for Bisq"
|
||||||
#osSudo "${ROOT_USER}" sed -i .orig "s/#blocknotify/blocknotify/" "${BITCOIN_HOME}/bitcoin.conf"
|
#osSudo "${ROOT_USER}" sed -i.orig "s/#blocknotify/blocknotify/" "${BITCOIN_HOME}/bitcoin.conf"
|
||||||
#osSudo "${BITCOIN_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${BISQ_HOME}/${BISQ_REPO_NAME}/seednode/blocknotify.sh" "${BITCOIN_HOME}/blocknotify.sh"
|
#osSudo "${BITCOIN_USER}" install -c -o "${BITCOIN_USER}" -g "${BITCOIN_GROUP}" -m 755 "${BISQ_HOME}/${BISQ_REPO_NAME}/seednode/blocknotify.sh" "${BITCOIN_HOME}/blocknotify.sh"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
@ -1198,7 +1210,79 @@ if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
Debian)
|
Debian)
|
||||||
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin-mainnet.service" "${DEBIAN_SERVICE_HOME}"
|
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/linux/bitcoin.service" "${DEBIAN_SERVICE_HOME}"
|
||||||
|
;;
|
||||||
|
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 #
|
||||||
|
################################
|
||||||
|
|
||||||
|
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||||
|
echo "[*] Installing Bitcoin Testnet service"
|
||||||
|
case $OS in
|
||||||
|
|
||||||
|
FreeBSD)
|
||||||
|
echo "[*] FIXME: Bitcoin Testnet 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-testnet.service" "${DEBIAN_SERVICE_HOME}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
###############################
|
||||||
|
# Bitcoin instance for Signet #
|
||||||
|
###############################
|
||||||
|
|
||||||
|
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
|
echo "[*] Installing Bitcoin Signet service"
|
||||||
|
case $OS in
|
||||||
|
|
||||||
|
FreeBSD)
|
||||||
|
echo "[*] FIXME: Bitcoin Signet 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-signet.service" "${DEBIAN_SERVICE_HOME}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
###############################
|
||||||
|
# Bitcoin instance for Liquid #
|
||||||
|
###############################
|
||||||
|
|
||||||
|
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
|
||||||
|
echo "[*] Installing Bitcoin Liquid service"
|
||||||
|
case $OS in
|
||||||
|
|
||||||
|
FreeBSD)
|
||||||
|
echo "[*] FIXME: Bitcoin Liquid 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/liquid.service" "${DEBIAN_SERVICE_HOME}"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
@ -1212,14 +1296,21 @@ 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}"
|
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"
|
echo "[*] Installing Bitcoin crontab"
|
||||||
# FIXME: must only crontab enabled daemons
|
case $OS in
|
||||||
osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.crontab"
|
FreeBSD)
|
||||||
osSudo "${ROOT_USER}" crontab -u "${MINFEE_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/minfee.crontab"
|
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"
|
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"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_USER__/${BITCOIN_RPC_USER}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
|
osSudo "${ROOT_USER}" sed -i.orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${BITCOIN_ELECTRS_HOME}/electrs-start-mainnet"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
@ -1230,10 +1321,17 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
|||||||
echo "[*] Installing Bitcoin Testnet electrs start script"
|
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}"
|
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"
|
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_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"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
|
osSudo "${ROOT_USER}" sed -i.orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${BITCOIN_ELECTRS_HOME}/electrs-start-testnet"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#######################################
|
#######################################
|
||||||
@ -1244,10 +1342,17 @@ if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
|||||||
echo "[*] Installing Bitcoin Signet electrs start script"
|
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}"
|
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"
|
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_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"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
|
osSudo "${ROOT_USER}" sed -i.orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${BITCOIN_ELECTRS_HOME}/electrs-start-signet"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
@ -1259,13 +1364,20 @@ 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}"
|
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"
|
echo "[*] Installing Elements crontab"
|
||||||
# FIXME: must only crontab enabled daemons
|
case $OS in
|
||||||
osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/elements.crontab"
|
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"
|
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"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__ELEMENTS_RPC_USER__/${ELEMENTS_RPC_USER}/" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquid"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__ELEMENTS_RPC_PASS__/${ELEMENTS_RPC_PASS}/" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquid"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__ELEMENTS_RPC_PASS__/${ELEMENTS_RPC_PASS}/" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquid"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquid"
|
osSudo "${ROOT_USER}" sed -i.orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquid"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
################################################
|
################################################
|
||||||
@ -1276,16 +1388,23 @@ if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
|
|||||||
echo "[*] Installing Elements Liquid Testnet electrs start script"
|
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}"
|
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"
|
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_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"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__BITCOIN_RPC_PASS__/${BITCOIN_RPC_PASS}/" "${ELEMENTS_HOME}/elements.conf"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__ELEMENTS_RPC_USER__/${ELEMENTS_RPC_USER}/" "${ELEMENTS_HOME}/elements.conf"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__ELEMENTS_RPC_USER__/${ELEMENTS_RPC_USER}/" "${ELEMENTS_HOME}/elements.conf"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__ELEMENTS_RPC_PASS__/${ELEMENTS_RPC_PASS}/" "${ELEMENTS_HOME}/elements.conf"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__ELEMENTS_RPC_PASS__/${ELEMENTS_RPC_PASS}/" "${ELEMENTS_HOME}/elements.conf"
|
||||||
|
|
||||||
echo "[*] Configuring Elements LiquidTestnet RPC credentials in electrs start script"
|
echo "[*] Configuring Elements LiquidTestnet RPC credentials in electrs start script"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__ELEMENTS_RPC_USER__/${ELEMENTS_RPC_USER}/" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquidtestnet"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__ELEMENTS_RPC_USER__/${ELEMENTS_RPC_USER}/" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquidtestnet"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s/__ELEMENTS_RPC_PASS__/${ELEMENTS_RPC_PASS}/" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquidtestnet"
|
osSudo "${ROOT_USER}" sed -i.orig "s/__ELEMENTS_RPC_PASS__/${ELEMENTS_RPC_PASS}/" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquidtestnet"
|
||||||
osSudo "${ROOT_USER}" sed -i .orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquidtestnet"
|
osSudo "${ROOT_USER}" sed -i.orig "s!__ELECTRS_DATA_ROOT__!${ELECTRS_DATA_ROOT}!" "${ELEMENTS_ELECTRS_HOME}/electrs-start-liquidtestnet"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
@ -1394,7 +1513,7 @@ case $OS in
|
|||||||
;;
|
;;
|
||||||
|
|
||||||
Debian)
|
Debian)
|
||||||
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/nginx.conf" "${NGINX_CONFIGURATION}"
|
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/nginx/nginx.conf" "${NGINX_CONFIGURATION}"
|
||||||
#echo "[*] Restarting Nginx"
|
#echo "[*] Restarting Nginx"
|
||||||
#osSudo "${ROOT_USER}" service nginx restart
|
#osSudo "${ROOT_USER}" service nginx restart
|
||||||
;;
|
;;
|
||||||
@ -1415,22 +1534,21 @@ case $OS in
|
|||||||
fi
|
fi
|
||||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
|
||||||
osSudo "${ROOT_USER}" systemctl enable bitcoin.service
|
osSudo "${ROOT_USER}" systemctl enable bitcoin.service
|
||||||
osSudo "${ROOT_USER}" systemctl enable electrs.service
|
fi
|
||||||
osSudo "${ROOT_USER}" systemctl enable mempool.service
|
if [ "${BITCOIN_MAINNET_MINFEE_ENABLE}" = ON ];then
|
||||||
|
osSudo "${ROOT_USER}" systemctl enable bitcoin-minfee.service
|
||||||
fi
|
fi
|
||||||
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||||
osSudo "${ROOT_USER}" systemctl enable bitcoin-testnet.service
|
osSudo "${ROOT_USER}" systemctl enable bitcoin-testnet.service
|
||||||
osSudo "${ROOT_USER}" systemctl enable electrs-testnet.service
|
fi
|
||||||
osSudo "${ROOT_USER}" systemctl enable mempool-testnet.service
|
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
|
osSudo "${ROOT_USER}" systemctl enable bitcoin-signet.service
|
||||||
fi
|
fi
|
||||||
if [ "${BISQ_MAINNET_ENABLE}" = ON ];then
|
if [ "${BISQ_MAINNET_ENABLE}" = ON ];then
|
||||||
osSudo "${ROOT_USER}" systemctl enable bisq.service
|
osSudo "${ROOT_USER}" systemctl enable bisq.service
|
||||||
osSudo "${ROOT_USER}" systemctl enable mempool-bisq.service
|
|
||||||
fi
|
fi
|
||||||
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
|
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
|
||||||
osSudo "${ROOT_USER}" systemctl enable liquid.service
|
osSudo "${ROOT_USER}" systemctl enable liquid.service
|
||||||
osSudo "${ROOT_USER}" systemctl enable electrs-liquid.service
|
|
||||||
osSudo "${ROOT_USER}" systemctl enable mempool-liquid.service
|
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
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
|
22
production/linux/bitcoin-signet.service
Normal file
22
production/linux/bitcoin-signet.service
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Bitcoind-signet
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/local/bin/bitcoind -conf=bitcoin-signet.conf -daemon -signet -printtoconsole -pid=/bitcoin/bitcoind-signet.pid
|
||||||
|
ExecStop=/usr/local/bin/bitcoin-cli -signet stop
|
||||||
|
|
||||||
|
Type=forking
|
||||||
|
PIDFile=/bitcoin/bitcoind-signet.pid
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
User=bitcoin
|
||||||
|
Group=bitcoin
|
||||||
|
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=full
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateDevices=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
22
production/linux/bitcoin-testnet.service
Normal file
22
production/linux/bitcoin-testnet.service
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Bitcoind-testnet
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/local/bin/bitcoind -conf=bitcoin-testnet.conf -daemon -testnet -printtoconsole -pid=/bitcoin/bitcoind-testnet.pid
|
||||||
|
ExecStop=/usr/local/bin/bitcoin-cli -testnet stop
|
||||||
|
|
||||||
|
Type=forking
|
||||||
|
PIDFile=/bitcoin/bitcoind-testnet.pid
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
User=bitcoin
|
||||||
|
Group=bitcoin
|
||||||
|
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=full
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateDevices=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
22
production/linux/bitcoin.service
Normal file
22
production/linux/bitcoin.service
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Bitcoind
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/local/bin/bitcoind -daemon -printtoconsole -pid=/bitcoin/bitcoind.pid
|
||||||
|
ExecStop=/usr/local/bin/bitcoin-cli stop
|
||||||
|
|
||||||
|
Type=forking
|
||||||
|
PIDFile=/bitcoin/bitcoind.pid
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
User=bitcoin
|
||||||
|
Group=bitcoin
|
||||||
|
|
||||||
|
PrivateTmp=true
|
||||||
|
ProtectSystem=full
|
||||||
|
NoNewPrivileges=true
|
||||||
|
PrivateDevices=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
22
production/linux/liquid.service
Normal file
22
production/linux/liquid.service
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Liquid
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/usr/local/bin/elementsd -daemon -printtoconsole -pid=/liquid/liquid.pid
|
||||||
|
ExecStop=/usr/local/bin/elements-cli stop
|
||||||
|
|
||||||
|
Type=forking
|
||||||
|
PIDFile=/liquid/liquid.pid
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
User=liquid
|
||||||
|
Group=liquid
|
||||||
|
|
||||||
|
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
|
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:$HOME/bin
|
||||||
HOSTNAME=$(hostname)
|
HOSTNAME=$(hostname)
|
||||||
LOCATION=$(hostname|cut -d . -f2)
|
LOCATION=$(hostname|cut -d . -f2)
|
||||||
@ -16,10 +16,13 @@ if [ -f "${LOCKFILE}" ];then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
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}"
|
touch "${LOCKFILE}"
|
||||||
|
|
||||||
|
# notify logged in users
|
||||||
echo "Upgrading mempool to ${REF}" | wall
|
echo "Upgrading mempool to ${REF}" | wall
|
||||||
|
|
||||||
update_repo()
|
update_repo()
|
||||||
@ -84,25 +87,48 @@ ship_frontend()
|
|||||||
rsync -av "./dist/mempool/browser/" "${HOME}/public_html/${site}/" || exit 1
|
rsync -av "./dist/mempool/browser/" "${HOME}/public_html/${site}/" || exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# load nvm if necessary
|
||||||
export NVM_DIR="${HOME}/.nvm"
|
export NVM_DIR="${HOME}/.nvm"
|
||||||
source "${NVM_DIR}/nvm.sh"
|
source "${NVM_DIR}/nvm.sh"
|
||||||
|
|
||||||
for target in mainnet testnet signet liquid liquidtestnet bisq;do
|
# what to look for
|
||||||
update_repo "${target}"
|
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
|
done
|
||||||
|
|
||||||
for target in mainnet testnet signet liquid liquidtestnet bisq;do
|
# find which backend repos we have
|
||||||
build_backend "${target}"
|
for repo in $backends;do
|
||||||
|
[ -d "${repo}" ] && backend_repos+="${repo}"
|
||||||
|
[ -d "${repo}-lightning" ] && backend_repos+="${repo}-lightning"
|
||||||
done
|
done
|
||||||
|
|
||||||
for target in mainnet liquid bisq;do
|
# update all repos
|
||||||
build_frontend "${target}"
|
for repo in $backend_repos;do
|
||||||
|
update_repo "${repo}"
|
||||||
done
|
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
|
for target in mainnet liquid bisq;do
|
||||||
ship_frontend "${target}"
|
ship_frontend "${target}"
|
||||||
done
|
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.dev
|
||||||
echo "${HOSTNAME} updated to \`${REF}\` @ \`${HASH}\`" | /usr/local/bin/keybase chat send --nonblock --channel general "mempool.ops.${LOCATION}"
|
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"
|
export NVM_DIR="$HOME/.nvm"
|
||||||
source "$NVM_DIR/nvm.sh"
|
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/" && \
|
cd "${HOME}/${site}/backend/" && \
|
||||||
screen -dmS "${site}" sh -c 'while true;do npm run start-production;sleep 1;done'
|
screen -dmS "${site}" sh -c 'while true;do npm run start-production;sleep 1;done'
|
||||||
done
|
done
|
||||||
|
@ -67,6 +67,16 @@ do for url in / \
|
|||||||
'/api/v1/mining/blocks/fee-rates/2y' \
|
'/api/v1/mining/blocks/fee-rates/2y' \
|
||||||
'/api/v1/mining/blocks/fee-rates/3y' \
|
'/api/v1/mining/blocks/fee-rates/3y' \
|
||||||
'/api/v1/mining/blocks/fee-rates/all' \
|
'/api/v1/mining/blocks/fee-rates/all' \
|
||||||
|
'/api/v1/mining/difficulty-adjustments/24h' \
|
||||||
|
'/api/v1/mining/difficulty-adjustments/3d' \
|
||||||
|
'/api/v1/mining/difficulty-adjustments/1w' \
|
||||||
|
'/api/v1/mining/difficulty-adjustments/1m' \
|
||||||
|
'/api/v1/mining/difficulty-adjustments/3m' \
|
||||||
|
'/api/v1/mining/difficulty-adjustments/6m' \
|
||||||
|
'/api/v1/mining/difficulty-adjustments/1y' \
|
||||||
|
'/api/v1/mining/difficulty-adjustments/2y' \
|
||||||
|
'/api/v1/mining/difficulty-adjustments/3y' \
|
||||||
|
'/api/v1/mining/difficulty-adjustments/all' \
|
||||||
|
|
||||||
do
|
do
|
||||||
curl -s "https://${hostname}${url}" >/dev/null
|
curl -s "https://${hostname}${url}" >/dev/null
|
||||||
|
@ -4,8 +4,6 @@ tcp_nopush on;
|
|||||||
tcp_nodelay on;
|
tcp_nodelay on;
|
||||||
server_tokens off;
|
server_tokens off;
|
||||||
server_name_in_redirect off;
|
server_name_in_redirect off;
|
||||||
include /usr/local/etc/nginx/mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
# default logs
|
# default logs
|
||||||
access_log /var/log/nginx/access.log;
|
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;
|
# mempool #
|
||||||
}
|
###########
|
||||||
location /api/v1/mining {
|
|
||||||
try_files /dev/null @mempool-api-v1-warmcache;
|
# websocket has special HTTP headers
|
||||||
}
|
location /api/v1/ws {
|
||||||
location /api/v1/block/ {
|
try_files /dev/null @mempool-api-v1-websocket;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
location @mempool-api-v1-forevercache {
|
# warm cache mining and mempool API responses
|
||||||
proxy_pass $mempoolBackend;
|
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_http_version 1.1;
|
||||||
|
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
@ -29,8 +54,16 @@ location @mempool-api-v1-forevercache {
|
|||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
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_background_update on;
|
||||||
proxy_cache_use_stale updating;
|
proxy_cache_use_stale updating;
|
||||||
proxy_cache api;
|
proxy_cache api;
|
||||||
@ -40,18 +73,14 @@ location @mempool-api-v1-forevercache {
|
|||||||
expires 30d;
|
expires 30d;
|
||||||
}
|
}
|
||||||
|
|
||||||
location @mempool-api-v1-warmcache {
|
location @mempool-api-v1-cache-warm {
|
||||||
proxy_pass $mempoolBackend;
|
proxy_pass $mempoolMainnet;
|
||||||
proxy_http_version 1.1;
|
|
||||||
|
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
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_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
proxy_cache_bypass $http_upgrade;
|
|
||||||
proxy_cache_background_update on;
|
proxy_cache_background_update on;
|
||||||
proxy_cache_use_stale updating;
|
proxy_cache_use_stale updating;
|
||||||
proxy_cache api;
|
proxy_cache api;
|
||||||
@ -59,18 +88,14 @@ location @mempool-api-v1-warmcache {
|
|||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
}
|
}
|
||||||
|
|
||||||
location @mempool-api-v1-coldcache {
|
location @mempool-api-v1-cache-normal {
|
||||||
proxy_pass $mempoolBackend;
|
proxy_pass $mempoolMainnet;
|
||||||
proxy_http_version 1.1;
|
|
||||||
|
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
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_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
proxy_cache_bypass $http_upgrade;
|
|
||||||
proxy_cache api;
|
proxy_cache api;
|
||||||
proxy_cache_valid 200 10s;
|
proxy_cache_valid 200 10s;
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
@ -78,54 +103,42 @@ location @mempool-api-v1-coldcache {
|
|||||||
expires 10s;
|
expires 10s;
|
||||||
}
|
}
|
||||||
|
|
||||||
location @mempool-api-v1-nocache {
|
location @mempool-api-v1-cache-disabled {
|
||||||
proxy_pass $mempoolBackend;
|
proxy_pass $mempoolMainnet;
|
||||||
proxy_http_version 1.1;
|
|
||||||
|
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
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_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
proxy_cache_bypass $http_upgrade;
|
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
|
|
||||||
expires -1;
|
expires -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
location @electrs-api-nocache {
|
location @esplora-api-cache-disabled {
|
||||||
proxy_pass $electrsBackend;
|
proxy_pass $esploraMainnet;
|
||||||
proxy_http_version 1.1;
|
|
||||||
|
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
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_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
proxy_cache_bypass $http_upgrade;
|
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
|
|
||||||
expires -1;
|
expires -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
location @electrs-api-forevercache {
|
location @esplora-api-cache-forever {
|
||||||
proxy_pass $electrsBackend;
|
proxy_pass $esploraMainnet;
|
||||||
proxy_http_version 1.1;
|
|
||||||
|
|
||||||
proxy_set_header Host $http_host;
|
proxy_set_header Host $http_host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
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_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
proxy_cache_bypass $http_upgrade;
|
|
||||||
proxy_cache_background_update on;
|
proxy_cache_background_update on;
|
||||||
proxy_cache_use_stale updating;
|
proxy_cache_use_stale updating;
|
||||||
proxy_cache api;
|
proxy_cache api;
|
||||||
|
@ -1,12 +1,150 @@
|
|||||||
|
###########
|
||||||
|
# mempool #
|
||||||
|
###########
|
||||||
|
|
||||||
|
# websocket has special HTTP headers
|
||||||
location /liquid/api/v1/ws {
|
location /liquid/api/v1/ws {
|
||||||
proxy_pass http://mempool-liquid-mainnet/;
|
rewrite ^/liquid/(.*) /$1 break;
|
||||||
proxy_http_version 1.1;
|
try_files /dev/null @mempool-liquid-api-v1-websocket;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "Upgrade";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 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 {
|
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/ {
|
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 {
|
location /liquidtestnet/api/v1/ws {
|
||||||
proxy_pass http://mempool-liquid-testnet/;
|
rewrite ^/liquidtestnet/(.*) /$1 break;
|
||||||
proxy_http_version 1.1;
|
try_files /dev/null @mempool-liquidtestnet-api-v1-websocket;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "Upgrade";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 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 {
|
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/ {
|
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 {
|
location /signet/api/v1/ws {
|
||||||
proxy_pass http://mempool-bitcoin-signet/;
|
rewrite ^/signet/(.*) /$1 break;
|
||||||
proxy_http_version 1.1;
|
try_files /dev/null @mempool-signet-api-v1-websocket;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "Upgrade";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 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 {
|
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/ {
|
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;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user