Merge pull request #5199 from mempool/mononaut/tracker-acceleration-eta
Add projected acceleration ETA to tracker page
This commit is contained in:
		
						commit
						9d3044efae
					
				| @ -21,7 +21,7 @@ | |||||||
|             <input type="radio" class="form-check-input" id="accelerate" name="accelerate" (change)="selectedOptionChanged($event)"> |             <input type="radio" class="form-check-input" id="accelerate" name="accelerate" (change)="selectedOptionChanged($event)"> | ||||||
|             <label class="form-check-label d-flex flex-column" for="accelerate"> |             <label class="form-check-label d-flex flex-column" for="accelerate"> | ||||||
|               <span class="font-weight-bold">Accelerate</span> |               <span class="font-weight-bold">Accelerate</span> | ||||||
|               <span style="color: rgb(186, 186, 186); font-size: 14px;">Confirmation expected within ~30 minutes<br> |               <span style="color: rgb(186, 186, 186); font-size: 14px;" *ngIf="(etaInfo$ | async) as etaInfo">Confirmation expected <app-time kind="within" [time]="etaInfo.acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time><br> | ||||||
|                 @if (!calculating) { |                 @if (!calculating) { | ||||||
|                   <app-fiat [value]="cost"></app-fiat>fee (<span><small style="font-family: monospace;">{{ cost | number }}</small> <span class="symbol" i18n="shared.sats">sats</span></span>) |                   <app-fiat [value]="cost"></app-fiat>fee (<span><small style="font-family: monospace;">{{ cost | number }}</small> <span class="symbol" i18n="shared.sats">sats</span></span>) | ||||||
|                 } @else { |                 } @else { | ||||||
|  | |||||||
| @ -1,9 +1,11 @@ | |||||||
| import { Component, OnInit, OnDestroy, Output, EventEmitter, Input, ChangeDetectorRef, SimpleChanges } from '@angular/core'; | import { Component, OnInit, OnDestroy, Output, EventEmitter, Input, ChangeDetectorRef, SimpleChanges } from '@angular/core'; | ||||||
| import { Subscription, tap, of, catchError } from 'rxjs'; | import { Subscription, tap, of, catchError, Observable } from 'rxjs'; | ||||||
| import { ServicesApiServices } from '../../services/services-api.service'; | import { ServicesApiServices } from '../../services/services-api.service'; | ||||||
| import { nextRoundNumber } from '../../shared/common.utils'; | import { nextRoundNumber } from '../../shared/common.utils'; | ||||||
| import { StateService } from '../../services/state.service'; | import { StateService } from '../../services/state.service'; | ||||||
| import { AudioService } from '../../services/audio.service'; | import { AudioService } from '../../services/audio.service'; | ||||||
|  | import { AccelerationEstimate } from '../accelerate-preview/accelerate-preview.component'; | ||||||
|  | import { EtaService } from '../../services/eta.service'; | ||||||
| 
 | 
 | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-accelerate-checkout', |   selector: 'app-accelerate-checkout', | ||||||
| @ -24,8 +26,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|   square: { appId: string, locationId: string}; |   square: { appId: string, locationId: string}; | ||||||
|   accelerationUUID: string; |   accelerationUUID: string; | ||||||
|   estimateSubscription: Subscription; |   estimateSubscription: Subscription; | ||||||
|  |   estimate: AccelerationEstimate; | ||||||
|   maxBidBoost: number; // sats
 |   maxBidBoost: number; // sats
 | ||||||
|   cost: number; // sats
 |   cost: number; // sats
 | ||||||
|  |   etaInfo$: Observable<{ hashratePercentage: number, ETA: number, acceleratedETA: number }>; | ||||||
| 
 | 
 | ||||||
|   // square
 |   // square
 | ||||||
|   loadingCashapp = false; |   loadingCashapp = false; | ||||||
| @ -39,6 +43,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|   constructor( |   constructor( | ||||||
|     private servicesApiService: ServicesApiServices, |     private servicesApiService: ServicesApiServices, | ||||||
|     private stateService: StateService, |     private stateService: StateService, | ||||||
|  |     private etaService: EtaService, | ||||||
|     private audioService: AudioService, |     private audioService: AudioService, | ||||||
|     private cd: ChangeDetectorRef |     private cd: ChangeDetectorRef | ||||||
|   ) { |   ) { | ||||||
| @ -59,7 +64,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|         locationId: ids.squareLocationId |         locationId: ids.squareLocationId | ||||||
|       }; |       }; | ||||||
|       if (this.step === 'cta') { |       if (this.step === 'cta') { | ||||||
|         this.estimate(); |         this.fetchEstimate(); | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| @ -99,7 +104,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|   /** |   /** | ||||||
|    * Accelerator |    * Accelerator | ||||||
|    */ |    */ | ||||||
|   estimate() { |   fetchEstimate() { | ||||||
|     if (this.estimateSubscription) { |     if (this.estimateSubscription) { | ||||||
|       this.estimateSubscription.unsubscribe(); |       this.estimateSubscription.unsubscribe(); | ||||||
|     } |     } | ||||||
| @ -110,16 +115,17 @@ export class AccelerateCheckout implements OnInit, OnDestroy { | |||||||
|         if (response.status === 204) { |         if (response.status === 204) { | ||||||
|           this.error = `cannot_accelerate_tx`; |           this.error = `cannot_accelerate_tx`; | ||||||
|         } else { |         } else { | ||||||
|           const estimation = response.body; |           this.estimate = response.body; | ||||||
|           if (!estimation) { |           if (!this.estimate) { | ||||||
|             this.error = `cannot_accelerate_tx`; |             this.error = `cannot_accelerate_tx`; | ||||||
|             return; |             return; | ||||||
|           } |           } | ||||||
|           // Make min extra fee at least 50% of the current tx fee
 |           // Make min extra fee at least 50% of the current tx fee
 | ||||||
|           const minExtraBoost = nextRoundNumber(Math.max(estimation.cost * 2, estimation.txSummary.effectiveFee)); |           const minExtraBoost = nextRoundNumber(Math.max(this.estimate.cost * 2, this.estimate.txSummary.effectiveFee)); | ||||||
|           const DEFAULT_BID_RATIO = 1.5; |           const DEFAULT_BID_RATIO = 1.5; | ||||||
|           this.maxBidBoost = minExtraBoost * DEFAULT_BID_RATIO; |           this.maxBidBoost = minExtraBoost * DEFAULT_BID_RATIO; | ||||||
|           this.cost = this.maxBidBoost + estimation.mempoolBaseFee + estimation.vsizeFee; |           this.cost = this.maxBidBoost + this.estimate.mempoolBaseFee + this.estimate.vsizeFee; | ||||||
|  |           this.etaInfo$ = this.etaService.getProjectedEtaObservable(this.estimate); | ||||||
|         } |         } | ||||||
|       }), |       }), | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -68,8 +68,10 @@ | |||||||
|       <h5 *ngIf="estimate?.pools?.length" i18n="accelerator.how-much-faster">How much faster?</h5> |       <h5 *ngIf="estimate?.pools?.length" i18n="accelerator.how-much-faster">How much faster?</h5> | ||||||
|       <div class="row"> |       <div class="row"> | ||||||
|         <div class="col"> |         <div class="col"> | ||||||
|           <small class="form-text text-muted mb-2" i18n="accelerator.hashrate-percentage-description">Your transaction will be prioritized by up to {{ hashratePercentage | number : '1.1-1' }}% of miners.</small> |           <ng-container *ngIf="(etaInfo$ | async) as etaInfo"> | ||||||
|           <small class="form-text text-muted mb-2" i18n="accelerator.time-estimate-description">This will reduce your expected waiting time until the first confirmation to <app-time kind="within" [time]="acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time></small> |             <small class="form-text text-muted mb-2" i18n="accelerator.hashrate-percentage-description">Your transaction will be prioritized by up to {{ etaInfo.hashratePercentage | number : '1.1-1' }}% of miners.</small> | ||||||
|  |             <small class="form-text text-muted mb-2" i18n="accelerator.time-estimate-description">This will reduce your expected waiting time until the first confirmation to <app-time kind="within" [time]="etaInfo.acceleratedETA" [fastRender]="false" [fixedRender]="true"></app-time></small> | ||||||
|  |           </ng-container> | ||||||
|         </div> |         </div> | ||||||
|         <div class="col pie"> |         <div class="col pie"> | ||||||
|           <app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box> |           <app-active-acceleration-box [miningStats]="miningStats" [pools]="estimate.pools" [chartOnly]="true"></app-active-acceleration-box> | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core'; | import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges, HostListener, ChangeDetectorRef } from '@angular/core'; | ||||||
| import { Subscription, catchError, of, tap } from 'rxjs'; | import { Observable, Subscription, catchError, of, tap } from 'rxjs'; | ||||||
| import { StorageService } from '../../services/storage.service'; | import { StorageService } from '../../services/storage.service'; | ||||||
| import { Transaction } from '../../interfaces/electrs.interface'; | import { Transaction } from '../../interfaces/electrs.interface'; | ||||||
| import { nextRoundNumber } from '../../shared/common.utils'; | import { nextRoundNumber } from '../../shared/common.utils'; | ||||||
| @ -8,7 +8,6 @@ import { AudioService } from '../../services/audio.service'; | |||||||
| import { StateService } from '../../services/state.service'; | import { StateService } from '../../services/state.service'; | ||||||
| import { MiningStats } from '../../services/mining.service'; | import { MiningStats } from '../../services/mining.service'; | ||||||
| import { EtaService } from '../../services/eta.service'; | import { EtaService } from '../../services/eta.service'; | ||||||
| import { DifficultyAdjustment, MempoolPosition, SinglePoolStats } from '../../interfaces/node-api.interface'; |  | ||||||
| 
 | 
 | ||||||
| export type AccelerationEstimate = { | export type AccelerationEstimate = { | ||||||
|   txSummary: TxSummary; |   txSummary: TxSummary; | ||||||
| @ -19,6 +18,7 @@ export type AccelerationEstimate = { | |||||||
|   cost: number; |   cost: number; | ||||||
|   mempoolBaseFee: number; |   mempoolBaseFee: number; | ||||||
|   vsizeFee: number; |   vsizeFee: number; | ||||||
|  |   pools: number[] | ||||||
| } | } | ||||||
| export type TxSummary = { | export type TxSummary = { | ||||||
|   txid: string; // txid of the current transaction
 |   txid: string; // txid of the current transaction
 | ||||||
| @ -44,7 +44,6 @@ export const MAX_BID_RATIO = 4; | |||||||
| }) | }) | ||||||
| export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges { | export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges { | ||||||
|   @Input() tx: Transaction; |   @Input() tx: Transaction; | ||||||
|   @Input() mempoolPosition: MempoolPosition; |  | ||||||
|   @Input() miningStats: MiningStats; |   @Input() miningStats: MiningStats; | ||||||
|   @Input() scrollEvent: boolean; |   @Input() scrollEvent: boolean; | ||||||
| 
 | 
 | ||||||
| @ -54,11 +53,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | |||||||
|   estimateSubscription: Subscription; |   estimateSubscription: Subscription; | ||||||
|   accelerationSubscription: Subscription; |   accelerationSubscription: Subscription; | ||||||
|   difficultySubscription: Subscription; |   difficultySubscription: Subscription; | ||||||
|   da: DifficultyAdjustment; |  | ||||||
|   estimate: any; |   estimate: any; | ||||||
|   hashratePercentage?: number; |   etaInfo$: Observable<{ hashratePercentage: number, ETA: number, acceleratedETA: number }>; | ||||||
|   ETA?: number; |  | ||||||
|   acceleratedETA?: number; |  | ||||||
|   hasAncestors: boolean = false; |   hasAncestors: boolean = false; | ||||||
|   minExtraCost = 0; |   minExtraCost = 0; | ||||||
|   minBidAllowed = 0; |   minBidAllowed = 0; | ||||||
| @ -87,27 +83,19 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | |||||||
|     if (this.estimateSubscription) { |     if (this.estimateSubscription) { | ||||||
|       this.estimateSubscription.unsubscribe(); |       this.estimateSubscription.unsubscribe(); | ||||||
|     } |     } | ||||||
|     this.difficultySubscription.unsubscribe(); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnInit() { |   ngOnInit(): void { | ||||||
|     this.accelerationUUID = window.crypto.randomUUID(); |     this.accelerationUUID = window.crypto.randomUUID(); | ||||||
|     this.difficultySubscription = this.stateService.difficultyAdjustment$.subscribe(da => { |  | ||||||
|       this.da = da; |  | ||||||
|       this.updateETA(); |  | ||||||
|     }) |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngOnChanges(changes: SimpleChanges): void { |   ngOnChanges(changes: SimpleChanges): void { | ||||||
|     if (changes.scrollEvent) { |     if (changes.scrollEvent) { | ||||||
|       this.scrollToPreview('acceleratePreviewAnchor', 'start'); |       this.scrollToPreview('acceleratePreviewAnchor', 'start'); | ||||||
|     } |     } | ||||||
|     if (changes.miningStats || changes.mempoolPosition) { |  | ||||||
|       this.updateETA(); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   ngAfterViewInit() { |   ngAfterViewInit(): void { | ||||||
|     this.user = this.storageService.getAuth()?.user ?? null; |     this.user = this.storageService.getAuth()?.user ?? null; | ||||||
| 
 | 
 | ||||||
|     this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe( |     this.estimateSubscription = this.servicesApiService.estimate$(this.tx.txid).pipe( | ||||||
| @ -132,7 +120,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | |||||||
|             } |             } | ||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           this.updateETA(); |           this.etaInfo$ = this.etaService.getProjectedEtaObservable(this.estimate, this.miningStats); | ||||||
| 
 | 
 | ||||||
|           this.hasAncestors = this.estimate.txSummary.ancestorCount > 1; |           this.hasAncestors = this.estimate.txSummary.ancestorCount > 1; | ||||||
|            |            | ||||||
| @ -178,40 +166,10 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | |||||||
|     ).subscribe(); |     ).subscribe(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   updateETA(): void { |  | ||||||
|     if (!this.mempoolPosition || !this.estimate?.pools?.length || !this.miningStats || !this.da) { |  | ||||||
|       this.hashratePercentage = undefined; |  | ||||||
|       this.ETA = undefined; |  | ||||||
|       this.acceleratedETA = undefined; |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|     const pools: { [id: number]: SinglePoolStats } = {}; |  | ||||||
|     for (const pool of this.miningStats.pools) { |  | ||||||
|       pools[pool.poolUniqueId] = pool; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let totalAcceleratedHashrate = 0; |  | ||||||
|     for (const poolId of this.estimate.pools) { |  | ||||||
|       const pool = pools[poolId]; |  | ||||||
|       if (!pool) { |  | ||||||
|         continue; |  | ||||||
|       } |  | ||||||
|       totalAcceleratedHashrate += pool.lastEstimatedHashrate; |  | ||||||
|     } |  | ||||||
|     const acceleratingHashrateFraction = (totalAcceleratedHashrate / this.miningStats.lastEstimatedHashrate) |  | ||||||
|     this.hashratePercentage = acceleratingHashrateFraction * 100; |  | ||||||
| 
 |  | ||||||
|     this.ETA = Date.now() + this.da.timeAvg * this.mempoolPosition.block; |  | ||||||
|     this.acceleratedETA = this.etaService.calculateETAFromShares([ |  | ||||||
|       { block: this.mempoolPosition.block, hashrateShare: (1 - acceleratingHashrateFraction) },  |  | ||||||
|       { block: 0, hashrateShare: acceleratingHashrateFraction }, |  | ||||||
|     ], this.da).time; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /** |   /** | ||||||
|    * User changed his bid |    * User changed his bid | ||||||
|    */ |    */ | ||||||
|   setUserBid({ fee, index }: { fee: number, index: number}) { |   setUserBid({ fee, index }: { fee: number, index: number}): void { | ||||||
|     if (this.estimate) { |     if (this.estimate) { | ||||||
|       this.selectFeeRateIndex = index; |       this.selectFeeRateIndex = index; | ||||||
|       this.userBid = Math.max(0, fee); |       this.userBid = Math.max(0, fee); | ||||||
| @ -222,12 +180,12 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | |||||||
|   /** |   /** | ||||||
|    * Scroll to element id with or without setTimeout |    * Scroll to element id with or without setTimeout | ||||||
|    */ |    */ | ||||||
|   scrollToPreviewWithTimeout(id: string, position: ScrollLogicalPosition) { |   scrollToPreviewWithTimeout(id: string, position: ScrollLogicalPosition): void { | ||||||
|     setTimeout(() => { |     setTimeout(() => { | ||||||
|       this.scrollToPreview(id, position); |       this.scrollToPreview(id, position); | ||||||
|     }, 100); |     }, 100); | ||||||
|   } |   } | ||||||
|   scrollToPreview(id: string, position: ScrollLogicalPosition) { |   scrollToPreview(id: string, position: ScrollLogicalPosition): void { | ||||||
|     const acceleratePreviewAnchor = document.getElementById(id); |     const acceleratePreviewAnchor = document.getElementById(id); | ||||||
|     if (acceleratePreviewAnchor) { |     if (acceleratePreviewAnchor) { | ||||||
|       this.cd.markForCheck(); |       this.cd.markForCheck(); | ||||||
| @ -242,7 +200,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | |||||||
|   /** |   /** | ||||||
|    * Send acceleration request |    * Send acceleration request | ||||||
|    */ |    */ | ||||||
|   accelerate() { |   accelerate(): void { | ||||||
|     if (this.accelerationSubscription) { |     if (this.accelerationSubscription) { | ||||||
|       this.accelerationSubscription.unsubscribe(); |       this.accelerationSubscription.unsubscribe(); | ||||||
|     } |     } | ||||||
| @ -268,7 +226,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | |||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   isLoggedIn() { |   isLoggedIn(): boolean { | ||||||
|     const auth = this.storageService.getAuth(); |     const auth = this.storageService.getAuth(); | ||||||
|     return auth !== null; |     return auth !== null; | ||||||
|   } |   } | ||||||
| @ -280,7 +238,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|   @HostListener('window:scroll', ['$event']) // for window scroll events
 |   @HostListener('window:scroll', ['$event']) // for window scroll events
 | ||||||
|   onScroll() { |   onScroll(): void { | ||||||
|     if (this.estimate) { |     if (this.estimate) { | ||||||
|       setTimeout(() => { |       setTimeout(() => { | ||||||
|         this.onScroll(); |         this.onScroll(); | ||||||
|  | |||||||
| @ -83,7 +83,7 @@ | |||||||
|       <div class="clearfix"></div> |       <div class="clearfix"></div> | ||||||
| 
 | 
 | ||||||
|       <div class="box"> |       <div class="box"> | ||||||
|         <app-accelerate-preview [tx]="tx" [miningStats]="miningStats" [mempoolPosition]="mempoolPosition" [scrollEvent]="scrollIntoAccelPreview"></app-accelerate-preview> |         <app-accelerate-preview [tx]="tx" [miningStats]="miningStats" [scrollEvent]="scrollIntoAccelPreview"></app-accelerate-preview> | ||||||
|       </div> |       </div> | ||||||
|     </ng-container> |     </ng-container> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3,8 +3,10 @@ import { AccelerationPosition, CpfpInfo, DifficultyAdjustment, MempoolPosition, | |||||||
| import { StateService } from './state.service'; | import { StateService } from './state.service'; | ||||||
| import { MempoolBlock } from '../interfaces/websocket.interface'; | import { MempoolBlock } from '../interfaces/websocket.interface'; | ||||||
| import { Transaction } from '../interfaces/electrs.interface'; | import { Transaction } from '../interfaces/electrs.interface'; | ||||||
| import { MiningStats } from './mining.service'; | import { MiningService, MiningStats } from './mining.service'; | ||||||
| import { getUnacceleratedFeeRate } from '../shared/transaction.utils'; | import { getUnacceleratedFeeRate } from '../shared/transaction.utils'; | ||||||
|  | import { AccelerationEstimate } from '../components/accelerate-preview/accelerate-preview.component'; | ||||||
|  | import { Observable, combineLatest, map, of } from 'rxjs'; | ||||||
| 
 | 
 | ||||||
| export interface ETA { | export interface ETA { | ||||||
|   now: number, // time at which calculation performed
 |   now: number, // time at which calculation performed
 | ||||||
| @ -19,8 +21,50 @@ export interface ETA { | |||||||
| export class EtaService { | export class EtaService { | ||||||
|   constructor( |   constructor( | ||||||
|     private stateService: StateService, |     private stateService: StateService, | ||||||
|  |     private miningService: MiningService, | ||||||
|   ) { } |   ) { } | ||||||
| 
 | 
 | ||||||
|  |   getProjectedEtaObservable(estimate: AccelerationEstimate, miningStats?: MiningStats): Observable<{ hashratePercentage: number, ETA: number, acceleratedETA: number }> { | ||||||
|  |     return combineLatest([ | ||||||
|  |       this.stateService.mempoolTxPosition$.pipe(map(p => p.position)), | ||||||
|  |       this.stateService.difficultyAdjustment$, | ||||||
|  |       miningStats ? of(miningStats) : this.miningService.getMiningStats('1w'), | ||||||
|  |     ]).pipe( | ||||||
|  |       map(([mempoolPosition, da, miningStats]) => { | ||||||
|  |         if (!mempoolPosition || !estimate?.pools?.length || !miningStats || !da) { | ||||||
|  |           return { | ||||||
|  |             hashratePercentage: undefined, | ||||||
|  |             ETA: undefined, | ||||||
|  |             acceleratedETA: undefined, | ||||||
|  |           }; | ||||||
|  |         } | ||||||
|  |         const pools: { [id: number]: SinglePoolStats } = {}; | ||||||
|  |         for (const pool of miningStats.pools) { | ||||||
|  |           pools[pool.poolUniqueId] = pool; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let totalAcceleratedHashrate = 0; | ||||||
|  |         for (const poolId of estimate.pools) { | ||||||
|  |           const pool = pools[poolId]; | ||||||
|  |           if (!pool) { | ||||||
|  |             continue; | ||||||
|  |           } | ||||||
|  |           totalAcceleratedHashrate += pool.lastEstimatedHashrate; | ||||||
|  |         } | ||||||
|  |         const acceleratingHashrateFraction = (totalAcceleratedHashrate / miningStats.lastEstimatedHashrate); | ||||||
|  | 
 | ||||||
|  |         return { | ||||||
|  |           hashratePercentage: acceleratingHashrateFraction * 100, | ||||||
|  |           ETA: Date.now() + da.timeAvg * mempoolPosition.block, | ||||||
|  |           acceleratedETA: this.calculateETAFromShares([ | ||||||
|  |             { block: mempoolPosition.block, hashrateShare: (1 - acceleratingHashrateFraction) }, | ||||||
|  |             { block: 0, hashrateShare: acceleratingHashrateFraction }, | ||||||
|  |           ], da).time, | ||||||
|  |         }; | ||||||
|  |       }) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   mempoolPositionFromFees(feerate: number, mempoolBlocks: MempoolBlock[]): MempoolPosition { |   mempoolPositionFromFees(feerate: number, mempoolBlocks: MempoolBlock[]): MempoolPosition { | ||||||
|     for (let txInBlockIndex = 0; txInBlockIndex < mempoolBlocks.length; txInBlockIndex++) { |     for (let txInBlockIndex = 0; txInBlockIndex < mempoolBlocks.length; txInBlockIndex++) { | ||||||
|       const block = mempoolBlocks[txInBlockIndex]; |       const block = mempoolBlocks[txInBlockIndex]; | ||||||
| @ -41,7 +85,7 @@ export class EtaService { | |||||||
|           return { |           return { | ||||||
|             block: txInBlockIndex, |             block: txInBlockIndex, | ||||||
|             vsize: (1 - feePosition) * blockedFilledPercentage * this.stateService.blockVSize, |             vsize: (1 - feePosition) * blockedFilledPercentage * this.stateService.blockVSize, | ||||||
|           } |           }; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       if (feerate >= block.feeRange[block.feeRange.length - 1]) { |       if (feerate >= block.feeRange[block.feeRange.length - 1]) { | ||||||
| @ -49,14 +93,14 @@ export class EtaService { | |||||||
|         return { |         return { | ||||||
|           block: txInBlockIndex, |           block: txInBlockIndex, | ||||||
|           vsize: 0, |           vsize: 0, | ||||||
|         } |         }; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     // at the very back of the last block
 |     // at the very back of the last block
 | ||||||
|     return { |     return { | ||||||
|       block: mempoolBlocks.length - 1, |       block: mempoolBlocks.length - 1, | ||||||
|       vsize: mempoolBlocks[mempoolBlocks.length - 1].blockVSize, |       vsize: mempoolBlocks[mempoolBlocks.length - 1].blockVSize, | ||||||
|     } |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   calculateETA( |   calculateETA( | ||||||
| @ -88,7 +132,7 @@ export class EtaService { | |||||||
|         time: now + (60_000 * (mempoolPosition.block + 1)), |         time: now + (60_000 * (mempoolPosition.block + 1)), | ||||||
|         wait: (60_000 * (mempoolPosition.block + 1)), |         wait: (60_000 * (mempoolPosition.block + 1)), | ||||||
|         blocks: mempoolPosition.block + 1, |         blocks: mempoolPosition.block + 1, | ||||||
|       } |       }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // difficulty adjustment estimate is required to know avg block time on non-Liquid networks
 |     // difficulty adjustment estimate is required to know avg block time on non-Liquid networks
 | ||||||
| @ -104,7 +148,7 @@ export class EtaService { | |||||||
|         time: wait + now + da.timeOffset, |         time: wait + now + da.timeOffset, | ||||||
|         wait, |         wait, | ||||||
|         blocks, |         blocks, | ||||||
|       } |       }; | ||||||
|     } else { |     } else { | ||||||
|       // accelerated transactions
 |       // accelerated transactions
 | ||||||
| 
 | 
 | ||||||
| @ -121,7 +165,7 @@ export class EtaService { | |||||||
|         pools[pool.poolUniqueId] = pool; |         pools[pool.poolUniqueId] = pool; | ||||||
|       } |       } | ||||||
|       const unacceleratedPosition = this.mempoolPositionFromFees(getUnacceleratedFeeRate(tx, true), mempoolBlocks); |       const unacceleratedPosition = this.mempoolPositionFromFees(getUnacceleratedFeeRate(tx, true), mempoolBlocks); | ||||||
|       let totalAcceleratedHashrate = accelerationPositions.reduce((total, pos) => total + (pools[pos.poolId].lastEstimatedHashrate), 0); |       const totalAcceleratedHashrate = accelerationPositions.reduce((total, pos) => total + (pools[pos.poolId].lastEstimatedHashrate), 0); | ||||||
|       const shares = [ |       const shares = [ | ||||||
|         { |         { | ||||||
|           block: unacceleratedPosition.block, |           block: unacceleratedPosition.block, | ||||||
| @ -163,7 +207,7 @@ export class EtaService { | |||||||
|         // find H_i
 |         // find H_i
 | ||||||
|         const H = shares.reduce((total, share) => total + (share.block <= i ? share.hashrateShare : 0), 0); |         const H = shares.reduce((total, share) => total + (share.block <= i ? share.hashrateShare : 0), 0); | ||||||
|         // find S_i
 |         // find S_i
 | ||||||
|         let S = H * (1 - tailProb); |         const S = H * (1 - tailProb); | ||||||
|         // accumulate sum (S_i x i)
 |         // accumulate sum (S_i x i)
 | ||||||
|         Q += (S * (i + 1)); |         Q += (S * (i + 1)); | ||||||
|         // accumulate sum (S_j)
 |         // accumulate sum (S_j)
 | ||||||
| @ -178,6 +222,6 @@ export class EtaService { | |||||||
|         time: eta + now + da.timeOffset, |         time: eta + now + da.timeOffset, | ||||||
|         wait: eta, |         wait: eta, | ||||||
|         blocks: Math.ceil(eta / da.adjustedTimeAvg), |         blocks: Math.ceil(eta / da.adjustedTimeAvg), | ||||||
|       } |       }; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -150,7 +150,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, cpfp: CpfpInfo | null, accelerationPositions?: AccelerationPosition[] }>(); |   mempoolTxPosition$ = new BehaviorSubject<{ txid: string, position: MempoolPosition, cpfp: CpfpInfo | null, accelerationPositions?: AccelerationPosition[] }>(null); | ||||||
|   mempoolRemovedTransactions$ = new Subject<Transaction>(); |   mempoolRemovedTransactions$ = new Subject<Transaction>(); | ||||||
|   multiAddressTransactions$ = new Subject<{ [address: string]: { mempool: Transaction[], confirmed: Transaction[], removed: Transaction[] }}>(); |   multiAddressTransactions$ = new Subject<{ [address: string]: { mempool: Transaction[], confirmed: Transaction[], removed: Transaction[] }}>(); | ||||||
|   blockTransactions$ = new Subject<Transaction>(); |   blockTransactions$ = new Subject<Transaction>(); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user