Compare commits
18 Commits
hunicus/su
...
mononaut/f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81669c39e9 | ||
|
|
00d94f5614 | ||
|
|
d18ebdfc59 | ||
|
|
604c3ba266 | ||
|
|
b8d063a4f7 | ||
|
|
3c30415982 | ||
|
|
c5252dc27d | ||
|
|
6016db2533 | ||
|
|
b23f14b798 | ||
|
|
09d52f9fbe | ||
|
|
c23e529f0a | ||
|
|
ab7cb5f681 | ||
|
|
db27e5a92c | ||
|
|
66109afb0d | ||
|
|
b6f1fd5a4a | ||
|
|
44a0913b81 | ||
|
|
6c81dcdc76 | ||
|
|
906f24f0ee |
@@ -3,68 +3,102 @@ import axios, { AxiosRequestConfig } from 'axios';
|
||||
import http from 'http';
|
||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
||||
import { IEsploraApi } from './esplora-api.interface';
|
||||
import logger from '../../logger';
|
||||
|
||||
const axiosConnection = axios.create({
|
||||
httpAgent: new http.Agent({ keepAlive: true, })
|
||||
});
|
||||
|
||||
class ElectrsApi implements AbstractBitcoinApi {
|
||||
axiosConfig: AxiosRequestConfig = config.ESPLORA.UNIX_SOCKET_PATH ? {
|
||||
private axiosConfigWithUnixSocket: AxiosRequestConfig = config.ESPLORA.UNIX_SOCKET_PATH ? {
|
||||
socketPath: config.ESPLORA.UNIX_SOCKET_PATH,
|
||||
timeout: 10000,
|
||||
} : {
|
||||
timeout: 10000,
|
||||
};
|
||||
private axiosConfigTcpSocketOnly: AxiosRequestConfig = {
|
||||
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'][]> {
|
||||
return axiosConnection.get<IEsploraApi.Transaction['txid'][]>(config.ESPLORA.REST_API_URL + '/mempool/txids', this.axiosConfig)
|
||||
.then((response) => response.data);
|
||||
return this.$queryWrapper<IEsploraApi.Transaction['txid'][]>(config.ESPLORA.REST_API_URL + '/mempool/txids');
|
||||
}
|
||||
|
||||
$getRawTransaction(txId: string): Promise<IEsploraApi.Transaction> {
|
||||
return axiosConnection.get<IEsploraApi.Transaction>(config.ESPLORA.REST_API_URL + '/tx/' + txId, this.axiosConfig)
|
||||
.then((response) => response.data);
|
||||
return this.$queryWrapper<IEsploraApi.Transaction>(config.ESPLORA.REST_API_URL + '/tx/' + txId);
|
||||
}
|
||||
|
||||
$getTransactionHex(txId: string): Promise<string> {
|
||||
return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/hex', this.axiosConfig)
|
||||
.then((response) => response.data);
|
||||
return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/hex');
|
||||
}
|
||||
|
||||
$getBlockHeightTip(): Promise<number> {
|
||||
return axiosConnection.get<number>(config.ESPLORA.REST_API_URL + '/blocks/tip/height', this.axiosConfig)
|
||||
.then((response) => response.data);
|
||||
return this.$queryWrapper<number>(config.ESPLORA.REST_API_URL + '/blocks/tip/height');
|
||||
}
|
||||
|
||||
$getBlockHashTip(): Promise<string> {
|
||||
return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/blocks/tip/hash', this.axiosConfig)
|
||||
.then((response) => response.data);
|
||||
return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/blocks/tip/hash');
|
||||
}
|
||||
|
||||
$getTxIdsForBlock(hash: string): Promise<string[]> {
|
||||
return axiosConnection.get<string[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids', this.axiosConfig)
|
||||
.then((response) => response.data);
|
||||
return this.$queryWrapper<string[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids');
|
||||
}
|
||||
|
||||
$getBlockHash(height: number): Promise<string> {
|
||||
return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/block-height/' + height, this.axiosConfig)
|
||||
.then((response) => response.data);
|
||||
return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/block-height/' + height);
|
||||
}
|
||||
|
||||
$getBlockHeader(hash: string): Promise<string> {
|
||||
return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/header', this.axiosConfig)
|
||||
.then((response) => response.data);
|
||||
return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/header');
|
||||
}
|
||||
|
||||
$getBlock(hash: string): Promise<IEsploraApi.Block> {
|
||||
return axiosConnection.get<IEsploraApi.Block>(config.ESPLORA.REST_API_URL + '/block/' + hash, this.axiosConfig)
|
||||
.then((response) => response.data);
|
||||
return this.$queryWrapper<IEsploraApi.Block>(config.ESPLORA.REST_API_URL + '/block/' + hash);
|
||||
}
|
||||
|
||||
$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); });
|
||||
}
|
||||
|
||||
@@ -85,13 +119,11 @@ class ElectrsApi implements AbstractBitcoinApi {
|
||||
}
|
||||
|
||||
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
|
||||
return axiosConnection.get<IEsploraApi.Outspend>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspend/' + vout, this.axiosConfig)
|
||||
.then((response) => response.data);
|
||||
return this.$queryWrapper<IEsploraApi.Outspend>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspend/' + vout);
|
||||
}
|
||||
|
||||
$getOutspends(txId: string): Promise<IEsploraApi.Outspend[]> {
|
||||
return axiosConnection.get<IEsploraApi.Outspend[]>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspends', this.axiosConfig)
|
||||
.then((response) => response.data);
|
||||
return this.$queryWrapper<IEsploraApi.Outspend[]>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspends');
|
||||
}
|
||||
|
||||
async $getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]> {
|
||||
|
||||
@@ -857,6 +857,7 @@ class Blocks {
|
||||
}
|
||||
if (cleanBlock.fee_amt_percentiles !== null) {
|
||||
cleanBlock.median_fee_amt = cleanBlock.fee_amt_percentiles[3];
|
||||
await blocksRepository.$updateFeeAmounts(cleanBlock.hash, cleanBlock.fee_amt_percentiles, cleanBlock.median_fee_amt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as fs from 'fs';
|
||||
import logger from '../../logger';
|
||||
|
||||
class Icons {
|
||||
private static FILE_NAME = './icons.json';
|
||||
private static FILE_NAME = '/elements/asset_registry_db/icons.json';
|
||||
private iconIds: string[] = [];
|
||||
private icons: { [assetId: string]: string; } = {};
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ interface IConfig {
|
||||
ESPLORA: {
|
||||
REST_API_URL: string;
|
||||
UNIX_SOCKET_PATH: string | void | null;
|
||||
RETRY_UNIX_SOCKET_AFTER: number;
|
||||
};
|
||||
LIGHTNING: {
|
||||
ENABLED: boolean;
|
||||
@@ -165,6 +166,7 @@ const defaults: IConfig = {
|
||||
'ESPLORA': {
|
||||
'REST_API_URL': 'http://127.0.0.1:3000',
|
||||
'UNIX_SOCKET_PATH': null,
|
||||
'RETRY_UNIX_SOCKET_AFTER': 30000,
|
||||
},
|
||||
'ELECTRUM': {
|
||||
'HOST': '127.0.0.1',
|
||||
|
||||
@@ -45,7 +45,8 @@ class Server {
|
||||
private wss: WebSocket.Server | undefined;
|
||||
private server: http.Server | undefined;
|
||||
private app: Application;
|
||||
private currentBackendRetryInterval = 5;
|
||||
private currentBackendRetryInterval = 1;
|
||||
private backendRetryCount = 0;
|
||||
|
||||
private maxHeapSize: number = 0;
|
||||
private heapLogInterval: number = 60;
|
||||
@@ -184,17 +185,17 @@ class Server {
|
||||
indexer.$run();
|
||||
|
||||
setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS);
|
||||
this.currentBackendRetryInterval = 5;
|
||||
this.backendRetryCount = 0;
|
||||
} 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)}.`;
|
||||
if (e?.stack) {
|
||||
loggerMsg += ` Stack trace: ${e.stack}`;
|
||||
}
|
||||
// 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
|
||||
// Maximum retry delay is 60 seconds
|
||||
if (this.currentBackendRetryInterval > 5) {
|
||||
if (this.backendRetryCount >= 5) {
|
||||
logger.warn(loggerMsg);
|
||||
mempool.setOutOfSync();
|
||||
} else {
|
||||
@@ -204,8 +205,6 @@ class Server {
|
||||
logger.debug(`AxiosError: ${e?.message}`);
|
||||
}
|
||||
setTimeout(this.runMainUpdateLoop.bind(this), 1000 * this.currentBackendRetryInterval);
|
||||
this.currentBackendRetryInterval *= 2;
|
||||
this.currentBackendRetryInterval = Math.min(this.currentBackendRetryInterval, 60);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,48 @@ import chainTips from '../api/chain-tips';
|
||||
import blocks from '../api/blocks';
|
||||
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 = `
|
||||
blocks.hash AS id,
|
||||
blocks.height,
|
||||
@@ -52,7 +94,7 @@ const BLOCK_DB_FIELDS = `
|
||||
blocks.header,
|
||||
blocks.utxoset_change AS utxoSetChange,
|
||||
blocks.utxoset_size AS utxoSetSize,
|
||||
blocks.total_input_amt AS totalInputAmts
|
||||
blocks.total_input_amt AS totalInputAmt
|
||||
`;
|
||||
|
||||
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]
|
||||
*/
|
||||
@@ -432,7 +500,7 @@ class BlocksRepository {
|
||||
|
||||
const blocks: BlockExtended[] = [];
|
||||
for (const block of rows) {
|
||||
blocks.push(await this.formatDbBlockIntoExtendedBlock(block));
|
||||
blocks.push(await this.formatDbBlockIntoExtendedBlock(block as DatabaseBlock));
|
||||
}
|
||||
|
||||
return blocks;
|
||||
@@ -459,7 +527,7 @@ class BlocksRepository {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await this.formatDbBlockIntoExtendedBlock(rows[0]);
|
||||
return await this.formatDbBlockIntoExtendedBlock(rows[0] as DatabaseBlock);
|
||||
} catch (e) {
|
||||
logger.err(`Cannot get indexed block ${height}. Reason: ` + (e instanceof Error ? e.message : e));
|
||||
throw e;
|
||||
@@ -908,7 +976,7 @@ class BlocksRepository {
|
||||
*
|
||||
* @param dbBlk
|
||||
*/
|
||||
private async formatDbBlockIntoExtendedBlock(dbBlk: any): Promise<BlockExtended> {
|
||||
private async formatDbBlockIntoExtendedBlock(dbBlk: DatabaseBlock): Promise<BlockExtended> {
|
||||
const blk: Partial<BlockExtended> = {};
|
||||
const extras: Partial<BlockExtension> = {};
|
||||
|
||||
@@ -980,11 +1048,12 @@ class BlocksRepository {
|
||||
if (extras.feePercentiles === null) {
|
||||
const block = await bitcoinClient.getBlock(dbBlk.id, 2);
|
||||
const summary = blocks.summarizeBlock(block);
|
||||
await BlocksSummariesRepository.$saveTransactions(dbBlk.height, dbBlk.hash, summary.transactions);
|
||||
await BlocksSummariesRepository.$saveTransactions(dbBlk.height, dbBlk.id, summary.transactions);
|
||||
extras.feePercentiles = await BlocksSummariesRepository.$getFeePercentilesByBlockId(dbBlk.id);
|
||||
}
|
||||
if (extras.feePercentiles !== null) {
|
||||
extras.medianFeeAmt = extras.feePercentiles[3];
|
||||
await this.$updateFeeAmounts(dbBlk.id, extras.feePercentiles, extras.medianFeeAmt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,26 +17,6 @@ class BlocksSummariesRepository {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public async $saveSummary(params: { height: number, mined?: BlockSummary}): Promise<void> {
|
||||
const blockId = params.mined?.id;
|
||||
try {
|
||||
const transactions = JSON.stringify(params.mined?.transactions || []);
|
||||
await DB.query(`
|
||||
INSERT INTO blocks_summaries (height, id, transactions, template)
|
||||
VALUE (?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
transactions = ?
|
||||
`, [params.height, blockId, transactions, '[]', transactions]);
|
||||
} catch (e: any) {
|
||||
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
||||
logger.debug(`Cannot save block summary for ${blockId} because it has already been indexed, ignoring`);
|
||||
} else {
|
||||
logger.debug(`Cannot save block summary for ${blockId}. Reason: ${e instanceof Error ? e.message : e}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async $saveTransactions(blockHeight: number, blockId: string, transactions: TransactionStripped[]): Promise<void> {
|
||||
try {
|
||||
const transactionsStr = JSON.stringify(transactions);
|
||||
|
||||
@@ -22,6 +22,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() offset: number = 0;
|
||||
@Input() height: number = 0; // max height of blocks in chunk (dynamic blocks only)
|
||||
@Input() count: number = 8; // number of blocks in this chunk (dynamic blocks only)
|
||||
@Input() dynamicBlockCount: number = 8; // number of blocks in the dynamic block chunk
|
||||
@Input() loadingTip: boolean = false;
|
||||
@Input() connected: boolean = true;
|
||||
|
||||
@@ -45,7 +46,6 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
feeRounding = '1.0-0';
|
||||
arrowVisible = false;
|
||||
arrowLeftPx = 30;
|
||||
blocksFilled = false;
|
||||
arrowTransition = '1s';
|
||||
showMiningInfo = false;
|
||||
timeLtrSubscription: Subscription;
|
||||
@@ -96,14 +96,13 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.tabHiddenSubscription = this.stateService.isTabHidden$.subscribe((tabHidden) => this.tabHidden = tabHidden);
|
||||
if (!this.static) {
|
||||
this.blocksSubscription = this.stateService.blocks$
|
||||
.subscribe(([block, txConfirmed]) => {
|
||||
.subscribe(([block, txConfirmed, batch]) => {
|
||||
if (this.blocks.some((b) => b.height === block.height)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.blocks.length && block.height !== this.blocks[0].height + 1) {
|
||||
this.blocks = [];
|
||||
this.blocksFilled = false;
|
||||
}
|
||||
|
||||
this.blocks.unshift(block);
|
||||
@@ -117,20 +116,18 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
this.blockStyles = [];
|
||||
if (this.blocksFilled && block.height > this.chainTip) {
|
||||
this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i, i ? -155 : -205)));
|
||||
setTimeout(() => {
|
||||
this.blockStyles = [];
|
||||
this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i)));
|
||||
this.cd.markForCheck();
|
||||
}, 50);
|
||||
} else {
|
||||
this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i)));
|
||||
}
|
||||
|
||||
if (this.blocks.length === this.dynamicBlocksAmount) {
|
||||
this.blocksFilled = true;
|
||||
}
|
||||
this.blocks.forEach((b, i) => {
|
||||
if (i === 0 && !batch && block.height > this.chainTip) {
|
||||
this.blockStyles.push(this.getStyleForBlock(b, i, -205));
|
||||
setTimeout(() => {
|
||||
this.blockStyles = [];
|
||||
this.blocks.forEach((b, i) => this.blockStyles.push(this.getStyleForBlock(b, i)));
|
||||
this.cd.markForCheck();
|
||||
}, 50);
|
||||
} else {
|
||||
this.blockStyles.push(this.getStyleForBlock(b, i));
|
||||
}
|
||||
});
|
||||
|
||||
this.chainTip = Math.max(this.chainTip, block.height);
|
||||
this.cd.markForCheck();
|
||||
@@ -160,7 +157,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (this.static) {
|
||||
const animateSlide = changes.height && (changes.height.currentValue === changes.height.previousValue + 1);
|
||||
const animateSlide = (changes.dynamicBlockCount && changes.dynamicBlockCount.previousValue != null) || (changes.height && (changes.height.currentValue === changes.height.previousValue + 1));
|
||||
this.updateStaticBlocks(animateSlide);
|
||||
}
|
||||
}
|
||||
@@ -227,8 +224,8 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
block = this.cacheService.getCachedBlock(height) || null;
|
||||
}
|
||||
this.blocks.push(block || {
|
||||
placeholder: height < 0,
|
||||
loading: height >= 0,
|
||||
placeholder: !isNaN(height) && height < 0,
|
||||
loading: isNaN(height) || height >= 0,
|
||||
id: '',
|
||||
height,
|
||||
version: 0,
|
||||
@@ -311,6 +308,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
return {
|
||||
left: addLeft + (155 * index) + 'px',
|
||||
background: "#2d3348",
|
||||
transition: animateEnterFrom ? 'background 2s, transform 1s' : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -318,6 +316,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
const addLeft = animateEnterFrom || 0;
|
||||
return {
|
||||
left: addLeft + (155 * index) + 'px',
|
||||
transition: animateEnterFrom ? 'background 2s, transform 1s' : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -326,6 +325,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
||||
|
||||
return {
|
||||
left: addLeft + 155 * this.emptyBlocks.indexOf(block) + 'px',
|
||||
transition: animateEnterFrom ? 'background 2s, transform 1s' : null,
|
||||
background: "#2d3348",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<app-mempool-blocks [hidden]="pageIndex > 0"></app-mempool-blocks>
|
||||
<app-blockchain-blocks [hidden]="pageIndex > 0"></app-blockchain-blocks>
|
||||
<ng-container *ngFor="let page of pages; trackBy: trackByPageFn">
|
||||
<app-blockchain-blocks [static]="true" [offset]="page.offset" [height]="page.height" [count]="blocksPerPage" [loadingTip]="loadingTip" [connected]="connected"></app-blockchain-blocks>
|
||||
<app-blockchain-blocks [static]="true" [offset]="page.offset" [height]="page.height" [dynamicBlockCount]="dynamicBlockCount" [count]="blocksPerPage" [loadingTip]="loadingTip" [connected]="connected"></app-blockchain-blocks>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div id="divider" [hidden]="pageIndex > 0">
|
||||
|
||||
@@ -12,6 +12,7 @@ export class BlockchainComponent implements OnInit, OnDestroy {
|
||||
@Input() pages: any[] = [];
|
||||
@Input() pageIndex: number;
|
||||
@Input() blocksPerPage: number = 8;
|
||||
@Input() dynamicBlockCount: number = 8;
|
||||
@Input() minScrollWidth: number = 0;
|
||||
|
||||
network: string;
|
||||
|
||||
@@ -105,7 +105,6 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
this.reduceMempoolBlocksToFitScreen(this.mempoolBlocks);
|
||||
this.stateService.isTabHidden$.subscribe((tabHidden) => this.tabHidden = tabHidden);
|
||||
this.loadingBlocks$ = this.stateService.isLoadingWebSocket$;
|
||||
|
||||
this.mempoolBlocks$ = merge(
|
||||
of(true),
|
||||
@@ -141,6 +140,13 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
);
|
||||
|
||||
this.loadingBlocks$ = combineLatest([
|
||||
this.stateService.isLoadingWebSocket$,
|
||||
this.mempoolBlocks$
|
||||
]).pipe(map(([loading, mempoolBlocks]) => {
|
||||
return loading || !mempoolBlocks.length;
|
||||
}));
|
||||
|
||||
this.difficultyAdjustments$ = this.stateService.difficultyAdjustment$
|
||||
.pipe(
|
||||
map((da) => {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
(dragstart)="onDragStart($event)"
|
||||
(scroll)="onScroll($event)"
|
||||
>
|
||||
<app-blockchain [pageIndex]="pageIndex" [pages]="pages" [blocksPerPage]="blocksPerPage" [minScrollWidth]="minScrollWidth"></app-blockchain>
|
||||
<app-blockchain [pageIndex]="pageIndex" [pages]="pages" [dynamicBlockCount]="dynamicBlocksAmount" [blocksPerPage]="blocksPerPage" [minScrollWidth]="minScrollWidth"></app-blockchain>
|
||||
</div>
|
||||
<div class="reset-scroll" [class.hidden]="pageIndex === 0" (click)="resetScroll()">
|
||||
<fa-icon [icon]="['fas', 'circle-left']" [fixedWidth]="true"></fa-icon>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Component, ElementRef, HostListener, OnInit, OnDestroy, ViewChild } fro
|
||||
import { Subscription } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { specialBlocks } from '../../app.constants';
|
||||
import { BlockExtended } from '../../interfaces/node-api.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-start',
|
||||
@@ -29,7 +30,8 @@ export class StartComponent implements OnInit, OnDestroy {
|
||||
isMobile: boolean = false;
|
||||
isiOS: boolean = false;
|
||||
blockWidth = 155;
|
||||
dynamicBlocksAmount: number = 8;
|
||||
blocks: BlockExtended[] = [];
|
||||
dynamicBlocksAmount: number;
|
||||
blockCount: number = 0;
|
||||
blocksPerPage: number = 1;
|
||||
pageWidth: number;
|
||||
@@ -49,15 +51,28 @@ export class StartComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.firstPageWidth = 40 + (this.blockWidth * this.dynamicBlocksAmount);
|
||||
this.blockCounterSubscription = this.stateService.blocks$.subscribe(() => {
|
||||
this.blockCount++;
|
||||
this.dynamicBlocksAmount = Math.min(this.blockCount, this.stateService.env.KEEP_BLOCKS_AMOUNT, 8);
|
||||
this.firstPageWidth = 40 + (this.blockWidth * (this.dynamicBlocksAmount || 0));
|
||||
|
||||
this.blockCounterSubscription = this.stateService.blocks$.subscribe(([block, txConfirmed, batch]) => {
|
||||
if (this.blocks.some((b) => b.height === block.height)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.blocks.length && block.height !== this.blocks[0].height + 1) {
|
||||
this.blocks = [];
|
||||
}
|
||||
|
||||
this.blocks.unshift(block);
|
||||
this.blocks = this.blocks.slice(0, Math.min(8, this.stateService.env.KEEP_BLOCKS_AMOUNT));
|
||||
|
||||
this.dynamicBlocksAmount = this.blocks.length;
|
||||
this.firstPageWidth = 40 + (this.blockWidth * this.dynamicBlocksAmount);
|
||||
if (this.blockCount <= Math.min(8, this.stateService.env.KEEP_BLOCKS_AMOUNT)) {
|
||||
|
||||
if (this.blocks.length <= Math.min(8, this.stateService.env.KEEP_BLOCKS_AMOUNT)) {
|
||||
this.onResize();
|
||||
}
|
||||
});
|
||||
|
||||
this.onResize();
|
||||
this.updatePages();
|
||||
this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => {
|
||||
|
||||
@@ -89,7 +89,7 @@ export class StateService {
|
||||
|
||||
networkChanged$ = new ReplaySubject<string>(1);
|
||||
lightningChanged$ = new ReplaySubject<boolean>(1);
|
||||
blocks$: ReplaySubject<[BlockExtended, boolean]>;
|
||||
blocks$: ReplaySubject<[BlockExtended, boolean, boolean]>;
|
||||
transactions$ = new ReplaySubject<TransactionStripped>(6);
|
||||
conversions$ = new ReplaySubject<any>(1);
|
||||
bsqPrice$ = new ReplaySubject<number>(1);
|
||||
@@ -157,7 +157,7 @@ export class StateService {
|
||||
}
|
||||
});
|
||||
|
||||
this.blocks$ = new ReplaySubject<[BlockExtended, boolean]>(this.env.KEEP_BLOCKS_AMOUNT);
|
||||
this.blocks$ = new ReplaySubject<[BlockExtended, boolean, boolean]>(this.env.KEEP_BLOCKS_AMOUNT);
|
||||
|
||||
if (this.env.BASE_MODULE === 'bisq') {
|
||||
this.network = this.env.BASE_MODULE;
|
||||
|
||||
@@ -228,7 +228,7 @@ export class WebsocketService {
|
||||
blocks.forEach((block: BlockExtended) => {
|
||||
if (block.height > this.stateService.latestBlockHeight) {
|
||||
maxHeight = Math.max(maxHeight, block.height);
|
||||
this.stateService.blocks$.next([block, false]);
|
||||
this.stateService.blocks$.next([block, false, true]);
|
||||
}
|
||||
});
|
||||
this.stateService.updateChainTip(maxHeight);
|
||||
@@ -241,7 +241,7 @@ export class WebsocketService {
|
||||
if (response.block) {
|
||||
if (response.block.height > this.stateService.latestBlockHeight) {
|
||||
this.stateService.updateChainTip(response.block.height);
|
||||
this.stateService.blocks$.next([response.block, !!response.txConfirmed]);
|
||||
this.stateService.blocks$.next([response.block, !!response.txConfirmed, false]);
|
||||
}
|
||||
|
||||
if (response.txConfirmed) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet
|
||||
@reboot sleep 60 ; /usr/local/bin/bitcoind -testnet >/dev/null 2>&1
|
||||
@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/electrs-start-testnet
|
||||
@reboot sleep 80 ; /usr/local/bin/bitcoind -signet >/dev/null 2>&1
|
||||
@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/electrs-start-signet
|
||||
@reboot screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet
|
||||
@reboot /usr/local/bin/bitcoind -testnet >/dev/null 2>&1
|
||||
@reboot screen -dmS testnet /bitcoin/electrs/electrs-start-testnet
|
||||
@reboot /usr/local/bin/bitcoind -signet >/dev/null 2>&1
|
||||
@reboot screen -dmS signet /bitcoin/electrs/electrs-start-signet
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# start elements on reboot
|
||||
@reboot sleep 60 ; /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=liquidv1 >/dev/null 2>&1
|
||||
@reboot /usr/local/bin/elementsd -chain=liquidtestnet >/dev/null 2>&1
|
||||
|
||||
# start electrs on reboot
|
||||
@reboot sleep 90 ; screen -dmS liquidv1 /elements/electrs/electrs-start-liquid
|
||||
@reboot sleep 90 ; screen -dmS liquidtestnet /elements/electrs/electrs-start-liquidtestnet
|
||||
@reboot screen -dmS liquidv1 /elements/electrs/electrs-start-liquid
|
||||
@reboot screen -dmS liquidtestnet /elements/electrs/electrs-start-liquidtestnet
|
||||
|
||||
# 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
|
||||
|
||||
@@ -1325,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_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 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 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 180 ; /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\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 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 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 20 ; /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}" -
|
||||
;;
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||
},
|
||||
"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": {
|
||||
"ENABLED": false,
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": 3306,
|
||||
"SOCKET": "/var/run/mysql/mysql.sock",
|
||||
"USERNAME": "__MEMPOOL_BISQ_USER__",
|
||||
"PASSWORD": "__MEMPOOL_BISQ_PASS__",
|
||||
"DATABASE": "mempool_bisq"
|
||||
|
||||
@@ -23,12 +23,13 @@
|
||||
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||
},
|
||||
"ESPLORA": {
|
||||
"REST_API_URL": "http://127.0.0.1:5001",
|
||||
"UNIX_SOCKET_PATH": "/elements/socket/esplora-liquid-mainnet"
|
||||
},
|
||||
"DATABASE": {
|
||||
"ENABLED": true,
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": 3306,
|
||||
"SOCKET": "/var/run/mysql/mysql.sock",
|
||||
"USERNAME": "__MEMPOOL_LIQUID_USER__",
|
||||
"PASSWORD": "__MEMPOOL_LIQUID_PASS__",
|
||||
"DATABASE": "mempool_liquid"
|
||||
|
||||
@@ -23,12 +23,13 @@
|
||||
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||
},
|
||||
"ESPLORA": {
|
||||
"REST_API_URL": "http://127.0.0.1:5004",
|
||||
"UNIX_SOCKET_PATH": "/elements/socket/esplora-liquid-testnet"
|
||||
},
|
||||
"DATABASE": {
|
||||
"ENABLED": true,
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": 3306,
|
||||
"SOCKET": "/var/run/mysql/mysql.sock",
|
||||
"USERNAME": "__MEMPOOL_LIQUIDTESTNET_USER__",
|
||||
"PASSWORD": "__MEMPOOL_LIQUIDTESTNET_PASS__",
|
||||
"DATABASE": "mempool_liquidtestnet"
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||
},
|
||||
"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": {
|
||||
"ENABLED": true,
|
||||
@@ -41,7 +42,7 @@
|
||||
"DATABASE": {
|
||||
"ENABLED": true,
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": 3306,
|
||||
"SOCKET": "/var/run/mysql/mysql.sock",
|
||||
"USERNAME": "__MEMPOOL_MAINNET_LIGHTNING_USER__",
|
||||
"PASSWORD": "__MEMPOOL_MAINNET_LIGHTNING_PASS__",
|
||||
"DATABASE": "mempool_mainnet_lightning"
|
||||
|
||||
@@ -31,12 +31,13 @@
|
||||
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||
},
|
||||
"ESPLORA": {
|
||||
"REST_API_URL": "http://127.0.0.1:5000",
|
||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet"
|
||||
},
|
||||
"DATABASE": {
|
||||
"ENABLED": true,
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": 3306,
|
||||
"SOCKET": "/var/run/mysql/mysql.sock",
|
||||
"USERNAME": "__MEMPOOL_MAINNET_USER__",
|
||||
"PASSWORD": "__MEMPOOL_MAINNET_PASS__",
|
||||
"DATABASE": "mempool"
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||
},
|
||||
"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": {
|
||||
"ENABLED": true,
|
||||
@@ -36,7 +37,7 @@
|
||||
"DATABASE": {
|
||||
"ENABLED": true,
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": 3306,
|
||||
"SOCKET": "/var/run/mysql/mysql.sock",
|
||||
"USERNAME": "__MEMPOOL_SIGNET_LIGHTNING_USER__",
|
||||
"PASSWORD": "__MEMPOOL_SIGNET_LIGHTNING_PASS__",
|
||||
"DATABASE": "mempool_signet_lightning"
|
||||
|
||||
@@ -22,12 +22,13 @@
|
||||
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||
},
|
||||
"ESPLORA": {
|
||||
"REST_API_URL": "http://127.0.0.1:5003",
|
||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet"
|
||||
},
|
||||
"DATABASE": {
|
||||
"ENABLED": true,
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": 3306,
|
||||
"SOCKET": "/var/run/mysql/mysql.sock",
|
||||
"USERNAME": "__MEMPOOL_SIGNET_USER__",
|
||||
"PASSWORD": "__MEMPOOL_SIGNET_PASS__",
|
||||
"DATABASE": "mempool_signet"
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||
},
|
||||
"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": {
|
||||
"ENABLED": true,
|
||||
@@ -36,7 +37,7 @@
|
||||
"DATABASE": {
|
||||
"ENABLED": true,
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": 3306,
|
||||
"SOCKET": "/var/run/mysql/mysql.sock",
|
||||
"USERNAME": "__MEMPOOL_TESTNET_LIGHTNING_USER__",
|
||||
"PASSWORD": "__MEMPOOL_TESTNET_LIGHTNING_PASS__",
|
||||
"DATABASE": "mempool_testnet_lightning"
|
||||
|
||||
@@ -22,12 +22,13 @@
|
||||
"PASSWORD": "__BITCOIN_RPC_PASS__"
|
||||
},
|
||||
"ESPLORA": {
|
||||
"REST_API_URL": "http://127.0.0.1:5002",
|
||||
"UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet"
|
||||
},
|
||||
"DATABASE": {
|
||||
"ENABLED": true,
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": 3306,
|
||||
"SOCKET": "/var/run/mysql/mysql.sock",
|
||||
"USERNAME": "__MEMPOOL_TESTNET_USER__",
|
||||
"PASSWORD": "__MEMPOOL_TESTNET_PASS__",
|
||||
"DATABASE": "mempool_testnet"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# start on reboot
|
||||
@reboot sleep 10 ; $HOME/start
|
||||
@reboot sleep 90 ; $HOME/start
|
||||
|
||||
# daily backup
|
||||
37 13 * * * sleep 30 ; /mempool/mempool.space/backup >/dev/null 2>&1 &
|
||||
|
||||
@@ -1 +1 @@
|
||||
@reboot sleep 120 ; /usr/local/bin/bitcoind >/dev/null 2>&1
|
||||
@reboot /usr/local/bin/bitcoind >/dev/null 2>&1
|
||||
|
||||
Reference in New Issue
Block a user