Merge pull request #4213 from mempool/mononaut/live-cpfp-updates
Send cpfp/effective fee rate changes to subscribed websocket clients
This commit is contained in:
		
						commit
						6780ba82d0
					
				@ -451,6 +451,7 @@ class MempoolBlocks {
 | 
				
			|||||||
  private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], accelerations, accelerationPool, saveResults): MempoolBlockWithTransactions[] {
 | 
					  private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], accelerations, accelerationPool, saveResults): MempoolBlockWithTransactions[] {
 | 
				
			||||||
    for (const [txid, rate] of rates) {
 | 
					    for (const [txid, rate] of rates) {
 | 
				
			||||||
      if (txid in mempool) {
 | 
					      if (txid in mempool) {
 | 
				
			||||||
 | 
					        mempool[txid].cpfpDirty = (rate !== mempool[txid].effectiveFeePerVsize);
 | 
				
			||||||
        mempool[txid].effectiveFeePerVsize = rate;
 | 
					        mempool[txid].effectiveFeePerVsize = rate;
 | 
				
			||||||
        mempool[txid].cpfpChecked = false;
 | 
					        mempool[txid].cpfpChecked = false;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -494,6 +495,9 @@ class MempoolBlocks {
 | 
				
			|||||||
              }
 | 
					              }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
 | 
					          if (mempoolTx.ancestors?.length !== ancestors.length || mempoolTx.descendants?.length !== descendants.length) {
 | 
				
			||||||
 | 
					            mempoolTx.cpfpDirty = true;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
          Object.assign(mempoolTx, {ancestors, descendants, bestDescendant: null, cpfpChecked: true});
 | 
					          Object.assign(mempoolTx, {ancestors, descendants, bestDescendant: null, cpfpChecked: true});
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@ -531,12 +535,21 @@ class MempoolBlocks {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
          const acceleration = accelerations[txid];
 | 
					          const acceleration = accelerations[txid];
 | 
				
			||||||
          if (isAccelerated[txid] || (acceleration && (!accelerationPool || acceleration.pools.includes(accelerationPool)))) {
 | 
					          if (isAccelerated[txid] || (acceleration && (!accelerationPool || acceleration.pools.includes(accelerationPool)))) {
 | 
				
			||||||
 | 
					            if (!mempoolTx.acceleration) {
 | 
				
			||||||
 | 
					              mempoolTx.cpfpDirty = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            mempoolTx.acceleration = true;
 | 
					            mempoolTx.acceleration = true;
 | 
				
			||||||
            for (const ancestor of mempoolTx.ancestors || []) {
 | 
					            for (const ancestor of mempoolTx.ancestors || []) {
 | 
				
			||||||
 | 
					              if (!mempool[ancestor.txid].acceleration) {
 | 
				
			||||||
 | 
					                mempool[ancestor.txid].cpfpDirty = true;
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
              mempool[ancestor.txid].acceleration = true;
 | 
					              mempool[ancestor.txid].acceleration = true;
 | 
				
			||||||
              isAccelerated[ancestor.txid] = true;
 | 
					              isAccelerated[ancestor.txid] = true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
 | 
					            if (mempoolTx.acceleration) {
 | 
				
			||||||
 | 
					              mempoolTx.cpfpDirty = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            delete mempoolTx.acceleration;
 | 
					            delete mempoolTx.acceleration;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -586,13 +586,25 @@ class WebsocketHandler {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const mempoolTx = newMempool[trackTxid];
 | 
					        const mempoolTx = newMempool[trackTxid];
 | 
				
			||||||
        if (mempoolTx && mempoolTx.position) {
 | 
					        if (mempoolTx && mempoolTx.position) {
 | 
				
			||||||
          response['txPosition'] = JSON.stringify({
 | 
					          const positionData = {
 | 
				
			||||||
            txid: trackTxid,
 | 
					            txid: trackTxid,
 | 
				
			||||||
            position: {
 | 
					            position: {
 | 
				
			||||||
              ...mempoolTx.position,
 | 
					              ...mempoolTx.position,
 | 
				
			||||||
              accelerated: mempoolTx.acceleration || undefined,
 | 
					              accelerated: mempoolTx.acceleration || undefined,
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          });
 | 
					          };
 | 
				
			||||||
 | 
					          if (mempoolTx.cpfpDirty) {
 | 
				
			||||||
 | 
					            positionData['cpfp'] = {
 | 
				
			||||||
 | 
					              ancestors: mempoolTx.ancestors,
 | 
				
			||||||
 | 
					              bestDescendant: mempoolTx.bestDescendant || null,
 | 
				
			||||||
 | 
					              descendants: mempoolTx.descendants || null,
 | 
				
			||||||
 | 
					              effectiveFeePerVsize: mempoolTx.effectiveFeePerVsize || null,
 | 
				
			||||||
 | 
					              sigops: mempoolTx.sigops,
 | 
				
			||||||
 | 
					              adjustedVsize: mempoolTx.adjustedVsize,
 | 
				
			||||||
 | 
					              acceleration: mempoolTx.acceleration
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          response['txPosition'] = JSON.stringify(positionData);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -104,6 +104,7 @@ export interface MempoolTransactionExtended extends TransactionExtended {
 | 
				
			|||||||
  adjustedFeePerVsize: number;
 | 
					  adjustedFeePerVsize: number;
 | 
				
			||||||
  inputs?: number[];
 | 
					  inputs?: number[];
 | 
				
			||||||
  lastBoosted?: number;
 | 
					  lastBoosted?: number;
 | 
				
			||||||
 | 
					  cpfpDirty?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface AuditTransaction {
 | 
					export interface AuditTransaction {
 | 
				
			||||||
 | 
				
			|||||||
@ -161,34 +161,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
        })
 | 
					        })
 | 
				
			||||||
      )
 | 
					      )
 | 
				
			||||||
      .subscribe((cpfpInfo) => {
 | 
					      .subscribe((cpfpInfo) => {
 | 
				
			||||||
        if (!cpfpInfo || !this.tx) {
 | 
					        this.setCpfpInfo(cpfpInfo);
 | 
				
			||||||
          this.cpfpInfo = null;
 | 
					 | 
				
			||||||
          this.hasEffectiveFeeRate = false;
 | 
					 | 
				
			||||||
          return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // merge ancestors/descendants
 | 
					 | 
				
			||||||
        const relatives = [...(cpfpInfo.ancestors || []), ...(cpfpInfo.descendants || [])];
 | 
					 | 
				
			||||||
        if (cpfpInfo.bestDescendant && !cpfpInfo.descendants?.length) {
 | 
					 | 
				
			||||||
          relatives.push(cpfpInfo.bestDescendant);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        const hasRelatives = !!relatives.length;
 | 
					 | 
				
			||||||
        if (!cpfpInfo.effectiveFeePerVsize && hasRelatives) {
 | 
					 | 
				
			||||||
          let totalWeight =
 | 
					 | 
				
			||||||
            this.tx.weight +
 | 
					 | 
				
			||||||
            relatives.reduce((prev, val) => prev + val.weight, 0);
 | 
					 | 
				
			||||||
          let totalFees =
 | 
					 | 
				
			||||||
            this.tx.fee +
 | 
					 | 
				
			||||||
            relatives.reduce((prev, val) => prev + val.fee, 0);
 | 
					 | 
				
			||||||
          this.tx.effectiveFeePerVsize = totalFees / (totalWeight / 4);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
          this.tx.effectiveFeePerVsize = cpfpInfo.effectiveFeePerVsize;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (cpfpInfo.acceleration) {
 | 
					 | 
				
			||||||
          this.tx.acceleration = cpfpInfo.acceleration;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.cpfpInfo = cpfpInfo;
 | 
					 | 
				
			||||||
        this.hasEffectiveFeeRate = hasRelatives || (this.tx.effectiveFeePerVsize && (Math.abs(this.tx.effectiveFeePerVsize - this.tx.feePerVsize) > 0.01));
 | 
					 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.fetchRbfSubscription = this.fetchRbfHistory$
 | 
					    this.fetchRbfSubscription = this.fetchRbfHistory$
 | 
				
			||||||
@ -259,6 +232,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
            mempoolPosition: this.mempoolPosition
 | 
					            mempoolPosition: this.mempoolPosition
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
          this.txInBlockIndex = this.mempoolPosition.block;
 | 
					          this.txInBlockIndex = this.mempoolPosition.block;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (txPosition.cpfp !== undefined) {
 | 
				
			||||||
 | 
					            this.setCpfpInfo(txPosition.cpfp);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        this.mempoolPosition = null;
 | 
					        this.mempoolPosition = null;
 | 
				
			||||||
@ -507,6 +484,37 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
 | 
				
			|||||||
      });
 | 
					      });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setCpfpInfo(cpfpInfo: CpfpInfo): void {
 | 
				
			||||||
 | 
					    if (!cpfpInfo || !this.tx) {
 | 
				
			||||||
 | 
					      this.cpfpInfo = null;
 | 
				
			||||||
 | 
					      this.hasEffectiveFeeRate = false;
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // merge ancestors/descendants
 | 
				
			||||||
 | 
					    const relatives = [...(cpfpInfo.ancestors || []), ...(cpfpInfo.descendants || [])];
 | 
				
			||||||
 | 
					    if (cpfpInfo.bestDescendant && !cpfpInfo.descendants?.length) {
 | 
				
			||||||
 | 
					      relatives.push(cpfpInfo.bestDescendant);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const hasRelatives = !!relatives.length;
 | 
				
			||||||
 | 
					    if (!cpfpInfo.effectiveFeePerVsize && hasRelatives) {
 | 
				
			||||||
 | 
					      const totalWeight =
 | 
				
			||||||
 | 
					        this.tx.weight +
 | 
				
			||||||
 | 
					        relatives.reduce((prev, val) => prev + val.weight, 0);
 | 
				
			||||||
 | 
					      const totalFees =
 | 
				
			||||||
 | 
					        this.tx.fee +
 | 
				
			||||||
 | 
					        relatives.reduce((prev, val) => prev + val.fee, 0);
 | 
				
			||||||
 | 
					      this.tx.effectiveFeePerVsize = totalFees / (totalWeight / 4);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this.tx.effectiveFeePerVsize = cpfpInfo.effectiveFeePerVsize;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (cpfpInfo.acceleration) {
 | 
				
			||||||
 | 
					      this.tx.acceleration = cpfpInfo.acceleration;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.cpfpInfo = cpfpInfo;
 | 
				
			||||||
 | 
					    this.hasEffectiveFeeRate = hasRelatives || (this.tx.effectiveFeePerVsize && (Math.abs(this.tx.effectiveFeePerVsize - this.tx.feePerVsize) > 0.01));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  setFeatures(): void {
 | 
					  setFeatures(): void {
 | 
				
			||||||
    if (this.tx) {
 | 
					    if (this.tx) {
 | 
				
			||||||
      this.segwitEnabled = !this.tx.status.confirmed || isFeatureActive(this.stateService.network, this.tx.status.block_height, 'segwit');
 | 
					      this.segwitEnabled = !this.tx.status.confirmed || isFeatureActive(this.stateService.network, this.tx.status.block_height, 'segwit');
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@ export interface Transaction {
 | 
				
			|||||||
  ancestors?: Ancestor[];
 | 
					  ancestors?: Ancestor[];
 | 
				
			||||||
  bestDescendant?: BestDescendant | null;
 | 
					  bestDescendant?: BestDescendant | null;
 | 
				
			||||||
  cpfpChecked?: boolean;
 | 
					  cpfpChecked?: boolean;
 | 
				
			||||||
  acceleration?: number;
 | 
					  acceleration?: boolean;
 | 
				
			||||||
  deleteAfter?: number;
 | 
					  deleteAfter?: number;
 | 
				
			||||||
  _unblinded?: any;
 | 
					  _unblinded?: any;
 | 
				
			||||||
  _deduced?: boolean;
 | 
					  _deduced?: boolean;
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,7 @@ export interface CpfpInfo {
 | 
				
			|||||||
  effectiveFeePerVsize?: number;
 | 
					  effectiveFeePerVsize?: number;
 | 
				
			||||||
  sigops?: number;
 | 
					  sigops?: number;
 | 
				
			||||||
  adjustedVsize?: number;
 | 
					  adjustedVsize?: number;
 | 
				
			||||||
  acceleration?: number;
 | 
					  acceleration?: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface RbfInfo {
 | 
					export interface RbfInfo {
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
 | 
				
			|||||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
 | 
					import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
 | 
				
			||||||
import { Transaction } from '../interfaces/electrs.interface';
 | 
					import { Transaction } from '../interfaces/electrs.interface';
 | 
				
			||||||
import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface';
 | 
					import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface';
 | 
				
			||||||
import { BlockExtended, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
 | 
					import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
 | 
				
			||||||
import { Router, NavigationStart } from '@angular/router';
 | 
					import { Router, NavigationStart } from '@angular/router';
 | 
				
			||||||
import { isPlatformBrowser } from '@angular/common';
 | 
					import { isPlatformBrowser } from '@angular/common';
 | 
				
			||||||
import { filter, map, scan, shareReplay } from 'rxjs/operators';
 | 
					import { filter, map, scan, shareReplay } from 'rxjs/operators';
 | 
				
			||||||
@ -113,7 +113,7 @@ export class StateService {
 | 
				
			|||||||
  utxoSpent$ = new Subject<object>();
 | 
					  utxoSpent$ = new Subject<object>();
 | 
				
			||||||
  difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1);
 | 
					  difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1);
 | 
				
			||||||
  mempoolTransactions$ = new Subject<Transaction>();
 | 
					  mempoolTransactions$ = new Subject<Transaction>();
 | 
				
			||||||
  mempoolTxPosition$ = new Subject<{ txid: string, position: MempoolPosition}>();
 | 
					  mempoolTxPosition$ = new Subject<{ txid: string, position: MempoolPosition, cpfp: CpfpInfo | null}>();
 | 
				
			||||||
  blockTransactions$ = new Subject<Transaction>();
 | 
					  blockTransactions$ = new Subject<Transaction>();
 | 
				
			||||||
  isLoadingWebSocket$ = new ReplaySubject<boolean>(1);
 | 
					  isLoadingWebSocket$ = new ReplaySubject<boolean>(1);
 | 
				
			||||||
  isLoadingMempool$ = new BehaviorSubject<boolean>(true);
 | 
					  isLoadingMempool$ = new BehaviorSubject<boolean>(true);
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user