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)