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
+
+ + + + + + + {{ epochData.change | absolute | number: '1.2-2' }} + % +
+ +
+
+
+ Previous: + + + + + + + + {{ epochData.previousRetarget | absolute | number: '1.2-2' }} % +
+
+
+
Current Period
+
{{ epochData.progress | number: '1.2-2' }} %
+
+
 
+
+
+
+
Next Halving
+
+ + {{ i }} blocks + {{ i }} block +
+
+
+
+
+
+
+ + +
+
+
Remaining
+
+
+
+
+
+
+
Estimate
+
+
+
+
+
+
+
Current Period
+
+
+
+
+
+
+
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
-
- - - - - - - {{ epochData.change | absolute | number: '1.2-2' }} - % +
+
+
+ ~ +
+
Average block time
- -
-
-
- Previous: - - - +
+
+ + - - + + - {{ epochData.previousRetarget | absolute | number: '1.2-2' }} % + {{ epochData.change | absolute | number: '1.2-2' }} + % +
+ +
+
+
+ Previous: + + + + + + + + {{ epochData.previousRetarget | absolute | number: '1.2-2' }} % +
-
-
-
Current Period
-
{{ epochData.progress | number: '1.2-2' }} %
-
-
 
+
+
+
+ {{ epochData.retargetDateString }} +
-
-
Next Halving
-
- - {{ i }} blocks - {{ i }} block -
-
-
+
+
+
-
Remaining
-
Estimate
-
Current Period
@@ -85,3 +104,10 @@
+ + \ 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: