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[] { | ||||
|     for (const [txid, rate] of rates) { | ||||
|       if (txid in mempool) { | ||||
|         mempool[txid].cpfpDirty = (rate !== mempool[txid].effectiveFeePerVsize); | ||||
|         mempool[txid].effectiveFeePerVsize = rate; | ||||
|         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}); | ||||
|         } | ||||
|       } | ||||
| @ -531,12 +535,21 @@ class MempoolBlocks { | ||||
| 
 | ||||
|           const acceleration = accelerations[txid]; | ||||
|           if (isAccelerated[txid] || (acceleration && (!accelerationPool || acceleration.pools.includes(accelerationPool)))) { | ||||
|             if (!mempoolTx.acceleration) { | ||||
|               mempoolTx.cpfpDirty = true; | ||||
|             } | ||||
|             mempoolTx.acceleration = true; | ||||
|             for (const ancestor of mempoolTx.ancestors || []) { | ||||
|               if (!mempool[ancestor.txid].acceleration) { | ||||
|                 mempool[ancestor.txid].cpfpDirty = true; | ||||
|               } | ||||
|               mempool[ancestor.txid].acceleration = true; | ||||
|               isAccelerated[ancestor.txid] = true; | ||||
|             } | ||||
|           } else { | ||||
|             if (mempoolTx.acceleration) { | ||||
|               mempoolTx.cpfpDirty = true; | ||||
|             } | ||||
|             delete mempoolTx.acceleration; | ||||
|           } | ||||
| 
 | ||||
|  | ||||
| @ -586,13 +586,25 @@ class WebsocketHandler { | ||||
| 
 | ||||
|         const mempoolTx = newMempool[trackTxid]; | ||||
|         if (mempoolTx && mempoolTx.position) { | ||||
|           response['txPosition'] = JSON.stringify({ | ||||
|           const positionData = { | ||||
|             txid: trackTxid, | ||||
|             position: { | ||||
|               ...mempoolTx.position, | ||||
|               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; | ||||
|   inputs?: number[]; | ||||
|   lastBoosted?: number; | ||||
|   cpfpDirty?: boolean; | ||||
| } | ||||
| 
 | ||||
| export interface AuditTransaction { | ||||
|  | ||||
| @ -161,34 +161,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|         }) | ||||
|       ) | ||||
|       .subscribe((cpfpInfo) => { | ||||
|         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) { | ||||
|           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.setCpfpInfo(cpfpInfo); | ||||
|       }); | ||||
| 
 | ||||
|     this.fetchRbfSubscription = this.fetchRbfHistory$ | ||||
| @ -259,6 +232,10 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy { | ||||
|             mempoolPosition: this.mempoolPosition | ||||
|           }); | ||||
|           this.txInBlockIndex = this.mempoolPosition.block; | ||||
| 
 | ||||
|           if (txPosition.cpfp !== undefined) { | ||||
|             this.setCpfpInfo(txPosition.cpfp); | ||||
|           } | ||||
|         } | ||||
|       } else { | ||||
|         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 { | ||||
|     if (this.tx) { | ||||
|       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[]; | ||||
|   bestDescendant?: BestDescendant | null; | ||||
|   cpfpChecked?: boolean; | ||||
|   acceleration?: number; | ||||
|   acceleration?: boolean; | ||||
|   deleteAfter?: number; | ||||
|   _unblinded?: any; | ||||
|   _deduced?: boolean; | ||||
|  | ||||
| @ -27,7 +27,7 @@ export interface CpfpInfo { | ||||
|   effectiveFeePerVsize?: number; | ||||
|   sigops?: number; | ||||
|   adjustedVsize?: number; | ||||
|   acceleration?: number; | ||||
|   acceleration?: boolean; | ||||
| } | ||||
| 
 | ||||
| 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 { Transaction } from '../interfaces/electrs.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 { isPlatformBrowser } from '@angular/common'; | ||||
| import { filter, map, scan, shareReplay } from 'rxjs/operators'; | ||||
| @ -113,7 +113,7 @@ export class StateService { | ||||
|   utxoSpent$ = new Subject<object>(); | ||||
|   difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1); | ||||
|   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>(); | ||||
|   isLoadingWebSocket$ = new ReplaySubject<boolean>(1); | ||||
|   isLoadingMempool$ = new BehaviorSubject<boolean>(true); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user