go-ethereum/core/rawdb/eradb/eradb.go
2025-05-29 15:28:51 -06:00

112 lines
3.4 KiB
Go

// 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 <http://www.gnu.org/licenses/>.
package eradb
import (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/ethereum/go-ethereum/common/lru"
"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
network string
cache *lru.Cache[uint64, *era.Era]
}
// New creates a new EraDatabase instance.
func New(datadir, network 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
}
db := &EraDatabase{datadir: datadir, network: network, cache: lru.NewCache[uint64, *era.Era](50)}
log.Info("Opened erastore", "datadir", datadir)
return db, 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)
}
return e, nil
}
func (db *EraDatabase) Close() {
// Close all open era1 files in the cache.
keys := db.cache.Keys()
for _, key := range keys {
if e, ok := db.cache.Get(key); ok {
e.Close()
}
}
}
func (db *EraDatabase) GetBlockByNumber(number uint64) (*types.Block, error) {
// Lookup the table by epoch.
epoch := number / uint64(era.MaxEra1Size)
// Check the cache first.
if e, ok := db.cache.Get(epoch); ok {
fmt.Printf("Cache hit for epoch %d\n", epoch)
return e.GetBlockByNumber(number)
}
// file name scheme is <network>-<epoch>-<root>.
glob := fmt.Sprintf("%s-%05d-*.era1", db.network, epoch)
matches, err := filepath.Glob(filepath.Join(db.datadir, glob))
if err != nil {
return nil, err
}
if len(matches) > 1 {
return nil, fmt.Errorf("multiple era1 files found for epoch %d", epoch)
}
if len(matches) == 0 {
return nil, nil
}
filename := matches[0]
e, err := db.openEra(filename)
if err != nil {
return nil, err
}
// Add the era to the cache.
db.cache.Add(epoch, e)
return e.GetBlockByNumber(number)
}