Skip to content

Commit da8be53

Browse files
authored
Merge pull request #386 from frstrtr/dashboard/pplns-parse-finite-total
web(pplns): clamp totalPrimary so overflowing finite-amount sums stay finite
2 parents 8b1ee9f + 5c765a3 commit da8be53

9 files changed

Lines changed: 57 additions & 24 deletions

File tree

web-static/sharechain-explorer/dist/pplns-view.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web-static/sharechain-explorer/dist/pplns-view.js.map

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web-static/sharechain-explorer/dist/sharechain-explorer.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web-static/sharechain-explorer/dist/sharechain-explorer.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web-static/sharechain-explorer/dist/shared-core.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web-static/sharechain-explorer/dist/shared-core.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"version": "0.1.0-alpha",
3-
"generated": "2026-04-20T11:31:29.618Z",
3+
"generated": "2026-06-23T20:03:41.175Z",
44
"files": [
55
"pplns-view.js",
66
"sharechain-explorer.js",
@@ -12,19 +12,19 @@
1212
"shared-core.js.map"
1313
],
1414
"integrity": {
15-
"pplns-view.js": "sha384-0VM4qQvoTorQ+CIVNmSffMMclp0lC+s8gufF8cTgGc0rSKrwRxhovNEjD9tCT1E6",
16-
"pplns-view.js.map": "sha384-+HmQQn0hfXMRrFXthc3U59CKNKB+cECK9YuUySdhFqfG+feAMeSk/CABTqFkOZoG",
17-
"sharechain-explorer.js": "sha384-NSA2iUIBBicV40Ui5pj0YE0yuSsh2AZexj92FvpdEqxSs/3m832k8Xmy9abGgWJF",
18-
"sharechain-explorer.js.map": "sha384-8gKhY+rVZ5SYmZNZ8q7hhsnU9sAabkGehUWcdoLK2ygS4Iwt/AoLj4gYulGNsYuv",
19-
"shared-core.js": "sha384-fLK/ciO7Ip2VNtUazZBKnenRgTfVKRjVYUCGQCnZgovE6Y/l+jpxBZkziKl21EBw",
20-
"shared-core.js.map": "sha384-cNe7eMGecoXitiz9msnxaJ9mEZUWLhnGDyhpk9Xe4UOjKZ909m17gYWKJo6yTEwT"
15+
"pplns-view.js": "sha384-6e2Mhmz6cUZqdwrPYB2ZRKlz0cOd/iTvibG6aY7N0WK3em99c69vwabsaRw0k4RZ",
16+
"pplns-view.js.map": "sha384-cOkK3Ps9z0WoqfND7NTmANSnCOXCSam3sPjMgdlg8wCXXV2waXlGMvSN3vJlvK4p",
17+
"sharechain-explorer.js": "sha384-36U2+DJ4qtenJNwOd9HDd+PoLyraP5y4BIZyV/y+VwfA0/K04hUv9ybHhSf2904n",
18+
"sharechain-explorer.js.map": "sha384-TvmBt1Ekl48fkyPOBTmt3uxeO3LRAJUH2ppSStqFRu/tc/zLncnMPdVnBMAxl9mj",
19+
"shared-core.js": "sha384-shFs3b7dkb/ooHhtaqwIZwSuYwFwk9VonRrrBg8nUSyS7Sqfbc1PDX5+ufap/Wvz",
20+
"shared-core.js.map": "sha384-A1CSDMTBmnHbS8vHRdtNIHb8kZN2GTgdWz/aTce3xNY6eAmWiTW0yN8hCpign5gM"
2121
},
2222
"sizes": {
23-
"pplns-view.js": 31019,
24-
"pplns-view.js.map": 117518,
25-
"sharechain-explorer.js": 58175,
26-
"sharechain-explorer.js.map": 273462,
27-
"shared-core.js": 25700,
28-
"shared-core.js.map": 113705
23+
"pplns-view.js": 31660,
24+
"pplns-view.js.map": 122139,
25+
"sharechain-explorer.js": 58166,
26+
"sharechain-explorer.js.map": 273452,
27+
"shared-core.js": 25691,
28+
"shared-core.js.map": 113695
2929
}
3030
}

