Merge branch 'master' into nymkappa/clip-label-overflow

This commit is contained in:
nymkappa 2023-04-22 20:10:27 +02:00 committed by GitHub
commit 99af881cdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 5058 additions and 2508 deletions

View File

@ -43,7 +43,9 @@
"TLS_ENABLED": true "TLS_ENABLED": true
}, },
"ESPLORA": { "ESPLORA": {
"REST_API_URL": "http://127.0.0.1:3000" "REST_API_URL": "http://127.0.0.1:3000",
"UNIX_SOCKET_PATH": "/tmp/esplora-bitcoin-mainnet",
"RETRY_UNIX_SOCKET_AFTER": 30000
}, },
"SECOND_CORE_RPC": { "SECOND_CORE_RPC": {
"HOST": "127.0.0.1", "HOST": "127.0.0.1",

6613
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -34,35 +34,35 @@
"prettier": "./node_modules/.bin/prettier --write \"src/**/*.{js,ts}\"" "prettier": "./node_modules/.bin/prettier --write \"src/**/*.{js,ts}\""
}, },
"dependencies": { "dependencies": {
"@babel/core": "^7.20.12", "@babel/core": "^7.21.3",
"@mempool/electrum-client": "1.1.9", "@mempool/electrum-client": "1.1.9",
"@types/node": "^16.18.11", "@types/node": "^18.15.3",
"axios": "~0.27.2", "axios": "~0.27.2",
"bitcoinjs-lib": "~6.1.0", "bitcoinjs-lib": "~6.1.0",
"crypto-js": "~4.1.1", "crypto-js": "~4.1.1",
"express": "~4.18.2", "express": "~4.18.2",
"maxmind": "~4.3.8", "maxmind": "~4.3.8",
"mysql2": "~2.3.3", "mysql2": "~3.2.0",
"node-worker-threads-pool": "~1.5.1", "node-worker-threads-pool": "~1.5.1",
"socks-proxy-agent": "~7.0.0", "socks-proxy-agent": "~7.0.0",
"typescript": "~4.7.4", "typescript": "~4.7.4",
"ws": "~8.11.0" "ws": "~8.13.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.7", "@babel/core": "^7.21.3",
"@babel/code-frame": "^7.18.6", "@babel/code-frame": "^7.18.6",
"@types/compression": "^1.7.2", "@types/compression": "^1.7.2",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/express": "^4.17.15", "@types/express": "^4.17.15",
"@types/jest": "^29.2.5", "@types/jest": "^29.5.0",
"@types/ws": "~8.5.4", "@types/ws": "~8.5.4",
"@typescript-eslint/eslint-plugin": "^5.48.1", "@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.48.1", "@typescript-eslint/parser": "^5.55.0",
"eslint": "^8.31.0", "eslint": "^8.36.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.7.0",
"jest": "^29.3.1", "jest": "^29.5.0",
"prettier": "^2.8.2", "prettier": "^2.8.4",
"ts-jest": "^29.0.3", "ts-jest": "^29.0.5",
"ts-node": "^10.9.1" "ts-node": "^10.9.1"
} }
} }

View File

@ -44,7 +44,9 @@
"TLS_ENABLED": true "TLS_ENABLED": true
}, },
"ESPLORA": { "ESPLORA": {
"REST_API_URL": "__ESPLORA_REST_API_URL__" "REST_API_URL": "__ESPLORA_REST_API_URL__",
"UNIX_SOCKET_PATH": "__ESPLORA_UNIX_SOCKET_PATH__",
"RETRY_UNIX_SOCKET_AFTER": "__ESPLORA_RETRY_UNIX_SOCKET_AFTER__"
}, },
"SECOND_CORE_RPC": { "SECOND_CORE_RPC": {
"HOST": "__SECOND_CORE_RPC_HOST__", "HOST": "__SECOND_CORE_RPC_HOST__",

View File

@ -47,7 +47,7 @@ describe('Mempool Backend Config', () => {
expect(config.ELECTRUM).toStrictEqual({ HOST: '127.0.0.1', PORT: 3306, TLS_ENABLED: true }); expect(config.ELECTRUM).toStrictEqual({ HOST: '127.0.0.1', PORT: 3306, TLS_ENABLED: true });
expect(config.ESPLORA).toStrictEqual({ REST_API_URL: 'http://127.0.0.1:3000' }); expect(config.ESPLORA).toStrictEqual({ REST_API_URL: 'http://127.0.0.1:3000', UNIX_SOCKET_PATH: null, RETRY_UNIX_SOCKET_AFTER: 30000 });
expect(config.CORE_RPC).toStrictEqual({ expect(config.CORE_RPC).toStrictEqual({
HOST: '127.0.0.1', HOST: '127.0.0.1',

View File

@ -3,65 +3,102 @@ import axios, { AxiosRequestConfig } from 'axios';
import http from 'http'; import http from 'http';
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory'; import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
import { IEsploraApi } from './esplora-api.interface'; import { IEsploraApi } from './esplora-api.interface';
import logger from '../../logger';
const axiosConnection = axios.create({ const axiosConnection = axios.create({
httpAgent: new http.Agent({ keepAlive: true }) httpAgent: new http.Agent({ keepAlive: true, })
}); });
class ElectrsApi implements AbstractBitcoinApi { class ElectrsApi implements AbstractBitcoinApi {
axiosConfig: AxiosRequestConfig = { private axiosConfigWithUnixSocket: AxiosRequestConfig = config.ESPLORA.UNIX_SOCKET_PATH ? {
socketPath: config.ESPLORA.UNIX_SOCKET_PATH,
timeout: 10000,
} : {
timeout: 10000,
};
private axiosConfigTcpSocketOnly: AxiosRequestConfig = {
timeout: 10000, timeout: 10000,
}; };
constructor() { } unixSocketRetryTimeout;
activeAxiosConfig;
constructor() {
this.activeAxiosConfig = this.axiosConfigWithUnixSocket;
}
fallbackToTcpSocket() {
if (!this.unixSocketRetryTimeout) {
logger.err(`Unable to connect to esplora unix socket. Falling back to tcp socket. Retrying unix socket in ${config.ESPLORA.RETRY_UNIX_SOCKET_AFTER / 1000} seconds`);
// Retry the unix socket after a few seconds
this.unixSocketRetryTimeout = setTimeout(() => {
logger.info(`Retrying to use unix socket for esplora now (applied for the next query)`);
this.activeAxiosConfig = this.axiosConfigWithUnixSocket;
this.unixSocketRetryTimeout = undefined;
}, config.ESPLORA.RETRY_UNIX_SOCKET_AFTER);
}
// Use the TCP socket (reach a different esplora instance through nginx)
this.activeAxiosConfig = this.axiosConfigTcpSocketOnly;
}
$queryWrapper<T>(url, responseType = 'json'): Promise<T> {
return axiosConnection.get<T>(url, { ...this.activeAxiosConfig, responseType: responseType })
.then((response) => response.data)
.catch((e) => {
if (e?.code === 'ECONNREFUSED') {
this.fallbackToTcpSocket();
// Retry immediately
return axiosConnection.get<T>(url, this.activeAxiosConfig)
.then((response) => response.data)
.catch((e) => {
logger.warn(`Cannot query esplora through the unix socket nor the tcp socket. Exception ${e}`);
throw e;
});
} else {
throw e;
}
});
}
$getRawMempool(): Promise<IEsploraApi.Transaction['txid'][]> { $getRawMempool(): Promise<IEsploraApi.Transaction['txid'][]> {
return axiosConnection.get<IEsploraApi.Transaction['txid'][]>(config.ESPLORA.REST_API_URL + '/mempool/txids', this.axiosConfig) return this.$queryWrapper<IEsploraApi.Transaction['txid'][]>(config.ESPLORA.REST_API_URL + '/mempool/txids');
.then((response) => response.data);
} }
$getRawTransaction(txId: string): Promise<IEsploraApi.Transaction> { $getRawTransaction(txId: string): Promise<IEsploraApi.Transaction> {
return axiosConnection.get<IEsploraApi.Transaction>(config.ESPLORA.REST_API_URL + '/tx/' + txId, this.axiosConfig) return this.$queryWrapper<IEsploraApi.Transaction>(config.ESPLORA.REST_API_URL + '/tx/' + txId);
.then((response) => response.data);
} }
$getTransactionHex(txId: string): Promise<string> { $getTransactionHex(txId: string): Promise<string> {
return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/hex', this.axiosConfig) return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/hex');
.then((response) => response.data);
} }
$getBlockHeightTip(): Promise<number> { $getBlockHeightTip(): Promise<number> {
return axiosConnection.get<number>(config.ESPLORA.REST_API_URL + '/blocks/tip/height', this.axiosConfig) return this.$queryWrapper<number>(config.ESPLORA.REST_API_URL + '/blocks/tip/height');
.then((response) => response.data);
} }
$getBlockHashTip(): Promise<string> { $getBlockHashTip(): Promise<string> {
return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/blocks/tip/hash', this.axiosConfig) return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/blocks/tip/hash');
.then((response) => response.data);
} }
$getTxIdsForBlock(hash: string): Promise<string[]> { $getTxIdsForBlock(hash: string): Promise<string[]> {
return axiosConnection.get<string[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids', this.axiosConfig) return this.$queryWrapper<string[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids');
.then((response) => response.data);
} }
$getBlockHash(height: number): Promise<string> { $getBlockHash(height: number): Promise<string> {
return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/block-height/' + height, this.axiosConfig) return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/block-height/' + height);
.then((response) => response.data);
} }
$getBlockHeader(hash: string): Promise<string> { $getBlockHeader(hash: string): Promise<string> {
return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/header', this.axiosConfig) return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/header');
.then((response) => response.data);
} }
$getBlock(hash: string): Promise<IEsploraApi.Block> { $getBlock(hash: string): Promise<IEsploraApi.Block> {
return axiosConnection.get<IEsploraApi.Block>(config.ESPLORA.REST_API_URL + '/block/' + hash, this.axiosConfig) return this.$queryWrapper<IEsploraApi.Block>(config.ESPLORA.REST_API_URL + '/block/' + hash);
.then((response) => response.data);
} }
$getRawBlock(hash: string): Promise<Buffer> { $getRawBlock(hash: string): Promise<Buffer> {
return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + "/raw", { ...this.axiosConfig, responseType: 'arraybuffer' }) return this.$queryWrapper<any>(config.ESPLORA.REST_API_URL + '/block/' + hash + "/raw", 'arraybuffer')
.then((response) => { return Buffer.from(response.data); }); .then((response) => { return Buffer.from(response.data); });
} }
@ -82,13 +119,11 @@ class ElectrsApi implements AbstractBitcoinApi {
} }
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> { $getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
return axiosConnection.get<IEsploraApi.Outspend>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspend/' + vout, this.axiosConfig) return this.$queryWrapper<IEsploraApi.Outspend>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspend/' + vout);
.then((response) => response.data);
} }
$getOutspends(txId: string): Promise<IEsploraApi.Outspend[]> { $getOutspends(txId: string): Promise<IEsploraApi.Outspend[]> {
return axiosConnection.get<IEsploraApi.Outspend[]>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspends', this.axiosConfig) return this.$queryWrapper<IEsploraApi.Outspend[]>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspends');
.then((response) => response.data);
} }
async $getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]> { async $getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]> {

View File

@ -410,12 +410,13 @@ class Blocks {
try { try {
// Get all indexed block hash // Get all indexed block hash
const unindexedBlockHeights = await blocksRepository.$getCPFPUnindexedBlocks(); const unindexedBlockHeights = await blocksRepository.$getCPFPUnindexedBlocks();
logger.info(`Indexing cpfp data for ${unindexedBlockHeights.length} blocks`);
if (!unindexedBlockHeights?.length) { if (!unindexedBlockHeights?.length) {
return; return;
} }
logger.info(`Indexing cpfp data for ${unindexedBlockHeights.length} blocks`);
// Logging // Logging
let count = 0; let count = 0;
let countThisRun = 0; let countThisRun = 0;
@ -589,11 +590,10 @@ class Blocks {
if (!fastForwarded) { if (!fastForwarded) {
const lastBlock = await blocksRepository.$getBlockByHeight(blockExtended.height - 1); const lastBlock = await blocksRepository.$getBlockByHeight(blockExtended.height - 1);
if (lastBlock !== null && blockExtended.previousblockhash !== lastBlock.id) { if (lastBlock !== null && blockExtended.previousblockhash !== lastBlock.id) {
logger.warn(`Chain divergence detected at block ${lastBlock.height}, re-indexing most recent data`); logger.warn(`Chain divergence detected at block ${lastBlock.height}, re-indexing most recent data`, logger.tags.mining);
// We assume there won't be a reorg with more than 10 block depth // We assume there won't be a reorg with more than 10 block depth
await BlocksRepository.$deleteBlocksFrom(lastBlock.height - 10); await BlocksRepository.$deleteBlocksFrom(lastBlock.height - 10);
await HashratesRepository.$deleteLastEntries(); await HashratesRepository.$deleteLastEntries();
await BlocksSummariesRepository.$deleteBlocksFrom(lastBlock.height - 10);
await cpfpRepository.$deleteClustersFrom(lastBlock.height - 10); await cpfpRepository.$deleteClustersFrom(lastBlock.height - 10);
for (let i = 10; i >= 0; --i) { for (let i = 10; i >= 0; --i) {
const newBlock = await this.$indexBlock(lastBlock.height - i); const newBlock = await this.$indexBlock(lastBlock.height - i);
@ -604,7 +604,7 @@ class Blocks {
} }
await mining.$indexDifficultyAdjustments(); await mining.$indexDifficultyAdjustments();
await DifficultyAdjustmentsRepository.$deleteLastAdjustment(); await DifficultyAdjustmentsRepository.$deleteLastAdjustment();
logger.info(`Re-indexed 10 blocks and summaries. Also re-indexed the last difficulty adjustments. Will re-index latest hashrates in a few seconds.`); logger.info(`Re-indexed 10 blocks and summaries. Also re-indexed the last difficulty adjustments. Will re-index latest hashrates in a few seconds.`, logger.tags.mining);
indexer.reindex(); indexer.reindex();
} }
await blocksRepository.$saveBlockInDatabase(blockExtended); await blocksRepository.$saveBlockInDatabase(blockExtended);
@ -616,7 +616,7 @@ class Blocks {
priceId: lastestPriceId, priceId: lastestPriceId,
}]); }]);
} else { } else {
logger.info(`Cannot save block price for ${blockExtended.height} because the price updater hasnt completed yet. Trying again in 10 seconds.`, logger.tags.mining); logger.debug(`Cannot save block price for ${blockExtended.height} because the price updater hasnt completed yet. Trying again in 10 seconds.`, logger.tags.mining);
setTimeout(() => { setTimeout(() => {
indexer.runSingleTask('blocksPrices'); indexer.runSingleTask('blocksPrices');
}, 10000); }, 10000);
@ -736,7 +736,7 @@ class Blocks {
// Index the response if needed // Index the response if needed
if (Common.blocksSummariesIndexingEnabled() === true) { if (Common.blocksSummariesIndexingEnabled() === true) {
await BlocksSummariesRepository.$saveSummary({height: block.height, mined: summary}); await BlocksSummariesRepository.$saveTransactions(block.height, block.hash, summary.transactions);
} }
return summary.transactions; return summary.transactions;
@ -852,11 +852,12 @@ class Blocks {
if (cleanBlock.fee_amt_percentiles === null) { if (cleanBlock.fee_amt_percentiles === null) {
const block = await bitcoinClient.getBlock(cleanBlock.hash, 2); const block = await bitcoinClient.getBlock(cleanBlock.hash, 2);
const summary = this.summarizeBlock(block); const summary = this.summarizeBlock(block);
await BlocksSummariesRepository.$saveSummary({ height: block.height, mined: summary }); await BlocksSummariesRepository.$saveTransactions(cleanBlock.height, cleanBlock.hash, summary.transactions);
cleanBlock.fee_amt_percentiles = await BlocksSummariesRepository.$getFeePercentilesByBlockId(cleanBlock.hash); cleanBlock.fee_amt_percentiles = await BlocksSummariesRepository.$getFeePercentilesByBlockId(cleanBlock.hash);
} }
if (cleanBlock.fee_amt_percentiles !== null) { if (cleanBlock.fee_amt_percentiles !== null) {
cleanBlock.median_fee_amt = cleanBlock.fee_amt_percentiles[3]; cleanBlock.median_fee_amt = cleanBlock.fee_amt_percentiles[3];
await blocksRepository.$updateFeeAmounts(cleanBlock.hash, cleanBlock.fee_amt_percentiles, cleanBlock.median_fee_amt);
} }
} }

View File

@ -164,7 +164,7 @@ class DiskCache {
} }
} }
} catch (e) { } catch (e) {
logger.info('Error parsing ' + fileName + '. Skipping. Reason: ' + (e instanceof Error ? e.message : e)); logger.err('Error parsing ' + fileName + '. Skipping. Reason: ' + (e instanceof Error ? e.message : e));
} }
} }

