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