Merge branch 'master' into mononaut/fix-mined-rbf-conflicts
This commit is contained in:
commit
f871300bfb
@ -1,19 +1,21 @@
|
|||||||
import config from '../config';
|
import config from '../config';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import { TransactionExtended, MempoolBlockWithTransactions } from '../mempool.interfaces';
|
import { TransactionExtended, MempoolBlockWithTransactions } from '../mempool.interfaces';
|
||||||
|
import rbfCache from './rbf-cache';
|
||||||
|
|
||||||
const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first seen after which it is assumed to have propagated to all miners
|
const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first seen after which it is assumed to have propagated to all miners
|
||||||
|
|
||||||
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[], sigop: string[], score: number, similarity: number } {
|
: { censored: string[], added: string[], fresh: string[], sigop: string[], fullrbf: string[], score: number, similarity: number } {
|
||||||
if (!projectedBlocks?.[0]?.transactionIds || !mempool) {
|
if (!projectedBlocks?.[0]?.transactionIds || !mempool) {
|
||||||
return { censored: [], added: [], fresh: [], sigop: [], score: 0, similarity: 1 };
|
return { censored: [], added: [], fresh: [], sigop: [], fullrbf: [], 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 fullrbf: string[] = []; // either missing or present, and part of a fullrbf replacement
|
||||||
const isCensored = {}; // missing, without excuse
|
const isCensored = {}; // missing, without excuse
|
||||||
const isDisplaced = {};
|
const isDisplaced = {};
|
||||||
let displacedWeight = 0;
|
let displacedWeight = 0;
|
||||||
@ -35,7 +37,9 @@ class Audit {
|
|||||||
for (const txid of projectedBlocks[0].transactionIds) {
|
for (const txid of projectedBlocks[0].transactionIds) {
|
||||||
if (!inBlock[txid]) {
|
if (!inBlock[txid]) {
|
||||||
// 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 (rbfCache.isFullRbf(txid)) {
|
||||||
|
fullrbf.push(txid);
|
||||||
|
} else if (mempool[txid]?.firstSeen != null && (now - (mempool[txid]?.firstSeen || 0)) <= PROPAGATION_MARGIN) {
|
||||||
fresh.push(txid);
|
fresh.push(txid);
|
||||||
} else {
|
} else {
|
||||||
isCensored[txid] = true;
|
isCensored[txid] = true;
|
||||||
@ -91,7 +95,9 @@ class Audit {
|
|||||||
if (inTemplate[tx.txid]) {
|
if (inTemplate[tx.txid]) {
|
||||||
matches.push(tx.txid);
|
matches.push(tx.txid);
|
||||||
} else {
|
} else {
|
||||||
if (!isDisplaced[tx.txid]) {
|
if (rbfCache.isFullRbf(tx.txid)) {
|
||||||
|
fullrbf.push(tx.txid);
|
||||||
|
} else if (!isDisplaced[tx.txid]) {
|
||||||
added.push(tx.txid);
|
added.push(tx.txid);
|
||||||
}
|
}
|
||||||
overflowWeight += tx.weight;
|
overflowWeight += tx.weight;
|
||||||
@ -138,6 +144,7 @@ class Audit {
|
|||||||
added,
|
added,
|
||||||
fresh,
|
fresh,
|
||||||
sigop: [],
|
sigop: [],
|
||||||
|
fullrbf,
|
||||||
score,
|
score,
|
||||||
similarity,
|
similarity,
|
||||||
};
|
};
|
||||||
|
@ -29,6 +29,7 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
weight: block.weight,
|
weight: block.weight,
|
||||||
previousblockhash: block.previousblockhash,
|
previousblockhash: block.previousblockhash,
|
||||||
mediantime: block.mediantime,
|
mediantime: block.mediantime,
|
||||||
|
stale: block.confirmations === -1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +89,7 @@ export namespace IEsploraApi {
|
|||||||
weight: number;
|
weight: number;
|
||||||
previousblockhash: string;
|
previousblockhash: string;
|
||||||
mediantime: number;
|
mediantime: number;
|
||||||
|
stale: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Address {
|
export interface Address {
|
||||||
|
@ -656,10 +656,6 @@ class Blocks {
|
|||||||
const blockSummary: BlockSummary = this.summarizeBlockTransactions(block.id, cpfpSummary.transactions);
|
const blockSummary: BlockSummary = this.summarizeBlockTransactions(block.id, cpfpSummary.transactions);
|
||||||
this.updateTimerProgress(timer, `got block data for ${this.currentBlockHeight}`);
|
this.updateTimerProgress(timer, `got block data for ${this.currentBlockHeight}`);
|
||||||
|
|
||||||
// start async callbacks
|
|
||||||
this.updateTimerProgress(timer, `starting async callbacks for ${this.currentBlockHeight}`);
|
|
||||||
const callbackPromises = this.newAsyncBlockCallbacks.map((cb) => cb(blockExtended, txIds, transactions));
|
|
||||||
|
|
||||||
if (Common.indexingEnabled()) {
|
if (Common.indexingEnabled()) {
|
||||||
if (!fastForwarded) {
|
if (!fastForwarded) {
|
||||||
const lastBlock = await blocksRepository.$getBlockByHeight(blockExtended.height - 1);
|
const lastBlock = await blocksRepository.$getBlockByHeight(blockExtended.height - 1);
|
||||||
@ -671,9 +667,11 @@ class Blocks {
|
|||||||
await BlocksRepository.$deleteBlocksFrom(lastBlock.height - 10);
|
await BlocksRepository.$deleteBlocksFrom(lastBlock.height - 10);
|
||||||
await HashratesRepository.$deleteLastEntries();
|
await HashratesRepository.$deleteLastEntries();
|
||||||
await cpfpRepository.$deleteClustersFrom(lastBlock.height - 10);
|
await cpfpRepository.$deleteClustersFrom(lastBlock.height - 10);
|
||||||
|
this.blocks = this.blocks.slice(0, -10);
|
||||||
this.updateTimerProgress(timer, `rolled back chain divergence from ${this.currentBlockHeight}`);
|
this.updateTimerProgress(timer, `rolled back chain divergence from ${this.currentBlockHeight}`);
|
||||||
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);
|
||||||
|
this.blocks.push(newBlock);
|
||||||
this.updateTimerProgress(timer, `reindexed block`);
|
this.updateTimerProgress(timer, `reindexed block`);
|
||||||
let cpfpSummary;
|
let cpfpSummary;
|
||||||
if (config.MEMPOOL.CPFP_INDEXING) {
|
if (config.MEMPOOL.CPFP_INDEXING) {
|
||||||
@ -722,6 +720,10 @@ class Blocks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// start async callbacks
|
||||||
|
this.updateTimerProgress(timer, `starting async callbacks for ${this.currentBlockHeight}`);
|
||||||
|
const callbackPromises = this.newAsyncBlockCallbacks.map((cb) => cb(blockExtended, txIds, transactions));
|
||||||
|
|
||||||
if (block.height % 2016 === 0) {
|
if (block.height % 2016 === 0) {
|
||||||
if (Common.indexingEnabled()) {
|
if (Common.indexingEnabled()) {
|
||||||
await DifficultyAdjustmentsRepository.$saveAdjustments({
|
await DifficultyAdjustmentsRepository.$saveAdjustments({
|
||||||
@ -814,6 +816,16 @@ class Blocks {
|
|||||||
return blockExtended;
|
return blockExtended;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async $indexStaleBlock(hash: string): Promise<BlockExtended> {
|
||||||
|
const block: IEsploraApi.Block = await bitcoinCoreApi.$getBlock(hash);
|
||||||
|
const transactions = await this.$getTransactionsExtended(hash, block.height, true);
|
||||||
|
const blockExtended = await this.$getBlockExtended(block, transactions);
|
||||||
|
|
||||||
|
blockExtended.canonical = await bitcoinApi.$getBlockHash(block.height);
|
||||||
|
|
||||||
|
return blockExtended;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get one block by its hash
|
* Get one block by its hash
|
||||||
*/
|
*/
|
||||||
@ -831,7 +843,11 @@ class Blocks {
|
|||||||
|
|
||||||
// Bitcoin network, add our custom data on top
|
// Bitcoin network, add our custom data on top
|
||||||
const block: IEsploraApi.Block = await bitcoinCoreApi.$getBlock(hash);
|
const block: IEsploraApi.Block = await bitcoinCoreApi.$getBlock(hash);
|
||||||
return await this.$indexBlock(block.height);
|
if (block.stale) {
|
||||||
|
return await this.$indexStaleBlock(hash);
|
||||||
|
} else {
|
||||||
|
return await this.$indexBlock(block.height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $getStrippedBlockTransactions(hash: string, skipMemoryCache = false,
|
public async $getStrippedBlockTransactions(hash: string, skipMemoryCache = false,
|
||||||
|
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
|||||||
import { RowDataPacket } from 'mysql2';
|
import { RowDataPacket } from 'mysql2';
|
||||||
|
|
||||||
class DatabaseMigration {
|
class DatabaseMigration {
|
||||||
private static currentVersion = 62;
|
private static currentVersion = 63;
|
||||||
private queryTimeout = 3600_000;
|
private queryTimeout = 3600_000;
|
||||||
private statisticsAddedIndexed = false;
|
private statisticsAddedIndexed = false;
|
||||||
private uniqueLogs: string[] = [];
|
private uniqueLogs: string[] = [];
|
||||||
@ -539,6 +539,10 @@ class DatabaseMigration {
|
|||||||
await this.updateToSchemaVersion(62);
|
await this.updateToSchemaVersion(62);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 63 && isBitcoin === true) {
|
||||||
|
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD fullrbf_txs JSON DEFAULT "[]"');
|
||||||
|
await this.updateToSchemaVersion(63);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -169,6 +169,19 @@ class RbfCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// is the transaction involved in a full rbf replacement?
|
||||||
|
public isFullRbf(txid: string): boolean {
|
||||||
|
const treeId = this.treeMap.get(txid);
|
||||||
|
if (!treeId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const tree = this.rbfTrees.get(treeId);
|
||||||
|
if (!tree) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return tree?.fullRbf;
|
||||||
|
}
|
||||||
|
|
||||||
private cleanup(): void {
|
private cleanup(): void {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
for (const txid of this.expiring.keys()) {
|
for (const txid of this.expiring.keys()) {
|
||||||
|
@ -583,6 +583,10 @@ class WebsocketHandler {
|
|||||||
|
|
||||||
const _memPool = memPool.getMempool();
|
const _memPool = memPool.getMempool();
|
||||||
|
|
||||||
|
const rbfTransactions = Common.findMinedRbfTransactions(transactions, memPool.getSpendMap());
|
||||||
|
memPool.handleMinedRbfTransactions(rbfTransactions);
|
||||||
|
memPool.removeFromSpendMap(transactions);
|
||||||
|
|
||||||
if (config.MEMPOOL.AUDIT) {
|
if (config.MEMPOOL.AUDIT) {
|
||||||
let projectedBlocks;
|
let projectedBlocks;
|
||||||
let auditMempool = _memPool;
|
let auditMempool = _memPool;
|
||||||
@ -605,7 +609,7 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Common.indexingEnabled() && memPool.isInSync()) {
|
if (Common.indexingEnabled() && memPool.isInSync()) {
|
||||||
const { censored, added, fresh, sigop, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool);
|
const { censored, added, fresh, sigop, fullrbf, 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 : [];
|
const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions : [];
|
||||||
@ -633,6 +637,7 @@ class WebsocketHandler {
|
|||||||
missingTxs: censored,
|
missingTxs: censored,
|
||||||
freshTxs: fresh,
|
freshTxs: fresh,
|
||||||
sigopTxs: sigop,
|
sigopTxs: sigop,
|
||||||
|
fullrbfTxs: fullrbf,
|
||||||
matchRate: matchRate,
|
matchRate: matchRate,
|
||||||
expectedFees: totalFees,
|
expectedFees: totalFees,
|
||||||
expectedWeight: totalWeight,
|
expectedWeight: totalWeight,
|
||||||
@ -652,10 +657,6 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rbfTransactions = Common.findMinedRbfTransactions(transactions, memPool.getSpendMap());
|
|
||||||
memPool.handleMinedRbfTransactions(rbfTransactions);
|
|
||||||
memPool.removeFromSpendMap(transactions);
|
|
||||||
|
|
||||||
// Update mempool to remove transactions included in the new block
|
// Update mempool to remove transactions included in the new block
|
||||||
for (const txId of txIds) {
|
for (const txId of txIds) {
|
||||||
delete _memPool[txId];
|
delete _memPool[txId];
|
||||||
|
@ -6,6 +6,7 @@ import logger from './logger';
|
|||||||
import bitcoinClient from './api/bitcoin/bitcoin-client';
|
import bitcoinClient from './api/bitcoin/bitcoin-client';
|
||||||
import priceUpdater from './tasks/price-updater';
|
import priceUpdater from './tasks/price-updater';
|
||||||
import PricesRepository from './repositories/PricesRepository';
|
import PricesRepository from './repositories/PricesRepository';
|
||||||
|
import config from './config';
|
||||||
|
|
||||||
export interface CoreIndex {
|
export interface CoreIndex {
|
||||||
name: string;
|
name: string;
|
||||||
@ -72,7 +73,7 @@ class Indexer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task === 'blocksPrices' && !this.tasksRunning.includes(task)) {
|
if (task === 'blocksPrices' && !this.tasksRunning.includes(task) && !['testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) {
|
||||||
this.tasksRunning.push(task);
|
this.tasksRunning.push(task);
|
||||||
const lastestPriceId = await PricesRepository.$getLatestPriceId();
|
const lastestPriceId = await PricesRepository.$getLatestPriceId();
|
||||||
if (priceUpdater.historyInserted === false || lastestPriceId === null) {
|
if (priceUpdater.historyInserted === false || lastestPriceId === null) {
|
||||||
|
@ -34,6 +34,7 @@ export interface BlockAudit {
|
|||||||
missingTxs: string[],
|
missingTxs: string[],
|
||||||
freshTxs: string[],
|
freshTxs: string[],
|
||||||
sigopTxs: string[],
|
sigopTxs: string[],
|
||||||
|
fullrbfTxs: string[],
|
||||||
addedTxs: string[],
|
addedTxs: string[],
|
||||||
matchRate: number,
|
matchRate: number,
|
||||||
expectedFees?: number,
|
expectedFees?: number,
|
||||||
@ -227,6 +228,7 @@ export interface BlockExtension {
|
|||||||
*/
|
*/
|
||||||
export interface BlockExtended extends IEsploraApi.Block {
|
export interface BlockExtended extends IEsploraApi.Block {
|
||||||
extras: BlockExtension;
|
extras: BlockExtension;
|
||||||
|
canonical?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BlockSummary {
|
export interface BlockSummary {
|
||||||
|
@ -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, sigop_txs, match_rate, expected_fees, expected_weight)
|
await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, fresh_txs, sigop_txs, fullrbf_txs, match_rate, expected_fees, expected_weight)
|
||||||
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), JSON.stringify(audit.sigopTxs), audit.matchRate, audit.expectedFees, audit.expectedWeight]);
|
JSON.stringify(audit.addedTxs), JSON.stringify(audit.freshTxs), JSON.stringify(audit.sigopTxs), JSON.stringify(audit.fullrbfTxs), audit.matchRate, audit.expectedFees, audit.expectedWeight]);
|
||||||
} 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`);
|
||||||
@ -62,18 +62,17 @@ class BlocksAuditRepositories {
|
|||||||
public async $getBlockAudit(hash: string): Promise<any> {
|
public async $getBlockAudit(hash: string): Promise<any> {
|
||||||
try {
|
try {
|
||||||
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_audits.height, blocks_audits.hash as id, UNIX_TIMESTAMP(blocks_audits.time) as timestamp,
|
||||||
blocks.weight, blocks.tx_count,
|
|
||||||
template,
|
template,
|
||||||
missing_txs as missingTxs,
|
missing_txs as missingTxs,
|
||||||
added_txs as addedTxs,
|
added_txs as addedTxs,
|
||||||
fresh_txs as freshTxs,
|
fresh_txs as freshTxs,
|
||||||
sigop_txs as sigopTxs,
|
sigop_txs as sigopTxs,
|
||||||
|
fullrbf_txs as fullrbfTxs,
|
||||||
match_rate as matchRate,
|
match_rate as matchRate,
|
||||||
expected_fees as expectedFees,
|
expected_fees as expectedFees,
|
||||||
expected_weight as expectedWeight
|
expected_weight as expectedWeight
|
||||||
FROM blocks_audits
|
FROM blocks_audits
|
||||||
JOIN blocks ON blocks.hash = blocks_audits.hash
|
|
||||||
JOIN blocks_templates ON blocks_templates.id = blocks_audits.hash
|
JOIN blocks_templates ON blocks_templates.id = blocks_audits.hash
|
||||||
WHERE blocks_audits.hash = "${hash}"
|
WHERE blocks_audits.hash = "${hash}"
|
||||||
`);
|
`);
|
||||||
@ -83,6 +82,7 @@ class BlocksAuditRepositories {
|
|||||||
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].sigopTxs = JSON.parse(rows[0].sigopTxs);
|
||||||
|
rows[0].fullrbfTxs = JSON.parse(rows[0].fullrbfTxs);
|
||||||
rows[0].template = JSON.parse(rows[0].template);
|
rows[0].template = JSON.parse(rows[0].template);
|
||||||
|
|
||||||
return rows[0];
|
return rows[0];
|
||||||
|
@ -269,7 +269,11 @@ class NetworkSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async $scanForClosedChannels(): Promise<void> {
|
private async $scanForClosedChannels(): Promise<void> {
|
||||||
if (this.closedChannelsScanBlock === blocks.getCurrentBlockHeight()) {
|
let currentBlockHeight = blocks.getCurrentBlockHeight();
|
||||||
|
if (config.MEMPOOL.ENABLED === false) { // https://github.com/mempool/mempool/issues/3582
|
||||||
|
currentBlockHeight = await bitcoinApi.$getBlockHeightTip();
|
||||||
|
}
|
||||||
|
if (this.closedChannelsScanBlock === currentBlockHeight) {
|
||||||
logger.debug(`We've already scan closed channels for this block, skipping.`);
|
logger.debug(`We've already scan closed channels for this block, skipping.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -305,7 +309,7 @@ class NetworkSyncService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.closedChannelsScanBlock = blocks.getCurrentBlockHeight();
|
this.closedChannelsScanBlock = currentBlockHeight;
|
||||||
logger.debug(`Closed channels scan completed at block ${this.closedChannelsScanBlock}`, logger.tags.ln);
|
logger.debug(`Closed channels scan completed at block ${this.closedChannelsScanBlock}`, logger.tags.ln);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.err(`$scanForClosedChannels() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
logger.err(`$scanForClosedChannels() error: ${e instanceof Error ? e.message : e}`, logger.tags.ln);
|
||||||
|
3
contributors/pfoytik.txt
Normal file
3
contributors/pfoytik.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of June 15, 2023.
|
||||||
|
|
||||||
|
Signed pfoytik
|
3
contributors/secondl1ght.txt
Normal file
3
contributors/secondl1ght.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of June 14, 2023.
|
||||||
|
|
||||||
|
Signed: secondl1ght
|
@ -144,8 +144,8 @@ Corresponding `docker-compose.yml` overrides:
|
|||||||
MEMPOOL_ADVANCED_GBT_AUDIT: ""
|
MEMPOOL_ADVANCED_GBT_AUDIT: ""
|
||||||
MEMPOOL_ADVANCED_GBT_MEMPOOL: ""
|
MEMPOOL_ADVANCED_GBT_MEMPOOL: ""
|
||||||
MEMPOOL_CPFP_INDEXING: ""
|
MEMPOOL_CPFP_INDEXING: ""
|
||||||
MAX_BLOCKS_BULK_QUERY: ""
|
MEMPOOL_MAX_BLOCKS_BULK_QUERY: ""
|
||||||
DISK_CACHE_BLOCK_INTERVAL: ""
|
MEMPOOL_DISK_CACHE_BLOCK_INTERVAL: ""
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ Get the latest Mempool code:
|
|||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/mempool/mempool
|
git clone https://github.com/mempool/mempool
|
||||||
cd mempool
|
cd mempool/frontend
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Specify Website
|
### 2. Specify Website
|
||||||
|
@ -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' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected';
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'fullrbf';
|
||||||
context?: 'projected' | 'actual';
|
context?: 'projected' | 'actual';
|
||||||
scene?: BlockScene;
|
scene?: BlockScene;
|
||||||
|
|
||||||
@ -172,6 +172,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
return auditColors.censored;
|
return auditColors.censored;
|
||||||
case 'missing':
|
case 'missing':
|
||||||
case 'sigop':
|
case 'sigop':
|
||||||
|
case 'fullrbf':
|
||||||
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
||||||
case 'fresh':
|
case 'fresh':
|
||||||
return auditColors.missing;
|
return auditColors.missing;
|
||||||
|
@ -52,6 +52,7 @@
|
|||||||
<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>
|
||||||
|
<td *ngSwitchCase="'fullrbf'"><span class="badge badge-warning" i18n="transaction.audit.fullrbf">Full RBF</span></td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
<div class="container-xl" (window:resize)="onResize($event)">
|
<div class="container-xl" (window:resize)="onResize($event)">
|
||||||
|
|
||||||
<div class="title-block" [class.time-ltr]="timeLtr" id="block">
|
<div class="title-block" [class.time-ltr]="timeLtr" id="block">
|
||||||
|
<div *ngIf="block?.stale" class="alert alert-mempool" role="alert">
|
||||||
|
<span i18n="block.reorged|Block reorg" class="alert-text">This block does not belong to the main chain, it has been replaced by:</span>
|
||||||
|
<app-truncate [text]="block.canonical" [lastChars]="12" [link]="['/block/' | relativeUrl, block.canonical]" [maxWidth]="480"></app-truncate>
|
||||||
|
</div>
|
||||||
<h1>
|
<h1>
|
||||||
<ng-container *ngIf="blockHeight == null || blockHeight > 0; else genesis" i18n="shared.block-title">Block</ng-container>
|
<ng-container *ngIf="blockHeight == null || blockHeight > 0; else genesis" i18n="shared.block-title">Block</ng-container>
|
||||||
<ng-template #genesis i18n="@@2303359202781425764">Genesis</ng-template>
|
<ng-template #genesis i18n="@@2303359202781425764">Genesis</ng-template>
|
||||||
@ -23,6 +27,8 @@
|
|||||||
|
|
||||||
<div class="grow"></div>
|
<div class="grow"></div>
|
||||||
|
|
||||||
|
<button *ngIf="block?.stale" type="button" class="btn btn-sm btn-danger container-button" i18n="block.stale|Stale block state">Stale</button>
|
||||||
|
|
||||||
<button [routerLink]="['/' | relativeUrl]" class="btn btn-sm">✕</button>
|
<button [routerLink]="['/' | relativeUrl]" class="btn btn-sm">✕</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,3 +1,26 @@
|
|||||||
|
.title-block {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: baseline;
|
||||||
|
@media (min-width: 650px) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin: 0rem;
|
||||||
|
margin-right: 15px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-mempool {
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-button {
|
||||||
|
align-self: center;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.qr-wrapper {
|
.qr-wrapper {
|
||||||
background-color: #FFF;
|
background-color: #FFF;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
@ -317,6 +317,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
const isSelected = {};
|
const isSelected = {};
|
||||||
const isFresh = {};
|
const isFresh = {};
|
||||||
const isSigop = {};
|
const isSigop = {};
|
||||||
|
const isFullRbf = {};
|
||||||
this.numMissing = 0;
|
this.numMissing = 0;
|
||||||
this.numUnexpected = 0;
|
this.numUnexpected = 0;
|
||||||
|
|
||||||
@ -339,6 +340,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
for (const txid of blockAudit.sigopTxs || []) {
|
for (const txid of blockAudit.sigopTxs || []) {
|
||||||
isSigop[txid] = true;
|
isSigop[txid] = true;
|
||||||
}
|
}
|
||||||
|
for (const txid of blockAudit.fullrbfTxs || []) {
|
||||||
|
isFullRbf[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';
|
||||||
@ -347,7 +351,15 @@ 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' : (isSigop[tx.txid] ? 'sigop' : 'missing');
|
if (isFresh[tx.txid]) {
|
||||||
|
tx.status = 'fresh';
|
||||||
|
} else if (isSigop[tx.txid]) {
|
||||||
|
tx.status = 'sigop';
|
||||||
|
} else if (isFullRbf[tx.txid]) {
|
||||||
|
tx.status = 'fullrbf';
|
||||||
|
} else {
|
||||||
|
tx.status = 'missing';
|
||||||
|
}
|
||||||
isMissing[tx.txid] = true;
|
isMissing[tx.txid] = true;
|
||||||
this.numMissing++;
|
this.numMissing++;
|
||||||
}
|
}
|
||||||
@ -360,6 +372,8 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
tx.status = 'added';
|
tx.status = 'added';
|
||||||
} else if (inTemplate[tx.txid]) {
|
} else if (inTemplate[tx.txid]) {
|
||||||
tx.status = 'found';
|
tx.status = 'found';
|
||||||
|
} else if (isFullRbf[tx.txid]) {
|
||||||
|
tx.status = 'fullrbf';
|
||||||
} else {
|
} else {
|
||||||
tx.status = 'selected';
|
tx.status = 'selected';
|
||||||
isSelected[tx.txid] = true;
|
isSelected[tx.txid] = true;
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<span [style]="change >= 0 ? 'color: #42B747' : 'color: #B74242'">
|
<span [style]="change >= 0 ? 'color: #42B747' : 'color: #B74242'">
|
||||||
{{ change >= 0 ? '+' : '' }}{{ change | amountShortener }}%
|
‎{{ change >= 0 ? '+' : '' }}{{ change | amountShortener }}%
|
||||||
</span>
|
</span>
|
||||||
|
@ -120,6 +120,8 @@ export interface Block {
|
|||||||
size: number;
|
size: number;
|
||||||
weight: number;
|
weight: number;
|
||||||
previousblockhash: string;
|
previousblockhash: string;
|
||||||
|
stale?: boolean;
|
||||||
|
canonical?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Address {
|
export interface Address {
|
||||||
|
@ -156,6 +156,7 @@ export interface BlockAudit extends BlockExtended {
|
|||||||
addedTxs: string[],
|
addedTxs: string[],
|
||||||
freshTxs: string[],
|
freshTxs: string[],
|
||||||
sigopTxs: string[],
|
sigopTxs: string[],
|
||||||
|
fullrbfTxs: string[],
|
||||||
matchRate: number,
|
matchRate: number,
|
||||||
expectedFees: number,
|
expectedFees: number,
|
||||||
expectedWeight: number,
|
expectedWeight: number,
|
||||||
@ -171,7 +172,7 @@ export interface TransactionStripped {
|
|||||||
fee: number;
|
fee: number;
|
||||||
vsize: number;
|
vsize: number;
|
||||||
value: number;
|
value: number;
|
||||||
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected';
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'fullrbf';
|
||||||
context?: 'projected' | 'actual';
|
context?: 'projected' | 'actual';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +77,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' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected';
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'fullrbf';
|
||||||
context?: 'projected' | 'actual';
|
context?: 'projected' | 'actual';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -993,6 +993,10 @@ th {
|
|||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-audit {
|
||||||
|
margin-left: .5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.scriptmessage {
|
.scriptmessage {
|
||||||
|
@ -332,7 +332,7 @@ BITCOIN_REPO_URL=https://github.com/bitcoin/bitcoin
|
|||||||
BITCOIN_REPO_NAME=bitcoin
|
BITCOIN_REPO_NAME=bitcoin
|
||||||
BITCOIN_REPO_BRANCH=master
|
BITCOIN_REPO_BRANCH=master
|
||||||
#BITCOIN_LATEST_RELEASE=$(curl -s https://api.github.com/repos/bitcoin/bitcoin/releases/latest|grep tag_name|head -1|cut -d '"' -f4)
|
#BITCOIN_LATEST_RELEASE=$(curl -s https://api.github.com/repos/bitcoin/bitcoin/releases/latest|grep tag_name|head -1|cut -d '"' -f4)
|
||||||
BITCOIN_LATEST_RELEASE=v23.0
|
BITCOIN_LATEST_RELEASE=v25.0
|
||||||
echo -n '.'
|
echo -n '.'
|
||||||
|
|
||||||
BISQ_REPO_URL=https://github.com/bisq-network/bisq
|
BISQ_REPO_URL=https://github.com/bisq-network/bisq
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
|
"ENABLED": false,
|
||||||
"NETWORK": "mainnet",
|
"NETWORK": "mainnet",
|
||||||
"BACKEND": "esplora",
|
"BACKEND": "esplora",
|
||||||
"HTTP_PORT": 8993,
|
"HTTP_PORT": 8993,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
|
"ENABLED": false,
|
||||||
"NETWORK": "signet",
|
"NETWORK": "signet",
|
||||||
"BACKEND": "esplora",
|
"BACKEND": "esplora",
|
||||||
"HTTP_PORT": 8991,
|
"HTTP_PORT": 8991,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
|
"ENABLED": false,
|
||||||
"NETWORK": "testnet",
|
"NETWORK": "testnet",
|
||||||
"BACKEND": "esplora",
|
"BACKEND": "esplora",
|
||||||
"HTTP_PORT": 8992,
|
"HTTP_PORT": 8992,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user