Skip to content

Commit

Permalink
WIP: library
Browse files Browse the repository at this point in the history
  • Loading branch information
coolaj86 committed Aug 2, 2024
1 parent 4e2ff00 commit 2b1570e
Showing 1 changed file with 144 additions and 104 deletions.
248 changes: 144 additions & 104 deletions dashgov.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
/** @typedef {any} Gov - TODO */

/**
* @typedef Snapshot
* @prop {Uint53} block - the block to be used for calculation
* @prop {Uint53} ms - the time of that block in ms
*/

/**
* @typedef Estimate
* @prop {Uint53} secondsPerBlock
* @prop {Uint53} voteHeight
* @prop {Uint53} voteDelta
* @prop {String} voteIso - date in ISO format
* @prop {Uint53} voteMs
* @prop {Uint53} voteDeltaMs
* @prop {Uint53} superblockHeight
* @prop {Uint53} superblockDelta
* @prop {String} superblockIso - date in ISO format
* @prop {Uint53} superblockMs
* @prop {Uint53} superblockDeltaMs
*/

/**
* @typedef Estimates
* @prop {Estimate} last - the most recent superblock
* @prop {Estimate?} lameduck - the current voting period, if close to deadline
* @prop {Array<Estimate>} upcoming - future voting periods
*/

/** @type {Gov} */
//@ts-ignore
var DashGov = ("object" === typeof module && exports) || {};
Expand Down Expand Up @@ -182,131 +210,127 @@ var DashGov = ("object" === typeof module && exports) || {};
};

// TODO move to a nice place
const SUPERBLOCK_INTERVAL = 16616; // actual
const SUPERBLOCK_INTERVAL = 16616;
const VOTE_LEAD_BLOCKS = 1662;
const PROPOSAL_LEAD_MS = 6 * 24 * 60 * 60 * 1000;
DashGov.SUPERBLOCK_INTERVAL = SUPERBLOCK_INTERVAL;
const SECONDS_PER_BLOCK_ESTIMATE = 155; // estimate (measured as ~157.64)

const VOTE_OFFSET_BLOCKS = 1662; // actual
const VOTE_OFFSET_MS = VOTE_OFFSET_BLOCKS * SECONDS_PER_BLOCK_ESTIMATE * 1000; // estimate
// not used because the actual average at any time is always closer to 157.5
//const SECONDS_PER_BLOCK_ESTIMATE = 155;
DashGov._AVG_SECS_PER_BLOCK = 157.58166827154548;

// used to calculate ~5 year (~60 month) averages
const MONTHLY_SUPERBLOCK_01_DATE = "2017-03-05T20:16:00Z";
const MONTHLY_SUPERBLOCK_01 = 631408;
const MONTHLY_SUPERBLOCK_61_DATE = "2022-02-26T03:53:00Z";
const MONTHLY_SUPERBLOCK_61 = 1628368;
const ERR_INCOMPLETE_BLOCK =
"both block and time must be given for snapshot calculations";

/**
* @typedef Snapshot
* @prop {Uint53} block - the block to be used for calculation
* @prop {Uint53} ms - the time of that block in ms
*/

/**
* @param {Snapshot} [snapshot]
* @param {Snapshot} snapshot
* @returns {Float64} - fractional seconds
*/
GObj.measureSecondsPerBlock = function (snapshot) {
if (!snapshot) {
snapshot = { ms: 0, block: 0 };
}

if (!snapshot.ms) {
if (snapshot.block) {
throw new Error(ERR_INCOMPLETE_BLOCK);
}
let d = new Date(MONTHLY_SUPERBLOCK_61_DATE);
snapshot.ms = d.valueOf();
snapshot.block = MONTHLY_SUPERBLOCK_61;
} else {
if (!snapshot.block) {
throw new Error(ERR_INCOMPLETE_BLOCK);
}
}

let firstSuperblockDate = new Date(MONTHLY_SUPERBLOCK_01_DATE);
let blockDelta = snapshot.block - MONTHLY_SUPERBLOCK_01;
let timeDelta = snapshot.ms - firstSuperblockDate.valueOf();
let timeDelta = snapshot.ms - Date.parse(MONTHLY_SUPERBLOCK_01_DATE);
let msPerBlock = timeDelta / blockDelta;
let sPerBlock = msPerBlock / 1000;

// let sPerBlockF = sPerBlock.toFixed(1);
// sPerBlock = parseFloat(sPerBlockF);

return sPerBlock;
};