View File

@ -2,7 +2,7 @@ import * as fs from 'fs';
import logger from '../../logger'; import logger from '../../logger';
class Icons { class Icons {
private static FILE_NAME = './icons.json'; private static FILE_NAME = '/elements/asset_registry_db/icons.json';
private iconIds: string[] = []; private iconIds: string[] = [];
private icons: { [assetId: string]: string; } = {}; private icons: { [assetId: string]: string; } = {};

View File

@ -452,7 +452,7 @@ class Mining {
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 > 5) { if (elapsedSeconds > 5) {
const progress = Math.round(totalBlockChecked / blocks.length * 100); const progress = Math.round(totalBlockChecked / blocks.length * 100);
logger.info(`Indexing difficulty adjustment at block #${block.height} | Progress: ${progress}%`, logger.tags.mining); logger.debug(`Indexing difficulty adjustment at block #${block.height} | Progress: ${progress}%`, logger.tags.mining);
timer = new Date().getTime() / 1000; timer = new Date().getTime() / 1000;
} }
} }
@ -558,8 +558,10 @@ class Mining {
currentBlockHeight -= 10000; currentBlockHeight -= 10000;
} }
if (totalIndexed) { if (totalIndexed > 0) {
logger.info(`Indexing missing coinstatsindex data completed`, logger.tags.mining); logger.info(`Indexing missing coinstatsindex data completed. Indexed ${totalIndexed}`, logger.tags.mining);
} else {
logger.debug(`Indexing missing coinstatsindex data completed. Indexed 0.`, logger.tags.mining);
} }
} }

View File

@ -107,7 +107,7 @@ function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
if (nextTx && !nextTx?.used) { if (nextTx && !nextTx?.used) {
// Check if the package fits into this block // Check if the package fits into this block
if (blockWeight + nextTx.ancestorWeight < config.MEMPOOL.BLOCK_WEIGHT_UNITS) { if (blocks.length >= 7 || (blockWeight + nextTx.ancestorWeight < config.MEMPOOL.BLOCK_WEIGHT_UNITS)) {
const ancestors: AuditTransaction[] = Array.from(nextTx.ancestorMap.values()); const ancestors: AuditTransaction[] = Array.from(nextTx.ancestorMap.values());
// sort ancestors by dependency graph (equivalent to sorting by ascending ancestor count) // sort ancestors by dependency graph (equivalent to sorting by ascending ancestor count)
const sortedTxSet = [...ancestors.sort((a, b) => { return (a.ancestorMap.size || 0) - (b.ancestorMap.size || 0); }), nextTx]; const sortedTxSet = [...ancestors.sort((a, b) => { return (a.ancestorMap.size || 0) - (b.ancestorMap.size || 0); }), nextTx];
@ -175,34 +175,14 @@ function makeBlockTemplates(mempool: { [txid: string]: ThreadTransaction })
overflow = []; overflow = [];
} }
} }
// pack any leftover transactions into the last block
for (const tx of overflow) { if (overflow.length > 0) {
if (!tx || tx?.used) { logger.warn('GBT overflow list unexpectedly non-empty after final block constructed');
continue;
}
blockWeight += tx.weight;
const mempoolTx = mempool[tx.txid];
// update original copy of this tx with effective fee rate & relatives data
mempoolTx.effectiveFeePerVsize = tx.score;
if (tx.ancestorMap.size > 0) {
cpfpClusters[tx.txid] = Array.from(tx.ancestorMap?.values()).map(a => a.txid);
mempoolTx.cpfpRoot = tx.txid;
}
mempoolTx.cpfpChecked = true;
transactions.push(tx);
tx.used = true;
} }
const blockTransactions = transactions.map(t => mempool[t.txid]); // add the final unbounded block if it contains any transactions
restOfArray.forEach(tx => { if (transactions.length > 0) {
blockWeight += tx.weight; blocks.push(transactions.map(t => mempool[t.txid]));
tx.effectiveFeePerVsize = tx.feePerVsize;
tx.cpfpChecked = false;
blockTransactions.push(tx);
});
if (blockTransactions.length) {
blocks.push(blockTransactions);
} }
transactions = [];
const end = Date.now(); const end = Date.now();
const time = end - start; const time = end - start;

View File

