From 000524691adcf57b2a8f34b18d0e3ad7eef5dad6 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 11 Feb 2024 22:50:34 +0000 Subject: [PATCH] Project early difficulty from sliding window --- .../api/difficulty-adjustment.test.ts | 53 +++++++++++++++---- backend/src/api/blocks.ts | 15 ++++++ backend/src/api/difficulty-adjustment.ts | 23 ++++++-- .../difficulty-mining.component.html | 8 +-- .../difficulty-mining.component.ts | 2 + .../difficulty/difficulty.component.ts | 2 + .../mempool-blocks.component.html | 2 +- .../transaction/transaction.component.html | 2 +- .../src/app/docs/api-docs/api-docs-data.ts | 4 ++ .../src/app/interfaces/node-api.interface.ts | 1 + 10 files changed, 93 insertions(+), 19 deletions(-) diff --git a/backend/src/__tests__/api/difficulty-adjustment.test.ts b/backend/src/__tests__/api/difficulty-adjustment.test.ts index c3e8e1a88..a365d3429 100644 --- a/backend/src/__tests__/api/difficulty-adjustment.test.ts +++ b/backend/src/__tests__/api/difficulty-adjustment.test.ts @@ -11,9 +11,35 @@ describe('Mempool Difficulty Adjustment', () => { }; const vectors = [ - [ // Vector 1 + [ // Vector 1 (normal adjustment) + [ // Inputs + dt('2024-02-02T15:42:06.000Z'), // Last DA time (in seconds) + dt('2024-02-08T14:43:05.000Z'), // timestamp of 504 blocks ago (in seconds) + dt('2024-02-11T22:43:01.000Z'), // Current time (now) (in seconds) + 830027, // Current block height + 7.333505241141637, // Previous retarget % (Passed through) + 'mainnet', // Network (if testnet, next value is non-zero) + 0, // Latest block timestamp in seconds (only used if difficulty already locked in) + ], + { // Expected Result + progressPercent: 71.97420634920636, + difficultyChange: 8.512745140778843, + estimatedRetargetDate: 1708004001715, + remainingBlocks: 565, + remainingTime: 312620715, + previousRetarget: 7.333505241141637, + previousTime: 1706888526, + nextRetargetHeight: 830592, + timeAvg: 553311, + adjustedTimeAvg: 553311, + timeOffset: 0, + expectedBlocks: 1338.0916666666667, + }, + ], + [ // Vector 2 (within quarter-epoch overlap) [ // Inputs dt('2022-08-18T11:07:00.000Z'), // Last DA time (in seconds) + dt('2022-08-16T03:16:54.000Z'), // timestamp of 504 blocks ago (in seconds) dt('2022-08-19T14:03:53.000Z'), // Current time (now) (in seconds) 750134, // Current block height 0.6280047707459726, // Previous retarget % (Passed through) @@ -22,21 +48,23 @@ describe('Mempool Difficulty Adjustment', () => { ], { // Expected Result progressPercent: 9.027777777777777, - difficultyChange: 13.180707740199772, - estimatedRetargetDate: 1661895424692, + difficultyChange: 1.0420538959004633, + estimatedRetargetDate: 1662009048328, remainingBlocks: 1834, - remainingTime: 977591692, + remainingTime: 1091215328, previousRetarget: 0.6280047707459726, previousTime: 1660820820, nextRetargetHeight: 751968, timeAvg: 533038, + adjustedTimeAvg: 594992, timeOffset: 0, expectedBlocks: 161.68833333333333, }, ], - [ // Vector 2 (testnet) + [ // Vector 3 (testnet) [ // Inputs dt('2022-08-18T11:07:00.000Z'), // Last DA time (in seconds) + dt('2022-08-16T03:16:54.000Z'), // timestamp of 504 blocks ago (in seconds) dt('2022-08-19T14:03:53.000Z'), // Current time (now) (in seconds) 750134, // Current block height 0.6280047707459726, // Previous retarget % (Passed through) @@ -45,22 +73,24 @@ describe('Mempool Difficulty Adjustment', () => { ], { // Expected Result is same other than timeOffset progressPercent: 9.027777777777777, - difficultyChange: 13.180707740199772, - estimatedRetargetDate: 1661895424692, + difficultyChange: 1.0420538959004633, + estimatedRetargetDate: 1662009048328, remainingBlocks: 1834, - remainingTime: 977591692, + remainingTime: 1091215328, previousTime: 1660820820, previousRetarget: 0.6280047707459726, nextRetargetHeight: 751968, timeAvg: 533038, + adjustedTimeAvg: 594992, 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, }, ], - [ // Vector 3 (mainnet lock-in (epoch ending 788255)) + [ // Vector 4 (mainnet lock-in (epoch ending 788255)) [ // Inputs dt('2023-04-20T09:57:33.000Z'), // Last DA time (in seconds) + dt('2022-08-16T03:16:54.000Z'), // timestamp of 504 blocks ago (in seconds) dt('2023-05-04T14:54:09.000Z'), // Current time (now) (in seconds) 788255, // Current block height 1.7220298879531821, // Previous retarget % (Passed through) @@ -77,16 +107,17 @@ describe('Mempool Difficulty Adjustment', () => { previousTime: 1681984653, nextRetargetHeight: 788256, timeAvg: 609129, + adjustedTimeAvg: 609129, timeOffset: 0, expectedBlocks: 2045.66, }, ], - ] as [[number, number, number, number, string, number], DifficultyAdjustment][]; + ] as [[number, number, number, number, number, string, number], DifficultyAdjustment][]; for (const vector of vectors) { const result = calcDifficultyAdjustment(...vector[0]); // previousRetarget is passed through untouched - expect(result.previousRetarget).toStrictEqual(vector[0][3]); + expect(result.previousRetarget).toStrictEqual(vector[0][4]); expect(result).toStrictEqual(vector[1]); } }); diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 837bc0ee9..4ccf58645 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -37,6 +37,7 @@ class Blocks { private currentBits = 0; private lastDifficultyAdjustmentTime = 0; private previousDifficultyRetarget = 0; + private quarterEpochBlockTime: number | null = null; private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = []; private newAsyncBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]) => Promise)[] = []; @@ -775,6 +776,16 @@ class Blocks { } else { this.currentBlockHeight = this.blocks[this.blocks.length - 1].height; } + if (this.currentBlockHeight >= 503) { + try { + const quarterEpochBlockHash = await bitcoinApi.$getBlockHash(this.currentBlockHeight - 503); + const quarterEpochBlock = await bitcoinApi.$getBlock(quarterEpochBlockHash); + this.quarterEpochBlockTime = quarterEpochBlock?.timestamp; + } catch (e) { + this.quarterEpochBlockTime = null; + logger.warn('failed to update last epoch block time: ' + (e instanceof Error ? e.message : e)); + } + } if (blockHeightTip - this.currentBlockHeight > config.MEMPOOL.INITIAL_BLOCKS_AMOUNT * 2) { logger.info(`${blockHeightTip - this.currentBlockHeight} blocks since tip. Fast forwarding to the ${config.MEMPOOL.INITIAL_BLOCKS_AMOUNT} recent blocks`); @@ -1308,6 +1319,10 @@ class Blocks { return this.previousDifficultyRetarget; } + public getQuarterEpochBlockTime(): number | null { + return this.quarterEpochBlockTime; + } + public getCurrentBlockHeight(): number { return this.currentBlockHeight; } diff --git a/backend/src/api/difficulty-adjustment.ts b/backend/src/api/difficulty-adjustment.ts index d93a5f91a..2f3b21029 100644 --- a/backend/src/api/difficulty-adjustment.ts +++ b/backend/src/api/difficulty-adjustment.ts @@ -12,6 +12,7 @@ export interface DifficultyAdjustment { previousTime: number; // Unix time in ms nextRetargetHeight: number; // Block Height timeAvg: number; // Duration of time in ms + adjustedTimeAvg; // Expected block interval with hashrate implied over last 504 blocks timeOffset: number; // (Testnet) Time since last block (cap @ 20min) in ms expectedBlocks: number; // Block count } @@ -80,6 +81,7 @@ export function calcBitsDifference(oldBits: number, newBits: number): number { export function calcDifficultyAdjustment( DATime: number, + quarterEpochTime: number | null, nowSeconds: number, blockHeight: number, previousRetarget: number, @@ -100,8 +102,20 @@ export function calcDifficultyAdjustment( let difficultyChange = 0; let timeAvgSecs = blocksInEpoch ? diffSeconds / blocksInEpoch : BLOCK_SECONDS_TARGET; + let adjustedTimeAvgSecs = timeAvgSecs; + + // for the first 504 blocks of the epoch, calculate the expected avg block interval + // from a sliding window over the last 504 blocks + if (quarterEpochTime && blocksInEpoch < 503) { + const timeLastEpoch = DATime - quarterEpochTime; + const adjustedTimeLastEpoch = timeLastEpoch * (1 + (previousRetarget / 100)); + const adjustedTimeSpan = diffSeconds + adjustedTimeLastEpoch; + adjustedTimeAvgSecs = adjustedTimeSpan / 503; + difficultyChange = (BLOCK_SECONDS_TARGET / (adjustedTimeSpan / 504) - 1) * 100; + } else { + difficultyChange = (BLOCK_SECONDS_TARGET / (actualTimespan / (blocksInEpoch + 1)) - 1) * 100; + } - difficultyChange = (BLOCK_SECONDS_TARGET / (actualTimespan / (blocksInEpoch + 1)) - 1) * 100; // Max increase is x4 (+300%) if (difficultyChange > 300) { difficultyChange = 300; @@ -126,7 +140,8 @@ export function calcDifficultyAdjustment( } const timeAvg = Math.floor(timeAvgSecs * 1000); - const remainingTime = remainingBlocks * timeAvg; + const adjustedTimeAvg = Math.floor(adjustedTimeAvgSecs * 1000); + const remainingTime = remainingBlocks * adjustedTimeAvg; const estimatedRetargetDate = remainingTime + nowSeconds * 1000; return { @@ -139,6 +154,7 @@ export function calcDifficultyAdjustment( previousTime: DATime, nextRetargetHeight, timeAvg, + adjustedTimeAvg, timeOffset, expectedBlocks, }; @@ -155,9 +171,10 @@ class DifficultyAdjustmentApi { return null; } const nowSeconds = Math.floor(new Date().getTime() / 1000); + const quarterEpochBlockTime = blocks.getQuarterEpochBlockTime(); return calcDifficultyAdjustment( - DATime, nowSeconds, blockHeight, previousRetarget, + DATime, quarterEpochBlockTime, nowSeconds, blockHeight, previousRetarget, config.MEMPOOL.NETWORK, latestBlock.timestamp ); } diff --git a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html index 9f3706916..eccddc3a6 100644 --- a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html +++ b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html @@ -49,13 +49,15 @@
Next Halving
- {{ timeUntilHalving | date }} + + {{ i }} blocks + {{ i }} block
- +
- + {{ timeUntilHalving | date }}
diff --git a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts index c1283b8b1..c650e2c02 100644 --- a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts +++ b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.ts @@ -16,6 +16,7 @@ interface EpochProgress { blocksUntilHalving: number; timeUntilHalving: number; timeAvg: number; + adjustedTimeAvg: number; } @Component({ @@ -85,6 +86,7 @@ export class DifficultyMiningComponent implements OnInit { blocksUntilHalving: this.blocksUntilHalving, timeUntilHalving: this.timeUntilHalving, timeAvg: da.timeAvg, + adjustedTimeAvg: da.adjustedTimeAvg, }; return data; }) diff --git a/frontend/src/app/components/difficulty/difficulty.component.ts b/frontend/src/app/components/difficulty/difficulty.component.ts index 81084f524..d37667312 100644 --- a/frontend/src/app/components/difficulty/difficulty.component.ts +++ b/frontend/src/app/components/difficulty/difficulty.component.ts @@ -19,6 +19,7 @@ interface EpochProgress { blocksUntilHalving: number; timeUntilHalving: number; timeAvg: number; + adjustedTimeAvg: number; } type BlockStatus = 'mined' | 'behind' | 'ahead' | 'next' | 'remaining'; @@ -153,6 +154,7 @@ export class DifficultyComponent implements OnInit { blocksUntilHalving, timeUntilHalving, timeAvg: da.timeAvg, + adjustedTimeAvg: da.adjustedTimeAvg, }; return data; }) diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html index 59d35c91e..fca1ce215 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.html @@ -34,7 +34,7 @@ - +
diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index c135fb909..2748b9ffc 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -133,7 +133,7 @@ - + Accelerate diff --git a/frontend/src/app/docs/api-docs/api-docs-data.ts b/frontend/src/app/docs/api-docs/api-docs-data.ts index b28f45195..5ce7c491f 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -155,6 +155,7 @@ export const restApiDocsData = [ previousRetarget: -4.807005268478962, nextRetargetHeight: 741888, timeAvg: 302328, + adjustedTimeAvg: 302328, timeOffset: 0 }` }, @@ -171,6 +172,7 @@ export const restApiDocsData = [ previousRetarget: -4.807005268478962, nextRetargetHeight: 741888, timeAvg: 302328, + adjustedTimeAvg: 302328, timeOffset: 0 }` }, @@ -187,6 +189,7 @@ export const restApiDocsData = [ previousRetarget: -4.807005268478962, nextRetargetHeight: 741888, timeAvg: 302328, + adjustedTimeAvg: 302328, timeOffset: 0 }` }, @@ -203,6 +206,7 @@ export const restApiDocsData = [ previousRetarget: -4.807005268478962, nextRetargetHeight: 741888, timeAvg: 302328, + adjustedTimeAvg: 302328, timeOffset: 0 }` } diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index eaeeca94c..6ef650e32 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -54,6 +54,7 @@ export interface DifficultyAdjustment { previousTime: number; nextRetargetHeight: number; timeAvg: number; + adjustedTimeAvg: number; timeOffset: number; expectedBlocks: number; }