Refactor Difficulty Adjustment calc + unit test it
This commit is contained in:
parent
bb1adf41e7
commit
a00eb2736b
@ -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]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,69 +2,96 @@ import config from '../config';
|
|||||||
import { IDifficultyAdjustment } from '../mempool.interfaces';
|
import { IDifficultyAdjustment } from '../mempool.interfaces';
|
||||||
import blocks from './blocks';
|
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 {
|
class DifficultyAdjustmentApi {
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
public getDifficultyAdjustment(): IDifficultyAdjustment {
|
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 DATime = blocks.getLastDifficultyAdjustmentTime();
|
||||||
const previousRetarget = blocks.getPreviousDifficultyRetarget();
|
const previousRetarget = blocks.getPreviousDifficultyRetarget();
|
||||||
const blockHeight = blocks.getCurrentBlockHeight();
|
const blockHeight = blocks.getCurrentBlockHeight();
|
||||||
const blocksCache = blocks.getBlocks();
|
const blocksCache = blocks.getBlocks();
|
||||||
const latestBlock = blocksCache[blocksCache.length - 1];
|
const latestBlock = blocksCache[blocksCache.length - 1];
|
||||||
|
|
||||||
const nowSeconds = Math.floor(new Date().getTime() / 1000);
|
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;
|
return calcDifficultyAdjustment(
|
||||||
// Only calculate the estimate once we have 7.2% of blocks in current epoch
|
DATime, nowSeconds, blockHeight, previousRetarget,
|
||||||
if (blocksInEpoch >= ESTIMATE_LAG_BLOCKS) {
|
config.MEMPOOL.NETWORK, latestBlock.timestamp
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user