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}