@@ -25,6 +25,7 @@ import (
2525 "sync"
2626
2727 "github.com/blinklabs-io/dingo/database"
28+ "github.com/blinklabs-io/dingo/database/types"
2829 "github.com/blinklabs-io/dingo/event"
2930)
3031
@@ -137,7 +138,7 @@ func (m *Manager) epochTransitionLoop(
137138 for evt := range evtCh {
138139 // Drain any queued events, keeping only the latest.
139140 latest := evt
140- drained := false
141+ skipped := false
141142 drain:
142143 for {
143144 select {
@@ -146,7 +147,7 @@ func (m *Manager) epochTransitionLoop(
146147 return
147148 }
148149 latest = newer
149- drained = true
150+ skipped = true
150151 default :
151152 break drain
152153 }
@@ -161,7 +162,8 @@ func (m *Manager) epochTransitionLoop(
161162 continue
162163 }
163164
164- if drained {
165+ if skipped {
166+ // We skipped intermediate events
165167 skippedEvt , _ := evt .Data .(event.EpochTransitionEvent )
166168 m .logger .Info (
167169 "fast-forwarded past intermediate epoch transitions" ,
@@ -172,14 +174,23 @@ func (m *Manager) epochTransitionLoop(
172174 }
173175
174176 if err := m .handleEpochTransition (ctx , epochEvent ); err != nil {
175- m .logger .Error (
176- "failed to handle epoch transition" ,
177- "component" , "snapshot" ,
178- "epoch" , epochEvent .NewEpoch ,
179- "error" , err ,
180- )
177+ if errors .Is (err , types .ErrNoEpochData ) {
178+ m .logger .Debug (
179+ "skipping snapshot: epoch data not yet synced" ,
180+ "component" , "snapshot" ,
181+ "epoch" , epochEvent .NewEpoch ,
182+ )
183+ } else {
184+ m .logger .Error (
185+ "failed to handle epoch transition" ,
186+ "component" , "snapshot" ,
187+ "epoch" , epochEvent .NewEpoch ,
188+ "error" , err ,
189+ )
190+ }
181191 }
182192
193+ // Check for context cancellation between events
183194 if ctx .Err () != nil {
184195 return
185196 }
@@ -259,3 +270,41 @@ func (m *Manager) captureMarkSnapshot(
259270
260271 return nil
261272}
273+
274+ // CaptureGenesisSnapshot captures the initial stake distribution from genesis
275+ // as a mark snapshot for epoch 0. This ensures the "Go" snapshot is available
276+ // at epoch 2 for leader election, matching the Cardano spec which uses the
277+ // genesis stake distribution for the first two epochs.
278+ func (m * Manager ) CaptureGenesisSnapshot (ctx context.Context ) error {
279+ calculator := NewCalculator (m .db )
280+
281+ distribution , err := calculator .CalculateStakeDistribution (ctx , 0 )
282+ if err != nil {
283+ return fmt .Errorf ("calculate genesis distribution: %w" , err )
284+ }
285+
286+ if distribution .TotalPools == 0 {
287+ m .logger .Debug (
288+ "no genesis pools, skipping genesis snapshot" ,
289+ "component" , "snapshot" ,
290+ )
291+ return nil
292+ }
293+
294+ evt := event.EpochTransitionEvent {
295+ NewEpoch : 0 ,
296+ BoundarySlot : 0 ,
297+ SnapshotSlot : 0 ,
298+ }
299+ if err := m .saveSnapshot (ctx , 0 , "mark" , distribution , evt ); err != nil {
300+ return fmt .Errorf ("save genesis snapshot: %w" , err )
301+ }
302+
303+ m .logger .Info (
304+ "captured genesis snapshot" ,
305+ "component" , "snapshot" ,
306+ "total_pools" , distribution .TotalPools ,
307+ "total_stake" , distribution .TotalStake ,
308+ )
309+ return nil
310+ }
0 commit comments