Observer: one checkpoint_locked event per index (the poll claims the state before its first await; the FinalityLock notification path raced it and the live feed showed two locked lines 30 ms apart); a lock claimed by the notification gets its votes_seen from the next poll

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-04 19:34:57 +00:00
parent 58c466df09
commit 8e243dfd3e

View file

@ -512,7 +512,8 @@ async function tick(rpc) {
}
// ---------- Finality v2 (spec 03) ----------
const checkpointStates = new Map(); // index -> last state written
const checkpointStates = new Map(); // index -> last state written; set BEFORE any await, so the two lock paths never both record
const checkpointDetailed = new Set(); // indices written with votesSeen (the FinalityLock notification carries none; the poll fills it in once)
let finalitySupported = null; // null = unknown, false = the node has no finality RPC
let lastWeightsAt = 0;
let lastWeights = null;
@ -554,16 +555,22 @@ async function finalityTick(rpc) {
if (!certBackfillDone) backfillCertificate(rpc, cps).catch(e => log('certificate backfill failed', e.message));
for (const cp of cps.checkpoints || []) {
const prev = checkpointStates.get(cp.index);
if (prev !== cp.state || prev === undefined) {
await upsertCheckpoint(cp);
if (cp.state === 'locked' && prev !== 'locked') await recordLock(cp);
const changed = prev !== cp.state || prev === undefined;
const detail = cp.votesSeen !== undefined && !checkpointDetailed.has(cp.index);
if (changed || detail) {
// claim the transition before the first await: the FinalityLock notification handler checks this map synchronously,
// and until 4 October 2026 both paths recorded the same lock (two "locked" events 30 ms apart on the live feed)
checkpointStates.set(cp.index, cp.state);
if (cp.votesSeen !== undefined) checkpointDetailed.add(cp.index);
await upsertCheckpoint(cp);
if (changed && cp.state === 'locked' && prev !== 'locked') await recordLock(cp);
} else if (cp.state !== 'locked' && (cp.index % 1 === 0)) {
// vote counts move while a checkpoint is open: refresh it
await upsertCheckpoint(cp);
}
}
for (const k of checkpointStates.keys()) if (k + 500 < Number(cps.nextIndex)) checkpointStates.delete(k);
for (const k of checkpointDetailed) if (k + 500 < Number(cps.nextIndex)) checkpointDetailed.delete(k);
const now = Date.now();
if (now - lastWeightsAt > 10_000) {
lastWeightsAt = now;