mirror of
https://github.com/mempool/mempool.git
synced 2025-02-23 22:46:54 +01:00
Merge pull request #5509 from mempool/mononaut/pool-next-block
mining pool next block info
This commit is contained in:
commit
703241acf0
4 changed files with 292 additions and 95 deletions
|
@ -10,7 +10,7 @@
|
||||||
<h1 class="m-0 pt-1 pt-md-0">{{ poolStats.pool.name }}</h1>
|
<h1 class="m-0 pt-1 pt-md-0">{{ poolStats.pool.name }}</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box pool-details">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="col-lg-6">
|
<div class="col-lg-6">
|
||||||
|
@ -173,7 +173,119 @@
|
||||||
<div class="spinner-border text-light"></div>
|
<div class="spinner-border text-light"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Stratum Job -->
|
||||||
|
<ng-container *ngIf="(job$ | async) as job;">
|
||||||
|
<h2 i18n="pool.next_block">Next block</h2>
|
||||||
|
<div class="box mb-3">
|
||||||
|
<div class="row" >
|
||||||
|
<div class="col">
|
||||||
|
<table class="table table-borderless table-striped">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table class="job-table table table-xs table-borderless table-fixed table-data">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="data-title clip text-center height" i18n="latest-blocks.height">Height</th>
|
||||||
|
<th class="data-title clip text-center expected" i18n="next-block.expected-time">Expected</th>
|
||||||
|
<th class="data-title clip text-center reward" i18n="latest-blocks.reward">Reward</th>
|
||||||
|
<th class="data-title clip text-center timestamp" i18n="next-block.timestamp">Timestamp</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center height">
|
||||||
|
{{ job.height }}
|
||||||
|
</td>
|
||||||
|
<td class="text-center expected">
|
||||||
|
<ng-container *ngIf="(expectedBlockTime$ | async) as expectedBlockTime; else expectedPlaceholder">
|
||||||
|
<app-time kind="until" [time]="expectedBlockTime" [fastRender]="false" [fixedRender]="true" [precision]="1" minUnit="minute"></app-time>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #expectedPlaceholder>~</ng-template>
|
||||||
|
</td>
|
||||||
|
<td class="text-center reward">
|
||||||
|
<app-amount [satoshis]="job.reward"></app-amount>
|
||||||
|
</td>
|
||||||
|
<td class="text-center timestamp">
|
||||||
|
<app-timestamp [customFormat]="'yyyy-MM-dd HH:mm:ss'" [unixTime]="job.timestamp" [precision]="1" minUnit="minute" [hideTimeSince]="true"></app-timestamp>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table class="job-table table table-xs table-borderless table-fixed table-data">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="data-title clip text-center coinbase" i18n="latest-blocks.coinbasetag">Coinbase tag</th>
|
||||||
|
<th class="data-title clip text-center clean" i18n="next-block.clean">Clean</th>
|
||||||
|
<th class="data-title clip text-center prevhash" i18n="next-block.prevhash">Prevhash</th>
|
||||||
|
<th class="data-title clip text-center job-received" i18n="next-block.job-received">Job Received</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center coinbase">
|
||||||
|
{{ job.scriptsig | hex2ascii }}
|
||||||
|
</td>
|
||||||
|
<td class="text-center clean">
|
||||||
|
@if (job.cleanJobs) {
|
||||||
|
<fa-icon [icon]="['fas', 'check-circle']" [fixedWidth]="true"></fa-icon>
|
||||||
|
} @else {
|
||||||
|
<fa-icon [icon]="['fas', 'times-circle']" [fixedWidth]="true"></fa-icon>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td class="text-center prevhash">
|
||||||
|
<a [routerLink]="['/block' | relativeUrl, job.prevHash]">
|
||||||
|
<app-truncate [text]="job.prevHash" [lastChars]="8"></app-truncate>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td class="text-center job-received">
|
||||||
|
<app-timestamp [customFormat]="'yyyy-MM-dd HH:mm:ss'" [unixTime]="job.received / 1000" [precision]="1" minUnit="minute" [hideTimeSince]="true"></app-timestamp>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<table class="stratum-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="data-title clip text-center" [attr.colspan]="Math.max(job.merkleBranches.length, 12)">
|
||||||
|
<a class="title-link" href="" [routerLink]="['/stratum' | relativeUrl]">
|
||||||
|
Merkle Branches
|
||||||
|
<span> </span>
|
||||||
|
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
@for (branch of job.merkleBranches; track $index) {
|
||||||
|
<td class="merkle" [style.background-color]="branch ? '#' + branch.slice(0, 6) : ''"></td>
|
||||||
|
}
|
||||||
|
@for (_ of [].constructor(Math.max(0, 12 - job.merkleBranches.length)); track $index) {
|
||||||
|
<td class="merkle empty-branch"></td>
|
||||||
|
}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<!-- Blocks list -->
|
<!-- Blocks list -->
|
||||||
|
<h2 i18n="master-page.blocks">Blocks</h2>
|
||||||
<table class="table table-borderless" [alwaysCallback]="true" infiniteScroll [infiniteScrollDistance]="1.5"
|
<table class="table table-borderless" [alwaysCallback]="true" infiniteScroll [infiniteScrollDistance]="1.5"
|
||||||
[infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="loadMore()">
|
[infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="loadMore()">
|
||||||
<ng-container *ngIf="blocks$ | async as blocks; else skeleton">
|
<ng-container *ngIf="blocks$ | async as blocks; else skeleton">
|
||||||
|
|
|
@ -49,111 +49,110 @@ div.scrollable {
|
||||||
max-height: 75px;
|
max-height: 75px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.box {
|
.pool-details {
|
||||||
padding-bottom: 5px;
|
|
||||||
@media (min-width: 767.98px) {
|
@media (min-width: 767.98px) {
|
||||||
min-height: 187px;
|
min-height: 187px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
@media (min-width: 767.98px) {
|
@media (min-width: 767.98px) {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 767.98px) {
|
.label.addresses {
|
||||||
font-weight: bold;
|
vertical-align: top;
|
||||||
|
padding-top: 25px;
|
||||||
|
}
|
||||||
|
.addresses-data {
|
||||||
|
vertical-align: top;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.label.addresses {
|
|
||||||
vertical-align: top;
|
|
||||||
padding-top: 25px;
|
|
||||||
}
|
|
||||||
.addresses-data {
|
|
||||||
vertical-align: top;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data {
|
.data {
|
||||||
text-align: right;
|
|
||||||
padding-left: 5%;
|
|
||||||
@media (max-width: 992px) {
|
|
||||||
text-align: left;
|
|
||||||
padding-left: 12px;
|
|
||||||
}
|
|
||||||
@media (max-width: 450px) {
|
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
padding-left: 5%;
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
@media (max-width: 450px) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.progress {
|
.progress {
|
||||||
background-color: var(--secondary);
|
background-color: var(--secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.coinbase {
|
.coinbase {
|
||||||
width: 20%;
|
|
||||||
@media (max-width: 875px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.height {
|
|
||||||
width: 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timestamp {
|
|
||||||
@media (max-width: 875px) {
|
|
||||||
padding-left: 50px;
|
|
||||||
}
|
|
||||||
@media (max-width: 685px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mined {
|
|
||||||
width: 13%;
|
|
||||||
@media (max-width: 1100px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.txs {
|
|
||||||
padding-right: 40px;
|
|
||||||
@media (max-width: 1100px) {
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
@media (max-width: 875px) {
|
|
||||||
padding-right: 20px;
|
|
||||||
}
|
|
||||||
@media (max-width: 567px) {
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.size {
|
|
||||||
width: 12%;
|
|
||||||
@media (max-width: 1000px) {
|
|
||||||
width: 15%;
|
|
||||||
}
|
|
||||||
@media (max-width: 875px) {
|
|
||||||
width: 20%;
|
width: 20%;
|
||||||
|
@media (max-width: 875px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 650px) {
|
|
||||||
width: 20%;
|
|
||||||
}
|
|
||||||
@media (max-width: 450px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scriptmessage {
|
.height {
|
||||||
overflow: hidden;
|
width: 10%;
|
||||||
display: inline-block;
|
}
|
||||||
text-overflow: ellipsis;
|
|
||||||
vertical-align: middle;
|
.timestamp {
|
||||||
width: auto;
|
@media (max-width: 875px) {
|
||||||
text-align: left;
|
padding-left: 50px;
|
||||||
|
}
|
||||||
|
@media (max-width: 685px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mined {
|
||||||
|
width: 13%;
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.txs {
|
||||||
|
padding-right: 40px;
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
@media (max-width: 875px) {
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
@media (max-width: 567px) {
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.size {
|
||||||
|
width: 12%;
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
@media (max-width: 875px) {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
@media (max-width: 650px) {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
@media (max-width: 450px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scriptmessage {
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: auto;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.skeleton-loader {
|
.skeleton-loader {
|
||||||
|
@ -214,4 +213,55 @@ div.scrollable {
|
||||||
|
|
||||||
.taller-row {
|
.taller-row {
|
||||||
height: 75px;
|
height: 75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stratum-table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.merkle {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-branch {
|
||||||
|
outline: solid 1px white;
|
||||||
|
outline-offset: -1px;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(to top left, transparent, transparent 48%, white 49%, white 51%, transparent 52%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
position: relative;
|
||||||
|
height: 2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-table {
|
||||||
|
td, th {
|
||||||
|
width: 25%;
|
||||||
|
max-width: 25%;
|
||||||
|
min-width: 25%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
padding: 0.1rem 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
.expected, .timestamp, .clean, .job-received {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-link, .title-link:hover, .title-link:focus, .title-link:active {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
|
@ -10,6 +10,9 @@ import { selectPowerOfTen } from '@app/bitcoin.utils';
|
||||||
import { formatNumber } from '@angular/common';
|
import { formatNumber } from '@angular/common';
|
||||||
import { SeoService } from '@app/services/seo.service';
|
import { SeoService } from '@app/services/seo.service';
|
||||||
import { HttpErrorResponse } from '@angular/common/http';
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { StratumJob } from '../../interfaces/websocket.interface';
|
||||||
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
|
import { MiningService } from '../../services/mining.service';
|
||||||
|
|
||||||
interface AccelerationTotal {
|
interface AccelerationTotal {
|
||||||
cost: number,
|
cost: number,
|
||||||
|
@ -27,12 +30,16 @@ export class PoolComponent implements OnInit {
|
||||||
@Input() left: number | string = 75;
|
@Input() left: number | string = 75;
|
||||||
|
|
||||||
gfg = true;
|
gfg = true;
|
||||||
|
stratumEnabled = this.stateService.env.STRATUM_ENABLED;
|
||||||
|
|
||||||
formatNumber = formatNumber;
|
formatNumber = formatNumber;
|
||||||
|
Math = Math;
|
||||||
slugSubscription: Subscription;
|
slugSubscription: Subscription;
|
||||||
poolStats$: Observable<PoolStat>;
|
poolStats$: Observable<PoolStat>;
|
||||||
blocks$: Observable<BlockExtended[]>;
|
blocks$: Observable<BlockExtended[]>;
|
||||||
oobFees$: Observable<AccelerationTotal[]>;
|
oobFees$: Observable<AccelerationTotal[]>;
|
||||||
|
job$: Observable<StratumJob | null>;
|
||||||
|
expectedBlockTime$: Observable<number>;
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
error: HttpErrorResponse | null = null;
|
error: HttpErrorResponse | null = null;
|
||||||
|
|
||||||
|
@ -53,6 +60,8 @@ export class PoolComponent implements OnInit {
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
|
private websocketService: WebsocketService,
|
||||||
|
private miningService: MiningService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
) {
|
) {
|
||||||
this.auditAvailable = this.stateService.env.AUDIT;
|
this.auditAvailable = this.stateService.env.AUDIT;
|
||||||
|
@ -62,7 +71,7 @@ export class PoolComponent implements OnInit {
|
||||||
this.slugSubscription = this.route.params.pipe(map((params) => params.slug)).subscribe((slug) => {
|
this.slugSubscription = this.route.params.pipe(map((params) => params.slug)).subscribe((slug) => {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.blocks = [];
|
this.blocks = [];
|
||||||
this.chartOptions = {};
|
this.chartOptions = {};
|
||||||
this.slug = slug;
|
this.slug = slug;
|
||||||
this.initializeObservables();
|
this.initializeObservables();
|
||||||
});
|
});
|
||||||
|
@ -129,6 +138,31 @@ export class PoolComponent implements OnInit {
|
||||||
}),
|
}),
|
||||||
filter(oob => oob.length === 3 && oob[2].count > 0)
|
filter(oob => oob.length === 3 && oob[2].count > 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (this.stratumEnabled) {
|
||||||
|
this.job$ = combineLatest([
|
||||||
|
this.poolStats$.pipe(
|
||||||
|
tap((poolStats) => {
|
||||||
|
this.websocketService.startTrackStratum(poolStats.pool.unique_id);
|
||||||
|
})
|
||||||
|
),
|
||||||
|
this.stateService.stratumJobs$
|
||||||
|
]).pipe(
|
||||||
|
map(([poolStats, jobs]) => {
|
||||||
|
return jobs[poolStats.pool.unique_id];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.expectedBlockTime$ = combineLatest([
|
||||||
|
this.miningService.getMiningStats('1w'),
|
||||||
|
this.poolStats$,
|
||||||
|
this.stateService.difficultyAdjustment$
|
||||||
|
]).pipe(
|
||||||
|
map(([miningStats, poolStat, da]) => {
|
||||||
|
return (da.timeAvg / ((poolStat.estimatedHashrate || 0) / (miningStats.lastEstimatedHashrate * 1_000_000_000_000_000_000))) + Date.now() + da.timeOffset;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareChartOptions(hashrate, share) {
|
prepareChartOptions(hashrate, share) {
|
||||||
|
|
|
@ -36,13 +36,14 @@ function parseTag(scriptSig: string): string {
|
||||||
for (let i = 0; i < hex.length; i += 2) {
|
for (let i = 0; i < hex.length; i += 2) {
|
||||||
bytes.push(parseInt(hex.substr(i, 2), 16));
|
bytes.push(parseInt(hex.substr(i, 2), 16));
|
||||||
}
|
}
|
||||||
const ascii = new TextDecoder('utf8').decode(Uint8Array.from(bytes)).replace(/\uFFFD/g, '').replace(/\\0/g, '');
|
// eslint-disable-next-line no-control-regex
|
||||||
|
const ascii = new TextDecoder('utf8').decode(Uint8Array.from(bytes)).replace(/\uFFFD/g, '').replace(/\\0/g, '').replace(/[\x00-\x1F\x7F-\x9F]/g, '');
|
||||||
if (ascii.includes('/ViaBTC/')) {
|
if (ascii.includes('/ViaBTC/')) {
|
||||||
return '/ViaBTC/';
|
return '/ViaBTC/';
|
||||||
} else if (ascii.includes('SpiderPool/')) {
|
} else if (ascii.includes('SpiderPool/')) {
|
||||||
return 'SpiderPool/';
|
return 'SpiderPool/';
|
||||||
}
|
}
|
||||||
return ascii.match(/\/.*\//)?.[0] || ascii;
|
return (ascii.match(/\/.*\//)?.[0] || ascii).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
Loading…
Add table
Reference in a new issue