Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 19 additions & 53 deletions db/snapshotsync/caplin_state_snapshots.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,32 +316,15 @@ func (s *CaplinStateSnapshots) OpenList(fileNames []string, optimistic bool) err
s.closeWhatNotInList(fileNames)
var segmentsMax uint64
var segmentsMaxSet bool
Loop:
for _, fName := range fileNames {
f, _, _ := snaptype.ParseFileName(s.dir, fName)

var processed bool = true
var exists bool
var sn *DirtySegment

dirtySegments, ok := s.dirty[f.CaplinTypeString]
if !ok {
continue
}
filePath := filepath.Join(s.dir, fName)
dirtySegments.Walk(func(segments []*DirtySegment) bool {
for _, sn2 := range segments {
if sn2.Decompressor == nil { // it's ok if some segment was not able to open
continue
}
if filePath == sn2.filePath {
sn = sn2
exists = true
break
}
}
return true
})
sn, exists := findOpenSegment(dirtySegments, func(sn2 *DirtySegment) bool { return sn2.filePath == filePath })
if !exists {
sn = &DirtySegment{
// segType: f.Type, Unsupported
Expand All @@ -352,19 +335,17 @@ Loop:
}
}
if err := s.openSegIfNeed(sn, filePath); err != nil {
if errors.Is(err, os.ErrNotExist) {
if optimistic {
continue Loop
} else {
break Loop
}
stop, failErr := ClassifyOpenErr(err, optimistic)
if failErr != nil {
return failErr
}
if optimistic {
if stop {
break
}
if !errors.Is(err, os.ErrNotExist) {
s.logger.Warn("[snapshots] open segment", "err", err)
continue Loop
} else {
return err
}
continue
}

if !exists {
Expand All @@ -375,15 +356,12 @@ Loop:
if err := openIdxForCaplinStateIfNeeded(sn, filePath, optimistic); err != nil {
return err
}
// Only bob sidecars count for progression
if processed {
if f.To > 0 {
segmentsMax = f.To - 1
} else {
segmentsMax = 0
}
segmentsMaxSet = true
if f.To > 0 {
segmentsMax = f.To - 1
} else {
segmentsMax = 0
}
segmentsMaxSet = true
}

if segmentsMaxSet {
Expand Down Expand Up @@ -600,24 +578,12 @@ func (s *CaplinStateSnapshots) closeWhatNotInList(l []string) {
}

for _, dirtySegments := range s.dirty {
toClose := make([]*DirtySegment, 0)
dirtySegments.Walk(func(segments []*DirtySegment) bool {
for _, sn := range segments {
if sn.Decompressor == nil {
continue
}
_, name := filepath.Split(sn.FilePath())
if _, ok := protectFiles[name]; ok {
continue
}
toClose = append(toClose, sn)
}
return true
closeAndDropNotProtected(dirtySegments, protectFiles, func(sn *DirtySegment) string {
// sn.filePath, not the promoted Decompressor.FilePath(): the field is
// set for every tree member, incl. stubs whose Decompressor is nil.
_, name := filepath.Split(sn.filePath)
return name
})
for _, sn := range toClose {
sn.close()
dirtySegments.Delete(sn)
}
}
}

Expand Down
41 changes: 41 additions & 0 deletions db/snapshotsync/caplin_state_snapshots_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2026 The Erigon Authors
// This file is part of Erigon.
//
// Erigon is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Erigon is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Erigon. If not, see <http://www.gnu.org/licenses/>.

package snapshotsync

import (
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
"github.com/tidwall/btree"
)

func TestCaplinStateCloseWhatNotInListDropsUnopenedStub(t *testing.T) {
tree := btree.NewBTreeGOptions[*DirtySegment](DirtySegmentLess, btree.Options{Degree: 4, NoLocks: false})
kept := &DirtySegment{Range: Range{0, 1000}, filePath: filepath.Join("snapshots", "keep.seg")}
stale := &DirtySegment{Range: Range{1000, 2000}, filePath: filepath.Join("snapshots", "drop.seg")}
tree.Set(kept)
tree.Set(stale)
s := &CaplinStateSnapshots{dirty: map[string]*btree.BTreeG[*DirtySegment]{"test": tree}}

s.closeWhatNotInList([]string{"keep.seg"})

require.Equal(t, 1, tree.Len(), "unopened stale stub must be dropped")
survivor, ok := tree.Min()
require.True(t, ok)
require.Same(t, kept, survivor)
}
173 changes: 35 additions & 138 deletions db/snapshotsync/freezeblocks/caplin_snapshots.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,119 +180,51 @@ func (s *CaplinSnapshots) OpenList(fileNames []string, optimistic bool) error {

var segmentsMax uint64
var segmentsMaxSet bool
Loop:
for _, fName := range fileNames {
f, _, ok := snaptype.ParseFileName(s.dir, fName)
if !ok {
continue
}
var processed bool = true
switch f.Type.Enum() {
case snaptype.CaplinEnums.BeaconBlocks:
var sn *snapshotsync.DirtySegment
var exists bool
s.dirty[snaptype.BeaconBlocks.Enum()].Walk(func(segments []*snapshotsync.DirtySegment) bool {
for _, sn2 := range segments {
if sn2.Decompressor == nil { // it's ok if some segment was not able to open
continue
}
if fName == sn2.FileName() {
sn = sn2
exists = true
break
}
}
return true
})
if !exists {
sn = snapshotsync.NewDirtySegment(
snaptype.BeaconBlocks,
f.Version,
f.From, f.To,
true)
}
if err := sn.Open(s.dir); err != nil {
if errors.Is(err, os.ErrNotExist) {
if optimistic {
continue Loop
} else {
break Loop
}
}
if optimistic {
s.logger.Warn("[snapshots] open segment", "err", err)
continue Loop
} else {
return err
}
}
typ := f.Type.Enum()
if typ != snaptype.CaplinEnums.BeaconBlocks && typ != snaptype.CaplinEnums.BlobSidecars {
continue
}
tree := s.dirty[typ]

if !exists {
// it's possible to iterate over .seg file even if you don't have index
// then make segment available even if index open may fail
s.dirty[snaptype.BeaconBlocks.Enum()].Set(sn)
}
if err := sn.OpenIdxIfNeed(s.dir, optimistic, dirEntries); err != nil {
return err
}
// Only bob sidecars count for progression
if processed {
if f.To > 0 {
segmentsMax = f.To - 1
} else {
segmentsMax = 0
}
segmentsMaxSet = true
sn, exists := snapshotsync.FindOpenSegment(tree, fName)
if !exists {
sn = snapshotsync.NewDirtySegment(f.Type, f.Version, f.From, f.To, true)
}
if err := sn.Open(s.dir); err != nil {
stop, failErr := snapshotsync.ClassifyOpenErr(err, optimistic)
if failErr != nil {
return failErr
}
case snaptype.CaplinEnums.BlobSidecars:
var sn *snapshotsync.DirtySegment
var exists bool
s.dirty[snaptype.BlobSidecars.Enum()].Walk(func(segments []*snapshotsync.DirtySegment) bool {
for _, sn2 := range segments {
if sn2.Decompressor == nil { // it's ok if some segment was not able to open
continue
}
if fName == sn2.FileName() {
sn = sn2
exists = true
break
}
}
return true
})
if !exists {
sn = snapshotsync.NewDirtySegment(
snaptype.BlobSidecars,
f.Version,
f.From, f.To,
true)
if stop {
break
}
if err := sn.Open(s.dir); err != nil {
if errors.Is(err, os.ErrNotExist) {
if optimistic {
continue Loop
} else {
break Loop
}
}
if optimistic {
s.logger.Warn("[snapshots] open segment", "err", err)
continue Loop
} else {
return err
}
if !errors.Is(err, os.ErrNotExist) {
s.logger.Warn("[snapshots] open segment", "err", err)
}
continue
}

if !exists {
// it's possible to iterate over .seg file even if you don't have index
// then make segment available even if index open may fail
s.dirty[snaptype.BlobSidecars.Enum()].Set(sn)
}
if err := sn.OpenIdxIfNeed(s.dir, optimistic, dirEntries); err != nil {
return err
if !exists {
// it's possible to iterate over .seg file even if you don't have index
// then make segment available even if index open may fail
tree.Set(sn)
}
if err := sn.OpenIdxIfNeed(s.dir, optimistic, dirEntries); err != nil {
return err
}
if typ == snaptype.CaplinEnums.BeaconBlocks {
if f.To > 0 {
segmentsMax = f.To - 1
} else {
segmentsMax = 0
}
segmentsMaxSet = true
}

}
if segmentsMaxSet {
s.segmentsMax.Store(segmentsMax)
Expand Down Expand Up @@ -339,43 +271,8 @@ func (s *CaplinSnapshots) closeWhatNotInList(l []string) {
for _, fName := range l {
protectFiles[fName] = struct{}{}
}
toClose := make([]*snapshotsync.DirtySegment, 0)
s.dirty[snaptype.BeaconBlocks.Enum()].Walk(func(segments []*snapshotsync.DirtySegment) bool {
for _, sn := range segments {
if sn.Decompressor == nil {
continue
}
_, name := filepath.Split(sn.FilePath())
if _, ok := protectFiles[name]; ok {
continue
}
toClose = append(toClose, sn)
}
return true
})
for _, sn := range toClose {
sn.Close()
s.dirty[snaptype.BeaconBlocks.Enum()].Delete(sn)
}

toClose = make([]*snapshotsync.DirtySegment, 0)
s.dirty[snaptype.BlobSidecars.Enum()].Walk(func(segments []*snapshotsync.DirtySegment) bool {
for _, sn := range segments {
if sn.Decompressor == nil {
continue
}
_, name := filepath.Split(sn.FilePath())
if _, ok := protectFiles[name]; ok {
continue
}
toClose = append(toClose, sn)
}
return true
})
for _, sn := range toClose {
sn.Close()
s.dirty[snaptype.BlobSidecars.Enum()].Delete(sn)
}
snapshotsync.CloseSegmentsNotInList(s.dirty[snaptype.BeaconBlocks.Enum()], protectFiles)
snapshotsync.CloseSegmentsNotInList(s.dirty[snaptype.BlobSidecars.Enum()], protectFiles)
}

type CaplinView struct {
Expand Down
Loading
Loading