diff --git a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts
index 6d4c88a00..e839459cd 100644
--- a/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts
+++ b/frontend/src/app/components/accelerate-preview/accelerate-preview.component.ts
@@ -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();
diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html
index 2a2925879..ea7bd284a 100644
--- a/frontend/src/app/components/transaction/transaction.component.html
+++ b/frontend/src/app/components/transaction/transaction.component.html
@@ -83,7 +83,7 @@
diff --git a/frontend/src/app/services/eta.service.ts b/frontend/src/app/services/eta.service.ts
index 467f49554..7753a80b5 100644
--- a/frontend/src/app/services/eta.service.ts
+++ b/frontend/src/app/services/eta.service.ts
@@ -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),
- }
+ };
}
}
diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts
index 0d502747c..647e5a972 100644
--- a/frontend/src/app/services/state.service.ts
+++ b/frontend/src/app/services/state.service.ts
@@ -150,7 +150,7 @@ export class StateService {
utxoSpent$ = new Subject