web-static/sharechain-explorer/src/pplns/parse.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ function num(v: unknown, fallback = 0): number {
4949
return typeof v === 'number' && Number.isFinite(v) ? v : fallback;
5050
}
5151

52+
// A sum of individually-finite per-miner amounts can still overflow to
53+
// +/-Infinity when values approach Number.MAX_VALUE (each passes the
54+
// amount>0 guard, but their aggregate does not). Clamp so totalPrimary
55+
// stays finite at the server->client trust boundary. Regression:
56+
// tests/unit/pplns-parse-properties.test.ts overflow case (seed 1831858192).
57+
function finiteTotal(sum: number): number {
58+
return Number.isFinite(sum) ? sum : Number.MAX_VALUE;
59+
}
60+
5261
function str(v: unknown): string | undefined {
5362
return typeof v === 'string' && v.length > 0 ? v : undefined;
5463
}
@@ -119,7 +128,7 @@ function parseNewShape(obj: Record<string, unknown>): PplnsSnapshot {
119128
const snap: PplnsSnapshot = {
120129
totalPrimary: totalPrimary > 0
121130
? totalPrimary
122-
: miners.reduce((s, m) => s + m.amount, 0),
131+
: finiteTotal(miners.reduce((s, m) => s + m.amount, 0)),
123132
mergedChains,
124133
mergedTotals,
125134
schemaVersion: str(obj.schema_version) ?? '1.0',
@@ -258,7 +267,7 @@ function parseLegacyShape(obj: Record<string, unknown>): PplnsSnapshot {
258267
entries.push({ address: addr, amount, merged });
259268
}
260269

261-
const totalPrimary = entries.reduce((s, e) => s + e.amount, 0);
270+
const totalPrimary = finiteTotal(entries.reduce((s, e) => s + e.amount, 0));
262271
entries.sort((a, b) => b.amount - a.amount);
263272

264273
const miners: PplnsMiner[] = entries.map((e) => ({

web-static/sharechain-explorer/tests/unit/pplns-parse-properties.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,30 @@ test('parseSnapshot: non-finite amounts are dropped, total stays finite', () =>
300300
}
301301
});
302302

303+
test('parseSnapshot: finite amounts whose SUM overflows stay finite', () => {
304+
// Each amount is finite and passes the amount>0 guard, but the
305+
// aggregate overflows to +Infinity. totalPrimary must be clamped
306+
// finite (finiteTotal). Shrunk counterexample: seed 1831858192,
307+
// raw = [8.98e292, 1.797e308] (legacy bare-number / array path).
308+
const big = 1.797693134862315e+308; // just under Number.MAX_VALUE
309+
for (const raw of [
310+
[8.98128139290624e+292, big], // legacy array path
311+
{ a: big, b: big }, // legacy dict path
312+
{ total_primary: 0, // new-shape fallback sum
313+
miners: [{ address: 'a', amount: big, pct: 0 },
314+
{ address: 'b', amount: big, pct: 0 }] },
315+
]) {
316+
const snap = parseSnapshot(raw);
317+
assert.ok(Number.isFinite(snap.totalPrimary),
318+
`totalPrimary overflowed for ${JSON.stringify(raw).slice(0, 40)}`);
319+
// Per-miner pct must remain a finite number in [0,1].
320+
for (const m of snap.miners) {
321+
assert.ok(Number.isFinite(m.pct));
322+
assert.ok(m.pct >= 0 && m.pct <= 1.0000001);
323+
}
324+
}
325+
});
326+
303327
test('parseSnapshot: non-finite legacy-object amounts are dropped', () => {
304328
// Legacy { addr: { amount } } shape with a non-finite amount field.
305329
for (const bad of [Infinity, -Infinity, NaN]) {

0 commit comments

Comments
 (0)