Skip to content

Commit 2bd2c39

Browse files
[r3.4] db/version: enforce upper-bound file version check (#20739)
Cherry-pick of #20722 to release/3.4.
1 parent 87ad656 commit 2bd2c39

5 files changed

Lines changed: 110 additions & 80 deletions

File tree

db/snaptype2/block_types.go

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -199,13 +199,7 @@ var (
199199
if !ok {
200200
return fmt.Errorf("can't find files with vers by pattern %s for indexing in bodies", bodiesPathPattern)
201201
}
202-
if bVer.Less(statecfg.Schema.BodiesBlock.FileVersion.DataSeg.MinSupported) {
203-
verToPanic := version.Versions{
204-
Current: bVer,
205-
MinSupported: statecfg.Schema.BodiesBlock.FileVersion.DataSeg.MinSupported,
206-
}
207-
version.VersionTooLowPanic(filepath.Base(bodiesPath), verToPanic)
208-
}
202+
statecfg.Schema.BodiesBlock.FileVersion.DataSeg.MustSupport(bVer, filepath.Base(bodiesPath))
209203
bodiesSegment, err := seg.NewDecompressor(bodiesPath)
210204
if err != nil {
211205
return fmt.Errorf("can't open %s for indexing in bodies: %w", sn.As(Bodies).Path, err)
@@ -228,13 +222,7 @@ var (
228222
if !ok {
229223
return fmt.Errorf("can't find files with vers by pattern %s for indexing in txs", txPathPattern)
230224
}
231-
if tVer.Less(statecfg.Schema.TransactionsBlock.FileVersion.DataSeg.MinSupported) {
232-
verToPanic := version.Versions{
233-
Current: tVer,
234-
MinSupported: statecfg.Schema.TransactionsBlock.FileVersion.DataSeg.MinSupported,
235-
}
236-
version.VersionTooLowPanic(filepath.Base(txPath), verToPanic)
237-
}
225+
statecfg.Schema.TransactionsBlock.FileVersion.DataSeg.MustSupport(tVer, filepath.Base(txPath))
238226
d, err := seg.NewDecompressor(txPath)
239227
if err != nil {
240228
return fmt.Errorf("can't open %s for indexing in transactions: %w", sn.Path, err)

db/state/dirty_files.go

Lines changed: 24 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ func (d *Domain) openDirtyFiles(dirEntries []string) (err error) {
327327
fNameMask := d.kvFileNameMask(fromStep, toStep)
328328
fPath, fileVer, ok, err := version.MatchVersionedFile(fNameMask, dirEntries, d.dirs.SnapDomain)
329329
if err != nil {
330-
_, fName := filepath.Split(fPath)
330+
fName := filepath.Base(fPath)
331331
d.logger.Debug("[agg] Domain.openDirtyFiles: FileExist err", "f", fName, "err", err)
332332
invalidFileItemsLock.Lock()
333333
invalidFileItems = append(invalidFileItems, item)
@@ -343,13 +343,10 @@ func (d *Domain) openDirtyFiles(dirEntries []string) (err error) {
343343
continue
344344
}
345345

346-
if fileVer.Less(d.FileVersion.DataKV.MinSupported) {
347-
_, fName := filepath.Split(fPath)
348-
versionTooLowPanic(fName, d.FileVersion.DataKV)
349-
}
346+
fName := filepath.Base(fPath)
347+
d.FileVersion.DataKV.MustSupport(fileVer, fName)
350348

351349
if item.decompressor, err = seg.NewDecompressor(fPath); err != nil {
352-
_, fName := filepath.Split(fPath)
353350
if errors.Is(err, &seg.ErrCompressedFileCorrupted{}) {
354351
d.logger.Debug("[agg] Domain.openDirtyFiles", "err", err, "f", fName)
355352
} else {
@@ -367,16 +364,13 @@ func (d *Domain) openDirtyFiles(dirEntries []string) (err error) {
367364
fNameMask := d.kviAccessorFileNameMask(fromStep, toStep)
368365
fPath, fileVer, ok, err := version.MatchVersionedFile(fNameMask, dirEntries, d.dirs.SnapDomain)
369366
if err != nil {
370-
_, fName := filepath.Split(fPath)
367+
fName := filepath.Base(fPath)
371368
d.logger.Warn("[agg] Domain.openDirtyFiles", "err", err, "f", fName)
372369
}
373370
if ok {
374-
if fileVer.Less(d.FileVersion.AccessorKVI.MinSupported) {
375-
_, fName := filepath.Split(fPath)
376-
versionTooLowPanic(fName, d.FileVersion.AccessorKVI)
377-
}
371+
fName := filepath.Base(fPath)
372+
d.FileVersion.AccessorKVI.MustSupport(fileVer, fName)
378373
if item.index, err = recsplit.OpenIndex(fPath); err != nil {
379-
_, fName := filepath.Split(fPath)
380374
d.logger.Warn("[agg] Domain.openDirtyFiles", "err", err, "f", fName)
381375
// don't interrupt on error. other files may be good
382376
}
@@ -386,16 +380,13 @@ func (d *Domain) openDirtyFiles(dirEntries []string) (err error) {
386380
fNameMask := d.kvBtAccessorFileNameMask(fromStep, toStep)
387381
fPath, fileVer, ok, err := version.MatchVersionedFile(fNameMask, dirEntries, d.dirs.SnapDomain)
388382
if err != nil {
389-
_, fName := filepath.Split(fPath)
383+
fName := filepath.Base(fPath)
390384
d.logger.Warn("[agg] Domain.openDirtyFiles", "err", err, "f", fName)
391385
}
392386
if ok {
393-
if fileVer.Less(d.FileVersion.AccessorBT.MinSupported) {
394-
_, fName := filepath.Split(fPath)
395-
versionTooLowPanic(fName, d.FileVersion.AccessorBT)
396-
}
387+
fName := filepath.Base(fPath)
388+
d.FileVersion.AccessorBT.MustSupport(fileVer, fName)
397389
if item.bindex, err = btindex.OpenBtreeIndexWithDecompressor(fPath, btindex.DefaultBtreeM, d.dataReader(item.decompressor)); err != nil {
398-
_, fName := filepath.Split(fPath)
399390
d.logger.Warn("[agg] Domain.openDirtyFiles", "err", err, "f", fName)
400391
// don't interrupt on error. other files may be good
401392
}
@@ -405,16 +396,13 @@ func (d *Domain) openDirtyFiles(dirEntries []string) (err error) {
405396
fNameMask := d.kvExistenceIdxFileNameMask(fromStep, toStep)
406397
fPath, fileVer, ok, err := version.MatchVersionedFile(fNameMask, dirEntries, d.dirs.SnapDomain)
407398
if err != nil {
408-
_, fName := filepath.Split(fPath)
399+
fName := filepath.Base(fPath)
409400
d.logger.Warn("[agg] Domain.openDirtyFiles", "err", err, "f", fName)
410401
}
411402
if ok {
412-
if fileVer.Less(d.FileVersion.AccessorKVEI.MinSupported) {
413-
_, fName := filepath.Split(fPath)
414-
versionTooLowPanic(fName, d.FileVersion.AccessorKVEI)
415-
}
403+
fName := filepath.Base(fPath)
404+
d.FileVersion.AccessorKVEI.MustSupport(fileVer, fName)
416405
if item.existence, err = existence.OpenFilter(fPath, false); err != nil {
417-
_, fName := filepath.Split(fPath)
418406
d.logger.Warn("[agg] Domain.openDirtyFiles", "err", err, "f", fName)
419407
// don't interrupt on error. other files may be good
420408
}
@@ -442,7 +430,7 @@ func (h *History) openDirtyFiles(dataEntries, accessorEntries []string) error {
442430
fNameMask := h.vFileNameMask(fromStep, toStep)
443431
fPath, fileVer, ok, err := version.MatchVersionedFile(fNameMask, dataEntries, h.dirs.SnapHistory)
444432
if err != nil {
445-
_, fName := filepath.Split(fPath)
433+
fName := filepath.Base(fPath)
446434
h.logger.Debug("[agg] History.openDirtyFiles: FileExist", "f", fName, "err", err)
447435
invalidFilesMu.Lock()
448436
invalidFileItems = append(invalidFileItems, item)
@@ -457,13 +445,10 @@ func (h *History) openDirtyFiles(dataEntries, accessorEntries []string) error {
457445
invalidFilesMu.Unlock()
458446
continue
459447
}
460-
if fileVer.Less(h.FileVersion.DataV.MinSupported) {
461-
_, fName := filepath.Split(fPath)
462-
versionTooLowPanic(fName, h.FileVersion.DataV)
463-
}
448+
fName := filepath.Base(fPath)
449+
h.FileVersion.DataV.MustSupport(fileVer, fName)
464450

465451
if item.decompressor, err = seg.NewDecompressor(fPath); err != nil {
466-
_, fName := filepath.Split(fPath)
467452
if errors.Is(err, &seg.ErrCompressedFileCorrupted{}) {
468453
h.logger.Debug("[agg] History.openDirtyFiles", "err", err, "f", fName)
469454
// TODO we do not restore those files so we could just remove them along with indices. Same for domains/indices.
@@ -494,16 +479,13 @@ func (h *History) openDirtyFiles(dataEntries, accessorEntries []string) error {
494479
fNameMask := h.vAccessorFileNameMask(fromStep, toStep)
495480
fPath, fileVer, ok, err := version.MatchVersionedFile(fNameMask, accessorEntries, h.dirs.SnapAccessors)
496481
if err != nil {
497-
_, fName := filepath.Split(fPath)
482+
fName := filepath.Base(fPath)
498483
h.logger.Warn("[agg] History.openDirtyFiles", "err", err, "f", fName)
499484
}
500485
if ok {
501-
if fileVer.Less(h.FileVersion.AccessorVI.MinSupported) {
502-
_, fName := filepath.Split(fPath)
503-
versionTooLowPanic(fName, h.FileVersion.AccessorVI)
504-
}
486+
fName := filepath.Base(fPath)
487+
h.FileVersion.AccessorVI.MustSupport(fileVer, fName)
505488
if item.index, err = recsplit.OpenIndex(fPath); err != nil {
506-
_, fName := filepath.Split(fPath)
507489
h.logger.Warn("[agg] History.openDirtyFiles", "err", err, "f", fName)
508490
// don't interrupt on error. other files may be good
509491
}
@@ -530,7 +512,7 @@ func (ii *InvertedIndex) openDirtyFiles(dataEntries, accessorEntries []string) e
530512
fNameMask := ii.efFileNameMask(fromStep, toStep)
531513
fPath, fileVer, ok, err := version.MatchVersionedFile(fNameMask, dataEntries, ii.dirs.SnapIdx)
532514
if err != nil {
533-
_, fName := filepath.Split(fPath)
515+
fName := filepath.Base(fPath)
534516
ii.logger.Debug("[agg] InvertedIndex.openDirtyFiles: MatchVersionedFile error", "f", fName, "err", err)
535517
invalidFileItemsLock.Lock()
536518
invalidFileItems = append(invalidFileItems, item)
@@ -547,13 +529,10 @@ func (ii *InvertedIndex) openDirtyFiles(dataEntries, accessorEntries []string) e
547529
continue
548530
}
549531

550-
if fileVer.Less(ii.FileVersion.DataEF.MinSupported) {
551-
_, fName := filepath.Split(fPath)
552-
versionTooLowPanic(fName, ii.FileVersion.DataEF)
553-
}
532+
fName := filepath.Base(fPath)
533+
ii.FileVersion.DataEF.MustSupport(fileVer, fName)
554534

555535
if item.decompressor, err = seg.NewDecompressor(fPath); err != nil {
556-
_, fName := filepath.Split(fPath)
557536
if errors.Is(err, &seg.ErrCompressedFileCorrupted{}) {
558537
ii.logger.Debug("[agg] InvertedIndex.openDirtyFiles", "err", err, "f", fName)
559538
} else {
@@ -571,17 +550,14 @@ func (ii *InvertedIndex) openDirtyFiles(dataEntries, accessorEntries []string) e
571550
fNameMask := ii.efAccessorFileNameMask(fromStep, toStep)
572551
fPath, fileVer, ok, err := version.MatchVersionedFile(fNameMask, accessorEntries, ii.dirs.SnapAccessors)
573552
if err != nil {
574-
_, fName := filepath.Split(fPath)
553+
fName := filepath.Base(fPath)
575554
ii.logger.Warn("[agg] InvertedIndex.openDirtyFiles", "err", err, "f", fName)
576555
// don't interrupt on error. other files may be good
577556
}
578557
if ok {
579-
if fileVer.Less(ii.FileVersion.AccessorEFI.MinSupported) {
580-
_, fName := filepath.Split(fPath)
581-
versionTooLowPanic(fName, ii.FileVersion.AccessorEFI)
582-
}
558+
fName := filepath.Base(fPath)
559+
ii.FileVersion.AccessorEFI.MustSupport(fileVer, fName)
583560
if item.index, err = recsplit.OpenIndex(fPath); err != nil {
584-
_, fName := filepath.Split(fPath)
585561
ii.logger.Warn("[agg] InvertedIndex.openDirtyFiles", "err", err, "f", fName)
586562
// don't interrupt on error. other files may be good
587563
}

db/state/domain.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2001,15 +2001,6 @@ func (dt *DomainRoTx) Files() (res VisibleFiles) {
20012001
}
20022002
func (dt *DomainRoTx) Name() kv.Domain { return dt.name }
20032003

2004-
func versionTooLowPanic(filename string, version version.Versions) {
2005-
panic(fmt.Sprintf(
2006-
"FileVersion is too low, try to run snapshot reset: `erigon --datadir $DATADIR --chain $CHAIN snapshots reset`. file=%s, min_supported=%s, current=%s",
2007-
filename,
2008-
version.MinSupported,
2009-
version.Current,
2010-
))
2011-
}
2012-
20132004
// [startTxNum, endTxNum)
20142005
func (dt *DomainRoTx) TraceKey(ctx context.Context, key []byte, startTxNum, endTxNum uint64, roTx kv.Tx) (stream.U64V, error) {
20152006
// need to do this first as TraceKey doesn't work if internal seg readers are seeked into different locations

db/version/file_version.go

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ import (
88
"sort"
99
"strconv"
1010
"strings"
11+
"sync"
1112

1213
"gopkg.in/yaml.v3"
14+
15+
"github.com/erigontech/erigon/common/log/v3"
1316
)
1417

1518
/*
@@ -185,6 +188,29 @@ func (v Versions) Supports(ver Version) bool {
185188
return ver.GreaterOrEqual(v.MinSupported) && ver.LessOrEqual(v.Current)
186189
}
187190

191+
var mustSupportLogOnce sync.Once
192+
193+
// MustSupport panics if ver is outside [MinSupported, Current].
194+
func (v Versions) MustSupport(ver Version, filename string) {
195+
if v.Supports(ver) {
196+
return
197+
}
198+
var msg string
199+
if ver.Less(v.MinSupported) {
200+
msg = fmt.Sprintf(
201+
"Snapshot file is too old for this Erigon build: file=%s, minimum_required=%s. To fix, reset snapshots: `erigon snapshots reset --datadir $DATADIR --chain $CHAIN`",
202+
filename, v.MinSupported,
203+
)
204+
} else {
205+
msg = fmt.Sprintf(
206+
"Snapshot file is newer than this Erigon build supports: file=%s, highest_supported=<%s. To fix, either upgrade Erigon to a newer release, or align snapshots by command: `erigon snapshots reset --datadir $DATADIR --chain $CHAIN`",
207+
filename, ver,
208+
)
209+
}
210+
mustSupportLogOnce.Do(func() { log.Error(msg) })
211+
panic(msg)
212+
}
213+
188214
// FindFilesWithVersionsByPattern return an filepath by pattern
189215
func FindFilesWithVersionsByPattern(pattern string) (string, Version, bool, error) {
190216
matches, err := filepath.Glob(pattern)
@@ -311,12 +337,3 @@ func (v *Version) UnmarshalYAML(node *yaml.Node) error {
311337
*v = ver
312338
return nil
313339
}
314-
315-
func VersionTooLowPanic(filename string, version Versions) {
316-
panic(fmt.Sprintf(
317-
"FileVersion is too low, try to run snapshot reset: `erigon --datadir $DATADIR --chain $CHAIN snapshots reset`. file=%s, min_supported=%s, current=%s",
318-
filename,
319-
version.MinSupported,
320-
version.Current,
321-
))
322-
}

db/version/file_version_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"path/filepath"
88
"reflect"
9+
"strings"
910
"testing"
1011
)
1112

@@ -414,6 +415,63 @@ func TestMatchVersionedFile_DifferentSegAndIdxNames(t *testing.T) {
414415
}
415416
}
416417

418+
func TestMustSupport(t *testing.T) {
419+
supported := Versions{Current: V1_2, MinSupported: V1_1}
420+
421+
t.Run("in-range does not panic", func(t *testing.T) {
422+
for _, ver := range []Version{V1_1, V1_2} {
423+
func() {
424+
defer func() {
425+
if r := recover(); r != nil {
426+
t.Errorf("unexpected panic for ver=%s: %v", ver, r)
427+
}
428+
}()
429+
supported.MustSupport(ver, "test.kv")
430+
}()
431+
}
432+
})
433+
434+
t.Run("too low panics with reset instructions", func(t *testing.T) {
435+
defer func() {
436+
r := recover()
437+
if r == nil {
438+
t.Fatal("expected panic for too-low version")
439+
}
440+
msg, ok := r.(string)
441+
if !ok {
442+
t.Fatalf("expected string panic, got %T", r)
443+
}
444+
if !strings.Contains(msg, "too old") || !strings.Contains(msg, "snapshots reset") {
445+
t.Errorf("panic message missing remediation hint: %q", msg)
446+
}
447+
if !strings.Contains(msg, "test.kv") {
448+
t.Errorf("panic message missing filename: %q", msg)
449+
}
450+
}()
451+
supported.MustSupport(V1_0, "test.kv")
452+
})
453+
454+
t.Run("too high panics with upgrade instructions", func(t *testing.T) {
455+
defer func() {
456+
r := recover()
457+
if r == nil {
458+
t.Fatal("expected panic for too-high version")
459+
}
460+
msg, ok := r.(string)
461+
if !ok {
462+
t.Fatalf("expected string panic, got %T", r)
463+
}
464+
if !strings.Contains(msg, "newer than") || !strings.Contains(msg, "upgrade Erigon") || !strings.Contains(msg, "snapshots reset") {
465+
t.Errorf("panic message missing remediation hint: %q", msg)
466+
}
467+
if !strings.Contains(msg, "test.kv") {
468+
t.Errorf("panic message missing filename: %q", msg)
469+
}
470+
}()
471+
supported.MustSupport(V2_0, "test.kv")
472+
})
473+
}
474+
417475
func BenchmarkMatchVersionedFile(b *testing.B) {
418476
// Simulate a large directory with thousands of snapshot files (realistic scenario)
419477
dirEntries := make([]string, 0, 2000)

0 commit comments

Comments
 (0)