Merge pull request #4774 from mempool/mononaut/accelerator-audit-pools
Show accelerator audit totals on pools page
This commit is contained in:
commit
fd41dfc152
@ -39,6 +39,7 @@ class MiningRoutes {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/pool/:slug', this.$getAccelerationsByPool)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/pool/:slug', this.$getAccelerationsByPool)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/block/:height', this.$getAccelerationsByHeight)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/block/:height', this.$getAccelerationsByHeight)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/recent/:interval', this.$getRecentAccelerations)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/recent/:interval', this.$getRecentAccelerations)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/total', this.$getAccelerationTotals)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -410,6 +411,21 @@ class MiningRoutes {
|
|||||||
res.status(500).send(e instanceof Error ? e.message : e);
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async $getAccelerationTotals(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
res.header('Pragma', 'public');
|
||||||
|
res.header('Cache-control', 'public');
|
||||||
|
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||||
|
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid'].includes(config.MEMPOOL.NETWORK)) {
|
||||||
|
res.status(400).send('Acceleration data is not available.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.status(200).send(await AccelerationRepository.$getAccelerationTotals(<string>req.query.pool, <string>req.query.interval));
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new MiningRoutes();
|
export default new MiningRoutes();
|
||||||
|
@ -118,6 +118,45 @@ class AccelerationRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async $getAccelerationTotals(poolSlug: string | null = null, interval: string | null = null): Promise<{ cost: number, count: number }> {
|
||||||
|
interval = Common.getSqlInterval(interval);
|
||||||
|
|
||||||
|
if (!config.MEMPOOL_SERVICES.ACCELERATIONS) {
|
||||||
|
return { cost: 0, count: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
let query = `
|
||||||
|
SELECT SUM(boost_cost) as total_cost, COUNT(txid) as count FROM accelerations
|
||||||
|
JOIN pools on pools.unique_id = accelerations.pool
|
||||||
|
`;
|
||||||
|
let params: any[] = [];
|
||||||
|
let hasFilter = false;
|
||||||
|
|
||||||
|
if (interval) {
|
||||||
|
query += ` WHERE accelerations.added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() `;
|
||||||
|
hasFilter = true;
|
||||||
|
}
|
||||||
|
if (poolSlug != null) {
|
||||||
|
if (hasFilter) {
|
||||||
|
query += ` AND pools.slug = ? `;
|
||||||
|
} else {
|
||||||
|
query += ` WHERE pools.slug = ? `;
|
||||||
|
}
|
||||||
|
params.push(poolSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [rows] = await DB.query(query, params) as RowDataPacket[][];
|
||||||
|
return {
|
||||||
|
cost: rows[0]?.total_cost || 0,
|
||||||
|
count: rows[0]?.count || 0,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
logger.err(`Cannot query acceleration totals. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async $getLastSyncedHeight(): Promise<number> {
|
public async $getLastSyncedHeight(): Promise<number> {
|
||||||
try {
|
try {
|
||||||
const [rows] = await DB.query(`
|
const [rows] = await DB.query(`
|
||||||
|
@ -85,10 +85,9 @@
|
|||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
<table class="table table-borderless table-striped">
|
<table class="table table-borderless table-striped">
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<!-- Hashrate -->
|
||||||
<!-- Hashrate desktop -->
|
<tr [class.taller-row]="!isMobile()">
|
||||||
<tr *ngIf="!isMobile()" class="taller-row">
|
<td [class.data]="!isMobile()" [attr.colspan]="isMobile() ? 2 : 1">
|
||||||
<td class="data">
|
|
||||||
<table class="table table-xs table-data">
|
<table class="table table-xs table-data">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@ -111,57 +110,10 @@
|
|||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!-- Hashrate mobile -->
|
|
||||||
<tr *ngIf="isMobile()">
|
|
||||||
<td colspan="2">
|
|
||||||
<table class="table table-xs table-data">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col" class="data-title clip text-center" style="width: 33%" i18n="mining.reward">Reward</th>
|
|
||||||
<th scope="col" class="data-title clip text-center" style="width: 33%" i18n="mining.hashrate">Hashrate (24h)</th>
|
|
||||||
<th scope="col" class="data-title clip text-center" style="width: 33%" i18n="latest-blocks.avg_health" *ngIf="auditAvailable">Avg Health</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<td class="text-center"><app-amount [satoshis]="poolStats.totalReward" digitsInfo="1.0-0" [noFiat]="true"></app-amount></td>
|
|
||||||
<td class="text-center">{{ poolStats.estimatedHashrate | amountShortener : 1 : 'H/s' }}</td>
|
|
||||||
<td *ngIf="auditAvailable; else emptyTd" class="text-center"><span class="health-badge badge" [class.badge-success]="poolStats.avgBlockHealth >= 99"
|
|
||||||
[class.badge-warning]="poolStats.avgBlockHealth >= 75 && poolStats.avgBlockHealth < 99" [class.badge-danger]="poolStats.avgBlockHealth < 75"
|
|
||||||
*ngIf="poolStats.avgBlockHealth != null; else nullHealth">{{ poolStats.avgBlockHealth }}%</span>
|
|
||||||
<ng-template #nullHealth>
|
|
||||||
<span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
|
|
||||||
</ng-template>
|
|
||||||
</td>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- Mined blocks desktop -->
|
<!-- Mined blocks -->
|
||||||
<tr *ngIf="!isMobile()" class="taller-row">
|
<tr [class.taller-row]="!isMobile()">
|
||||||
<td class="data">
|
<td [class.data]="!isMobile()" [attr.colspan]="isMobile() ? 2 : 1">
|
||||||
<table class="table table-xs table-data">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col" class="data-title text-center" style="width: 33%" i18n="24h">Blocks (24h)</th>
|
|
||||||
<th scope="col" class="data-title text-center" style="width: 33%" i18n="1w">1w</th>
|
|
||||||
<th scope="col" class="data-title text-center" style="width: 33%" i18n="all">All</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<td class="text-center">{{ formatNumber(poolStats.blockCount['24h'], this.locale, '1.0-0') }} ({{ formatNumber(100 *
|
|
||||||
poolStats.blockShare['24h'], this.locale, '1.0-0') }}%)</td>
|
|
||||||
<td class="text-center">{{ formatNumber(poolStats.blockCount['1w'], this.locale, '1.0-0') }} ({{ formatNumber(100 *
|
|
||||||
poolStats.blockShare['1w'], this.locale, '1.0-0') }}%)</td>
|
|
||||||
<td class="text-center">{{ formatNumber(poolStats.blockCount['all'], this.locale, '1.0-0') }} ({{ formatNumber(100 *
|
|
||||||
poolStats.blockShare['all'], this.locale, '1.0-0') }}%)</td>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<!-- Mined blocks mobile -->
|
|
||||||
<tr *ngIf="isMobile()">
|
|
||||||
<td colspan=2>
|
|
||||||
<table class="table table-xs table-data">
|
<table class="table table-xs table-data">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { echarts, EChartsOption } from '../../graphs/echarts';
|
import { echarts, EChartsOption } from '../../graphs/echarts';
|
||||||
import { BehaviorSubject, Observable, of, timer } from 'rxjs';
|
import { BehaviorSubject, Observable, combineLatest, of, timer } from 'rxjs';
|
||||||
import { catchError, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators';
|
import { catchError, distinctUntilChanged, filter, map, share, switchMap, tap } from 'rxjs/operators';
|
||||||
import { BlockExtended, PoolStat } from '../../interfaces/node-api.interface';
|
import { BlockExtended, PoolStat } from '../../interfaces/node-api.interface';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
@ -10,6 +10,11 @@ import { selectPowerOfTen } from '../../bitcoin.utils';
|
|||||||
import { formatNumber } from '@angular/common';
|
import { formatNumber } from '@angular/common';
|
||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
|
|
||||||
|
interface AccelerationTotal {
|
||||||
|
cost: number,
|
||||||
|
count: number,
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-pool',
|
selector: 'app-pool',
|
||||||
templateUrl: './pool.component.html',
|
templateUrl: './pool.component.html',
|
||||||
@ -25,6 +30,7 @@ export class PoolComponent implements OnInit {
|
|||||||
formatNumber = formatNumber;
|
formatNumber = formatNumber;
|
||||||
poolStats$: Observable<PoolStat>;
|
poolStats$: Observable<PoolStat>;
|
||||||
blocks$: Observable<BlockExtended[]>;
|
blocks$: Observable<BlockExtended[]>;
|
||||||
|
oobFees$: Observable<AccelerationTotal[]>;
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
|
|
||||||
chartOptions: EChartsOption = {};
|
chartOptions: EChartsOption = {};
|
||||||
@ -111,6 +117,17 @@ export class PoolComponent implements OnInit {
|
|||||||
map(() => this.blocks),
|
map(() => this.blocks),
|
||||||
share(),
|
share(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.oobFees$ = this.route.params.pipe(map((params) => params.slug)).pipe(
|
||||||
|
switchMap(slug => {
|
||||||
|
return combineLatest([
|
||||||
|
this.apiService.getAccelerationTotals$(this.slug, '1w'),
|
||||||
|
this.apiService.getAccelerationTotals$(this.slug, '1m'),
|
||||||
|
this.apiService.getAccelerationTotals$(this.slug),
|
||||||
|
]);
|
||||||
|
}),
|
||||||
|
filter(oob => oob.length === 3 && oob[2].count > 0)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareChartOptions(data) {
|
prepareChartOptions(data) {
|
||||||
|
@ -477,4 +477,18 @@ export class ApiService {
|
|||||||
this.apiBaseUrl + this.apiBasePath + '/api/v1/accelerations/interval' + (interval !== undefined ? `/${interval}` : '')
|
this.apiBaseUrl + this.apiBasePath + '/api/v1/accelerations/interval' + (interval !== undefined ? `/${interval}` : '')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAccelerationTotals$(pool?: string, interval?: string): Observable<{ cost: number, count: number }> {
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
if (pool) {
|
||||||
|
queryParams.append('pool', pool);
|
||||||
|
}
|
||||||
|
if (interval) {
|
||||||
|
queryParams.append('interval', interval);
|
||||||
|
}
|
||||||
|
const queryString = queryParams.toString();
|
||||||
|
return this.httpClient.get<{ cost: number, count: number }>(
|
||||||
|
this.apiBaseUrl + this.apiBasePath + '/api/v1/accelerations/total' + (queryString?.length ? '?' + queryString : '')
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user