core/tracker: fix inclusion trim underflow at chain start - #4662
Conversation
The inclusion checker's slot arithmetic underflows on fresh chains: before genesis the wall-clock offset is negative, during the first InclCheckLag slots the checked slot goes negative, and until wall slot InclCheckLag+InclMissedLag the trim slot goes negative. The trim underflow makes Trim(huge) delete every pending submission and report each fresh duty as "duty not included on-chain", failing all early proposals on every fresh-genesis network. Skip ticks until a slot is old enough to check, and only trim once a slot can actually be declared missed.
c73fb7f to
cc9adfe
Compare
There was a problem hiding this comment.
Pull request overview
Fixes unsigned-arithmetic underflows in the tracker inclusion checker at/near genesis so fresh-chain networks don’t incorrectly trim all pending submissions and mark early duties as “not included on-chain”.
Changes:
- Add a chain-start guard to skip inclusion checks until
sinceGenesisis large enough to compute a non-underflowing checked slot. - Guard trimming so
slot-InclMissedLagcannot underflow and trigger mass deletion/false misses. - Add a regression test covering the trim-underflow behavior at chain start.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| core/tracker/inclusion.go | Adds chain-start guards to prevent underflow in checked-slot and trim-slot computations. |
| core/tracker/inclusion_internal_test.go | Adds a regression test to ensure early-chain submissions are not falsely reported missed due to trim underflow. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #4662 +/- ##
==========================================
+ Coverage 58.35% 58.45% +0.09%
==========================================
Files 247 247
Lines 34094 34098 +4
==========================================
+ Hits 19896 19931 +35
+ Misses 11723 11684 -39
- Partials 2475 2483 +8 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The zero-valued checkedSlot skipped slot 0 as already-checked; start from a MaxUint64 sentinel instead. Synchronize the chain-start regression test on the first inclusion check rather than sleeping, and pin it to the genesis slot so it covers both guards.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (2)
core/tracker/inclusion.go:612
- With the new early-continue guard, the first eligible computed slot can be 0 (when wall slot == InclCheckLag). Since checkedSlot defaults to 0, the
if checkedSlot == slotcheck will skip processing slot 0 entirely, delaying inclusion checking by one slot and potentially never checking slot 0.
// Skip until a slot is old enough to check: the unsigned
// arithmetic below underflows before genesis (negative elapsed
// time) and during the first InclCheckLag slots.
sinceGenesis := time.Since(a.genesis)
if sinceGenesis < a.slotDuration*InclCheckLag {
continue
}
slot := uint64(sinceGenesis/a.slotDuration) - InclCheckLag
core/tracker/inclusion_internal_test.go:635
- This regression test can pass without actually exercising the buggy path if
Runreturns early (e.g., FetchSlotsConfig failure), and the fixedtime.Sleepmakes the test more timing-flaky. Prefer waiting for an observedcheckBlockFunccall and for the Run goroutine to exit deterministically (e.g., via channels + require.Eventually) instead of sleeping.
}
// A pending proposal from slot 3: too recent to be declared missed.
block := testutil.RandomDenebVersionedSignedProposal()
coreBlock, err := core.NewVersionedSignedProposal(block)
require.NoError(t, err)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
core/tracker/inclusion_internal_test.go:595
- This test constructs an InclusionChecker without setting checkBlockAndAttsFunc. If the attestation_inclusion feature is enabled (e.g., via environment/config), InclusionChecker.Run will call checkBlockAndAtts and panic when invoking the nil function. Make the test deterministic by explicitly disabling the feature (or wiring checkBlockAndAttsFunc) within the test.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
bmock, err := beaconmock.New(ctx)
require.NoError(t, err)
The test wires only checkBlockFunc; disable the attestation_inclusion feature explicitly so Run cannot take the nil checkBlockAndAttsFunc path regardless of test ordering.
|



The inclusion checker's unsigned slot arithmetic underflows on fresh chains: until wall slot
InclCheckLag+InclMissedLag,Trimreceived a huge cutoff and flushed every pending submission asduty not included on-chain. Skip ticks until a slot is old enough to check, and only trim once a slot can be declared missed.category: bug
ticket: none