Fetch missing block audit scores

This commit is contained in:
Mononaut
2022-11-01 14:01:50 -06:00
parent 1b3bc0ef4e
commit 5b6f713ef3
12 changed files with 194 additions and 26 deletions

View File

@@ -114,7 +114,7 @@
<td i18n="block.health">Block health</td>
<td>
<a *ngIf="block.extras?.matchRate != null" [routerLink]="['/block-audit/' | relativeUrl, blockHash]">{{ block.extras.matchRate }}%</a>
<span *ngIf="block.extras?.matchRate == null" i18n="unknown">Unknown</span>
<span *ngIf="block.extras?.matchRate === null" i18n="unknown">Unknown</span>
</td>
</tr>
</ng-template>

View File

@@ -4,7 +4,7 @@ import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { ElectrsApiService } from '../../services/electrs-api.service';
import { switchMap, tap, throttleTime, catchError, map, shareReplay, startWith, pairwise } from 'rxjs/operators';
import { Transaction, Vout } from '../../interfaces/electrs.interface';
import { Observable, of, Subscription, asyncScheduler, EMPTY } from 'rxjs';
import { Observable, of, Subscription, asyncScheduler, EMPTY, Subject } from 'rxjs';
import { StateService } from '../../services/state.service';
import { SeoService } from '../../services/seo.service';
import { WebsocketService } from '../../services/websocket.service';
@@ -60,6 +60,8 @@ export class BlockComponent implements OnInit, OnDestroy {
nextBlockTxListSubscription: Subscription = undefined;
timeLtrSubscription: Subscription;
timeLtr: boolean;
fetchAuditScore$ = new Subject<string>();
fetchAuditScoreSubscription: Subscription;
@ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent;
@@ -105,12 +107,30 @@ export class BlockComponent implements OnInit, OnDestroy {
if (block.id === this.blockHash) {
this.block = block;
if (this.block.id && this.block?.extras?.matchRate == null) {
this.fetchAuditScore$.next(this.block.id);
}
if (block?.extras?.reward != undefined) {
this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
}
}
});
if (this.indexingAvailable) {
this.fetchAuditScoreSubscription = this.fetchAuditScore$
.pipe(
switchMap((hash) => this.apiService.getBlockAuditScore$(hash)),
catchError(() => EMPTY),
)
.subscribe((score) => {
if (score && score.hash === this.block.id) {
this.block.extras.matchRate = score.matchRate || null;
} else {
this.block.extras.matchRate = null;
}
});
}
const block$ = this.route.paramMap.pipe(
switchMap((params: ParamMap) => {
const blockHash: string = params.get('id') || '';
@@ -209,6 +229,9 @@ export class BlockComponent implements OnInit, OnDestroy {
this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
}
this.stateService.markBlock$.next({ blockHeight: this.blockHeight });
if (this.block.id && this.block?.extras?.matchRate == null) {
this.fetchAuditScore$.next(this.block.id);
}
this.isLoadingTransactions = true;
this.transactions = null;
this.transactionsError = null;
@@ -311,6 +334,7 @@ export class BlockComponent implements OnInit, OnDestroy {
this.networkChangedSubscription.unsubscribe();
this.queryParamsSubscription.unsubscribe();
this.timeLtrSubscription.unsubscribe();
this.fetchAuditScoreSubscription?.unsubscribe();
this.unsubscribeNextBlockSubscriptions();
}

View File

@@ -46,22 +46,17 @@
<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since>
</td>
<td *ngIf="indexingAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
<a *ngIf="block.extras?.matchRate != null" class="clear-link" [routerLink]="['/block-audit/' | relativeUrl, block.id]">
<a class="clear-link" [routerLink]="auditScores[block.id] != null ? ['/block-audit/' | relativeUrl, block.id] : null">
<div class="progress progress-health">
<div class="progress-bar progress-bar-health" role="progressbar"
[ngStyle]="{'width': (100 - (block.extras?.matchRate || 0)) + '%' }"></div>
[ngStyle]="{'width': (100 - (auditScores[block.id] || 0)) + '%' }"></div>
<div class="progress-text">
<span>{{ block.extras.matchRate }}%</span>
<span *ngIf="auditScores[block.id] != null;">{{ auditScores[block.id] }}%</span>
<span *ngIf="auditScores[block.id] === undefined" class="skeleton-loader"></span>
<span *ngIf="auditScores[block.id] === null">~</span>
</div>
</div>
</a>
<div *ngIf="block.extras?.matchRate == null" class="progress progress-health">
<div class="progress-bar progress-bar-health" role="progressbar"
[ngStyle]="{'width': '100%' }"></div>
<div class="progress-text">
<span>~</span>
</div>
</div>
</td>
<td *ngIf="indexingAvailable" class="reward text-right" [ngClass]="{'widget': widget, 'legacy': !indexingAvailable}">
<app-amount [satoshis]="block.extras.reward" [noFiat]="true" digitsInfo="1.2-2"></app-amount>

View File

@@ -196,6 +196,10 @@ tr, td, th {
@media (max-width: 950px) {
display: none;
}
.progress-text .skeleton-loader {
top: -8.5px;
}
}
.health.widget {
width: 25%;

View File

@@ -1,6 +1,6 @@
import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core';
import { BehaviorSubject, combineLatest, concat, Observable, timer } from 'rxjs';
import { delayWhen, map, retryWhen, scan, skip, switchMap, tap } from 'rxjs/operators';
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input } from '@angular/core';
import { BehaviorSubject, combineLatest, concat, Observable, timer, EMPTY, Subscription, of } from 'rxjs';
import { catchError, delayWhen, map, retryWhen, scan, skip, switchMap, tap } from 'rxjs/operators';
import { BlockExtended } from '../../interfaces/node-api.interface';
import { ApiService } from '../../services/api.service';
import { StateService } from '../../services/state.service';
@@ -12,10 +12,14 @@ import { WebsocketService } from '../../services/websocket.service';
styleUrls: ['./blocks-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BlocksList implements OnInit {
export class BlocksList implements OnInit, OnDestroy {
@Input() widget: boolean = false;
blocks$: Observable<BlockExtended[]> = undefined;
auditScores: { [hash: string]: number | void } = {};
auditScoreSubscription: Subscription;
latestScoreSubscription: Subscription;
indexingAvailable = false;
isLoading = true;
@@ -105,6 +109,53 @@ export class BlocksList implements OnInit {
return acc;
}, [])
);
if (this.indexingAvailable) {
this.auditScoreSubscription = this.fromHeightSubject.pipe(
switchMap((fromBlockHeight) => {
return this.apiService.getBlockAuditScores$(this.page === 1 ? undefined : fromBlockHeight)
.pipe(
catchError(() => {
return EMPTY;
})
);
})
).subscribe((scores) => {
Object.values(scores).forEach(score => {
this.auditScores[score.hash] = score?.matchRate != null ? score.matchRate : null;
});
});
this.latestScoreSubscription = this.stateService.blocks$.pipe(
switchMap((block) => {
if (block[0]?.extras?.matchRate != null) {
return of({
hash: block[0].id,
matchRate: block[0]?.extras?.matchRate,
});
}
else if (block[0]?.id && this.auditScores[block[0].id] === undefined) {
return this.apiService.getBlockAuditScore$(block[0].id)
.pipe(
catchError(() => {
return EMPTY;
})
);
} else {
return EMPTY;
}
}),
).subscribe((score) => {
if (score && score.hash) {
this.auditScores[score.hash] = score?.matchRate != null ? score.matchRate : null;
}
});
}
}
ngOnDestroy(): void {
this.auditScoreSubscription?.unsubscribe();
this.latestScoreSubscription?.unsubscribe();
}
pageChange(page: number) {

View File

@@ -152,6 +152,11 @@ export interface RewardStats {
totalTx: number;
}
export interface AuditScore {
hash: string;
matchRate?: number;
}
export interface ITopNodesPerChannels {
publicKey: string,
alias: string,

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators,
PoolStat, BlockExtended, TransactionStripped, RewardStats } from '../interfaces/node-api.interface';
PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore } from '../interfaces/node-api.interface';
import { Observable } from 'rxjs';
import { StateService } from './state.service';
import { WebsocketResponse } from '../interfaces/websocket.interface';
@@ -234,6 +234,19 @@ export class ApiService {
);
}
getBlockAuditScores$(from: number): Observable<AuditScore[]> {
return this.httpClient.get<AuditScore[]>(
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/audit/scores` +
(from !== undefined ? `/${from}` : ``)
);
}
getBlockAuditScore$(hash: string) : Observable<any> {
return this.httpClient.get<any>(
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/audit/score/` + hash
);
}
getRewardStats$(blockCount: number = 144): Observable<RewardStats> {
return this.httpClient.get<RewardStats>(this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/reward-stats/${blockCount}`);
}