From 772765959b2713899d478712a82581b7f1f8f1ed Mon Sep 17 00:00:00 2001 From: junderw Date: Fri, 19 Aug 2022 22:00:47 +0900 Subject: [PATCH 1/7] Fix Difficulty API (REST) --- backend/src/api/difficulty-adjustment.ts | 36 ++++++++++--------- .../difficulty/difficulty.component.html | 2 +- .../difficulty/difficulty.component.ts | 4 +-- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/backend/src/api/difficulty-adjustment.ts b/backend/src/api/difficulty-adjustment.ts index 1f85fdb80..37a0e7a8d 100644 --- a/backend/src/api/difficulty-adjustment.ts +++ b/backend/src/api/difficulty-adjustment.ts @@ -6,33 +6,37 @@ class DifficultyAdjustmentApi { constructor() { } public getDifficultyAdjustment(): IDifficultyAdjustment { + const ESTIMATE_LAG_BLOCKS = 146; // For first 7.2% of epoch, don't estimate. + const EPOCH_BLOCK_LENGTH = 2016; + const DATime = blocks.getLastDifficultyAdjustmentTime(); const previousRetarget = blocks.getPreviousDifficultyRetarget(); const blockHeight = blocks.getCurrentBlockHeight(); const blocksCache = blocks.getBlocks(); const latestBlock = blocksCache[blocksCache.length - 1]; - const now = new Date().getTime() / 1000; - const diff = now - DATime; - const blocksInEpoch = blockHeight % 2016; - const progressPercent = (blocksInEpoch >= 0) ? blocksInEpoch / 2016 * 100 : 100; - const remainingBlocks = 2016 - blocksInEpoch; - const nextRetargetHeight = blockHeight + remainingBlocks; + const nowSeconds = Math.floor(new Date().getTime() / 1000); + const diffSeconds = nowSeconds - DATime; + const blocksInEpoch = (blockHeight >= 0) ? blockHeight % EPOCH_BLOCK_LENGTH : 0; + const progressPercent = (blockHeight >= 0) ? blocksInEpoch / EPOCH_BLOCK_LENGTH * 100 : 100; + const remainingBlocks = EPOCH_BLOCK_LENGTH - blocksInEpoch; + const nextRetargetHeight = (blockHeight >= 0) ? blockHeight + remainingBlocks : 0; let difficultyChange = 0; - if (remainingBlocks < 1870) { - if (blocksInEpoch > 0) { - difficultyChange = (600 / (diff / blocksInEpoch) - 1) * 100; - } + // Only calculate the estimate once we have 7.2% of blocks in current epoch + if (blocksInEpoch >= ESTIMATE_LAG_BLOCKS) { + difficultyChange = (600 / (diffSeconds / blocksInEpoch) - 1) * 100; + // Max increase is x4 (+300%) if (difficultyChange > 300) { difficultyChange = 300; } + // Max decrease is /4 (-75%) if (difficultyChange < -75) { difficultyChange = -75; } } - let timeAvgMins = blocksInEpoch && blocksInEpoch > 146 ? diff / blocksInEpoch / 60 : 10; + let timeAvgMins = blocksInEpoch >= ESTIMATE_LAG_BLOCKS ? diffSeconds / blocksInEpoch / 60 : 10; // Testnet difficulty is set to 1 after 20 minutes of no blocks, // therefore the time between blocks will always be below 20 minutes (1200s). @@ -41,14 +45,14 @@ class DifficultyAdjustmentApi { if (timeAvgMins > 20) { timeAvgMins = 20; } - if (now - latestBlock.timestamp + timeAvgMins * 60 > 1200) { - timeOffset = -Math.min(now - latestBlock.timestamp, 1200) * 1000; + if (nowSeconds - latestBlock.timestamp + timeAvgMins * 60 > 1200) { + timeOffset = -Math.min(nowSeconds - latestBlock.timestamp, 1200) * 1000; } } - const timeAvg = timeAvgMins * 60 * 1000 ; - const remainingTime = (remainingBlocks * timeAvg) + (now * 1000); - const estimatedRetargetDate = remainingTime + now; + const timeAvg = Math.floor(timeAvgMins * 60 * 1000); + const remainingTime = remainingBlocks * timeAvg; + const estimatedRetargetDate = remainingTime + nowSeconds * 1000; return { progressPercent, diff --git a/frontend/src/app/components/difficulty/difficulty.component.html b/frontend/src/app/components/difficulty/difficulty.component.html index 3684b8de4..e030f74fa 100644 --- a/frontend/src/app/components/difficulty/difficulty.component.html +++ b/frontend/src/app/components/difficulty/difficulty.component.html @@ -10,7 +10,7 @@ {{ i }} blocks {{ i }} block -
+
Estimate
diff --git a/frontend/src/app/components/difficulty/difficulty.component.ts b/frontend/src/app/components/difficulty/difficulty.component.ts index 5d969bf1e..76a996acc 100644 --- a/frontend/src/app/components/difficulty/difficulty.component.ts +++ b/frontend/src/app/components/difficulty/difficulty.component.ts @@ -11,7 +11,7 @@ interface EpochProgress { newDifficultyHeight: number; colorAdjustments: string; colorPreviousAdjustments: string; - remainingTime: number; + estimatedRetargetDate: number; previousRetarget: number; blocksUntilHalving: number; timeUntilHalving: number; @@ -74,7 +74,7 @@ export class DifficultyComponent implements OnInit { colorAdjustments, colorPreviousAdjustments, newDifficultyHeight: da.nextRetargetHeight, - remainingTime: da.remainingTime, + estimatedRetargetDate: da.estimatedRetargetDate, previousRetarget: da.previousRetarget, blocksUntilHalving, timeUntilHalving, From bb1adf41e788c02d949c13ac80a2f43560e5669b Mon Sep 17 00:00:00 2001 From: junderw Date: Fri, 19 Aug 2022 22:01:04 +0900 Subject: [PATCH 2/7] Update API docs for Difficulty API (REST) --- .../src/app/docs/api-docs/api-docs-data.ts | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) 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 f8f0e23b8..1b02ceceb 100644 --- a/frontend/src/app/docs/api-docs/api-docs-data.ts +++ b/frontend/src/app/docs/api-docs/api-docs-data.ts @@ -114,11 +114,14 @@ export const restApiDocsData = [ curl: [], response: `{ progressPercent: 44.397234501112074, - difficultyChange: 0.9845932018381687, - estimatedRetargetDate: 1627762478.9111245, + difficultyChange: 98.45932018381687, + estimatedRetargetDate: 1627762478, remainingBlocks: 1121, - remainingTime: 665977.6261244365, - previousRetarget: -4.807005268478962 + remainingTime: 665977, + previousRetarget: -4.807005268478962, + nextRetargetHeight: 741888, + timeAvg: 302328, + timeOffset: 0 }` }, codeSampleTestnet: { @@ -127,11 +130,14 @@ export const restApiDocsData = [ curl: [], response: `{ progressPercent: 44.397234501112074, - difficultyChange: 0.9845932018381687, - estimatedRetargetDate: 1627762478.9111245, + difficultyChange: 98.45932018381687, + estimatedRetargetDate: 1627762478, remainingBlocks: 1121, - remainingTime: 665977.6261244365, - previousRetarget: -4.807005268478962 + remainingTime: 665977, + previousRetarget: -4.807005268478962, + nextRetargetHeight: 741888, + timeAvg: 302328, + timeOffset: 0 }` }, codeSampleSignet: { @@ -140,11 +146,14 @@ export const restApiDocsData = [ curl: [], response: `{ progressPercent: 44.397234501112074, - difficultyChange: 0.9845932018381687, - estimatedRetargetDate: 1627762478.9111245, + difficultyChange: 98.45932018381687, + estimatedRetargetDate: 1627762478, remainingBlocks: 1121, - remainingTime: 665977.6261244365, - previousRetarget: -4.807005268478962 + remainingTime: 665977, + previousRetarget: -4.807005268478962, + nextRetargetHeight: 741888, + timeAvg: 302328, + timeOffset: 0 }` }, codeSampleLiquid: { @@ -153,11 +162,14 @@ export const restApiDocsData = [ curl: [], response: `{ progressPercent: 44.397234501112074, - difficultyChange: 0.9845932018381687, - estimatedRetargetDate: 1627762478.9111245, + difficultyChange: 98.45932018381687, + estimatedRetargetDate: 1627762478, remainingBlocks: 1121, - remainingTime: 665977.6261244365, - previousRetarget: -4.807005268478962 + remainingTime: 665977, + previousRetarget: -4.807005268478962, + nextRetargetHeight: 741888, + timeAvg: 302328, + timeOffset: 0 }` } } From a00eb2736b6a52c548691d03f66a55712fd254c8 Mon Sep 17 00:00:00 2001 From: junderw Date: Fri, 19 Aug 2022 23:48:59 +0900 Subject: [PATCH 3/7] Refactor Difficulty Adjustment calc + unit test it --- backend/src/__tests__/config.test.ts | 58 ++++++++++ backend/src/api/difficulty-adjustment.ts | 131 ++++++++++++++--------- 2 files changed, 137 insertions(+), 52 deletions(-) diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts index 9453505f4..b469d3e07 100644 --- a/backend/src/__tests__/config.test.ts +++ b/backend/src/__tests__/config.test.ts @@ -137,4 +137,62 @@ describe('Mempool Backend Config', () => { }); }); + test('should calculate Difficulty Adjustments properly', () => { + jest.isolateModules(() => { + const { calcDifficultyAdjustment } = jest.requireActual('../api/difficulty-adjustment'); + const dt = dtString => Math.floor(new Date(dtString).getTime() / 1000); + const vectors = [ + [ // Vector 1 + [ // Inputs + dt('2022-08-18T11:07:00.000Z'), // Last DA time (in seconds) + dt('2022-08-19T14:03:53.000Z'), // Current time (now) (in seconds) + 750134, // Current block height + 0.6280047707459726, // Previous retarget % (Passed through) + 'mainnet', // Network (if testnet, next value is non-zero) + 0, // If not testnet, not used + ], + { // Expected Result + progressPercent: 9.027777777777777, + difficultyChange: 12.562233927411782, + estimatedRetargetDate: 1661895424692, + remainingBlocks: 1834, + remainingTime: 977591692, + previousRetarget: 0.6280047707459726, + nextRetargetHeight: 751968, + timeAvg: 533038, + timeOffset: 0, + }, + ], + [ // Vector 2 (testnet) + [ // Inputs + dt('2022-08-18T11:07:00.000Z'), // Last DA time (in seconds) + dt('2022-08-19T14:03:53.000Z'), // Current time (now) (in seconds) + 750134, // Current block height + 0.6280047707459726, // Previous retarget % (Passed through) + 'testnet', // Network + dt('2022-08-19T13:52:46.000Z'), // Latest block timestamp in seconds + ], + { // Expected Result is same other than timeOffset + progressPercent: 9.027777777777777, + difficultyChange: 12.562233927411782, + estimatedRetargetDate: 1661895424692, + remainingBlocks: 1834, + remainingTime: 977591692, + 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 + }, + ], + ] as [[number, number, number, number, string, number], any][]; + + for (const vector of vectors) { + const result = calcDifficultyAdjustment(...vector[0]); + // previousRetarget is passed through untouched + expect(result.previousRetarget).toStrictEqual(vector[0][3]); + expect(result).toStrictEqual(vector[1]); + } + }); + }); }); diff --git a/backend/src/api/difficulty-adjustment.ts b/backend/src/api/difficulty-adjustment.ts index 37a0e7a8d..31d525342 100644 --- a/backend/src/api/difficulty-adjustment.ts +++ b/backend/src/api/difficulty-adjustment.ts @@ -2,69 +2,96 @@ import config from '../config'; import { IDifficultyAdjustment } from '../mempool.interfaces'; import blocks from './blocks'; +export interface DifficultyAdjustment { + progressPercent: number; // Percent: 0 to 100 + difficultyChange: number; // Percent: -75 to 300 + estimatedRetargetDate: number; // Unix time in ms + remainingBlocks: number; // Block count + remainingTime: number; // Duration of time in ms + previousRetarget: number; // Percent: -75 to 300 + nextRetargetHeight: number; // Block Height + timeAvg: number; // Duration of time in ms + timeOffset: number; // (Testnet) Time since last block (cap @ 20min) in ms +} + +export function calcDifficultyAdjustment( + DATime: number, + nowSeconds: number, + blockHeight: number, + previousRetarget: number, + network: string, + latestBlockTimestamp: number, +): DifficultyAdjustment { + const ESTIMATE_LAG_BLOCKS = 146; // For first 7.2% of epoch, don't estimate. + const EPOCH_BLOCK_LENGTH = 2016; + + const diffSeconds = nowSeconds - DATime; + const blocksInEpoch = (blockHeight >= 0) ? blockHeight % EPOCH_BLOCK_LENGTH : 0; + const progressPercent = (blockHeight >= 0) ? blocksInEpoch / EPOCH_BLOCK_LENGTH * 100 : 100; + const remainingBlocks = EPOCH_BLOCK_LENGTH - blocksInEpoch; + const nextRetargetHeight = (blockHeight >= 0) ? blockHeight + remainingBlocks : 0; + + let difficultyChange = 0; + // Only calculate the estimate once we have 7.2% of blocks in current epoch + if (blocksInEpoch >= ESTIMATE_LAG_BLOCKS) { + difficultyChange = (600 / (diffSeconds / blocksInEpoch) - 1) * 100; + // Max increase is x4 (+300%) + if (difficultyChange > 300) { + difficultyChange = 300; + } + // Max decrease is /4 (-75%) + if (difficultyChange < -75) { + difficultyChange = -75; + } + } + + let timeAvgMins = blocksInEpoch >= ESTIMATE_LAG_BLOCKS ? diffSeconds / blocksInEpoch / 60 : 10; + + // Testnet difficulty is set to 1 after 20 minutes of no blocks, + // therefore the time between blocks will always be below 20 minutes (1200s). + let timeOffset = 0; + if (network === 'testnet') { + if (timeAvgMins > 20) { + timeAvgMins = 20; + } + + if (nowSeconds - latestBlockTimestamp + timeAvgMins * 60 > 1200) { + timeOffset = -Math.min(nowSeconds - latestBlockTimestamp, 1200) * 1000; + } + } + + const timeAvg = Math.floor(timeAvgMins * 60 * 1000); + const remainingTime = remainingBlocks * timeAvg; + const estimatedRetargetDate = remainingTime + nowSeconds * 1000; + + return { + progressPercent, + difficultyChange, + estimatedRetargetDate, + remainingBlocks, + remainingTime, + previousRetarget, + nextRetargetHeight, + timeAvg, + timeOffset, + }; +} + class DifficultyAdjustmentApi { constructor() { } public getDifficultyAdjustment(): IDifficultyAdjustment { - const ESTIMATE_LAG_BLOCKS = 146; // For first 7.2% of epoch, don't estimate. - const EPOCH_BLOCK_LENGTH = 2016; - const DATime = blocks.getLastDifficultyAdjustmentTime(); const previousRetarget = blocks.getPreviousDifficultyRetarget(); const blockHeight = blocks.getCurrentBlockHeight(); const blocksCache = blocks.getBlocks(); const latestBlock = blocksCache[blocksCache.length - 1]; - const nowSeconds = Math.floor(new Date().getTime() / 1000); - const diffSeconds = nowSeconds - DATime; - const blocksInEpoch = (blockHeight >= 0) ? blockHeight % EPOCH_BLOCK_LENGTH : 0; - const progressPercent = (blockHeight >= 0) ? blocksInEpoch / EPOCH_BLOCK_LENGTH * 100 : 100; - const remainingBlocks = EPOCH_BLOCK_LENGTH - blocksInEpoch; - const nextRetargetHeight = (blockHeight >= 0) ? blockHeight + remainingBlocks : 0; - let difficultyChange = 0; - // Only calculate the estimate once we have 7.2% of blocks in current epoch - if (blocksInEpoch >= ESTIMATE_LAG_BLOCKS) { - difficultyChange = (600 / (diffSeconds / blocksInEpoch) - 1) * 100; - // Max increase is x4 (+300%) - if (difficultyChange > 300) { - difficultyChange = 300; - } - // Max decrease is /4 (-75%) - if (difficultyChange < -75) { - difficultyChange = -75; - } - } - - let timeAvgMins = blocksInEpoch >= ESTIMATE_LAG_BLOCKS ? diffSeconds / blocksInEpoch / 60 : 10; - - // Testnet difficulty is set to 1 after 20 minutes of no blocks, - // therefore the time between blocks will always be below 20 minutes (1200s). - let timeOffset = 0; - if (config.MEMPOOL.NETWORK === 'testnet') { - if (timeAvgMins > 20) { - timeAvgMins = 20; - } - if (nowSeconds - latestBlock.timestamp + timeAvgMins * 60 > 1200) { - timeOffset = -Math.min(nowSeconds - latestBlock.timestamp, 1200) * 1000; - } - } - - const timeAvg = Math.floor(timeAvgMins * 60 * 1000); - const remainingTime = remainingBlocks * timeAvg; - const estimatedRetargetDate = remainingTime + nowSeconds * 1000; - - return { - progressPercent, - difficultyChange, - estimatedRetargetDate, - remainingBlocks, - remainingTime, - previousRetarget, - nextRetargetHeight, - timeAvg, - timeOffset, - }; + return calcDifficultyAdjustment( + DATime, nowSeconds, blockHeight, previousRetarget, + config.MEMPOOL.NETWORK, latestBlock.timestamp + ); } } From 1bc2c1816792f2c52429122d73318dd986e9068d Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 20 Aug 2022 09:35:47 +0900 Subject: [PATCH 4/7] Fix tests --- backend/jest.config.ts | 7 ++- .../api/difficulty-adjustment.test.ts | 60 +++++++++++++++++++ backend/src/__tests__/config.test.ts | 59 ------------------ backend/testSetup.ts | 1 + 4 files changed, 66 insertions(+), 61 deletions(-) create mode 100644 backend/src/__tests__/api/difficulty-adjustment.test.ts create mode 100644 backend/testSetup.ts diff --git a/backend/jest.config.ts b/backend/jest.config.ts index 5576bfe80..14f932f98 100644 --- a/backend/jest.config.ts +++ b/backend/jest.config.ts @@ -7,11 +7,14 @@ const config: Config.InitialOptions = { automock: false, collectCoverage: true, collectCoverageFrom: ["./src/**/**.ts"], - coverageProvider: "v8", + coverageProvider: "babel", coverageThreshold: { global: { lines: 1 } - } + }, + setupFiles: [ + "./testSetup.ts", + ], } export default config; diff --git a/backend/src/__tests__/api/difficulty-adjustment.test.ts b/backend/src/__tests__/api/difficulty-adjustment.test.ts new file mode 100644 index 000000000..4cee25c2f --- /dev/null +++ b/backend/src/__tests__/api/difficulty-adjustment.test.ts @@ -0,0 +1,60 @@ +import { calcDifficultyAdjustment, DifficultyAdjustment } from '../../api/difficulty-adjustment'; + +test('should calculate Difficulty Adjustments properly', () => { + const dt = (dtString) => { + return Math.floor(new Date(dtString).getTime() / 1000); + }; + + const vectors = [ + [ // Vector 1 + [ // Inputs + dt('2022-08-18T11:07:00.000Z'), // Last DA time (in seconds) + dt('2022-08-19T14:03:53.000Z'), // Current time (now) (in seconds) + 750134, // Current block height + 0.6280047707459726, // Previous retarget % (Passed through) + 'mainnet', // Network (if testnet, next value is non-zero) + 0, // If not testnet, not used + ], + { // Expected Result + progressPercent: 9.027777777777777, + difficultyChange: 12.562233927411782, + estimatedRetargetDate: 1661895424692, + remainingBlocks: 1834, + remainingTime: 977591692, + previousRetarget: 0.6280047707459726, + nextRetargetHeight: 751968, + timeAvg: 533038, + timeOffset: 0, + }, + ], + [ // Vector 2 (testnet) + [ // Inputs + dt('2022-08-18T11:07:00.000Z'), // Last DA time (in seconds) + dt('2022-08-19T14:03:53.000Z'), // Current time (now) (in seconds) + 750134, // Current block height + 0.6280047707459726, // Previous retarget % (Passed through) + 'testnet', // Network + dt('2022-08-19T13:52:46.000Z'), // Latest block timestamp in seconds + ], + { // Expected Result is same other than timeOffset + progressPercent: 9.027777777777777, + difficultyChange: 12.562233927411782, + estimatedRetargetDate: 1661895424692, + remainingBlocks: 1834, + remainingTime: 977591692, + 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 + }, + ], + ] as [[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).toStrictEqual(vector[1]); + } +}); diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts index b469d3e07..7314fde6f 100644 --- a/backend/src/__tests__/config.test.ts +++ b/backend/src/__tests__/config.test.ts @@ -136,63 +136,4 @@ describe('Mempool Backend Config', () => { expect(config.EXTERNAL_DATA_SERVER).toStrictEqual(fixture.EXTERNAL_DATA_SERVER); }); }); - - test('should calculate Difficulty Adjustments properly', () => { - jest.isolateModules(() => { - const { calcDifficultyAdjustment } = jest.requireActual('../api/difficulty-adjustment'); - const dt = dtString => Math.floor(new Date(dtString).getTime() / 1000); - const vectors = [ - [ // Vector 1 - [ // Inputs - dt('2022-08-18T11:07:00.000Z'), // Last DA time (in seconds) - dt('2022-08-19T14:03:53.000Z'), // Current time (now) (in seconds) - 750134, // Current block height - 0.6280047707459726, // Previous retarget % (Passed through) - 'mainnet', // Network (if testnet, next value is non-zero) - 0, // If not testnet, not used - ], - { // Expected Result - progressPercent: 9.027777777777777, - difficultyChange: 12.562233927411782, - estimatedRetargetDate: 1661895424692, - remainingBlocks: 1834, - remainingTime: 977591692, - previousRetarget: 0.6280047707459726, - nextRetargetHeight: 751968, - timeAvg: 533038, - timeOffset: 0, - }, - ], - [ // Vector 2 (testnet) - [ // Inputs - dt('2022-08-18T11:07:00.000Z'), // Last DA time (in seconds) - dt('2022-08-19T14:03:53.000Z'), // Current time (now) (in seconds) - 750134, // Current block height - 0.6280047707459726, // Previous retarget % (Passed through) - 'testnet', // Network - dt('2022-08-19T13:52:46.000Z'), // Latest block timestamp in seconds - ], - { // Expected Result is same other than timeOffset - progressPercent: 9.027777777777777, - difficultyChange: 12.562233927411782, - estimatedRetargetDate: 1661895424692, - remainingBlocks: 1834, - remainingTime: 977591692, - 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 - }, - ], - ] as [[number, number, number, number, string, number], any][]; - - for (const vector of vectors) { - const result = calcDifficultyAdjustment(...vector[0]); - // previousRetarget is passed through untouched - expect(result.previousRetarget).toStrictEqual(vector[0][3]); - expect(result).toStrictEqual(vector[1]); - } - }); - }); }); diff --git a/backend/testSetup.ts b/backend/testSetup.ts new file mode 100644 index 000000000..dda44ef6e --- /dev/null +++ b/backend/testSetup.ts @@ -0,0 +1 @@ +jest.mock('./mempool-config.json', () => ({}), { virtual: true }); From d700b5f1450127cf4cf7e4c0d5f15b56e5cc1568 Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 20 Aug 2022 09:46:18 +0900 Subject: [PATCH 5/7] Fix test setup --- .../api/difficulty-adjustment.test.ts | 108 +++++++++--------- backend/testSetup.ts | 4 + 2 files changed, 59 insertions(+), 53 deletions(-) diff --git a/backend/src/__tests__/api/difficulty-adjustment.test.ts b/backend/src/__tests__/api/difficulty-adjustment.test.ts index 4cee25c2f..eb774d445 100644 --- a/backend/src/__tests__/api/difficulty-adjustment.test.ts +++ b/backend/src/__tests__/api/difficulty-adjustment.test.ts @@ -1,60 +1,62 @@ import { calcDifficultyAdjustment, DifficultyAdjustment } from '../../api/difficulty-adjustment'; -test('should calculate Difficulty Adjustments properly', () => { - const dt = (dtString) => { - return Math.floor(new Date(dtString).getTime() / 1000); - }; +describe('Mempool Difficulty Adjustment', () => { + test('should calculate Difficulty Adjustments properly', () => { + const dt = (dtString) => { + return Math.floor(new Date(dtString).getTime() / 1000); + }; - const vectors = [ - [ // Vector 1 - [ // Inputs - dt('2022-08-18T11:07:00.000Z'), // Last DA time (in seconds) - dt('2022-08-19T14:03:53.000Z'), // Current time (now) (in seconds) - 750134, // Current block height - 0.6280047707459726, // Previous retarget % (Passed through) - 'mainnet', // Network (if testnet, next value is non-zero) - 0, // If not testnet, not used + const vectors = [ + [ // Vector 1 + [ // Inputs + dt('2022-08-18T11:07:00.000Z'), // Last DA time (in seconds) + dt('2022-08-19T14:03:53.000Z'), // Current time (now) (in seconds) + 750134, // Current block height + 0.6280047707459726, // Previous retarget % (Passed through) + 'mainnet', // Network (if testnet, next value is non-zero) + 0, // If not testnet, not used + ], + { // Expected Result + progressPercent: 9.027777777777777, + difficultyChange: 12.562233927411782, + estimatedRetargetDate: 1661895424692, + remainingBlocks: 1834, + remainingTime: 977591692, + previousRetarget: 0.6280047707459726, + nextRetargetHeight: 751968, + timeAvg: 533038, + timeOffset: 0, + }, ], - { // Expected Result - progressPercent: 9.027777777777777, - difficultyChange: 12.562233927411782, - estimatedRetargetDate: 1661895424692, - remainingBlocks: 1834, - remainingTime: 977591692, - previousRetarget: 0.6280047707459726, - nextRetargetHeight: 751968, - timeAvg: 533038, - timeOffset: 0, - }, - ], - [ // Vector 2 (testnet) - [ // Inputs - dt('2022-08-18T11:07:00.000Z'), // Last DA time (in seconds) - dt('2022-08-19T14:03:53.000Z'), // Current time (now) (in seconds) - 750134, // Current block height - 0.6280047707459726, // Previous retarget % (Passed through) - 'testnet', // Network - dt('2022-08-19T13:52:46.000Z'), // Latest block timestamp in seconds + [ // Vector 2 (testnet) + [ // Inputs + dt('2022-08-18T11:07:00.000Z'), // Last DA time (in seconds) + dt('2022-08-19T14:03:53.000Z'), // Current time (now) (in seconds) + 750134, // Current block height + 0.6280047707459726, // Previous retarget % (Passed through) + 'testnet', // Network + dt('2022-08-19T13:52:46.000Z'), // Latest block timestamp in seconds + ], + { // Expected Result is same other than timeOffset + progressPercent: 9.027777777777777, + difficultyChange: 12.562233927411782, + estimatedRetargetDate: 1661895424692, + remainingBlocks: 1834, + remainingTime: 977591692, + 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 + }, ], - { // Expected Result is same other than timeOffset - progressPercent: 9.027777777777777, - difficultyChange: 12.562233927411782, - estimatedRetargetDate: 1661895424692, - remainingBlocks: 1834, - remainingTime: 977591692, - 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 - }, - ], - ] as [[number, number, number, number, string, number], DifficultyAdjustment][]; + ] as [[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).toStrictEqual(vector[1]); - } + for (const vector of vectors) { + const result = calcDifficultyAdjustment(...vector[0]); + // previousRetarget is passed through untouched + expect(result.previousRetarget).toStrictEqual(vector[0][3]); + expect(result).toStrictEqual(vector[1]); + } + }); }); diff --git a/backend/testSetup.ts b/backend/testSetup.ts index dda44ef6e..ca51bbbe6 100644 --- a/backend/testSetup.ts +++ b/backend/testSetup.ts @@ -1 +1,5 @@ jest.mock('./mempool-config.json', () => ({}), { virtual: true }); +jest.mock('./src/logger.ts', () => ({}), { virtual: true }); +jest.mock('./src/api/rbf-cache.ts', () => ({}), { virtual: true }); +jest.mock('./src/api/mempool.ts', () => ({}), { virtual: true }); +jest.mock('./src/api/memory-cache.ts', () => ({}), { virtual: true }); From 87c9f881c06e6f5b4210c672647c106411521a23 Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 20 Aug 2022 11:24:48 +0900 Subject: [PATCH 6/7] Refactor difficulty API logic --- backend/src/api/difficulty-adjustment.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/src/api/difficulty-adjustment.ts b/backend/src/api/difficulty-adjustment.ts index 31d525342..ce023931d 100644 --- a/backend/src/api/difficulty-adjustment.ts +++ b/backend/src/api/difficulty-adjustment.ts @@ -23,7 +23,8 @@ export function calcDifficultyAdjustment( latestBlockTimestamp: number, ): DifficultyAdjustment { const ESTIMATE_LAG_BLOCKS = 146; // For first 7.2% of epoch, don't estimate. - const EPOCH_BLOCK_LENGTH = 2016; + const EPOCH_BLOCK_LENGTH = 2016; // Bitcoin mainnet + const BLOCK_SECONDS_TARGET = 600; // Bitcoin mainnet const diffSeconds = nowSeconds - DATime; const blocksInEpoch = (blockHeight >= 0) ? blockHeight % EPOCH_BLOCK_LENGTH : 0; @@ -32,9 +33,12 @@ export function calcDifficultyAdjustment( const nextRetargetHeight = (blockHeight >= 0) ? blockHeight + remainingBlocks : 0; let difficultyChange = 0; + let timeAvgMins = 10; // Only calculate the estimate once we have 7.2% of blocks in current epoch if (blocksInEpoch >= ESTIMATE_LAG_BLOCKS) { - difficultyChange = (600 / (diffSeconds / blocksInEpoch) - 1) * 100; + const secondsPerBlock = diffSeconds / blocksInEpoch; + timeAvgMins = secondsPerBlock / 60; + difficultyChange = (BLOCK_SECONDS_TARGET / secondsPerBlock - 1) * 100; // Max increase is x4 (+300%) if (difficultyChange > 300) { difficultyChange = 300; @@ -45,8 +49,6 @@ export function calcDifficultyAdjustment( } } - let timeAvgMins = blocksInEpoch >= ESTIMATE_LAG_BLOCKS ? diffSeconds / blocksInEpoch / 60 : 10; - // Testnet difficulty is set to 1 after 20 minutes of no blocks, // therefore the time between blocks will always be below 20 minutes (1200s). let timeOffset = 0; From 3a4982c5e65f7d40718144033dadb196d1c9dcb2 Mon Sep 17 00:00:00 2001 From: junderw Date: Sat, 20 Aug 2022 13:02:22 +0900 Subject: [PATCH 7/7] Refactor Difficulty API logic --- backend/src/api/difficulty-adjustment.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/backend/src/api/difficulty-adjustment.ts b/backend/src/api/difficulty-adjustment.ts index ce023931d..43c6e463f 100644 --- a/backend/src/api/difficulty-adjustment.ts +++ b/backend/src/api/difficulty-adjustment.ts @@ -25,6 +25,7 @@ export function calcDifficultyAdjustment( const ESTIMATE_LAG_BLOCKS = 146; // For first 7.2% of epoch, don't estimate. const EPOCH_BLOCK_LENGTH = 2016; // Bitcoin mainnet const BLOCK_SECONDS_TARGET = 600; // Bitcoin mainnet + const TESTNET_MAX_BLOCK_SECONDS = 1200; // Bitcoin testnet const diffSeconds = nowSeconds - DATime; const blocksInEpoch = (blockHeight >= 0) ? blockHeight % EPOCH_BLOCK_LENGTH : 0; @@ -33,12 +34,11 @@ export function calcDifficultyAdjustment( const nextRetargetHeight = (blockHeight >= 0) ? blockHeight + remainingBlocks : 0; let difficultyChange = 0; - let timeAvgMins = 10; + let timeAvgSecs = BLOCK_SECONDS_TARGET; // Only calculate the estimate once we have 7.2% of blocks in current epoch if (blocksInEpoch >= ESTIMATE_LAG_BLOCKS) { - const secondsPerBlock = diffSeconds / blocksInEpoch; - timeAvgMins = secondsPerBlock / 60; - difficultyChange = (BLOCK_SECONDS_TARGET / secondsPerBlock - 1) * 100; + timeAvgSecs = diffSeconds / blocksInEpoch; + difficultyChange = (BLOCK_SECONDS_TARGET / timeAvgSecs - 1) * 100; // Max increase is x4 (+300%) if (difficultyChange > 300) { difficultyChange = 300; @@ -53,16 +53,17 @@ export function calcDifficultyAdjustment( // therefore the time between blocks will always be below 20 minutes (1200s). let timeOffset = 0; if (network === 'testnet') { - if (timeAvgMins > 20) { - timeAvgMins = 20; + if (timeAvgSecs > TESTNET_MAX_BLOCK_SECONDS) { + timeAvgSecs = TESTNET_MAX_BLOCK_SECONDS; } - if (nowSeconds - latestBlockTimestamp + timeAvgMins * 60 > 1200) { - timeOffset = -Math.min(nowSeconds - latestBlockTimestamp, 1200) * 1000; + const secondsSinceLastBlock = nowSeconds - latestBlockTimestamp; + if (secondsSinceLastBlock + timeAvgSecs > TESTNET_MAX_BLOCK_SECONDS) { + timeOffset = -Math.min(secondsSinceLastBlock, TESTNET_MAX_BLOCK_SECONDS) * 1000; } } - const timeAvg = Math.floor(timeAvgMins * 60 * 1000); + const timeAvg = Math.floor(timeAvgSecs * 1000); const remainingTime = remainingBlocks * timeAvg; const estimatedRetargetDate = remainingTime + nowSeconds * 1000;