@ -37,6 +37,8 @@ interface IConfig {
}; };
ESPLORA: { ESPLORA: {
REST_API_URL: string; REST_API_URL: string;
UNIX_SOCKET_PATH: string | void | null;
RETRY_UNIX_SOCKET_AFTER: number;
}; };
LIGHTNING: { LIGHTNING: {
ENABLED: boolean; ENABLED: boolean;
@ -163,6 +165,8 @@ const defaults: IConfig = {
}, },
'ESPLORA': { 'ESPLORA': {
'REST_API_URL': 'http://127.0.0.1:3000', 'REST_API_URL': 'http://127.0.0.1:3000',
'UNIX_SOCKET_PATH': null,
'RETRY_UNIX_SOCKET_AFTER': 30000,
}, },
'ELECTRUM': { 'ELECTRUM': {
'HOST': '127.0.0.1', 'HOST': '127.0.0.1',

View File

@ -45,7 +45,8 @@ class Server {
private wss: WebSocket.Server | undefined; private wss: WebSocket.Server | undefined;
private server: http.Server | undefined; private server: http.Server | undefined;
private app: Application; private app: Application;
private currentBackendRetryInterval = 5; private currentBackendRetryInterval = 1;
private backendRetryCount = 0;
private maxHeapSize: number = 0; private maxHeapSize: number = 0;
private heapLogInterval: number = 60; private heapLogInterval: number = 60;
@ -184,17 +185,17 @@ class Server {
indexer.$run(); indexer.$run();
setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS); setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS);
this.currentBackendRetryInterval = 5; this.backendRetryCount = 0;
} catch (e: any) { } catch (e: any) {
let loggerMsg = `Exception in runMainUpdateLoop(). Retrying in ${this.currentBackendRetryInterval} sec.`; this.backendRetryCount++;
let loggerMsg = `Exception in runMainUpdateLoop() (count: ${this.backendRetryCount}). Retrying in ${this.currentBackendRetryInterval} sec.`;
loggerMsg += ` Reason: ${(e instanceof Error ? e.message : e)}.`; loggerMsg += ` Reason: ${(e instanceof Error ? e.message : e)}.`;
if (e?.stack) { if (e?.stack) {
loggerMsg += ` Stack trace: ${e.stack}`; loggerMsg += ` Stack trace: ${e.stack}`;
} }
// When we get a first Exception, only `logger.debug` it and retry after 5 seconds // When we get a first Exception, only `logger.debug` it and retry after 5 seconds
// From the second Exception, `logger.warn` the Exception and increase the retry delay // From the second Exception, `logger.warn` the Exception and increase the retry delay
// Maximum retry delay is 60 seconds if (this.backendRetryCount >= 5) {
if (this.currentBackendRetryInterval > 5) {
logger.warn(loggerMsg); logger.warn(loggerMsg);
mempool.setOutOfSync(); mempool.setOutOfSync();
} else { } else {
@ -204,8 +205,6 @@ class Server {
logger.debug(`AxiosError: ${e?.message}`); logger.debug(`AxiosError: ${e?.message}`);
} }
setTimeout(this.runMainUpdateLoop.bind(this), 1000 * this.currentBackendRetryInterval); setTimeout(this.runMainUpdateLoop.bind(this), 1000 * this.currentBackendRetryInterval);
this.currentBackendRetryInterval *= 2;
this.currentBackendRetryInterval = Math.min(this.currentBackendRetryInterval, 60);
} }
} }
@ -276,7 +275,7 @@ class Server {
if (!this.warnedHeapCritical && this.maxHeapSize > warnThreshold) { if (!this.warnedHeapCritical && this.maxHeapSize > warnThreshold) {
this.warnedHeapCritical = true; this.warnedHeapCritical = true;
logger.warn(`Used ${(this.maxHeapSize / stats.heap_size_limit).toFixed(2)}% of heap limit (${formatBytes(this.maxHeapSize, byteUnits, true)} / ${formatBytes(stats.heap_size_limit, byteUnits)})!`); logger.warn(`Used ${(this.maxHeapSize / stats.heap_size_limit * 100).toFixed(2)}% of heap limit (${formatBytes(this.maxHeapSize, byteUnits, true)} / ${formatBytes(stats.heap_size_limit, byteUnits)})!`);
} }
if (this.lastHeapLogTime === null || (now - this.lastHeapLogTime) > (this.heapLogInterval * 1000)) { if (this.lastHeapLogTime === null || (now - this.lastHeapLogTime) > (this.heapLogInterval * 1000)) {
logger.debug(`Memory usage: ${formatBytes(this.maxHeapSize, byteUnits)} / ${formatBytes(stats.heap_size_limit, byteUnits)}`); logger.debug(`Memory usage: ${formatBytes(this.maxHeapSize, byteUnits)} / ${formatBytes(stats.heap_size_limit, byteUnits)}`);

View File

@ -13,6 +13,48 @@ import chainTips from '../api/chain-tips';
import blocks from '../api/blocks'; import blocks from '../api/blocks';
import BlocksAuditsRepository from './BlocksAuditsRepository'; import BlocksAuditsRepository from './BlocksAuditsRepository';
interface DatabaseBlock {
id: string;
height: number;
version: number;
timestamp: number;
bits: number;
nonce: number;
difficulty: number;
merkle_root: string;
tx_count: number;
size: number;
weight: number;
previousblockhash: string;
mediantime: number;
totalFees: number;
medianFee: number;
feeRange: string;
reward: number;
poolId: number;
poolName: string;
poolSlug: string;
avgFee: number;
avgFeeRate: number;
coinbaseRaw: string;
coinbaseAddress: string;
coinbaseSignature: string;
coinbaseSignatureAscii: string;
avgTxSize: number;
totalInputs: number;
totalOutputs: number;
totalOutputAmt: number;
medianFeeAmt: number;
feePercentiles: string;
segwitTotalTxs: number;
segwitTotalSize: number;
segwitTotalWeight: number;
header: string;
utxoSetChange: number;
utxoSetSize: number;
totalInputAmt: number;
}
const BLOCK_DB_FIELDS = ` const BLOCK_DB_FIELDS = `
blocks.hash AS id, blocks.hash AS id,
blocks.height, blocks.height,
@ -52,7 +94,7 @@ const BLOCK_DB_FIELDS = `
blocks.header, blocks.header,
blocks.utxoset_change AS utxoSetChange, blocks.utxoset_change AS utxoSetChange,
blocks.utxoset_size AS utxoSetSize, blocks.utxoset_size AS utxoSetSize,
blocks.total_input_amt AS totalInputAmts blocks.total_input_amt AS totalInputAmt
`; `;
class BlocksRepository { class BlocksRepository {
@ -171,6 +213,32 @@ class BlocksRepository {
} }
} }
/**
* Update missing fee amounts fields
*
* @param blockHash
* @param feeAmtPercentiles
* @param medianFeeAmt
*/
public async $updateFeeAmounts(blockHash: string, feeAmtPercentiles, medianFeeAmt) : Promise<void> {
try {
const query = `
UPDATE blocks
SET fee_percentiles = ?, median_fee_amt = ?
WHERE hash = ?
`;
const params: any[] = [
JSON.stringify(feeAmtPercentiles),
medianFeeAmt,
blockHash
];
await DB.query(query, params);
} catch (e: any) {
logger.err(`Cannot update fee amounts for block ${blockHash}. Reason: ' + ${e instanceof Error ? e.message : e}`);
throw e;
}
}
/** /**
* Get all block height that have not been indexed between [startHeight, endHeight] * Get all block height that have not been indexed between [startHeight, endHeight]
*/ */
@ -432,7 +500,7 @@ class BlocksRepository {
const blocks: BlockExtended[] = []; const blocks: BlockExtended[] = [];
for (const block of rows) { for (const block of rows) {
blocks.push(await this.formatDbBlockIntoExtendedBlock(block)); blocks.push(await this.formatDbBlockIntoExtendedBlock(block as DatabaseBlock));
} }
return blocks; return blocks;
@ -459,37 +527,13 @@ class BlocksRepository {
return null; return null;
} }
return await this.formatDbBlockIntoExtendedBlock(rows[0]); return await this.formatDbBlockIntoExtendedBlock(rows[0] as DatabaseBlock);
} catch (e) { } catch (e) {
logger.err(`Cannot get indexed block ${height}. Reason: ` + (e instanceof Error ? e.message : e)); logger.err(`Cannot get indexed block ${height}. Reason: ` + (e instanceof Error ? e.message : e));
throw e; throw e;
} }
} }
/**
* Get one block by hash
*/
public async $getBlockByHash(hash: string): Promise<object | null> {
try {
const query = `
SELECT ${BLOCK_DB_FIELDS}
FROM blocks
JOIN pools ON blocks.pool_id = pools.id
WHERE hash = ?;
`;
const [rows]: any[] = await DB.query(query, [hash]);
if (rows.length <= 0) {
return null;
}
return await this.formatDbBlockIntoExtendedBlock(rows[0]);
} catch (e) {
logger.err(`Cannot get indexed block ${hash}. Reason: ` + (e instanceof Error ? e.message : e));
throw e;
}
}
/** /**
* Return blocks difficulty * Return blocks difficulty
*/ */
@ -599,7 +643,6 @@ class BlocksRepository {
if (blocks[idx].previous_block_hash !== blocks[idx - 1].hash) { if (blocks[idx].previous_block_hash !== blocks[idx - 1].hash) {
logger.warn(`Chain divergence detected at block ${blocks[idx - 1].height}`); logger.warn(`Chain divergence detected at block ${blocks[idx - 1].height}`);
await this.$deleteBlocksFrom(blocks[idx - 1].height); await this.$deleteBlocksFrom(blocks[idx - 1].height);
await BlocksSummariesRepository.$deleteBlocksFrom(blocks[idx - 1].height);
await HashratesRepository.$deleteHashratesFromTimestamp(blocks[idx - 1].timestamp - 604800); await HashratesRepository.$deleteHashratesFromTimestamp(blocks[idx - 1].timestamp - 604800);
await DifficultyAdjustmentsRepository.$deleteAdjustementsFromHeight(blocks[idx - 1].height); await DifficultyAdjustmentsRepository.$deleteAdjustementsFromHeight(blocks[idx - 1].height);
return false; return false;
@ -619,7 +662,7 @@ class BlocksRepository {
* Delete blocks from the database from blockHeight * Delete blocks from the database from blockHeight
*/ */
public async $deleteBlocksFrom(blockHeight: number) { public async $deleteBlocksFrom(blockHeight: number) {
logger.info(`Delete newer blocks from height ${blockHeight} from the database`); logger.info(`Delete newer blocks from height ${blockHeight} from the database`, logger.tags.mining);
try { try {
await DB.query(`DELETE FROM blocks where height >= ${blockHeight}`); await DB.query(`DELETE FROM blocks where height >= ${blockHeight}`);
@ -933,7 +976,7 @@ class BlocksRepository {
* *
* @param dbBlk * @param dbBlk
*/ */
private async formatDbBlockIntoExtendedBlock(dbBlk: any): Promise<BlockExtended> { private async formatDbBlockIntoExtendedBlock(dbBlk: DatabaseBlock): Promise<BlockExtended> {
const blk: Partial<BlockExtended> = {}; const blk: Partial<BlockExtended> = {};
const extras: Partial<BlockExtension> = {}; const extras: Partial<BlockExtension> = {};
@ -997,6 +1040,7 @@ class BlocksRepository {
} }
// If we're missing block summary related field, check if we can populate them on the fly now // If we're missing block summary related field, check if we can populate them on the fly now
// This is for example triggered upon re-org
if (Common.blocksSummariesIndexingEnabled() && if (Common.blocksSummariesIndexingEnabled() &&
(extras.medianFeeAmt === null || extras.feePercentiles === null)) (extras.medianFeeAmt === null || extras.feePercentiles === null))
{ {
@ -1004,11 +1048,12 @@ class BlocksRepository {
if (extras.feePercentiles === null) { if (extras.feePercentiles === null) {
const block = await bitcoinClient.getBlock(dbBlk.id, 2); const block = await bitcoinClient.getBlock(dbBlk.id, 2);
const summary = blocks.summarizeBlock(block); const summary = blocks.summarizeBlock(block);
await BlocksSummariesRepository.$saveSummary({ height: block.height, mined: summary }); await BlocksSummariesRepository.$saveTransactions(dbBlk.height, dbBlk.id, summary.transactions);
extras.feePercentiles = await BlocksSummariesRepository.$getFeePercentilesByBlockId(dbBlk.id); extras.feePercentiles = await BlocksSummariesRepository.$getFeePercentilesByBlockId(dbBlk.id);
} }
if (extras.feePercentiles !== null) { if (extras.feePercentiles !== null) {
extras.medianFeeAmt = extras.feePercentiles[3]; extras.medianFeeAmt = extras.feePercentiles[3];
await this.$updateFeeAmounts(dbBlk.id, extras.feePercentiles, extras.medianFeeAmt);
} }
} }

View File

@ -1,6 +1,6 @@
import DB from '../database'; import DB from '../database';
import logger from '../logger'; import logger from '../logger';
import { BlockSummary } from '../mempool.interfaces'; import { BlockSummary, TransactionStripped } from '../mempool.interfaces';
class BlocksSummariesRepository { class BlocksSummariesRepository {
public async $getByBlockId(id: string): Promise<BlockSummary | undefined> { public async $getByBlockId(id: string): Promise<BlockSummary | undefined> {
@ -17,23 +17,17 @@ class BlocksSummariesRepository {
return undefined; return undefined;
} }
public async $saveSummary(params: { height: number, mined?: BlockSummary}) { public async $saveTransactions(blockHeight: number, blockId: string, transactions: TransactionStripped[]): Promise<void> {
const blockId = params.mined?.id;
try { try {
const transactions = JSON.stringify(params.mined?.transactions || []); const transactionsStr = JSON.stringify(transactions);
await DB.query(` await DB.query(`
INSERT INTO blocks_summaries (height, id, transactions, template) INSERT INTO blocks_summaries
VALUE (?, ?, ?, ?) SET height = ?, transactions = ?, id = ?
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE transactions = ?`,
transactions = ? [blockHeight, transactionsStr, blockId, transactionsStr]);
`, [params.height, blockId, transactions, '[]', transactions]);
} catch (e: any) { } catch (e: any) {
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart logger.debug(`Cannot save block summary transactions for ${blockId}. Reason: ${e instanceof Error ? e.message : e}`);
logger.debug(`Cannot save block summary for ${blockId} because it has already been indexed, ignoring`); throw e;
} else {
logger.debug(`Cannot save block summary for ${blockId}. Reason: ${e instanceof Error ? e.message : e}`);
throw e;
}
} }
} }
@ -68,19 +62,6 @@ class BlocksSummariesRepository {
return []; return [];
} }
/**
* Delete blocks from the database from blockHeight
*/
public async $deleteBlocksFrom(blockHeight: number) {
logger.info(`Delete newer blocks summary from height ${blockHeight} from the database`);
try {
await DB.query(`DELETE FROM blocks_summaries where height >= ${blockHeight}`);
} catch (e) {
logger.err('Cannot delete indexed blocks summaries. Reason: ' + (e instanceof Error ? e.message : e));
}
}
/** /**
* Get the fee percentiles if the block has already been indexed, [] otherwise * Get the fee percentiles if the block has already been indexed, [] otherwise
* *

View File

@ -220,7 +220,7 @@ class HashratesRepository {
* Delete hashrates from the database from timestamp * Delete hashrates from the database from timestamp
*/ */
public async $deleteHashratesFromTimestamp(timestamp: number) { public async $deleteHashratesFromTimestamp(timestamp: number) {
logger.info(`Delete newer hashrates from timestamp ${new Date(timestamp * 1000).toUTCString()} from the database`); logger.info(`Delete newer hashrates from timestamp ${new Date(timestamp * 1000).toUTCString()} from the database`, logger.tags.mining);
try { try {
await DB.query(`DELETE FROM hashrates WHERE hashrate_timestamp >= FROM_UNIXTIME(?)`, [timestamp]); await DB.query(`DELETE FROM hashrates WHERE hashrate_timestamp >= FROM_UNIXTIME(?)`, [timestamp]);

View File

@ -160,7 +160,7 @@ class PricesRepository {
// Compute fiat exchange rates // Compute fiat exchange rates
let latestPrice = rates[0] as ApiPrice; let latestPrice = rates[0] as ApiPrice;
if (latestPrice.USD === -1) { if (!latestPrice || latestPrice.USD === -1) {
latestPrice = priceUpdater.getEmptyPricesObj(); latestPrice = priceUpdater.getEmptyPricesObj();
} }

View File

@ -27,7 +27,7 @@ class ForensicsService {
private async $runTasks(): Promise<void> { private async $runTasks(): Promise<void> {
try { try {
logger.info(`Running forensics scans`); logger.debug(`Running forensics scans`);
if (config.MEMPOOL.BACKEND === 'esplora') { if (config.MEMPOOL.BACKEND === 'esplora') {
await this.$runClosedChannelsForensics(false); await this.$runClosedChannelsForensics(false);
@ -73,7 +73,7 @@ class ForensicsService {
let progress = 0; let progress = 0;
try { try {
logger.info(`Started running closed channel forensics...`); logger.debug(`Started running closed channel forensics...`);
let channels; let channels;
if (onlyNewChannels) { if (onlyNewChannels) {
channels = await channelsApi.$getClosedChannelsWithoutReason(); channels = await channelsApi.$getClosedChannelsWithoutReason();
@ -156,7 +156,7 @@ class ForensicsService {
this.loggerTimer = new Date().getTime() / 1000; this.loggerTimer = new Date().getTime() / 1000;
} }
} }
logger.info(`Closed channels forensics scan complete.`); logger.debug(`Closed channels forensics scan complete.`);
} catch (e) { } catch (e) {
logger.err('$runClosedChannelsForensics() error: ' + (e instanceof Error ? e.message : e)); logger.err('$runClosedChannelsForensics() error: ' + (e instanceof Error ? e.message : e));
} }
@ -217,7 +217,7 @@ class ForensicsService {
let progress = 0; let progress = 0;
try { try {
logger.info(`Started running open channel forensics...`); logger.debug(`Started running open channel forensics...`);
const channels = await channelsApi.$getChannelsWithoutSourceChecked(); const channels = await channelsApi.$getChannelsWithoutSourceChecked();
for (const openChannel of channels) { for (const openChannel of channels) {
@ -266,7 +266,7 @@ class ForensicsService {
} }
} }
logger.info(`Open channels forensics scan complete.`); logger.debug(`Open channels forensics scan complete.`);
} catch (e) { } catch (e) {
logger.err('$runOpenedChannelsForensics() error: ' + (e instanceof Error ? e.message : e)); logger.err('$runOpenedChannelsForensics() error: ' + (e instanceof Error ? e.message : e));
} finally { } finally {

View File

@ -283,7 +283,7 @@ class NetworkSyncService {
} else { } else {
log += ` for the first time`; log += ` for the first time`;
} }
logger.info(`${log}`, logger.tags.ln); logger.debug(`${log}`, logger.tags.ln);
const channels = await channelsApi.$getChannelsByStatus([0, 1]); const channels = await channelsApi.$getChannelsByStatus([0, 1]);
for (const channel of channels) { for (const channel of channels) {

View File

@ -15,16 +15,20 @@ class LightningStatsImporter {
topologiesFolder = config.LIGHTNING.TOPOLOGY_FOLDER; topologiesFolder = config.LIGHTNING.TOPOLOGY_FOLDER;
async $run(): Promise<void> { async $run(): Promise<void> {
const [channels]: any[] = await DB.query('SELECT short_id from channels;'); try {
logger.info(`Caching funding txs for currently existing channels`, logger.tags.ln); const [channels]: any[] = await DB.query('SELECT short_id from channels;');
await fundingTxFetcher.$fetchChannelsFundingTxs(channels.map(channel => channel.short_id)); logger.info(`Caching funding txs for currently existing channels`, logger.tags.ln);
await fundingTxFetcher.$fetchChannelsFundingTxs(channels.map(channel => channel.short_id));
if (config.MEMPOOL.NETWORK !== 'mainnet' || config.DATABASE.ENABLED === false) { if (config.MEMPOOL.NETWORK !== 'mainnet' || config.DATABASE.ENABLED === false) {
return; return;
}
await this.$importHistoricalLightningStats();
await this.$cleanupIncorrectSnapshot();
} catch (e) {
logger.err(`Exception in LightningStatsImporter::$run(). ${e}`);
} }
await this.$importHistoricalLightningStats();
await this.$cleanupIncorrectSnapshot();
} }
/** /**

View File

@ -62,7 +62,7 @@ class PoolsUpdater {
if (this.currentSha === null) { if (this.currentSha === null) {
logger.info(`Downloading pools-v2.json for the first time from ${this.poolsUrl} over ${network}`, logger.tags.mining); logger.info(`Downloading pools-v2.json for the first time from ${this.poolsUrl} over ${network}`, logger.tags.mining);
} else { } else {
logger.warn(`pools-v2.json is outdated, fetch latest from ${this.poolsUrl} over ${network}`, logger.tags.mining); logger.warn(`pools-v2.json is outdated, fetching latest from ${this.poolsUrl} over ${network}`, logger.tags.mining);
} }
const poolsJson = await this.query(this.poolsUrl); const poolsJson = await this.query(this.poolsUrl);
if (poolsJson === undefined) { if (poolsJson === undefined) {

View File

@ -222,7 +222,7 @@ class PriceUpdater {
private async $insertMissingRecentPrices(type: 'hour' | 'day'): Promise<void> { private async $insertMissingRecentPrices(type: 'hour' | 'day'): Promise<void> {
const existingPriceTimes = await PricesRepository.$getPricesTimes(); const existingPriceTimes = await PricesRepository.$getPricesTimes();
logger.info(`Fetching ${type === 'day' ? 'dai' : 'hour'}ly price history from exchanges and saving missing ones into the database`, logger.tags.mining); logger.debug(`Fetching ${type === 'day' ? 'dai' : 'hour'}ly price history from exchanges and saving missing ones into the database`, logger.tags.mining);
const historicalPrices: PriceHistory[] = []; const historicalPrices: PriceHistory[] = [];

View File

@ -5,6 +5,7 @@
"types": ["node", "jest"], "types": ["node", "jest"],
"lib": ["es2019", "dom"], "lib": ["es2019", "dom"],
"strict": true, "strict": true,
"skipLibCheck": true,
"noImplicitAny": false, "noImplicitAny": false,
"sourceMap": false, "sourceMap": false,
"outDir": "dist", "outDir": "dist",

View File

@ -204,7 +204,9 @@ Corresponding `docker-compose.yml` overrides:
`mempool-config.json`: `mempool-config.json`:
```json ```json
"ESPLORA": { "ESPLORA": {
"REST_API_URL": "http://127.0.0.1:3000" "REST_API_URL": "http://127.0.0.1:3000",
"UNIX_SOCKET_PATH": "/tmp/esplora-socket",
"RETRY_UNIX_SOCKET_AFTER": 30000
}, },
``` ```
@ -213,6 +215,8 @@ Corresponding `docker-compose.yml` overrides:
api: api:
environment: environment:
ESPLORA_REST_API_URL: "" ESPLORA_REST_API_URL: ""
ESPLORA_UNIX_SOCKET_PATH: ""
ESPLORA_RETRY_UNIX_SOCKET_AFTER: ""
... ...
``` ```

View File

@ -42,7 +42,9 @@
"TLS_ENABLED": __ELECTRUM_TLS_ENABLED__ "TLS_ENABLED": __ELECTRUM_TLS_ENABLED__
}, },
"ESPLORA": { "ESPLORA": {
"REST_API_URL": "__ESPLORA_REST_API_URL__" "REST_API_URL": "__ESPLORA_REST_API_URL__",
"UNIX_SOCKET_PATH": "__ESPLORA_UNIX_SOCKET_PATH__",
"RETRY_UNIX_SOCKET_AFTER": __ESPLORA_RETRY_UNIX_SOCKET_AFTER__
}, },
"SECOND_CORE_RPC": { "SECOND_CORE_RPC": {
"HOST": "__SECOND_CORE_RPC_HOST__", "HOST": "__SECOND_CORE_RPC_HOST__",

View File

@ -46,6 +46,8 @@ __ELECTRUM_TLS_ENABLED__=${ELECTRUM_TLS_ENABLED:=false}
# ESPLORA # ESPLORA
__ESPLORA_REST_API_URL__=${ESPLORA_REST_API_URL:=http://127.0.0.1:3000} __ESPLORA_REST_API_URL__=${ESPLORA_REST_API_URL:=http://127.0.0.1:3000}
__ESPLORA_UNIX_SOCKET_PATH__=${ESPLORA_UNIX_SOCKET_PATH:=null}
__ESPLORA_RETRY_UNIX_SOCKET_AFTER__=${ESPLORA_RETRY_UNIX_SOCKET_AFTER:=30000}
# SECOND_CORE_RPC # SECOND_CORE_RPC
__SECOND_CORE_RPC_HOST__=${SECOND_CORE_RPC_HOST:=127.0.0.1} __SECOND_CORE_RPC_HOST__=${SECOND_CORE_RPC_HOST:=127.0.0.1}
@ -166,6 +168,8 @@ sed -i "s/__ELECTRUM_PORT__/${__ELECTRUM_PORT__}/g" mempool-config.json
sed -i "s/__ELECTRUM_TLS_ENABLED__/${__ELECTRUM_TLS_ENABLED__}/g" mempool-config.json sed -i "s/__ELECTRUM_TLS_ENABLED__/${__ELECTRUM_TLS_ENABLED__}/g" mempool-config.json
sed -i "s!__ESPLORA_REST_API_URL__!${__ESPLORA_REST_API_URL__}!g" mempool-config.json sed -i "s!__ESPLORA_REST_API_URL__!${__ESPLORA_REST_API_URL__}!g" mempool-config.json
sed -i "s!__ESPLORA_UNIX_SOCKET_PATH__!${__ESPLORA_UNIX_SOCKET_PATH__}!g" mempool-config.json
sed -i "s!__ESPLORA_RETRY_UNIX_SOCKET_AFTER__!${__ESPLORA_RETRY_UNIX_SOCKET_AFTER__}!g" mempool-config.json
sed -i "s/__SECOND_CORE_RPC_HOST__/${__SECOND_CORE_RPC_HOST__}/g" mempool-config.json sed -i "s/__SECOND_CORE_RPC_HOST__/${__SECOND_CORE_RPC_HOST__}/g" mempool-config.json
sed -i "s/__SECOND_CORE_RPC_PORT__/${__SECOND_CORE_RPC_PORT__}/g" mempool-config.json sed -i "s/__SECOND_CORE_RPC_PORT__/${__SECOND_CORE_RPC_PORT__}/g" mempool-config.json

View File

@ -127,7 +127,7 @@ describe('Mainnet', () => {
cy.get('.search-box-container > .form-control').type('S').then(() => { cy.get('.search-box-container > .form-control').type('S').then(() => {
cy.wait('@search-1wizS'); cy.wait('@search-1wizS');
cy.get('app-search-results button.dropdown-item').should('have.length', 5); cy.get('app-search-results button.dropdown-item').should('have.length', 6);
}); });
cy.get('.search-box-container > .form-control').type('A').then(() => { cy.get('.search-box-container > .form-control').type('A').then(() => {

View File

@ -204,9 +204,9 @@
<img class="image" src="/resources/profile/raspiblitz.svg" /> <img class="image" src="/resources/profile/raspiblitz.svg" />
<span>RaspiBlitz</span> <span>RaspiBlitz</span>
</a> </a>
<a href="https://github.com/mynodebtc/mynode" target="_blank" title="MyNode"> <a href="https://github.com/mynodebtc/mynode" target="_blank" title="myNode">
<img class="image" src="/resources/profile/mynodebtc.jpg" /> <img class="image" src="/resources/profile/mynodebtc.png" />
<span>MyNode</span> <span>myNode</span>
</a> </a>
<a href="https://github.com/RoninDojo/RoninDojo" target="_blank" title="RoninDojo"> <a href="https://github.com/RoninDojo/RoninDojo" target="_blank" title="RoninDojo">
<img class="image" src="/resources/profile/ronindojo.png" /> <img class="image" src="/resources/profile/ronindojo.png" />
@ -253,7 +253,7 @@
<span>Sparrow</span> <span>Sparrow</span>
</a> </a>
<a href="https://github.com/ACINQ/phoenix" target="_blank" title="Phoenix Wallet by ACINQ"> <a href="https://github.com/ACINQ/phoenix" target="_blank" title="Phoenix Wallet by ACINQ">
<img class="image" src="/resources/profile/phoenix.jpg" /> <img class="image not-rounded" src="/resources/profile/phoenix.svg" />
<span>Phoenix</span> <span>Phoenix</span>
</a> </a>
<a href="https://github.com/lnbits/lnbits-legend" target="_blank" title="LNbits"> <a href="https://github.com/lnbits/lnbits-legend" target="_blank" title="LNbits">

View File

@ -23,7 +23,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
@Input() unavailable: boolean = false; @Input() unavailable: boolean = false;
@Input() auditHighlighting: boolean = false; @Input() auditHighlighting: boolean = false;
@Input() blockConversion: Price; @Input() blockConversion: Price;
@Output() txClickEvent = new EventEmitter<TransactionStripped>(); @Output() txClickEvent = new EventEmitter<{ tx: TransactionStripped, keyModifier: boolean}>();
@Output() txHoverEvent = new EventEmitter<string>(); @Output() txHoverEvent = new EventEmitter<string>();
@Output() readyEvent = new EventEmitter(); @Output() readyEvent = new EventEmitter();
@ -326,7 +326,9 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
if (event.target === this.canvas.nativeElement && event.pointerType === 'touch') { if (event.target === this.canvas.nativeElement && event.pointerType === 'touch') {
this.setPreviewTx(event.offsetX, event.offsetY, true); this.setPreviewTx(event.offsetX, event.offsetY, true);
} else if (event.target === this.canvas.nativeElement) { } else if (event.target === this.canvas.nativeElement) {
this.onTxClick(event.offsetX, event.offsetY); const keyMod = event.shiftKey || event.ctrlKey || event.metaKey;
const middleClick = event.which === 2 || event.button === 1;
this.onTxClick(event.offsetX, event.offsetY, keyMod || middleClick);
} }
} }
@ -409,12 +411,12 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On
} }
} }
onTxClick(cssX: number, cssY: number) { onTxClick(cssX: number, cssY: number, keyModifier: boolean = false) {
const x = cssX * window.devicePixelRatio; const x = cssX * window.devicePixelRatio;
const y = cssY * window.devicePixelRatio; const y = cssY * window.devicePixelRatio;
const selected = this.scene.getTxAt({ x, y }); const selected = this.scene.getTxAt({ x, y });
if (selected && selected.txid) { if (selected && selected.txid) {
this.txClickEvent.emit(selected); this.txClickEvent.emit({ tx: selected, keyModifier });
} }
} }

View File

@ -612,9 +612,13 @@ export class BlockComponent implements OnInit, OnDestroy {
}); });
} }
onTxClick(event: TransactionStripped): void { onTxClick(event: { tx: TransactionStripped, keyModifier: boolean }): void {
const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.txid}`); const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.tx.txid}`);
this.router.navigate([url]); if (!event.keyModifier) {
this.router.navigate([url]);
} else {
window.open(url, '_blank');
}
} }
onTxHover(txid: string): void { onTxHover(txid: string): void {

View File

@ -90,6 +90,8 @@
</nav> </nav>
</header> </header>
<app-testnet-alert *ngIf="network.val === 'liquidtestnet'"></app-testnet-alert>
<br /> <br />
<router-outlet></router-outlet> <router-outlet></router-outlet>

View File

@ -62,6 +62,8 @@
</nav> </nav>
</header> </header>
<app-testnet-alert *ngIf="network.val === 'testnet' || network.val === 'signet'"></app-testnet-alert>
<br /> <br />
<router-outlet></router-outlet> <router-outlet></router-outlet>

View File

@ -192,4 +192,4 @@ nav {
margin: 33px 0px 0px -19px; margin: 33px 0px 0px -19px;
font-size: 7px; font-size: 7px;
} }
} }

View File

@ -1,4 +1,4 @@
import { Component, Inject, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Env, StateService } from '../../services/state.service'; import { Env, StateService } from '../../services/state.service';
import { Observable, merge, of } from 'rxjs'; import { Observable, merge, of } from 'rxjs';
import { LanguageService } from '../../services/language.service'; import { LanguageService } from '../../services/language.service';

View File

@ -107,8 +107,12 @@ export class MempoolBlockOverviewComponent implements OnInit, OnDestroy, OnChang
this.isLoading$.next(false); this.isLoading$.next(false);
} }
onTxClick(event: TransactionStripped): void { onTxClick(event: { tx: TransactionStripped, keyModifier: boolean }): void {
const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.txid}`); const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.tx.txid}`);
this.router.navigate([url]); if (!event.keyModifier) {
this.router.navigate([url]);
} else {
window.open(url, '_blank');
}
} }
} }

View File

@ -2,7 +2,7 @@
<div class="mempool-blocks-container" [class.time-ltr]="timeLtr" *ngIf="(difficultyAdjustments$ | async) as da;"> <div class="mempool-blocks-container" [class.time-ltr]="timeLtr" *ngIf="(difficultyAdjustments$ | async) as da;">
<div class="flashing"> <div class="flashing">
<ng-template ngFor let-projectedBlock [ngForOf]="mempoolBlocks$ | async" let-i="index" [ngForTrackBy]="trackByFn"> <ng-template ngFor let-projectedBlock [ngForOf]="mempoolBlocks$ | async" let-i="index" [ngForTrackBy]="trackByFn">
<div @blockEntryTrigger [@.disabled]="!animateEntry" [attr.data-cy]="'mempool-block-' + i" class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]" [class.blink-bg]="projectedBlock.blink"> <div @blockEntryTrigger [@.disabled]="i > 0 || !animateEntry" [attr.data-cy]="'mempool-block-' + i" class="bitcoin-block text-center mempool-block" id="mempool-block-{{ i }}" [ngStyle]="mempoolBlockStyles[i]" [class.blink-bg]="projectedBlock.blink">
<a draggable="false" [routerLink]="['/mempool-block/' | relativeUrl, i]" <a draggable="false" [routerLink]="['/mempool-block/' | relativeUrl, i]"
class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}">&nbsp;</a> class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}">&nbsp;</a>
<div class="block-body"> <div class="block-body">

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, HostListener } from '@angular/core';
import { Subscription, Observable, fromEvent, merge, of, combineLatest } from 'rxjs'; import { Subscription, Observable, fromEvent, merge, of, combineLatest } from 'rxjs';
import { MempoolBlock } from '../../interfaces/websocket.interface'; import { MempoolBlock } from '../../interfaces/websocket.interface';
import { StateService } from '../../services/state.service'; import { StateService } from '../../services/state.service';
@ -222,8 +222,13 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
clearTimeout(this.resetTransitionTimeout); clearTimeout(this.resetTransitionTimeout);
} }
@HostListener('window:resize', ['$event'])
onResize(): void {
this.animateEntry = false;
}
trackByFn(index: number, block: MempoolBlock) { trackByFn(index: number, block: MempoolBlock) {
return (block.isStack) ? 'stack' : block.index; return (block.isStack) ? `stack-${block.index}` : block.index;
} }
reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] { reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {

View File

@ -137,9 +137,11 @@ export class StartComponent implements OnInit, OnDestroy {
} }
onMouseDown(event: MouseEvent) { onMouseDown(event: MouseEvent) {
this.mouseDragStartX = event.clientX; if (!(event.which > 1 || event.button > 0)) {
this.resetMomentum(event.clientX); this.mouseDragStartX = event.clientX;
this.blockchainScrollLeftInit = this.blockchainContainer.nativeElement.scrollLeft; this.resetMomentum(event.clientX);
this.blockchainScrollLeftInit = this.blockchainContainer.nativeElement.scrollLeft;
}
} }
onPointerDown(event: PointerEvent) { onPointerDown(event: PointerEvent) {
if (this.isiOS) { if (this.isiOS) {

View File

@ -8,10 +8,7 @@
</div> </div>
<div *ngIf="network !== 'liquid' && network !== 'liquidtestnet'" class="features"> <div *ngIf="network !== 'liquid' && network !== 'liquidtestnet'" class="features">
<app-tx-features [tx]="tx"></app-tx-features> <app-tx-features [tx]="tx"></app-tx-features>
<span *ngIf="cpfpInfo && (cpfpInfo.bestDescendant || cpfpInfo.descendants.length)" class="badge badge-primary mr-1"> <span *ngIf="cpfpInfo && (cpfpInfo?.bestDescendant || cpfpInfo?.descendants?.length || cpfpInfo?.ancestors?.length)" class="badge badge-primary ml-1 mr-1">
CPFP
</span>
<span *ngIf="cpfpInfo && !cpfpInfo.bestDescendant && !cpfpInfo.descendants.length && cpfpInfo.ancestors.length" class="badge badge-info mr-1">
CPFP CPFP
</span> </span>
</div> </div>

View File

@ -1,3 +1,5 @@
<div infiniteScroll [alwaysCallback]="true" [infiniteScrollDistance]="2" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="onScroll()">
<ng-container *ngFor="let tx of transactions; let i = index; trackBy: trackByFn"> <ng-container *ngFor="let tx of transactions; let i = index; trackBy: trackByFn">
<div *ngIf="!transactionPage" class="header-bg box tx-page-container"> <div *ngIf="!transactionPage" class="header-bg box tx-page-container">
<a class="tx-link" [routerLink]="['/tx/' | relativeUrl, tx.txid]"> <a class="tx-link" [routerLink]="['/tx/' | relativeUrl, tx.txid]">
@ -11,7 +13,7 @@
</div> </div>
</div> </div>
<div class="header-bg box" infiniteScroll [alwaysCallback]="true" [infiniteScrollDistance]="2" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="onScroll()" [attr.data-cy]="'tx-' + i"> <div class="header-bg box" [attr.data-cy]="'tx-' + i">
<div *ngIf="errorUnblinded" class="error-unblinded">{{ errorUnblinded }}</div> <div *ngIf="errorUnblinded" class="error-unblinded">{{ errorUnblinded }}</div>
<div class="row"> <div class="row">
@ -321,6 +323,8 @@
</ng-container> </ng-container>
</div>
<ng-template #assetBox let-item> <ng-template #assetBox let-item>
{{ item.value / pow(10, assetsMinimal[item.asset][3]) | number: '1.' + assetsMinimal[item.asset][3] + '-' + assetsMinimal[item.asset][3] }} {{ assetsMinimal[item.asset][1] }} {{ item.value / pow(10, assetsMinimal[item.asset][3]) | number: '1.' + assetsMinimal[item.asset][3] + '-' + assetsMinimal[item.asset][3] }} {{ assetsMinimal[item.asset][1] }}
<br /> <br />

View File

@ -182,14 +182,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
} }
onScroll(): void { onScroll(): void {
const scrollHeight = document.body.scrollHeight; this.loadMore.emit();
const scrollTop = document.documentElement.scrollTop;
if (scrollHeight > 0) {
const percentageScrolled = scrollTop * 100 / scrollHeight;
if (percentageScrolled > 50) {
this.loadMore.emit();
}
}
} }
haveBlindedOutputValues(tx: Transaction): boolean { haveBlindedOutputValues(tx: Transaction): boolean {

View File

@ -29,7 +29,7 @@
<ng-template #pegout> <ng-template #pegout>
<ng-container *ngIf="line.pegout; else normal"> <ng-container *ngIf="line.pegout; else normal">
<p *ngIf="!isConnector">Peg Out</p> <p *ngIf="!isConnector">Peg Out</p>
<p *ngIf="line.value != null"><app-amount [satoshis]="line.value"></app-amount></p> <p *ngIf="line.displayValue != null"><app-amount [satoshis]="line.displayValue"></app-amount></p>
<p class="address"> <p class="address">
<app-truncate [text]="line.pegout"></app-truncate> <app-truncate [text]="line.pegout"></app-truncate>
</p> </p>
@ -55,18 +55,18 @@
<p *ngSwitchCase="'output'"><span i18n="transaction.input">Input</span>&nbsp; #{{ line.vin + 1 }}</p> <p *ngSwitchCase="'output'"><span i18n="transaction.input">Input</span>&nbsp; #{{ line.vin + 1 }}</p>
</ng-container> </ng-container>
</ng-container> </ng-container>
<p *ngIf="line.value == null && line.confidential" i18n="shared.confidential">Confidential</p> <p *ngIf="line.displayValue == null && line.confidential" i18n="shared.confidential">Confidential</p>
<p *ngIf="line.value != null"> <p *ngIf="line.displayValue != null">
<ng-template [ngIf]="line.asset && line.asset !== nativeAssetId" [ngIfElse]="defaultOutput"> <ng-template [ngIf]="line.asset && line.asset !== nativeAssetId" [ngIfElse]="defaultOutput">
<div *ngIf="assetsMinimal && assetsMinimal[line.asset] else assetNotFound"> <div *ngIf="assetsMinimal && assetsMinimal[line.asset] else assetNotFound">
<ng-container *ngTemplateOutlet="assetBox; context:{ $implicit: line }"></ng-container> <ng-container *ngTemplateOutlet="assetBox; context:{ $implicit: line }"></ng-container>
</div> </div>
<ng-template #assetNotFound> <ng-template #assetNotFound>
{{ line.value }} <span class="symbol">{{ line.asset | slice : 0 : 7 }}</span> {{ line.displayValue }} <span class="symbol">{{ line.asset | slice : 0 : 7 }}</span>
</ng-template> </ng-template>
</ng-template> </ng-template>
<ng-template #defaultOutput> <ng-template #defaultOutput>
<app-amount [blockConversion]="blockConversion" [satoshis]="line.value"></app-amount> <app-amount [blockConversion]="blockConversion" [satoshis]="line.displayValue"></app-amount>
</ng-template> </ng-template>
</p> </p>
<p *ngIf="line.type !== 'fee' && line.address" class="address"> <p *ngIf="line.type !== 'fee' && line.address" class="address">
@ -76,5 +76,5 @@
</div> </div>
<ng-template #assetBox let-item> <ng-template #assetBox let-item>
{{ item.value / pow(10, assetsMinimal[item.asset][3]) | number: '1.' + assetsMinimal[item.asset][3] + '-' + assetsMinimal[item.asset][3] }} <span class="symbol">{{ assetsMinimal[item.asset][1] }}</span> {{ item.displayValue / pow(10, assetsMinimal[item.asset][3]) | number: '1.' + assetsMinimal[item.asset][3] + '-' + assetsMinimal[item.asset][3] }} <span class="symbol">{{ assetsMinimal[item.asset][1] }}</span>
</ng-template> </ng-template>

View File

@ -7,6 +7,7 @@ import { environment } from '../../../environments/environment';
interface Xput { interface Xput {
type: 'input' | 'output' | 'fee'; type: 'input' | 'output' | 'fee';
value?: number; value?: number;
displayValue?: number;
index?: number; index?: number;
txid?: string; txid?: string;
vin?: number; vin?: number;

View File

@ -1,12 +1,13 @@
import { Component, OnInit, Input, OnChanges, HostListener, Inject, LOCALE_ID } from '@angular/core'; import { Component, OnInit, Input, OnChanges, HostListener, Inject, LOCALE_ID } from '@angular/core';
import { StateService } from '../../services/state.service'; import { StateService } from '../../services/state.service';
import { Outspend, Transaction } from '../../interfaces/electrs.interface'; import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.interface';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { ReplaySubject, merge, Subscription, of } from 'rxjs'; import { ReplaySubject, merge, Subscription, of } from 'rxjs';
import { tap, switchMap } from 'rxjs/operators'; import { tap, switchMap } from 'rxjs/operators';
import { ApiService } from '../../services/api.service'; import { ApiService } from '../../services/api.service';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { AssetsService } from '../../services/assets.service'; import { AssetsService } from '../../services/assets.service';
import { environment } from '../../../environments/environment';
interface SvgLine { interface SvgLine {
path: string; path: string;
@ -20,6 +21,7 @@ interface SvgLine {
interface Xput { interface Xput {
type: 'input' | 'output' | 'fee'; type: 'input' | 'output' | 'fee';
value?: number; value?: number;
displayValue?: number;
index?: number; index?: number;
txid?: string; txid?: string;
vin?: number; vin?: number;
@ -74,6 +76,7 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
zeroValueThickness = 20; zeroValueThickness = 20;
hasLine: boolean; hasLine: boolean;
assetsMinimal: any; assetsMinimal: any;
nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId;
outspendsSubscription: Subscription; outspendsSubscription: Subscription;
refreshOutspends$: ReplaySubject<string> = new ReplaySubject(); refreshOutspends$: ReplaySubject<string> = new ReplaySubject();
@ -167,7 +170,8 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
let voutWithFee = this.tx.vout.map((v, i) => { let voutWithFee = this.tx.vout.map((v, i) => {
return { return {
type: v.scriptpubkey_type === 'fee' ? 'fee' : 'output', type: v.scriptpubkey_type === 'fee' ? 'fee' : 'output',
value: v?.value, value: this.getOutputValue(v),
displayValue: v?.value,
address: v?.scriptpubkey_address || v?.scriptpubkey_type?.toUpperCase(), address: v?.scriptpubkey_address || v?.scriptpubkey_type?.toUpperCase(),
index: i, index: i,
pegout: v?.pegout?.scriptpubkey_address, pegout: v?.pegout?.scriptpubkey_address,
@ -185,7 +189,8 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
let truncatedInputs = this.tx.vin.map((v, i) => { let truncatedInputs = this.tx.vin.map((v, i) => {
return { return {
type: 'input', type: 'input',
value: v?.prevout?.value || (v?.is_coinbase && !totalValue ? 0 : undefined), value: (v?.is_coinbase && !totalValue ? 0 : this.getInputValue(v)),
displayValue: v?.prevout?.value,
txid: v.txid, txid: v.txid,
vout: v.vout, vout: v.vout,
address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(), address: v?.prevout?.scriptpubkey_address || v?.prevout?.scriptpubkey_type?.toUpperCase(),
@ -229,14 +234,14 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
} }
calcTotalValue(tx: Transaction): number { calcTotalValue(tx: Transaction): number {
const totalOutput = this.tx.vout.reduce((acc, v) => (v.value == null ? 0 : v.value) + acc, 0); let totalOutput = this.tx.vout.reduce((acc, v) => (this.getOutputValue(v) || 0) + acc, 0);
// simple sum of outputs + fee for bitcoin // simple sum of outputs + fee for bitcoin
if (!this.isLiquid) { if (!this.isLiquid) {
return this.tx.fee ? totalOutput + this.tx.fee : totalOutput; return this.tx.fee ? totalOutput + this.tx.fee : totalOutput;
} else { } else {
const totalInput = this.tx.vin.reduce((acc, v) => (v?.prevout?.value == null ? 0 : v.prevout.value) + acc, 0); const totalInput = this.tx.vin.reduce((acc, v) => (this.getInputValue(v) || 0) + acc, 0);
const confidentialInputCount = this.tx.vin.reduce((acc, v) => acc + (v?.prevout?.value == null ? 1 : 0), 0); const confidentialInputCount = this.tx.vin.reduce((acc, v) => acc + (this.isUnknownInputValue(v) ? 1 : 0), 0);
const confidentialOutputCount = this.tx.vout.reduce((acc, v) => acc + (v.value == null ? 1 : 0), 0); const confidentialOutputCount = this.tx.vout.reduce((acc, v) => acc + (this.isUnknownOutputValue(v) ? 1 : 0), 0);
// if there are unknowns on both sides, the total is indeterminate, so we'll just fudge it // if there are unknowns on both sides, the total is indeterminate, so we'll just fudge it
if (confidentialInputCount && confidentialOutputCount) { if (confidentialInputCount && confidentialOutputCount) {
@ -456,6 +461,34 @@ export class TxBowtieGraphComponent implements OnInit, OnChanges {
} }
} }
getOutputValue(v: Vout): number | void {
if (!v) {
return null;
} else if (this.isLiquid && v.asset !== this.nativeAssetId) {
return null;
} else {
return v.value;
}
}
getInputValue(v: Vin): number | void {
if (!v?.prevout) {
return null;
} else if (this.isLiquid && v.prevout.asset !== this.nativeAssetId) {
return null;
} else {
return v.prevout.value;
}
}
isUnknownInputValue(v: Vin): boolean {
return v?.prevout?.value == null || this.isLiquid && v?.prevout?.asset !== this.nativeAssetId;
}
isUnknownOutputValue(v: Vout): boolean {
return v?.value == null || this.isLiquid && v?.asset !== this.nativeAssetId;
}
@HostListener('pointermove', ['$event']) @HostListener('pointermove', ['$event'])
onPointerMove(event) { onPointerMove(event) {
if (this.dir === 'rtl') { if (this.dir === 'rtl') {

View File

@ -8863,7 +8863,7 @@ export const faqData = [
type: "endpoint", type: "endpoint",
category: "advanced", category: "advanced",
showConditions: bitcoinNetworks, showConditions: bitcoinNetworks,
fragment: "how-big-is-mempool-used-by-mempool.space", fragment: "how-big-is-mempool-used-by-mempool-space",
title: "How big is the mempool used by mempool.space?", title: "How big is the mempool used by mempool.space?",
options: { officialOnly: true }, options: { officialOnly: true },
}, },

View File

@ -207,7 +207,7 @@
<p>When a Bitcoin transaction is made, it is stored in a Bitcoin node's mempool before it is confirmed into a block. When the rate of incoming transactions exceeds the rate transactions are confirmed, the mempool grows in size.</p><p>By default, Bitcoin Core allocates 300MB of memory for its mempool, so when a node's mempool grows big enough to use all 300MB of allocated memory, we say it's "full".</p><p>Once a node's mempool is using all of its allocated memory, it will start rejecting new transactions below a certain feerate threshold—so when this is the case, be extra sure to set a feerate that (at a minimum) exceeds that threshold. The current threshold feerate (and memory usage) are displayed right on Mempool's front page.</p> <p>When a Bitcoin transaction is made, it is stored in a Bitcoin node's mempool before it is confirmed into a block. When the rate of incoming transactions exceeds the rate transactions are confirmed, the mempool grows in size.</p><p>By default, Bitcoin Core allocates 300MB of memory for its mempool, so when a node's mempool grows big enough to use all 300MB of allocated memory, we say it's "full".</p><p>Once a node's mempool is using all of its allocated memory, it will start rejecting new transactions below a certain feerate threshold—so when this is the case, be extra sure to set a feerate that (at a minimum) exceeds that threshold. The current threshold feerate (and memory usage) are displayed right on Mempool's front page.</p>
</ng-template> </ng-template>
<ng-template type="how-big-is-mempool-used-by-mempool.space"> <ng-template type="how-big-is-mempool-used-by-mempool-space">
<p>mempool.space uses multiple Bitcoin nodes to obtain data: some with the default 300MB mempool memory limit (call these Small Nodes) and others with a much larger mempool memory limit (call these Big Nodes).</p> <p>mempool.space uses multiple Bitcoin nodes to obtain data: some with the default 300MB mempool memory limit (call these Small Nodes) and others with a much larger mempool memory limit (call these Big Nodes).</p>
<p>Many nodes on the Bitcoin network are configured to run with the default 300MB mempool memory setting. When all 300MB of memory are used up, such nodes will reject transactions below a certain threshold feerate. Running Small Nodes allows mempool.space to tell you what this threshold feerate is—this is the "Purging" feerate that shows on the front page when mempools are full, which you can use to be reasonably sure that your transaction will be widely propagated.</p> <p>Many nodes on the Bitcoin network are configured to run with the default 300MB mempool memory setting. When all 300MB of memory are used up, such nodes will reject transactions below a certain threshold feerate. Running Small Nodes allows mempool.space to tell you what this threshold feerate is—this is the "Purging" feerate that shows on the front page when mempools are full, which you can use to be reasonably sure that your transaction will be widely propagated.</p>
<p>Big Node mempools are so big that they don't need to reject (or purge) transactions. Such nodes allow for mempool.space to provide you with information on any pending transaction it has received—no matter how congested the mempool is, and no matter how low-feerate or low-priority the transaction is.</p> <p>Big Node mempools are so big that they don't need to reject (or purge) transactions. Such nodes allow for mempool.space to provide you with information on any pending transaction it has received—no matter how congested the mempool is, and no matter how low-feerate or low-priority the transaction is.</p>

View File

@ -1,4 +1,4 @@
<span class="green-color" *ngIf="blockConversion; else noblockconversion"> <span [class]="colorClass" *ngIf="blockConversion; else noblockconversion">
{{ {{
( (
(blockConversion.price[currency] > -1 ? blockConversion.price[currency] : null) ?? (blockConversion.price[currency] > -1 ? blockConversion.price[currency] : null) ??
@ -8,7 +8,7 @@
</span> </span>
<ng-template #noblockconversion> <ng-template #noblockconversion>
<span class="green-color" *ngIf="(conversions$ | async) as conversions"> <span [class]="colorClass" *ngIf="(conversions$ | async) as conversions">
{{ (conversions[currency] > -1 ? conversions[currency] : 0) * value / 100000000 | fiatCurrency : digitsInfo : currency }} {{ (conversions[currency] > -1 ? conversions[currency] : 0) * value / 100000000 | fiatCurrency : digitsInfo : currency }}
</span> </span>
</ng-template> </ng-template>

View File

@ -1,3 +0,0 @@
.green-color {
color: #3bcc49;
}

View File

@ -6,7 +6,7 @@ import { StateService } from '../services/state.service';
@Component({ @Component({
selector: 'app-fiat', selector: 'app-fiat',
templateUrl: './fiat.component.html', templateUrl: './fiat.component.html',
styleUrls: ['./fiat.component.scss'], styleUrls: [],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class FiatComponent implements OnInit, OnDestroy { export class FiatComponent implements OnInit, OnDestroy {
@ -17,6 +17,7 @@ export class FiatComponent implements OnInit, OnDestroy {
@Input() value: number; @Input() value: number;
@Input() digitsInfo = '1.2-2'; @Input() digitsInfo = '1.2-2';
@Input() blockConversion: Price; @Input() blockConversion: Price;
@Input() colorClass = 'green-color';
constructor( constructor(
private stateService: StateService, private stateService: StateService,

View File

@ -12,7 +12,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md"> <div class="col-md table-col">
<a class="subtitle" [routerLink]="['/lightning/node' | relativeUrl, node.public_key]">{{ node.public_key }}</a> <a class="subtitle" [routerLink]="['/lightning/node' | relativeUrl, node.public_key]">{{ node.public_key }}</a>
<table class="table table-borderless table-striped"> <table class="table table-borderless table-striped">
<tbody> <tbody>

View File

@ -18,6 +18,10 @@
} }
} }
.table-col {
max-width: calc(100% - 470px);
}
.map-col { .map-col {
flex-grow: 0; flex-grow: 0;
flex-shrink: 0; flex-shrink: 0;

View File

@ -12,20 +12,22 @@ export class StorageService {
setDefaultValueIfNeeded(key: string, defaultValue: string) { setDefaultValueIfNeeded(key: string, defaultValue: string) {
const graphWindowPreference: string = this.getValue(key); const graphWindowPreference: string = this.getValue(key);
const fragment = window.location.hash.replace('#', '');
if (graphWindowPreference === null) { // First visit to mempool.space if (graphWindowPreference === null) { // First visit to mempool.space
if (this.router.url.includes('graphs') && key === 'graphWindowPreference' || if (window.location.pathname.includes('graphs') && key === 'graphWindowPreference' ||
this.router.url.includes('pools') && key === 'miningWindowPreference' window.location.pathname.includes('pools') && key === 'miningWindowPreference'
) { ) {
this.setValue(key, this.route.snapshot.fragment ? this.route.snapshot.fragment : defaultValue); this.setValue(key, fragment ? fragment : defaultValue);
} else { } else {
this.setValue(key, defaultValue); this.setValue(key, defaultValue);
} }
} else if (this.router.url.includes('graphs') && key === 'graphWindowPreference' || } else if (window.location.pathname.includes('graphs') && key === 'graphWindowPreference' ||
this.router.url.includes('pools') && key === 'miningWindowPreference' window.location.pathname.includes('pools') && key === 'miningWindowPreference'
) { ) {
// Visit a different graphs#fragment from last visit // Visit a different graphs#fragment from last visit
if (this.route.snapshot.fragment !== null && graphWindowPreference !== this.route.snapshot.fragment) { if (fragment !== null && graphWindowPreference !== fragment) {
this.setValue(key, this.route.snapshot.fragment); this.setValue(key, fragment);
} }
} }
} }

View File

@ -0,0 +1,8 @@
<div class="container p-lg-0 pb-0" style="max-width: 100%; margin-top: 7px" *ngIf="storageService.getValue('hideWarning') !== 'hidden'">
<div class="alert alert-danger mb-0 text-center">
<div class="message-container" i18n="warning-testnet">This is a test network. Coins have no value.</div>
<button type="button" class="close" (click)="dismissWarning()">
<span aria-hidden="true">&times;</span>
</button>
</div>
</div>

View File

@ -0,0 +1,31 @@
.alert-danger {
color: #fff;
background-color: #b71c1c;
border-color: #b71c1c;
padding: 0.5rem 1.25rem;
margin: 0px 10px 0px 10px;
display: flex;
justify-content: center;
}
.message-container {
display: flex;
margin-left: auto;
}
.close {
display: flex;
color: #fff;
align-items: center;
}
button {
display: flex;
margin-left: auto;
}
span {
position: relative;
top: -2px;
}

View File

@ -0,0 +1,20 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { StorageService } from '../../../services/storage.service';
@Component({
selector: 'app-testnet-alert',
templateUrl: './testnet-alert.component.html',
styleUrls: ['./testnet-alert.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TestnetAlertComponent {
constructor(
public storageService: StorageService,
) { }
dismissWarning(): void {
this.storageService.setValue('hideWarning', 'hidden');
}
}

View File

@ -84,6 +84,7 @@ import { SearchResultsComponent } from '../components/search-form/search-results
import { TimestampComponent } from './components/timestamp/timestamp.component'; import { TimestampComponent } from './components/timestamp/timestamp.component';
import { ToggleComponent } from './components/toggle/toggle.component'; import { ToggleComponent } from './components/toggle/toggle.component';
import { GeolocationComponent } from '../shared/components/geolocation/geolocation.component'; import { GeolocationComponent } from '../shared/components/geolocation/geolocation.component';
import { TestnetAlertComponent } from './components/testnet-alert/testnet-alert.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -162,6 +163,7 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
TimestampComponent, TimestampComponent,
ToggleComponent, ToggleComponent,
GeolocationComponent, GeolocationComponent,
TestnetAlertComponent,
], ],
imports: [ imports: [
CommonModule, CommonModule,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 62.449287 55.101883"
version="1.1"
id="svg395"
sodipodi:docname="phoenix.svg"
width="62.449287"
height="55.101883"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview397"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="12.259259"
inkscape:cx="29.936556"
inkscape:cy="27.978852"
inkscape:window-width="3840"
inkscape:window-height="2091"
inkscape:window-x="0"
inkscape:window-y="32"
inkscape:window-maximized="1"
inkscape:current-layer="svg395" />
<defs
id="defs389">
<style
id="style387">.outline{fill:#50b338;}.bg{fill:#fff;}</style>
</defs>
<path
class="outline"
d="m 58.070086,17.34 a 6,6 0 0 0 0.58,-0.65 7.16,7.16 0 0 0 1,-7 c -2.14,-6.51 -10.58,-9.69 -25.75,-9.69 -11.82,0 -23,0 -28.8400005,19.87 -7.53,25.62 -6.16,34.13 0.84,35.13 4.2400005,0.61 6.6200005,-1.62 9.0000005,-4 2,-2 4,-4.46 7,-4 14,2 24.1,-0.21 29.85,-4.83 a 7.7,7.7 0 0 0 3.15,-6.17 5.52,5.52 0 0 0 -0.49,-2.38 c 4.61,-1.11 7.58,-3.4 8,-7.76 0.33,-3.86 -1.45,-6.59 -4.34,-8.52 z m -0.67,8.07 c -0.43,4.74 -11.72,4.46 -26.44,3.59 a 1,1 0 0 0 -1.06,0.91 1,1 0 0 0 0.88,1.09 l 2.43,0.3 c 5.83,0.7 16.69,1.98 16.69,4.7 a 3.44,3.44 0 0 1 -1.41,2.56 c -4,3.4 -15.54,5.16 -24.77,3.76 -4.9,-0.74 -7.55,1.45 -9.9,3.38 -2.09,1.71 -3.73,3.06 -6.6800005,2.33 a 1.59,1.59 0 0 1 -1,-0.83 c -1.74,-3.2 -0.36,-12.85 3.76,-25.9 C 14.950086,5 22.490086,5 33.900086,5 c 12.22,0 19.69,2.24 21,6.32 a 2.17,2.17 0 0 1 -0.2,2.25 c -2.47,3.16 -15.44,1.86 -19.7,1.43 a 1.0054974,1.0054974 0 0 0 -0.21,2 l 2.06,0.23 c 8.4,0.88 21.1,2.22 20.55,8.18 z"
id="path391" />
<path
class="bg"
d="m 36.900086,17.22 -2.11,-0.22 a 1.0054974,1.0054974 0 0 1 0.21,-2 c 4.26,0.43 17.23,1.73 19.75,-1.43 a 2.17,2.17 0 0 0 0.2,-2.25 c -1.36,-4.08 -8.83,-6.32 -21.05,-6.32 -11.41,0 -19,0 -24.0000005,16.3 -4.12,13.05 -5.5,22.7 -3.72,25.9 a 1.59,1.59 0 0 0 1,0.83 c 2.9500005,0.73 4.5900005,-0.62 6.6800005,-2.33 2.35,-1.93 5,-4.12 9.9,-3.38 9.23,1.4 20.81,-0.36 24.77,-3.76 a 3.44,3.44 0 0 0 1.37,-2.56 c 0,-2.72 -10.86,-4 -16.69,-4.71 l -2.43,-0.29 a 1,1 0 0 1 -0.88,-1.09 1,1 0 0 1 1.06,-0.91 c 14.72,0.87 26,1.15 26.44,-3.59 0.55,-5.96 -12.15,-7.3 -20.5,-8.19 z"
id="path393" />
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1,5 +1,5 @@
@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet @reboot screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet
@reboot sleep 60 ; /usr/local/bin/bitcoind -testnet >/dev/null 2>&1 @reboot /usr/local/bin/bitcoind -testnet >/dev/null 2>&1
@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/electrs-start-testnet @reboot screen -dmS testnet /bitcoin/electrs/electrs-start-testnet
@reboot sleep 80 ; /usr/local/bin/bitcoind -signet >/dev/null 2>&1 @reboot /usr/local/bin/bitcoind -signet >/dev/null 2>&1
@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/electrs-start-signet @reboot screen -dmS signet /bitcoin/electrs/electrs-start-signet

View File

@ -17,7 +17,7 @@ do
--db-dir __ELECTRS_DATA_ROOT__ \ --db-dir __ELECTRS_DATA_ROOT__ \
--network liquid \ --network liquid \
--daemon-dir "${HOME}" \ --daemon-dir "${HOME}" \
--http-addr '[::]:3001' \ --http-socket-file '/elements/socket/esplora-liquid-mainnet' \
--cookie '__ELEMENTS_RPC_USER__:__ELEMENTS_RPC_PASS__' \ --cookie '__ELEMENTS_RPC_USER__:__ELEMENTS_RPC_PASS__' \
--precache-scripts "${HOME}/electrs/contrib/popular-scripts.txt" --precache-scripts "${HOME}/electrs/contrib/popular-scripts.txt"
sleep 1 sleep 1

View File

@ -17,7 +17,7 @@ do
--db-dir __ELECTRS_DATA_ROOT__ \ --db-dir __ELECTRS_DATA_ROOT__ \
--network liquidtestnet \ --network liquidtestnet \
--daemon-dir "${HOME}" \ --daemon-dir "${HOME}" \
--http-addr '[::]:3004' \ --http-socket-file '/elements/socket/esplora-liquid-testnet' \
--cookie '__ELEMENTS_RPC_USER__:__ELEMENTS_RPC_PASS__' \ --cookie '__ELEMENTS_RPC_USER__:__ELEMENTS_RPC_PASS__' \
--precache-scripts "${HOME}/electrs/contrib/popular-scripts.txt" --precache-scripts "${HOME}/electrs/contrib/popular-scripts.txt"
sleep 1 sleep 1

View File

@ -14,7 +14,7 @@ do
--cors '*' \ --cors '*' \
--db-dir __ELECTRS_DATA_ROOT__ \ --db-dir __ELECTRS_DATA_ROOT__ \
--daemon-dir "${HOME}" \ --daemon-dir "${HOME}" \
--http-addr '[::]:3000' \ --http-socket-file '/bitcoin/socket/esplora-bitcoin-mainnet' \
--cookie '__BITCOIN_RPC_USER__:__BITCOIN_RPC_PASS__' \ --cookie '__BITCOIN_RPC_USER__:__BITCOIN_RPC_PASS__' \
--precache-scripts "${HOME}/electrs/contrib/popular-scripts.txt" --precache-scripts "${HOME}/electrs/contrib/popular-scripts.txt"

View File

@ -16,7 +16,7 @@ do
--db-dir __ELECTRS_DATA_ROOT__ \ --db-dir __ELECTRS_DATA_ROOT__ \
--daemon-rpc-addr '127.0.0.1:38332' \ --daemon-rpc-addr '127.0.0.1:38332' \
--daemon-dir "${HOME}" \ --daemon-dir "${HOME}" \
--http-addr '[::]:3003' \ --http-socket-file '/bitcoin/socket/esplora-bitcoin-signet' \
--cookie '__BITCOIN_RPC_USER__:__BITCOIN_RPC_PASS__' \ --cookie '__BITCOIN_RPC_USER__:__BITCOIN_RPC_PASS__' \
--precache-scripts "${HOME}/electrs/contrib/popular-scripts.txt" --precache-scripts "${HOME}/electrs/contrib/popular-scripts.txt"
sleep 1 sleep 1

View File

@ -15,7 +15,7 @@ do
--cors '*' \ --cors '*' \
--db-dir __ELECTRS_DATA_ROOT__ \ --db-dir __ELECTRS_DATA_ROOT__ \
--daemon-dir "${HOME}" \ --daemon-dir "${HOME}" \
--http-addr '[::]:3002' \ --http-socket-file '/bitcoin/socket/esplora-bitcoin-testnet' \
--cookie '__BITCOIN_RPC_USER__:__BITCOIN_RPC_PASS__' \ --cookie '__BITCOIN_RPC_USER__:__BITCOIN_RPC_PASS__' \
--precache-scripts "${HOME}/electrs/contrib/popular-scripts.txt" --precache-scripts "${HOME}/electrs/contrib/popular-scripts.txt"

View File

@ -1,10 +1,10 @@
# start elements on reboot # start elements on reboot
@reboot sleep 60 ; /usr/local/bin/elementsd -chain=liquidv1 >/dev/null 2>&1 @reboot /usr/local/bin/elementsd -chain=liquidv1 >/dev/null 2>&1
@reboot sleep 60 ; /usr/local/bin/elementsd -chain=liquidtestnet >/dev/null 2>&1 @reboot /usr/local/bin/elementsd -chain=liquidtestnet >/dev/null 2>&1
# start electrs on reboot # start electrs on reboot
@reboot sleep 90 ; screen -dmS liquidv1 /elements/electrs/electrs-start-liquid @reboot screen -dmS liquidv1 /elements/electrs/electrs-start-liquid
@reboot sleep 90 ; screen -dmS liquidtestnet /elements/electrs/electrs-start-liquidtestnet @reboot screen -dmS liquidtestnet /elements/electrs/electrs-start-liquidtestnet
# hourly asset update and electrs restart # hourly asset update and electrs restart
6 * * * * cd $HOME/asset_registry_db && git pull --quiet origin master && cd $HOME/asset_registry_testnet_db && git pull --quiet origin master && killall electrs 6 * * * * cd $HOME/asset_registry_db && git pull --quiet origin master && cd $HOME/asset_registry_testnet_db && git pull --quiet origin master && killall electrs

View File

@ -192,6 +192,7 @@ case $OS in
TOR_USER=_tor TOR_USER=_tor
TOR_GROUP=_tor TOR_GROUP=_tor
NGINX_USER=www NGINX_USER=www
NGINX_GROUP=www
NGINX_ETC_FOLDER=/usr/local/etc/nginx NGINX_ETC_FOLDER=/usr/local/etc/nginx
NGINX_CONFIGURATION=/usr/local/etc/nginx/nginx.conf NGINX_CONFIGURATION=/usr/local/etc/nginx/nginx.conf
CERTBOT_PKG=py39-certbot CERTBOT_PKG=py39-certbot
@ -209,6 +210,7 @@ case $OS in
TOR_GROUP=debian-tor TOR_GROUP=debian-tor
CERTBOT_PKG=python3-certbot-nginx CERTBOT_PKG=python3-certbot-nginx
NGINX_USER=www-data NGINX_USER=www-data
NGINX_GROUP=www-data
NGINX_ETC_FOLDER=/etc/nginx NGINX_ETC_FOLDER=/etc/nginx
NGINX_CONFIGURATION=/etc/nginx/nginx.conf NGINX_CONFIGURATION=/etc/nginx/nginx.conf
;; ;;
@ -301,12 +303,6 @@ BISQ_HOME=/bisq
# tor HS folder # tor HS folder
BISQ_TOR_HS=bisq BISQ_TOR_HS=bisq
# Unfurl user/group
UNFURL_USER=unfurl
UNFURL_GROUP=unfurl
# Unfurl home folder
UNFURL_HOME=/unfurl
# liquid user/group # liquid user/group
ELEMENTS_USER=elements ELEMENTS_USER=elements
ELEMENTS_GROUP=elements ELEMENTS_GROUP=elements
@ -396,7 +392,7 @@ DEBIAN_UNFURL_PKG+=(libxdamage-dev libxrandr-dev libgbm-dev libpango1.0-dev liba
# packages needed for mempool ecosystem # packages needed for mempool ecosystem
FREEBSD_PKG=() FREEBSD_PKG=()
FREEBSD_PKG+=(zsh sudo git git-lfs screen curl wget calc neovim) FREEBSD_PKG+=(zsh sudo git git-lfs screen curl wget calc neovim)
FREEBSD_PKG+=(openssh-portable py39-pip rust llvm90 jq base64 libzmq4) FREEBSD_PKG+=(openssh-portable py39-pip rust llvm10 jq base64 libzmq4)
FREEBSD_PKG+=(boost-libs autoconf automake gmake gcc libevent libtool pkgconf) FREEBSD_PKG+=(boost-libs autoconf automake gmake gcc libevent libtool pkgconf)
FREEBSD_PKG+=(nginx rsync py39-certbot-nginx mariadb105-server keybase) FREEBSD_PKG+=(nginx rsync py39-certbot-nginx mariadb105-server keybase)
FREEBSD_PKG+=(geoipupdate) FREEBSD_PKG+=(geoipupdate)
@ -547,6 +543,12 @@ zfsCreateFilesystems()
zfs create -o "mountpoint=${ELEMENTS_HOME}/liquidv1" "${ZPOOL}/elements/liquidv1" zfs create -o "mountpoint=${ELEMENTS_HOME}/liquidv1" "${ZPOOL}/elements/liquidv1"
zfs create -o "mountpoint=${ELEMENTS_ELECTRS_HOME}" "${ZPOOL}/elements/electrs" zfs create -o "mountpoint=${ELEMENTS_ELECTRS_HOME}" "${ZPOOL}/elements/electrs"
# create /bitcoin/socket with custom ACL for electrs unix sockets
zfs create -o "mountpoint=${BITCOIN_HOME}/socket" "${ZPOOL}/bitcoin/socket"
# create /elements/socket with custom ACL for electrs unix sockets
zfs create -o "mountpoint=${ELEMENTS_HOME}/socket" "${ZPOOL}/elements/socket"
# Bitcoin Mainnet # Bitcoin Mainnet
if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
for folder in chainstate indexes blocks for folder in chainstate indexes blocks
@ -630,6 +632,7 @@ zfsCreateFilesystems()
ext4CreateDir() ext4CreateDir()
{ {
mkdir -p "/backup" "${ELEMENTS_HOME}" "${BITCOIN_HOME}" "${MINFEE_HOME}" "${ELECTRS_HOME}" "${MEMPOOL_HOME}" "${MYSQL_HOME}" "${BITCOIN_ELECTRS_HOME}" "${ELEMENTS_HOME}/liquidv1" "${ELEMENTS_ELECTRS_HOME}" mkdir -p "/backup" "${ELEMENTS_HOME}" "${BITCOIN_HOME}" "${MINFEE_HOME}" "${ELECTRS_HOME}" "${MEMPOOL_HOME}" "${MYSQL_HOME}" "${BITCOIN_ELECTRS_HOME}" "${ELEMENTS_HOME}/liquidv1" "${ELEMENTS_ELECTRS_HOME}"
# Bitcoin Mainnet # Bitcoin Mainnet
if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then
for folder in chainstate indexes blocks for folder in chainstate indexes blocks
@ -1019,7 +1022,7 @@ case $OS in
osSudo "${ROOT_USER}" mkdir -p /usr/local/etc/syslog.d osSudo "${ROOT_USER}" mkdir -p /usr/local/etc/syslog.d
osSudo "${ROOT_USER}" install -c -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/mempool-logger" /usr/local/bin/mempool-logger osSudo "${ROOT_USER}" install -c -m 755 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/mempool-logger" /usr/local/bin/mempool-logger
osSudo "${ROOT_USER}" install -c -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/syslog.conf" /usr/local/etc/syslog.d/mempool.conf osSudo "${ROOT_USER}" install -c -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/syslog.conf" /usr/local/etc/syslog.d/mempool.conf
echo "[*] Installing newsyslog configuration" echo "[*] Installing newsyslog configuration"
osSudo "${ROOT_USER}" mkdir -p /usr/local/etc/newsyslog.conf.d osSudo "${ROOT_USER}" mkdir -p /usr/local/etc/newsyslog.conf.d
osSudo "${ROOT_USER}" install -c -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/newsyslog-mempool-backend.conf" /usr/local/etc/newsyslog.conf.d/newsyslog-mempool-backend.conf osSudo "${ROOT_USER}" install -c -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/newsyslog-mempool-backend.conf" /usr/local/etc/newsyslog.conf.d/newsyslog-mempool-backend.conf
@ -1057,17 +1060,8 @@ if [ "${TOR_INSTALL}" = ON ];then
osSudo "${ROOT_USER}" install -c -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/torrc" "${TOR_CONFIGURATION}" osSudo "${ROOT_USER}" install -c -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/torrc" "${TOR_CONFIGURATION}"
osSudo "${ROOT_USER}" sed -i.orig "s!__TOR_RESOURCES__!${TOR_RESOURCES}!" "${TOR_CONFIGURATION}" osSudo "${ROOT_USER}" sed -i.orig "s!__TOR_RESOURCES__!${TOR_RESOURCES}!" "${TOR_CONFIGURATION}"
echo "[*] Adding Tor HS configuration for Mempool"
if [ "${MEMPOOL_ENABLE}" = "ON" ];then
if ! grep "${MEMPOOL_TOR_HS}" "${TOR_CONFIGURATION}" >/dev/null 2>&1;then
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 HiddenServiceVersion 3 >> ${TOR_CONFIGURATION}"
fi
fi
echo "[*] Adding Tor HS configuration for Bisq" echo "[*] Adding Tor HS configuration for Bisq"
if [ "${BISQ_ENABLE}" = "ON" ];then if [ "${BISQ_MAINNET_ENABLE}" = "ON" ];then
if ! grep "${BISQ_TOR_HS}" "${TOR_CONFIGURATION}" >/dev/null 2>&1;then if ! grep "${BISQ_TOR_HS}" "${TOR_CONFIGURATION}" >/dev/null 2>&1;then
osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServiceDir ${TOR_RESOURCES}/${BISQ_TOR_HS}/ >> ${TOR_CONFIGURATION}" osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServiceDir ${TOR_RESOURCES}/${BISQ_TOR_HS}/ >> ${TOR_CONFIGURATION}"
osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServicePort 80 127.0.0.1:82 >> ${TOR_CONFIGURATION}" osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServicePort 80 127.0.0.1:82 >> ${TOR_CONFIGURATION}"
@ -1076,7 +1070,7 @@ if [ "${TOR_INSTALL}" = ON ];then
fi fi
echo "[*] Adding Tor HS configuration for Liquid" echo "[*] Adding Tor HS configuration for Liquid"
if [ "${LIQUID_ENABLE}" = "ON" ];then if [ "${ELEMENTS_LIQUID_ENABLE}" = "ON" ];then
if ! grep "${LIQUID_TOR_HS}" "${TOR_CONFIGURATION}" >/dev/null 2>&1;then if ! grep "${LIQUID_TOR_HS}" "${TOR_CONFIGURATION}" >/dev/null 2>&1;then
osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServiceDir ${TOR_RESOURCES}/${LIQUID_TOR_HS}/ >> ${TOR_CONFIGURATION}" osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServiceDir ${TOR_RESOURCES}/${LIQUID_TOR_HS}/ >> ${TOR_CONFIGURATION}"
osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServicePort 80 127.0.0.1:83 >> ${TOR_CONFIGURATION}" osSudo "${ROOT_USER}" /bin/sh -c "echo HiddenServicePort 80 127.0.0.1:83 >> ${TOR_CONFIGURATION}"
@ -1273,25 +1267,25 @@ if [ "${ELEMENTS_ELECTRS_INSTALL}" = ON ];then
if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
osSudo "${ROOT_USER}" chown -R "${ELEMENTS_USER}:${ELEMENTS_GROUP}" "${ELECTRS_LIQUIDTESTNET_DATA}" osSudo "${ROOT_USER}" chown -R "${ELEMENTS_USER}:${ELEMENTS_GROUP}" "${ELECTRS_LIQUIDTESTNET_DATA}"
fi fi
echo "[*] Cloning Liquid Electrs repo from ${ELEMENTS_ELECTRS_REPO_URL}" echo "[*] Cloning Liquid Electrs repo from ${ELEMENTS_ELECTRS_REPO_URL}"
osSudo "${ELEMENTS_USER}" git config --global advice.detachedHead false osSudo "${ELEMENTS_USER}" git config --global advice.detachedHead false
osSudo "${ELEMENTS_USER}" git clone --branch "${ELEMENTS_ELECTRS_REPO_BRANCH}" "${ELEMENTS_ELECTRS_REPO_URL}" "${ELEMENTS_HOME}/${ELEMENTS_ELECTRS_REPO_NAME}" osSudo "${ELEMENTS_USER}" git clone --branch "${ELEMENTS_ELECTRS_REPO_BRANCH}" "${ELEMENTS_ELECTRS_REPO_URL}" "${ELEMENTS_HOME}/${ELEMENTS_ELECTRS_REPO_NAME}"
echo "[*] Checking out Liquid Electrs ${ELEMENTS_ELECTRS_LATEST_RELEASE}" echo "[*] Checking out Liquid Electrs ${ELEMENTS_ELECTRS_LATEST_RELEASE}"
osSudo "${ELEMENTS_USER}" sh -c "cd ${ELEMENTS_HOME}/${ELEMENTS_ELECTRS_REPO_NAME} && git checkout ${ELEMENTS_ELECTRS_LATEST_RELEASE}" osSudo "${ELEMENTS_USER}" sh -c "cd ${ELEMENTS_HOME}/${ELEMENTS_ELECTRS_REPO_NAME} && git checkout ${ELEMENTS_ELECTRS_LATEST_RELEASE}"
echo "[*] Cloning Liquid Asset Registry repo from ${LIQUID_ASSET_REGISTRY_DB_URL}" echo "[*] Cloning Liquid Asset Registry repo from ${LIQUID_ASSET_REGISTRY_DB_URL}"
osSudo "${ELEMENTS_USER}" git config --global advice.detachedHead false osSudo "${ELEMENTS_USER}" git config --global advice.detachedHead false
osSudo "${ELEMENTS_USER}" git clone "${LIQUID_ASSET_REGISTRY_DB_URL}" "${ELEMENTS_HOME}/${LIQUID_ASSET_REGISTRY_DB_NAME}" osSudo "${ELEMENTS_USER}" git clone "${LIQUID_ASSET_REGISTRY_DB_URL}" "${ELEMENTS_HOME}/${LIQUID_ASSET_REGISTRY_DB_NAME}"
echo "[*] Cloning Liquid Asset Registry testnet repo from ${LIQUIDTESTNET_ASSET_REGISTRY_DB_URL}" echo "[*] Cloning Liquid Asset Registry testnet repo from ${LIQUIDTESTNET_ASSET_REGISTRY_DB_URL}"
osSudo "${ELEMENTS_USER}" git config --global advice.detachedHead false osSudo "${ELEMENTS_USER}" git config --global advice.detachedHead false
osSudo "${ELEMENTS_USER}" git clone "${LIQUIDTESTNET_ASSET_REGISTRY_DB_URL}" "${ELEMENTS_HOME}/${LIQUIDTESTNET_ASSET_REGISTRY_DB_NAME}" osSudo "${ELEMENTS_USER}" git clone "${LIQUIDTESTNET_ASSET_REGISTRY_DB_URL}" "${ELEMENTS_HOME}/${LIQUIDTESTNET_ASSET_REGISTRY_DB_NAME}"
echo "[*] Building Liquid Electrs release binary" echo "[*] Building Liquid Electrs release binary"
osSudo "${ELEMENTS_USER}" sh -c "cd ${ELEMENTS_ELECTRS_HOME} && cargo run --release --features liquid --bin electrs -- --network liquid --version" || true osSudo "${ELEMENTS_USER}" sh -c "cd ${ELEMENTS_ELECTRS_HOME} && cargo run --release --features liquid --bin electrs -- --network liquid --version" || true
case $OS in case $OS in
FreeBSD) FreeBSD)
echo "[*] Patching Liquid Electrs code for FreeBSD" echo "[*] Patching Liquid Electrs code for FreeBSD"
@ -1300,11 +1294,11 @@ if [ "${ELEMENTS_ELECTRS_INSTALL}" = ON ];then
Debian) Debian)
;; ;;
esac esac
echo "[*] Building Liquid Electrs release binary" echo "[*] Building Liquid Electrs release binary"
osSudo "${ELEMENTS_USER}" sh -c "cd ${ELEMENTS_ELECTRS_HOME} && cargo run --release --features liquid --bin electrs -- --network liquid --version" || true osSudo "${ELEMENTS_USER}" sh -c "cd ${ELEMENTS_ELECTRS_HOME} && cargo run --release --features liquid --bin electrs -- --network liquid --version" || true
fi fi
############################## ##############################
# Core Lightning for Bitcoin # # Core Lightning for Bitcoin #
############################## ##############################
@ -1331,10 +1325,10 @@ case $OS in
public_ipv4=$( ifconfig | grep 'inet ' | awk -F ' ' '{ print $2 }' | grep -v '^103\.165\.192\.' | grep -v '^127\.0\.0\.1' ) public_ipv4=$( ifconfig | grep 'inet ' | awk -F ' ' '{ print $2 }' | grep -v '^103\.165\.192\.' | grep -v '^127\.0\.0\.1' )
public_ipv6=$( ifconfig | grep 'inet6' | awk -F ' ' '{ print $2 }' | grep -v '^2001:df6:7280::' | grep -v '^fe80::' | grep -v '^::1' ) public_ipv6=$( ifconfig | grep 'inet6' | awk -F ' ' '{ print $2 }' | grep -v '^2001:df6:7280::' | grep -v '^fe80::' | grep -v '^::1' )
crontab_cln+="@reboot sleep 60 ; screen -dmS main lightningd --rpc-file-mode 0660 --alias `hostname` --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6\n" crontab_cln+="@reboot sleep 10 ; screen -dmS main lightningd --rpc-file-mode 0660 --alias `hostname` --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6\n"
crontab_cln+="@reboot sleep 90 ; screen -dmS tes lightningd --rpc-file-mode 0660 --alias `hostname` --network testnet --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6\n" crontab_cln+="@reboot sleep 10 ; screen -dmS tes lightningd --rpc-file-mode 0660 --alias `hostname` --network testnet --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6\n"
crontab_cln+="@reboot sleep 120 ; screen -dmS sig lightningd --rpc-file-mode 0660 --alias `hostname` --network signet --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6 \n" crontab_cln+="@reboot sleep 10 ; screen -dmS sig lightningd --rpc-file-mode 0660 --alias `hostname` --network signet --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6 \n"
crontab_cln+="@reboot sleep 180 ; /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\n" crontab_cln+="@reboot sleep 20 ; /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\n"
crontab_cln+="1 * * * * /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\n" crontab_cln+="1 * * * * /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\n"
echo "${crontab_cln}" | crontab -u "${CLN_USER}" - echo "${crontab_cln}" | crontab -u "${CLN_USER}" -
;; ;;
@ -1430,16 +1424,6 @@ fi
if [ "${UNFURL_INSTALL}" = ON ];then if [ "${UNFURL_INSTALL}" = ON ];then
echo "[*] Creating Unfurl user"
osGroupCreate "${UNFURL_GROUP}"
osUserCreate "${UNFURL_USER}" "${UNFURL_HOME}" "${UNFURL_GROUP}"
osSudo "${ROOT_USER}" chsh -s `which zsh` "${UNFURL_USER}"
echo "[*] Creating Unfurl folder"
osSudo "${ROOT_USER}" mkdir -p "${UNFURL_HOME}"
osSudo "${ROOT_USER}" chown -R "${UNFURL_USER}:${UNFURL_GROUP}" "${UNFURL_HOME}"
osSudo "${UNFURL_USER}" touch "${UNFURL_HOME}/.zshrc"
echo "[*] Insalling Unfurl source" echo "[*] Insalling Unfurl source"
case $OS in case $OS in
@ -1530,7 +1514,6 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
case $OS in case $OS in
FreeBSD) FreeBSD)
echo "[*] FIXME: Bitcoin Minfee service must be installed manually on FreeBSD"
;; ;;
Debian) Debian)
@ -1548,7 +1531,6 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
case $OS in case $OS in
FreeBSD) FreeBSD)
echo "[*] FIXME: Bitcoin Testnet service must be installed manually on FreeBSD"
;; ;;
Debian) Debian)
@ -1566,7 +1548,6 @@ if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
case $OS in case $OS in
FreeBSD) FreeBSD)
echo "[*] FIXME: Bitcoin Signet service must be installed manually on FreeBSD"
;; ;;
Debian) Debian)
@ -1584,7 +1565,6 @@ if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
case $OS in case $OS in
FreeBSD) FreeBSD)
echo "[*] FIXME: Bitcoin Liquid service must be installed manually on FreeBSD"
;; ;;
Debian) Debian)
@ -1602,7 +1582,6 @@ if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
case $OS in case $OS in
FreeBSD) FreeBSD)
echo "[*] FIXME: Bitcoin Liquid service must be installed manually on FreeBSD"
;; ;;
Debian) Debian)
@ -1841,6 +1820,9 @@ case $OS in
;; ;;
esac esac
# wait for mysql to start
sleep 5
mysql << _EOF_ mysql << _EOF_
create database mempool; create database mempool;
grant all on mempool.* to '${MEMPOOL_MAINNET_USER}'@'localhost' identified by '${MEMPOOL_MAINNET_PASS}'; grant all on mempool.* to '${MEMPOOL_MAINNET_USER}'@'localhost' identified by '${MEMPOOL_MAINNET_PASS}';
@ -1895,39 +1877,60 @@ chown "${MEMPOOL_USER}:${MEMPOOL_GROUP}" "${MEMPOOL_MYSQL_CREDENTIALS}"
##### nginx ##### nginx
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}"
mkdir -p /var/cache/nginx/services /var/cache/nginx/api
chown "${NGINX_USER}:${NGINX_GROUP}" /var/cache/nginx/services /var/cache/nginx/api
ln -s "${MEMPOOL_HOME}/mempool" "${NGINX_ETC_FOLDER}/mempool"
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}"
if [ "${TOR_INSTALL}" = ON ];then
echo "[*] Read tor v3 onion hostnames"
NGINX_MEMPOOL_ONION=$(cat "${TOR_RESOURCES}/mempool/hostname")
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_MEMPOOL_ONION__!${NGINX_MEMPOOL_ONION%.onion}!" "${NGINX_CONFIGURATION}"
if [ "${ELEMENTS_LIQUID_ENABLE}" = "ON" ];then
NGINX_LIQUID_ONION=$(cat "${TOR_RESOURCES}/liquid/hostname")
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_LIQUID_ONION__!${NGINX_LIQUID_ONIONi%.onion}!" "${NGINX_CONFIGURATION}"
fi
if [ "${BISQ_MAINNET_ENABLE}" = "ON" ];then
NGINX_BISQ_ONION=$(cat "${TOR_RESOURCES}/bisq/hostname")
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_BISQ_ONION__!${NGINX_BISQ_ONION%.onion}!" "${NGINX_CONFIGURATION}"
fi
fi
##### OS systemd
echo "[*] Setting permissions for electrs sockets"
case $OS in case $OS in
FreeBSD) FreeBSD)
setfacl -m "user:bitcoin:full_set:f:allow,user:mempool:full_set:f:allow,user:www:full_set:f:allow,everyone@::f:allow" "${BITCOIN_HOME}/socket"
chown "${BITCOIN_USER}:${BITCOIN_GROUP}" "${BITCOIN_HOME}/socket"
setfacl -m "user:elements:full_set:f:allow,user:mempool:full_set:f:allow,user:www:full_set:f:allow,everyone@::f:allow" "${ELEMENTS_HOME}/socket"
chown "${ELEMENTS_USER}:${ELEMENTS_GROUP}" "${ELEMENTS_HOME}/socket"
;; ;;
Debian) Debian)
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}"
mkdir -p /var/cache/nginx/services /var/cache/nginx/api
chown ${NGINX_USER}: /var/cache/nginx/services /var/cache/nginx/api
ln -s /mempool/mempool /etc/nginx/mempool
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}"
if [ "${TOR_INSTALL}" = ON ];then
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")
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_MEMPOOL_ONION__!${NGINX_MEMPOOL_ONION%.onion}!" "${NGINX_CONFIGURATION}"
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_BISQ_ONION__!${NGINX_BISQ_ONION%.onion}!" "${NGINX_CONFIGURATION}"
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_LIQUID_ONION__!${NGINX_LIQUID_ONIONi%.onion}!" "${NGINX_CONFIGURATION}"
fi
echo "[*] Restarting Nginx"
osSudo "${ROOT_USER}" service nginx restart
;; ;;
esac esac
##### OS systemd ##### OS systemd
echo "[*] Updating systemd daemon configuration" echo "[*] Updating system startup configuration"
case $OS in case $OS in
FreeBSD) FreeBSD)
echo 'nginx_enable="YES"' >> /etc/rc.conf
echo 'bitcoin_enable="YES"' >> /etc/rc.conf
echo 'tor_enable="YES"' >> /etc/rc.conf
echo 'postfix_enable="YES"' >> /etc/rc.conf
echo 'mysql_enable="YES"' >> /etc/rc.conf
echo 'mysql_dbdir="/mysql"' >> /etc/rc.conf
echo 'tor_enable="YES"' >> /etc/rc.conf
;; ;;
Debian) Debian)
@ -1959,6 +1962,9 @@ case $OS in
;; ;;
esac esac
echo "[*] Restarting Nginx"
osSudo "${ROOT_USER}" service nginx restart
##### OS set Linux user ulimits ##### OS set Linux user ulimits
echo "[*] Setting ulimits for users" echo "[*] Setting ulimits for users"
@ -2060,20 +2066,12 @@ osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME} && ./upgrade" || true
##### finish ##### finish
case $OS in if [ "${TOR_INSTALL}" = ON ];then
echo "Your auto-generated Tor addresses are:"
FreeBSD) echo "${NGINX_MEMPOOL_ONION}"
;; echo "${NGINX_BISQ_ONION}"
echo "${NGINX_LIQUID_ONION}"
Debian) fi
if [ "${TOR_INSTALL}" = ON ];then
echo "This are the generated Tor addresses:"
echo "${NGINX_MEMPOOL_ONION}"
echo "${NGINX_BISQ_ONION}"
echo "${NGINX_LIQUID_ONION}"
fi
;;
esac
echo echo
echo 'Please reboot to start all the services.' echo 'Please reboot to start all the services.'

View File

@ -15,12 +15,13 @@
"PASSWORD": "__BITCOIN_RPC_PASS__" "PASSWORD": "__BITCOIN_RPC_PASS__"
}, },
"ESPLORA": { "ESPLORA": {
"REST_API_URL": "http://127.0.0.1:4000" "REST_API_URL": "http://127.0.0.1:5000",
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet"
}, },
"DATABASE": { "DATABASE": {
"ENABLED": false, "ENABLED": false,
"HOST": "127.0.0.1", "HOST": "127.0.0.1",
"PORT": 3306, "SOCKET": "/var/run/mysql/mysql.sock",
"USERNAME": "__MEMPOOL_BISQ_USER__", "USERNAME": "__MEMPOOL_BISQ_USER__",
"PASSWORD": "__MEMPOOL_BISQ_PASS__", "PASSWORD": "__MEMPOOL_BISQ_PASS__",
"DATABASE": "mempool_bisq" "DATABASE": "mempool_bisq"

View File

@ -23,12 +23,13 @@
"PASSWORD": "__BITCOIN_RPC_PASS__" "PASSWORD": "__BITCOIN_RPC_PASS__"
}, },
"ESPLORA": { "ESPLORA": {
"REST_API_URL": "http://127.0.0.1:4001" "REST_API_URL": "http://127.0.0.1:5001",
"UNIX_SOCKET_PATH": "/elements/socket/esplora-liquid-mainnet"
}, },
"DATABASE": { "DATABASE": {
"ENABLED": true, "ENABLED": true,
"HOST": "127.0.0.1", "HOST": "127.0.0.1",
"PORT": 3306, "SOCKET": "/var/run/mysql/mysql.sock",
"USERNAME": "__MEMPOOL_LIQUID_USER__", "USERNAME": "__MEMPOOL_LIQUID_USER__",
"PASSWORD": "__MEMPOOL_LIQUID_PASS__", "PASSWORD": "__MEMPOOL_LIQUID_PASS__",
"DATABASE": "mempool_liquid" "DATABASE": "mempool_liquid"

View File

@ -23,12 +23,13 @@
"PASSWORD": "__BITCOIN_RPC_PASS__" "PASSWORD": "__BITCOIN_RPC_PASS__"
}, },
"ESPLORA": { "ESPLORA": {
"REST_API_URL": "http://127.0.0.1:4004" "REST_API_URL": "http://127.0.0.1:5004",
"UNIX_SOCKET_PATH": "/elements/socket/esplora-liquid-testnet"
}, },
"DATABASE": { "DATABASE": {
"ENABLED": true, "ENABLED": true,
"HOST": "127.0.0.1", "HOST": "127.0.0.1",
"PORT": 3306, "SOCKET": "/var/run/mysql/mysql.sock",
"USERNAME": "__MEMPOOL_LIQUIDTESTNET_USER__", "USERNAME": "__MEMPOOL_LIQUIDTESTNET_USER__",
"PASSWORD": "__MEMPOOL_LIQUIDTESTNET_PASS__", "PASSWORD": "__MEMPOOL_LIQUIDTESTNET_PASS__",
"DATABASE": "mempool_liquidtestnet" "DATABASE": "mempool_liquidtestnet"

View File

@ -15,7 +15,8 @@
"PASSWORD": "__BITCOIN_RPC_PASS__" "PASSWORD": "__BITCOIN_RPC_PASS__"
}, },
"ESPLORA": { "ESPLORA": {
"REST_API_URL": "http://127.0.0.1:4000" "REST_API_URL": "http://127.0.0.1:5000",
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet"
}, },
"LIGHTNING": { "LIGHTNING": {
"ENABLED": true, "ENABLED": true,
@ -41,7 +42,7 @@
"DATABASE": { "DATABASE": {
"ENABLED": true, "ENABLED": true,
"HOST": "127.0.0.1", "HOST": "127.0.0.1",
"PORT": 3306, "SOCKET": "/var/run/mysql/mysql.sock",
"USERNAME": "__MEMPOOL_MAINNET_LIGHTNING_USER__", "USERNAME": "__MEMPOOL_MAINNET_LIGHTNING_USER__",
"PASSWORD": "__MEMPOOL_MAINNET_LIGHTNING_PASS__", "PASSWORD": "__MEMPOOL_MAINNET_LIGHTNING_PASS__",
"DATABASE": "mempool_mainnet_lightning" "DATABASE": "mempool_mainnet_lightning"

View File

@ -31,12 +31,13 @@
"PASSWORD": "__BITCOIN_RPC_PASS__" "PASSWORD": "__BITCOIN_RPC_PASS__"
}, },
"ESPLORA": { "ESPLORA": {
"REST_API_URL": "http://127.0.0.1:4000" "REST_API_URL": "http://127.0.0.1:5000",
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet"
}, },
"DATABASE": { "DATABASE": {
"ENABLED": true, "ENABLED": true,
"HOST": "127.0.0.1", "HOST": "127.0.0.1",
"PORT": 3306, "SOCKET": "/var/run/mysql/mysql.sock",
"USERNAME": "__MEMPOOL_MAINNET_USER__", "USERNAME": "__MEMPOOL_MAINNET_USER__",
"PASSWORD": "__MEMPOOL_MAINNET_PASS__", "PASSWORD": "__MEMPOOL_MAINNET_PASS__",
"DATABASE": "mempool" "DATABASE": "mempool"

View File

@ -15,7 +15,8 @@
"PASSWORD": "__BITCOIN_RPC_PASS__" "PASSWORD": "__BITCOIN_RPC_PASS__"
}, },
"ESPLORA": { "ESPLORA": {
"REST_API_URL": "http://127.0.0.1:4003" "REST_API_URL": "http://127.0.0.1:5003",
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet"
}, },
"LIGHTNING": { "LIGHTNING": {
"ENABLED": true, "ENABLED": true,
@ -36,7 +37,7 @@
"DATABASE": { "DATABASE": {
"ENABLED": true, "ENABLED": true,
"HOST": "127.0.0.1", "HOST": "127.0.0.1",
"PORT": 3306, "SOCKET": "/var/run/mysql/mysql.sock",
"USERNAME": "__MEMPOOL_SIGNET_LIGHTNING_USER__", "USERNAME": "__MEMPOOL_SIGNET_LIGHTNING_USER__",
"PASSWORD": "__MEMPOOL_SIGNET_LIGHTNING_PASS__", "PASSWORD": "__MEMPOOL_SIGNET_LIGHTNING_PASS__",
"DATABASE": "mempool_signet_lightning" "DATABASE": "mempool_signet_lightning"

View File

@ -22,12 +22,13 @@
"PASSWORD": "__BITCOIN_RPC_PASS__" "PASSWORD": "__BITCOIN_RPC_PASS__"
}, },
"ESPLORA": { "ESPLORA": {
"REST_API_URL": "http://127.0.0.1:4003" "REST_API_URL": "http://127.0.0.1:5003",
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet"
}, },
"DATABASE": { "DATABASE": {
"ENABLED": true, "ENABLED": true,
"HOST": "127.0.0.1", "HOST": "127.0.0.1",
"PORT": 3306, "SOCKET": "/var/run/mysql/mysql.sock",
"USERNAME": "__MEMPOOL_SIGNET_USER__", "USERNAME": "__MEMPOOL_SIGNET_USER__",
"PASSWORD": "__MEMPOOL_SIGNET_PASS__", "PASSWORD": "__MEMPOOL_SIGNET_PASS__",
"DATABASE": "mempool_signet" "DATABASE": "mempool_signet"

View File

@ -15,7 +15,8 @@
"PASSWORD": "__BITCOIN_RPC_PASS__" "PASSWORD": "__BITCOIN_RPC_PASS__"
}, },
"ESPLORA": { "ESPLORA": {
"REST_API_URL": "http://127.0.0.1:4002" "REST_API_URL": "http://127.0.0.1:5002",
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet"
}, },
"LIGHTNING": { "LIGHTNING": {
"ENABLED": true, "ENABLED": true,
@ -36,7 +37,7 @@
"DATABASE": { "DATABASE": {
"ENABLED": true, "ENABLED": true,
"HOST": "127.0.0.1", "HOST": "127.0.0.1",
"PORT": 3306, "SOCKET": "/var/run/mysql/mysql.sock",
"USERNAME": "__MEMPOOL_TESTNET_LIGHTNING_USER__", "USERNAME": "__MEMPOOL_TESTNET_LIGHTNING_USER__",
"PASSWORD": "__MEMPOOL_TESTNET_LIGHTNING_PASS__", "PASSWORD": "__MEMPOOL_TESTNET_LIGHTNING_PASS__",
"DATABASE": "mempool_testnet_lightning" "DATABASE": "mempool_testnet_lightning"

View File

@ -22,12 +22,13 @@
"PASSWORD": "__BITCOIN_RPC_PASS__" "PASSWORD": "__BITCOIN_RPC_PASS__"
}, },
"ESPLORA": { "ESPLORA": {
"REST_API_URL": "http://127.0.0.1:4002" "REST_API_URL": "http://127.0.0.1:5002",
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet"
}, },
"DATABASE": { "DATABASE": {
"ENABLED": true, "ENABLED": true,
"HOST": "127.0.0.1", "HOST": "127.0.0.1",
"PORT": 3306, "SOCKET": "/var/run/mysql/mysql.sock",
"USERNAME": "__MEMPOOL_TESTNET_USER__", "USERNAME": "__MEMPOOL_TESTNET_USER__",
"PASSWORD": "__MEMPOOL_TESTNET_PASS__", "PASSWORD": "__MEMPOOL_TESTNET_PASS__",
"DATABASE": "mempool_testnet" "DATABASE": "mempool_testnet"

View File

@ -1,5 +1,5 @@
# start on reboot # start on reboot
@reboot sleep 10 ; $HOME/start @reboot sleep 90 ; $HOME/start
# daily backup # daily backup
37 13 * * * sleep 30 ; /mempool/mempool.space/backup >/dev/null 2>&1 & 37 13 * * * sleep 30 ; /mempool/mempool.space/backup >/dev/null 2>&1 &

View File

@ -1 +1 @@
@reboot sleep 120 ; /usr/local/bin/bitcoind >/dev/null 2>&1 @reboot /usr/local/bin/bitcoind >/dev/null 2>&1

View File

@ -1,8 +1,8 @@
/var/log/nginx/access.log nobody:nobody 644 10 * @T00 C /var/run/mempool.pid 30 /var/log/nginx/access.log www:www 644 10 * @T00 C /var/run/mempool.pid 30
/var/log/nginx/error.log nobody:nobody 644 10 * @T00 C /var/run/mempool.pid 30 /var/log/nginx/error.log www:www 644 10 * @T00 C /var/run/mempool.pid 30
/var/log/nginx/bisq-access.log nobody:nobody 644 10 * @T00 C /var/run/mempool.pid 30 /var/log/nginx/bisq-access.log www:www 644 10 * @T00 C /var/run/mempool.pid 30
/var/log/nginx/bisq-error.log nobody:nobody 644 10 * @T00 C /var/run/mempool.pid 30 /var/log/nginx/bisq-error.log www:www 644 10 * @T00 C /var/run/mempool.pid 30
/var/log/nginx/liquid-access.log nobody:nobody 644 10 * @T00 C /var/run/mempool.pid 30 /var/log/nginx/liquid-access.log www:www 644 10 * @T00 C /var/run/mempool.pid 30
/var/log/nginx/liquid-error.log nobody:nobody 644 10 * @T00 C /var/run/mempool.pid 30 /var/log/nginx/liquid-error.log www:www 644 10 * @T00 C /var/run/mempool.pid 30
/var/log/nginx/mempool-access.log nobody:nobody 644 10 * @T00 C /var/run/mempool.pid 30 /var/log/nginx/mempool-access.log www:www 644 10 * @T00 C /var/run/mempool.pid 30
/var/log/nginx/mempool-error.log nobody:nobody 644 10 * @T00 C /var/run/mempool.pid 30 /var/log/nginx/mempool-error.log www:www 644 10 * @T00 C /var/run/mempool.pid 30

View File

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