···340340 return nil, stacktrace.Propagate(err)
341341 }
342342343343+ committedHeight := d.ongoingRead.Height()
344344+343345 for _, r := range d.lastProcessedProposalExecTxResults {
344346 for _, cb := range r.commitSideEffects {
345347 cb()
···348350349351 d.ongoingWrite = nil
350352 d.ongoingRead = nil
353353+354354+ // Notify snapshot manager of new committed height (non-blocking)
355355+ select {
356356+ case d.snapshotManagerLatestHeightChan <- committedHeight:
357357+ default:
358358+ // try updating the height buffered in the chan
359359+360360+ // tentative read because we could race at reading with the snapshot manager goroutine
361361+ select {
362362+ case <-d.snapshotManagerLatestHeightChan:
363363+ default:
364364+ }
365365+366366+ // this must succeed because no other goroutine writes to the chan, and we just read:
367367+ d.snapshotManagerLatestHeightChan <- committedHeight
368368+ }
351369352370 return &abcitypes.ResponseCommit{
353371 // TODO only discard actual blockchain history based on settings
+45
abciapp/snapshots.go
···1515 "tangled.org/gbl08ma.com/didplcbft/store"
1616)
17171818+// runSnapshotManager monitors block heights and creates/deletes snapshots as needed
1919+func (d *DIDPLCApplication) runSnapshotManager(ctx context.Context, snapshotDirectory string) {
2020+ var lastHeight int64
2121+2222+ for {
2323+ select {
2424+ case <-ctx.Done():
2525+ return
2626+ case height := <-d.snapshotManagerLatestHeightChan:
2727+ lastHeight = height
2828+ }
2929+3030+ currentHeight := lastHeight
3131+ snapshotInterval := int64(d.plcConfig.SnapshotInterval)
3232+3333+ // Skip if we haven't reached the interval yet
3434+ if currentHeight-d.lastSnapshotHeight < snapshotInterval {
3535+ continue
3636+ }
3737+3838+ tempFilename := filepath.Join(snapshotDirectory, fmt.Sprintf("%020d.snapshot.tmp", currentHeight))
3939+4040+ d.logger.Info("Creating snapshot", "height", currentHeight)
4141+4242+ err := d.createSnapshot(currentHeight, tempFilename)
4343+ if err != nil {
4444+ d.logger.Error("failed to create snapshot", "height", currentHeight, "error", stacktrace.Propagate(err))
4545+ continue
4646+ }
4747+4848+ d.logger.Info("Created snapshot", "height", currentHeight)
4949+ d.lastSnapshotHeight = currentHeight
5050+5151+ // Clean up old snapshots if retention is set
5252+ if d.plcConfig.SnapshotRetentionCount > 0 {
5353+ numDeleted, err := store.Snapshot.PruneOldSnapshots(snapshotDirectory, int(d.plcConfig.SnapshotRetentionCount))
5454+ if err != nil {
5555+ d.logger.Error("failed to prune old snapshots", "error", stacktrace.Propagate(err))
5656+ continue
5757+ }
5858+ d.logger.Info("Pruned old snapshots", "numDeleted", numDeleted)
5959+ }
6060+ }
6161+}
6262+1863// snapshotNumRecentBlockHeaders is the number of recent block headers to include in snapshots
1964// It should be enough to handle challenges that depend on recent block headers
2065const snapshotNumRecentBlockHeaders = max(CommitToChallengeMaxAgeInBlocks, CompleteChallengeMaxAgeInBlocks) + 5
+11
config/config.go
···32323333 // Server response timeout for API endpoints
3434 ResponseTimeout time.Duration `mapstructure:"response_timeout"`
3535+3636+ // SnapshotInterval defines the number of blocks between automatic snapshots.
3737+ // If set to 0, automatic snapshot creation is disabled.
3838+ SnapshotInterval uint64 `mapstructure:"snapshot_interval"`
3939+4040+ // SnapshotRetentionCount defines the maximum number of snapshots to retain.
4141+ // When creating a new snapshot, older snapshots are automatically deleted.
4242+ // If set to 0, all snapshots are retained.
4343+ SnapshotRetentionCount uint64 `mapstructure:"snapshot_retention_count"`
3544}
36453746func DefaultPLCConfig() *PLCConfig {
···4049 Pprof: true, // TODO set to false once we move past alpha phase
4150 MaxStreamingExportCursorAge: 7 * 24 * time.Hour,
4251 ResponseTimeout: 10 * time.Second,
5252+ SnapshotInterval: 0, // Disable snapshots by default
5353+ SnapshotRetentionCount: 1,
4354 }
4455}