From 9c232a0101a4b366cfbad62130d612b0a5b693e5 Mon Sep 17 00:00:00 2001 From: jeevan-sid Date: Fri, 15 May 2026 13:04:15 +0530 Subject: [PATCH 1/2] support erae files in era store --- core/rawdb/eradb/eradb.go | 68 ++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/core/rawdb/eradb/eradb.go b/core/rawdb/eradb/eradb.go index d715c824ed..39a85f83be 100644 --- a/core/rawdb/eradb/eradb.go +++ b/core/rawdb/eradb/eradb.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -// Package eradb implements a history backend using era1 files. +// Package eradb implements a history backend using era1 and erae files. package eradb import ( @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common/lru" "github.com/ethereum/go-ethereum/internal/era" + "github.com/ethereum/go-ethereum/internal/era/execdb" "github.com/ethereum/go-ethereum/internal/era/onedb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" @@ -36,7 +37,7 @@ const openFileLimit = 64 var errClosed = errors.New("era store is closed") -// Store manages read access to a directory of era1 files. +// Store manages read access to a directory of era1 and erae files. // The getter methods are thread-safe. type Store struct { datadir string @@ -52,7 +53,7 @@ type Store struct { type fileCacheEntry struct { refcount int // reference count. This is protected by Store.mu! opened chan struct{} // signals opening of file has completed - file *onedb.Era // the file + file era.Era // the file (era1 or erae) err error // error from opening the file } @@ -250,7 +251,7 @@ func (db *Store) getCacheEntry(epoch uint64) (stat fileCacheStatus, entry *fileC } // fileOpened is called after an era file has been successfully opened. -func (db *Store) fileOpened(epoch uint64, entry *fileCacheEntry, file *onedb.Era) { +func (db *Store) fileOpened(epoch uint64, entry *fileCacheEntry, file era.Era) { db.mu.Lock() defer db.mu.Unlock() @@ -283,32 +284,41 @@ func (db *Store) fileFailedToOpen(epoch uint64, entry *fileCacheEntry, err error entry.err = err } -func (db *Store) openEraFile(epoch uint64) (*onedb.Era, error) { - // File name scheme is --. - glob := fmt.Sprintf("*-%05d-*.era1", epoch) - matches, err := filepath.Glob(filepath.Join(db.datadir, glob)) - if err != nil { - return nil, err +func (db *Store) openEraFile(epoch uint64) (era.Era, error) { + // File name scheme is --. + // Try era1 first, then erae. + for _, ext := range []string{"era1", "erae"} { + glob := fmt.Sprintf("*-%05d-*.%s", epoch, ext) + matches, err := filepath.Glob(filepath.Join(db.datadir, glob)) + if err != nil { + return nil, err + } + if len(matches) > 1 { + return nil, fmt.Errorf("multiple %s files found for epoch %d", ext, epoch) + } + if len(matches) == 0 { + continue + } + filename := matches[0] + var e era.Era + switch ext { + case "era1": + e, err = onedb.Open(filename) + case "erae": + e, err = execdb.Open(filename) + } + if err != nil { + return nil, err + } + // Sanity-check start block. + if e.Start()%uint64(era.MaxSize) != 0 { + e.Close() + return nil, fmt.Errorf("%s file has invalid boundary. %d %% %d != 0", ext, e.Start(), era.MaxSize) + } + log.Debug("Opened era file", "type", ext, "epoch", epoch) + return e, nil } - if len(matches) > 1 { - return nil, fmt.Errorf("multiple era1 files found for epoch %d", epoch) - } - if len(matches) == 0 { - return nil, fs.ErrNotExist - } - filename := matches[0] - - e, err := onedb.Open(filename) - if err != nil { - return nil, err - } - // Sanity-check start block. - if e.Start()%uint64(era.MaxSize) != 0 { - e.Close() - return nil, fmt.Errorf("pre-merge era1 file has invalid boundary. %d %% %d != 0", e.Start(), era.MaxSize) - } - log.Debug("Opened era1 file", "epoch", epoch) - return e.(*onedb.Era), nil + return nil, fs.ErrNotExist } // doneWithFile signals that the caller has finished using a file. From b673e63a73c78d51023522697dbcbc728b333af0 Mon Sep 17 00:00:00 2001 From: jeevan-sid Date: Fri, 15 May 2026 15:01:52 +0530 Subject: [PATCH 2/2] fix: fall back to era files for block retrieval on pruned nodes --- core/rawdb/chain_freezer.go | 4 ++++ core/rawdb/database.go | 22 ++++++++++++++++++++++ core/rawdb/eradb/eradb.go | 16 ++++++++++++++++ eth/api_backend.go | 13 +++++++++++-- node/node.go | 5 +++++ 5 files changed, 58 insertions(+), 2 deletions(-) diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go index d33f7ce33d..4411dbdb52 100644 --- a/core/rawdb/chain_freezer.go +++ b/core/rawdb/chain_freezer.go @@ -422,3 +422,7 @@ func (f *chainFreezer) TruncateTail(items uint64) (uint64, error) { func (f *chainFreezer) SyncAncient() error { return f.ancients.SyncAncient() } + +func (f *chainFreezer) EraStore() *eradb.Store { + return f.eradb +} diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 39e1a64e5a..0d08cce052 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -32,6 +32,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb/eradb" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/memorydb" @@ -86,6 +87,27 @@ func (frdb *freezerdb) Freeze() error { return nil } +// Unwrapper allows retrieving the underlying database from a wrapper. +type Unwrapper interface { + Unwrap() ethdb.Database +} + +// EraStore returns the eradb.Store from the database if available. +// Returns nil if the database does not have an era store (e.g. in-memory or no freezer). +func EraStore(db ethdb.Database) *eradb.Store { + for { + if frdb, ok := db.(*freezerdb); ok { + return frdb.EraStore() + } + if unwrapper, ok := db.(Unwrapper); ok { + db = unwrapper.Unwrap() + } else { + break + } + } + return nil +} + // nofreezedb is a database wrapper that disables freezer data retrievals. type nofreezedb struct { ethdb.KeyValueStore diff --git a/core/rawdb/eradb/eradb.go b/core/rawdb/eradb/eradb.go index 39a85f83be..6ab55432a3 100644 --- a/core/rawdb/eradb/eradb.go +++ b/core/rawdb/eradb/eradb.go @@ -26,6 +26,7 @@ import ( "sync" "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/internal/era/execdb" "github.com/ethereum/go-ethereum/internal/era/onedb" @@ -102,6 +103,21 @@ func (db *Store) Close() { } } +// GetBlockByNumber returns the block for a given block number. +func (db *Store) GetBlockByNumber(number uint64) (*types.Block, error) { + epoch := number / uint64(era.MaxSize) + entry := db.getEraByEpoch(epoch) + if entry.err != nil { + if errors.Is(entry.err, fs.ErrNotExist) { + return nil, nil + } + return nil, entry.err + } + defer db.doneWithFile(epoch, entry) + + return entry.file.GetBlockByNumber(number) +} + // GetRawBody returns the raw body for a given block number. func (db *Store) GetRawBody(number uint64) ([]byte, error) { epoch := number / uint64(era.MaxSize) diff --git a/eth/api_backend.go b/eth/api_backend.go index 33fe4fe5d9..5c97627034 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -156,10 +156,19 @@ func (b *EthAPIBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumbe bn = b.HistoryPruningCutoff() } block := b.eth.blockchain.GetBlockByNumber(bn) - if block == nil && bn < b.HistoryPruningCutoff() { + if block != nil { + return block, nil + } + // Block not found in the local database, try the ERA store as a fallback. + if eraStore := rawdb.EraStore(b.eth.chainDb); eraStore != nil { + if eraBlock, err := eraStore.GetBlockByNumber(bn); err == nil && eraBlock != nil { + return eraBlock, nil + } + } + if bn < b.HistoryPruningCutoff() { return nil, &history.PrunedHistoryError{} } - return block, nil + return nil, nil } func (b *EthAPIBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { diff --git a/node/node.go b/node/node.go index 56ecd7d522..e6e96b54d5 100644 --- a/node/node.go +++ b/node/node.go @@ -777,6 +777,11 @@ func (db *closeTrackingDB) Close() error { return db.Database.Close() } +// Unwrap returns the underlying database. +func (db *closeTrackingDB) Unwrap() ethdb.Database { + return db.Database +} + // wrapDatabase ensures the database will be auto-closed when Node is closed. func (n *Node) wrapDatabase(db ethdb.Database) ethdb.Database { wrapper := &closeTrackingDB{db, n}