Generate mining basic pool ranking (sorted by block found) for a specified timeframe
This commit is contained in:
@@ -22,6 +22,7 @@ import { BisqMasterPageComponent } from './components/bisq-master-page/bisq-mast
|
||||
import { SponsorComponent } from './components/sponsor/sponsor.component';
|
||||
import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component';
|
||||
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
|
||||
import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component';
|
||||
|
||||
let routes: Routes = [
|
||||
{
|
||||
@@ -58,6 +59,10 @@ let routes: Routes = [
|
||||
path: 'blocks',
|
||||
component: LatestBlocksComponent,
|
||||
},
|
||||
{
|
||||
path: 'pools',
|
||||
component: PoolRankingComponent,
|
||||
},
|
||||
{
|
||||
path: 'graphs',
|
||||
component: StatisticsComponent,
|
||||
|
||||
@@ -37,6 +37,7 @@ import { IncomingTransactionsGraphComponent } from './components/incoming-transa
|
||||
import { TimeSpanComponent } from './components/time-span/time-span.component';
|
||||
import { SeoService } from './services/seo.service';
|
||||
import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.component';
|
||||
import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component';
|
||||
import { LbtcPegsGraphComponent } from './components/lbtc-pegs-graph/lbtc-pegs-graph.component';
|
||||
import { AssetComponent } from './components/asset/asset.component';
|
||||
import { AssetsComponent } from './assets/assets.component';
|
||||
@@ -91,6 +92,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
FeeDistributionGraphComponent,
|
||||
IncomingTransactionsGraphComponent,
|
||||
MempoolGraphComponent,
|
||||
PoolRankingComponent,
|
||||
LbtcPegsGraphComponent,
|
||||
AssetComponent,
|
||||
AssetsComponent,
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<div class="container-xl">
|
||||
<div class="card-header">
|
||||
<i class="fa fa-area-chart"></i> <span i18n="mining.pools-by-vBytes">Pools</span>
|
||||
<form [formGroup]="radioGroupForm" class="formRadioGroup" (click)="savePoolsPreference()">
|
||||
<div class="btn-group btn-group-toggle" ngbRadioGroup name="radioBasic" formControlName="dateSpan">
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'1d'" [routerLink]="['/pools' | relativeUrl]" fragment="1d"> 1D
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'3d'" [routerLink]="['/pools' | relativeUrl]" fragment="3d"> 3D
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'1w'" [routerLink]="['/pools' | relativeUrl]" fragment="1w"> 1W
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'1m'" [routerLink]="['/pools' | relativeUrl]" fragment="1m"> 1M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'3m'" [routerLink]="['/pools' | relativeUrl]" fragment="3m"> 3M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'6m'" [routerLink]="['/pools' | relativeUrl]" fragment="6m"> 6M
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'1y'" [routerLink]="['/pools' | relativeUrl]" fragment="1y"> 1Y
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'2y'" [routerLink]="['/pools' | relativeUrl]" fragment="2y"> 2Y
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'3y'" [routerLink]="['/pools' | relativeUrl]" fragment="3y"> 3Y
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" [value]="'all'" [routerLink]="['/pools' | relativeUrl]" fragment="all"> ALL
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<table class="table table-borderless" [alwaysCallback]="true" infiniteScroll [infiniteScrollDistance]="1.5" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50">
|
||||
<thead>
|
||||
<th i18n="latest-blocks.height">Rank</th>
|
||||
<th class="d-none d-md-block"i18n="latest-blocks.timestamp">Name</th>
|
||||
<th i18n="latest-blocks.timestamp">Hashrate</th>
|
||||
<th class="d-none d-md-block" i18n="latest-blocks.mined">Block Count (share)</th>
|
||||
<th i18n="latest-blocks.transactions">Empty Blocks (ratio)</th>
|
||||
</thead>
|
||||
<tbody *ngIf="pools$ | async as pools">
|
||||
<tr>
|
||||
<td>-</td>
|
||||
<td>All miners</td>
|
||||
<td>{{ pools["lastEstimatedHashrate"]}} PH/s</td>
|
||||
<td>{{ pools["blockCount"] }}</td>
|
||||
<td>{{ pools["totalEmptyBlock"] }} ({{ pools["totalEmptyBlockRatio"] }}%)</td>
|
||||
</tr>
|
||||
<ng-template ngFor let-pool [ngForOf]="pools['poolsStats']">
|
||||
<tr>
|
||||
<td>{{ pool.rank }}</td>
|
||||
<td><a href="{{ pool.link }}">{{ pool.name }}</a></td>
|
||||
<td>{{ pool.lastEstimatedHashrate }} PH/s</td>
|
||||
<td>{{ pool.blockCount }} ({{ pool.share }}%)</td>
|
||||
<td>{{ pool.emptyBlocks }} ({{ pool.emptyBlockRatio }}%)</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { merge, Observable, ObservableInput, of } from 'rxjs';
|
||||
import { map, share, switchMap, tap } from 'rxjs/operators';
|
||||
import { PoolsStats } from 'src/app/interfaces/node-api.interface';
|
||||
import { StorageService } from 'src/app/services/storage.service';
|
||||
import { ApiService } from '../../services/api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pool-ranking',
|
||||
templateUrl: './pool-ranking.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class PoolRankingComponent implements OnInit {
|
||||
pools$: Observable<object>
|
||||
|
||||
radioGroupForm: FormGroup;
|
||||
poolsWindowPreference: string;
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private route: ActivatedRoute,
|
||||
private apiService: ApiService,
|
||||
private storageService: StorageService,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.poolsWindowPreference = this.storageService.getValue('poolsWindowPreference') ? this.storageService.getValue('poolsWindowPreference').trim() : '2h';
|
||||
this.radioGroupForm = this.formBuilder.group({
|
||||
dateSpan: this.poolsWindowPreference
|
||||
});
|
||||
|
||||
// Setup datespan triggers
|
||||
this.route.fragment.subscribe((fragment) => {
|
||||
if (['1d', '3d', '1w', '1m', '3m', '6m', '1y', '2y', '3y', 'all'].indexOf(fragment) > -1) {
|
||||
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
|
||||
}
|
||||
});
|
||||
merge(of(''), this.radioGroupForm.controls.dateSpan.valueChanges)
|
||||
.pipe(switchMap(() => this.onDateSpanChanged()))
|
||||
.subscribe((pools: any) => {
|
||||
console.log(pools);
|
||||
});
|
||||
|
||||
// Fetch initial mining pool data
|
||||
this.onDateSpanChanged();
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
}
|
||||
|
||||
rendered() {
|
||||
}
|
||||
|
||||
savePoolsPreference() {
|
||||
this.storageService.setValue('poolsWindowPreference', this.radioGroupForm.controls.dateSpan.value);
|
||||
this.poolsWindowPreference = this.radioGroupForm.controls.dateSpan.value;
|
||||
}
|
||||
|
||||
onDateSpanChanged(): ObservableInput<any> {
|
||||
let interval: string;
|
||||
console.log(this.poolsWindowPreference);
|
||||
switch (this.poolsWindowPreference) {
|
||||
case '1d': interval = '1 DAY'; break;
|
||||
case '3d': interval = '3 DAY'; break;
|
||||
case '1w': interval = '1 WEEK'; break;
|
||||
case '1m': interval = '1 MONTH'; break;
|
||||
case '3m': interval = '3 MONTH'; break;
|
||||
case '6m': interval = '6 MONTH'; break;
|
||||
case '1y': interval = '1 YEAR'; break;
|
||||
case '2y': interval = '2 YEAR'; break;
|
||||
case '3y': interval = '3 YEAR'; break;
|
||||
case 'all': interval = '1000 YEAR'; break;
|
||||
}
|
||||
this.pools$ = this.apiService.listPools$(interval).pipe(map(res => this.computeMiningStats(res)));
|
||||
return this.pools$;
|
||||
}
|
||||
|
||||
computeMiningStats(stats: PoolsStats) {
|
||||
const totalEmptyBlock = Object.values(stats.poolsStats).reduce((prev, cur) => {
|
||||
return prev + cur.emptyBlocks;
|
||||
}, 0);
|
||||
const totalEmptyBlockRatio = (totalEmptyBlock / stats.blockCount * 100).toFixed(2);
|
||||
const poolsStats = stats.poolsStats.map((poolStat) => {
|
||||
return {
|
||||
share: (poolStat.blockCount / stats.blockCount * 100).toFixed(2),
|
||||
lastEstimatedHashrate: (poolStat.blockCount / stats.blockCount * stats.lastEstimatedHashrate / Math.pow(10, 15)).toFixed(2),
|
||||
emptyBlockRatio: (poolStat.emptyBlocks / poolStat.blockCount * 100).toFixed(2),
|
||||
...poolStat
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
lastEstimatedHashrate: (stats.lastEstimatedHashrate / Math.pow(10, 15)).toFixed(2),
|
||||
blockCount: stats.blockCount,
|
||||
totalEmptyBlock: totalEmptyBlock,
|
||||
totalEmptyBlockRatio: totalEmptyBlockRatio,
|
||||
poolsStats: poolsStats,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,3 +51,16 @@ export interface LiquidPegs {
|
||||
}
|
||||
|
||||
export interface ITranslators { [language: string]: string; }
|
||||
|
||||
export interface PoolsStats {
|
||||
poolsStats: {
|
||||
pooldId: number,
|
||||
name: string,
|
||||
link: string,
|
||||
blockCount: number,
|
||||
emptyBlocks: number,
|
||||
rank: number,
|
||||
}[],
|
||||
blockCount: number,
|
||||
lastEstimatedHashrate: number,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { CpfpInfo, OptimizedMempoolStats, DifficultyAdjustment, AddressInformation, LiquidPegs, ITranslators } from '../interfaces/node-api.interface';
|
||||
import { CpfpInfo, OptimizedMempoolStats, DifficultyAdjustment, AddressInformation, LiquidPegs, ITranslators, PoolsStats } from '../interfaces/node-api.interface';
|
||||
import { Observable } from 'rxjs';
|
||||
import { StateService } from './state.service';
|
||||
import { WebsocketResponse } from '../interfaces/websocket.interface';
|
||||
@@ -120,4 +120,9 @@ export class ApiService {
|
||||
postTransaction$(hexPayload: string): Observable<any> {
|
||||
return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
|
||||
}
|
||||
|
||||
listPools$(interval: string) : Observable<PoolsStats> {
|
||||
const params = new HttpParams().set('interval', interval);
|
||||
return this.httpClient.get<PoolsStats>(this.apiBaseUrl + this.apiBasePath + '/api/v1/pools', {params});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,23 @@ import { Router, ActivatedRoute } from '@angular/router';
|
||||
})
|
||||
export class StorageService {
|
||||
constructor(private router: Router, private route: ActivatedRoute) {
|
||||
let graphWindowPreference: string = this.getValue('graphWindowPreference');
|
||||
this.setDefaultValueIfNeeded('graphWindowPreference', '2h');
|
||||
this.setDefaultValueIfNeeded('poolsWindowPreference', '1d');
|
||||
}
|
||||
|
||||
setDefaultValueIfNeeded(key: string, defaultValue: string) {
|
||||
let graphWindowPreference: string = this.getValue(key);
|
||||
if (graphWindowPreference === null) { // First visit to mempool.space
|
||||
if (this.router.url.includes("graphs")) {
|
||||
this.setValue('graphWindowPreference', this.route.snapshot.fragment ? this.route.snapshot.fragment : "2h");
|
||||
if (this.router.url.includes("graphs") || this.router.url.includes("pools")) {
|
||||
this.setValue(key, this.route.snapshot.fragment ? this.route.snapshot.fragment : defaultValue);
|
||||
} else {
|
||||
this.setValue('graphWindowPreference', "2h");
|
||||
this.setValue(key, defaultValue);
|
||||
}
|
||||
} else if (this.router.url.includes("graphs")) { // Visit a different graphs#fragment from last visit
|
||||
if (this.route.snapshot.fragment !== null && graphWindowPreference !== this.route.snapshot.fragment) {
|
||||
this.setValue('graphWindowPreference', this.route.snapshot.fragment);
|
||||
}
|
||||
} else if (this.router.url.includes("graphs") || this.router.url.includes("pools")) { // Visit a different graphs#fragment from last visit
|
||||
if (this.route.snapshot.fragment !== null && graphWindowPreference !== this.route.snapshot.fragment) {
|
||||
this.setValue(key, this.route.snapshot.fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getValue(key: string): string {
|
||||
|
||||
Reference in New Issue
Block a user