Skip to content

Commit 9da4db4

Browse files
committed
WIP: library
1 parent 92b03b8 commit 9da4db4

File tree

1 file changed

+147
-106
lines changed

1 file changed

+147
-106
lines changed

dashgov.js

Lines changed: 147 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,33 @@
11
/** @typedef {any} Gov - TODO */
22

3+
/**
4+
* @typedef Snapshot
5+
* @prop {Uint53} block - the block to be used for calculation
6+
* @prop {Uint53} ms - the time of that block in ms
7+
*/
8+
9+
/**
10+
* @typedef Estimate
11+
* @prop {Uint53} secondsPerBlock
12+
* @prop {Uint53} voteHeight
13+
* @prop {Uint53} voteDelta
14+
* @prop {String} voteIso - date in ISO format
15+
* @prop {Uint53} voteMs
16+
* @prop {Uint53} voteDeltaMs
17+
* @prop {Uint53} superblockHeight
18+
* @prop {Uint53} superblockDelta
19+
* @prop {String} superblockIso - date in ISO format
20+
* @prop {Uint53} superblockMs
21+
* @prop {Uint53} superblockDeltaMs
22+
*/
23+
24+
/**
25+
* @typedef Estimates
26+
* @prop {Estimate} last - the most recent superblock
27+
* @prop {Estimate?} lameduck - the current voting period, if close to deadline
28+
* @prop {Array<Estimate>} upcoming - future voting periods
29+
*/
30+
331
/** @type {Gov} */
432
//@ts-ignore
533
var DashGov = ("object" === typeof module && exports) || {};
@@ -182,131 +210,128 @@ var DashGov = ("object" === typeof module && exports) || {};
182210
};
183211

184212
// TODO move to a nice place
185-
const SUPERBLOCK_INTERVAL = 16616; // actual
213+
const SUPERBLOCK_INTERVAL = 16616;
214+
const VOTE_LEAD_BLOCKS = 1662;
215+
const PROPOSAL_LEAD_MS = 6 * 24 * 60 * 60 * 1000;
216+
DashGov.PROPOSAL_LEAD_MS = PROPOSAL_LEAD_MS;
186217
DashGov.SUPERBLOCK_INTERVAL = SUPERBLOCK_INTERVAL;
187-
const SECONDS_PER_BLOCK_ESTIMATE = 155; // estimate (measured as ~157.64)
188218

189-
const VOTE_OFFSET_BLOCKS = 1662; // actual
190-
const VOTE_OFFSET_MS = VOTE_OFFSET_BLOCKS * SECONDS_PER_BLOCK_ESTIMATE * 1000; // estimate
219+
// not used because the actual average at any time is always closer to 157.5
220+
//const SECONDS_PER_BLOCK_ESTIMATE = 155;
221+
DashGov._AVG_SECS_PER_BLOCK = 157.5816652623977;
191222

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

199229
/**
200-
* @typedef Snapshot
201-
* @prop {Uint53} block - the block to be used for calculation
202-
* @prop {Uint53} ms - the time of that block in ms
203-
*/
204-
205-
/**
206-
* @param {Snapshot} [snapshot]
230+
* @param {Snapshot} snapshot
207231
* @returns {Float64} - fractional seconds
208232
*/
209233
GObj.measureSecondsPerBlock = function (snapshot) {
210-
if (!snapshot) {
211-
snapshot = { ms: 0, block: 0 };
212-
}
213-
214-
if (!snapshot.ms) {
215-
if (snapshot.block) {
216-
throw new Error(ERR_INCOMPLETE_BLOCK);
217-
}
218-
let d = new Date(MONTHLY_SUPERBLOCK_61_DATE);
219-
snapshot.ms = d.valueOf();
220-
snapshot.block = MONTHLY_SUPERBLOCK_61;
221-
} else {
222-
if (!snapshot.block) {
223-
throw new Error(ERR_INCOMPLETE_BLOCK);
224-
}
225-
}
226-
227-
let firstSuperblockDate = new Date(MONTHLY_SUPERBLOCK_01_DATE);
228234
let blockDelta = snapshot.block - MONTHLY_SUPERBLOCK_01;
229-
let timeDelta = snapshot.ms - firstSuperblockDate.valueOf();
235+
let timeDelta = snapshot.ms - Date.parse(MONTHLY_SUPERBLOCK_01_DATE);
230236
let msPerBlock = timeDelta / blockDelta;
231237
let sPerBlock = msPerBlock / 1000;
232238

233-
// let sPerBlockF = sPerBlock.toFixed(1);
234-
// sPerBlock = parseFloat(sPerBlockF);
235-
236239
return sPerBlock;
237240
};
238241

239-
GObj.estimateFutureBlocks = function (
240-
now = Date.now(),
241-
currentBlock = 0,
242-
secondsPerBlock = 0,
243-
cycles = 3,
244-
) {
245-
if (!currentBlock) {
246-
let d = new Date(MONTHLY_SUPERBLOCK_61_DATE);
247-
let then = d.valueOf();
248-
let spb = GObj.measureSecondsPerBlock({
242+
/**
243+
* @param {Snapshot} [snapshot] - defaults to monthly superblock 61
244+
*/
245+
GObj.estimateSecondsPerBlock = function (snapshot) {
246+
if (!snapshot) {
247+
snapshot = {
249248
block: MONTHLY_SUPERBLOCK_61,
250-
ms: then,
251-
});
252-
let delta = now - then;
253-
let deltaS = delta / 1000;
254-
let blocks = deltaS / spb;
255-
currentBlock = MONTHLY_SUPERBLOCK_61 + blocks;
256-
if (!secondsPerBlock) {
257-
secondsPerBlock = spb;
258-
}
249+
ms: Date.parse(MONTHLY_SUPERBLOCK_61_DATE),
250+
};
259251
}
260252

253+
let spb = GObj.measureSecondsPerBlock(snapshot);
254+
return spb;
255+
};
256+
257+
/**
258+
* @param {Uint53} ms - the current time
259+
* @param {Float64} secondsPerBlock
260+
*/
261+
GObj.estimateBlockHeight = function (ms, secondsPerBlock) {
262+
let then = Date.parse(MONTHLY_SUPERBLOCK_61_DATE);
263+
let delta = ms - then;
264+
let deltaS = delta / 1000;
265+
let blocks = deltaS / secondsPerBlock;
266+
blocks = Math.round(blocks);
267+
268+
let height = MONTHLY_SUPERBLOCK_61 + blocks;
269+
return height;
270+
};
271+
272+
/**
273+
* Note: since we're dealing with estimates that are typically reliable
274+
* within an hour (and often even within 15 minutes), this may
275+
* generate more results than it presents.
276+
* @param {Uint8} [cycles] - 3 by default
277+
* @param {Snapshot?} [snapshot]
278+
* @param {Uint32} [proposalLeadtime] - default 3 days in ms
279+
* @param {Float64} [secondsPerBlock] - typically close to 157.6
280+
* @returns {Estimates} - the last, due, and upcoming proposal cycles
281+
*/
282+
GObj.estimateProposalCycles = function (
283+
cycles = 3,
284+
snapshot = null,
285+
secondsPerBlock = 0,
286+
proposalLeadtime = PROPOSAL_LEAD_MS,
287+
) {
288+
let now = snapshot?.ms || Date.now();
289+
let currentBlock = snapshot?.block;
261290
if (!secondsPerBlock) {
262-
secondsPerBlock = GObj.measureSecondsPerBlock({
263-
block: currentBlock,
264-
ms: now,
265-
});
291+
if (currentBlock) {
292+
snapshot = { block: currentBlock, ms: now };
293+
}
294+
secondsPerBlock = GObj.measureSecondsPerBlock(snapshot);
295+
}
296+
if (!currentBlock) {
297+
currentBlock = GObj.estimateBlockHeight(now, secondsPerBlock);
266298
}
267299

268300
/** @type {Array<Estimate>} */
269301
let estimates = [];
270-
for (let i = -1; i < cycles; i += 1) {
271-
let estimate = GObj.estimateNthGovCycle(
302+
for (let i = 0; i <= cycles + 1; i += 1) {
303+
let estimate = GObj.estimateNthNextGovCycle(
272304
{ block: currentBlock, ms: now },
273305
secondsPerBlock,
274306
i,
275307
);
276308
estimates.push(estimate);
277309
}
278310

279-
return estimates;
280-
};
281-
282-
/**
283-
* @typedef Estimate
284-
* @prop {Uint53} secondsPerBlock
285-
* @prop {Uint53} voteHeight
286-
* @prop {Uint53} voteDelta
287-
* @prop {String} voteIso - date in ISO format
288-
* @prop {Uint53} voteMs
289-
* @prop {Uint53} voteDeltaMs
290-
* @prop {Uint53} superblockHeight
291-
* @prop {Uint53} superblockDelta
292-
* @prop {String} superblockIso - date in ISO format
293-
* @prop {Uint53} superblockMs
294-
* @prop {Uint53} superblockDeltaMs
295-
*/
296-
297-
/**
298-
* @param {Uint53} height
299-
* @param {Uint53} offset - 0 (current / previous), 1 (next), 2, 3, nth
300-
* @returns {Uint53} - the superblock after the given height
301-
*/
302-
GObj.getNthNextSuperblock = function (height, offset) {
303-
let superblockCount = height / SUPERBLOCK_INTERVAL;
304-
superblockCount = Math.floor(superblockCount);
305-
306-
superblockCount += offset;
307-
let superblockHeight = superblockCount * SUPERBLOCK_INTERVAL;
311+
{
312+
/** @type {Estimate} */
313+
//@ts-ignore - we know there is at least one (past) estimate
314+
let last = estimates.shift();
315+
316+
/** @type {Estimate?} */
317+
let lameduck = null;
318+
if (estimates.length) {
319+
if (estimates[0].voteDeltaMs < proposalLeadtime) {
320+
//@ts-ignore - we just checked the length
321+
lameduck = estimates.shift();
322+
} else {
323+
// lose the extra cycle
324+
void estimates.pop();
325+
}
326+
}
327+
let upcoming = estimates;
308328

309-
return superblockHeight;
329+
return {
330+
last,
331+
lameduck,
332+
upcoming,
333+
};
334+
}
310335
};
311336

312337
/**
@@ -315,38 +340,39 @@ var DashGov = ("object" === typeof module && exports) || {};
315340
* @param {Uint53} offset - how many superblocks in the future
316341
* @returns {Estimate} - details about the current governance cycle
317342
*/
318-
GObj.estimateNthGovCycle = function (
319-
{ block, ms },
343+
GObj.estimateNthNextGovCycle = function (
344+
snapshot,
320345
secondsPerBlock,
321346
offset = 0,
322347
) {
323-
let blockOffset = offset * SUPERBLOCK_INTERVAL;
324-
let blockTarget = block + blockOffset;
325-
let superblockHeight = GObj.getNthNextSuperblock(blockTarget, offset);
348+
if (!secondsPerBlock) {
349+
secondsPerBlock = GObj.estimateSecondsPerBlock(snapshot);
350+
}
351+
352+
let superblockHeight = GObj.getNthNextSuperblock(snapshot.block, offset);
326353

327-
let superblockDelta = superblockHeight - block;
328-
// let superblockDeltaMs = superblockDelta * SECONDS_PER_BLOCK_ESTIMATE * 1000;
354+
let superblockDelta = superblockHeight - snapshot.block;
329355
let superblockDeltaMs = superblockDelta * secondsPerBlock * 1000;
356+
let voteDeltaMs = VOTE_LEAD_BLOCKS * secondsPerBlock * 1000;
330357

331-
console.log(ms, superblockDeltaMs);
332-
let d = new Date(ms);
358+
let d = new Date(snapshot.ms);
333359
d.setUTCMilliseconds(0);
334360

335361
d.setUTCMilliseconds(superblockDeltaMs);
336362
let sbms = d.valueOf();
337363
let sbts = d.toISOString();
338364

339-
d.setUTCMilliseconds(-VOTE_OFFSET_MS);
365+
d.setUTCMilliseconds(-voteDeltaMs);
340366
let vtms = d.valueOf();
341367
let vtts = d.toISOString();
342368

343369
return {
344-
secondsPerBlock: secondsPerBlock,
345-
voteHeight: superblockHeight - VOTE_OFFSET_BLOCKS,
370+
// TODO split into objects
371+
voteHeight: superblockHeight - VOTE_LEAD_BLOCKS,
346372
voteIso: vtts,
347373
voteMs: vtms,
348-
voteDelta: superblockDelta - VOTE_OFFSET_BLOCKS,
349-
voteDeltaMs: superblockDeltaMs - VOTE_OFFSET_MS,
374+
voteDelta: superblockDelta - VOTE_LEAD_BLOCKS,
375+
voteDeltaMs: superblockDeltaMs - voteDeltaMs,
350376
superblockHeight: superblockHeight,
351377
superblockDelta: superblockDelta,
352378
superblockIso: sbts,
@@ -355,6 +381,21 @@ var DashGov = ("object" === typeof module && exports) || {};
355381
};
356382
};
357383

384+
/**
385+
* @param {Uint53} height
386+
* @param {Uint53} offset - 0 (current / previous), 1 (next), 2, 3, nth
387+
* @returns {Uint53} - the superblock after the given height
388+
*/
389+
GObj.getNthNextSuperblock = function (height, offset) {
390+
let superblockCount = height / SUPERBLOCK_INTERVAL;
391+
superblockCount = Math.floor(superblockCount);
392+
393+
superblockCount += offset;
394+
let superblockHeight = superblockCount * SUPERBLOCK_INTERVAL;
395+
396+
return superblockHeight;
397+
};
398+
358399
//@ts-ignore
359400
window.DashGov = GObj;
360401
})(globalThis.window || {}, DashGov);

0 commit comments

Comments
 (0)