GObj.estimateFutureBlocks = function (
now = Date.now(),
currentBlock = 0,
secondsPerBlock = 0,
cycles = 3,
) {
if (!currentBlock) {
let d = new Date(MONTHLY_SUPERBLOCK_61_DATE);
let then = d.valueOf();
let spb = GObj.measureSecondsPerBlock({
/**
* @param {Snapshot} [snapshot] - defaults to monthly superblock 61
*/
GObj.estimateSecondsPerBlock = function (snapshot) {
if (!snapshot) {
snapshot = {
block: MONTHLY_SUPERBLOCK_61,
ms: then,
});
let delta = now - then;
let deltaS = delta / 1000;
let blocks = deltaS / spb;
currentBlock = MONTHLY_SUPERBLOCK_61 + blocks;
if (!secondsPerBlock) {
secondsPerBlock = spb;
}
ms: Date.parse(MONTHLY_SUPERBLOCK_61_DATE),
};
}

let spb = GObj.measureSecondsPerBlock(snapshot);
return spb;
};

/**
* @param {Uint53} ms - the current time
* @param {Float64} secondsPerBlock
*/
GObj.estimateBlockHeight = function (ms, secondsPerBlock) {
let then = Date.parse(MONTHLY_SUPERBLOCK_61_DATE);
let delta = ms - then;
let deltaS = delta / 1000;
let blocks = deltaS / secondsPerBlock;
blocks = Math.round(blocks);

let height = MONTHLY_SUPERBLOCK_61 + blocks;
return height;
};

/**
* Note: since we're dealing with estimates that are typically reliable
* within an hour (and often even within 15 minutes), this may
* generate more results than it presents.
* @param {Uint8} [cycles] - 3 by default
* @param {Snapshot?} [snapshot]
* @param {Uint32} [proposalLeadtime] - default 3 days in ms
* @param {Float64} [secondsPerBlock] - typically close to 157.6
* @returns {Estimates} - the last, due, and upcoming proposal cycles
*/
GObj.estimateProposalCycles = function (
cycles = 3,
snapshot = null,
proposalLeadtime = PROPOSAL_LEAD_MS,
secondsPerBlock = 0,
) {
let now = snapshot?.ms || Date.now();
let currentBlock = snapshot?.block;
if (!secondsPerBlock) {
secondsPerBlock = GObj.measureSecondsPerBlock({
block: currentBlock,
ms: now,
});
if (currentBlock) {
snapshot = { block: currentBlock, ms: now };
}
secondsPerBlock = GObj.measureSecondsPerBlock(snapshot);
}
if (!currentBlock) {
currentBlock = GObj.estimateBlockHeight(now, secondsPerBlock);
}

/** @type {Array<Estimate>} */
let estimates = [];
for (let i = -1; i < cycles; i += 1) {
let estimate = GObj.estimateNthGovCycle(
for (let i = 0; i <= cycles + 1; i += 1) {
let estimate = GObj.estimateNthNextGovCycle(
{ block: currentBlock, ms: now },
secondsPerBlock,
i,
);
estimates.push(estimate);
}

return estimates;
};

/**
* @typedef Estimate
* @prop {Uint53} secondsPerBlock
* @prop {Uint53} voteHeight
* @prop {Uint53} voteDelta
* @prop {String} voteIso - date in ISO format
* @prop {Uint53} voteMs
* @prop {Uint53} voteDeltaMs
* @prop {Uint53} superblockHeight
* @prop {Uint53} superblockDelta
* @prop {String} superblockIso - date in ISO format
* @prop {Uint53} superblockMs
* @prop {Uint53} superblockDeltaMs
*/

/**
* @param {Uint53} height
* @param {Uint53} offset - 0 (current / previous), 1 (next), 2, 3, nth
* @returns {Uint53} - the superblock after the given height
*/
GObj.getNthNextSuperblock = function (height, offset) {
let superblockCount = height / SUPERBLOCK_INTERVAL;
superblockCount = Math.floor(superblockCount);

superblockCount += offset;
let superblockHeight = superblockCount * SUPERBLOCK_INTERVAL;
{
/** @type {Estimate} */
//@ts-ignore - we know there is at least one (past) estimate
let last = estimates.shift();

/** @type {Estimate?} */
let lameduck = null;
if (estimates.length) {
if (estimates[0].voteDeltaMs < proposalLeadtime) {
//@ts-ignore - we just checked the length
lameduck = estimates.shift();
} else {
// lose the extra cycle
void estimates.pop();
}
}
let upcoming = estimates;

return superblockHeight;
return {
last,
lameduck,
upcoming,
};
}
};

/**
Expand All @@ -315,38 +339,39 @@ var DashGov = ("object" === typeof module && exports) || {};
* @param {Uint53} offset - how many superblocks in the future
* @returns {Estimate} - details about the current governance cycle
*/
GObj.estimateNthGovCycle = function (
{ block, ms },
GObj.estimateNthNextGovCycle = function (
snapshot,
secondsPerBlock,
offset = 0,
) {
let blockOffset = offset * SUPERBLOCK_INTERVAL;
let blockTarget = block + blockOffset;
let superblockHeight = GObj.getNthNextSuperblock(blockTarget, offset);
if (!secondsPerBlock) {
secondsPerBlock = GObj.estimateSecondsPerBlock(snapshot);
}

let superblockHeight = GObj.getNthNextSuperblock(snapshot.block, offset);

let superblockDelta = superblockHeight - block;
// let superblockDeltaMs = superblockDelta * SECONDS_PER_BLOCK_ESTIMATE * 1000;
let superblockDelta = superblockHeight - snapshot.block;
let superblockDeltaMs = superblockDelta * secondsPerBlock * 1000;
let voteDeltaMs = VOTE_LEAD_BLOCKS * secondsPerBlock * 1000;

console.log(ms, superblockDeltaMs);
let d = new Date(ms);
let d = new Date(snapshot.ms);
d.setUTCMilliseconds(0);

d.setUTCMilliseconds(superblockDeltaMs);
let sbms = d.valueOf();
let sbts = d.toISOString();

d.setUTCMilliseconds(-VOTE_OFFSET_MS);
d.setUTCMilliseconds(-voteDeltaMs);
let vtms = d.valueOf();
let vtts = d.toISOString();

return {
secondsPerBlock: secondsPerBlock,
voteHeight: superblockHeight - VOTE_OFFSET_BLOCKS,
// TODO split into objects
voteHeight: superblockHeight - VOTE_LEAD_BLOCKS,
voteIso: vtts,
voteMs: vtms,
voteDelta: superblockDelta - VOTE_OFFSET_BLOCKS,
voteDeltaMs: superblockDeltaMs - VOTE_OFFSET_MS,
voteDelta: superblockDelta - VOTE_LEAD_BLOCKS,
voteDeltaMs: superblockDeltaMs - voteDeltaMs,
superblockHeight: superblockHeight,
superblockDelta: superblockDelta,
superblockIso: sbts,
Expand All @@ -355,6 +380,21 @@ var DashGov = ("object" === typeof module && exports) || {};
};
};

/**
* @param {Uint53} height
* @param {Uint53} offset - 0 (current / previous), 1 (next), 2, 3, nth
* @returns {Uint53} - the superblock after the given height
*/
GObj.getNthNextSuperblock = function (height, offset) {
let superblockCount = height / SUPERBLOCK_INTERVAL;
superblockCount = Math.floor(superblockCount);

superblockCount += offset;
let superblockHeight = superblockCount * SUPERBLOCK_INTERVAL;

return superblockHeight;
};

//@ts-ignore
window.DashGov = GObj;
})(globalThis.window || {}, DashGov);
Expand Down

0 comments on commit 2b1570e

Please sign in to comment.