diff --git a/core/rawdb/eradb/eradb.go b/core/rawdb/eradb/eradb.go index d715c824ed..21db083a10 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 ere files. package eradb import ( @@ -23,10 +23,12 @@ import ( "fmt" "io/fs" "path/filepath" + "strings" "sync" "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 +38,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 ere files. // The getter methods are thread-safe. type Store struct { datadir string @@ -52,7 +54,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 ere) err error // error from opening the file } @@ -250,7 +252,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 +285,46 @@ 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 ere. + for _, ext := range []string{"era1", "ere"} { + 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 "ere": + // The era store serves receipts via RPC. Reject noreceipts + // profiles to avoid silently returning empty receipt data. + if strings.Contains(filepath.Base(filename), "-noreceipts") { + return nil, fmt.Errorf("era store does not support noreceipts profile: %s", filepath.Base(filename)) + } + 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. @@ -339,9 +355,9 @@ func (entry *fileCacheEntry) derefAndClose(epoch uint64) (closed bool) { closeErr := entry.file.Close() if closeErr == nil { - log.Debug("Closed era1 file", "epoch", epoch) + log.Debug("Closed era file", "epoch", epoch) } else { - log.Warn("Error closing era1 file", "epoch", epoch, "err", closeErr) + log.Warn("Error closing era file", "epoch", epoch, "err", closeErr) } return true } diff --git a/core/rawdb/eradb/eradb_test.go b/core/rawdb/eradb/eradb_test.go index 41047dbbe9..5c442b9695 100644 --- a/core/rawdb/eradb/eradb_test.go +++ b/core/rawdb/eradb/eradb_test.go @@ -17,6 +17,8 @@ package eradb import ( + "os" + "path/filepath" "sync" "testing" @@ -48,6 +50,32 @@ func TestEraDatabase(t *testing.T) { assert.Equal(t, 3, len(receipts), "receipts length mismatch") } +func TestEraStoreRejectsNoReceiptsProfile(t *testing.T) { + dir := t.TempDir() + stubName := "mainnet-00000-deadbeef-noreceipts.ere" + stubPath := filepath.Join(dir, stubName) + + // Write a non-empty stub so the glob finds the file. Contents don't matter + // because the noreceipts check fires before execdb.Open is called. + err := os.WriteFile(stubPath, []byte("stub"), 0644) + require.NoError(t, err) + + db, err := New(dir) + require.NoError(t, err) + defer db.Close() + + // Any block in epoch 0 should trigger the same rejection. + const block = uint64(0) + + _, err = db.GetRawBody(block) + require.Error(t, err) + assert.Contains(t, err.Error(), "era store does not support noreceipts profile") + + _, err = db.GetRawReceipts(block) + require.Error(t, err) + assert.Contains(t, err.Error(), "era store does not support noreceipts profile") +} + func TestEraDatabaseConcurrentOpen(t *testing.T) { db, err := New("testdata") require.NoError(t, err)