Compare commits
2 Commits
master
...
natsoni/bl
Author | SHA1 | Date | |
---|---|---|---|
|
b6b3e52436 | ||
|
d04e5128ba |
@ -30,6 +30,7 @@ class MiningRoutes {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fee-rates/:interval', this.$getHistoricalBlockFeeRates)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fee-rates/:interval', this.$getHistoricalBlockFeeRates)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/sizes-weights/:interval', this.$getHistoricalBlockSizeAndWeight)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/sizes-weights/:interval', this.$getHistoricalBlockSizeAndWeight)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments/:interval', this.$getDifficultyAdjustments)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments/:interval', this.$getDifficultyAdjustments)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustment/:height', this.$getDifficultyAdjustment)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/predictions/:interval', this.$getHistoricalBlocksHealth)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/predictions/:interval', this.$getHistoricalBlocksHealth)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/scores', this.$getBlockAuditScores)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/scores', this.$getBlockAuditScores)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/scores/:height', this.$getBlockAuditScores)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/scores/:height', this.$getBlockAuditScores)
|
||||||
@ -297,6 +298,18 @@ class MiningRoutes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async $getDifficultyAdjustment(req: Request, res: Response) {
|
||||||
|
try {
|
||||||
|
const adjustment = await DifficultyAdjustmentsRepository.$getAdjustmentAtHeight(parseInt(req.params.height, 10));
|
||||||
|
res.header('Pragma', 'public');
|
||||||
|
res.header('Cache-control', 'public');
|
||||||
|
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
|
||||||
|
res.json(adjustment);
|
||||||
|
} catch (e) {
|
||||||
|
res.status(e instanceof Error && e.message === 'not found' ? 204 : 500).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async $getRewardStats(req: Request, res: Response) {
|
private async $getRewardStats(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const response = await mining.$getRewardStats(parseInt(req.params.blockCount, 10));
|
const response = await mining.$getRewardStats(parseInt(req.params.blockCount, 10));
|
||||||
|
@ -88,6 +88,22 @@ class DifficultyAdjustmentsRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async $getAdjustmentAtHeight(height: number): Promise<IndexedDifficultyAdjustment> {
|
||||||
|
try {
|
||||||
|
if (isNaN(height)) {
|
||||||
|
throw new Error(`argument must be a number`);
|
||||||
|
}
|
||||||
|
const [rows] = await DB.query(`SELECT * FROM difficulty_adjustments WHERE height = ?`, [height]);
|
||||||
|
if (!rows[0]) {
|
||||||
|
throw new Error(`not found`);
|
||||||
|
}
|
||||||
|
return rows[0] as IndexedDifficultyAdjustment;
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.err(`Cannot get difficulty adjustment from the database. Reason: ${e instanceof Error ? e.message : e}`, logger.tags.mining);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async $getAdjustmentsHeights(): Promise<number[]> {
|
public async $getAdjustmentsHeights(): Promise<number[]> {
|
||||||
try {
|
try {
|
||||||
const [rows]: any[] = await DB.query(`SELECT height FROM difficulty_adjustments`);
|
const [rows]: any[] = await DB.query(`SELECT height FROM difficulty_adjustments`);
|
||||||
|
@ -78,6 +78,25 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr *ngIf="this.stateService.network === '' && block.height % 2016 === 0">
|
||||||
|
<td i18n="mining.difficulty-adjustment">Adjustment</td>
|
||||||
|
<td>
|
||||||
|
<ng-container *ngIf="cacheService.daCache[block.height]?.adjustment > 0; else loadingAdjustment">
|
||||||
|
<div [style.color]="cacheService.daCache[block.height].adjustment > 1 ? 'var(--green)' : (cacheService.daCache[block.height].adjustment < 1 ? 'var(--red)' : '')">
|
||||||
|
@if (cacheService.daCache[block.height].adjustment > 1) {
|
||||||
|
<fa-icon class="retarget-sign up" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
|
||||||
|
} @else if (cacheService.daCache[block.height].adjustment < 1) {
|
||||||
|
<fa-icon class="retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon>
|
||||||
|
}
|
||||||
|
{{ (cacheService.daCache[block.height].adjustment - 1) * 100 | absolute | number: '1.2-2' }}
|
||||||
|
<span class="symbol">%</span>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #loadingAdjustment>
|
||||||
|
<span class="skeleton-loader" style="max-width: 60px"></span>
|
||||||
|
</ng-template>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #skeletonRows>
|
<ng-template #skeletonRows>
|
||||||
<tr>
|
<tr>
|
||||||
@ -193,6 +212,10 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr *ngIf="this.stateService.network === '' && block.height % 2016 === 0">
|
||||||
|
<td i18n="block.difficulty">Difficulty</td>
|
||||||
|
<td>{{ block.difficulty | amountShortener: 2 }}</td>
|
||||||
|
</tr>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-template #loadingRest>
|
<ng-template #loadingRest>
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -280,3 +280,11 @@ h1 {
|
|||||||
top: -1px;
|
top: -1px;
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.retarget-sign {
|
||||||
|
margin-right: -3px;
|
||||||
|
&.up {
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
}
|
@ -99,7 +99,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
private relativeUrlPipe: RelativeUrlPipe,
|
private relativeUrlPipe: RelativeUrlPipe,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private priceService: PriceService,
|
private priceService: PriceService,
|
||||||
private cacheService: CacheService,
|
public cacheService: CacheService,
|
||||||
private servicesApiService: ServicesApiServices,
|
private servicesApiService: ServicesApiServices,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
private preloadService: PreloadService,
|
private preloadService: PreloadService,
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, Input, OnChanges, SimpleChanges } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
import { Observable, Subscription, delay, filter, tap } from 'rxjs';
|
import { Observable, Subscription, delay, filter, of, retryWhen, switchMap, take, tap, throwError } from 'rxjs';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { specialBlocks } from '../../app.constants';
|
import { specialBlocks } from '../../app.constants';
|
||||||
import { BlockExtended } from '../../interfaces/node-api.interface';
|
import { BlockExtended } from '../../interfaces/node-api.interface';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { CacheService } from '../../services/cache.service';
|
import { CacheService } from '../../services/cache.service';
|
||||||
|
import { ApiService } from '../../services/api.service';
|
||||||
|
import { colorFromRetarget } from '../../shared/common.utils';
|
||||||
|
|
||||||
interface BlockchainBlock extends BlockExtended {
|
interface BlockchainBlock extends BlockExtended {
|
||||||
placeholder?: boolean;
|
placeholder?: boolean;
|
||||||
@ -77,6 +79,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
constructor(
|
constructor(
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
public cacheService: CacheService,
|
public cacheService: CacheService,
|
||||||
|
public apiService: ApiService,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
private location: Location,
|
private location: Location,
|
||||||
) {
|
) {
|
||||||
@ -334,6 +337,31 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
return this.specialBlocks[height]?.networks.includes(this.stateService.network || 'mainnet') ? true : false;
|
return this.specialBlocks[height]?.networks.includes(this.stateService.network || 'mainnet') ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isDA(height: number): boolean {
|
||||||
|
const isDA = height % 2016 === 0 && this.stateService.network === '';
|
||||||
|
if (isDA && !this.cacheService.daCache[height]?.exact) {
|
||||||
|
const estimatedAdjustment = this.cacheService.daCache[height]?.adjustment || 0;
|
||||||
|
this.cacheService.daCache[height] = { adjustment: estimatedAdjustment, exact: true };
|
||||||
|
this.apiService.getDifficultyAdjustmentByHeight$(height).pipe(
|
||||||
|
switchMap(da => {
|
||||||
|
const blocksAvailable = (this.height || this.chainTip) && this.blockStyles[(this.height || this.chainTip) - height];
|
||||||
|
return blocksAvailable ? of(da) : throwError(() => new Error());
|
||||||
|
}),
|
||||||
|
retryWhen(errors =>
|
||||||
|
errors.pipe(
|
||||||
|
delay(1000),
|
||||||
|
take(3)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
tap((da) => {
|
||||||
|
this.cacheService.daCache[height] = { adjustment: da?.adjustment || 1, exact: true };
|
||||||
|
this.blockStyles[(this.height || this.chainTip) - height].background = colorFromRetarget(da?.adjustment);
|
||||||
|
})
|
||||||
|
).subscribe();
|
||||||
|
}
|
||||||
|
return isDA;
|
||||||
|
}
|
||||||
|
|
||||||
getStyleForBlock(block: BlockchainBlock, index: number, animateEnterFrom: number = 0) {
|
getStyleForBlock(block: BlockchainBlock, index: number, animateEnterFrom: number = 0) {
|
||||||
if (!block || block.placeholder) {
|
if (!block || block.placeholder) {
|
||||||
return this.getStyleForPlaceholderBlock(index, animateEnterFrom);
|
return this.getStyleForPlaceholderBlock(index, animateEnterFrom);
|
||||||
@ -349,7 +377,8 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
left: addLeft + this.blockOffset * index + 'px',
|
left: addLeft + this.blockOffset * index + 'px',
|
||||||
background: `repeating-linear-gradient(
|
background: this.isDA(block.height) ? colorFromRetarget(this.cacheService.daCache[block.height]?.adjustment || 1) :
|
||||||
|
`repeating-linear-gradient(
|
||||||
var(--secondary),
|
var(--secondary),
|
||||||
var(--secondary) ${greenBackgroundHeight}%,
|
var(--secondary) ${greenBackgroundHeight}%,
|
||||||
${this.gradientColors[this.network][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
${this.gradientColors[this.network][0]} ${Math.max(greenBackgroundHeight, 0)}%,
|
||||||
|
@ -5,6 +5,7 @@ import { ApiService } from '../../services/api.service';
|
|||||||
import { formatNumber } from '@angular/common';
|
import { formatNumber } from '@angular/common';
|
||||||
import { selectPowerOfTen } from '../../bitcoin.utils';
|
import { selectPowerOfTen } from '../../bitcoin.utils';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { CacheService } from '../../services/cache.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-difficulty-adjustments-table',
|
selector: 'app-difficulty-adjustments-table',
|
||||||
@ -27,7 +28,8 @@ export class DifficultyAdjustmentsTable implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(LOCALE_ID) public locale: string,
|
@Inject(LOCALE_ID) public locale: string,
|
||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
public stateService: StateService
|
public stateService: StateService,
|
||||||
|
private cacheService: CacheService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,6 +54,7 @@ export class DifficultyAdjustmentsTable implements OnInit {
|
|||||||
adjustment[2] / selectedPowerOfTen.divider,
|
adjustment[2] / selectedPowerOfTen.divider,
|
||||||
this.locale, `1.${decimals}-${decimals}`) + selectedPowerOfTen.unit
|
this.locale, `1.${decimals}-${decimals}`) + selectedPowerOfTen.unit
|
||||||
});
|
});
|
||||||
|
this.cacheService.daCache[adjustment[1]] = { adjustment: adjustment[3], exact: true };
|
||||||
}
|
}
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
return tableData.slice(0, 6);
|
return tableData.slice(0, 6);
|
||||||
|
@ -41,6 +41,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr *ngIf="this.stateService.network === '' && mempoolBlock.height % 2016 === 0">
|
||||||
|
<td i18n="mining.difficulty-adjustment">Adjustment</td>
|
||||||
|
<td>
|
||||||
|
<ng-container *ngIf="cacheService.daCache[mempoolBlock.height]?.adjustment > 0; else loadingAdjustment">
|
||||||
|
<div [style.color]="cacheService.daCache[mempoolBlock.height].adjustment > 1 ? 'var(--green)' : (cacheService.daCache[mempoolBlock.height].adjustment < 1 ? 'var(--red)' : '')">
|
||||||
|
@if (cacheService.daCache[mempoolBlock.height].adjustment > 1) {
|
||||||
|
<fa-icon class="retarget-sign up" [icon]="['fas', 'caret-up']" [fixedWidth]="true"></fa-icon>
|
||||||
|
} @else if (cacheService.daCache[mempoolBlock.height].adjustment < 1) {
|
||||||
|
<fa-icon class="retarget-sign" [icon]="['fas', 'caret-down']" [fixedWidth]="true"></fa-icon>
|
||||||
|
}
|
||||||
|
{{ (cacheService.daCache[mempoolBlock.height].adjustment - 1) * 100 | absolute | number: '1.2-2' }}
|
||||||
|
<span class="symbol">%</span>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #loadingAdjustment>
|
||||||
|
<span class="skeleton-loader" style="max-width: 60px"></span>
|
||||||
|
</ng-template>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<app-fee-distribution-graph *ngIf="webGlEnabled" [transactions]="mempoolBlockTransactions$ | async" [feeRange]="mempoolBlock.isStack ? mempoolBlock.feeRange : []" [vsize]="mempoolBlock.blockVSize" ></app-fee-distribution-graph>
|
<app-fee-distribution-graph *ngIf="webGlEnabled" [transactions]="mempoolBlockTransactions$ | async" [feeRange]="mempoolBlock.isStack ? mempoolBlock.feeRange : []" [vsize]="mempoolBlock.blockVSize" ></app-fee-distribution-graph>
|
||||||
|
@ -36,3 +36,11 @@ h1 {
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.retarget-sign {
|
||||||
|
margin-right: -3px;
|
||||||
|
&.up {
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,6 +9,7 @@ import { Observable, BehaviorSubject } from 'rxjs';
|
|||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
import { seoDescriptionNetwork } from '../../shared/common.utils';
|
||||||
import { WebsocketService } from '../../services/websocket.service';
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
|
import { CacheService } from '../../services/cache.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-mempool-block',
|
selector: 'app-mempool-block',
|
||||||
@ -30,6 +31,7 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
|
|||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private websocketService: WebsocketService,
|
private websocketService: WebsocketService,
|
||||||
|
public cacheService: CacheService,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
@Inject(PLATFORM_ID) private platformId: Object,
|
@Inject(PLATFORM_ID) private platformId: Object,
|
||||||
) {
|
) {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, Input, OnChanges, SimpleChanges, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, HostListener, Input, OnChanges, SimpleChanges, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
|
||||||
import { Subscription, Observable, of, combineLatest } from 'rxjs';
|
import { Subscription, Observable, of, combineLatest, throwError } from 'rxjs';
|
||||||
import { MempoolBlock } from '../../interfaces/websocket.interface';
|
import { MempoolBlock } from '../../interfaces/websocket.interface';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { EtaService } from '../../services/eta.service';
|
import { EtaService } from '../../services/eta.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { delay, filter, map, switchMap, tap } from 'rxjs/operators';
|
import { delay, filter, map, retryWhen, switchMap, take, tap } from 'rxjs/operators';
|
||||||
import { feeLevels } from '../../app.constants';
|
import { feeLevels } from '../../app.constants';
|
||||||
import { specialBlocks } from '../../app.constants';
|
import { specialBlocks } from '../../app.constants';
|
||||||
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
|
||||||
@ -12,6 +12,8 @@ import { Location } from '@angular/common';
|
|||||||
import { DifficultyAdjustment, MempoolPosition } from '../../interfaces/node-api.interface';
|
import { DifficultyAdjustment, MempoolPosition } from '../../interfaces/node-api.interface';
|
||||||
import { animate, style, transition, trigger } from '@angular/animations';
|
import { animate, style, transition, trigger } from '@angular/animations';
|
||||||
import { ThemeService } from '../../services/theme.service';
|
import { ThemeService } from '../../services/theme.service';
|
||||||
|
import { CacheService } from '../../services/cache.service';
|
||||||
|
import { colorFromRetarget } from '../../shared/common.utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-mempool-blocks',
|
selector: 'app-mempool-blocks',
|
||||||
@ -93,6 +95,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
public stateService: StateService,
|
public stateService: StateService,
|
||||||
|
public cacheService: CacheService,
|
||||||
private etaService: EtaService,
|
private etaService: EtaService,
|
||||||
private themeService: ThemeService,
|
private themeService: ThemeService,
|
||||||
private cd: ChangeDetectorRef,
|
private cd: ChangeDetectorRef,
|
||||||
@ -387,6 +390,37 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.mempoolBlocksFull.forEach((block, i) => this.mempoolBlockStyles.push(this.getStyleForMempoolBlock(block, i)));
|
this.mempoolBlocksFull.forEach((block, i) => this.mempoolBlockStyles.push(this.getStyleForMempoolBlock(block, i)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isDA(height: number): boolean {
|
||||||
|
if (this.chainTip === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const isDA = height % 2016 === 0 && this.stateService.network === '';
|
||||||
|
if (isDA && !this.cacheService.daCache[height]) {
|
||||||
|
this.cacheService.daCache[height] = { adjustment: 0 };
|
||||||
|
this.difficultyAdjustments$.pipe(
|
||||||
|
filter(da => !!da),
|
||||||
|
switchMap(da => {
|
||||||
|
const mempoolBlocksAvailable = this.chainTip && this.mempoolBlockStyles[height - this.chainTip - 1];
|
||||||
|
return mempoolBlocksAvailable ? of(da) : throwError(() => new Error());
|
||||||
|
}),
|
||||||
|
retryWhen(errors =>
|
||||||
|
errors.pipe(
|
||||||
|
delay(100),
|
||||||
|
take(3)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
tap(da => {
|
||||||
|
const adjustment = parseFloat((1 + da.difficultyChange / 100).toFixed(4));
|
||||||
|
if (adjustment !== this.cacheService.daCache[height].adjustment) {
|
||||||
|
this.cacheService.daCache[height].adjustment = adjustment;
|
||||||
|
this.mempoolBlockStyles[height - this.chainTip - 1].background = colorFromRetarget(adjustment);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
).subscribe();
|
||||||
|
}
|
||||||
|
return isDA;
|
||||||
|
}
|
||||||
|
|
||||||
getStyleForMempoolBlock(mempoolBlock: MempoolBlock, index: number) {
|
getStyleForMempoolBlock(mempoolBlock: MempoolBlock, index: number) {
|
||||||
const emptyBackgroundSpacePercentage = Math.max(100 - mempoolBlock.blockVSize / this.stateService.blockVSize * 100, 0);
|
const emptyBackgroundSpacePercentage = Math.max(100 - mempoolBlock.blockVSize / this.stateService.blockVSize * 100, 0);
|
||||||
const usedBlockSpace = 100 - emptyBackgroundSpacePercentage;
|
const usedBlockSpace = 100 - emptyBackgroundSpacePercentage;
|
||||||
@ -410,7 +444,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'right': this.containerOffset + index * this.blockOffset + 'px',
|
'right': this.containerOffset + index * this.blockOffset + 'px',
|
||||||
'background': backgroundGradients.join(',') + ')'
|
'background': this.isDA(mempoolBlock.height) ? colorFromRetarget(this.cacheService.daCache[mempoolBlock.height]?.adjustment || 1) : backgroundGradients.join(',') + ')'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3670,6 +3670,39 @@ export const restApiDocsData = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "endpoint",
|
||||||
|
category: "mining",
|
||||||
|
httpRequestMethod: "GET",
|
||||||
|
fragment: "get-difficulty-adjustment-by-height",
|
||||||
|
title: "GET Difficulty Adjustment",
|
||||||
|
description: {
|
||||||
|
default: "<p>Returns difficulty adjustment for the block at the specified <code>:blockHeight</code>. If no adjustment happened at that height, an empty response is returned.</p>"
|
||||||
|
},
|
||||||
|
urlString: "/v1/mining/difficulty-adjustment/:blockHeight",
|
||||||
|
showConditions: [""],
|
||||||
|
showJsExamples: showJsExamplesDefaultFalse,
|
||||||
|
codeExample: {
|
||||||
|
default: {
|
||||||
|
codeTemplate: {
|
||||||
|
curl: `/api/v1/mining/difficulty-adjustment/%{1}`,
|
||||||
|
commonJS: ``,
|
||||||
|
esModule: ``
|
||||||
|
},
|
||||||
|
codeSampleMainnet: {
|
||||||
|
esModule: [],
|
||||||
|
commonJS: [],
|
||||||
|
curl: [`756000`],
|
||||||
|
response: `{
|
||||||
|
"time": "2022-09-28T02:56:34.000Z",
|
||||||
|
"height": 756000,
|
||||||
|
"difficulty": 31360548173144.9,
|
||||||
|
"adjustment": 0.97863
|
||||||
|
}`
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: "endpoint",
|
type: "endpoint",
|
||||||
category: "mining",
|
category: "mining",
|
||||||
|
@ -315,6 +315,12 @@ export class ApiService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDifficultyAdjustmentByHeight$(height: number): Observable<any> {
|
||||||
|
return this.httpClient.get<any>(
|
||||||
|
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/difficulty-adjustment/${height}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
getHistoricalHashrate$(interval: string | undefined): Observable<any> {
|
getHistoricalHashrate$(interval: string | undefined): Observable<any> {
|
||||||
return this.httpClient.get<any[]>(
|
return this.httpClient.get<any[]>(
|
||||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/hashrate` +
|
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/hashrate` +
|
||||||
|
@ -23,6 +23,7 @@ export class CacheService {
|
|||||||
blockLoading: { [height: number]: boolean } = {};
|
blockLoading: { [height: number]: boolean } = {};
|
||||||
copiesInBlockQueue: { [height: number]: number } = {};
|
copiesInBlockQueue: { [height: number]: number } = {};
|
||||||
blockPriorities: number[] = [];
|
blockPriorities: number[] = [];
|
||||||
|
daCache: { [height: number]: { adjustment: number, exact?: boolean } } = {};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private stateService: StateService,
|
private stateService: StateService,
|
||||||
@ -128,6 +129,7 @@ export class CacheService {
|
|||||||
this.blockLoading = {};
|
this.blockLoading = {};
|
||||||
this.copiesInBlockQueue = {};
|
this.copiesInBlockQueue = {};
|
||||||
this.blockPriorities = [];
|
this.blockPriorities = [];
|
||||||
|
this.daCache = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
getCachedBlock(height) {
|
getCachedBlock(height) {
|
||||||
|
@ -240,3 +240,25 @@ export function md5(inputString): string {
|
|||||||
}
|
}
|
||||||
return rh(a)+rh(b)+rh(c)+rh(d);
|
return rh(a)+rh(b)+rh(c)+rh(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function colorFromRetarget(da: number): string {
|
||||||
|
const minDA = 0.95;
|
||||||
|
const maxDA = 1.05;
|
||||||
|
const midDA = 1;
|
||||||
|
|
||||||
|
const red = { r: 220, g: 53, b: 69 };
|
||||||
|
const grey = { r: 108, g: 117, b: 125 };
|
||||||
|
const green = { r: 59, g: 204, b: 73 };
|
||||||
|
|
||||||
|
const interpolateColor = (color1, color2, ratio) => {
|
||||||
|
ratio = Math.min(1, Math.max(0, ratio));
|
||||||
|
const r = Math.round(color1.r + ratio * (color2.r - color1.r));
|
||||||
|
const g = Math.round(color1.g + ratio * (color2.g - color1.g));
|
||||||
|
const b = Math.round(color1.b + ratio * (color2.b - color1.b));
|
||||||
|
return `rgba(${r}, ${g}, ${b}, 0.7)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return da <= midDA ?
|
||||||
|
interpolateColor(red, grey, (da - minDA) / (midDA - minDA)) :
|
||||||
|
interpolateColor(grey, green, (da - midDA) / (maxDA - midDA));
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user