diff --git a/backend/src/__tests__/api/difficulty-adjustment.test.ts b/backend/src/__tests__/api/difficulty-adjustment.test.ts
index eb774d445..5ef1936e0 100644
--- a/backend/src/__tests__/api/difficulty-adjustment.test.ts
+++ b/backend/src/__tests__/api/difficulty-adjustment.test.ts
@@ -23,9 +23,11 @@ describe('Mempool Difficulty Adjustment', () => {
remainingBlocks: 1834,
remainingTime: 977591692,
previousRetarget: 0.6280047707459726,
+ previousTime: 1660820820,
nextRetargetHeight: 751968,
timeAvg: 533038,
timeOffset: 0,
+ expectedBlocks: 161.68833333333333,
},
],
[ // Vector 2 (testnet)
@@ -43,11 +45,13 @@ describe('Mempool Difficulty Adjustment', () => {
estimatedRetargetDate: 1661895424692,
remainingBlocks: 1834,
remainingTime: 977591692,
+ previousTime: 1660820820,
previousRetarget: 0.6280047707459726,
nextRetargetHeight: 751968,
timeAvg: 533038,
timeOffset: -667000, // 11 min 7 seconds since last block (testnet only)
// If we add time avg to abs(timeOffset) it makes exactly 1200000 ms, or 20 minutes
+ expectedBlocks: 161.68833333333333,
},
],
] as [[number, number, number, number, string, number], DifficultyAdjustment][];
diff --git a/backend/src/api/difficulty-adjustment.ts b/backend/src/api/difficulty-adjustment.ts
index a1b6ab70e..c4e2abf31 100644
--- a/backend/src/api/difficulty-adjustment.ts
+++ b/backend/src/api/difficulty-adjustment.ts
@@ -9,9 +9,11 @@ export interface DifficultyAdjustment {
remainingBlocks: number; // Block count
remainingTime: number; // Duration of time in ms
previousRetarget: number; // Percent: -75 to 300
+ previousTime: number; // Unix time in ms
nextRetargetHeight: number; // Block Height
timeAvg: number; // Duration of time in ms
timeOffset: number; // (Testnet) Time since last block (cap @ 20min) in ms
+ expectedBlocks: number; // Block count
}
export function calcDifficultyAdjustment(
@@ -32,12 +34,12 @@ export function calcDifficultyAdjustment(
const progressPercent = (blockHeight >= 0) ? blocksInEpoch / EPOCH_BLOCK_LENGTH * 100 : 100;
const remainingBlocks = EPOCH_BLOCK_LENGTH - blocksInEpoch;
const nextRetargetHeight = (blockHeight >= 0) ? blockHeight + remainingBlocks : 0;
+ const expectedBlocks = diffSeconds / BLOCK_SECONDS_TARGET;
let difficultyChange = 0;
- let timeAvgSecs = BLOCK_SECONDS_TARGET;
+ let timeAvgSecs = diffSeconds / blocksInEpoch;
// Only calculate the estimate once we have 7.2% of blocks in current epoch
if (blocksInEpoch >= ESTIMATE_LAG_BLOCKS) {
- timeAvgSecs = diffSeconds / blocksInEpoch;
difficultyChange = (BLOCK_SECONDS_TARGET / timeAvgSecs - 1) * 100;
// Max increase is x4 (+300%)
if (difficultyChange > 300) {
@@ -74,9 +76,11 @@ export function calcDifficultyAdjustment(
remainingBlocks,
remainingTime,
previousRetarget,
+ previousTime: DATime,
nextRetargetHeight,
timeAvg,
timeOffset,
+ expectedBlocks,
};
}
diff --git a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html
new file mode 100644
index 000000000..ce0bf7eff
--- /dev/null
+++ b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html
@@ -0,0 +1,87 @@
+
Difficulty Adjustment
+
+
+
+
+
+
Remaining
+
+
+ {{ i }} blocks
+ {{ i }} block
+
+
+
+
+
Estimate
+
+ 0; else arrowDownDifficulty" >
+
+
+
+
+
+ {{ epochData.change | absolute | number: '1.2-2' }}
+ %
+
+
+ —
+
+
+ Previous:
+
+ 0; else arrowDownPreviousDifficulty" >
+
+
+
+
+
+ {{ epochData.previousRetarget | absolute | number: '1.2-2' }} %
+
+
+
+
Current Period
+
{{ epochData.progress | number: '1.2-2' }} %
+
+
+
+
Next Halving
+
+
+ {{ i }} blocks
+ {{ i }} block
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.scss b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.scss
new file mode 100644
index 000000000..c5cd2dc5e
--- /dev/null
+++ b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.scss
@@ -0,0 +1,154 @@
+.difficulty-adjustment-container {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ height: 76px;
+ .shared-block {
+ color: #ffffff66;
+ font-size: 12px;
+ }
+ .item {
+ padding: 0 5px;
+ width: 100%;
+ &:nth-child(1) {
+ display: none;
+ @media (min-width: 485px) {
+ display: table-cell;
+ }
+ @media (min-width: 768px) {
+ display: none;
+ }
+ @media (min-width: 992px) {
+ display: table-cell;
+ }
+ }
+ }
+ .card-text {
+ font-size: 22px;
+ margin-top: -9px;
+ position: relative;
+ }
+}
+
+
+.difficulty-skeleton {
+ display: flex;
+ justify-content: space-between;
+ @media (min-width: 376px) {
+ flex-direction: row;
+ }
+ .item {
+ max-width: 150px;
+ margin: 0;
+ width: -webkit-fill-available;
+ @media (min-width: 376px) {
+ margin: 0 auto 0px;
+ }
+ &:first-child{
+ display: none;
+ @media (min-width: 485px) {
+ display: block;
+ }
+ @media (min-width: 768px) {
+ display: none;
+ }
+ @media (min-width: 992px) {
+ display: block;
+ }
+ }
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+ .card-text {
+ .skeleton-loader {
+ width: 100%;
+ display: block;
+ &:first-child {
+ margin: 14px auto 0;
+ max-width: 80px;
+ }
+ &:last-child {
+ margin: 10px auto 0;
+ max-width: 120px;
+ }
+ }
+ }
+}
+
+.card {
+ background-color: #1d1f31;
+ height: 100%;
+}
+
+.card-title {
+ color: #4a68b9;
+ font-size: 1rem;
+}
+
+.progress {
+ display: inline-flex;
+ width: 100%;
+ background-color: #2d3348;
+ height: 1.1rem;
+ max-width: 180px;
+}
+
+.skeleton-loader {
+ max-width: 100%;
+}
+
+.more-padding {
+ padding: 18px;
+}
+
+.small-bar {
+ height: 8px;
+ top: -4px;
+ max-width: 120px;
+}
+
+.loading-container {
+ min-height: 76px;
+}
+
+.main-title {
+ position: relative;
+ color: #ffffff91;
+ margin-top: -13px;
+ font-size: 10px;
+ text-transform: uppercase;
+ font-weight: 500;
+ text-align: center;
+ padding-bottom: 3px;
+}
+
+.card-wrapper {
+ .card {
+ height: auto !important;
+ }
+ .card-body {
+ display: flex;
+ flex: inherit;
+ text-align: center;
+ flex-direction: column;
+ justify-content: space-around;
+ padding: 24px 20px;
+ }
+}
+
+.retarget-sign {
+ margin-right: -3px;
+ font-size: 14px;
+ top: -2px;
+ position: relative;
+}
+
+.previous-retarget-sign {
+ margin-right: -2px;
+ font-size: 10px;
+}
+
+.symbol {
+ font-size: 13px;
+}
\ No newline at end of file
diff --git a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts
new file mode 100644
index 000000000..2abb02e22
--- /dev/null
+++ b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts
@@ -0,0 +1,86 @@
+import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
+import { combineLatest, Observable, timer } from 'rxjs';
+import { map, switchMap } from 'rxjs/operators';
+import { StateService } from '../../services/state.service';
+
+interface EpochProgress {
+ base: string;
+ change: number;
+ progress: number;
+ remainingBlocks: number;
+ newDifficultyHeight: number;
+ colorAdjustments: string;
+ colorPreviousAdjustments: string;
+ estimatedRetargetDate: number;
+ previousRetarget: number;
+ blocksUntilHalving: number;
+ timeUntilHalving: number;
+}
+
+@Component({
+ selector: 'app-difficulty-mining',
+ templateUrl: './difficulty-mining.component.html',
+ styleUrls: ['./difficulty-mining.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class DifficultyMiningComponent implements OnInit {
+ isLoadingWebSocket$: Observable;
+ difficultyEpoch$: Observable;
+
+ @Input() showProgress = true;
+ @Input() showHalving = false;
+ @Input() showTitle = true;
+
+ constructor(
+ public stateService: StateService,
+ ) { }
+
+ ngOnInit(): void {
+ this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$;
+ this.difficultyEpoch$ = combineLatest([
+ this.stateService.blocks$.pipe(map(([block]) => block)),
+ this.stateService.difficultyAdjustment$,
+ ])
+ .pipe(
+ map(([block, da]) => {
+ let colorAdjustments = '#ffffff66';
+ if (da.difficultyChange > 0) {
+ colorAdjustments = '#3bcc49';
+ }
+ if (da.difficultyChange < 0) {
+ colorAdjustments = '#dc3545';
+ }
+
+ let colorPreviousAdjustments = '#dc3545';
+ if (da.previousRetarget) {
+ if (da.previousRetarget >= 0) {
+ colorPreviousAdjustments = '#3bcc49';
+ }
+ if (da.previousRetarget === 0) {
+ colorPreviousAdjustments = '#ffffff66';
+ }
+ } else {
+ colorPreviousAdjustments = '#ffffff66';
+ }
+
+ const blocksUntilHalving = 210000 - (block.height % 210000);
+ const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000);
+
+ const data = {
+ base: `${da.progressPercent.toFixed(2)}%`,
+ change: da.difficultyChange,
+ progress: da.progressPercent,
+ remainingBlocks: da.remainingBlocks - 1,
+ colorAdjustments,
+ colorPreviousAdjustments,
+ newDifficultyHeight: da.nextRetargetHeight,
+ estimatedRetargetDate: da.estimatedRetargetDate,
+ previousRetarget: da.previousRetarget,
+ blocksUntilHalving,
+ timeUntilHalving,
+ };
+ return data;
+ })
+ );
+ }
+}
diff --git a/frontend/src/app/components/difficulty/difficulty-tooltip.component.html b/frontend/src/app/components/difficulty/difficulty-tooltip.component.html
new file mode 100644
index 000000000..57a96cca7
--- /dev/null
+++ b/frontend/src/app/components/difficulty/difficulty-tooltip.component.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+ {{ i }} blocks expected
+ {{ i }} block expected
+
+
+
+ {{ i }} blocks mined
+ {{ i }} block mined
+
+
+
+
+ {{ i }} blocks remaining
+ {{ i }} block remaining
+
+
+
+ {{ i }} blocks ahead
+ {{ i }} block ahead
+
+
+
+ {{ i }} blocks behind
+ {{ i }} block behind
+
+
+ next block
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/components/difficulty/difficulty-tooltip.component.scss b/frontend/src/app/components/difficulty/difficulty-tooltip.component.scss
new file mode 100644
index 000000000..82b762acd
--- /dev/null
+++ b/frontend/src/app/components/difficulty/difficulty-tooltip.component.scss
@@ -0,0 +1,18 @@
+.difficulty-tooltip {
+ position: fixed;
+ background: rgba(#11131f, 0.95);
+ border-radius: 4px;
+ box-shadow: 1px 1px 10px rgba(0,0,0,0.5);
+ color: #b1b1b1;
+ padding: 10px 15px;
+ text-align: left;
+ pointer-events: none;
+ max-width: 300px;
+ min-width: 200px;
+ text-align: center;
+
+ p {
+ margin: 0;
+ white-space: nowrap;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/components/difficulty/difficulty-tooltip.component.ts b/frontend/src/app/components/difficulty/difficulty-tooltip.component.ts
new file mode 100644
index 000000000..c7d26f61a
--- /dev/null
+++ b/frontend/src/app/components/difficulty/difficulty-tooltip.component.ts
@@ -0,0 +1,66 @@
+import { Component, ElementRef, ViewChild, Input, OnChanges } from '@angular/core';
+
+interface EpochProgress {
+ base: string;
+ change: number;
+ progress: number;
+ minedBlocks: number;
+ remainingBlocks: number;
+ expectedBlocks: number;
+ newDifficultyHeight: number;
+ colorAdjustments: string;
+ colorPreviousAdjustments: string;
+ estimatedRetargetDate: number;
+ previousRetarget: number;
+ blocksUntilHalving: number;
+ timeUntilHalving: number;
+}
+
+const EPOCH_BLOCK_LENGTH = 2016; // Bitcoin mainnet
+
+@Component({
+ selector: 'app-difficulty-tooltip',
+ templateUrl: './difficulty-tooltip.component.html',
+ styleUrls: ['./difficulty-tooltip.component.scss'],
+})
+export class DifficultyTooltipComponent implements OnChanges {
+ @Input() status: string | void;
+ @Input() progress: EpochProgress | void = null;
+ @Input() cursorPosition: { x: number, y: number };
+
+ mined: number;
+ ahead: number;
+ behind: number;
+ expected: number;
+ remaining: number;
+ isAhead: boolean;
+ isBehind: boolean;
+
+ tooltipPosition = { x: 0, y: 0 };
+
+ @ViewChild('tooltip') tooltipElement: ElementRef;
+
+ constructor() {}
+
+ ngOnChanges(changes): void {
+ if (changes.cursorPosition && changes.cursorPosition.currentValue) {
+ let x = changes.cursorPosition.currentValue.x;
+ let y = changes.cursorPosition.currentValue.y - 50;
+ if (this.tooltipElement) {
+ const elementBounds = this.tooltipElement.nativeElement.getBoundingClientRect();
+ x -= elementBounds.width / 2;
+ x = Math.min(Math.max(x, 20), (window.innerWidth - 20 - elementBounds.width));
+ }
+ this.tooltipPosition = { x, y };
+ }
+ if ((changes.progress || changes.status) && this.progress && this.status) {
+ this.remaining = this.progress.remainingBlocks;
+ this.expected = this.progress.expectedBlocks;
+ this.mined = this.progress.minedBlocks;
+ this.ahead = Math.max(0, this.mined - this.expected);
+ this.behind = Math.max(0, this.expected - this.mined);
+ this.isAhead = this.ahead > 0;
+ this.isBehind = this.behind > 0;
+ }
+ }
+}
diff --git a/frontend/src/app/components/difficulty/difficulty.component.html b/frontend/src/app/components/difficulty/difficulty.component.html
index ce0bf7eff..d23edcfe3 100644
--- a/frontend/src/app/components/difficulty/difficulty.component.html
+++ b/frontend/src/app/components/difficulty/difficulty.component.html
@@ -3,81 +3,100 @@
-
-
Remaining
-
-
- {{ i }} blocks
- {{ i }} block
-
-
+
+
-
-
Estimate
-
-
0; else arrowDownDifficulty" >
-
-
-
-
-
- {{ epochData.change | absolute | number: '1.2-2' }}
-
%
+
+
-
- —
-
-
-
Previous:
-
- 0; else arrowDownPreviousDifficulty" >
-
+
+
+ 0; else arrowDownDifficulty" >
+
-
-
+
+
- {{ epochData.previousRetarget | absolute | number: '1.2-2' }} %
+ {{ epochData.change | absolute | number: '1.2-2' }}
+ %
+
+
+ —
+
+
+ Previous:
+
+ 0; else arrowDownPreviousDifficulty" >
+
+
+
+
+
+ {{ epochData.previousRetarget | absolute | number: '1.2-2' }} %
+
-
-
-
Current Period
-
{{ epochData.progress | number: '1.2-2' }} %
-
-
+
+
+
+ {{ epochData.retargetDateString }}
+
-
-
Next Halving
-
-
- {{ i }} blocks
- {{ i }} block
-
-
-
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/components/difficulty/difficulty.component.scss b/frontend/src/app/components/difficulty/difficulty.component.scss
index c5cd2dc5e..9828ba8f5 100644
--- a/frontend/src/app/components/difficulty/difficulty.component.scss
+++ b/frontend/src/app/components/difficulty/difficulty.component.scss
@@ -1,8 +1,14 @@
.difficulty-adjustment-container {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+}
+
+.difficulty-stats {
display: flex;
flex-direction: row;
justify-content: space-around;
- height: 76px;
+ height: 50.5px;
.shared-block {
color: #ffffff66;
font-size: 12px;
@@ -24,8 +30,8 @@
}
}
.card-text {
- font-size: 22px;
- margin-top: -9px;
+ font-size: 20px;
+ margin: auto;
position: relative;
}
}
@@ -33,7 +39,9 @@
.difficulty-skeleton {
display: flex;
- justify-content: space-between;
+ flex-direction: row;
+ justify-content: space-around;
+ height: 50.5px;
@media (min-width: 376px) {
flex-direction: row;
}
@@ -65,7 +73,7 @@
width: 100%;
display: block;
&:first-child {
- margin: 14px auto 0;
+ margin: 10px auto 4px;
max-width: 80px;
}
&:last-child {
@@ -109,7 +117,7 @@
}
.loading-container {
- min-height: 76px;
+ min-height: 50.5px;
}
.main-title {
@@ -133,7 +141,7 @@
text-align: center;
flex-direction: column;
justify-content: space-around;
- padding: 24px 20px;
+ padding: 20px;
}
}
@@ -151,4 +159,50 @@
.symbol {
font-size: 13px;
+}
+
+.epoch-progress {
+ width: 100%;
+ height: 22px;
+ margin-bottom: 12px;
+}
+
+.epoch-blocks {
+ display: block;
+ width: 100%;
+ background: #2d3348;
+
+ .rect {
+ fill: #2d3348;
+
+ &.behind {
+ fill: #D81B60;
+ }
+ &.mined {
+ fill: url(#diff-gradient);
+ }
+ &.ahead {
+ fill: #1a9436;
+ }
+
+ &.hover {
+ fill: #535e84;
+ &.behind {
+ fill: #e94d86;
+ }
+ &.mined {
+ fill: url(#diff-hover-gradient);
+ }
+ &.ahead {
+ fill: #29d951;
+ }
+ }
+ }
+}
+
+.blocks-ahead {
+ color: #3bcc49;
+}
+.blocks-behind {
+ color: #D81B60;
}
\ No newline at end of file
diff --git a/frontend/src/app/components/difficulty/difficulty.component.ts b/frontend/src/app/components/difficulty/difficulty.component.ts
index 76a996acc..910ea1384 100644
--- a/frontend/src/app/components/difficulty/difficulty.component.ts
+++ b/frontend/src/app/components/difficulty/difficulty.component.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
+import { ChangeDetectionStrategy, Component, HostListener, Inject, Input, LOCALE_ID, OnInit } from '@angular/core';
import { combineLatest, Observable, timer } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { StateService } from '../..//services/state.service';
@@ -7,16 +7,33 @@ interface EpochProgress {
base: string;
change: number;
progress: number;
+ minedBlocks: number;
remainingBlocks: number;
+ expectedBlocks: number;
newDifficultyHeight: number;
colorAdjustments: string;
colorPreviousAdjustments: string;
estimatedRetargetDate: number;
+ retargetDateString: string;
previousRetarget: number;
blocksUntilHalving: number;
timeUntilHalving: number;
+ timeAvg: number;
}
+type BlockStatus = 'mined' | 'behind' | 'ahead' | 'next' | 'remaining';
+
+interface DiffShape {
+ x: number;
+ y: number;
+ w: number;
+ h: number;
+ status: BlockStatus;
+ expected: boolean;
+}
+
+const EPOCH_BLOCK_LENGTH = 2016; // Bitcoin mainnet
+
@Component({
selector: 'app-difficulty',
templateUrl: './difficulty.component.html',
@@ -24,15 +41,27 @@ interface EpochProgress {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DifficultyComponent implements OnInit {
- isLoadingWebSocket$: Observable
;
- difficultyEpoch$: Observable;
-
@Input() showProgress = true;
@Input() showHalving = false;
@Input() showTitle = true;
+
+ isLoadingWebSocket$: Observable;
+ difficultyEpoch$: Observable;
+
+ epochStart: number;
+ currentHeight: number;
+ currentIndex: number;
+ expectedHeight: number;
+ expectedIndex: number;
+ difference: number;
+ shapes: DiffShape[];
+
+ tooltipPosition = { x: 0, y: 0 };
+ hoverSection: DiffShape | void;
constructor(
public stateService: StateService,
+ @Inject(LOCALE_ID) private locale: string,
) { }
ngOnInit(): void {
@@ -65,22 +94,110 @@ export class DifficultyComponent implements OnInit {
const blocksUntilHalving = 210000 - (block.height % 210000);
const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000);
+ const newEpochStart = Math.floor(this.stateService.latestBlockHeight / EPOCH_BLOCK_LENGTH) * EPOCH_BLOCK_LENGTH;
+ const newExpectedHeight = Math.floor(newEpochStart + da.expectedBlocks);
+
+ if (newEpochStart !== this.epochStart || newExpectedHeight !== this.expectedHeight || this.currentHeight !== this.stateService.latestBlockHeight) {
+ this.epochStart = newEpochStart;
+ this.expectedHeight = newExpectedHeight;
+ this.currentHeight = this.stateService.latestBlockHeight;
+ this.currentIndex = this.currentHeight - this.epochStart;
+ this.expectedIndex = Math.min(this.expectedHeight - this.epochStart, 2106) - 1;
+ this.difference = this.currentIndex - this.expectedIndex;
+
+ this.shapes = [];
+ this.shapes = this.shapes.concat(this.blocksToShapes(
+ 0, Math.min(this.currentIndex, this.expectedIndex), 'mined', true
+ ));
+ this.shapes = this.shapes.concat(this.blocksToShapes(
+ this.currentIndex + 1, this.expectedIndex, 'behind', true
+ ));
+ this.shapes = this.shapes.concat(this.blocksToShapes(
+ this.expectedIndex + 1, this.currentIndex, 'ahead', false
+ ));
+ if (this.currentIndex < 2105) {
+ this.shapes = this.shapes.concat(this.blocksToShapes(
+ this.currentIndex + 1, this.currentIndex + 1, 'next', (this.expectedIndex > this.currentIndex)
+ ));
+ }
+ this.shapes = this.shapes.concat(this.blocksToShapes(
+ Math.max(this.currentIndex + 2, this.expectedIndex + 1), 2105, 'remaining', false
+ ));
+ }
+
+
+ let retargetDateString;
+ if (da.remainingBlocks > 1870) {
+ retargetDateString = (new Date(da.estimatedRetargetDate)).toLocaleDateString(this.locale, { month: 'long', day: 'numeric' });
+ } else {
+ retargetDateString = (new Date(da.estimatedRetargetDate)).toLocaleTimeString(this.locale, { month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' });
+ }
const data = {
base: `${da.progressPercent.toFixed(2)}%`,
change: da.difficultyChange,
progress: da.progressPercent,
- remainingBlocks: da.remainingBlocks,
+ minedBlocks: this.currentIndex + 1,
+ remainingBlocks: da.remainingBlocks - 1,
+ expectedBlocks: Math.floor(da.expectedBlocks),
colorAdjustments,
colorPreviousAdjustments,
newDifficultyHeight: da.nextRetargetHeight,
estimatedRetargetDate: da.estimatedRetargetDate,
+ retargetDateString,
previousRetarget: da.previousRetarget,
blocksUntilHalving,
timeUntilHalving,
+ timeAvg: da.timeAvg,
};
return data;
})
);
}
+
+ blocksToShapes(start: number, end: number, status: BlockStatus, expected: boolean = false): DiffShape[] {
+ const startY = start % 9;
+ const startX = Math.floor(start / 9);
+ const endY = (end % 9);
+ const endX = Math.floor(end / 9);
+
+ if (startX > endX) {
+ return [];
+ }
+
+ if (startX === endX) {
+ return [{
+ x: startX, y: startY, w: 1, h: 1 + endY - startY, status, expected
+ }];
+ }
+
+ const shapes = [];
+ shapes.push({
+ x: startX, y: startY, w: 1, h: 9 - startY, status, expected
+ });
+ shapes.push({
+ x: endX, y: 0, w: 1, h: endY + 1, status, expected
+ });
+
+ if (startX < endX - 1) {
+ shapes.push({
+ x: startX + 1, y: 0, w: endX - startX - 1, h: 9, status, expected
+ });
+ }
+
+ return shapes;
+ }
+
+ @HostListener('pointermove', ['$event'])
+ onPointerMove(event) {
+ this.tooltipPosition = { x: event.clientX, y: event.clientY };
+ }
+
+ onHover(event, rect): void {
+ this.hoverSection = rect;
+ }
+
+ onBlur(event): void {
+ this.hoverSection = null;
+ }
}
diff --git a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html
index 1a932567c..a51f90f6c 100644
--- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html
+++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.html
@@ -22,7 +22,7 @@
Difficulty Adjustment
-
+
diff --git a/frontend/src/app/components/time/time.component.ts b/frontend/src/app/components/time/time.component.ts
index 4cc76975e..061061cc9 100644
--- a/frontend/src/app/components/time/time.component.ts
+++ b/frontend/src/app/components/time/time.component.ts
@@ -19,6 +19,7 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
@Input() fixedRender = false;
@Input() relative = false;
@Input() forceFloorOnTimeIntervals: string[];
+ @Input() fractionDigits: number = 0;
constructor(
private ref: ChangeDetectorRef,
@@ -88,7 +89,12 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
} else {
counter = Math.round(seconds / this.intervals[i]);
}
- const dateStrings = dates(counter);
+ let rounded = counter;
+ if (this.fractionDigits) {
+ const roundFactor = Math.pow(10,this.fractionDigits);
+ rounded = Math.round((seconds / this.intervals[i]) * roundFactor) / roundFactor;
+ }
+ const dateStrings = dates(rounded);
if (counter > 0) {
switch (this.kind) {
case 'since':
diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts
index 7ed32a9de..cad623f9f 100644
--- a/frontend/src/app/interfaces/node-api.interface.ts
+++ b/frontend/src/app/interfaces/node-api.interface.ts
@@ -33,9 +33,11 @@ export interface DifficultyAdjustment {
remainingBlocks: number;
remainingTime: number;
previousRetarget: number;
+ previousTime: number;
nextRetargetHeight: number;
timeAvg: number;
timeOffset: number;
+ expectedBlocks: number;
}
export interface AddressInformation {
diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts
index 52b469836..188942c02 100644
--- a/frontend/src/app/shared/shared.module.ts
+++ b/frontend/src/app/shared/shared.module.ts
@@ -58,6 +58,8 @@ import { AssetsNavComponent } from '../components/assets/assets-nav/assets-nav.c
import { StatusViewComponent } from '../components/status-view/status-view.component';
import { FeesBoxComponent } from '../components/fees-box/fees-box.component';
import { DifficultyComponent } from '../components/difficulty/difficulty.component';
+import { DifficultyTooltipComponent } from '../components/difficulty/difficulty-tooltip.component';
+import { DifficultyMiningComponent } from '../components/difficulty-mining/difficulty-mining.component';
import { TermsOfServiceComponent } from '../components/terms-of-service/terms-of-service.component';
import { TxBowtieGraphComponent } from '../components/tx-bowtie-graph/tx-bowtie-graph.component';
import { TxBowtieGraphTooltipComponent } from '../components/tx-bowtie-graph-tooltip/tx-bowtie-graph-tooltip.component';
@@ -133,6 +135,8 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
StatusViewComponent,
FeesBoxComponent,
DifficultyComponent,
+ DifficultyMiningComponent,
+ DifficultyTooltipComponent,
TxBowtieGraphComponent,
TxBowtieGraphTooltipComponent,
TermsOfServiceComponent,
@@ -234,6 +238,8 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
StatusViewComponent,
FeesBoxComponent,
DifficultyComponent,
+ DifficultyMiningComponent,
+ DifficultyTooltipComponent,
TxBowtieGraphComponent,
TxBowtieGraphTooltipComponent,
TermsOfServiceComponent,
diff --git a/frontend/src/locale/messages.xlf b/frontend/src/locale/messages.xlf
index 48c7da18d..e040e7bc9 100644
--- a/frontend/src/locale/messages.xlf
+++ b/frontend/src/locale/messages.xlf
@@ -2452,6 +2452,10 @@
src/app/components/block/block.component.html
8,9
+
+ src/app/components/difficulty/difficulty-tooltip.component.html
+ 38
+
src/app/components/mempool-block/mempool-block.component.ts
75
@@ -2870,6 +2874,10 @@
Difficulty Adjustment
+
+ src/app/components/difficulty-mining/difficulty-mining.component.html
+ 1,5
+
src/app/components/difficulty/difficulty.component.html
1,5
@@ -2883,11 +2891,11 @@
Remaining
- src/app/components/difficulty/difficulty.component.html
+ src/app/components/difficulty-mining/difficulty-mining.component.html
7,9
- src/app/components/difficulty/difficulty.component.html
+ src/app/components/difficulty-mining/difficulty-mining.component.html
66,69
difficulty-box.remaining
@@ -2896,11 +2904,11 @@
blocks
- src/app/components/difficulty/difficulty.component.html
+ src/app/components/difficulty-mining/difficulty-mining.component.html
10,11
- src/app/components/difficulty/difficulty.component.html
+ src/app/components/difficulty-mining/difficulty-mining.component.html
53,54
@@ -2921,11 +2929,11 @@
block
- src/app/components/difficulty/difficulty.component.html
+ src/app/components/difficulty-mining/difficulty-mining.component.html
11,12
- src/app/components/difficulty/difficulty.component.html
+ src/app/components/difficulty-mining/difficulty-mining.component.html
54,55
@@ -2937,11 +2945,11 @@
Estimate
- src/app/components/difficulty/difficulty.component.html
+ src/app/components/difficulty-mining/difficulty-mining.component.html
16,17
- src/app/components/difficulty/difficulty.component.html
+ src/app/components/difficulty-mining/difficulty-mining.component.html
73,76
difficulty-box.estimate
@@ -2949,19 +2957,23 @@
Previous
- src/app/components/difficulty/difficulty.component.html
+ src/app/components/difficulty-mining/difficulty-mining.component.html
31,33
+
+ src/app/components/difficulty/difficulty.component.html
+ 59,61
+
difficulty-box.previous
Current Period
- src/app/components/difficulty/difficulty.component.html
+ src/app/components/difficulty-mining/difficulty-mining.component.html
43,44
- src/app/components/difficulty/difficulty.component.html
+ src/app/components/difficulty-mining/difficulty-mining.component.html
80,83
difficulty-box.current-period
@@ -2969,11 +2981,99 @@
Next Halving
- src/app/components/difficulty/difficulty.component.html
+ src/app/components/difficulty-mining/difficulty-mining.component.html
50,52
difficulty-box.next-halving
+
+ blocks expected
+
+ src/app/components/difficulty/difficulty-tooltip.component.html
+ 13
+
+ difficulty-box.expected-blocks
+
+
+ block expected
+
+ src/app/components/difficulty/difficulty-tooltip.component.html
+ 14
+
+ difficulty-box.expected-block
+
+
+ blocks mined
+
+ src/app/components/difficulty/difficulty-tooltip.component.html
+ 18
+
+ difficulty-box.mined-blocks
+
+
+ block mined
+
+ src/app/components/difficulty/difficulty-tooltip.component.html
+ 19
+
+ difficulty-box.mined-block
+
+
+ blocks remaining
+
+ src/app/components/difficulty/difficulty-tooltip.component.html
+ 24
+
+ difficulty-box.remaining-blocks
+
+
+ block remaining
+
+ src/app/components/difficulty/difficulty-tooltip.component.html
+ 25
+
+ difficulty-box.remaining-block
+
+
+ blocks ahead
+
+ src/app/components/difficulty/difficulty-tooltip.component.html
+ 29
+
+ difficulty-box.blocks-ahead
+
+
+ block ahead
+
+ src/app/components/difficulty/difficulty-tooltip.component.html
+ 30
+
+ difficulty-box.block-ahead
+
+
+ blocks behind
+
+ src/app/components/difficulty/difficulty-tooltip.component.html
+ 34
+
+ difficulty-box.blocks-behind
+
+
+ block behind
+
+ src/app/components/difficulty/difficulty-tooltip.component.html
+ 35
+
+ difficulty-box.block-behind
+
+
+ Average block time
+
+ src/app/components/difficulty/difficulty.component.html
+ 42,45
+
+ difficulty-box.average-block-time
+
Either 2x the minimum, or the Low Priority rate (whichever is lower)
@@ -4054,39 +4154,27 @@
Just now
src/app/components/time/time.component.ts
- 78
+ 79
ago
-
- src/app/components/time/time.component.ts
- 97
-
-
- src/app/components/time/time.component.ts
- 98
-
-
- src/app/components/time/time.component.ts
- 99
-
-
- src/app/components/time/time.component.ts
- 100
-
-
- src/app/components/time/time.component.ts
- 101
-
-
- src/app/components/time/time.component.ts
- 102
-
src/app/components/time/time.component.ts
103
+
+ src/app/components/time/time.component.ts
+ 104
+
+
+ src/app/components/time/time.component.ts
+ 105
+
+
+ src/app/components/time/time.component.ts
+ 106
+
src/app/components/time/time.component.ts
107
@@ -4099,53 +4187,53 @@
src/app/components/time/time.component.ts
109
-
- src/app/components/time/time.component.ts
- 110
-
-
- src/app/components/time/time.component.ts
- 111
-
-
- src/app/components/time/time.component.ts
- 112
-
src/app/components/time/time.component.ts
113
+
+ src/app/components/time/time.component.ts
+ 114
+
+
+ src/app/components/time/time.component.ts
+ 115
+
+
+ src/app/components/time/time.component.ts
+ 116
+
+
+ src/app/components/time/time.component.ts
+ 117
+
+
+ src/app/components/time/time.component.ts
+ 118
+
+
+ src/app/components/time/time.component.ts
+ 119
+
In ~
-
- src/app/components/time/time.component.ts
- 120
-
-
- src/app/components/time/time.component.ts
- 121
-
-
- src/app/components/time/time.component.ts
- 122
-
-
- src/app/components/time/time.component.ts
- 123
-
-
- src/app/components/time/time.component.ts
- 124
-
-
- src/app/components/time/time.component.ts
- 125
-
src/app/components/time/time.component.ts
126
+
+ src/app/components/time/time.component.ts
+ 127
+
+
+ src/app/components/time/time.component.ts
+ 128
+
+
+ src/app/components/time/time.component.ts
+ 129
+
src/app/components/time/time.component.ts
130
@@ -4158,53 +4246,53 @@
src/app/components/time/time.component.ts
132
-
- src/app/components/time/time.component.ts
- 133
-
-
- src/app/components/time/time.component.ts
- 134
-
-
- src/app/components/time/time.component.ts
- 135
-
src/app/components/time/time.component.ts
136
+
+ src/app/components/time/time.component.ts
+ 137
+
+
+ src/app/components/time/time.component.ts
+ 138
+
+
+ src/app/components/time/time.component.ts
+ 139
+
+
+ src/app/components/time/time.component.ts
+ 140
+
+
+ src/app/components/time/time.component.ts
+ 141
+
+
+ src/app/components/time/time.component.ts
+ 142
+
After
-
- src/app/components/time/time.component.ts
- 143
-
-
- src/app/components/time/time.component.ts
- 144
-
-
- src/app/components/time/time.component.ts
- 145
-
-
- src/app/components/time/time.component.ts
- 146
-
-
- src/app/components/time/time.component.ts
- 147
-
-
- src/app/components/time/time.component.ts
- 148
-
src/app/components/time/time.component.ts
149
+
+ src/app/components/time/time.component.ts
+ 150
+
+
+ src/app/components/time/time.component.ts
+ 151
+
+
+ src/app/components/time/time.component.ts
+ 152
+
src/app/components/time/time.component.ts
153
@@ -4217,22 +4305,34 @@
src/app/components/time/time.component.ts
155
-
- src/app/components/time/time.component.ts
- 156
-
-
- src/app/components/time/time.component.ts
- 157
-
-
- src/app/components/time/time.component.ts
- 158
-
src/app/components/time/time.component.ts
159
+
+ src/app/components/time/time.component.ts
+ 160
+
+
+ src/app/components/time/time.component.ts
+ 161
+
+
+ src/app/components/time/time.component.ts
+ 162
+
+
+ src/app/components/time/time.component.ts
+ 163
+
+
+ src/app/components/time/time.component.ts
+ 164
+
+
+ src/app/components/time/time.component.ts
+ 165
+
This transaction has been replaced by: