Skip to content

Commit 2b1570e

Browse files
committed
WIP: library
1 parent 4e2ff00 commit 2b1570e

File tree

1 file changed

+144
-104
lines changed

1 file changed

+144
-104
lines changed

dashgov.js

Lines changed: 144 additions & 104 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,127 @@ 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;
186216
DashGov.SUPERBLOCK_INTERVAL = SUPERBLOCK_INTERVAL;
187-
const SECONDS_PER_BLOCK_ESTIMATE = 155; // estimate (measured as ~157.64)
188217

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

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

199228
/**
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]
229+
* @param {Snapshot} snapshot
207230
* @returns {Float64} - fractional seconds
208231
*/
209232
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);
228233
let blockDelta = snapshot.block - MONTHLY_SUPERBLOCK_01;
229-
let timeDelta = snapshot.ms - firstSuperblockDate.valueOf();
234+
let timeDelta = snapshot.ms - Date.parse(MONTHLY_SUPERBLOCK_01_DATE);
230235
let msPerBlock = timeDelta / blockDelta;
231236
let sPerBlock = msPerBlock / 1000;
232237

233-
// let sPerBlockF = sPerBlock.toFixed(1);
234-
// sPerBlock = parseFloat(sPerBlockF);
235-
236238
return sPerBlock;
237239
};
238240

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({
241+
/**
242+
* @param {Snapshot} [snapshot] - defaults to monthly superblock 61
243+
*/
244+
GObj.estimateSecondsPerBlock = function (snapshot) {
245+
if (!snapshot) {
246+
snapshot = {
249247
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-
}
248+
ms: Date.parse(MONTHLY_SUPERBLOCK_61_DATE),
249+
};
259250
}
260251

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

268299
/** @type {Array<Estimate>} */
269300
let estimates = [];
270-
for (let i = -1; i < cycles; i += 1) {
271-
let estimate = GObj.estimateNthGovCycle(
301+
for (let i = 0; i <= cycles + 1; i += 1) {
302+
let estimate = GObj.estimateNthNextGovCycle(
272303
{ block: currentBlock, ms: now },
273304
secondsPerBlock,
274305
i,
275306
);
276307
estimates.push(estimate);
277308
}
278309

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;
310+
{
311+
/** @type {Estimate} */
312+
//@ts-ignore - we know there is at least one (past) estimate
313+
let last = estimates.shift();
314+
315+
/** @type {Estimate?} */
316+
let lameduck = null;
317+
if (estimates.length) {
318+
if (estimates[0].voteDeltaMs < proposalLeadtime) {
319+
//@ts-ignore - we just checked the length
320+
lameduck = estimates.shift();
321+
} else {
322+
// lose the extra cycle
323+
void estimates.pop();
324+
}
325+
}
326+
let upcoming = estimates;
308327

309-
return superblockHeight;
328+
return {
329+
last,
330+
lameduck,
331+
upcoming,
332+
};
333+
}
310334
};
311335

312336
/**
@@ -315,38 +339,39 @@ var DashGov = ("object" === typeof module && exports) || {};
315339
* @param {Uint53} offset - how many superblocks in the future
316340
* @returns {Estimate} - details about the current governance cycle
317341
*/
318-
GObj.estimateNthGovCycle = function (
319-
{ block, ms },
342+
GObj.estimateNthNextGovCycle = function (
343+
snapshot,
320344
secondsPerBlock,
321345
offset = 0,
322346
) {
323-
let blockOffset = offset * SUPERBLOCK_INTERVAL;
324-
let blockTarget = block + blockOffset;
325-
let superblockHeight = GObj.getNthNextSuperblock(blockTarget, offset);
347+
if (!secondsPerBlock) {
348+
secondsPerBlock = GObj.estimateSecondsPerBlock(snapshot);
349+
}
350+
351+
let superblockHeight = GObj.getNthNextSuperblock(snapshot.block, offset);
326352

327-
let superblockDelta = superblockHeight - block;
328-
// let superblockDeltaMs = superblockDelta * SECONDS_PER_BLOCK_ESTIMATE * 1000;
353+
let superblockDelta = superblockHeight - snapshot.block;
329354
let superblockDeltaMs = superblockDelta * secondsPerBlock * 1000;
355+
let voteDeltaMs = VOTE_LEAD_BLOCKS * secondsPerBlock * 1000;
330356

331-
console.log(ms, superblockDeltaMs);
332-
let d = new Date(ms);
357+
let d = new Date(snapshot.ms);
333358
d.setUTCMilliseconds(0);
334359

335360
d.setUTCMilliseconds(superblockDeltaMs);
336361
let sbms = d.valueOf();
337362
let sbts = d.toISOString();
338363

339-
d.setUTCMilliseconds(-VOTE_OFFSET_MS);
364+
d.setUTCMilliseconds(-voteDeltaMs);
340365
let vtms = d.valueOf();
341366
let vtts = d.toISOString();
342367

343368
return {
344-
secondsPerBlock: secondsPerBlock,
345-
voteHeight: superblockHeight - VOTE_OFFSET_BLOCKS,
369+
// TODO split into objects
370+
voteHeight: superblockHeight - VOTE_LEAD_BLOCKS,
346371
voteIso: vtts,
347372
voteMs: vtms,
348-
voteDelta: superblockDelta - VOTE_OFFSET_BLOCKS,
349-
voteDeltaMs: superblockDeltaMs - VOTE_OFFSET_MS,
373+
voteDelta: superblockDelta - VOTE_LEAD_BLOCKS,
374+
voteDeltaMs: superblockDeltaMs - voteDeltaMs,
350375
superblockHeight: superblockHeight,
351376
superblockDelta: superblockDelta,
352377
superblockIso: sbts,
@@ -355,6 +380,21 @@ var DashGov = ("object" === typeof module && exports) || {};
355380
};
356381
};
357382

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

0 commit comments

Comments
 (0)