Skip to content

Commit ba40fe6

Browse files
authored
fix: genesis snapshot (#1447)
Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>
1 parent f4e8265 commit ba40fe6

File tree

2 files changed

+63
-10
lines changed

2 files changed

+63
-10
lines changed

database/plugin/metadata/sqlite/stake_snapshot.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/blinklabs-io/dingo/database/models"
2222
"github.com/blinklabs-io/dingo/database/types"
2323
"gorm.io/gorm"
24+
"gorm.io/gorm/clause"
2425
)
2526

2627
// Pool Stake Snapshot Operations
@@ -132,7 +133,10 @@ func (d *MetadataStoreSqlite) SaveEpochSummary(
132133
if err != nil {
133134
return err
134135
}
135-
return db.Create(summary).Error
136+
// Use ON CONFLICT DO NOTHING so duplicate epoch events (from both
137+
// slot-clock and block-based transitions) are idempotent.
138+
return db.Clauses(clause.OnConflict{DoNothing: true}).
139+
Create(summary).Error
136140
}
137141

138142
// GetEpochSummary retrieves an epoch summary by epoch number

ledger/snapshot/manager.go

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)