Merge pull request #3971 from mempool/mononaut/audit-recently-cpfpd
Add "recently cpfpd" exception to audits
This commit is contained in:
		
						commit
						c20e6d9458
					
				@ -1,12 +1,12 @@
 | 
			
		||||
import config from '../config';
 | 
			
		||||
import logger from '../logger';
 | 
			
		||||
import { TransactionExtended, MempoolBlockWithTransactions } from '../mempool.interfaces';
 | 
			
		||||
import { MempoolTransactionExtended, 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
 | 
			
		||||
 | 
			
		||||
class Audit {
 | 
			
		||||
  auditBlock(transactions: TransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: TransactionExtended })
 | 
			
		||||
  auditBlock(transactions: MempoolTransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: MempoolTransactionExtended })
 | 
			
		||||
   : { censored: string[], added: string[], fresh: string[], sigop: string[], fullrbf: string[], score: number, similarity: number } {
 | 
			
		||||
    if (!projectedBlocks?.[0]?.transactionIds || !mempool) {
 | 
			
		||||
      return { censored: [], added: [], fresh: [], sigop: [], fullrbf: [], score: 0, similarity: 1 };
 | 
			
		||||
@ -14,7 +14,7 @@ class Audit {
 | 
			
		||||
 | 
			
		||||
    const matches: string[] = []; // present in both mined block and 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 or lastBoosted within PROPAGATION_MARGIN
 | 
			
		||||
    const fullrbf: string[] = []; // either missing or present, and part of a fullrbf replacement
 | 
			
		||||
    const isCensored = {}; // missing, without excuse
 | 
			
		||||
    const isDisplaced = {};
 | 
			
		||||
@ -36,10 +36,13 @@ class Audit {
 | 
			
		||||
    // look for transactions that were expected in the template, but missing from the mined block
 | 
			
		||||
    for (const txid of projectedBlocks[0].transactionIds) {
 | 
			
		||||
      if (!inBlock[txid]) {
 | 
			
		||||
        // tx is recent, may have reached the miner too late for inclusion
 | 
			
		||||
        if (rbfCache.isFullRbf(txid)) {
 | 
			
		||||
          fullrbf.push(txid);
 | 
			
		||||
        } else if (mempool[txid]?.firstSeen != null && (now - (mempool[txid]?.firstSeen || 0)) <= PROPAGATION_MARGIN) {
 | 
			
		||||
          // tx is recent, may have reached the miner too late for inclusion
 | 
			
		||||
          fresh.push(txid);
 | 
			
		||||
        } else if (mempool[txid]?.lastBoosted != null && (now - (mempool[txid]?.lastBoosted || 0)) <= PROPAGATION_MARGIN) {
 | 
			
		||||
          // tx was recently cpfp'd, miner may not have the latest effective rate
 | 
			
		||||
          fresh.push(txid);
 | 
			
		||||
        } else {
 | 
			
		||||
          isCensored[txid] = true;
 | 
			
		||||
 | 
			
		||||
@ -457,6 +457,7 @@ class MempoolBlocks {
 | 
			
		||||
              };
 | 
			
		||||
              if (matched) {
 | 
			
		||||
                descendants.push(relative);
 | 
			
		||||
                mempoolTx.lastBoosted = Math.max(mempoolTx.lastBoosted || 0, mempool[txid].firstSeen || 0);
 | 
			
		||||
              } else {
 | 
			
		||||
                ancestors.push(relative);
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
@ -100,6 +100,7 @@ export interface MempoolTransactionExtended extends TransactionExtended {
 | 
			
		||||
  adjustedVsize: number;
 | 
			
		||||
  adjustedFeePerVsize: number;
 | 
			
		||||
  inputs?: number[];
 | 
			
		||||
  lastBoosted?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface AuditTransaction {
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,7 @@ export default class TxView implements TransactionStripped {
 | 
			
		||||
  value: number;
 | 
			
		||||
  feerate: number;
 | 
			
		||||
  rate?: number;
 | 
			
		||||
  status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'fullrbf';
 | 
			
		||||
  status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'fullrbf';
 | 
			
		||||
  context?: 'projected' | 'actual';
 | 
			
		||||
  scene?: BlockScene;
 | 
			
		||||
 | 
			
		||||
@ -210,6 +210,7 @@ export default class TxView implements TransactionStripped {
 | 
			
		||||
      case 'fullrbf':
 | 
			
		||||
        return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
 | 
			
		||||
      case 'fresh':
 | 
			
		||||
      case 'freshcpfp':
 | 
			
		||||
        return auditColors.missing;
 | 
			
		||||
      case 'added':
 | 
			
		||||
        return auditColors.added;
 | 
			
		||||
 | 
			
		||||
@ -50,6 +50,7 @@
 | 
			
		||||
          <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="'freshcpfp'"><span class="badge badge-warning" i18n="transaction.audit.recently-cpfped">Recently CPFP'd</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="'fullrbf'"><span class="badge badge-warning" i18n="transaction.audit.fullrbf">Full RBF</span></td>
 | 
			
		||||
 | 
			
		||||
@ -370,7 +370,11 @@ export class BlockComponent implements OnInit, OnDestroy {
 | 
			
		||||
              tx.status = 'found';
 | 
			
		||||
            } else {
 | 
			
		||||
              if (isFresh[tx.txid]) {
 | 
			
		||||
                tx.status = 'fresh';
 | 
			
		||||
                if (tx.rate - (tx.fee / tx.vsize) >= 0.1) {
 | 
			
		||||
                  tx.status = 'freshcpfp';
 | 
			
		||||
                } else {
 | 
			
		||||
                  tx.status = 'fresh';
 | 
			
		||||
                }
 | 
			
		||||
              } else if (isSigop[tx.txid]) {
 | 
			
		||||
                tx.status = 'sigop';
 | 
			
		||||
              } else if (isFullRbf[tx.txid]) {
 | 
			
		||||
 | 
			
		||||
@ -173,7 +173,8 @@ export interface TransactionStripped {
 | 
			
		||||
  fee: number;
 | 
			
		||||
  vsize: number;
 | 
			
		||||
  value: number;
 | 
			
		||||
  status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'fullrbf';
 | 
			
		||||
  rate?: number; // effective fee rate
 | 
			
		||||
  status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'fullrbf';
 | 
			
		||||
  context?: 'projected' | 'actual';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -89,7 +89,7 @@ export interface TransactionStripped {
 | 
			
		||||
  vsize: number;
 | 
			
		||||
  value: number;
 | 
			
		||||
  rate?: number; // effective fee rate
 | 
			
		||||
  status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected' | 'fullrbf';
 | 
			
		||||
  status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'censored' | 'selected' | 'fullrbf';
 | 
			
		||||
  context?: 'projected' | 'actual';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user