diff --git a/core/rawdb/eradb/eradb.go b/core/rawdb/eradb/eradb.go new file mode 100644 index 0000000000..b9d2bb7dff --- /dev/null +++ b/core/rawdb/eradb/eradb.go @@ -0,0 +1,131 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library 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. +// +// The go-ethereum library 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 the go-ethereum library. If not, see . + +package eradb + +import ( + "errors" + "fmt" + "os" + "path" + "sync" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/era" + "github.com/ethereum/go-ethereum/log" +) + +type EraDatabase struct { + datadir string + table map[uint64]*era.Era + mu sync.RWMutex +} + +// New creates a new EraDatabase instance. +func New(datadir string) (*EraDatabase, error) { + // Ensure the datadir is not a symbolic link if it exists. + if info, err := os.Lstat(datadir); !os.IsNotExist(err) { + if info == nil { + log.Warn("Could not Lstat the database", "path", datadir) + return nil, errors.New("lstat failed") + } + if info.Mode()&os.ModeSymlink != 0 { + log.Warn("Symbolic link erastore is not supported", "path", datadir) + return nil, errors.New("symbolic link datadir is not supported") + } + } + if err := os.MkdirAll(datadir, 0755); err != nil { + return nil, err + } + log.Info("Opened erastore", "datadir", datadir) + return &EraDatabase{datadir: datadir, table: make(map[uint64]*era.Era)}, nil +} + +// scan returns a list of all era1 files in the datadir. +func (db *EraDatabase) scan() ([]string, error) { + entries, err := os.ReadDir(db.datadir) + if err != nil { + return nil, err + } + var files []string + for _, entry := range entries { + if entry.IsDir() || entry.Type()&os.ModeSymlink != 0 { + continue + } + // Skip files that are not era1 files. + if path.Ext(entry.Name()) != ".era1" { + continue + } + files = append(files, entry.Name()) + } + return files, nil +} + +func (db *EraDatabase) openEra(name string) (*era.Era, error) { + e, err := era.Open(name) + if err != nil { + return nil, err + } + // Assign an epoch to the table. + if e.Count() != uint64(era.MaxEra1Size) { + return nil, fmt.Errorf("pre-merge era1 files must be full. Want: %d, have: %d", era.MaxEra1Size, e.Count()) + } + if e.Start()%uint64(era.MaxEra1Size) != 0 { + return nil, fmt.Errorf("pre-merge era1 file has invalid boundary. %d %% %d != 0", e.Start(), era.MaxEra1Size) + } + epoch := e.Start() / uint64(era.MaxEra1Size) + db.mu.Lock() + db.table[epoch] = e + db.mu.Unlock() + return e, nil +} + +func (db *EraDatabase) Close() { + db.mu.Lock() + defer db.mu.Unlock() + for _, e := range db.table { + if err := e.Close(); err != nil { + log.Warn("Failed to close era", "error", err) + } + } + db.table = nil +} + +func (db *EraDatabase) GetBlockByNumber(number uint64) (*types.Block, error) { + files, err := db.scan() + if err != nil { + return nil, err + } + for _, file := range files { + _, err := db.openEra(path.Join(db.datadir, file)) + if err != nil { + return nil, err + } + } + + // Lookup the table by epoch. + epoch := number / uint64(era.MaxEra1Size) + db.mu.RLock() + defer db.mu.RUnlock() + if e, ok := db.table[epoch]; ok { + block, err := e.GetBlockByNumber(number) + if err == nil { + return block, nil + } + } + + return nil, errors.New("block not found") +} diff --git a/core/rawdb/eradb/eradb_test.go b/core/rawdb/eradb/eradb_test.go new file mode 100644 index 0000000000..452103d1b3 --- /dev/null +++ b/core/rawdb/eradb/eradb_test.go @@ -0,0 +1,39 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library 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. +// +// The go-ethereum library 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 the go-ethereum library. If not, see . + +package eradb + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEraDatabase_Scan(t *testing.T) { + // Create the database + db, err := New("testdata") + require.NoError(t, err) + + // Scan for era files + files, err := db.scan() + require.NoError(t, err) + + t.Log(files) + block, err := db.GetBlockByNumber(15000) + require.NoError(t, err) + defer db.Close() + require.Equal(t, uint64(15000), block.NumberU64()) +} diff --git a/core/rawdb/eradb/testdata/sepolia-00000-643a00f7.era1 b/core/rawdb/eradb/testdata/sepolia-00000-643a00f7.era1 new file mode 100644 index 0000000000..a601a40c23 Binary files /dev/null and b/core/rawdb/eradb/testdata/sepolia-00000-643a00f7.era1 differ diff --git a/core/rawdb/eradb/testdata/sepolia-00001-f3ea493e.era1 b/core/rawdb/eradb/testdata/sepolia-00001-f3ea493e.era1 new file mode 100644 index 0000000000..426de38810 Binary files /dev/null and b/core/rawdb/eradb/testdata/sepolia-00001-f3ea493e.era1 differ diff --git a/core/rawdb/eradb/testdata/sepolia-00002-dcb01f4d.era1 b/core/rawdb/eradb/testdata/sepolia-00002-dcb01f4d.era1 new file mode 100644 index 0000000000..34f7bd4424 Binary files /dev/null and b/core/rawdb/eradb/testdata/sepolia-00002-dcb01f4d.era1 differ diff --git a/core/rawdb/eradb/testdata/sepolia-00003-18b32584.era1 b/core/rawdb/eradb/testdata/sepolia-00003-18b32584.era1 new file mode 100644 index 0000000000..2a3b1a5354 Binary files /dev/null and b/core/rawdb/eradb/testdata/sepolia-00003-18b32584.era1 differ diff --git a/core/rawdb/eradb/testdata/sepolia-00004-e93b3f11.era1 b/core/rawdb/eradb/testdata/sepolia-00004-e93b3f11.era1 new file mode 100644 index 0000000000..0b6ccc2ef4 Binary files /dev/null and b/core/rawdb/eradb/testdata/sepolia-00004-e93b3f11.era1 differ