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 d715c824ed..6ab55432a3 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 (
@@ -26,7 +26,9 @@ 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"
"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 erae 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 erae)
err error // error from opening the file
}
@@ -101,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)
@@ -250,7 +267,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 +300,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.
diff --git a/eth/api_backend.go b/eth/api_backend.go
index 5e3558d8eb..fc17f2e1e3 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -157,10 +157,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}