Skip to content

Commit b9ee40a

Browse files
authored
Merge pull request #293
Direct merge requested by repository maintainer
2 parents db25de1 + 6d98763 commit b9ee40a

2 files changed

Lines changed: 67 additions & 3 deletions

File tree

packages/tlock/src/freshness.test.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { test } from "node:test";
33
import assert from "node:assert/strict";
44

5-
import { classifyDrandRound, DEFAULT_STALE_THRESHOLD_MS } from "./freshness.js";
5+
import { classifyDrandRound, computePublishAtMs, DEFAULT_STALE_THRESHOLD_MS } from "./freshness.js";
66

77
test("freshness: missing or invalid round returns unknown", () => {
88
const info = { genesis_time: 1677685200, period: 3 };
@@ -76,3 +76,32 @@ test("freshness: stale round", () => {
7676
// Custom threshold stale
7777
assert.equal(classifyDrandRound(round, info, 1030011, 10).status, "stale");
7878
});
79+
80+
test("computePublishAtMs rejects unsafe round and period combinations", () => {
81+
const info = { genesis_time: 1_000_000_000, period: 3 };
82+
83+
assert.equal(computePublishAtMs(info, Number.MAX_SAFE_INTEGER), null);
84+
assert.equal(
85+
computePublishAtMs({ genesis_time: Number.MAX_SAFE_INTEGER, period: 2 }, 1_000),
86+
null,
87+
);
88+
assert.equal(computePublishAtMs({ genesis_time: 0, period: 3 }, 1), 3000);
89+
});
90+
91+
test("freshness: unsafe timestamp math returns unknown near MAX_SAFE_INTEGER", () => {
92+
const info = { genesis_time: Number.MAX_SAFE_INTEGER - 1, period: 2 };
93+
const round = 2;
94+
const now = 1_700_000_000_000;
95+
96+
const res = classifyDrandRound(round, info, now);
97+
assert.equal(res.status, "unknown");
98+
assert.match(String(res.reason ?? ""), /overflow|unsafe/i);
99+
});
100+
101+
test("freshness: valid boundary round still classifies correctly", () => {
102+
const info = { genesis_time: 1000, period: 1 };
103+
const round = 1_000_000;
104+
const publishAtMs = computePublishAtMs(info, round);
105+
assert.equal(publishAtMs, 1_001_000_000);
106+
assert.equal(classifyDrandRound(round, info, publishAtMs!).status, "fresh");
107+
});

packages/tlock/src/freshness.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,39 @@ export interface FreshnessResult {
1515
ageMs?: number;
1616
}
1717

18+
/**
19+
* Derives a Drand round publication timestamp in milliseconds, rejecting
20+
* intermediate calculations that would exceed Number.MAX_SAFE_INTEGER.
21+
*/
22+
export function computePublishAtMs(info: DrandRoundInfo, round: number): number | null {
23+
if (!Number.isSafeInteger(round) || round <= 0) {
24+
return null;
25+
}
26+
if (!Number.isSafeInteger(info.period) || info.period <= 0) {
27+
return null;
28+
}
29+
if (!Number.isSafeInteger(info.genesis_time) || info.genesis_time < 0) {
30+
return null;
31+
}
32+
33+
const offsetSeconds = info.period * round;
34+
if (!Number.isSafeInteger(offsetSeconds)) {
35+
return null;
36+
}
37+
38+
const publishAtSeconds = info.genesis_time + offsetSeconds;
39+
if (!Number.isSafeInteger(publishAtSeconds) || publishAtSeconds < 0) {
40+
return null;
41+
}
42+
43+
const publishAtMs = publishAtSeconds * 1000;
44+
if (!Number.isSafeInteger(publishAtMs)) {
45+
return null;
46+
}
47+
48+
return publishAtMs;
49+
}
50+
1851
/**
1952
* Classifies a Drand round's freshness deterministically using the current time
2053
* and Drand network info.
@@ -41,8 +74,10 @@ export function classifyDrandRound(
4174
return { status: "unknown", reason: "invalid timestamp" };
4275
}
4376

44-
// Compute publish time matching the existing keeper logic convention.
45-
const publishAtMs = (info.genesis_time + info.period * round) * 1000;
77+
const publishAtMs = computePublishAtMs(info, round);
78+
if (publishAtMs == null) {
79+
return { status: "unknown", reason: "timestamp overflow or unsafe calculation" };
80+
}
4681

4782
if (nowMs < publishAtMs) {
4883
return {

0 commit comments

Comments
 (0)