Compare commits

..

1 Commits

Author SHA1 Message Date
Felipe Knorr Kuhn
897049903b Fix Liquid proxy routing 2022-07-07 07:22:51 -07:00
78 changed files with 10913 additions and 17480 deletions

View File

@@ -2,93 +2,91 @@ name: CI Pipeline for the Backend and Frontend
on: on:
pull_request: pull_request:
types: [opened, review_requested, synchronize] types: [ opened, review_requested, synchronize ]
env:
NODE_VERSION: 16.15.0
jobs: jobs:
backend: backend:
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')" if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
strategy: strategy:
matrix: matrix:
node: ["16.16.0", "18.5.0"] flavor: ['dev', 'prod']
flavor: ["dev", "prod"] runs-on: 'ubuntu-latest'
fail-fast: false
runs-on: "ubuntu-latest"
name: Backend (${{ matrix.flavor }}) - node ${{ matrix.node }} name: Backend (${{ matrix.flavor }})
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
path: ${{ matrix.node }}/${{ matrix.flavor }} path: ${{ matrix.flavor }}
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node }} 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'}} if: ${{ matrix.flavor == 'dev'}}
run: npm ci run: npm ci
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend working-directory: ${{ matrix.flavor }}/backend
- name: Install (Prod dependencies only) - name: Install (Prod dependencies only)
if: ${{ matrix.flavor == 'prod'}} if: ${{ matrix.flavor == 'prod'}}
run: npm ci --omit=dev --omit=optional run: npm ci --prod --no-optional
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend working-directory: ${{ matrix.flavor }}/backend
- name: Lint - name: Lint
if: ${{ matrix.flavor == 'dev'}} if: ${{ matrix.flavor == 'dev'}}
run: npm run lint run: npm run lint
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend 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: ${{ matrix.node }}/${{ matrix.flavor }}/backend working-directory: ${{ matrix.flavor }}/backend
frontend: frontend:
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')" if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
strategy: strategy:
matrix: matrix:
node: ["16.15.0", "18.5.0"] flavor: ['dev', 'prod']
flavor: ["dev", "prod"] runs-on: 'ubuntu-latest'
fail-fast: false
runs-on: "ubuntu-latest"
name: Frontend (${{ matrix.flavor }}) - node ${{ matrix.node }} name: Frontend (${{ matrix.flavor }})
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
path: ${{ matrix.node }}/${{ matrix.flavor }} path: ${{ matrix.flavor }}
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node }} node-version: ${{ env.NODE_VERSION }}
registry-url: "https://registry.npmjs.org" registry-url: 'https://registry.npmjs.org'
- name: Install (Prod dependencies only) - name: Install (Prod dependencies only)
run: npm ci --omit=dev --omit=optional run: npm ci --prod --no-optional
if: ${{ matrix.flavor == 'prod'}} if: ${{ matrix.flavor == 'prod'}}
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend working-directory: ${{ matrix.flavor }}/frontend
- name: Install - name: Install
if: ${{ matrix.flavor == 'dev'}} if: ${{ matrix.flavor == 'dev'}}
run: npm ci run: npm ci
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend working-directory: ${{ matrix.flavor }}/frontend
- name: Lint - name: Lint
if: ${{ matrix.flavor == 'dev'}} if: ${{ matrix.flavor == 'dev'}}
run: npm run lint run: npm run lint
working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend 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: ${{ matrix.node }}/${{ matrix.flavor }}/frontend working-directory: ${{ matrix.flavor }}/frontend

2
.nvmrc
View File

@@ -1 +1 @@
v16.16.0 v16.15.0

View File

