mirror of
https://github.com/mempool/mempool.git
synced 2025-03-13 11:36:07 +01:00
Add projected acceleration ETA to tracker page
This commit is contained in:
parent
0c72e1b6ed
commit
517e82ec8b
7 changed files with 85 additions and 75 deletions
|
@ -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…
Add table
Reference in a new issue