Merge branch 'master' into docker_vars_test
This commit is contained in:
commit
c382e03e4a
@ -6,14 +6,15 @@ const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first
|
|||||||
|
|
||||||
class Audit {
|
class Audit {
|
||||||
auditBlock(transactions: TransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: TransactionExtended })
|
auditBlock(transactions: TransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: TransactionExtended })
|
||||||
: { censored: string[], added: string[], fresh: string[], score: number, similarity: number } {
|
: { censored: string[], added: string[], fresh: string[], sigop: string[], score: number, similarity: number } {
|
||||||
if (!projectedBlocks?.[0]?.transactionIds || !mempool) {
|
if (!projectedBlocks?.[0]?.transactionIds || !mempool) {
|
||||||
return { censored: [], added: [], fresh: [], score: 0, similarity: 1 };
|
return { censored: [], added: [], fresh: [], sigop: [], score: 0, similarity: 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const matches: string[] = []; // present in both mined block and template
|
const matches: string[] = []; // present in both mined block and template
|
||||||
const added: string[] = []; // present in mined block, not in template
|
const added: string[] = []; // present in mined block, not in template
|
||||||
const fresh: string[] = []; // missing, but firstSeen within PROPAGATION_MARGIN
|
const fresh: string[] = []; // missing, but firstSeen within PROPAGATION_MARGIN
|
||||||
|
const sigop: string[] = []; // missing, but possibly has an adjusted vsize due to high sigop count
|
||||||
const isCensored = {}; // missing, without excuse
|
const isCensored = {}; // missing, without excuse
|
||||||
const isDisplaced = {};
|
const isDisplaced = {};
|
||||||
let displacedWeight = 0;
|
let displacedWeight = 0;
|
||||||
@ -37,6 +38,8 @@ class Audit {
|
|||||||
// tx is recent, may have reached the miner too late for inclusion
|
// tx is recent, may have reached the miner too late for inclusion
|
||||||
if (mempool[txid]?.firstSeen != null && (now - (mempool[txid]?.firstSeen || 0)) <= PROPAGATION_MARGIN) {
|
if (mempool[txid]?.firstSeen != null && (now - (mempool[txid]?.firstSeen || 0)) <= PROPAGATION_MARGIN) {
|
||||||
fresh.push(txid);
|
fresh.push(txid);
|
||||||
|
} else if (this.isPossibleHighSigop(mempool[txid])) {
|
||||||
|
sigop.push(txid);
|
||||||
} else {
|
} else {
|
||||||
isCensored[txid] = true;
|
isCensored[txid] = true;
|
||||||
}
|
}
|
||||||
@ -137,10 +140,19 @@ class Audit {
|
|||||||
censored: Object.keys(isCensored),
|
censored: Object.keys(isCensored),
|
||||||
added,
|
added,
|
||||||
fresh,
|
fresh,
|
||||||
|
sigop,
|
||||||
score,
|
score,
|
||||||
similarity,
|
similarity,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect transactions with a possibly adjusted vsize due to high sigop count
|
||||||
|
// very rough heuristic based on number of OP_CHECKMULTISIG outputs
|
||||||
|
// will miss cases with other sources of sigops
|
||||||
|
isPossibleHighSigop(tx: TransactionExtended): boolean {
|
||||||
|
const numBareMultisig = tx.vout.reduce((count, vout) => count + (vout.scriptpubkey_asm.includes('OP_CHECKMULTISIG') ? 1 : 0), 0);
|
||||||
|
return (numBareMultisig * 400) > tx.vsize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new Audit();
|
export default new Audit();
|
@ -306,7 +306,7 @@ class Blocks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const asciiScriptSig = transactionUtils.hex2ascii(txMinerInfo.vin[0].scriptsig);
|
const asciiScriptSig = transactionUtils.hex2ascii(txMinerInfo.vin[0].scriptsig);
|
||||||
const address = txMinerInfo.vout[0].scriptpubkey_address;
|
const addresses = txMinerInfo.vout.map((vout) => vout.scriptpubkey_address).filter((address) => address);
|
||||||
|
|
||||||
let pools: PoolTag[] = [];
|
let pools: PoolTag[] = [];
|
||||||
if (config.DATABASE.ENABLED === true) {
|
if (config.DATABASE.ENABLED === true) {
|
||||||
@ -316,11 +316,13 @@ class Blocks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < pools.length; ++i) {
|
for (let i = 0; i < pools.length; ++i) {
|
||||||
if (address !== undefined) {
|
if (addresses.length) {
|
||||||
const addresses: string[] = typeof pools[i].addresses === 'string' ?
|
const poolAddresses: string[] = typeof pools[i].addresses === 'string' ?
|
||||||
JSON.parse(pools[i].addresses) : pools[i].addresses;
|
JSON.parse(pools[i].addresses) : pools[i].addresses;
|
||||||
if (addresses.indexOf(address) !== -1) {
|
for (let y = 0; y < poolAddresses.length; y++) {
|
||||||
return pools[i];
|
if (addresses.indexOf(poolAddresses[y]) !== -1) {
|
||||||
|
return pools[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
|||||||
import { RowDataPacket } from 'mysql2';
|
import { RowDataPacket } from 'mysql2';
|
||||||
|
|
||||||
class DatabaseMigration {
|
class DatabaseMigration {
|
||||||
private static currentVersion = 59;
|
private static currentVersion = 60;
|
||||||
private queryTimeout = 3600_000;
|
private queryTimeout = 3600_000;
|
||||||
private statisticsAddedIndexed = false;
|
private statisticsAddedIndexed = false;
|
||||||
private uniqueLogs: string[] = [];
|
private uniqueLogs: string[] = [];
|
||||||
@ -516,6 +516,11 @@ class DatabaseMigration {
|
|||||||
// https://github.com/mempool/mempool/issues/3360
|
// https://github.com/mempool/mempool/issues/3360
|
||||||
await this.$executeQuery(`TRUNCATE prices`);
|
await this.$executeQuery(`TRUNCATE prices`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 60 && isBitcoin === true) {
|
||||||
|
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD sigop_txs JSON DEFAULT "[]"');
|
||||||
|
await this.updateToSchemaVersion(60);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,6 +21,7 @@ class DiskCache {
|
|||||||
private static RBF_FILE_NAME = config.MEMPOOL.CACHE_DIR + '/rbfcache.json';
|
private static RBF_FILE_NAME = config.MEMPOOL.CACHE_DIR + '/rbfcache.json';
|
||||||
private static CHUNK_FILES = 25;
|
private static CHUNK_FILES = 25;
|
||||||
private isWritingCache = false;
|
private isWritingCache = false;
|
||||||
|
private ignoreBlocksCache = false;
|
||||||
|
|
||||||
private semaphore: { resume: (() => void)[], locks: number } = {
|
private semaphore: { resume: (() => void)[], locks: number } = {
|
||||||
resume: [],
|
resume: [],
|
||||||
@ -218,8 +219,13 @@ class DiskCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await memPool.$setMempool(data.mempool);
|
await memPool.$setMempool(data.mempool);
|
||||||
blocks.setBlocks(data.blocks);
|
if (!this.ignoreBlocksCache) {
|
||||||
blocks.setBlockSummaries(data.blockSummaries || []);
|
blocks.setBlocks(data.blocks);
|
||||||
|
blocks.setBlockSummaries(data.blockSummaries || []);
|
||||||
|
} else {
|
||||||
|
logger.info('Re-saving cache with empty recent blocks data');
|
||||||
|
await this.$saveCacheToDisk(true);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn('Failed to parse mempoool and blocks cache. Skipping. Reason: ' + (e instanceof Error ? e.message : e));
|
logger.warn('Failed to parse mempoool and blocks cache. Skipping. Reason: ' + (e instanceof Error ? e.message : e));
|
||||||
}
|
}
|
||||||
@ -273,6 +279,10 @@ class DiskCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setIgnoreBlocksCache(): void {
|
||||||
|
this.ignoreBlocksCache = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new DiskCache();
|
export default new DiskCache();
|
||||||
|
@ -41,7 +41,7 @@ class PoolsParser {
|
|||||||
public async migratePoolsJson(): Promise<void> {
|
public async migratePoolsJson(): Promise<void> {
|
||||||
// We also need to wipe the backend cache to make sure we don't serve blocks with
|
// We also need to wipe the backend cache to make sure we don't serve blocks with
|
||||||
// the wrong mining pool (usually happen with unknown blocks)
|
// the wrong mining pool (usually happen with unknown blocks)
|
||||||
diskCache.wipeCache();
|
diskCache.setIgnoreBlocksCache();
|
||||||
|
|
||||||
await this.$insertUnknownPool();
|
await this.$insertUnknownPool();
|
||||||
|
|
||||||
@ -118,10 +118,6 @@ class PoolsParser {
|
|||||||
* @param pool
|
* @param pool
|
||||||
*/
|
*/
|
||||||
private async $deleteBlocksForPool(pool: PoolTag): Promise<void> {
|
private async $deleteBlocksForPool(pool: PoolTag): Promise<void> {
|
||||||
if (config.MEMPOOL.AUTOMATIC_BLOCK_REINDEXING === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get oldest blocks mined by the pool and assume pools-v2.json updates only concern most recent years
|
// Get oldest blocks mined by the pool and assume pools-v2.json updates only concern most recent years
|
||||||
// Ignore early days of Bitcoin as there were no mining pool yet
|
// Ignore early days of Bitcoin as there were no mining pool yet
|
||||||
const [oldestPoolBlock]: any[] = await DB.query(`
|
const [oldestPoolBlock]: any[] = await DB.query(`
|
||||||
|
@ -557,7 +557,7 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Common.indexingEnabled() && memPool.isInSync()) {
|
if (Common.indexingEnabled() && memPool.isInSync()) {
|
||||||
const { censored, added, fresh, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool);
|
const { censored, added, fresh, sigop, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool);
|
||||||
const matchRate = Math.round(score * 100 * 100) / 100;
|
const matchRate = Math.round(score * 100 * 100) / 100;
|
||||||
|
|
||||||
const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions.map((tx) => {
|
const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions.map((tx) => {
|
||||||
@ -584,6 +584,7 @@ class WebsocketHandler {
|
|||||||
addedTxs: added,
|
addedTxs: added,
|
||||||
missingTxs: censored,
|
missingTxs: censored,
|
||||||
freshTxs: fresh,
|
freshTxs: fresh,
|
||||||
|
sigopTxs: sigop,
|
||||||
matchRate: matchRate,
|
matchRate: matchRate,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ export interface BlockAudit {
|
|||||||
hash: string,
|
hash: string,
|
||||||
missingTxs: string[],
|
missingTxs: string[],
|
||||||
freshTxs: string[],
|
freshTxs: string[],
|
||||||
|
sigopTxs: string[],
|
||||||
addedTxs: string[],
|
addedTxs: string[],
|
||||||
matchRate: number,
|
matchRate: number,
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@ import { BlockAudit, AuditScore } from '../mempool.interfaces';
|
|||||||
class BlocksAuditRepositories {
|
class BlocksAuditRepositories {
|
||||||
public async $saveAudit(audit: BlockAudit): Promise<void> {
|
public async $saveAudit(audit: BlockAudit): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, fresh_txs, match_rate)
|
await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, fresh_txs, sigop_txs, match_rate)
|
||||||
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs),
|
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs),
|
||||||
JSON.stringify(audit.addedTxs), JSON.stringify(audit.freshTxs), audit.matchRate]);
|
JSON.stringify(audit.addedTxs), JSON.stringify(audit.freshTxs), JSON.stringify(audit.sigopTxs), audit.matchRate]);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
||||||
logger.debug(`Cannot save block audit for block ${audit.hash} because it has already been indexed, ignoring`);
|
logger.debug(`Cannot save block audit for block ${audit.hash} because it has already been indexed, ignoring`);
|
||||||
@ -52,7 +52,7 @@ class BlocksAuditRepositories {
|
|||||||
const [rows]: any[] = await DB.query(
|
const [rows]: any[] = await DB.query(
|
||||||
`SELECT blocks.height, blocks.hash as id, UNIX_TIMESTAMP(blocks.blockTimestamp) as timestamp, blocks.size,
|
`SELECT blocks.height, blocks.hash as id, UNIX_TIMESTAMP(blocks.blockTimestamp) as timestamp, blocks.size,
|
||||||
blocks.weight, blocks.tx_count,
|
blocks.weight, blocks.tx_count,
|
||||||
transactions, template, missing_txs as missingTxs, added_txs as addedTxs, fresh_txs as freshTxs, match_rate as matchRate
|
transactions, template, missing_txs as missingTxs, added_txs as addedTxs, fresh_txs as freshTxs, sigop_txs as sigopTxs, match_rate as matchRate
|
||||||
FROM blocks_audits
|
FROM blocks_audits
|
||||||
JOIN blocks ON blocks.hash = blocks_audits.hash
|
JOIN blocks ON blocks.hash = blocks_audits.hash
|
||||||
JOIN blocks_summaries ON blocks_summaries.id = blocks_audits.hash
|
JOIN blocks_summaries ON blocks_summaries.id = blocks_audits.hash
|
||||||
@ -63,6 +63,7 @@ class BlocksAuditRepositories {
|
|||||||
rows[0].missingTxs = JSON.parse(rows[0].missingTxs);
|
rows[0].missingTxs = JSON.parse(rows[0].missingTxs);
|
||||||
rows[0].addedTxs = JSON.parse(rows[0].addedTxs);
|
rows[0].addedTxs = JSON.parse(rows[0].addedTxs);
|
||||||
rows[0].freshTxs = JSON.parse(rows[0].freshTxs);
|
rows[0].freshTxs = JSON.parse(rows[0].freshTxs);
|
||||||
|
rows[0].sigopTxs = JSON.parse(rows[0].sigopTxs);
|
||||||
rows[0].transactions = JSON.parse(rows[0].transactions);
|
rows[0].transactions = JSON.parse(rows[0].transactions);
|
||||||
rows[0].template = JSON.parse(rows[0].template);
|
rows[0].template = JSON.parse(rows[0].template);
|
||||||
|
|
||||||
|
@ -205,9 +205,9 @@
|
|||||||
<img class="image" src="/resources/profile/nix-bitcoin.png" />
|
<img class="image" src="/resources/profile/nix-bitcoin.png" />
|
||||||
<span>NixOS</span>
|
<span>NixOS</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/Start9Labs/embassy-os" target="_blank" title="EmbassyOS">
|
<a href="https://github.com/Start9Labs/start-os" target="_blank" title="StartOS">
|
||||||
<img class="image" src="/resources/profile/start9.png" />
|
<img class="image" src="/resources/profile/start9.png" />
|
||||||
<span>EmbassyOS</span>
|
<span>StartOS</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/btcpayserver/btcpayserver" target="_blank" title="BTCPay Server">
|
<a href="https://github.com/btcpayserver/btcpayserver" target="_blank" title="BTCPay Server">
|
||||||
<img class="image not-rounded" src="/resources/profile/btcpayserver.svg" />
|
<img class="image not-rounded" src="/resources/profile/btcpayserver.svg" />
|
||||||
|
@ -37,7 +37,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
value: number;
|
value: number;
|
||||||
feerate: number;
|
feerate: number;
|
||||||
rate?: number;
|
rate?: number;
|
||||||
status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||||
context?: 'projected' | 'actual';
|
context?: 'projected' | 'actual';
|
||||||
scene?: BlockScene;
|
scene?: BlockScene;
|
||||||
|
|
||||||
@ -171,6 +171,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
case 'censored':
|
case 'censored':
|
||||||
return auditColors.censored;
|
return auditColors.censored;
|
||||||
case 'missing':
|
case 'missing':
|
||||||
|
case 'sigop':
|
||||||
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
||||||
case 'fresh':
|
case 'fresh':
|
||||||
return auditColors.missing;
|
return auditColors.missing;
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
<td *ngSwitchCase="'found'"><span class="badge badge-success" i18n="transaction.audit.match">Match</span></td>
|
<td *ngSwitchCase="'found'"><span class="badge badge-success" i18n="transaction.audit.match">Match</span></td>
|
||||||
<td *ngSwitchCase="'censored'"><span class="badge badge-danger" i18n="transaction.audit.removed">Removed</span></td>
|
<td *ngSwitchCase="'censored'"><span class="badge badge-danger" i18n="transaction.audit.removed">Removed</span></td>
|
||||||
<td *ngSwitchCase="'missing'"><span class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span></td>
|
<td *ngSwitchCase="'missing'"><span class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span></td>
|
||||||
|
<td *ngSwitchCase="'sigop'"><span class="badge badge-warning" i18n="transaction.audit.sigop">High sigop count</span></td>
|
||||||
<td *ngSwitchCase="'fresh'"><span class="badge badge-warning" i18n="transaction.audit.recently-broadcasted">Recently broadcasted</span></td>
|
<td *ngSwitchCase="'fresh'"><span class="badge badge-warning" i18n="transaction.audit.recently-broadcasted">Recently broadcasted</span></td>
|
||||||
<td *ngSwitchCase="'added'"><span class="badge badge-warning" i18n="transaction.audit.added">Added</span></td>
|
<td *ngSwitchCase="'added'"><span class="badge badge-warning" i18n="transaction.audit.added">Added</span></td>
|
||||||
<td *ngSwitchCase="'selected'"><span class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span></td>
|
<td *ngSwitchCase="'selected'"><span class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span></td>
|
||||||
|
@ -335,6 +335,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
const isMissing = {};
|
const isMissing = {};
|
||||||
const isSelected = {};
|
const isSelected = {};
|
||||||
const isFresh = {};
|
const isFresh = {};
|
||||||
|
const isSigop = {};
|
||||||
this.numMissing = 0;
|
this.numMissing = 0;
|
||||||
this.numUnexpected = 0;
|
this.numUnexpected = 0;
|
||||||
|
|
||||||
@ -354,6 +355,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
for (const txid of blockAudit.freshTxs || []) {
|
for (const txid of blockAudit.freshTxs || []) {
|
||||||
isFresh[txid] = true;
|
isFresh[txid] = true;
|
||||||
}
|
}
|
||||||
|
for (const txid of blockAudit.sigopTxs || []) {
|
||||||
|
isSigop[txid] = true;
|
||||||
|
}
|
||||||
// set transaction statuses
|
// set transaction statuses
|
||||||
for (const tx of blockAudit.template) {
|
for (const tx of blockAudit.template) {
|
||||||
tx.context = 'projected';
|
tx.context = 'projected';
|
||||||
@ -362,7 +366,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
} else if (inBlock[tx.txid]) {
|
} else if (inBlock[tx.txid]) {
|
||||||
tx.status = 'found';
|
tx.status = 'found';
|
||||||
} else {
|
} else {
|
||||||
tx.status = isFresh[tx.txid] ? 'fresh' : 'missing';
|
tx.status = isFresh[tx.txid] ? 'fresh' : (isSigop[tx.txid] ? 'sigop' : 'missing');
|
||||||
isMissing[tx.txid] = true;
|
isMissing[tx.txid] = true;
|
||||||
this.numMissing++;
|
this.numMissing++;
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import { download, formatterXAxis, formatterXAxisLabel } from '../../shared/grap
|
|||||||
export class MempoolGraphComponent implements OnInit, OnChanges {
|
export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||||
@Input() data: any[];
|
@Input() data: any[];
|
||||||
@Input() filterSize = 100000;
|
@Input() filterSize = 100000;
|
||||||
|
@Input() limitFilterFee = 1;
|
||||||
@Input() height: number | string = 200;
|
@Input() height: number | string = 200;
|
||||||
@Input() top: number | string = 20;
|
@Input() top: number | string = 20;
|
||||||
@Input() right: number | string = 10;
|
@Input() right: number | string = 10;
|
||||||
@ -40,7 +41,9 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
};
|
};
|
||||||
windowPreference: string;
|
windowPreference: string;
|
||||||
hoverIndexSerie = 0;
|
hoverIndexSerie = 0;
|
||||||
|
maxFee: number;
|
||||||
feeLimitIndex: number;
|
feeLimitIndex: number;
|
||||||
|
maxFeeIndex: number;
|
||||||
feeLevelsOrdered = [];
|
feeLevelsOrdered = [];
|
||||||
chartColorsOrdered = chartColors;
|
chartColorsOrdered = chartColors;
|
||||||
inverted: boolean;
|
inverted: boolean;
|
||||||
@ -98,8 +101,9 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateArray(mempoolStats: OptimizedMempoolStats[]) {
|
generateArray(mempoolStats: OptimizedMempoolStats[]) {
|
||||||
let finalArray: number[][][] = [];
|
const finalArray: number[][][] = [];
|
||||||
let feesArray: number[][] = [];
|
let feesArray: number[][] = [];
|
||||||
|
|
||||||
let maxTier = 0;
|
let maxTier = 0;
|
||||||
for (let index = 37; index > -1; index--) {
|
for (let index = 37; index > -1; index--) {
|
||||||
feesArray = [];
|
feesArray = [];
|
||||||
@ -111,7 +115,8 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
});
|
});
|
||||||
finalArray.push(feesArray);
|
finalArray.push(feesArray);
|
||||||
}
|
}
|
||||||
this.feeLimitIndex = maxTier;
|
this.maxFeeIndex = maxTier;
|
||||||
|
|
||||||
finalArray.reverse();
|
finalArray.reverse();
|
||||||
return finalArray;
|
return finalArray;
|
||||||
}
|
}
|
||||||
@ -124,7 +129,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
const newColors = [];
|
const newColors = [];
|
||||||
for (let index = 0; index < series.length; index++) {
|
for (let index = 0; index < series.length; index++) {
|
||||||
const value = series[index];
|
const value = series[index];
|
||||||
if (index < this.feeLimitIndex) {
|
if (index >= this.feeLimitIndex && index <= this.maxFeeIndex) {
|
||||||
newColors.push(this.chartColorsOrdered[index]);
|
newColors.push(this.chartColorsOrdered[index]);
|
||||||
seriesGraph.push({
|
seriesGraph.push({
|
||||||
zlevel: 0,
|
zlevel: 0,
|
||||||
@ -383,21 +388,26 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
orderLevels() {
|
orderLevels() {
|
||||||
this.feeLevelsOrdered = [];
|
this.feeLevelsOrdered = [];
|
||||||
let maxIndex = Math.min(feeLevels.length, this.feeLimitIndex);
|
const maxIndex = Math.min(feeLevels.length, this.maxFeeIndex);
|
||||||
for (let i = 0; i < maxIndex; i++) {
|
for (let i = 0; i < feeLevels.length; i++) {
|
||||||
|
if (feeLevels[i] === this.limitFilterFee) {
|
||||||
|
this.feeLimitIndex = i;
|
||||||
|
}
|
||||||
|
if (feeLevels[i] <= feeLevels[this.maxFeeIndex]) {
|
||||||
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
|
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
|
||||||
if (i === maxIndex - 1) {
|
if (i === maxIndex || feeLevels[i] == null) {
|
||||||
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)}+`);
|
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)}+`);
|
||||||
} else {
|
} else {
|
||||||
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)} - ${(feeLevels[i + 1] / 10).toFixed(1)}`);
|
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)} - ${(feeLevels[i + 1] / 10).toFixed(1)}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (i === maxIndex - 1) {
|
if (i === maxIndex || feeLevels[i] == null) {
|
||||||
this.feeLevelsOrdered.push(`${feeLevels[i]}+`);
|
this.feeLevelsOrdered.push(`${feeLevels[i]}+`);
|
||||||
} else {
|
} else {
|
||||||
this.feeLevelsOrdered.push(`${feeLevels[i]} - ${feeLevels[i + 1]}`);
|
this.feeLevelsOrdered.push(`${feeLevels[i]} - ${feeLevels[i + 1]}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.chartColorsOrdered = chartColors.slice(0, this.feeLevelsOrdered.length);
|
this.chartColorsOrdered = chartColors.slice(0, this.feeLevelsOrdered.length);
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
<div class="dropdown-fees" ngbDropdownMenu aria-labelledby="dropdownFees">
|
<div class="dropdown-fees" ngbDropdownMenu aria-labelledby="dropdownFees">
|
||||||
<ul>
|
<ul>
|
||||||
<ng-template ngFor let-feeData let-i="index" [ngForOf]="feeLevelDropdownData">
|
<ng-template ngFor let-feeData let-i="index" [ngForOf]="feeLevelDropdownData">
|
||||||
<ng-template [ngIf]="feeData.fee <= 400">
|
<ng-template [ngIf]="feeData.fee <= (feeLevels[maxFeeIndex])">
|
||||||
<li (click)="filterFeeIndex = feeData.fee"
|
<li (click)="filterFeeIndex = feeData.fee"
|
||||||
[class]="filterFeeIndex > feeData.fee ? 'inactive' : ''">
|
[class]="filterFeeIndex > feeData.fee ? 'inactive' : ''">
|
||||||
<span class="square" [ngStyle]="{'backgroundColor': feeData.color}"></span>
|
<span class="square" [ngStyle]="{'backgroundColor': feeData.color}"></span>
|
||||||
@ -84,7 +84,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="incoming-transactions-graph">
|
<div class="incoming-transactions-graph">
|
||||||
<app-mempool-graph #mempoolgraph dir="ltr" [template]="'advanced'" [height]="500" [left]="65" [right]="10"
|
<app-mempool-graph #mempoolgraph dir="ltr" [template]="'advanced'"
|
||||||
|
[limitFilterFee]="filterFeeIndex" [height]="500" [left]="65" [right]="10"
|
||||||
[data]="mempoolStats && mempoolStats.length ? mempoolStats : null"></app-mempool-graph>
|
[data]="mempoolStats && mempoolStats.length ? mempoolStats : null"></app-mempool-graph>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,7 +30,9 @@ export class StatisticsComponent implements OnInit {
|
|||||||
spinnerLoading = false;
|
spinnerLoading = false;
|
||||||
feeLevels = feeLevels;
|
feeLevels = feeLevels;
|
||||||
chartColors = chartColors;
|
chartColors = chartColors;
|
||||||
|
filterSize = 100000;
|
||||||
filterFeeIndex = 1;
|
filterFeeIndex = 1;
|
||||||
|
maxFeeIndex: number;
|
||||||
dropDownOpen = false;
|
dropDownOpen = false;
|
||||||
|
|
||||||
mempoolStats: OptimizedMempoolStats[] = [];
|
mempoolStats: OptimizedMempoolStats[] = [];
|
||||||
@ -134,6 +136,16 @@ export class StatisticsComponent implements OnInit {
|
|||||||
mempoolStats.reverse();
|
mempoolStats.reverse();
|
||||||
const labels = mempoolStats.map(stats => stats.added);
|
const labels = mempoolStats.map(stats => stats.added);
|
||||||
|
|
||||||
|
let maxTier = 0;
|
||||||
|
for (let index = 37; index > -1; index--) {
|
||||||
|
mempoolStats.forEach((stats) => {
|
||||||
|
if (stats.vsizes[index] >= this.filterSize) {
|
||||||
|
maxTier = Math.max(maxTier, index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.maxFeeIndex = maxTier;
|
||||||
|
|
||||||
this.capExtremeVbytesValues();
|
this.capExtremeVbytesValues();
|
||||||
|
|
||||||
this.mempoolTransactionsWeightPerSecondData = {
|
this.mempoolTransactionsWeightPerSecondData = {
|
||||||
@ -152,27 +164,42 @@ export class StatisticsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setFeeLevelDropdownData() {
|
setFeeLevelDropdownData() {
|
||||||
let _feeLevels = feeLevels
|
let _feeLevels = feeLevels;
|
||||||
let _chartColors = chartColors;
|
let _chartColors = chartColors;
|
||||||
if (!this.inverted) {
|
if (!this.inverted) {
|
||||||
_feeLevels = [...feeLevels].reverse();
|
_feeLevels = [...feeLevels].reverse();
|
||||||
_chartColors = [...chartColors].reverse();
|
_chartColors = [...chartColors].reverse();
|
||||||
}
|
}
|
||||||
_feeLevels.forEach((fee, i) => {
|
_feeLevels.forEach((fee, i) => {
|
||||||
|
let range;
|
||||||
|
const nextIndex = this.inverted ? i + 1 : i - 1;
|
||||||
|
if (this.stateService.isLiquid()) {
|
||||||
|
if (_feeLevels[nextIndex] == null) {
|
||||||
|
range = `${(_feeLevels[i] / 10).toFixed(1)}+`;
|
||||||
|
} else {
|
||||||
|
range = `${(_feeLevels[i] / 10).toFixed(1)} - ${(_feeLevels[nextIndex] / 10).toFixed(1)}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_feeLevels[nextIndex] == null) {
|
||||||
|
range = `${_feeLevels[i]}+`;
|
||||||
|
} else {
|
||||||
|
range = `${_feeLevels[i]} - ${_feeLevels[nextIndex]}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this.inverted) {
|
if (this.inverted) {
|
||||||
this.feeLevelDropdownData.push({
|
this.feeLevelDropdownData.push({
|
||||||
fee: fee,
|
fee: fee,
|
||||||
range: this.stateService.isLiquid() ? `${(_feeLevels[i] / 10).toFixed(1)} - ${(_feeLevels[i + 1] / 10).toFixed(1)}` : `${_feeLevels[i]} - ${_feeLevels[i + 1]}`,
|
range,
|
||||||
color: _chartColors[i],
|
color: _chartColors[i],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.feeLevelDropdownData.push({
|
this.feeLevelDropdownData.push({
|
||||||
fee: fee,
|
fee: fee,
|
||||||
range: this.stateService.isLiquid() ? `${(_feeLevels[i] / 10).toFixed(1)} - ${(_feeLevels[i - 1] / 10).toFixed(1)}` : `${_feeLevels[i]} - ${_feeLevels[i - 1]}`,
|
range,
|
||||||
color: _chartColors[i - 1],
|
color: _chartColors[i - 1],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
<div class="mempool-graph">
|
<div class="mempool-graph">
|
||||||
<app-mempool-graph
|
<app-mempool-graph
|
||||||
[template]="'widget'"
|
[template]="'widget'"
|
||||||
[filterSize]="1000000"
|
|
||||||
[data]="mempoolStats.value?.mempool"
|
[data]="mempoolStats.value?.mempool"
|
||||||
[windowPreferenceOverride]="'2h'"
|
[windowPreferenceOverride]="'2h'"
|
||||||
></app-mempool-graph>
|
></app-mempool-graph>
|
||||||
|
@ -155,7 +155,7 @@ export interface TransactionStripped {
|
|||||||
fee: number;
|
fee: number;
|
||||||
vsize: number;
|
vsize: number;
|
||||||
value: number;
|
value: number;
|
||||||
status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RbfTransaction extends TransactionStripped {
|
interface RbfTransaction extends TransactionStripped {
|
||||||
|
@ -76,7 +76,7 @@ export interface TransactionStripped {
|
|||||||
vsize: number;
|
vsize: number;
|
||||||
value: number;
|
value: number;
|
||||||
rate?: number; // effective fee rate
|
rate?: number; // effective fee rate
|
||||||
status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||||
context?: 'projected' | 'actual';
|
context?: 'projected' | 'actual';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1312,8 +1312,13 @@ case $OS in
|
|||||||
osSudo "${ROOT_USER}" pw usermod ${MEMPOOL_USER} -G "${CLN_GROUP}"
|
osSudo "${ROOT_USER}" pw usermod ${MEMPOOL_USER} -G "${CLN_GROUP}"
|
||||||
osSudo "${ROOT_USER}" chsh -s `which zsh` "${CLN_USER}"
|
osSudo "${ROOT_USER}" chsh -s `which zsh` "${CLN_USER}"
|
||||||
echo "export PATH=$PATH:$HOME/.local/bin" >> "${CLN_HOME}/.zshrc"
|
echo "export PATH=$PATH:$HOME/.local/bin" >> "${CLN_HOME}/.zshrc"
|
||||||
osSudo "${ROOT_USER}" mkdir -p "${CLN_HOME}/.lightning/{bitcoin,signet,testnet}"
|
osSudo "${ROOT_USER}" mkdir -p "${CLN_HOME}/.lightning/bitcoin"
|
||||||
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning" "${CLN_HOME}/.lightning/{bitcoin,signet,testnet}"
|
osSudo "${ROOT_USER}" mkdir -p "${CLN_HOME}/.lightning/signet"
|
||||||
|
osSudo "${ROOT_USER}" mkdir -p "${CLN_HOME}/.lightning/testnet"
|
||||||
|
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning"
|
||||||
|
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning/bitcoin"
|
||||||
|
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning/signet"
|
||||||
|
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning/testnet"
|
||||||
osSudo "${ROOT_USER}" chown -R "${CLN_USER}:${CLN_GROUP}" "${CLN_HOME}"
|
osSudo "${ROOT_USER}" chown -R "${CLN_USER}:${CLN_GROUP}" "${CLN_HOME}"
|
||||||
echo "[*] Creating symlink to .bitcoin folder"
|
echo "[*] Creating symlink to .bitcoin folder"
|
||||||
osSudo "${CLN_USER}" ln -s "${BITCOIN_HOME}/.bitcoin" "${CLN_HOME}/.bitcoin"
|
osSudo "${CLN_USER}" ln -s "${BITCOIN_HOME}/.bitcoin" "${CLN_HOME}/.bitcoin"
|
||||||
@ -1321,16 +1326,18 @@ case $OS in
|
|||||||
echo "[*] Installing Core Lightning package"
|
echo "[*] Installing Core Lightning package"
|
||||||
osPackageInstall ${CLN_PKG}
|
osPackageInstall ${CLN_PKG}
|
||||||
|
|
||||||
echo "[*] Installing Core Lightning mainnet Cronjob"
|
######## FIXME: this code doesn't work properly, needs fixing
|
||||||
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' )
|
# echo "[*] Installing Core Lightning mainnet Cronjob"
|
||||||
|
# public_ipv4=$( ifconfig | grep 'inet ' | awk -F ' ' '{ print $2 }' | grep -v '^103\.165\.192\.' | grep -v '^127\.0\.0\.1' )
|
||||||
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"
|
# public_ipv6=$( ifconfig | grep 'inet6' | awk -F ' ' '{ print $2 }' | grep -v '^2001:df6:7280::' | grep -v '^fe80::' | grep -v '^::1' )
|
||||||
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 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 20 ; /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\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+="1 * * * * /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\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"
|
||||||
echo "${crontab_cln}" | crontab -u "${CLN_USER}" -
|
# 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}" -
|
||||||
;;
|
;;
|
||||||
Debian)
|
Debian)
|
||||||
;;
|
;;
|
||||||
@ -1813,7 +1820,7 @@ echo "[*] Adding MySQL configuration"
|
|||||||
case $OS in
|
case $OS in
|
||||||
|
|
||||||
FreeBSD)
|
FreeBSD)
|
||||||
osSudo "${ROOT_USER}" service mysql-server start
|
osSudo "${ROOT_USER}" service mysql-server onestart
|
||||||
;;
|
;;
|
||||||
Debian)
|
Debian)
|
||||||
osSudo "${ROOT_USER}" service mysql start
|
osSudo "${ROOT_USER}" service mysql start
|
||||||
|
@ -59,7 +59,7 @@ location = / {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# cache /<lang>/main.f40e91d908a068a2.js forever since they never change
|
# cache /<lang>/main.f40e91d908a068a2.js forever since they never change
|
||||||
location ~ ^/([a-z][a-z])/(.+\..+\.(js|css)) {
|
location ~ ^/([a-z][a-z])/(.+\..+\.(js|css))$ {
|
||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
expires 1y;
|
expires 1y;
|
||||||
}
|
}
|
||||||
@ -92,7 +92,7 @@ location /resources/config. {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# cache /main.f40e91d908a068a2.js forever since they never change
|
# cache /main.f40e91d908a068a2.js forever since they never change
|
||||||
location ~* ^/.+\..+\.(js|css) {
|
location ~* ^/.+\..+\.(js|css)$ {
|
||||||
try_files /$lang/$uri /en-US/$uri =404;
|
try_files /$lang/$uri /en-US/$uri =404;
|
||||||
expires 1y;
|
expires 1y;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user