@@ -20,8 +20,7 @@
"EXTERNAL_MAX_RETRY": 1, "EXTERNAL_MAX_RETRY": 1,
"EXTERNAL_RETRY_INTERVAL": 0, "EXTERNAL_RETRY_INTERVAL": 0,
"USER_AGENT": "mempool", "USER_AGENT": "mempool",
"STDOUT_LOG_MIN_PRIORITY": "debug", "STDOUT_LOG_MIN_PRIORITY": "debug"
"AUTOMATIC_BLOCK_REINDEXING": false
}, },
"CORE_RPC": { "CORE_RPC": {
"HOST": "127.0.0.1", "HOST": "127.0.0.1",

View File

@@ -33,6 +33,32 @@
"prettier": "^2.7.1" "prettier": "^2.7.1"
} }
}, },
"node_modules/@babel/code-frame": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
"integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
"dev": true,
"dependencies": {
"@babel/highlight": "^7.10.4"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.12.11",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
"integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==",
"dev": true
},
"node_modules/@babel/highlight": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
"integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.10.4",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
},
"node_modules/@eslint/eslintrc": { "node_modules/@eslint/eslintrc": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz",

View File

@@ -168,7 +168,7 @@ class Blocks {
blockExtended.extras.avgFeeRate = stats.avgfeerate; blockExtended.extras.avgFeeRate = stats.avgfeerate;
} }
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) { if (['mainnet', 'testnet', 'signet', 'regtest'].includes(config.MEMPOOL.NETWORK)) {
let pool: PoolTag; let pool: PoolTag;
if (blockExtended.extras?.coinbaseTx !== undefined) { if (blockExtended.extras?.coinbaseTx !== undefined) {
pool = await this.$findBlockMiner(blockExtended.extras?.coinbaseTx); pool = await this.$findBlockMiner(blockExtended.extras?.coinbaseTx);
@@ -405,7 +405,7 @@ class Blocks {
if (blockHeightTip >= 2016) { if (blockHeightTip >= 2016) {
const previousPeriodBlockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff - 2016); const previousPeriodBlockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff - 2016);
const previousPeriodBlock = await bitcoinClient.getBlock(previousPeriodBlockHash) const previousPeriodBlock = await bitcoinApi.$getBlock(previousPeriodBlockHash);
this.previousDifficultyRetarget = (block.difficulty - previousPeriodBlock.difficulty) / previousPeriodBlock.difficulty * 100; this.previousDifficultyRetarget = (block.difficulty - previousPeriodBlock.difficulty) / previousPeriodBlock.difficulty * 100;
logger.debug(`Initial difficulty adjustment data set.`); logger.debug(`Initial difficulty adjustment data set.`);
} }
@@ -527,15 +527,13 @@ class Blocks {
} }
} }
let block = await bitcoinClient.getBlock(hash); const block = await bitcoinApi.$getBlock(hash);
// Not Bitcoin network, return the block as it // Not Bitcoin network, return the block as it
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) { if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) {
return block; return block;
} }
block = prepareBlock(block);
// Bitcoin network, add our custom data on top // Bitcoin network, add our custom data on top
const transactions = await this.$getTransactionsExtended(hash, block.height, true); const transactions = await this.$getTransactionsExtended(hash, block.height, true);
const blockExtended = await this.$getBlockExtended(block, transactions); const blockExtended = await this.$getBlockExtended(block, transactions);
@@ -579,39 +577,47 @@ class Blocks {
} }
public async $getBlocks(fromHeight?: number, limit: number = 15): Promise<BlockExtended[]> { public async $getBlocks(fromHeight?: number, limit: number = 15): Promise<BlockExtended[]> {
let currentHeight = fromHeight !== undefined ? fromHeight : await blocksRepository.$mostRecentBlockHeight(); try {
const returnBlocks: BlockExtended[] = []; let currentHeight = fromHeight !== undefined ? fromHeight : this.getCurrentBlockHeight();
const returnBlocks: BlockExtended[] = [];
if (currentHeight < 0) { if (currentHeight < 0) {
return returnBlocks; return returnBlocks;
}
// Check if block height exist in local cache to skip the hash lookup
const blockByHeight = this.getBlocks().find((b) => b.height === currentHeight);
let startFromHash: string | null = null;
if (blockByHeight) {
startFromHash = blockByHeight.id;
} else if (!Common.indexingEnabled()) {
startFromHash = await bitcoinApi.$getBlockHash(currentHeight);
}
let nextHash = startFromHash;
for (let i = 0; i < limit && currentHeight >= 0; i++) {
let block = this.getBlocks().find((b) => b.height === currentHeight);
if (block) {
returnBlocks.push(block);
} else if (Common.indexingEnabled()) {
block = await this.$indexBlock(currentHeight);
returnBlocks.push(block);
} else if (nextHash != null) {
block = prepareBlock(await bitcoinClient.getBlock(nextHash));
nextHash = block.previousblockhash;
returnBlocks.push(block);
} }
currentHeight--;
}
return returnBlocks; if (currentHeight === 0 && Common.indexingEnabled()) {
currentHeight = await blocksRepository.$mostRecentBlockHeight();
}
// Check if block height exist in local cache to skip the hash lookup
const blockByHeight = this.getBlocks().find((b) => b.height === currentHeight);
let startFromHash: string | null = null;
if (blockByHeight) {
startFromHash = blockByHeight.id;
} else if (!Common.indexingEnabled()) {
startFromHash = await bitcoinApi.$getBlockHash(currentHeight);
}
let nextHash = startFromHash;
for (let i = 0; i < limit && currentHeight >= 0; i++) {
let block = this.getBlocks().find((b) => b.height === currentHeight);
if (block) {
returnBlocks.push(block);
} else if (Common.indexingEnabled()) {
block = await this.$indexBlock(currentHeight);
returnBlocks.push(block);
} else if (nextHash != null) {
block = prepareBlock(await bitcoinApi.$getBlock(nextHash));
nextHash = block.previousblockhash;
returnBlocks.push(block);
}
currentHeight--;
}
return returnBlocks;
} catch (e) {
throw e;
}
} }
public getLastDifficultyAdjustmentTime(): number { public getLastDifficultyAdjustmentTime(): number {

View File

@@ -173,25 +173,26 @@ class Mining {
*/ */
public async $generatePoolHashrateHistory(): Promise<void> { public async $generatePoolHashrateHistory(): Promise<void> {
const now = new Date(); const now = new Date();
const lastestRunDate = await HashratesRepository.$getLatestRun('last_weekly_hashrates_indexing');
// Run only if: try {
// * lastestRunDate is set to 0 (node backend restart, reorg) const lastestRunDate = await HashratesRepository.$getLatestRun('last_weekly_hashrates_indexing');
// * we started a new week (around Monday midnight)
const runIndexing = lastestRunDate === 0 || now.getUTCDay() === 1 && lastestRunDate !== now.getUTCDate(); // Run only if:
if (!runIndexing) { // * lastestRunDate is set to 0 (node backend restart, reorg)
return; // * we started a new week (around Monday midnight)
const runIndexing = lastestRunDate === 0 || now.getUTCDay() === 1 && lastestRunDate !== now.getUTCDate();
if (!runIndexing) {
return;
}
} catch (e) {
throw e;
} }
try { try {
const oldestConsecutiveBlockTimestamp = 1000 * (await BlocksRepository.$getOldestConsecutiveBlock()).timestamp;
const genesisBlock = await bitcoinClient.getBlock(await bitcoinClient.getBlockHash(0));
const genesisTimestamp = genesisBlock.time * 1000;
const indexedTimestamp = await HashratesRepository.$getWeeklyHashrateTimestamps(); const indexedTimestamp = await HashratesRepository.$getWeeklyHashrateTimestamps();
const hashrates: any[] = []; const hashrates: any[] = [];
const genesisTimestamp = 1231006505000; // bitcoin-cli getblock 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
const lastMonday = new Date(now.setDate(now.getDate() - (now.getDay() + 6) % 7)); const lastMonday = new Date(now.setDate(now.getDate() - (now.getDay() + 6) % 7));
const lastMondayMidnight = this.getDateMidnight(lastMonday); const lastMondayMidnight = this.getDateMidnight(lastMonday);
let toTimestamp = lastMondayMidnight.getTime(); let toTimestamp = lastMondayMidnight.getTime();
@@ -206,7 +207,7 @@ class Mining {
logger.debug(`Indexing weekly mining pool hashrate`); logger.debug(`Indexing weekly mining pool hashrate`);
loadingIndicators.setProgress('weekly-hashrate-indexing', 0); loadingIndicators.setProgress('weekly-hashrate-indexing', 0);
while (toTimestamp > genesisTimestamp && toTimestamp > oldestConsecutiveBlockTimestamp) { while (toTimestamp > genesisTimestamp) {
const fromTimestamp = toTimestamp - 604800000; const fromTimestamp = toTimestamp - 604800000;
// Skip already indexed weeks // Skip already indexed weeks
@@ -216,6 +217,14 @@ class Mining {
continue; continue;
} }
// Check if we have blocks for the previous week (which mean that the week
// we are currently indexing has complete data)
const blockStatsPreviousWeek: any = await BlocksRepository.$blockCountBetweenTimestamp(
null, (fromTimestamp - 604800000) / 1000, (toTimestamp - 604800000) / 1000);
if (blockStatsPreviousWeek.blockCount === 0) { // We are done indexing
break;
}
const blockStats: any = await BlocksRepository.$blockCountBetweenTimestamp( const blockStats: any = await BlocksRepository.$blockCountBetweenTimestamp(
null, fromTimestamp / 1000, toTimestamp / 1000); null, fromTimestamp / 1000, toTimestamp / 1000);
const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(blockStats.blockCount, const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(blockStats.blockCount,
@@ -223,28 +232,26 @@ class Mining {
let pools = await PoolsRepository.$getPoolsInfoBetween(fromTimestamp / 1000, toTimestamp / 1000); let pools = await PoolsRepository.$getPoolsInfoBetween(fromTimestamp / 1000, toTimestamp / 1000);
const totalBlocks = pools.reduce((acc, pool) => acc + pool.blockCount, 0); const totalBlocks = pools.reduce((acc, pool) => acc + pool.blockCount, 0);
if (totalBlocks > 0) { pools = pools.map((pool: any) => {
pools = pools.map((pool: any) => { pool.hashrate = (pool.blockCount / totalBlocks) * lastBlockHashrate;
pool.hashrate = (pool.blockCount / totalBlocks) * lastBlockHashrate; pool.share = (pool.blockCount / totalBlocks);
pool.share = (pool.blockCount / totalBlocks); return pool;
return pool; });
for (const pool of pools) {
hashrates.push({
hashrateTimestamp: toTimestamp / 1000,
avgHashrate: pool['hashrate'],
poolId: pool.poolId,
share: pool['share'],
type: 'weekly',
}); });
for (const pool of pools) {
hashrates.push({
hashrateTimestamp: toTimestamp / 1000,
avgHashrate: pool['hashrate'] ,
poolId: pool.poolId,
share: pool['share'],
type: 'weekly',
});
}
newlyIndexed += hashrates.length;
await HashratesRepository.$saveHashrates(hashrates);
hashrates.length = 0;
} }
newlyIndexed += hashrates.length;
await HashratesRepository.$saveHashrates(hashrates);
hashrates.length = 0;
const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - timer)); const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - timer));
if (elapsedSeconds > 1) { if (elapsedSeconds > 1) {
const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt)); const runningFor = Math.max(1, Math.round((new Date().getTime() / 1000) - startedAt));
@@ -278,19 +285,20 @@ class Mining {
* [INDEXING] Generate daily hashrate data * [INDEXING] Generate daily hashrate data
*/ */
public async $generateNetworkHashrateHistory(): Promise<void> { public async $generateNetworkHashrateHistory(): Promise<void> {
// We only run this once a day around midnight try {
const latestRunDate = await HashratesRepository.$getLatestRun('last_hashrates_indexing'); // We only run this once a day around midnight
const now = new Date().getUTCDate(); const latestRunDate = await HashratesRepository.$getLatestRun('last_hashrates_indexing');
if (now === latestRunDate) { const now = new Date().getUTCDate();
return; if (now === latestRunDate) {
return;
}
} catch (e) {
throw e;
} }
const oldestConsecutiveBlockTimestamp = 1000 * (await BlocksRepository.$getOldestConsecutiveBlock()).timestamp;
try { try {
const genesisBlock = await bitcoinClient.getBlock(await bitcoinClient.getBlockHash(0)); const indexedTimestamp = (await HashratesRepository.$getNetworkDailyHashrate(null)).map(hashrate => hashrate.timestamp);
const genesisTimestamp = genesisBlock.time * 1000; const genesisTimestamp = 1231006505000; // bitcoin-cli getblock 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
const indexedTimestamp = (await HashratesRepository.$getRawNetworkDailyHashrate(null)).map(hashrate => hashrate.timestamp);
const lastMidnight = this.getDateMidnight(new Date()); const lastMidnight = this.getDateMidnight(new Date());
let toTimestamp = Math.round(lastMidnight.getTime()); let toTimestamp = Math.round(lastMidnight.getTime());
const hashrates: any[] = []; const hashrates: any[] = [];
@@ -305,7 +313,7 @@ class Mining {
logger.debug(`Indexing daily network hashrate`); logger.debug(`Indexing daily network hashrate`);
loadingIndicators.setProgress('daily-hashrate-indexing', 0); loadingIndicators.setProgress('daily-hashrate-indexing', 0);
while (toTimestamp > genesisTimestamp && toTimestamp > oldestConsecutiveBlockTimestamp) { while (toTimestamp > genesisTimestamp) {
const fromTimestamp = toTimestamp - 86400000; const fromTimestamp = toTimestamp - 86400000;
// Skip already indexed days // Skip already indexed days
@@ -315,9 +323,17 @@ class Mining {
continue; continue;
} }
// Check if we have blocks for the previous day (which mean that the day
// we are currently indexing has complete data)
const blockStatsPreviousDay: any = await BlocksRepository.$blockCountBetweenTimestamp(
null, (fromTimestamp - 86400000) / 1000, (toTimestamp - 86400000) / 1000);
if (blockStatsPreviousDay.blockCount === 0 && config.MEMPOOL.NETWORK === 'mainnet') { // We are done indexing
break;
}
const blockStats: any = await BlocksRepository.$blockCountBetweenTimestamp( const blockStats: any = await BlocksRepository.$blockCountBetweenTimestamp(
null, fromTimestamp / 1000, toTimestamp / 1000); null, fromTimestamp / 1000, toTimestamp / 1000);
const lastBlockHashrate = blockStats.blockCount === 0 ? 0 : await bitcoinClient.getNetworkHashPs(blockStats.blockCount, const lastBlockHashrate = await bitcoinClient.getNetworkHashPs(blockStats.blockCount,
blockStats.lastBlockHeight); blockStats.lastBlockHeight);
hashrates.push({ hashrates.push({
@@ -353,7 +369,7 @@ class Mining {
} }
// Add genesis block manually // Add genesis block manually
if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT === -1 && !indexedTimestamp.includes(genesisTimestamp / 1000)) { if (toTimestamp <= genesisTimestamp && !indexedTimestamp.includes(genesisTimestamp)) {
hashrates.push({ hashrates.push({
hashrateTimestamp: genesisTimestamp / 1000, hashrateTimestamp: genesisTimestamp / 1000,
avgHashrate: await bitcoinClient.getNetworkHashPs(1, 1), avgHashrate: await bitcoinClient.getNetworkHashPs(1, 1),
@@ -389,37 +405,27 @@ class Mining {
} }
const blocks: any = await BlocksRepository.$getBlocksDifficulty(); const blocks: any = await BlocksRepository.$getBlocksDifficulty();
const genesisBlock = await bitcoinClient.getBlock(await bitcoinClient.getBlockHash(0));
let currentDifficulty = genesisBlock.difficulty; let currentDifficulty = 0;
let totalIndexed = 0; let totalIndexed = 0;
if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT === -1 && indexedHeights[0] !== true) { if (indexedHeights[0] === false) {
await DifficultyAdjustmentsRepository.$saveAdjustments({ await DifficultyAdjustmentsRepository.$saveAdjustments({
time: genesisBlock.time, time: 1231006505,
height: 0, height: 0,
difficulty: currentDifficulty, difficulty: 1.0,
adjustment: 0.0, adjustment: 0.0,
}); });
} }
const oldestConsecutiveBlock = await BlocksRepository.$getOldestConsecutiveBlock();
if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT !== -1) {
currentDifficulty = oldestConsecutiveBlock.difficulty;
}
let totalBlockChecked = 0;
let timer = new Date().getTime() / 1000;
for (const block of blocks) { for (const block of blocks) {
if (block.difficulty !== currentDifficulty) { if (block.difficulty !== currentDifficulty) {
if (indexedHeights[block.height] === true) { // Already indexed if (block.height === 0 || indexedHeights[block.height] === true) { // Already indexed
if (block.height >= oldestConsecutiveBlock.height) { currentDifficulty = block.difficulty;
currentDifficulty = block.difficulty;
}
continue; continue;
} }
let adjustment = block.difficulty / currentDifficulty; let adjustment = block.difficulty / Math.max(1, currentDifficulty);
adjustment = Math.round(adjustment * 1000000) / 1000000; // Remove float point noise adjustment = Math.round(adjustment * 1000000) / 1000000; // Remove float point noise
await DifficultyAdjustmentsRepository.$saveAdjustments({ await DifficultyAdjustmentsRepository.$saveAdjustments({
@@ -430,17 +436,7 @@ class Mining {
}); });
totalIndexed++; totalIndexed++;
if (block.height >= oldestConsecutiveBlock.height) { currentDifficulty = block.difficulty;
currentDifficulty = block.difficulty;
}
}
totalBlockChecked++;
const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - timer));
if (elapsedSeconds > 5) {
const progress = Math.round(totalBlockChecked / blocks.length * 100);
logger.info(`Indexing difficulty adjustment at block #${block.height} | Progress: ${progress}%`);
timer = new Date().getTime() / 1000;
} }
} }

View File

@@ -1,7 +1,6 @@
import DB from '../database'; import DB from '../database';
import logger from '../logger'; import logger from '../logger';
import config from '../config'; import config from '../config';
import BlocksRepository from '../repositories/BlocksRepository';
interface Pool { interface Pool {
name: string; name: string;
@@ -33,6 +32,7 @@ class PoolsParser {
// First we save every entries without paying attention to pool duplication // First we save every entries without paying attention to pool duplication
const poolsDuplicated: Pool[] = []; const poolsDuplicated: Pool[] = [];
logger.debug('Parse coinbase_tags');
const coinbaseTags = Object.entries(poolsJson['coinbase_tags']); const coinbaseTags = Object.entries(poolsJson['coinbase_tags']);
for (let i = 0; i < coinbaseTags.length; ++i) { for (let i = 0; i < coinbaseTags.length; ++i) {
poolsDuplicated.push({ poolsDuplicated.push({
@@ -43,6 +43,7 @@ class PoolsParser {
'slug': '' 'slug': ''
}); });
} }
logger.debug('Parse payout_addresses');
const addressesTags = Object.entries(poolsJson['payout_addresses']); const addressesTags = Object.entries(poolsJson['payout_addresses']);
for (let i = 0; i < addressesTags.length; ++i) { for (let i = 0; i < addressesTags.length; ++i) {
poolsDuplicated.push({ poolsDuplicated.push({
@@ -55,6 +56,7 @@ class PoolsParser {
} }
// Then, we find unique mining pool names // Then, we find unique mining pool names
logger.debug('Identify unique mining pools');
const poolNames: string[] = []; const poolNames: string[] = [];
for (let i = 0; i < poolsDuplicated.length; ++i) { for (let i = 0; i < poolsDuplicated.length; ++i) {
if (poolNames.indexOf(poolsDuplicated[i].name) === -1) { if (poolNames.indexOf(poolsDuplicated[i].name) === -1) {
@@ -117,15 +119,8 @@ class PoolsParser {
'slug': slug 'slug': slug
}; };
const existingPool = existingPools.find((pool) => pool.name === poolNames[i]); if (existingPools.find((pool) => pool.name === poolNames[i]) !== undefined) {
if (existingPool !== undefined) { finalPoolDataUpdate.push(poolObj);
// Check if any data was actually updated
const equals = (a, b) =>
a.length === b.length &&
a.every((v, i) => v === b[i]);
if (!equals(JSON.parse(existingPool.addresses), poolObj.addresses) || !equals(JSON.parse(existingPool.regexes), poolObj.regexes)) {
finalPoolDataUpdate.push(poolObj);
}
} else { } else {
logger.debug(`Add '${finalPoolName}' mining pool`); logger.debug(`Add '${finalPoolName}' mining pool`);
finalPoolDataAdd.push(poolObj); finalPoolDataAdd.push(poolObj);
@@ -145,51 +140,40 @@ class PoolsParser {
return; return;
} }
if (finalPoolDataAdd.length > 0 || finalPoolDataUpdate.length > 0) { logger.debug(`Update pools table now`);
logger.debug(`Update pools table now`);
// Add new mining pools into the database // Add new mining pools into the database
let queryAdd: string = 'INSERT INTO pools(name, link, regexes, addresses, slug) VALUES '; let queryAdd: string = 'INSERT INTO pools(name, link, regexes, addresses, slug) VALUES ';
for (let i = 0; i < finalPoolDataAdd.length; ++i) { for (let i = 0; i < finalPoolDataAdd.length; ++i) {
queryAdd += `('${finalPoolDataAdd[i].name}', '${finalPoolDataAdd[i].link}', queryAdd += `('${finalPoolDataAdd[i].name}', '${finalPoolDataAdd[i].link}',
'${JSON.stringify(finalPoolDataAdd[i].regexes)}', '${JSON.stringify(finalPoolDataAdd[i].addresses)}', '${JSON.stringify(finalPoolDataAdd[i].regexes)}', '${JSON.stringify(finalPoolDataAdd[i].addresses)}',
${JSON.stringify(finalPoolDataAdd[i].slug)}),`; ${JSON.stringify(finalPoolDataAdd[i].slug)}),`;
} }
queryAdd = queryAdd.slice(0, -1) + ';'; queryAdd = queryAdd.slice(0, -1) + ';';
// Updated existing mining pools in the database // Updated existing mining pools in the database
const updateQueries: string[] = []; const updateQueries: string[] = [];
for (let i = 0; i < finalPoolDataUpdate.length; ++i) { for (let i = 0; i < finalPoolDataUpdate.length; ++i) {
updateQueries.push(` updateQueries.push(`
UPDATE pools UPDATE pools
SET name='${finalPoolDataUpdate[i].name}', link='${finalPoolDataUpdate[i].link}', SET name='${finalPoolDataUpdate[i].name}', link='${finalPoolDataUpdate[i].link}',
regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}', regexes='${JSON.stringify(finalPoolDataUpdate[i].regexes)}', addresses='${JSON.stringify(finalPoolDataUpdate[i].addresses)}',
slug='${finalPoolDataUpdate[i].slug}' slug='${finalPoolDataUpdate[i].slug}'
WHERE name='${finalPoolDataUpdate[i].name}' WHERE name='${finalPoolDataUpdate[i].name}'
;`); ;`);
}
try {
await this.$deleteBlocskToReindex(finalPoolDataUpdate);
if (finalPoolDataAdd.length > 0) {
await DB.query({ sql: queryAdd, timeout: 120000 });
}
for (const query of updateQueries) {
await DB.query({ sql: query, timeout: 120000 });
}
await this.insertUnknownPool();
logger.info('Mining pools.json import completed');
} catch (e) {
logger.err(`Cannot import pools in the database`);
throw e;
}
} }
try { try {
if (finalPoolDataAdd.length > 0) {
await DB.query({ sql: queryAdd, timeout: 120000 });
}
for (const query of updateQueries) {
await DB.query({ sql: query, timeout: 120000 });
}
await this.insertUnknownPool(); await this.insertUnknownPool();
logger.info('Mining pools.json import completed');
} catch (e) { } catch (e) {
logger.err(`Cannot insert unknown pool in the database`); logger.err(`Cannot import pools in the database`);
throw e; throw e;
} }
} }
@@ -217,36 +201,6 @@ class PoolsParser {
logger.err('Unable to insert "Unknown" mining pool'); logger.err('Unable to insert "Unknown" mining pool');
} }
} }
/**
* Delete blocks which needs to be reindexed
*/
private async $deleteBlocskToReindex(finalPoolDataUpdate: any[]) {
if (config.MEMPOOL.AUTOMATIC_BLOCK_REINDEXING === false) {
return;
}
const blockCount = await BlocksRepository.$blockCount(null, null);
if (blockCount === 0) {
return;
}
for (const updatedPool of finalPoolDataUpdate) {
const [pool]: any[] = await DB.query(`SELECT id, name from pools where slug = "${updatedPool.slug}"`);
if (pool.length > 0) {
logger.notice(`Deleting blocks from ${pool[0].name} mining pool for future re-indexing`);
await DB.query(`DELETE FROM blocks WHERE pool_id = ${pool[0].id}`);
}
}
// Ignore early days of Bitcoin as there were not mining pool yet
logger.notice('Deleting blocks with unknown mining pool from height 130635 for future re-indexing');
const [unknownPool] = await DB.query(`SELECT id from pools where slug = "unknown"`);
await DB.query(`DELETE FROM blocks WHERE pool_id = ${unknownPool[0].id} AND height > 130635`);
logger.notice('Truncating hashrates for future re-indexing');
await DB.query(`DELETE FROM hashrates`);
}
} }
export default new PoolsParser(); export default new PoolsParser();

View File

@@ -23,7 +23,6 @@ interface IConfig {
EXTERNAL_RETRY_INTERVAL: number; EXTERNAL_RETRY_INTERVAL: number;
USER_AGENT: string; USER_AGENT: string;
STDOUT_LOG_MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug'; STDOUT_LOG_MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug';
AUTOMATIC_BLOCK_REINDEXING: boolean;
}; };
ESPLORA: { ESPLORA: {
REST_API_URL: string; REST_API_URL: string;
@@ -114,7 +113,6 @@ const defaults: IConfig = {
'EXTERNAL_RETRY_INTERVAL': 0, 'EXTERNAL_RETRY_INTERVAL': 0,
'USER_AGENT': 'mempool', 'USER_AGENT': 'mempool',
'STDOUT_LOG_MIN_PRIORITY': 'debug', 'STDOUT_LOG_MIN_PRIORITY': 'debug',
'AUTOMATIC_BLOCK_REINDEXING': false,
}, },
'ESPLORA': { 'ESPLORA': {
'REST_API_URL': 'http://127.0.0.1:3000', 'REST_API_URL': 'http://127.0.0.1:3000',

View File

@@ -35,8 +35,6 @@ class Indexer {
this.runIndexer = false; this.runIndexer = false;
this.indexerRunning = true; this.indexerRunning = true;
logger.debug(`Running mining indexer`);
try { try {
const chainValid = await blocks.$generateBlockDatabase(); const chainValid = await blocks.$generateBlockDatabase();
if (chainValid === false) { if (chainValid === false) {
@@ -56,15 +54,9 @@ class Indexer {
this.indexerRunning = false; this.indexerRunning = false;
logger.err(`Indexer failed, trying again in 10 seconds. 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); setTimeout(() => this.reindex(), 10000);
this.indexerRunning = false;
return;
} }
this.indexerRunning = false; this.indexerRunning = false;
const runEvery = 1000 * 3600; // 1 hour
logger.debug(`Indexing completed. Next run planned at ${new Date(new Date().getTime() + runEvery).toUTCString()}`);
setTimeout(() => this.reindex(), runEvery);
} }
async $resetHashratesIndexingState() { async $resetHashratesIndexingState() {

View File

@@ -610,24 +610,6 @@ class BlocksRepository {
throw e; throw e;
} }
} }
/**
* Return the oldest block from a consecutive chain of block from the most recent one
*/
public async $getOldestConsecutiveBlock(): Promise<any> {
try {
const [rows]: any = await DB.query(`SELECT height, UNIX_TIMESTAMP(blockTimestamp) as timestamp, difficulty FROM blocks ORDER BY height DESC`);
for (let i = 0; i < rows.length - 1; ++i) {
if (rows[i].height - rows[i + 1].height > 1) {
return rows[i];
}
}
return rows[rows.length - 1];
} catch (e) {
logger.err('Cannot generate block size and weight history. Reason: ' + (e instanceof Error ? e.message : e));
throw e;
}
}
} }
export default new BlocksRepository(); export default new BlocksRepository();

View File

@@ -46,38 +46,9 @@ class DifficultyAdjustmentsRepository {
query += ` GROUP BY UNIX_TIMESTAMP(time) DIV ${86400}`; query += ` GROUP BY UNIX_TIMESTAMP(time) DIV ${86400}`;
if (descOrder === true) { if (descOrder === true) {
query += ` ORDER BY height DESC`; query += ` ORDER BY time DESC`;
} else { } else {
query += ` ORDER BY height`; 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 $getRawAdjustments(interval: string | null, descOrder: boolean = false): Promise<IndexedDifficultyAdjustment[]> {
interval = Common.getSqlInterval(interval);
let query = `SELECT
UNIX_TIMESTAMP(time) as time,
height as height,
difficulty as difficulty,
adjustment as adjustment
FROM difficulty_adjustments`;
if (interval) {
query += ` WHERE time BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`;
}
if (descOrder === true) {
query += ` ORDER BY height DESC`;
} else {
query += ` ORDER BY height`;
} }
try { try {

View File

@@ -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';
@@ -29,32 +30,6 @@ class HashratesRepository {
} }
} }
public async $getRawNetworkDailyHashrate(interval: string | null): Promise<any[]> {
interval = Common.getSqlInterval(interval);
let query = `SELECT
UNIX_TIMESTAMP(hashrate_timestamp) as timestamp,
avg_hashrate as avgHashrate
FROM hashrates`;
if (interval) {
query += ` WHERE hashrate_timestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()
AND hashrates.type = 'daily'`;
} else {
query += ` WHERE hashrates.type = 'daily'`;
}
query += ` ORDER by hashrate_timestamp`;
try {
const [rows]: any[] = await DB.query(query);
return rows;
} catch (e) {
logger.err('Cannot fetch network hashrate history. Reason: ' + (e instanceof Error ? e.message : e));
throw e;
}
}
public async $getNetworkDailyHashrate(interval: string | null): Promise<any[]> { public async $getNetworkDailyHashrate(interval: string | null): Promise<any[]> {
interval = Common.getSqlInterval(interval); interval = Common.getSqlInterval(interval);

View File

@@ -4,12 +4,6 @@ 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> {
if (prices.USD === -1) {
// Some historical price entries have not USD prices, so we just ignore them to avoid future UX issues
// As of today there are only 4 (on 2013-09-05, 2013-09-19, 2013-09-12 and 2013-09-26) so that's fine
return;
}
try { try {
await DB.query(` await DB.query(`
INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY) INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY)
@@ -23,17 +17,17 @@ class PricesRepository {
} }
public async $getOldestPriceTime(): Promise<number> { public async $getOldestPriceTime(): Promise<number> {
const [oldestRow] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices WHERE USD != -1 ORDER BY time LIMIT 1`); const [oldestRow] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices ORDER BY time LIMIT 1`);
return oldestRow[0] ? oldestRow[0].time : 0; return oldestRow[0] ? oldestRow[0].time : 0;
} }
public async $getLatestPriceTime(): Promise<number> { public async $getLatestPriceTime(): Promise<number> {
const [oldestRow] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices WHERE USD != -1 ORDER BY time DESC LIMIT 1`); const [oldestRow] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices ORDER BY time DESC LIMIT 1`);
return oldestRow[0] ? oldestRow[0].time : 0; return oldestRow[0] ? oldestRow[0].time : 0;
} }
public async $getPricesTimes(): Promise<number[]> { public async $getPricesTimes(): Promise<number[]> {
const [times]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices WHERE USD != -1`); const [times]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(time) as time from prices`);
return times.map(time => time.time); return times.map(time => time.time);
} }
} }

View File

@@ -734,7 +734,7 @@ class Routes {
public async $getDifficultyAdjustments(req: Request, res: Response) { public async $getDifficultyAdjustments(req: Request, res: Response) {
try { try {
const difficulty = await DifficultyAdjustmentsRepository.$getRawAdjustments(req.params.interval, true); const difficulty = await DifficultyAdjustmentsRepository.$getAdjustments(req.params.interval, true);
res.header('Pragma', 'public'); res.header('Pragma', 'public');
res.header('Cache-control', 'public'); res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString()); res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
@@ -790,7 +790,7 @@ class Routes {
public async getBlocks(req: Request, res: Response) { public async getBlocks(req: Request, res: Response) {
try { try {
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin if (['mainnet', 'testnet', 'signet', 'regtest'].includes(config.MEMPOOL.NETWORK)) { // Bitcoin
const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10); const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10);
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
res.json(await blocks.$getBlocks(height, 15)); res.json(await blocks.$getBlocks(height, 15));

View File

@@ -3,14 +3,14 @@ import { BlockExtended } from '../mempool.interfaces';
export function prepareBlock(block: any): BlockExtended { export function prepareBlock(block: any): BlockExtended {
return <BlockExtended>{ return <BlockExtended>{
id: block.id ?? block.hash, // hash for indexed block id: block.id ?? block.hash, // hash for indexed block
timestamp: block.timestamp ?? block.time ?? block.blockTimestamp, // blockTimestamp for indexed block timestamp: block.timestamp ?? block.blockTimestamp, // blockTimestamp for indexed block
height: block.height, height: block.height,
version: block.version, version: block.version,
bits: (typeof block.bits === 'string' ? parseInt(block.bits, 16): block.bits), bits: block.bits,
nonce: block.nonce, nonce: block.nonce,
difficulty: block.difficulty, difficulty: block.difficulty,
merkle_root: block.merkle_root ?? block.merkleroot, merkle_root: block.merkle_root,
tx_count: block.tx_count ?? block.nTx, tx_count: block.tx_count,
size: block.size, size: block.size,
weight: block.weight, weight: block.weight,
previousblockhash: block.previousblockhash, previousblockhash: block.previousblockhash,

View File

@@ -1,4 +1,4 @@
FROM node:16.16.0-buster-slim AS builder FROM node:16.15.0-buster-slim AS builder
ARG commitHash ARG commitHash
ENV DOCKER_COMMIT_HASH=${commitHash} ENV DOCKER_COMMIT_HASH=${commitHash}
@@ -8,10 +8,10 @@ COPY . .
RUN apt-get update RUN apt-get update
RUN apt-get install -y build-essential python3 pkg-config RUN apt-get install -y build-essential python3 pkg-config
RUN npm install --omit=dev --omit=optional RUN npm install
RUN npm run build RUN npm run build
FROM node:16.16.0-buster-slim FROM node:16.15.0-buster-slim
WORKDIR /backend WORKDIR /backend

View File

@@ -20,8 +20,7 @@
"USER_AGENT": "__MEMPOOL_USER_AGENT__", "USER_AGENT": "__MEMPOOL_USER_AGENT__",
"STDOUT_LOG_MIN_PRIORITY": "__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__", "STDOUT_LOG_MIN_PRIORITY": "__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__",
"INDEXING_BLOCKS_AMOUNT": __MEMPOOL_INDEXING_BLOCKS_AMOUNT__, "INDEXING_BLOCKS_AMOUNT": __MEMPOOL_INDEXING_BLOCKS_AMOUNT__,
"BLOCKS_SUMMARIES_INDEXING": __MEMPOOL_BLOCKS_SUMMARIES_INDEXING__, "BLOCKS_SUMMARIES_INDEXING": __MEMPOOL_BLOCKS_SUMMARIES_INDEXING__
"AUTOMATIC_BLOCK_REINDEXING": __MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__
}, },
"CORE_RPC": { "CORE_RPC": {
"HOST": "__CORE_RPC_HOST__", "HOST": "__CORE_RPC_HOST__",

View File

@@ -22,8 +22,6 @@ __MEMPOOL_EXTERNAL_MAX_RETRY__=${MEMPOOL_EXTERNAL_MAX_RETRY:=1}
__MEMPOOL_EXTERNAL_RETRY_INTERVAL__=${MEMPOOL_EXTERNAL_RETRY_INTERVAL:=0} __MEMPOOL_EXTERNAL_RETRY_INTERVAL__=${MEMPOOL_EXTERNAL_RETRY_INTERVAL:=0}
__MEMPOOL_USER_AGENT__=${MEMPOOL_USER_AGENT:=mempool} __MEMPOOL_USER_AGENT__=${MEMPOOL_USER_AGENT:=mempool}
__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__=${MEMPOOL_STDOUT_LOG_MIN_PRIORITY:=info} __MEMPOOL_STDOUT_LOG_MIN_PRIORITY__=${MEMPOOL_STDOUT_LOG_MIN_PRIORITY:=info}
__MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=false}
__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__=${MEMPOOL_AUTOMATIC_BLOCK_REINDEXING:=false}
# CORE_RPC # CORE_RPC
__CORE_RPC_HOST__=${CORE_RPC_HOST:=127.0.0.1} __CORE_RPC_HOST__=${CORE_RPC_HOST:=127.0.0.1}
@@ -112,8 +110,6 @@ sed -i "s!__MEMPOOL_EXTERNAL_MAX_RETRY__!${__MEMPOOL_EXTERNAL_MAX_RETRY__}!g" me
sed -i "s!__MEMPOOL_EXTERNAL_RETRY_INTERVAL__!${__MEMPOOL_EXTERNAL_RETRY_INTERVAL__}!g" mempool-config.json sed -i "s!__MEMPOOL_EXTERNAL_RETRY_INTERVAL__!${__MEMPOOL_EXTERNAL_RETRY_INTERVAL__}!g" mempool-config.json
sed -i "s!__MEMPOOL_USER_AGENT__!${__MEMPOOL_USER_AGENT__}!g" mempool-config.json sed -i "s!__MEMPOOL_USER_AGENT__!${__MEMPOOL_USER_AGENT__}!g" mempool-config.json
sed -i "s/__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__/${__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__}/g" mempool-config.json sed -i "s/__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__/${__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__}/g" mempool-config.json
sed -i "s/__MEMPOOL_INDEXING_BLOCKS_AMOUNT__/${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}/g" mempool-config.json
sed -i "s/__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__/${__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__}/g" mempool-config.json
sed -i "s/__CORE_RPC_HOST__/${__CORE_RPC_HOST__}/g" mempool-config.json sed -i "s/__CORE_RPC_HOST__/${__CORE_RPC_HOST__}/g" mempool-config.json
sed -i "s/__CORE_RPC_PORT__/${__CORE_RPC_PORT__}/g" mempool-config.json sed -i "s/__CORE_RPC_PORT__/${__CORE_RPC_PORT__}/g" mempool-config.json

View File

@@ -1,4 +1,4 @@
FROM node:16.16.0-buster-slim AS builder FROM node:16.15.0-buster-slim AS builder
ARG commitHash ARG commitHash
ENV DOCKER_COMMIT_HASH=${commitHash} ENV DOCKER_COMMIT_HASH=${commitHash}
@@ -8,7 +8,7 @@ WORKDIR /build
COPY . . COPY . .
RUN apt-get update RUN apt-get update
RUN apt-get install -y build-essential rsync RUN apt-get install -y build-essential rsync
RUN npm install --omit=dev --omit=optional RUN npm i
RUN npm run build RUN npm run build
FROM nginx:1.17.8-alpine FROM nginx:1.17.8-alpine

View File

@@ -36,6 +36,7 @@
"echarts": "~5.3.2", "echarts": "~5.3.2",
"express": "^4.17.1", "express": "^4.17.1",
"lightweight-charts": "~3.8.0", "lightweight-charts": "~3.8.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",
"qrcode": "1.5.0", "qrcode": "1.5.0",
@@ -11041,12 +11042,6 @@
"@sideway/pinpoint": "^2.0.0" "@sideway/pinpoint": "^2.0.0"
} }
}, },
"node_modules/jquery": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==",
"peer": true
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -12793,6 +12788,19 @@
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
}, },
"node_modules/ngx-bootrap-multiselect": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ngx-bootrap-multiselect/-/ngx-bootrap-multiselect-2.0.0.tgz",
"integrity": "sha512-GV/2MigCS5oi6P+zWtFSmq1TLWW1kcKsJNAXLP3hHXxmY3HgMKeUPk57o3T+YHje73JRp5reXMhEIlYuoOmoRg==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": "^10.0.6",
"@angular/core": "^10.0.6",
"@angular/forms": "^10.0.6"
}
},
"node_modules/ngx-echarts": { "node_modules/ngx-echarts": {
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz", "resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz",
@@ -13797,17 +13805,6 @@
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
"node_modules/popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/portfinder": { "node_modules/portfinder": {
"version": "1.0.28", "version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
@@ -26086,12 +26083,6 @@
"@sideway/pinpoint": "^2.0.0" "@sideway/pinpoint": "^2.0.0"
} }
}, },
"jquery": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==",
"peer": true
},
"js-tokens": { "js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -27427,6 +27418,14 @@
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
}, },
"ngx-bootrap-multiselect": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ngx-bootrap-multiselect/-/ngx-bootrap-multiselect-2.0.0.tgz",
"integrity": "sha512-GV/2MigCS5oi6P+zWtFSmq1TLWW1kcKsJNAXLP3hHXxmY3HgMKeUPk57o3T+YHje73JRp5reXMhEIlYuoOmoRg==",
"requires": {
"tslib": "^2.0.0"
}
},
"ngx-echarts": { "ngx-echarts": {
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz", "resolved": "https://registry.npmjs.org/ngx-echarts/-/ngx-echarts-8.0.1.tgz",
@@ -28188,12 +28187,6 @@
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==" "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="
}, },
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"peer": true
},
"portfinder": { "portfinder": {
"version": "1.0.28", "version": "1.0.28",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",

View File

@@ -90,6 +90,7 @@
"echarts": "~5.3.2", "echarts": "~5.3.2",
"express": "^4.17.1", "express": "^4.17.1",
"lightweight-charts": "~3.8.0", "lightweight-charts": "~3.8.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",
"qrcode": "1.5.0", "qrcode": "1.5.0",

View File

@@ -25,7 +25,7 @@ PROXY_CONFIG = [
'!/bisq', '!/bisq/**', '!/bisq/', '!/bisq', '!/bisq/**', '!/bisq/',
'!/liquid', '!/liquid/**', '!/liquid/', '!/liquid', '!/liquid/**', '!/liquid/',
'!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/', '!/liquidtestnet', '!/liquidtestnet/**', '!/liquidtestnet/',
'/testnet/api/**', '/signet/api/**' '/testnet/api/**', '/signet/api/**', '!/api/v1/asset/**', '!/api/asset/**'
], ],
target: "https://mempool.space", target: "https://mempool.space",
ws: true, ws: true,
@@ -59,6 +59,12 @@ PROXY_CONFIG = [
secure: false, secure: false,
changeOrigin: true changeOrigin: true
}, },
{
context: ['/api/v1/asset/**/icon', '/api/asset/**'],
target: "https://liquid.network",
secure: false,
changeOrigin: true
},
{ {
context: ['/api/liquidtestnet**', '/liquidtestnet/api/**'], context: ['/api/liquidtestnet**', '/liquidtestnet/api/**'],
target: "https://liquid.network", target: "https://liquid.network",

View File

@@ -3,7 +3,7 @@
<div class="d-block float-right" id="filter"> <div class="d-block float-right" id="filter">
<form [formGroup]="radioGroupForm"> <form [formGroup]="radioGroupForm">
<ngx-bootstrap-multiselect [options]="txTypeOptions" [settings]="txTypeDropdownSettings" [texts]="txTypeDropdownTexts" formControlName="txTypes"></ngx-bootstrap-multiselect> <ngx-bootrap-multiselect [options]="txTypeOptions" [settings]="txTypeDropdownSettings" [texts]="txTypeDropdownTexts" formControlName="txTypes"></ngx-bootrap-multiselect>
</form> </form>
</div> </div>

View File

@@ -7,7 +7,7 @@ import { BisqApiService } from '../bisq-api.service';
import { SeoService } from 'src/app/services/seo.service'; import { SeoService } from 'src/app/services/seo.service';
import { FormGroup, FormBuilder } from '@angular/forms'; import { FormGroup, FormBuilder } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router'; import { Router, ActivatedRoute } from '@angular/router';
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'src/app/components/ngx-bootstrap-multiselect/types' import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'ngx-bootrap-multiselect';
import { WebsocketService } from 'src/app/services/websocket.service'; import { WebsocketService } from 'src/app/services/websocket.service';
@Component({ @Component({

View File

@@ -1,6 +1,7 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BisqRoutingModule } from './bisq.routing.module'; import { BisqRoutingModule } from './bisq.routing.module';
import { SharedModule } from '../shared/shared.module'; import { SharedModule } from '../shared/shared.module';
import { NgxBootstrapMultiselectModule } from 'ngx-bootrap-multiselect';
import { LightweightChartsComponent } from './lightweight-charts/lightweight-charts.component'; import { LightweightChartsComponent } from './lightweight-charts/lightweight-charts.component';
import { LightweightChartsAreaComponent } from './lightweight-charts-area/lightweight-charts-area.component'; import { LightweightChartsAreaComponent } from './lightweight-charts-area/lightweight-charts-area.component';
@@ -23,10 +24,6 @@ import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
import { BsqAmountComponent } from './bsq-amount/bsq-amount.component'; import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
import { BisqTradesComponent } from './bisq-trades/bisq-trades.component'; import { BisqTradesComponent } from './bisq-trades/bisq-trades.component';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { AutofocusDirective } from '../components/ngx-bootstrap-multiselect/autofocus.directive';
import { MultiSelectSearchFilter } from '../components/ngx-bootstrap-multiselect/search-filter.pipe';
import { OffClickDirective } from '../components/ngx-bootstrap-multiselect/off-click.directive';
import { NgxDropdownMultiselectComponent } from '../components/ngx-bootstrap-multiselect/ngx-bootstrap-multiselect.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -47,21 +44,16 @@ import { NgxDropdownMultiselectComponent } from '../components/ngx-bootstrap-mul
BisqMarketComponent, BisqMarketComponent,
BisqTradesComponent, BisqTradesComponent,
BisqMainDashboardComponent, BisqMainDashboardComponent,
NgxDropdownMultiselectComponent,
AutofocusDirective,
OffClickDirective,
], ],
imports: [ imports: [
CommonModule, CommonModule,
BisqRoutingModule, BisqRoutingModule,
SharedModule, SharedModule,
FontAwesomeModule, FontAwesomeModule,
NgxBootstrapMultiselectModule,
], ],
providers: [ providers: [
BisqApiService, BisqApiService,
MultiSelectSearchFilter,
AutofocusDirective,
OffClickDirective,
] ]
}) })
export class BisqModule { export class BisqModule {

View File

@@ -1,5 +1,5 @@
import { createChart, CrosshairMode, isBusinessDay } from 'lightweight-charts'; import { createChart, CrosshairMode, isBusinessDay } from 'lightweight-charts';
import { ChangeDetectionStrategy, Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'; import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
@Component({ @Component({
selector: 'app-lightweight-charts-area', selector: 'app-lightweight-charts-area',
@@ -25,15 +25,6 @@ export class LightweightChartsAreaComponent implements OnInit, OnChanges, OnDest
private element: ElementRef, private element: ElementRef,
) { } ) { }
@HostListener('window:resize', ['$event'])
resizeCanvas(): void {
this.width = this.element.nativeElement.parentElement.offsetWidth;
this.chart.applyOptions({
width: this.width,
height: this.height,
});
}
ngOnInit() { ngOnInit() {
this.width = this.element.nativeElement.parentElement.offsetWidth; this.width = this.element.nativeElement.parentElement.offsetWidth;
this.container = document.createElement('div'); this.container = document.createElement('div');

View File

@@ -1,5 +1,5 @@
import { createChart, CrosshairMode } from 'lightweight-charts'; import { createChart, CrosshairMode } from 'lightweight-charts';
import { ChangeDetectionStrategy, Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'; import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
@Component({ @Component({
selector: 'app-lightweight-charts', selector: 'app-lightweight-charts',
@@ -21,14 +21,6 @@ export class LightweightChartsComponent implements OnInit, OnChanges, OnDestroy
private element: ElementRef, private element: ElementRef,
) { } ) { }
@HostListener('window:resize', ['$event'])
resizeCanvas(): void {
this.chart.applyOptions({
width: this.element.nativeElement.parentElement.offsetWidth,
height: this.height,
});
}
ngOnInit() { ngOnInit() {
this.chart = createChart(this.element.nativeElement, { this.chart = createChart(this.element.nativeElement, {
width: this.element.nativeElement.parentElement.offsetWidth, width: this.element.nativeElement.parentElement.offsetWidth,

View File

@@ -9,13 +9,13 @@
<table> <table>
<tbody> <tbody>
<tr> <tr>
<td class="td-width" i18n="shared.transaction">Transaction</td> <td i18n="shared.transaction">Transaction</td>
<td> <td>
<a [routerLink]="['/tx/' | relativeUrl, txid]">{{ txid | shortenString : 16}}</a> <a [routerLink]="['/tx/' | relativeUrl, txid]">{{ txid | shortenString : 16}}</a>
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="td-width" i18n="dashboard.latest-transactions.amount">Amount</td> <td class="td-width" i18n="transaction.value|Transaction value">Value</td>
<td><app-amount [satoshis]="value"></app-amount></td> <td><app-amount [satoshis]="value"></app-amount></td>
</tr> </tr>
<tr> <tr>
@@ -23,13 +23,13 @@
<td>{{ fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> &nbsp; <span class="fiat"><app-fiat [value]="fee"></app-fiat></span></td> <td>{{ fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span> &nbsp; <span class="fiat"><app-fiat [value]="fee"></app-fiat></span></td>
</tr> </tr>
<tr> <tr>
<td class="td-width" i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td> <td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
<td> <td>
{{ feeRate | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> {{ feeRate | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="td-width" i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td> <td i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
<td [innerHTML]="'&lrm;' + (vsize | vbytes: 2)"></td> <td [innerHTML]="'&lrm;' + (vsize | vbytes: 2)"></td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -9,14 +9,10 @@
justify-content: space-between; justify-content: space-between;
padding: 10px 15px; padding: 10px 15px;
text-align: left; text-align: left;
min-width: 320px; width: 320px;
pointer-events: none; pointer-events: none;
&.clickable { &.clickable {
pointer-events: all; pointer-events: all;
} }
} }
.td-width {
padding-right: 10px;
}

View File

@@ -114,11 +114,7 @@
} }
.flashing { .flashing {
/* force compositing */ animation: opacityPulse 2s ease-out;
will-change: opacity;
transform: translateZ(0);
/* effective max frame rate = (#keyframes - 1) x steps / duration */
animation: opacityPulse 2s steps(30, end);
animation-iteration-count: infinite; animation-iteration-count: infinite;
opacity: 1; opacity: 1;
} }

View File

@@ -17,11 +17,7 @@
} }
.flashing { .flashing {
/* force compositing */ animation: opacityPulse 2s ease-out;
will-change: opacity;
transform: translateZ(0);
/* effective max frame rate = (#keyframes - 1) x steps / duration */
animation: opacityPulse 2s steps(30, end);
animation-iteration-count: infinite; animation-iteration-count: infinite;
opacity: 1; opacity: 1;
} }

View File

@@ -1,41 +0,0 @@
import { Directive, ElementRef, Host, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
@Directive({
selector: '[ssAutofocus]'
})
export class AutofocusDirective implements OnInit, OnChanges {
/**
* Will set focus if set to falsy value or not set at all
*/
@Input() ssAutofocus: any;
get element(): { focus?: Function } {
return this.elemRef.nativeElement;
}
constructor(
@Host() private elemRef: ElementRef,
) { }
ngOnInit() {
this.focus();
}
ngOnChanges(changes: SimpleChanges) {
const ssAutofocusChange = changes.ssAutofocus;
if (ssAutofocusChange && !ssAutofocusChange.isFirstChange()) {
this.focus();
}
}
focus() {
if (this.ssAutofocus) {
return;
}
this.element.focus && this.element.focus();
}
}

View File

@@ -1,48 +0,0 @@
a {
outline: none !important;
}
.dropdown-inline {
display: inline-block;
}
.dropdown-toggle .caret {
margin-left: 4px;
white-space: nowrap;
display: inline-block;
}
.chunkydropdown-menu {
min-width: 20em;
}
.chunkyrow {
line-height: 2;
margin-left: 1em;
font-size: 2em;
}
.slider {
width:3.8em;
height:3.8em;
display:block;
-webkit-transition: all 0.125s linear;
-moz-transition: all 0.125s linear;
-o-transition: all 0.125s linear;
transition: all 0.125s linear;
margin-left: 0.125em;
margin-top: auto;
}
.slideron {
margin-left: 1.35em;
}
.content_wrapper{
display: table-cell;
vertical-align: middle;
}
.search-container {
padding: 0px 5px 5px 5px;
}

View File

@@ -1,72 +0,0 @@
<div *ngIf="options" class="dropdown" [ngClass]="settings.containerClasses" [class.open]="isVisible" (offClick)="clickedOutside()">
<button type="button" class="dropdown-toggle" [ngClass]="settings.buttonClasses" (click)="toggleDropdown($event)" [disabled]="disabled"
[ssAutofocus]="!focusBack">
{{ title }}
<span class="caret"></span>
</button>
<div #scroller *ngIf="isVisible" class="dropdown-menu" [ngClass]="{'chunkydropdown-menu': settings.checkedStyle == 'visual' }"
(scroll)="settings.isLazyLoad ? checkScrollPosition($event) : null" (wheel)="settings.stopScrollPropagation ? checkScrollPropagation($event, scroller) : null"
[class.pull-right]="settings.pullRight" [class.dropdown-menu-right]="settings.pullRight" [style.max-height]="settings.maxHeight"
style="display: block; height: auto; overflow-y: auto;" (keydown.tab)="focusItem(1, $event)" (keydown.shift.tab)="focusItem(-1, $event)">
<div class="input-group search-container" *ngIf="settings.enableSearch && (renderFilteredOptions.length > 1 || filterControl.value.length > 0)">
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon1">
<i class="fa fa-search" aria-hidden="true"></i>
</span>
</div>
<input type="text" class="form-control" ssAutofocus [formControl]="filterControl" [placeholder]="texts.searchPlaceholder"
class="form-control">
<div class="input-group-append" *ngIf="filterControl.value.length>0">
<button class="btn btn-default btn-secondary" type="button" (click)="clearSearch($event)">
<i class="fa fa-times"></i>
</button>
</div>
</div>
<a role="menuitem" href="javascript:;" tabindex="-1" class="dropdown-item check-control check-control-check" *ngIf="settings.showCheckAll && !disabledSelection && renderFilteredOptions.length > 1"
(click)="checkAll()">
<span style="width: 16px;"><span [ngClass]="{'glyphicon glyphicon-ok': settings.checkedStyle !== 'fontawesome','fa fa-check': settings.checkedStyle === 'fontawesome'}"></span></span>
{{ texts.checkAll }}
</a>
<a role="menuitem" href="javascript:;" tabindex="-1" class="dropdown-item check-control check-control-uncheck" *ngIf="settings.showUncheckAll && !disabledSelection && renderFilteredOptions.length > 1"
(click)="uncheckAll()">
<span style="width: 16px;"><span [ngClass]="{'glyphicon glyphicon-remove': settings.checkedStyle !== 'fontawesome','fa fa-times': settings.checkedStyle === 'fontawesome'}"></span></span>
{{ texts.uncheckAll }}
</a>
<a *ngIf="settings.showCheckAll || settings.showUncheckAll" href="javascript:;" class="dropdown-divider divider"></a>
<a *ngIf="!renderItems" href="javascript:;" class="dropdown-item empty">{{ texts.searchNoRenderText }}</a>
<a *ngIf="renderItems && !renderFilteredOptions.length" href="javascript:;" class="dropdown-item empty">{{ texts.searchEmptyResult }}</a>
<a class="dropdown-item" href="javascript:;" *ngFor="let option of renderFilteredOptions; trackBy: trackById" [class.active]="isSelected(option)"
[ngStyle]="getItemStyle(option)" [ngClass]="option.classes" [class.dropdown-header]="option.isLabel" [ssAutofocus]="option !== focusedItem"
tabindex="-1" (click)="setSelected($event, option)" (keydown.space)="setSelected($event, option)" (keydown.enter)="setSelected($event, option)">
<span *ngIf="!option.isLabel; else label" role="menuitem" tabindex="-1" [style.padding-left]="this.parents.length>0&&this.parents.indexOf(option.id)<0&&'30px'"
[ngStyle]="getItemStyleSelectionDisabled()">
<ng-container [ngSwitch]="settings.checkedStyle">
<input *ngSwitchCase="'checkboxes'" type="checkbox" [checked]="isSelected(option)" (click)="preventCheckboxCheck($event, option)"
[disabled]="isCheckboxDisabled(option)" [ngStyle]="getItemStyleSelectionDisabled()" />
<span *ngSwitchCase="'glyphicon'" style="width: 16px;" class="glyphicon" [class.glyphicon-ok]="isSelected(option)" [class.glyphicon-lock]="isCheckboxDisabled(option)"></span>
<span *ngSwitchCase="'fontawesome'" style="width: 16px;display: inline-block;">
<span *ngIf="isSelected(option)"><i class="fa fa-check" aria-hidden="true"></i></span>
<span *ngIf="isCheckboxDisabled(option)"><i class="fa fa-lock" aria-hidden="true"></i></span>
</span>
<span *ngSwitchCase="'visual'" style="display:block;float:left; border-radius: 0.2em; border: 0.1em solid rgba(44, 44, 44, 0.63);background:rgba(0, 0, 0, 0.1);width: 5.5em;">
<div class="slider" [ngClass]="{'slideron': isSelected(option)}">
<img *ngIf="option.image != null" [src]="option.image" style="height: 100%; width: 100%; object-fit: contain" />
<div *ngIf="option.image == null" style="height: 100%; width: 100%;text-align: center; display: table; background-color:rgba(0, 0, 0, 0.74)">
<div class="content_wrapper">
<span style="font-size:3em;color:white" class="glyphicon glyphicon-eye-close"></span>
</div>
</div>
</div>
</span>
</ng-container>
<span [ngClass]="{'chunkyrow': settings.checkedStyle == 'visual' }" [class.disabled]="isCheckboxDisabled(option)" [ngClass]="settings.itemClasses"
[style.font-weight]="this.parents.indexOf(option.id)>=0?'bold':'normal'">
{{ option.name }}
</span>
</span>
<ng-template #label>
<span [class.disabled]="isCheckboxDisabled(option)">{{ option.name }}</span>
</ng-template>
</a>
</div>
</div>

View File

@@ -1,710 +0,0 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DoCheck,
EventEmitter,
forwardRef,
Input,
IterableDiffers,
OnChanges,
OnDestroy,
OnInit,
Output,
SimpleChanges,
} from '@angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormBuilder,
FormControl,
NG_VALUE_ACCESSOR,
Validator,
} from '@angular/forms';
import { takeUntil } from 'rxjs/operators';
import { MultiSelectSearchFilter } from './search-filter.pipe';
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts, } from './types';
import { Subject, Observable } from 'rxjs';
const MULTISELECT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NgxDropdownMultiselectComponent),
multi: true,
};
// tslint:disable-next-line: no-conflicting-lifecycle
@Component({
selector: 'ngx-bootstrap-multiselect',
templateUrl: './ngx-bootstrap-multiselect.component.html',
styleUrls: ['./ngx-bootstrap-multiselect.component.css'],
providers: [MULTISELECT_VALUE_ACCESSOR, MultiSelectSearchFilter],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class NgxDropdownMultiselectComponent implements OnInit,
OnChanges,
DoCheck,
OnDestroy,
ControlValueAccessor,
Validator {
private localIsVisible = false;
private workerDocClicked = false;
filterControl: FormControl = this.fb.control('');
@Input() options: Array<IMultiSelectOption>;
@Input() settings: IMultiSelectSettings;
@Input() texts: IMultiSelectTexts;
@Input() disabled = false;
@Input() disabledSelection = false;
@Input() searchFunction: (str: string) => RegExp = this._escapeRegExp;
@Output() selectionLimitReached = new EventEmitter();
@Output() dropdownClosed = new EventEmitter();
@Output() dropdownOpened = new EventEmitter();
@Output() added = new EventEmitter();
@Output() removed = new EventEmitter();
@Output() lazyLoad = new EventEmitter();
@Output() filter: Observable<string> = this.filterControl.valueChanges;
get focusBack(): boolean {
return this.settings.focusBack && this._focusBack;
}
destroyed$ = new Subject<any>();
filteredOptions: IMultiSelectOption[] = [];
lazyLoadOptions: IMultiSelectOption[] = [];
renderFilteredOptions: IMultiSelectOption[] = [];
model: any[] = [];
prevModel: any[] = [];
parents: any[];
title: string;
differ: any;
numSelected = 0;
set isVisible(val: boolean) {
this.localIsVisible = val;
this.workerDocClicked = val ? false : this.workerDocClicked;
}
get isVisible(): boolean {
return this.localIsVisible;
}
renderItems = true;
checkAllSearchRegister = new Set();
checkAllStatus = false;
loadedValueIds = [];
_focusBack = false;
focusedItem: IMultiSelectOption | undefined;
defaultSettings: IMultiSelectSettings = {
closeOnClickOutside: true,
pullRight: false,
enableSearch: false,
searchRenderLimit: 0,
searchRenderAfter: 1,
searchMaxLimit: 0,
searchMaxRenderedItems: 0,
checkedStyle: 'checkboxes',
buttonClasses: 'btn btn-primary dropdown-toggle',
containerClasses: 'dropdown-inline',
selectionLimit: 0,
minSelectionLimit: 0,
closeOnSelect: false,
autoUnselect: false,
showCheckAll: false,
showUncheckAll: false,
fixedTitle: false,
dynamicTitleMaxItems: 3,
maxHeight: '300px',
isLazyLoad: false,
stopScrollPropagation: false,
loadViewDistance: 1,
selectAddedValues: false,
ignoreLabels: false,
maintainSelectionOrderInTitle: false,
focusBack: true
};
defaultTexts: IMultiSelectTexts = {
checkAll: 'Select all',
uncheckAll: 'Unselect all',
checked: 'selected',
checkedPlural: 'selected',
searchPlaceholder: 'Search...',
searchEmptyResult: 'Nothing found...',
searchNoRenderText: 'Type in search box to see results...',
defaultTitle: 'Select',
allSelected: 'All selected',
};
get searchLimit(): number | undefined {
return this.settings.searchRenderLimit;
}
get searchRenderAfter(): number | undefined {
return this.settings.searchRenderAfter;
}
get searchLimitApplied(): boolean {
return this.searchLimit > 0 && this.options.length > this.searchLimit;
}
constructor(
private fb: FormBuilder,
private searchFilter: MultiSelectSearchFilter,
differs: IterableDiffers,
private cdRef: ChangeDetectorRef
) {
this.differ = differs.find([]).create(null);
this.settings = this.defaultSettings;
this.texts = this.defaultTexts;
}
clickedOutside(): void {
if (!this.isVisible || !this.settings.closeOnClickOutside) { return; }
this.isVisible = false;
this._focusBack = true;
this.dropdownClosed.emit();
}
getItemStyle(option: IMultiSelectOption): any {
const style = {};
if (!option.isLabel) {
style['cursor'] = 'pointer';
}
if (option.disabled) {
style['cursor'] = 'default';
}
}
getItemStyleSelectionDisabled(): any {
if (this.disabledSelection) {
return { cursor: 'default' };
}
}
ngOnInit(): void {
this.title = this.texts.defaultTitle || '';
this.filterControl.valueChanges.pipe(takeUntil(this.destroyed$)).subscribe(() => {
this.updateRenderItems();
if (this.settings.isLazyLoad) {
this.load();
}
});
}
ngOnChanges(changes: SimpleChanges) {
if (changes['options']) {
this.options = this.options || [];
this.parents = this.options
.filter(option => typeof option.parentId === 'number')
.map(option => option.parentId);
this.updateRenderItems();
if (
this.settings.isLazyLoad &&
this.settings.selectAddedValues &&
this.loadedValueIds.length === 0
) {
this.loadedValueIds = this.loadedValueIds.concat(
changes.options.currentValue.map(value => value.id)
);
}
if (
this.settings.isLazyLoad &&
this.settings.selectAddedValues &&
changes.options.previousValue
) {
const addedValues = changes.options.currentValue.filter(
value => this.loadedValueIds.indexOf(value.id) === -1
);
this.loadedValueIds.concat(addedValues.map(value => value.id));
if (this.checkAllStatus) {
this.addChecks(addedValues);
} else if (this.checkAllSearchRegister.size > 0) {
this.checkAllSearchRegister.forEach((searchValue: string) =>
this.addChecks(this.applyFilters(addedValues, searchValue))
);
}
}
if (this.texts) {
this.updateTitle();
}
this.fireModelChange();
}
if (changes['settings']) {
this.settings = { ...this.defaultSettings, ...this.settings };
}
if (changes['texts']) {
this.texts = { ...this.defaultTexts, ...this.texts };
if (!changes['texts'].isFirstChange()) { this.updateTitle(); }
}
}
ngOnDestroy() {
this.destroyed$.next(false);
}
updateRenderItems() {
this.renderItems =
!this.searchLimitApplied ||
this.filterControl.value.length >= this.searchRenderAfter;
this.filteredOptions = this.applyFilters(
this.options,
this.settings.isLazyLoad ? '' : this.filterControl.value
);
this.renderFilteredOptions = this.renderItems ? this.filteredOptions : [];
this.focusedItem = undefined;
}
applyFilters(options: IMultiSelectOption[], value: string): IMultiSelectOption[] {
return this.searchFilter.transform(
options,
value,
this.settings.searchMaxLimit,
this.settings.searchMaxRenderedItems,
this.searchFunction
);
}
fireModelChange(): void {
if (this.model != this.prevModel) {
this.prevModel = this.model;
this.onModelChange(this.model);
this.onModelTouched();
this.cdRef.markForCheck();
}
}
onModelChange: Function = (_: any) => { };
onModelTouched: Function = () => { };
writeValue(value: any): void {
if (value !== undefined && value !== null) {
this.model = Array.isArray(value) ? value : [value];
this.ngDoCheck();
} else {
this.model = [];
}
}
registerOnChange(fn: Function): void {
this.onModelChange = fn;
}
registerOnTouched(fn: Function): void {
this.onModelTouched = fn;
}
setDisabledState(isDisabled: boolean) {
this.disabled = isDisabled;
}
ngDoCheck() {
const changes = this.differ.diff(this.model);
if (changes) {
this.updateNumSelected();
this.updateTitle();
}
}
validate(_c: AbstractControl): { [key: string]: any } {
if (this.model && this.model.length) {
return {
required: {
valid: false
}
};
}
if (this.options.filter(o => this.model.indexOf(o.id) && !o.disabled).length === 0) {
return {
selection: {
valid: false
}
};
}
return null;
}
registerOnValidatorChange(_fn: () => void): void {
throw new Error('Method not implemented.');
}
clearSearch(event: Event) {
this.maybeStopPropagation(event);
this.filterControl.setValue('');
}
toggleDropdown(e?: Event) {
if (this.isVisible) {
this._focusBack = true;
}
this.isVisible = !this.isVisible;
this.isVisible ? this.dropdownOpened.emit() : this.dropdownClosed.emit();
this.focusedItem = undefined;
}
closeDropdown(e?: Event) {
this.isVisible = true;
this.toggleDropdown(e);
}
isSelected(option: IMultiSelectOption): boolean {
return this.model && this.model.indexOf(option.id) > -1;
}
setSelected(_event: Event, option: IMultiSelectOption) {
if (option.isLabel) {
return;
}
if (option.disabled) {
return;
}
if (this.disabledSelection) {
return;
}
setTimeout(() => {
this.maybeStopPropagation(_event);
this.maybePreventDefault(_event);
const index = this.model.indexOf(option.id);
const isAtSelectionLimit =
this.settings.selectionLimit > 0 &&
this.model.length >= this.settings.selectionLimit;
const removeItem = (idx, id): void => {
this.model.splice(idx, 1);
this.removed.emit(id);
if (
this.settings.isLazyLoad &&
this.lazyLoadOptions.some(val => val.id === id)
) {
this.lazyLoadOptions.splice(
this.lazyLoadOptions.indexOf(
this.lazyLoadOptions.find(val => val.id === id)
),
1
);
}
};
if (index > -1) {
if (
this.settings.minSelectionLimit === undefined ||
this.numSelected > this.settings.minSelectionLimit
) {
removeItem(index, option.id);
}
const parentIndex =
option.parentId && this.model.indexOf(option.parentId);
if (parentIndex > -1) {
removeItem(parentIndex, option.parentId);
} else if (this.parents.indexOf(option.id) > -1) {
this.options
.filter(
child =>
this.model.indexOf(child.id) > -1 &&
child.parentId === option.id
)
.forEach(child =>
removeItem(this.model.indexOf(child.id), child.id)
);
}
} else if (isAtSelectionLimit && !this.settings.autoUnselect) {
this.selectionLimitReached.emit(this.model.length);
return;
} else {
const addItem = (id): void => {
this.model.push(id);
this.added.emit(id);
if (
this.settings.isLazyLoad &&
!this.lazyLoadOptions.some(val => val.id === id)
) {
this.lazyLoadOptions.push(option);
}
};
addItem(option.id);
if (!isAtSelectionLimit) {
if (option.parentId && !this.settings.ignoreLabels) {
const children = this.options.filter(
child =>
child.id !== option.id && child.parentId === option.parentId
);
if (children.every(child => this.model.indexOf(child.id) > -1)) {
addItem(option.parentId);
}
} else if (this.parents.indexOf(option.id) > -1) {
const children = this.options.filter(
child =>
this.model.indexOf(child.id) < 0 && child.parentId === option.id
);
children.forEach(child => addItem(child.id));
}
} else {
removeItem(0, this.model[0]);
}
}
if (this.settings.closeOnSelect) {
this.toggleDropdown();
}
this.model = this.model.slice();
this.fireModelChange();
}, 0)
}
updateNumSelected() {
this.numSelected =
this.model.filter(id => this.parents.indexOf(id) < 0).length || 0;
}
updateTitle() {
let numSelectedOptions = this.options.length;
if (this.settings.ignoreLabels) {
numSelectedOptions = this.options.filter(
(option: IMultiSelectOption) => !option.isLabel
).length;
}
if (this.numSelected === 0 || this.settings.fixedTitle) {
this.title = this.texts ? this.texts.defaultTitle : '';
} else if (
this.settings.displayAllSelectedText &&
this.model.length === numSelectedOptions
) {
this.title = this.texts ? this.texts.allSelected : '';
} else if (
this.settings.dynamicTitleMaxItems &&
this.settings.dynamicTitleMaxItems >= this.numSelected
) {
const useOptions =
this.settings.isLazyLoad && this.lazyLoadOptions.length
? this.lazyLoadOptions
: this.options;
let titleSelections: Array<IMultiSelectOption>;
if (this.settings.maintainSelectionOrderInTitle) {
const optionIds = useOptions.map((selectOption: IMultiSelectOption, idx: number) => selectOption.id);
titleSelections = this.model
.map((selectedId) => optionIds.indexOf(selectedId))
.filter((optionIndex) => optionIndex > -1)
.map((optionIndex) => useOptions[optionIndex]);
} else {
titleSelections = useOptions.filter((option: IMultiSelectOption) => this.model.indexOf(option.id) > -1);
}
this.title = titleSelections.map((option: IMultiSelectOption) => option.name).join(', ');
} else {
this.title =
this.numSelected +
' ' +
(this.numSelected === 1
? this.texts.checked
: this.texts.checkedPlural);
}
this.cdRef.markForCheck();
}
searchFilterApplied() {
return (
this.settings.enableSearch &&
this.filterControl.value &&
this.filterControl.value.length > 0
);
}
addChecks(options) {
const checkedOptions = options
.filter((option: IMultiSelectOption) => {
if (
!option.disabled &&
(
this.model.indexOf(option.id) === -1 &&
!(this.settings.ignoreLabels && option.isLabel)
)
) {
this.added.emit(option.id);
return true;
}
return false;
})
.map((option: IMultiSelectOption) => option.id);
this.model = this.model.concat(checkedOptions);
}
checkAll(): void {
if (!this.disabledSelection) {
this.addChecks(
!this.searchFilterApplied() ? this.options : this.filteredOptions
);
if (this.settings.isLazyLoad && this.settings.selectAddedValues) {
if (this.searchFilterApplied() && !this.checkAllStatus) {
this.checkAllSearchRegister.add(this.filterControl.value);
} else {
this.checkAllSearchRegister.clear();
this.checkAllStatus = true;
}
this.load();
}
this.fireModelChange();
}
}
uncheckAll(): void {
if (!this.disabledSelection) {
const checkedOptions = this.model;
let unCheckedOptions = !this.searchFilterApplied()
? this.model
: this.filteredOptions.map((option: IMultiSelectOption) => option.id);
// set unchecked options only to the ones that were checked
unCheckedOptions = checkedOptions.filter(item => unCheckedOptions.indexOf(item) > -1);
this.model = this.model.filter((id: number) => {
if (
(unCheckedOptions.indexOf(id) < 0 &&
this.settings.minSelectionLimit === undefined) ||
unCheckedOptions.indexOf(id) < this.settings.minSelectionLimit
) {
return true;
} else {
this.removed.emit(id);
return false;
}
});
if (this.settings.isLazyLoad && this.settings.selectAddedValues) {
if (this.searchFilterApplied()) {
if (this.checkAllSearchRegister.has(this.filterControl.value)) {
this.checkAllSearchRegister.delete(this.filterControl.value);
this.checkAllSearchRegister.forEach(function(searchTerm) {
const filterOptions = this.applyFilters(this.options.filter(option => unCheckedOptions.indexOf(option.id) > -1), searchTerm);
this.addChecks(filterOptions);
});
}
} else {
this.checkAllSearchRegister.clear();
this.checkAllStatus = false;
}
this.load();
}
this.fireModelChange();
}
}
preventCheckboxCheck(event: Event, option: IMultiSelectOption): void {
if (
option.disabled ||
(
this.settings.selectionLimit &&
!this.settings.autoUnselect &&
this.model.length >= this.settings.selectionLimit &&
this.model.indexOf(option.id) === -1 &&
this.maybePreventDefault(event)
)
) {
this.maybePreventDefault(event);
}
}
isCheckboxDisabled(option?: IMultiSelectOption): boolean {
return this.disabledSelection || option && option.disabled;
}
checkScrollPosition(ev): void {
const scrollTop = ev.target.scrollTop;
const scrollHeight = ev.target.scrollHeight;
const scrollElementHeight = ev.target.clientHeight;
const roundingPixel = 1;
const gutterPixel = 1;
if (
scrollTop >=
scrollHeight -
(1 + this.settings.loadViewDistance) * scrollElementHeight -
roundingPixel -
gutterPixel
) {
this.load();
}
}
checkScrollPropagation(ev, element): void {
const scrollTop = element.scrollTop;
const scrollHeight = element.scrollHeight;
const scrollElementHeight = element.clientHeight;
if (
(ev.deltaY > 0 && scrollTop + scrollElementHeight >= scrollHeight) ||
(ev.deltaY < 0 && scrollTop <= 0)
) {
ev = ev || window.event;
this.maybePreventDefault(ev);
ev.returnValue = false;
}
}
trackById(idx: number, selectOption: IMultiSelectOption): void {
return selectOption.id;
}
load(): void {
this.lazyLoad.emit({
length: this.options.length,
filter: this.filterControl.value,
checkAllSearches: this.checkAllSearchRegister,
checkAllStatus: this.checkAllStatus,
});
}
focusItem(dir: number, e?: Event): void {
if (!this.isVisible) {
return;
}
this.maybePreventDefault(e);
const idx = this.filteredOptions.indexOf(this.focusedItem);
if (idx === -1) {
this.focusedItem = this.filteredOptions[0];
return;
}
const nextIdx = idx + dir;
const newIdx =
nextIdx < 0
? this.filteredOptions.length - 1
: nextIdx % this.filteredOptions.length;
this.focusedItem = this.filteredOptions[newIdx];
}
private maybePreventDefault(e?: Event): void {
if (e && e.preventDefault) {
e.preventDefault();
}
}
private maybeStopPropagation(e?: Event): void {
if (e && e.stopPropagation) {
e.stopPropagation();
}
}
private _escapeRegExp(str: string): RegExp {
const regExpStr = str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
return new RegExp(regExpStr, 'i');
}
}

View File

@@ -1,39 +0,0 @@
import { Directive, HostListener } from '@angular/core';
import { EventEmitter } from '@angular/core';
import { Output } from '@angular/core';
@Directive({
// tslint:disable-next-line:directive-selector
selector: '[offClick]',
})
export class OffClickDirective {
@Output('offClick') onOffClick = new EventEmitter<any>();
private _clickEvent: MouseEvent;
private _touchEvent: TouchEvent;
@HostListener('click', ['$event'])
public onClick(event: MouseEvent): void {
this._clickEvent = event;
}
@HostListener('touchstart', ['$event'])
public onTouch(event: TouchEvent): void {
this._touchEvent = event;
}
@HostListener('document:click', ['$event'])
public onDocumentClick(event: MouseEvent): void {
if (event !== this._clickEvent) {
this.onOffClick.emit(event);
}
}
@HostListener('document:touchstart', ['$event'])
public onDocumentTouch(event: TouchEvent): void {
if (event !== this._touchEvent) {
this.onOffClick.emit(event);
}
}
}

View File

@@ -1,130 +0,0 @@
import { Pipe, PipeTransform } from '@angular/core';
import { IMultiSelectOption } from './types';
interface StringHashMap<T> {
[k: string]: T;
}
@Pipe({
name: 'searchFilter'
})
export class MultiSelectSearchFilter implements PipeTransform {
private _lastOptions: IMultiSelectOption[];
private _searchCache: StringHashMap<IMultiSelectOption[]> = {};
private _searchCacheInclusive: StringHashMap<boolean | number> = {};
private _prevSkippedItems: StringHashMap<number> = {};
transform(
options: IMultiSelectOption[],
str = '',
limit = 0,
renderLimit = 0,
searchFunction: (str: string) => RegExp,
): IMultiSelectOption[] {
str = str.toLowerCase();
// Drop cache because options were updated
if (options !== this._lastOptions) {
this._lastOptions = options;
this._searchCache = {};
this._searchCacheInclusive = {};
this._prevSkippedItems = {};
}
const filteredOpts = this._searchCache.hasOwnProperty(str)
? this._searchCache[str]
: this._doSearch(options, str, limit, searchFunction);
const isUnderLimit = options.length <= limit;
return isUnderLimit
? filteredOpts
: this._limitRenderedItems(filteredOpts, renderLimit);
}
private _getSubsetOptions(
options: IMultiSelectOption[],
prevOptions: IMultiSelectOption[],
prevSearchStr: string
) {
const prevInclusiveOrIdx = this._searchCacheInclusive[prevSearchStr];
if (prevInclusiveOrIdx === true) {
// If have previous results and it was inclusive, do only subsearch
return prevOptions;
} else if (typeof prevInclusiveOrIdx === 'number') {
// Or reuse prev results with unchecked ones
return [...prevOptions, ...options.slice(prevInclusiveOrIdx)];
}
return options;
}
private _doSearch(options: IMultiSelectOption[], str: string, limit: number, searchFunction: (str: string) => RegExp) {
const prevStr = str.slice(0, -1);
const prevResults = this._searchCache[prevStr];
const prevResultShift = this._prevSkippedItems[prevStr] || 0;
if (prevResults) {
options = this._getSubsetOptions(options, prevResults, prevStr);
}
const optsLength = options.length;
const maxFound = limit > 0 ? Math.min(limit, optsLength) : optsLength;
const regexp = searchFunction(str);
const filteredOpts: IMultiSelectOption[] = [];
let i = 0, founded = 0, removedFromPrevResult = 0;
const doesOptionMatch = (option: IMultiSelectOption) => regexp.test(option.name);
const getChildren = (option: IMultiSelectOption) =>
options.filter(child => child.parentId === option.id);
const getParent = (option: IMultiSelectOption) =>
options.find(parent => option.parentId === parent.id);
const foundFn = (item: any) => { filteredOpts.push(item); founded++; };
const notFoundFn = prevResults ? () => removedFromPrevResult++ : () => { };
for (; i < optsLength && founded < maxFound; ++i) {
const option = options[i];
const directMatch = doesOptionMatch(option);
if (directMatch) {
foundFn(option);
continue;
}
if (typeof option.parentId === 'undefined') {
const childrenMatch = getChildren(option).some(doesOptionMatch);
if (childrenMatch) {
foundFn(option);
continue;
}
}
if (typeof option.parentId !== 'undefined') {
const parentMatch = doesOptionMatch(getParent(option));
if (parentMatch) {
foundFn(option);
continue;
}
}
notFoundFn();
}
const totalIterations = i + prevResultShift;
this._searchCache[str] = filteredOpts;
this._searchCacheInclusive[str] = i === optsLength || totalIterations;
this._prevSkippedItems[str] = removedFromPrevResult + prevResultShift;
return filteredOpts;
}
private _limitRenderedItems<T>(items: T[], limit: number): T[] {
return items.length > limit && limit > 0 ? items.slice(0, limit) : items;
}
}

View File

@@ -1,82 +0,0 @@
export interface IMultiSelectOption {
id: any;
name: string;
disabled?: boolean;
isLabel?: boolean;
parentId?: any;
params?: any;
classes?: string;
image?: string;
}
export interface IMultiSelectSettings {
pullRight?: boolean;
enableSearch?: boolean;
closeOnClickOutside?: boolean;
/**
* 0 - By default
* If `enableSearch=true` and total amount of items more then `searchRenderLimit` (0 - No limit)
* then render items only when user typed more then or equal `searchRenderAfter` charachters
*/
searchRenderLimit?: number;
/**
* 3 - By default
*/
searchRenderAfter?: number;
/**
* 0 - By default
* If >0 will render only N first items
*/
searchMaxLimit?: number;
/**
* 0 - By default
* Used with searchMaxLimit to further limit rendering for optimization
* Should be less than searchMaxLimit to take effect
*/
searchMaxRenderedItems?: number;
checkedStyle?: 'checkboxes' | 'glyphicon' | 'fontawesome' | 'visual';
buttonClasses?: string;
itemClasses?: string;
containerClasses?: string;
selectionLimit?: number;
minSelectionLimit?: number;
closeOnSelect?: boolean;
autoUnselect?: boolean;
showCheckAll?: boolean;
showUncheckAll?: boolean;
fixedTitle?: boolean;
dynamicTitleMaxItems?: number;
maxHeight?: string;
displayAllSelectedText?: boolean;
isLazyLoad?: boolean;
loadViewDistance?: number;
stopScrollPropagation?: boolean;
selectAddedValues?: boolean;
/**
* false - By default
* If activated label IDs don't count and won't be written to the model.
*/
ignoreLabels?: boolean;
/**
* false - By default
* If activated, the title will show selections in the order they were selected.
*/
maintainSelectionOrderInTitle?: boolean;
/**
* @default true
* Set the focus back to the input control when the dropdown closed
*/
focusBack?: boolean;
}
export interface IMultiSelectTexts {
checkAll?: string;
uncheckAll?: string;
checked?: string;
checkedPlural?: string;
searchPlaceholder?: string;
searchEmptyResult?: string;
searchNoRenderText?: string;
defaultTitle?: string;
allSelected?: string;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -878,10 +878,6 @@
<context context-type="sourcefile">src/app/bisq/bisq-transactions/bisq-transactions.component.html</context> <context context-type="sourcefile">src/app/bisq/bisq-transactions/bisq-transactions.component.html</context>
<context context-type="linenumber">20,21</context> <context context-type="linenumber">20,21</context>
</context-group> </context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/block-overview-tooltip/block-overview-tooltip.component.html</context>
<context context-type="linenumber">18</context>
</context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/dashboard/dashboard.component.html</context> <context context-type="sourcefile">src/app/dashboard/dashboard.component.html</context>
<context context-type="linenumber">124,125</context> <context context-type="linenumber">124,125</context>
@@ -1267,14 +1263,14 @@
<source>Trades</source> <source>Trades</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/bisq/lightweight-charts-area/lightweight-charts-area.component.ts</context> <context context-type="sourcefile">src/app/bisq/lightweight-charts-area/lightweight-charts-area.component.ts</context>
<context context-type="linenumber">99</context> <context context-type="linenumber">90</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="bisq-graph-volume" datatype="html"> <trans-unit id="bisq-graph-volume" datatype="html">
<source>Volume</source> <source>Volume</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/bisq/lightweight-charts-area/lightweight-charts-area.component.ts</context> <context context-type="sourcefile">src/app/bisq/lightweight-charts-area/lightweight-charts-area.component.ts</context>
<context context-type="linenumber">100</context> <context context-type="linenumber">91</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="4b137ec8bf73a47063740b75c0c40d5fd3c48015" datatype="html"> <trans-unit id="4b137ec8bf73a47063740b75c0c40d5fd3c48015" datatype="html">
@@ -1922,6 +1918,15 @@
<context context-type="linenumber">264,266</context> <context context-type="linenumber">264,266</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="bc4c61d3713989e3c8c6610fca3ea1ca1cb19edb" datatype="html">
<source>Value</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/block-overview-tooltip/block-overview-tooltip.component.html</context>
<context context-type="linenumber">18</context>
</context-group>
<note priority="1" from="description">Transaction value</note>
<note priority="1" from="meaning">transaction.value</note>
</trans-unit>
<trans-unit id="cb1b52c13b95fa29ea4044f2bbe0ac623b890c80" datatype="html"> <trans-unit id="cb1b52c13b95fa29ea4044f2bbe0ac623b890c80" datatype="html">
<source>Fee</source> <source>Fee</source>
<context-group purpose="location"> <context-group purpose="location">

File diff suppressed because it is too large Load Diff

View File

@@ -1046,7 +1046,3 @@ th {
box-shadow: -10px -15px 75px rgba(#eba814, 1); box-shadow: -10px -15px 75px rgba(#eba814, 1);
transition: 100ms all ease-in; transition: 100ms all ease-in;
} }
.page-item {
font-family: monospace;
}

View File

@@ -82,11 +82,11 @@ pkg install -y zsh sudo git screen curl wget neovim rsync nginx openssl openssh-
### Node.js + npm ### Node.js + npm
Build Node.js v16.16.0 and npm v8 from source using `nvm`: Build Node.js v16.15 and npm v8 from source using `nvm`:
``` ```
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh
source $HOME/.zshrc source $HOME/.zshrc
nvm install v16.16.0 --shared-zlib nvm install v16.15.0
nvm alias default node nvm alias default node
``` ```

View File

@@ -183,9 +183,6 @@ case $OS in
TOR_PKG=tor TOR_PKG=tor
TOR_USER=_tor TOR_USER=_tor
TOR_GROUP=_tor TOR_GROUP=_tor
NGINX_USER=www
NGINX_ETC_FOLDER=/usr/local/etc/nginx
NGINX_CONFIGURATION=/usr/local/etc/nginx/nginx.conf
CERTBOT_PKG=py39-certbot CERTBOT_PKG=py39-certbot
;; ;;
@@ -200,8 +197,6 @@ case $OS in
TOR_USER=debian-tor TOR_USER=debian-tor
TOR_GROUP=debian-tor TOR_GROUP=debian-tor
CERTBOT_PKG=python3-certbot-nginx CERTBOT_PKG=python3-certbot-nginx
NGINX_USER=www-data
NGINX_ETC_FOLDER=/etc/nginx
NGINX_CONFIGURATION=/etc/nginx/nginx.conf NGINX_CONFIGURATION=/etc/nginx/nginx.conf
;; ;;
esac esac
@@ -651,193 +646,193 @@ ext4CreateDir()
# does bitcoin exist? # does bitcoin exist?
########### ##########
## dialog # # dialog #
########### ##########
#
#: ${DIALOG=dialog} : ${DIALOG=dialog}
#
#: ${DIALOG_OK=0} : ${DIALOG_OK=0}
#: ${DIALOG_CANCEL=1} : ${DIALOG_CANCEL=1}
#: ${DIALOG_HELP=2} : ${DIALOG_HELP=2}
#: ${DIALOG_EXTRA=3} : ${DIALOG_EXTRA=3}
#: ${DIALOG_ITEM_HELP=4} : ${DIALOG_ITEM_HELP=4}
#: ${DIALOG_ESC=255} : ${DIALOG_ESC=255}
#
#: ${SIG_OFFNE=0} : ${SIG_OFFNE=0}
#: ${SIG_HUP=1} : ${SIG_HUP=1}
#: ${SIG_INT=2} : ${SIG_INT=2}
#: ${SIG_QUIT=3} : ${SIG_QUIT=3}
#: ${SIG_KILL=9} : ${SIG_KILL=9}
#: ${SIG_TERM=15} : ${SIG_TERM=15}
#
#input=`tempfile 2>/dev/null` || input=/tmp/input$$ input=`tempfile 2>/dev/null` || input=/tmp/input$$
#output=`tempfile 2>/dev/null` || output=/tmp/test$$ output=`tempfile 2>/dev/null` || output=/tmp/test$$
#trap "rm -f $input $output" $SIG_OFFNE $SIG_HUP $SIG_INT $SIG_TRAP $SIG_TERM trap "rm -f $input $output" $SIG_OFFNE $SIG_HUP $SIG_INT $SIG_TRAP $SIG_TERM
#
#DIALOG_ERROR=254 DIALOG_ERROR=254
#export DIALOG_ERROR export DIALOG_ERROR
#
#backtitle="Mempool Fullnode Installer" backtitle="Mempool Fullnode Installer"
#title="Mempool Fullnode Installer" title="Mempool Fullnode Installer"
#returncode=0 returncode=0
#
################## #################
## dialog part 1 # # dialog part 1 #
################## #################
#
#$CUT >$input <<-EOF $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 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
#Lightmode:Enable Electrs Lightmode to save disk space:ON Lightmode:Enable Electrs Lightmode to save disk space:ON
#Smalldisk:Disable Electrs Compaction to save disk space:ON Smalldisk:Disable Electrs Compaction to save disk space:ON
#Firewall:Enable Firewall:ON Firewall:Enable Firewall:ON
#EOF EOF
#
#cat $input | sed -e 's/^/"/' -e 's/:/" "/g' -e 's/$/"/' >$output cat $input | sed -e 's/^/"/' -e 's/:/" "/g' -e 's/$/"/' >$output
#cat $output >$input cat $output >$input
#
#$DIALOG --backtitle "${backtitle}" \ $DIALOG --backtitle "${backtitle}" \
# --title "${title}" "$@" \ --title "${title}" "$@" \
# --checklist "Toggle the features below to configure your fullnode:\n" \ --checklist "Toggle the features below to configure your fullnode:\n" \
# 20 80 10 \ 20 80 10 \
# --file $input 2> $output --file $input 2> $output
#
#retval=$? retval=$?
#
#tempfile=$output tempfile=$output
#if [ $retval != $DIALOG_OK ];then if [ $retval != $DIALOG_OK ];then
# echo "Installation aborted." echo "Installation aborted."
# exit 1 exit 1
#fi fi
#
#if grep Tor $tempfile >/dev/null 2>&1;then if grep Tor $tempfile >/dev/null 2>&1;then
# TOR_INSTALL=ON TOR_INSTALL=ON
#else else
# TOR_INSTALL=OFF TOR_INSTALL=OFF
#fi fi
#
#if grep Certbot $tempfile >/dev/null 2>&1;then if grep Certbot $tempfile >/dev/null 2>&1;then
# CERTBOT_INSTALL=ON CERTBOT_INSTALL=ON
#else else
# CERTBOT_INSTALL=OFF CERTBOT_INSTALL=OFF
#fi fi
#
#if grep Mainnet $tempfile >/dev/null 2>&1;then if grep Mainnet $tempfile >/dev/null 2>&1;then
# BITCOIN_MAINNET_ENABLE=ON BITCOIN_MAINNET_ENABLE=ON
#else else
# BITCOIN_MAINNET_ENABLE=OFF BITCOIN_MAINNET_ENABLE=OFF
#fi fi
#
#if grep Mainnet-Minfee $tempfile >/dev/null 2>&1;then if grep Mainnet-Minfee $tempfile >/dev/null 2>&1;then
# BITCOIN_MAINNET_MINFEE_ENABLE=ON BITCOIN_MAINNET_MINFEE_ENABLE=ON
#else else
# BITCOIN_MAINNET_MINFEE_ENABLE=OFF BITCOIN_MAINNET_MINFEE_ENABLE=OFF
#fi 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
# BITCOIN_TESTNET_ENABLE=OFF BITCOIN_TESTNET_ENABLE=OFF
#fi fi
#
#if grep Liquid $tempfile >/dev/null 2>&1;then if grep Liquid $tempfile >/dev/null 2>&1;then
# ELEMENTS_INSTALL=ON ELEMENTS_INSTALL=ON
# ELEMENTS_LIQUID_ENABLE=ON ELEMENTS_LIQUID_ENABLE=ON
#else else
# ELEMENTS_INSTALL=OFF ELEMENTS_INSTALL=OFF
# ELEMENTS_LIQUID_ENABLE=OFF ELEMENTS_LIQUID_ENABLE=OFF
#fi fi
#
#if grep Bisq $tempfile >/dev/null 2>&1;then if grep Bisq $tempfile >/dev/null 2>&1;then
# BISQ_INSTALL=ON BISQ_INSTALL=ON
# BISQ_MAINNET_ENABLE=ON BISQ_MAINNET_ENABLE=ON
#else else
# BISQ_INSTALL=OFF BISQ_INSTALL=OFF
# BISQ_MAINNET_ENABLE=OFF BISQ_MAINNET_ENABLE=OFF
#fi fi
#
#if grep Lightmode $tempfile >/dev/null 2>&1;then if grep Lightmode $tempfile >/dev/null 2>&1;then
# BITCOIN_ELECTRS_LIGHT_MODE=ON BITCOIN_ELECTRS_LIGHT_MODE=ON
#else else
# BITCOIN_ELECTRS_LIGHT_MODE=OFF BITCOIN_ELECTRS_LIGHT_MODE=OFF
#fi fi
#
#if grep Smalldisk $tempfile >/dev/null 2>&1;then if grep Smalldisk $tempfile >/dev/null 2>&1;then
# BITCOIN_ELECTRS_LIGHT_MODE=ON BITCOIN_ELECTRS_LIGHT_MODE=ON
#else else
# BITCOIN_ELECTRS_LIGHT_MODE=OFF BITCOIN_ELECTRS_LIGHT_MODE=OFF
#fi fi
#
################## #################
## dialog part 2 # # dialog part 2 #
################## #################
#
#$DIALOG --cr-wrap \ $DIALOG --cr-wrap \
# --title "INPUT BOX" --clear \ --title "INPUT BOX" --clear \
# --inputbox "$@" \ --inputbox "$@" \
#"Enter the FQDN hostname for obtaining an SSL certificate using Certbot:" 0 0 "${HOSTNAME}" 2> $tempfile "Enter the FQDN hostname for obtaining an SSL certificate using Certbot:" 0 0 "${HOSTNAME}" 2> $tempfile
#HOSTNAME=$(cat $tempfile) HOSTNAME=$(cat $tempfile)
#
################## #################
## dialog part 3 # # dialog part 3 #
################## #################
#
## --form text height width formheight # --form text height width formheight
## [ label y x item y x flen ilen ] # [ label y x item y x flen ilen ]
# #"BISQ_BLOCKNOTIFY_HOST" 0 1 "${BISQ_BLOCKNOTIFY_HOST}" 0 30 0 0 \ #"BISQ_BLOCKNOTIFY_HOST" 0 1 "${BISQ_BLOCKNOTIFY_HOST}" 0 30 0 0 \
#
#$DIALOG --ok-label "Submit" \ $DIALOG --ok-label "Submit" \
# --backtitle "$backtitle" "$@" \ --backtitle "$backtitle" "$@" \
# --form "Your fullnode will be installed as follows:" 0 0 0 \ --form "Your fullnode will be installed as follows:" 0 0 0 \
# "BISQ_LATEST_RELEASE" 1 1 "${BISQ_LATEST_RELEASE}" 1 35 35 0 \ "BISQ_LATEST_RELEASE" 1 1 "${BISQ_LATEST_RELEASE}" 1 35 35 0 \
# "BISQ_REPO_BRANCH" 2 1 "${BISQ_REPO_BRANCH}" 2 35 35 0 \ "BISQ_REPO_BRANCH" 2 1 "${BISQ_REPO_BRANCH}" 2 35 35 0 \
# "BISQ_REPO_NAME" 3 1 "${BISQ_REPO_NAME}" 3 35 35 0 \ "BISQ_REPO_NAME" 3 1 "${BISQ_REPO_NAME}" 3 35 35 0 \
# "BISQ_REPO_URL" 4 1 "${BISQ_REPO_URL}" 4 35 35 0 \ "BISQ_REPO_URL" 4 1 "${BISQ_REPO_URL}" 4 35 35 0 \
# "BITCOIN_ELECTRS_LATEST_RELEASE" 5 1 "${BITCOIN_ELECTRS_LATEST_RELEASE}" 5 35 35 0 \ "BITCOIN_ELECTRS_LATEST_RELEASE" 5 1 "${BITCOIN_ELECTRS_LATEST_RELEASE}" 5 35 35 0 \
# "BITCOIN_ELECTRS_LIGHT_MODE" 6 1 "${BITCOIN_ELECTRS_LIGHT_MODE}" 6 35 35 0 \ "BITCOIN_ELECTRS_LIGHT_MODE" 6 1 "${BITCOIN_ELECTRS_LIGHT_MODE}" 6 35 35 0 \
# "BITCOIN_ELECTRS_REPO_BRANCH" 7 1 "${BITCOIN_ELECTRS_REPO_BRANCH}" 7 35 35 0 \ "BITCOIN_ELECTRS_REPO_BRANCH" 7 1 "${BITCOIN_ELECTRS_REPO_BRANCH}" 7 35 35 0 \
# "BITCOIN_ELECTRS_REPO_NAME" 8 1 "${BITCOIN_ELECTRS_REPO_NAME}" 8 35 35 0 \ "BITCOIN_ELECTRS_REPO_NAME" 8 1 "${BITCOIN_ELECTRS_REPO_NAME}" 8 35 35 0 \
# "BITCOIN_ELECTRS_REPO_URL" 9 1 "${BITCOIN_ELECTRS_REPO_URL}" 9 35 35 0 \ "BITCOIN_ELECTRS_REPO_URL" 9 1 "${BITCOIN_ELECTRS_REPO_URL}" 9 35 35 0 \
# "BITCOIN_LATEST_RELEASE" 10 1 "${BITCOIN_LATEST_RELEASE}" 10 35 35 0 \ "BITCOIN_LATEST_RELEASE" 10 1 "${BITCOIN_LATEST_RELEASE}" 10 35 35 0 \
# "BITCOIN_MAINNET_ENABLE" 11 1 "${BITCOIN_MAINNET_ENABLE}" 11 35 35 0 \ "BITCOIN_MAINNET_ENABLE" 11 1 "${BITCOIN_MAINNET_ENABLE}" 11 35 35 0 \
# "BITCOIN_REPO_BRANCH" 12 1 "${BITCOIN_REPO_BRANCH}" 12 35 35 0 \ "BITCOIN_REPO_BRANCH" 12 1 "${BITCOIN_REPO_BRANCH}" 12 35 35 0 \
# "BITCOIN_REPO_NAME" 13 1 "${BITCOIN_REPO_NAME}" 13 35 35 0 \ "BITCOIN_REPO_NAME" 13 1 "${BITCOIN_REPO_NAME}" 13 35 35 0 \
# "BITCOIN_REPO_URL" 14 1 "${BITCOIN_REPO_URL}" 14 35 35 0 \ "BITCOIN_REPO_URL" 14 1 "${BITCOIN_REPO_URL}" 14 35 35 0 \
# "BITCOIN_TESTNET_ENABLE" 15 1 "${BITCOIN_TESTNET_ENABLE}" 15 35 35 0 \ "BITCOIN_TESTNET_ENABLE" 15 1 "${BITCOIN_TESTNET_ENABLE}" 15 35 35 0 \
# "ELEMENTS_INSTALL" 16 1 "${ELEMENTS_INSTALL}" 16 35 35 0 \ "ELEMENTS_INSTALL" 16 1 "${ELEMENTS_INSTALL}" 16 35 35 0 \
# "ELEMENTS_LATEST_RELEASE" 17 1 "${ELEMENTS_LATEST_RELEASE}" 17 35 35 0 \ "ELEMENTS_LATEST_RELEASE" 17 1 "${ELEMENTS_LATEST_RELEASE}" 17 35 35 0 \
# "ELEMENTS_LIQUID_ENABLE" 18 1 "${ELEMENTS_LIQUID_ENABLE}" 18 35 35 0 \ "ELEMENTS_LIQUID_ENABLE" 18 1 "${ELEMENTS_LIQUID_ENABLE}" 18 35 35 0 \
# "ELEMENTS_REPO_BRANCH" 19 1 "${ELEMENTS_REPO_BRANCH}" 19 35 35 0 \ "ELEMENTS_REPO_BRANCH" 19 1 "${ELEMENTS_REPO_BRANCH}" 19 35 35 0 \
# "ELEMENTS_REPO_NAME" 20 1 "${ELEMENTS_REPO_NAME}" 20 35 35 0 \ "ELEMENTS_REPO_NAME" 20 1 "${ELEMENTS_REPO_NAME}" 20 35 35 0 \
# "ELEMENTS_REPO_URL" 21 1 "${ELEMENTS_REPO_URL}" 21 35 35 0 \ "ELEMENTS_REPO_URL" 21 1 "${ELEMENTS_REPO_URL}" 21 35 35 0 \
# "MEMPOOL_LATEST_RELEASE" 22 1 "${MEMPOOL_LATEST_RELEASE}" 22 35 35 0 \ "MEMPOOL_LATEST_RELEASE" 22 1 "${MEMPOOL_LATEST_RELEASE}" 22 35 35 0 \
# "MEMPOOL_LIQUID_HTTP_HOST" 23 1 "${MEMPOOL_LIQUID_HTTP_HOST}" 23 35 35 0 \ "MEMPOOL_LIQUID_HTTP_HOST" 23 1 "${MEMPOOL_LIQUID_HTTP_HOST}" 23 35 35 0 \
# "MEMPOOL_LIQUID_HTTP_PORT" 24 1 "${MEMPOOL_LIQUID_HTTP_PORT}" 24 35 35 0 \ "MEMPOOL_LIQUID_HTTP_PORT" 24 1 "${MEMPOOL_LIQUID_HTTP_PORT}" 24 35 35 0 \
# "MEMPOOL_MAINNET_HTTP_HOST" 25 1 "${MEMPOOL_MAINNET_HTTP_HOST}" 25 35 35 0 \ "MEMPOOL_MAINNET_HTTP_HOST" 25 1 "${MEMPOOL_MAINNET_HTTP_HOST}" 25 35 35 0 \
# "MEMPOOL_MAINNET_HTTP_PORT" 26 1 "${MEMPOOL_MAINNET_HTTP_PORT}" 26 35 35 0 \ "MEMPOOL_MAINNET_HTTP_PORT" 26 1 "${MEMPOOL_MAINNET_HTTP_PORT}" 26 35 35 0 \
# "MEMPOOL_REPO_BRANCH" 27 1 "${MEMPOOL_REPO_BRANCH}" 27 35 35 0 \ "MEMPOOL_REPO_BRANCH" 27 1 "${MEMPOOL_REPO_BRANCH}" 27 35 35 0 \
# "MEMPOOL_REPO_NAME" 28 1 "${MEMPOOL_REPO_NAME}" 28 35 35 0 \ "MEMPOOL_REPO_NAME" 28 1 "${MEMPOOL_REPO_NAME}" 28 35 35 0 \
# "MEMPOOL_REPO_URL" 29 1 "${MEMPOOL_REPO_URL}" 29 35 35 0 \ "MEMPOOL_REPO_URL" 29 1 "${MEMPOOL_REPO_URL}" 29 35 35 0 \
# "MEMPOOL_TESTNET_HTTP_HOST" 30 1 "${MEMPOOL_TESTNET_HTTP_HOST}" 30 35 35 0 \ "MEMPOOL_TESTNET_HTTP_HOST" 30 1 "${MEMPOOL_TESTNET_HTTP_HOST}" 30 35 35 0 \
# "MEMPOOL_TESTNET_HTTP_PORT" 31 1 "${MEMPOOL_TESTNET_HTTP_PORT}" 31 35 35 0 \ "MEMPOOL_TESTNET_HTTP_PORT" 31 1 "${MEMPOOL_TESTNET_HTTP_PORT}" 31 35 35 0 \
# "MEMPOOL_TOR_HS" 32 1 "${MEMPOOL_TOR_HS}" 32 35 35 0 \ "MEMPOOL_TOR_HS" 32 1 "${MEMPOOL_TOR_HS}" 32 35 35 0 \
# "HOSTNAME" 33 1 "${HOSTNAME}" 33 35 35 0 \ "HOSTNAME" 33 1 "${HOSTNAME}" 33 35 35 0 \
# "TOR_INSTALL" 34 1 "${TOR_INSTALL}" 34 35 35 0 \ "TOR_INSTALL" 34 1 "${TOR_INSTALL}" 34 35 35 0 \
# "CERTBOT_INSTALL" 35 1 "${CERTBOT_INSTALL}" 35 35 35 0 \ "CERTBOT_INSTALL" 35 1 "${CERTBOT_INSTALL}" 35 35 35 0 \
#2> $tempfile 2> $tempfile
#
#retval=$? retval=$?
#
#if [ $retval != $DIALOG_OK ];then if [ $retval != $DIALOG_OK ];then
# echo "Installation aborted." echo "Installation aborted."
# exit 1 exit 1
#fi fi
############################ ############################
# START DOING ACTUAL STUFF # # START DOING ACTUAL STUFF #
@@ -846,6 +841,8 @@ ext4CreateDir()
date date
echo "[*] Mempool installation script for ${OS}" echo "[*] Mempool installation script for ${OS}"
set -x
################################### ###################################
# create filesystems if necessary # # create filesystems if necessary #
################################### ###################################
@@ -909,7 +906,7 @@ echo "[*] Installing nvm.sh from GitHub"
osSudo "${MEMPOOL_USER}" sh -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh' osSudo "${MEMPOOL_USER}" sh -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh'
echo "[*] Building NodeJS via nvm.sh" echo "[*] Building NodeJS via nvm.sh"
osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm install v16.16.0 --shared-zlib' osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm install v16.15.0'
#################### ####################
# Tor installation # # Tor installation #
@@ -928,8 +925,6 @@ if [ "${TOR_INSTALL}" = ON ];then
osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServiceDir ${TOR_RESOURCES}/${MEMPOOL_TOR_HS}/ >> ${TOR_CONFIGURATION}" osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServiceDir ${TOR_RESOURCES}/${MEMPOOL_TOR_HS}/ >> ${TOR_CONFIGURATION}"
osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServicePort 80 127.0.0.1:81 >> ${TOR_CONFIGURATION}" osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServicePort 80 127.0.0.1:81 >> ${TOR_CONFIGURATION}"
osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServiceVersion 3 >> ${TOR_CONFIGURATION}" osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServiceVersion 3 >> ${TOR_CONFIGURATION}"
else
osSudo "${ROOT_USER}" sed -i.orig "s!__TOR_RESOURCES__!${TOR_RESOURCES}!" "${TOR_CONFIGURATION}"
fi fi
case $OS in case $OS in
@@ -941,7 +936,7 @@ if [ "${TOR_INSTALL}" = ON ];then
# start tor now so it can bootstrap in time for bitcoin starting a few mins later # start tor now so it can bootstrap in time for bitcoin starting a few mins later
echo "[*] Starting Tor service" echo "[*] Starting Tor service"
osSudo "${ROOT_USER}" service tor restart osSudo "${ROOT_USER}" service tor start
fi fi
######################## ########################
@@ -1287,25 +1282,7 @@ if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
;; ;;
Debian) Debian)
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/linux/elements-liquid.service" "${DEBIAN_SERVICE_HOME}" 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
fi
#######################################
# Bitcoin instance for Liquid Testnet #
#######################################
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/elements-liquidtestnet.service" "${DEBIAN_SERVICE_HOME}"
;; ;;
esac esac
fi fi
@@ -1318,6 +1295,18 @@ if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
echo "[*] Installing Bitcoin Mainnet electrs start script" echo "[*] Installing Bitcoin Mainnet electrs start script"
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"
case $OS in
FreeBSD)
echo [*] FIXME: must only crontab enabled daemons
osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.crontab"
osSudo "${ROOT_USER}" crontab -u "${MINFEE_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/minfee.crontab"
;;
Debian)
(crontab -l ; echo "@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet") | osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" -
;;
esac
echo "[*] Configuring Bitcoin Mainnet RPC credentials in electrs start script" 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"
@@ -1332,6 +1321,13 @@ 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"
@@ -1346,6 +1342,13 @@ 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"
@@ -1363,9 +1366,12 @@ if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
echo "[*] Installing Elements crontab" echo "[*] Installing Elements crontab"
case $OS in case $OS in
FreeBSD) FreeBSD)
echo "[*] FIXME: must only crontab enabled daemons" echo [*] FIXME: must only crontab enabled daemons
osSudo "${ROOT_USER}" crontab -u "${ELEMENTS_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/elements.crontab" 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 esac
echo "[*] Configuring Elements Liquid RPC credentials in electrs start script" echo "[*] Configuring Elements Liquid RPC credentials in electrs start script"
@@ -1382,6 +1388,13 @@ 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"
@@ -1394,45 +1407,6 @@ if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
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
################################
# Install all Electrs Cronjobs #
################################
echo "[*] Installing crontabs"
case $OS in
FreeBSD)
echo "[*] FIXME: must only crontab enabled daemons"
osSudo "${ROOT_USER}" crontab -u "${BITCOIN_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/bitcoin.crontab"
osSudo "${ROOT_USER}" crontab -u "${MINFEE_USER}" "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/minfee.crontab"
;;
Debian)
crontab_bitcoin=()
if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
echo "[*] Installing Electrs Mainnet Cronjob"
crontab_bitcoin+="@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet\n"
fi
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
echo "[*] Installing Electrs Testnet Cronjob"
crontab_bitcoin+="@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/electrs-start-testnet\n"
fi
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
echo "[*] Installing Electrs Signet Cronjob"
crontab_bitcoin+="@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/electrs-start-signet\n"
fi
echo "${crontab_bitcoin}" | crontab -u "${BITCOIN_USER}" -
crontab_elements=()
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
echo "[*] Installing Liquid Asset Mainnet Cronjob"
crontab_elements+="6 * * * * cd $HOME/asset_registry_db && git pull origin master >/dev/null 2>&1\n"
fi
if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
echo "[*] Installing Liquid Asset Testnet Cronjob"
crontab_elements+="6 * * * * cd $HOME/asset_registry_testnet_db && git pull origin master >/dev/null 2>&1\n"
fi
echo "${crontab_elements}" | crontab -u "${ELEMENTS_USER}" -
;;
esac
##################################### #####################################
# Bisq instance for Bitcoin Mainnet # # Bisq instance for Bitcoin Mainnet #
##################################### #####################################
@@ -1531,24 +1505,19 @@ _EOF_
##### nginx ##### nginx
echo "[*] Read tor v3 onion hostnames"
NGINX_MEMPOOL_ONION=$(cat "${TOR_RESOURCES}/mempool/hostname")
NGINX_BISQ_ONION=$(cat "${TOR_RESOURCES}/bisq/hostname")
NGINX_LIQUID_ONION=$(cat "${TOR_RESOURCES}/liquid/hostname")
echo "[*] Adding Nginx configuration" echo "[*] Adding 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}" case $OS in
mkdir -p /var/cache/nginx/services /var/cache/nginx/api
chown ${NGINX_USER}: /var/cache/nginx/services /var/cache/nginx/api FreeBSD)
ln -s /mempool/mempool /etc/nginx/mempool echo "[*] FIXME: nginx must be configured manually on FreeBSD"
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_USER__!${NGINX_USER}!" "${NGINX_CONFIGURATION}" ;;
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_ETC_FOLDER__!${NGINX_ETC_FOLDER}!" "${NGINX_CONFIGURATION}"
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_MEMPOOL_ONION__!${NGINX_MEMPOOL_ONION%.onion}!" "${NGINX_CONFIGURATION}" Debian)
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_BISQ_ONION__!${NGINX_BISQ_ONION%.onion}!" "${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}"
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_LIQUID_ONION__!${NGINX_LIQUID_ONIONi%.onion}!" "${NGINX_CONFIGURATION}" #echo "[*] Restarting Nginx"
echo "[*] Restarting Nginx" #osSudo "${ROOT_USER}" service nginx restart
osSudo "${ROOT_USER}" service nginx restart ;;
esac
##### OS systemd ##### OS systemd
@@ -1579,40 +1548,11 @@ case $OS in
osSudo "${ROOT_USER}" systemctl enable bisq.service osSudo "${ROOT_USER}" systemctl enable bisq.service
fi fi
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" systemctl enable elements-liquid.service osSudo "${ROOT_USER}" systemctl enable liquid.service
fi
if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" systemctl enable elements-liquidtestnet.service
fi fi
;; ;;
esac esac
##### OS set Linux user ulimits
echo "[*] Setting ulimits for users"
case $OS in
FreeBSD)
;;
Debian)
cat >> /etc/security/limits.conf <<EOF
* soft nproc 200000
* hard nproc 200000
* soft nofile 200000
* hard nofile 200000
EOF
echo "session required pam_limits.so" >> /etc/pam.d/common-session
;;
esac
##### Build Mempool
echo "[*] Build Mempool"
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME} && ./upgrade"
##### OS services ##### OS services
#if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then #if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
@@ -1688,21 +1628,6 @@ osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME} && ./upgrade"
##### finish ##### finish
case $OS in
FreeBSD)
;;
Debian)
echo "This are the generated Tor addresses:"
echo "${NGINX_MEMPOOL_ONION}"
echo "${NGINX_BISQ_ONION}"
echo "${NGINX_LIQUID_ONION}"
;;
esac
echo
echo 'Please reboot to start all the services.'
echo '[*] Done!' echo '[*] Done!'
exit 0 exit 0

View File

@@ -1,13 +1,13 @@
[Unit] [Unit]
Description=Elementsd-liquid Description=Elementsd
After=network.target After=network.target
[Service] [Service]
ExecStart=/usr/local/bin/elementsd -daemon -printtoconsole -chain=liquidv1 -pid=/elements/elements-liquid.pid ExecStart=/usr/local/bin/elementsd -daemon -printtoconsole -chain=liquidtestnet -pid=/elements/elements-testnet.pid
ExecStop=/usr/local/bin/elements-cli stop ExecStop=/usr/local/bin/elements-cli stop
Type=forking Type=forking
PIDFile=/elements/elements-liquid.pid PIDFile=/elements/elements-testnet.pid
Restart=on-failure Restart=on-failure
User=elements User=elements

View File

@@ -1,13 +1,13 @@
[Unit] [Unit]
Description=Elementsd-liquidtestnet Description=Elementsd
After=network.target After=network.target
[Service] [Service]
ExecStart=/usr/local/bin/elementsd -daemon -printtoconsole -chain=liquidtestnet -pid=/elements/elements-liquidtestnet.pid ExecStart=/usr/local/bin/elementsd -daemon -printtoconsole -chain=liquidv1 -pid=/elements/elements.pid
ExecStop=/usr/local/bin/elements-cli stop ExecStop=/usr/local/bin/elements-cli stop
Type=forking Type=forking
PIDFile=/elements/elements-liquidtestnet.pid PIDFile=/elements/elements.pid
Restart=on-failure Restart=on-failure
User=elements User=elements

View File

@@ -56,7 +56,7 @@ build_frontend()
if [ ! -e "mempool-frontend-config.json" ];then if [ ! -e "mempool-frontend-config.json" ];then
cp "${HOME}/mempool/production/mempool-frontend-config.${site}.json" "mempool-frontend-config.json" cp "${HOME}/mempool/production/mempool-frontend-config.${site}.json" "mempool-frontend-config.json"
fi fi
npm install --omit=dev --omit=optional || exit 1 npm install --no-optional || exit 1
npm run build || exit 1 npm run build || exit 1
} }
@@ -75,7 +75,7 @@ build_backend()
-e "s!__ELEMENTS_RPC_PASS__!${ELEMENTS_RPC_PASS}!" \ -e "s!__ELEMENTS_RPC_PASS__!${ELEMENTS_RPC_PASS}!" \
"mempool-config.json" "mempool-config.json"
fi fi
npm install --omit=dev --omit=optional || exit 1 npm install --no-optional || exit 1
npm run build || exit 1 npm run build || exit 1
} }

View File

@@ -1,4 +1,4 @@
user __NGINX_USER__; user nobody;
pid /var/run/nginx.pid; pid /var/run/nginx.pid;
worker_processes auto; worker_processes auto;
@@ -10,11 +10,11 @@ events {
} }
http { http {
# DNS servers for on-demand resolution, change if desired # DNS servers for on-demand recursive resolver
resolver 8.8.8.8; resolver 8.8.8.8;
# include default mime types # include default mime types
include __NGINX_ETC_FOLDER__/mime.types; include /usr/local/etc/nginx/mime.types;
default_type application/octet-stream; default_type application/octet-stream;
# HTTP basic configuration # HTTP basic configuration
@@ -32,13 +32,9 @@ http {
# MEMPOOL.NINJA # MEMPOOL.NINJA
server { server {
# clearnet v4/v6 # clearnet v4/v6
#listen 443 ssl http2; listen 443 ssl http2;
#listen [::]:443 ssl http2; listen [::]:443 ssl http2;
server_name _; server_name mempool.ninja;
# tor v3
listen 127.0.0.1:81;
set $onion "__NGINX_MEMPOOL_ONION__";
# for services from mempool.space like contributors on about page # for services from mempool.space like contributors on about page
set $mempoolSpaceServices "https://mempool.space"; set $mempoolSpaceServices "https://mempool.space";
@@ -56,24 +52,30 @@ http {
set $esploraTestnet "http://esplora-bitcoin-testnet"; set $esploraTestnet "http://esplora-bitcoin-testnet";
set $esploraSignet "http://esplora-bitcoin-signet"; set $esploraSignet "http://esplora-bitcoin-signet";
# tor v3
listen 127.0.0.1:81;
set $onion "mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad";
# filesystem paths # filesystem paths
root /mempool/public_html/mainnet/; root /mempool/public_html/mainnet/;
access_log /var/log/nginx/mempool-access.log; access_log /var/log/nginx/mempool-access.log;
error_log /var/log/nginx/mempool-error.log; error_log /var/log/nginx/mempool-error.log;
# ssl configuration
ssl_certificate /usr/local/etc/letsencrypt/live/mempool.ninja/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/mempool.ninja/privkey.pem;
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
# site configuration # site configuration
include mempool/production/nginx/server-mempool.conf; include mempool/production/nginx/server-mempool.conf;
} }
# BISQ.NINJA # BISQ.NINJA
server { server {
# clearnet v4/v6 # clearnet v4/v6
#listen 443 ssl http2; listen 443 ssl http2;
#listen [::]:443 ssl http2; listen [::]:443 ssl http2;
server_name _; server_name bisq.ninja;
# tor v3
listen 127.0.0.1:82;
set $onion "__NGINX_BISQ_ONION__";
# for services from mempool.space like contributors on about page # for services from mempool.space like contributors on about page
set $mempoolSpaceServices "https://mempool.space"; set $mempoolSpaceServices "https://mempool.space";
@@ -84,24 +86,30 @@ http {
# for blockstream/esplora daemon, see upstream-esplora.conf # for blockstream/esplora daemon, see upstream-esplora.conf
set $esploraMainnet "http://esplora-bitcoin-mainnet"; set $esploraMainnet "http://esplora-bitcoin-mainnet";
# tor v3
listen 127.0.0.1:82;
set $onion "bisqmktse2cabavbr2xjq7xw3h6g5ottemo5rolfcwt6aly6tp5fdryd";
# filesystem paths # filesystem paths
root /mempool/public_html/bisq/; root /mempool/public_html/bisq/;
access_log /var/log/nginx/bisq-access.log; access_log /var/log/nginx/bisq-access.log;
error_log /var/log/nginx/bisq-error.log; error_log /var/log/nginx/bisq-error.log;
# ssl configuration
ssl_certificate /usr/local/etc/letsencrypt/live/bisq.ninja/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/bisq.ninja/privkey.pem;
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
# site configuration # site configuration
include mempool/production/nginx/server-bisq.conf; include mempool/production/nginx/server-bisq.conf;
} }
# LIQUID.PLACE # LIQUID.PLACE
server { server {
# clearnet v4/v6 # clearnet v4/v6
#listen 443 ssl http2; listen 443 ssl http2;
#listen [::]:443 ssl http2; listen [::]:443 ssl http2;
server_name _; server_name liquid.place;
# tor v3
listen 127.0.0.1:83;
set $onion "__NGINX_LIQUID_ONION__";
# for services from mempool.space like contributors on about page # for services from mempool.space like contributors on about page
set $mempoolSpaceServices "https://mempool.space"; set $mempoolSpaceServices "https://mempool.space";
@@ -114,12 +122,36 @@ http {
set $esploraMainnet "http://esplora-liquid-mainnet"; set $esploraMainnet "http://esplora-liquid-mainnet";
set $esploraTestnet "http://esplora-liquid-testnet"; set $esploraTestnet "http://esplora-liquid-testnet";
# tor v3
listen 127.0.0.1:83;
set $onion "liquidmom47f6s3m53ebfxn47p76a6tlnxib3wp6deux7wuzotdr6cyd";
# filesystem paths # filesystem paths
root /mempool/public_html/liquid/; root /mempool/public_html/liquid/;
access_log /var/log/nginx/liquid-access.log; access_log /var/log/nginx/liquid-access.log;
error_log /var/log/nginx/liquid-error.log; error_log /var/log/nginx/liquid-error.log;
# ssl configuration
ssl_certificate /usr/local/etc/letsencrypt/live/liquid.place/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/liquid.place/privkey.pem;
include /usr/local/etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /usr/local/etc/letsencrypt/ssl-dhparams.pem;
# site configuration # site configuration
include mempool/production/nginx/server-liquid.conf; include mempool/production/nginx/server-liquid.conf;
} }
# HTTP to HTTPS redirect
server {
# clearnet v4/v6
listen 80;
listen [::]:80;
server_name _;
# only redirect for our hosted domains
if ($host ~ "^(mempool.ninja|bisq.ninja|liquid.place)$")
{
return 301 https://$host$request_uri;
}
return 503;
}
} }

View File

@@ -3,21 +3,21 @@ SOCKSPort 9050
ControlPort 9051 ControlPort 9051
Log notice syslog Log notice syslog
DataDirectory __TOR_RESOURCES__ DataDirectory /var/db/tor
DataDirectoryGroupReadable 1 DataDirectoryGroupReadable 1
CookieAuthentication 1 CookieAuthentication 1
CookieAuthFile __TOR_RESOURCES__/control_auth_cookie CookieAuthFile /var/db/tor/control_auth_cookie
CookieAuthFileGroupReadable 1 CookieAuthFileGroupReadable 1
HiddenServiceDir __TOR_RESOURCES__/mempool HiddenServiceDir /var/db/tor/mempool
HiddenServicePort 80 127.0.0.1:81 HiddenServicePort 80 127.0.0.1:81
HiddenServiceVersion 3 HiddenServiceVersion 3
HiddenServiceDir __TOR_RESOURCES__/bisq HiddenServiceDir /var/db/tor/bisq
HiddenServicePort 80 127.0.0.1:82 HiddenServicePort 80 127.0.0.1:82
HiddenServiceVersion 3 HiddenServiceVersion 3
HiddenServiceDir __TOR_RESOURCES__/liquid HiddenServiceDir /var/db/tor/liquid
HiddenServicePort 80 127.0.0.1:83 HiddenServicePort 80 127.0.0.1:83
HiddenServiceVersion 3 HiddenServiceVersion 3