From a0dfdef72b76d5e29cabc08284cb0b0b84bb4585 Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Tue, 21 Apr 2026 16:52:16 +0200 Subject: [PATCH] fix: prevent uint64 underflow in ListFinalizedSlots on short-lived chains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the finalized epoch is smaller than historical_epoch_count (e.g. on a freshly started devnet or kurtosis enclave), the previous expression `latestSlot - SlotsPerEpoch*HistoricalEpochCount` underflows uint64 and produces a value near 2^64, so the loop never executes and the /checkpointz/v1/beacon/slots endpoint returns an empty list — leaving the UI's historical checkpoints table blank until the chain has produced HistoricalEpochCount epochs. Clamp the lower bound at 0 so we return every available finalized epoch-boundary slot, regardless of chain age. Same fix as #241 but targeted at release/gloas so gloas devnets can pick it up without waiting for a master merge. --- pkg/beacon/default.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/beacon/default.go b/pkg/beacon/default.go index c18407b..43562d7 100644 --- a/pkg/beacon/default.go +++ b/pkg/beacon/default.go @@ -748,10 +748,18 @@ func (d *Default) ListFinalizedSlots(ctx context.Context) ([]phase0.Slot, error) return slots, errors.New("no finalized checkpoint available") } - latestSlot := phase0.Slot(uint64(finality.Finalized.Epoch) * uint64(sp.SlotsPerEpoch)) - + latestSlot := uint64(finality.Finalized.Epoch) * uint64(sp.SlotsPerEpoch) //nolint:gosec // HistoricalEpochCount is validated as positive in config. - for i, val := uint64(latestSlot), uint64(latestSlot)-uint64(sp.SlotsPerEpoch)*uint64(d.config.HistoricalEpochCount); i > val; i -= uint64(sp.SlotsPerEpoch) { + lookback := uint64(sp.SlotsPerEpoch) * uint64(d.config.HistoricalEpochCount) + + // Clamp the lower bound at 0 so short-lived chains (finalized epoch < + // HistoricalEpochCount) don't underflow uint64 and skip the loop entirely. + var earliest uint64 + if latestSlot > lookback { + earliest = latestSlot - lookback + } + + for i := latestSlot; i > earliest; i -= uint64(sp.SlotsPerEpoch) { slots = append(slots, phase0.Slot(i)) }