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)">
 | 
			
		||||
            <label class="form-check-label d-flex flex-column" for="accelerate">
 | 
			
		||||
              <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) {
 | 
			
		||||
                  <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 {
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,11 @@
 | 
			
		||||
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 { nextRoundNumber } from '../../shared/common.utils';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { AudioService } from '../../services/audio.service';
 | 
			
		||||
import { AccelerationEstimate } from '../accelerate-preview/accelerate-preview.component';
 | 
			
		||||
import { EtaService } from '../../services/eta.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-accelerate-checkout',
 | 
			
		||||
@ -24,8 +26,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
 | 
			
		||||
  square: { appId: string, locationId: string};
 | 
			
		||||
  accelerationUUID: string;
 | 
			
		||||
  estimateSubscription: Subscription;
 | 
			
		||||
  estimate: AccelerationEstimate;
 | 
			
		||||
  maxBidBoost: number; // sats
 | 
			
		||||
  cost: number; // sats
 | 
			
		||||
  etaInfo$: Observable<{ hashratePercentage: number, ETA: number, acceleratedETA: number }>;
 | 
			
		||||
 | 
			
		||||
  // square
 | 
			
		||||
  loadingCashapp = false;
 | 
			
		||||
@ -39,6 +43,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private servicesApiService: ServicesApiServices,
 | 
			
		||||
    private stateService: StateService,
 | 
			
		||||
    private etaService: EtaService,
 | 
			
		||||
    private audioService: AudioService,
 | 
			
		||||
    private cd: ChangeDetectorRef
 | 
			
		||||
  ) {
 | 
			
		||||
@ -59,7 +64,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
 | 
			
		||||
        locationId: ids.squareLocationId
 | 
			
		||||
      };
 | 
			
		||||
      if (this.step === 'cta') {
 | 
			
		||||
        this.estimate();
 | 
			
		||||
        this.fetchEstimate();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
@ -99,7 +104,7 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
 | 
			
		||||
  /**
 | 
			
		||||
   * Accelerator
 | 
			
		||||
   */
 | 
			
		||||
  estimate() {
 | 
			
		||||
  fetchEstimate() {
 | 
			
		||||
    if (this.estimateSubscription) {
 | 
			
		||||
      this.estimateSubscription.unsubscribe();
 | 
			
		||||
    }
 | 
			
		||||
@ -110,16 +115,17 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
 | 
			
		||||
        if (response.status === 204) {
 | 
			
		||||
          this.error = `cannot_accelerate_tx`;
 | 
			
		||||
        } else {
 | 
			
		||||
          const estimation = response.body;
 | 
			
		||||
          if (!estimation) {
 | 
			
		||||
          this.estimate = response.body;
 | 
			
		||||
          if (!this.estimate) {
 | 
			
		||||
            this.error = `cannot_accelerate_tx`;
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          // 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;
 | 
			
		||||
          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>
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <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>
 | 
			
		||||
          <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>
 | 
			
		||||
          <ng-container *ngIf="(etaInfo$ | async) as etaInfo">
 | 
			
		||||
            <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 class="col pie">
 | 
			
		||||
          <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 { Subscription, catchError, of, tap } from 'rxjs';
 | 
			
		||||
import { Observable, Subscription, catchError, of, tap } from 'rxjs';
 | 
			
		||||
import { StorageService } from '../../services/storage.service';
 | 
			
		||||
import { Transaction } from '../../interfaces/electrs.interface';
 | 
			
		||||
import { nextRoundNumber } from '../../shared/common.utils';
 | 
			
		||||
@ -8,7 +8,6 @@ import { AudioService } from '../../services/audio.service';
 | 
			
		||||
import { StateService } from '../../services/state.service';
 | 
			
		||||
import { MiningStats } from '../../services/mining.service';
 | 
			
		||||
import { EtaService } from '../../services/eta.service';
 | 
			
		||||
import { DifficultyAdjustment, MempoolPosition, SinglePoolStats } from '../../interfaces/node-api.interface';
 | 
			
		||||
 | 
			
		||||
export type AccelerationEstimate = {
 | 
			
		||||
  txSummary: TxSummary;
 | 
			
		||||
@ -19,6 +18,7 @@ export type AccelerationEstimate = {
 | 
			
		||||
  cost: number;
 | 
			
		||||
  mempoolBaseFee: number;
 | 
			
		||||
  vsizeFee: number;
 | 
			
		||||
  pools: number[]
 | 
			
		||||
}
 | 
			
		||||
export type TxSummary = {
 | 
			
		||||
  txid: string; // txid of the current transaction
 | 
			
		||||
@ -44,7 +44,6 @@ export const MAX_BID_RATIO = 4;
 | 
			
		||||
})
 | 
			
		||||
export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges {
 | 
			
		||||
  @Input() tx: Transaction;
 | 
			
		||||
  @Input() mempoolPosition: MempoolPosition;
 | 
			
		||||
  @Input() miningStats: MiningStats;
 | 
			
		||||
  @Input() scrollEvent: boolean;
 | 
			
		||||
 | 
			
		||||
@ -54,11 +53,8 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
 | 
			
		||||
  estimateSubscription: Subscription;
 | 
			
		||||
  accelerationSubscription: Subscription;
 | 
			
		||||
  difficultySubscription: Subscription;
 | 
			
		||||
  da: DifficultyAdjustment;
 | 
			
		||||
  estimate: any;
 | 
			
		||||
  hashratePercentage?: number;
 | 
			
		||||
  ETA?: number;
 | 
			
		||||
  acceleratedETA?: number;
 | 
			
		||||
  etaInfo$: Observable<{ hashratePercentage: number, ETA: number, acceleratedETA: number }>;
 | 
			
		||||
  hasAncestors: boolean = false;
 | 
			
		||||
  minExtraCost = 0;
 | 
			
		||||
  minBidAllowed = 0;
 | 
			
		||||
@ -87,27 +83,19 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
 | 
			
		||||
    if (this.estimateSubscription) {
 | 
			
		||||
      this.estimateSubscription.unsubscribe();
 | 
			
		||||
    }
 | 
			
		||||
    this.difficultySubscription.unsubscribe();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnInit() {
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.accelerationUUID = window.crypto.randomUUID();
 | 
			
		||||
    this.difficultySubscription = this.stateService.difficultyAdjustment$.subscribe(da => {
 | 
			
		||||
      this.da = da;
 | 
			
		||||
      this.updateETA();
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngOnChanges(changes: SimpleChanges): void {
 | 
			
		||||
    if (changes.scrollEvent) {
 | 
			
		||||
      this.scrollToPreview('acceleratePreviewAnchor', 'start');
 | 
			
		||||
    }
 | 
			
		||||
    if (changes.miningStats || changes.mempoolPosition) {
 | 
			
		||||
      this.updateETA();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ngAfterViewInit() {
 | 
			
		||||
  ngAfterViewInit(): void {
 | 
			
		||||
    this.user = this.storageService.getAuth()?.user ?? null;
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
          
 | 
			
		||||
@ -178,40 +166,10 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
 | 
			
		||||
    ).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
 | 
			
		||||
   */
 | 
			
		||||
  setUserBid({ fee, index }: { fee: number, index: number}) {
 | 
			
		||||
  setUserBid({ fee, index }: { fee: number, index: number}): void {
 | 
			
		||||
    if (this.estimate) {
 | 
			
		||||
      this.selectFeeRateIndex = index;
 | 
			
		||||
      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
 | 
			
		||||
   */
 | 
			
		||||
  scrollToPreviewWithTimeout(id: string, position: ScrollLogicalPosition) {
 | 
			
		||||
  scrollToPreviewWithTimeout(id: string, position: ScrollLogicalPosition): void {
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      this.scrollToPreview(id, position);
 | 
			
		||||
    }, 100);
 | 
			
		||||
  }
 | 
			
		||||
  scrollToPreview(id: string, position: ScrollLogicalPosition) {
 | 
			
		||||
  scrollToPreview(id: string, position: ScrollLogicalPosition): void {
 | 
			
		||||
    const acceleratePreviewAnchor = document.getElementById(id);
 | 
			
		||||
    if (acceleratePreviewAnchor) {
 | 
			
		||||
      this.cd.markForCheck();
 | 
			
		||||
@ -242,7 +200,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
 | 
			
		||||
  /**
 | 
			
		||||
   * Send acceleration request
 | 
			
		||||
   */
 | 
			
		||||
  accelerate() {
 | 
			
		||||
  accelerate(): void {
 | 
			
		||||
    if (this.accelerationSubscription) {
 | 
			
		||||
      this.accelerationSubscription.unsubscribe();
 | 
			
		||||
    }
 | 
			
		||||
@ -268,7 +226,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  isLoggedIn() {
 | 
			
		||||
  isLoggedIn(): boolean {
 | 
			
		||||
    const auth = this.storageService.getAuth();
 | 
			
		||||
    return auth !== null;
 | 
			
		||||
  }
 | 
			
		||||
@ -280,7 +238,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  @HostListener('window:scroll', ['$event']) // for window scroll events
 | 
			
		||||
  onScroll() {
 | 
			
		||||
  onScroll(): void {
 | 
			
		||||
    if (this.estimate) {
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        this.onScroll();
 | 
			
		||||
 | 
			
		||||
@ -83,7 +83,7 @@
 | 
			
		||||
      <div class="clearfix"></div>
 | 
			
		||||
 | 
			
		||||
      <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>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,8 +3,10 @@ import { AccelerationPosition, CpfpInfo, DifficultyAdjustment, MempoolPosition,
 | 
			
		||||
import { StateService } from './state.service';
 | 
			
		||||
import { MempoolBlock } from '../interfaces/websocket.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 { AccelerationEstimate } from '../components/accelerate-preview/accelerate-preview.component';
 | 
			
		||||
import { Observable, combineLatest, map, of } from 'rxjs';
 | 
			
		||||
 | 
			
		||||
export interface ETA {
 | 
			
		||||
  now: number, // time at which calculation performed
 | 
			
		||||
@ -19,8 +21,50 @@ export interface ETA {
 | 
			
		||||
export class EtaService {
 | 
			
		||||
  constructor(
 | 
			
		||||
    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 {
 | 
			
		||||
    for (let txInBlockIndex = 0; txInBlockIndex < mempoolBlocks.length; txInBlockIndex++) {
 | 
			
		||||
      const block = mempoolBlocks[txInBlockIndex];
 | 
			
		||||
@ -41,7 +85,7 @@ export class EtaService {
 | 
			
		||||
          return {
 | 
			
		||||
            block: txInBlockIndex,
 | 
			
		||||
            vsize: (1 - feePosition) * blockedFilledPercentage * this.stateService.blockVSize,
 | 
			
		||||
          }
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (feerate >= block.feeRange[block.feeRange.length - 1]) {
 | 
			
		||||
@ -49,14 +93,14 @@ export class EtaService {
 | 
			
		||||
        return {
 | 
			
		||||
          block: txInBlockIndex,
 | 
			
		||||
          vsize: 0,
 | 
			
		||||
        }
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    // at the very back of the last block
 | 
			
		||||
    return {
 | 
			
		||||
      block: mempoolBlocks.length - 1,
 | 
			
		||||
      vsize: mempoolBlocks[mempoolBlocks.length - 1].blockVSize,
 | 
			
		||||
    }
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  calculateETA(
 | 
			
		||||
@ -88,7 +132,7 @@ export class EtaService {
 | 
			
		||||
        time: now + (60_000 * (mempoolPosition.block + 1)),
 | 
			
		||||
        wait: (60_000 * (mempoolPosition.block + 1)),
 | 
			
		||||
        blocks: mempoolPosition.block + 1,
 | 
			
		||||
      }
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 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,
 | 
			
		||||
        wait,
 | 
			
		||||
        blocks,
 | 
			
		||||
      }
 | 
			
		||||
      };
 | 
			
		||||
    } else {
 | 
			
		||||
      // accelerated transactions
 | 
			
		||||
 | 
			
		||||
@ -121,7 +165,7 @@ export class EtaService {
 | 
			
		||||
        pools[pool.poolUniqueId] = pool;
 | 
			
		||||
      }
 | 
			
		||||
      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 = [
 | 
			
		||||
        {
 | 
			
		||||
          block: unacceleratedPosition.block,
 | 
			
		||||
@ -163,7 +207,7 @@ export class EtaService {
 | 
			
		||||
        // find H_i
 | 
			
		||||
        const H = shares.reduce((total, share) => total + (share.block <= i ? share.hashrateShare : 0), 0);
 | 
			
		||||
        // find S_i
 | 
			
		||||
        let S = H * (1 - tailProb);
 | 
			
		||||
        const S = H * (1 - tailProb);
 | 
			
		||||
        // accumulate sum (S_i x i)
 | 
			
		||||
        Q += (S * (i + 1));
 | 
			
		||||
        // accumulate sum (S_j)
 | 
			
		||||
@ -178,6 +222,6 @@ export class EtaService {
 | 
			
		||||
        time: eta + now + da.timeOffset,
 | 
			
		||||
        wait: eta,
 | 
			
		||||
        blocks: Math.ceil(eta / da.adjustedTimeAvg),
 | 
			
		||||
      }
 | 
			
		||||
      };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -150,7 +150,7 @@ export class StateService {
 | 
			
		||||
  utxoSpent$ = new Subject<object>();
 | 
			
		||||
  difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1);
 | 
			
		||||
  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>();
 | 
			
		||||
  multiAddressTransactions$ = new Subject<{ [address: string]: { mempool: Transaction[], confirmed: Transaction[], removed: Transaction[] }}>();
 | 
			
		||||
  blockTransactions$ = new Subject<Transaction>();
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user