diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go
index dc071725c1..d8488b8268 100644
--- a/cmd/geth/chaincmd.go
+++ b/cmd/geth/chaincmd.go
@@ -45,6 +45,7 @@ import (
"github.com/ethereum/go-ethereum/internal/era/eradl"
"github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/urfave/cli/v2"
)
@@ -277,10 +278,7 @@ func initGenesis(ctx *cli.Context) error {
overrides.OverrideVerkle = &v
}
- chaindb, err := stack.OpenDatabaseWithFreezer("chaindata", 0, 0, ctx.String(utils.AncientFlag.Name), "", false)
- if err != nil {
- utils.Fatalf("Failed to open database: %v", err)
- }
+ chaindb := utils.MakeChainDatabase(ctx, stack, false)
defer chaindb.Close()
triedb := utils.MakeTrieDatabase(ctx, chaindb, ctx.Bool(utils.CachePreimagesFlag.Name), false, genesis.IsVerkle())
@@ -317,7 +315,7 @@ func dumpGenesis(ctx *cli.Context) error {
// dump whatever already exists in the datadir
stack, _ := makeConfigNode(ctx)
- db, err := stack.OpenDatabase("chaindata", 0, 0, "", true)
+ db, err := stack.OpenDatabaseWithOptions("chaindata", node.DatabaseOptions{ReadOnly: true})
if err != nil {
return err
}
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 60a2cc13f1..debec7278d 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -111,6 +111,11 @@ var (
Usage: "Root directory for ancient data (default = inside chaindata)",
Category: flags.EthCategory,
}
+ EraFlag = &flags.DirectoryFlag{
+ Name: "datadir.era",
+ Usage: "Root directory for era1 history (default = inside ancient/chain)",
+ Category: flags.EthCategory,
+ }
MinFreeDiskSpaceFlag = &flags.DirectoryFlag{
Name: "datadir.minfreedisk",
Usage: "Minimum free disk space in MB, once reached triggers auto shut down (default = --cache.gc converted to MB, 0 = disabled)",
@@ -977,6 +982,7 @@ var (
DatabaseFlags = []cli.Flag{
DataDirFlag,
AncientFlag,
+ EraFlag,
RemoteDBFlag,
DBEngineFlag,
StateSchemeFlag,
@@ -1613,6 +1619,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(AncientFlag.Name) {
cfg.DatabaseFreezer = ctx.String(AncientFlag.Name)
}
+ if ctx.IsSet(EraFlag.Name) {
+ cfg.DatabaseEra = ctx.String(EraFlag.Name)
+ }
if gcmode := ctx.String(GCModeFlag.Name); gcmode != "full" && gcmode != "archive" {
Fatalf("--%s must be either 'full' or 'archive'", GCModeFlag.Name)
@@ -2082,7 +2091,15 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb.
}
chainDb = remotedb.New(client)
default:
- chainDb, err = stack.OpenDatabaseWithFreezer("chaindata", cache, handles, ctx.String(AncientFlag.Name), "eth/db/chaindata/", readonly)
+ options := node.DatabaseOptions{
+ ReadOnly: readonly,
+ Cache: cache,
+ Handles: handles,
+ AncientsDirectory: ctx.String(AncientFlag.Name),
+ MetricsNamespace: "eth/db/chaindata/",
+ EraDirectory: ctx.String(EraFlag.Name),
+ }
+ chainDb, err = stack.OpenDatabaseWithOptions("chaindata", options)
}
if err != nil {
Fatalf("Could not open database: %v", err)
diff --git a/cmd/utils/history_test.go b/cmd/utils/history_test.go
index d3c6bda1c5..be51803f8c 100644
--- a/cmd/utils/history_test.go
+++ b/cmd/utils/history_test.go
@@ -158,7 +158,7 @@ func TestHistoryImportAndExport(t *testing.T) {
}
// Now import Era.
- db2, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
+ db2, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
if err != nil {
panic(err)
}
diff --git a/common/lru/basiclru.go b/common/lru/basiclru.go
index 7386c77840..154831a796 100644
--- a/common/lru/basiclru.go
+++ b/common/lru/basiclru.go
@@ -47,30 +47,37 @@ func NewBasicLRU[K comparable, V any](capacity int) BasicLRU[K, V] {
// Add adds a value to the cache. Returns true if an item was evicted to store the new item.
func (c *BasicLRU[K, V]) Add(key K, value V) (evicted bool) {
+ _, _, evicted = c.Add3(key, value)
+ return evicted
+}
+
+// Add3 adds a value to the cache. If an item was evicted to store the new one, it returns the evicted item.
+func (c *BasicLRU[K, V]) Add3(key K, value V) (ek K, ev V, evicted bool) {
item, ok := c.items[key]
if ok {
- // Already exists in cache.
item.value = value
c.items[key] = item
c.list.moveToFront(item.elem)
- return false
+ return ek, ev, false
}
var elem *listElem[K]
if c.Len() >= c.cap {
elem = c.list.removeLast()
- delete(c.items, elem.v)
evicted = true
+ ek = elem.v
+ ev = c.items[ek].value
+ delete(c.items, ek)
} else {
elem = new(listElem[K])
}
// Store the new item.
- // Note that, if another item was evicted, we re-use its list element here.
+ // Note that if another item was evicted, we re-use its list element here.
elem.v = key
c.items[key] = cacheItem[K, V]{elem, value}
c.list.pushElem(elem)
- return evicted
+ return ek, ev, evicted
}
// Contains reports whether the given key exists in the cache.
diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go
index edc8854892..9661cee4c7 100644
--- a/core/blockchain_repair_test.go
+++ b/core/blockchain_repair_test.go
@@ -1769,7 +1769,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err)
}
- db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false)
+ db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
if err != nil {
t.Fatalf("Failed to create persistent freezer database: %v", err)
}
@@ -1854,7 +1854,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s
if err != nil {
t.Fatalf("Failed to reopen persistent key-value database: %v", err)
}
- db, err = rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false)
+ db, err = rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
if err != nil {
t.Fatalf("Failed to reopen persistent freezer database: %v", err)
}
@@ -1919,7 +1919,7 @@ func testIssue23496(t *testing.T, scheme string) {
if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err)
}
- db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false)
+ db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
if err != nil {
t.Fatalf("Failed to create persistent freezer database: %v", err)
}
@@ -1979,7 +1979,7 @@ func testIssue23496(t *testing.T, scheme string) {
if err != nil {
t.Fatalf("Failed to reopen persistent key-value database: %v", err)
}
- db, err = rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false)
+ db, err = rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
if err != nil {
t.Fatalf("Failed to reopen persistent freezer database: %v", err)
}
diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go
index 51e2a5275f..b094ed3b65 100644
--- a/core/blockchain_sethead_test.go
+++ b/core/blockchain_sethead_test.go
@@ -1973,7 +1973,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme
if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err)
}
- db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false)
+ db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
if err != nil {
t.Fatalf("Failed to create persistent freezer database: %v", err)
}
diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go
index 23640fe843..e12a0c67c4 100644
--- a/core/blockchain_snapshot_test.go
+++ b/core/blockchain_snapshot_test.go
@@ -70,7 +70,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo
if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err)
}
- db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false)
+ db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
if err != nil {
t.Fatalf("Failed to create persistent freezer database: %v", err)
}
@@ -265,7 +265,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err)
}
- newdb, err := rawdb.NewDatabaseWithFreezer(pdb, snaptest.ancient, "", false)
+ newdb, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: snaptest.ancient})
if err != nil {
t.Fatalf("Failed to create persistent freezer database: %v", err)
}
diff --git a/core/blockchain_test.go b/core/blockchain_test.go
index 434b494490..2401402f32 100644
--- a/core/blockchain_test.go
+++ b/core/blockchain_test.go
@@ -738,7 +738,7 @@ func testFastVsFullChains(t *testing.T, scheme string) {
t.Fatalf("failed to insert receipt %d: %v", n, err)
}
// Freezer style fast import the chain.
- ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
+ ancientDb, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -824,7 +824,7 @@ func testLightVsFastVsFullChainHeads(t *testing.T, scheme string) {
// makeDb creates a db instance for testing.
makeDb := func() ethdb.Database {
- db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
+ db, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -1623,7 +1623,7 @@ func testLargeReorgTrieGC(t *testing.T, scheme string) {
competitor, _ := GenerateChain(genesis.Config, shared[len(shared)-1], engine, genDb, 2*state.TriesInMemory+1, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{3}) })
// Import the shared chain and the original canonical one
- db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
+ db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
defer db.Close()
chain, err := NewBlockChain(db, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil)
@@ -1689,7 +1689,7 @@ func testBlockchainRecovery(t *testing.T, scheme string) {
_, blocks, receipts := GenerateChainWithGenesis(gspec, ethash.NewFaker(), int(height), nil)
// Import the chain as a ancient-first node and ensure all pointers are updated
- ancientDb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
+ ancientDb, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{Ancient: t.TempDir()})
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -1747,7 +1747,7 @@ func testLowDiffLongChain(t *testing.T, scheme string) {
})
// Import the canonical chain
- diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
+ diskdb, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
defer diskdb.Close()
chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), genesis, nil, engine, vm.Config{}, nil)
@@ -1959,7 +1959,7 @@ func testInsertKnownChainData(t *testing.T, typ string, scheme string) {
b.OffsetTime(-9) // A higher difficulty
})
// Import the shared chain and the original canonical one
- chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
+ chaindb, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -2122,7 +2122,7 @@ func testInsertKnownChainDataWithMerging(t *testing.T, typ string, mergeHeight i
}
})
// Import the shared chain and the original canonical one
- chaindb, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
+ chaindb, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
if err != nil {
t.Fatalf("failed to create temp freezer db: %v", err)
}
@@ -2496,7 +2496,7 @@ func testSideImportPrunedBlocks(t *testing.T, scheme string) {
if err != nil {
t.Fatalf("Failed to create persistent key-value database: %v", err)
}
- db, err := rawdb.NewDatabaseWithFreezer(pdb, ancient, "", false)
+ db, err := rawdb.Open(pdb, rawdb.OpenOptions{Ancient: ancient})
if err != nil {
t.Fatalf("Failed to create persistent freezer database: %v", err)
}
@@ -3403,7 +3403,7 @@ func testSetCanonical(t *testing.T, scheme string) {
}
gen.AddTx(tx)
})
- diskdb, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
+ diskdb, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
defer diskdb.Close()
chain, err := NewBlockChain(diskdb, DefaultCacheConfigWithScheme(scheme), gspec, nil, engine, vm.Config{}, nil)
@@ -4199,7 +4199,7 @@ func testChainReorgSnapSync(t *testing.T, ancientLimit uint64) {
gen.SetCoinbase(common.Address{0: byte(0xb), 19: byte(i)})
})
- db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
+ db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
defer db.Close()
chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
@@ -4315,7 +4315,7 @@ func testInsertChainWithCutoff(t *testing.T, cutoff uint64, ancientLimit uint64,
config := DefaultCacheConfigWithScheme(rawdb.PathScheme)
config.ChainHistoryMode = history.KeepPostMerge
- db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
+ db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
defer db.Close()
chain, _ := NewBlockChain(db, DefaultCacheConfigWithScheme(rawdb.PathScheme), genesis, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil)
defer chain.Stop()
diff --git a/core/rawdb/accessors_chain_test.go b/core/rawdb/accessors_chain_test.go
index d98fc9a1eb..862b8cd2fd 100644
--- a/core/rawdb/accessors_chain_test.go
+++ b/core/rawdb/accessors_chain_test.go
@@ -416,7 +416,7 @@ func checkReceiptsRLP(have, want types.Receipts) error {
func TestAncientStorage(t *testing.T) {
// Freezer style fast import the chain.
frdir := t.TempDir()
- db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false)
+ db, err := Open(NewMemoryDatabase(), OpenOptions{Ancient: frdir})
if err != nil {
t.Fatalf("failed to create database with ancient backend")
}
@@ -469,7 +469,7 @@ func TestAncientStorage(t *testing.T) {
}
func TestWriteAncientHeaderChain(t *testing.T) {
- db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), t.TempDir(), "", false)
+ db, err := Open(NewMemoryDatabase(), OpenOptions{Ancient: t.TempDir()})
if err != nil {
t.Fatalf("failed to create database with ancient backend")
}
@@ -586,7 +586,7 @@ func TestHashesInRange(t *testing.T) {
func BenchmarkWriteAncientBlocks(b *testing.B) {
// Open freezer database.
frdir := b.TempDir()
- db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false)
+ db, err := Open(NewMemoryDatabase(), OpenOptions{Ancient: frdir})
if err != nil {
b.Fatalf("failed to create database with ancient backend")
}
@@ -890,7 +890,7 @@ func TestHeadersRLPStorage(t *testing.T) {
// Have N headers in the freezer
frdir := t.TempDir()
- db, err := NewDatabaseWithFreezer(NewMemoryDatabase(), frdir, "", false)
+ db, err := Open(NewMemoryDatabase(), OpenOptions{Ancient: frdir})
if err != nil {
t.Fatalf("failed to create database with ancient backend")
}
diff --git a/core/rawdb/ancienttest/testsuite.go b/core/rawdb/ancienttest/testsuite.go
index 70de263c04..e33e768947 100644
--- a/core/rawdb/ancienttest/testsuite.go
+++ b/core/rawdb/ancienttest/testsuite.go
@@ -77,13 +77,6 @@ func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
}
for _, c := range cases {
for i := c.start; i < c.limit; i++ {
- exist, err := db.HasAncient("a", uint64(i))
- if err != nil {
- t.Fatalf("Failed to check presence, %v", err)
- }
- if exist {
- t.Fatalf("Item %d is already truncated", uint64(i))
- }
_, err = db.Ancient("a", uint64(i))
if err == nil {
t.Fatal("Error is expected for non-existent item")
@@ -93,13 +86,6 @@ func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
// Test the items in range should be reachable
for i := 10; i < 90; i++ {
- exist, err := db.HasAncient("a", uint64(i))
- if err != nil {
- t.Fatalf("Failed to check presence, %v", err)
- }
- if !exist {
- t.Fatalf("Item %d is missing", uint64(i))
- }
blob, err := db.Ancient("a", uint64(i))
if err != nil {
t.Fatalf("Failed to retrieve item, %v", err)
@@ -110,13 +96,6 @@ func basicRead(t *testing.T, newFn func(kinds []string) ethdb.AncientStore) {
}
// Test the items in unknown table shouldn't be reachable
- exist, err := db.HasAncient("b", uint64(0))
- if err != nil {
- t.Fatalf("Failed to check presence, %v", err)
- }
- if exist {
- t.Fatal("Item in unknown table shouldn't be found")
- }
_, err = db.Ancient("b", uint64(0))
if err == nil {
t.Fatal("Error is expected for unknown table")
diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go
index cc7a62df32..3a5218c023 100644
--- a/core/rawdb/chain_freezer.go
+++ b/core/rawdb/chain_freezer.go
@@ -23,6 +23,7 @@ import (
"time"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/rawdb/eradb"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
@@ -43,7 +44,10 @@ const (
// feature. The background thread will keep moving ancient chain segments from
// key-value database to flat files for saving space on live database.
type chainFreezer struct {
- ethdb.AncientStore // Ancient store for storing cold chain segment
+ ancients ethdb.AncientStore // Ancient store for storing cold chain segment
+
+ // Optional Era database used as a backup for the pruned chain.
+ eradb *eradb.Store
quit chan struct{}
wg sync.WaitGroup
@@ -56,23 +60,27 @@ type chainFreezer struct {
// state freezer (e.g. dev mode).
// - if non-empty directory is given, initializes the regular file-based
// state freezer.
-func newChainFreezer(datadir string, namespace string, readonly bool) (*chainFreezer, error) {
- var (
- err error
- freezer ethdb.AncientStore
- )
+func newChainFreezer(datadir string, eraDir string, namespace string, readonly bool) (*chainFreezer, error) {
if datadir == "" {
- freezer = NewMemoryFreezer(readonly, chainFreezerTableConfigs)
- } else {
- freezer, err = NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerTableConfigs)
+ return &chainFreezer{
+ ancients: NewMemoryFreezer(readonly, chainFreezerTableConfigs),
+ quit: make(chan struct{}),
+ trigger: make(chan chan struct{}),
+ }, nil
}
+ freezer, err := NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerTableConfigs)
+ if err != nil {
+ return nil, err
+ }
+ edb, err := eradb.New(resolveChainEraDir(datadir, eraDir))
if err != nil {
return nil, err
}
return &chainFreezer{
- AncientStore: freezer,
- quit: make(chan struct{}),
- trigger: make(chan chan struct{}),
+ ancients: freezer,
+ eradb: edb,
+ quit: make(chan struct{}),
+ trigger: make(chan chan struct{}),
}, nil
}
@@ -84,7 +92,11 @@ func (f *chainFreezer) Close() error {
close(f.quit)
}
f.wg.Wait()
- return f.AncientStore.Close()
+
+ if f.eradb != nil {
+ f.eradb.Close()
+ }
+ return f.ancients.Close()
}
// readHeadNumber returns the number of chain head block. 0 is returned if the
@@ -334,3 +346,75 @@ func (f *chainFreezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hash
})
return hashes, err
}
+
+// Ancient retrieves an ancient binary blob from the append-only immutable files.
+func (f *chainFreezer) Ancient(kind string, number uint64) ([]byte, error) {
+ // Lookup the entry in the underlying ancient store, assuming that
+ // headers and hashes are always available.
+ if kind == ChainFreezerHeaderTable || kind == ChainFreezerHashTable {
+ return f.ancients.Ancient(kind, number)
+ }
+ tail, err := f.ancients.Tail()
+ if err != nil {
+ return nil, err
+ }
+ // Lookup the entry in the underlying ancient store if it's not pruned
+ if number >= tail {
+ return f.ancients.Ancient(kind, number)
+ }
+ // Lookup the entry in the optional era backend
+ if f.eradb == nil {
+ return nil, errOutOfBounds
+ }
+ switch kind {
+ case ChainFreezerBodiesTable:
+ return f.eradb.GetRawBody(number)
+ case ChainFreezerReceiptTable:
+ return f.eradb.GetRawReceipts(number)
+ }
+ return nil, errUnknownTable
+}
+
+// ReadAncients executes an operation while preventing mutations to the freezer,
+// i.e. if fn performs multiple reads, they will be consistent with each other.
+func (f *chainFreezer) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) {
+ if store, ok := f.ancients.(*Freezer); ok {
+ store.writeLock.Lock()
+ defer store.writeLock.Unlock()
+ }
+ return fn(f)
+}
+
+// Methods below are just pass-through to the underlying ancient store.
+
+func (f *chainFreezer) Ancients() (uint64, error) {
+ return f.ancients.Ancients()
+}
+
+func (f *chainFreezer) Tail() (uint64, error) {
+ return f.ancients.Tail()
+}
+
+func (f *chainFreezer) AncientSize(kind string) (uint64, error) {
+ return f.ancients.AncientSize(kind)
+}
+
+func (f *chainFreezer) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) {
+ return f.ancients.AncientRange(kind, start, count, maxBytes)
+}
+
+func (f *chainFreezer) ModifyAncients(fn func(ethdb.AncientWriteOp) error) (int64, error) {
+ return f.ancients.ModifyAncients(fn)
+}
+
+func (f *chainFreezer) TruncateHead(items uint64) (uint64, error) {
+ return f.ancients.TruncateHead(items)
+}
+
+func (f *chainFreezer) TruncateTail(items uint64) (uint64, error) {
+ return f.ancients.TruncateTail(items)
+}
+
+func (f *chainFreezer) SyncAncient() error {
+ return f.ancients.SyncAncient()
+}
diff --git a/core/rawdb/database.go b/core/rawdb/database.go
index a03dbafb1f..86f4ac19cb 100644
--- a/core/rawdb/database.go
+++ b/core/rawdb/database.go
@@ -86,11 +86,6 @@ type nofreezedb struct {
ethdb.KeyValueStore
}
-// HasAncient returns an error as we don't have a backing chain freezer.
-func (db *nofreezedb) HasAncient(kind string, number uint64) (bool, error) {
- return false, errNotSupported
-}
-
// Ancient returns an error as we don't have a backing chain freezer.
func (db *nofreezedb) Ancient(kind string, number uint64) ([]byte, error) {
return nil, errNotSupported
@@ -186,19 +181,49 @@ func resolveChainFreezerDir(ancient string) string {
return freezer
}
-// NewDatabaseWithFreezer creates a high level database on top of a given key-
-// value data store with a freezer moving immutable chain segments into cold
-// storage. The passed ancient indicates the path of root ancient directory
-// where the chain freezer can be opened.
+// resolveChainEraDir is a helper function which resolves the absolute path of era database.
+func resolveChainEraDir(chainFreezerDir string, era string) string {
+ switch {
+ case era == "":
+ return filepath.Join(chainFreezerDir, "era")
+ case !filepath.IsAbs(era):
+ return filepath.Join(chainFreezerDir, era)
+ default:
+ return era
+ }
+}
+
+// NewDatabaseWithFreezer creates a high level database on top of a given key-value store.
+// The passed ancient indicates the path of root ancient directory where the chain freezer
+// can be opened.
+//
+// Deprecated: use Open.
func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace string, readonly bool) (ethdb.Database, error) {
+ return Open(db, OpenOptions{
+ Ancient: ancient,
+ MetricsNamespace: namespace,
+ ReadOnly: readonly,
+ })
+}
+
+// OpenOptions specifies options for opening the database.
+type OpenOptions struct {
+ Ancient string // ancients directory
+ Era string // era files directory
+ MetricsNamespace string // prefix added to freezer metric names
+ ReadOnly bool
+}
+
+// Open creates a high-level database wrapper for the given key-value store.
+func Open(db ethdb.KeyValueStore, opts OpenOptions) (ethdb.Database, error) {
// Create the idle freezer instance. If the given ancient directory is empty,
// in-memory chain freezer is used (e.g. dev mode); otherwise the regular
// file-based freezer is created.
- chainFreezerDir := ancient
+ chainFreezerDir := opts.Ancient
if chainFreezerDir != "" {
chainFreezerDir = resolveChainFreezerDir(chainFreezerDir)
}
- frdb, err := newChainFreezer(chainFreezerDir, namespace, readonly)
+ frdb, err := newChainFreezer(chainFreezerDir, opts.Era, opts.MetricsNamespace, opts.ReadOnly)
if err != nil {
printChainMetadata(db)
return nil, err
@@ -282,7 +307,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
}
}
// Freezer is consistent with the key-value database, permit combining the two
- if !readonly {
+ if !opts.ReadOnly {
frdb.wg.Add(1)
go func() {
frdb.freeze(db)
@@ -290,7 +315,7 @@ func NewDatabaseWithFreezer(db ethdb.KeyValueStore, ancient string, namespace st
}()
}
return &freezerdb{
- ancientRoot: ancient,
+ ancientRoot: opts.Ancient,
KeyValueStore: db,
chainFreezer: frdb,
}, nil
diff --git a/core/rawdb/eradb/eradb.go b/core/rawdb/eradb/eradb.go
new file mode 100644
index 0000000000..29e658798e
--- /dev/null
+++ b/core/rawdb/eradb/eradb.go
@@ -0,0 +1,345 @@
+// Copyright 2025 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// 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
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io/fs"
+ "path/filepath"
+ "sync"
+
+ "github.com/ethereum/go-ethereum/common/lru"
+ "github.com/ethereum/go-ethereum/internal/era"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+const openFileLimit = 64
+
+var errClosed = errors.New("era store is closed")
+
+// Store manages read access to a directory of era1 files.
+// The getter methods are thread-safe.
+type Store struct {
+ datadir string
+
+ // The mutex protects all remaining fields.
+ mu sync.Mutex
+ cond *sync.Cond
+ lru lru.BasicLRU[uint64, *fileCacheEntry]
+ opening map[uint64]*fileCacheEntry
+ closing bool
+}
+
+type fileCacheEntry struct {
+ refcount int // reference count. This is protected by Store.mu!
+ opened chan struct{} // signals opening of file has completed
+ file *era.Era // the file
+ err error // error from opening the file
+}
+
+type fileCacheStatus byte
+
+const (
+ storeClosing fileCacheStatus = iota
+ fileIsNew
+ fileIsOpening
+ fileIsCached
+)
+
+// New opens the store directory.
+func New(datadir string) (*Store, error) {
+ db := &Store{
+ datadir: datadir,
+ lru: lru.NewBasicLRU[uint64, *fileCacheEntry](openFileLimit),
+ opening: make(map[uint64]*fileCacheEntry),
+ }
+ db.cond = sync.NewCond(&db.mu)
+ log.Info("Opened Era store", "datadir", datadir)
+ return db, nil
+}
+
+// Close closes all open era1 files in the cache.
+func (db *Store) Close() {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ // Prevent new cache additions.
+ db.closing = true
+
+ // Deref all active files. Since inactive files have a refcount of one, they will be
+ // closed right here and now after decrementing. Files which are currently being used
+ // have a refcount > 1 and will hit zero when their access finishes.
+ for _, epoch := range db.lru.Keys() {
+ entry, _ := db.lru.Peek(epoch)
+ if entry.derefAndClose(epoch) {
+ db.lru.Remove(epoch)
+ }
+ }
+
+ // Wait for all store access to finish.
+ for db.lru.Len() > 0 || len(db.opening) > 0 {
+ db.cond.Wait()
+ }
+}
+
+// GetRawBody returns the raw body for a given block number.
+func (db *Store) GetRawBody(number uint64) ([]byte, error) {
+ epoch := number / uint64(era.MaxEra1Size)
+ 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.GetRawBodyByNumber(number)
+}
+
+// GetRawReceipts returns the raw receipts for a given block number.
+func (db *Store) GetRawReceipts(number uint64) ([]byte, error) {
+ epoch := number / uint64(era.MaxEra1Size)
+ 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)
+
+ data, err := entry.file.GetRawReceiptsByNumber(number)
+ if err != nil {
+ return nil, err
+ }
+ return convertReceipts(data)
+}
+
+// convertReceipts transforms an encoded block receipts list from the format
+// used by era1 into the 'storage' format used by the go-ethereum ancients database.
+func convertReceipts(input []byte) ([]byte, error) {
+ var (
+ out bytes.Buffer
+ enc = rlp.NewEncoderBuffer(&out)
+ )
+ blockListIter, err := rlp.NewListIterator(input)
+ if err != nil {
+ return nil, fmt.Errorf("invalid block receipts list: %v", err)
+ }
+ outerList := enc.List()
+ for i := 0; blockListIter.Next(); i++ {
+ kind, content, _, err := rlp.Split(blockListIter.Value())
+ if err != nil {
+ return nil, fmt.Errorf("receipt %d invalid: %v", i, err)
+ }
+ var receiptData []byte
+ switch kind {
+ case rlp.Byte:
+ return nil, fmt.Errorf("receipt %d is single byte", i)
+ case rlp.String:
+ // Typed receipt - skip type.
+ receiptData = content[1:]
+ case rlp.List:
+ // Legacy receipt
+ receiptData = blockListIter.Value()
+ }
+ // Convert data list.
+ // Input is [status, gas-used, bloom, logs]
+ // Output is [status, gas-used, logs], i.e. we need to skip the bloom.
+ dataIter, err := rlp.NewListIterator(receiptData)
+ if err != nil {
+ return nil, fmt.Errorf("receipt %d has invalid data: %v", i, err)
+ }
+ innerList := enc.List()
+ for field := 0; dataIter.Next(); field++ {
+ if field == 2 {
+ continue // skip bloom
+ }
+ enc.Write(dataIter.Value())
+ }
+ enc.ListEnd(innerList)
+ if dataIter.Err() != nil {
+ return nil, fmt.Errorf("receipt %d iterator error: %v", i, dataIter.Err())
+ }
+ }
+ enc.ListEnd(outerList)
+ if blockListIter.Err() != nil {
+ return nil, fmt.Errorf("block receipt list iterator error: %v", blockListIter.Err())
+ }
+ enc.Flush()
+ return out.Bytes(), nil
+}
+
+// getEraByEpoch opens an era file or gets it from the cache.
+// The caller can freely access the returned entry's .file and .err
+// db.doneWithFile must be called when it is done reading the file.
+func (db *Store) getEraByEpoch(epoch uint64) *fileCacheEntry {
+ stat, entry := db.getCacheEntry(epoch)
+
+ switch stat {
+ case storeClosing:
+ return &fileCacheEntry{err: errClosed}
+
+ case fileIsNew:
+ // Open the file and put it into the cache.
+ e, err := db.openEraFile(epoch)
+ if err != nil {
+ db.fileFailedToOpen(epoch, entry, err)
+ } else {
+ db.fileOpened(epoch, entry, e)
+ }
+ close(entry.opened)
+
+ case fileIsOpening:
+ // Wait for open to finish.
+ <-entry.opened
+
+ case fileIsCached:
+ // Nothing to do.
+
+ default:
+ panic(fmt.Sprintf("invalid file state %d", stat))
+ }
+ return entry
+}
+
+// getCacheEntry gets an open era file from the cache.
+func (db *Store) getCacheEntry(epoch uint64) (stat fileCacheStatus, entry *fileCacheEntry) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ if db.closing {
+ return storeClosing, nil
+ }
+ if entry = db.opening[epoch]; entry != nil {
+ stat = fileIsOpening
+ } else if entry, _ = db.lru.Get(epoch); entry != nil {
+ stat = fileIsCached
+ } else {
+ // It's a new file, create an entry in the opening table. Note the entry is
+ // created with an initial refcount of one. We increment the count once more
+ // before returning, but the count will return to one when the file has been
+ // accessed. When the store is closed or the file gets evicted from the cache,
+ // refcount will be decreased by one, thus allowing it to hit zero.
+ entry = &fileCacheEntry{refcount: 1, opened: make(chan struct{})}
+ db.opening[epoch] = entry
+ stat = fileIsNew
+ }
+ entry.refcount++
+ return stat, entry
+}
+
+// fileOpened is called after an era file has been successfully opened.
+func (db *Store) fileOpened(epoch uint64, entry *fileCacheEntry, file *era.Era) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ delete(db.opening, epoch)
+ db.cond.Signal() // db.opening was modified
+
+ // The database may have been closed while opening the file. When that happens, we
+ // need to close the file here, since it isn't tracked by the LRU yet.
+ if db.closing {
+ entry.err = errClosed
+ file.Close()
+ return
+ }
+
+ // Add it to the LRU. This may evict an existing item, which we have to close.
+ entry.file = file
+ evictedEpoch, evictedEntry, _ := db.lru.Add3(epoch, entry)
+ if evictedEntry != nil {
+ evictedEntry.derefAndClose(evictedEpoch)
+ }
+}
+
+// fileFailedToOpen is called when an era file could not be opened.
+func (db *Store) fileFailedToOpen(epoch uint64, entry *fileCacheEntry, err error) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ delete(db.opening, epoch)
+ db.cond.Signal() // db.opening was modified
+ entry.err = err
+}
+
+func (db *Store) openEraFile(epoch uint64) (*era.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
+ }
+ 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 := era.Open(filename)
+ if err != nil {
+ return nil, err
+ }
+ // Sanity-check start block.
+ if e.Start()%uint64(era.MaxEra1Size) != 0 {
+ return nil, fmt.Errorf("pre-merge era1 file has invalid boundary. %d %% %d != 0", e.Start(), era.MaxEra1Size)
+ }
+ log.Debug("Opened era1 file", "epoch", epoch)
+ return e, nil
+}
+
+// doneWithFile signals that the caller has finished using a file.
+// This decrements the refcount and ensures the file is closed by the last user.
+func (db *Store) doneWithFile(epoch uint64, entry *fileCacheEntry) {
+ db.mu.Lock()
+ defer db.mu.Unlock()
+
+ if entry.err != nil {
+ return
+ }
+ if entry.derefAndClose(epoch) {
+ // Delete closed entry from LRU if it is still present.
+ if e, _ := db.lru.Peek(epoch); e == entry {
+ db.lru.Remove(epoch)
+ db.cond.Signal() // db.lru was modified
+ }
+ }
+}
+
+// derefAndClose decrements the reference counter and closes the file
+// when it hits zero.
+func (entry *fileCacheEntry) derefAndClose(epoch uint64) (closed bool) {
+ entry.refcount--
+ if entry.refcount > 0 {
+ return false
+ }
+
+ closeErr := entry.file.Close()
+ if closeErr == nil {
+ log.Debug("Closed era1 file", "epoch", epoch)
+ } else {
+ log.Warn("Error closing era1 file", "epoch", epoch, "err", closeErr)
+ }
+ return true
+}
diff --git a/core/rawdb/eradb/eradb_test.go b/core/rawdb/eradb/eradb_test.go
new file mode 100644
index 0000000000..41047dbbe9
--- /dev/null
+++ b/core/rawdb/eradb/eradb_test.go
@@ -0,0 +1,103 @@
+// Copyright 2025 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package eradb
+
+import (
+ "sync"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestEraDatabase(t *testing.T) {
+ db, err := New("testdata")
+ require.NoError(t, err)
+ defer db.Close()
+
+ r, err := db.GetRawBody(175881)
+ require.NoError(t, err)
+ var body *types.Body
+ err = rlp.DecodeBytes(r, &body)
+ require.NoError(t, err)
+ require.NotNil(t, body, "block body not found")
+ assert.Equal(t, 3, len(body.Transactions))
+
+ r, err = db.GetRawReceipts(175881)
+ require.NoError(t, err)
+ var receipts []*types.ReceiptForStorage
+ err = rlp.DecodeBytes(r, &receipts)
+ require.NoError(t, err)
+ require.NotNil(t, receipts, "receipts not found")
+ assert.Equal(t, 3, len(receipts), "receipts length mismatch")
+}
+
+func TestEraDatabaseConcurrentOpen(t *testing.T) {
+ db, err := New("testdata")
+ require.NoError(t, err)
+ defer db.Close()
+
+ const N = 25
+ var wg sync.WaitGroup
+ wg.Add(N)
+ for range N {
+ go func() {
+ defer wg.Done()
+ r, err := db.GetRawBody(1024)
+ if err != nil {
+ t.Error("err:", err)
+ }
+ if len(r) == 0 {
+ t.Error("empty body")
+ }
+ }()
+ }
+ wg.Wait()
+}
+
+func TestEraDatabaseConcurrentOpenClose(t *testing.T) {
+ db, err := New("testdata")
+ require.NoError(t, err)
+ defer db.Close()
+
+ const N = 10
+ var wg sync.WaitGroup
+ wg.Add(N)
+ for range N {
+ go func() {
+ defer wg.Done()
+ r, err := db.GetRawBody(1024)
+ if err == errClosed {
+ return
+ }
+ if err != nil {
+ t.Error("err:", err)
+ }
+ if len(r) == 0 {
+ t.Error("empty body")
+ }
+ }()
+ }
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ db.Close()
+ }()
+ wg.Wait()
+}
diff --git a/core/rawdb/eradb/testdata/sepolia-00000-643a00f7.era1 b/core/rawdb/eradb/testdata/sepolia-00000-643a00f7.era1
new file mode 100644
index 0000000000..a601a40c23
Binary files /dev/null and b/core/rawdb/eradb/testdata/sepolia-00000-643a00f7.era1 differ
diff --git a/core/rawdb/eradb/testdata/sepolia-00021-b8814b14.era1 b/core/rawdb/eradb/testdata/sepolia-00021-b8814b14.era1
new file mode 100644
index 0000000000..2b5ce2bc75
Binary files /dev/null and b/core/rawdb/eradb/testdata/sepolia-00021-b8814b14.era1 differ
diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go
index 1e5b98c7fe..a9600c1eef 100644
--- a/core/rawdb/freezer.go
+++ b/core/rawdb/freezer.go
@@ -172,10 +172,7 @@ func (f *Freezer) Close() error {
errs = append(errs, err)
}
})
- if errs != nil {
- return fmt.Errorf("%v", errs)
- }
- return nil
+ return errors.Join(errs...)
}
// AncientDatadir returns the path of the ancient store.
@@ -183,15 +180,6 @@ func (f *Freezer) AncientDatadir() (string, error) {
return f.datadir, nil
}
-// HasAncient returns an indicator whether the specified ancient data exists
-// in the freezer.
-func (f *Freezer) HasAncient(kind string, number uint64) (bool, error) {
- if table := f.tables[kind]; table != nil {
- return table.has(number), nil
- }
- return false, nil
-}
-
// Ancient retrieves an ancient binary blob from the append-only immutable files.
func (f *Freezer) Ancient(kind string, number uint64) ([]byte, error) {
if table := f.tables[kind]; table != nil {
diff --git a/core/rawdb/freezer_memory.go b/core/rawdb/freezer_memory.go
index bd286f45f5..f5621ac4c6 100644
--- a/core/rawdb/freezer_memory.go
+++ b/core/rawdb/freezer_memory.go
@@ -45,14 +45,6 @@ func newMemoryTable(name string, config freezerTableConfig) *memoryTable {
return &memoryTable{name: name, config: config}
}
-// has returns an indicator whether the specified data exists.
-func (t *memoryTable) has(number uint64) bool {
- t.lock.RLock()
- defer t.lock.RUnlock()
-
- return number >= t.offset && number < t.items
-}
-
// retrieve retrieves multiple items in sequence, starting from the index 'start'.
// It will return:
// - at most 'count' items,
@@ -232,17 +224,6 @@ func NewMemoryFreezer(readonly bool, tableName map[string]freezerTableConfig) *M
}
}
-// HasAncient returns an indicator whether the specified data exists.
-func (f *MemoryFreezer) HasAncient(kind string, number uint64) (bool, error) {
- f.lock.RLock()
- defer f.lock.RUnlock()
-
- if table := f.tables[kind]; table != nil {
- return table.has(number), nil
- }
- return false, nil
-}
-
// Ancient retrieves an ancient binary blob from the in-memory freezer.
func (f *MemoryFreezer) Ancient(kind string, number uint64) ([]byte, error) {
f.lock.RLock()
diff --git a/core/rawdb/freezer_resettable.go b/core/rawdb/freezer_resettable.go
index 01df2877d9..9db71cfd0e 100644
--- a/core/rawdb/freezer_resettable.go
+++ b/core/rawdb/freezer_resettable.go
@@ -105,15 +105,6 @@ func (f *resettableFreezer) Close() error {
return f.freezer.Close()
}
-// HasAncient returns an indicator whether the specified ancient data exists
-// in the freezer
-func (f *resettableFreezer) HasAncient(kind string, number uint64) (bool, error) {
- f.lock.RLock()
- defer f.lock.RUnlock()
-
- return f.freezer.HasAncient(kind, number)
-}
-
// Ancient retrieves an ancient binary blob from the append-only immutable files.
func (f *resettableFreezer) Ancient(kind string, number uint64) ([]byte, error) {
f.lock.RLock()
diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go
index aec24b207e..3900b5d558 100644
--- a/core/rawdb/freezer_table.go
+++ b/core/rawdb/freezer_table.go
@@ -1106,12 +1106,6 @@ func (t *freezerTable) retrieveItems(start, count, maxBytes uint64) ([]byte, []i
return output, sizes, nil
}
-// has returns an indicator whether the specified number data is still accessible
-// in the freezer table.
-func (t *freezerTable) has(number uint64) bool {
- return t.items.Load() > number && t.itemHidden.Load() <= number
-}
-
// size returns the total data size in the freezer table.
func (t *freezerTable) size() (uint64, error) {
t.lock.RLock()
diff --git a/core/rawdb/freezer_test.go b/core/rawdb/freezer_test.go
index a7a3559ec4..b8a3d4a6d2 100644
--- a/core/rawdb/freezer_test.go
+++ b/core/rawdb/freezer_test.go
@@ -357,9 +357,6 @@ func checkAncientCount(t *testing.T, f *Freezer, kind string, n uint64) {
// Check at index n-1.
if n > 0 {
index := n - 1
- if ok, _ := f.HasAncient(kind, index); !ok {
- t.Errorf("HasAncient(%q, %d) returned false unexpectedly", kind, index)
- }
if _, err := f.Ancient(kind, index); err != nil {
t.Errorf("Ancient(%q, %d) returned unexpected error %q", kind, index, err)
}
@@ -367,9 +364,6 @@ func checkAncientCount(t *testing.T, f *Freezer, kind string, n uint64) {
// Check at index n.
index := n
- if ok, _ := f.HasAncient(kind, index); ok {
- t.Errorf("HasAncient(%q, %d) returned true unexpectedly", kind, index)
- }
if _, err := f.Ancient(kind, index); err == nil {
t.Errorf("Ancient(%q, %d) didn't return expected error", kind, index)
} else if err != errOutOfBounds {
diff --git a/core/rawdb/table.go b/core/rawdb/table.go
index 9a342a8217..5eb3fe45d1 100644
--- a/core/rawdb/table.go
+++ b/core/rawdb/table.go
@@ -50,12 +50,6 @@ func (t *table) Get(key []byte) ([]byte, error) {
return t.db.Get(append([]byte(t.prefix), key...))
}
-// HasAncient is a noop passthrough that just forwards the request to the underlying
-// database.
-func (t *table) HasAncient(kind string, number uint64) (bool, error) {
- return t.db.HasAncient(kind, number)
-}
-
// Ancient is a noop passthrough that just forwards the request to the underlying
// database.
func (t *table) Ancient(kind string, number uint64) ([]byte, error) {
diff --git a/core/txindexer_test.go b/core/txindexer_test.go
index 615a34de41..71c78d506b 100644
--- a/core/txindexer_test.go
+++ b/core/txindexer_test.go
@@ -116,7 +116,7 @@ func TestTxIndexer(t *testing.T) {
},
}
for _, c := range cases {
- db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
+ db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...)))
// Index the initial blocks from ancient store
@@ -235,7 +235,7 @@ func TestTxIndexerRepair(t *testing.T) {
},
}
for _, c := range cases {
- db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
+ db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
encReceipts := types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...))
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), encReceipts)
@@ -426,7 +426,7 @@ func TestTxIndexerReport(t *testing.T) {
},
}
for _, c := range cases {
- db, _ := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
+ db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
encReceipts := types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...))
rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), encReceipts)
diff --git a/eth/backend.go b/eth/backend.go
index 15243ad5c9..ba087c5843 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -128,7 +128,14 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
}
log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024)
- chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false)
+ dbOptions := node.DatabaseOptions{
+ Cache: config.DatabaseCache,
+ Handles: config.DatabaseHandles,
+ AncientsDirectory: config.DatabaseFreezer,
+ EraDirectory: config.DatabaseEra,
+ MetricsNamespace: "eth/db/chaindata/",
+ }
+ chainDb, err := stack.OpenDatabaseWithOptions("chaindata", dbOptions)
if err != nil {
return nil, err
}
diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go
index ecc820fd35..aeb286b553 100644
--- a/eth/downloader/downloader_test.go
+++ b/eth/downloader/downloader_test.go
@@ -56,7 +56,7 @@ func newTester(t *testing.T) *downloadTester {
// newTesterWithNotification creates a new downloader test mocker.
func newTesterWithNotification(t *testing.T, success func()) *downloadTester {
- db, err := rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), "", "", false)
+ db, err := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{})
if err != nil {
panic(err)
}
diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go
index 5e19824135..b6029d4974 100644
--- a/eth/ethconfig/config.go
+++ b/eth/ethconfig/config.go
@@ -120,6 +120,7 @@ type Config struct {
DatabaseHandles int `toml:"-"`
DatabaseCache int
DatabaseFreezer string
+ DatabaseEra string
TrieCleanCache int
TrieDirtyCache int
diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go
index cdcddcc772..05f07178ac 100644
--- a/eth/ethconfig/gen_config.go
+++ b/eth/ethconfig/gen_config.go
@@ -37,6 +37,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
DatabaseHandles int `toml:"-"`
DatabaseCache int
DatabaseFreezer string
+ DatabaseEra string
TrieCleanCache int
TrieDirtyCache int
TrieTimeout time.Duration
@@ -77,6 +78,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.DatabaseHandles = c.DatabaseHandles
enc.DatabaseCache = c.DatabaseCache
enc.DatabaseFreezer = c.DatabaseFreezer
+ enc.DatabaseEra = c.DatabaseEra
enc.TrieCleanCache = c.TrieCleanCache
enc.TrieDirtyCache = c.TrieDirtyCache
enc.TrieTimeout = c.TrieTimeout
@@ -121,6 +123,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
DatabaseHandles *int `toml:"-"`
DatabaseCache *int
DatabaseFreezer *string
+ DatabaseEra *string
TrieCleanCache *int
TrieDirtyCache *int
TrieTimeout *time.Duration
@@ -204,6 +207,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.DatabaseFreezer != nil {
c.DatabaseFreezer = *dec.DatabaseFreezer
}
+ if dec.DatabaseEra != nil {
+ c.DatabaseEra = *dec.DatabaseEra
+ }
if dec.TrieCleanCache != nil {
c.TrieCleanCache = *dec.TrieCleanCache
}
diff --git a/ethdb/database.go b/ethdb/database.go
index 7f421752c4..b8aef2ae1d 100644
--- a/ethdb/database.go
+++ b/ethdb/database.go
@@ -92,10 +92,6 @@ type KeyValueStore interface {
// AncientReaderOp contains the methods required to read from immutable ancient data.
type AncientReaderOp interface {
- // HasAncient returns an indicator whether the specified data exists in the
- // ancient store.
- HasAncient(kind string, number uint64) (bool, error)
-
// Ancient retrieves an ancient binary blob from the append-only immutable files.
Ancient(kind string, number uint64) ([]byte, error)
diff --git a/ethdb/remotedb/remotedb.go b/ethdb/remotedb/remotedb.go
index a417a25854..7fe154ea95 100644
--- a/ethdb/remotedb/remotedb.go
+++ b/ethdb/remotedb/remotedb.go
@@ -48,13 +48,6 @@ func (db *Database) Get(key []byte) ([]byte, error) {
return resp, nil
}
-func (db *Database) HasAncient(kind string, number uint64) (bool, error) {
- if _, err := db.Ancient(kind, number); err != nil {
- return false, err
- }
- return true, nil
-}
-
func (db *Database) Ancient(kind string, number uint64) ([]byte, error) {
var resp hexutil.Bytes
err := db.remote.Call(&resp, "debug_dbAncient", kind, number)
diff --git a/internal/era/era.go b/internal/era/era.go
index 5129186fe7..118c67abfd 100644
--- a/internal/era/era.go
+++ b/internal/era/era.go
@@ -18,7 +18,6 @@ package era
import (
"encoding/binary"
- "errors"
"fmt"
"io"
"math/big"
@@ -126,32 +125,10 @@ func (e *Era) Close() error {
return e.f.Close()
}
-// GetHeaderByNumber returns the header for the given block number.
-func (e *Era) GetHeaderByNumber(num uint64) (*types.Header, error) {
- if e.m.start > num || e.m.start+e.m.count <= num {
- return nil, errors.New("out-of-bounds")
- }
- off, err := e.readOffset(num)
- if err != nil {
- return nil, err
- }
-
- // Read and decompress header.
- r, _, err := newSnappyReader(e.s, TypeCompressedHeader, off)
- if err != nil {
- return nil, err
- }
- var header types.Header
- if err := rlp.Decode(r, &header); err != nil {
- return nil, err
- }
- return &header, nil
-}
-
// GetBlockByNumber returns the block for the given block number.
func (e *Era) GetBlockByNumber(num uint64) (*types.Block, error) {
if e.m.start > num || e.m.start+e.m.count <= num {
- return nil, errors.New("out-of-bounds")
+ return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count)
}
off, err := e.readOffset(num)
if err != nil {
@@ -177,10 +154,30 @@ func (e *Era) GetBlockByNumber(num uint64) (*types.Block, error) {
return types.NewBlockWithHeader(&header).WithBody(body), nil
}
-// GetReceiptsByNumber returns the receipts for the given block number.
-func (e *Era) GetReceiptsByNumber(num uint64) (types.Receipts, error) {
+// GetRawBodyByNumber returns the RLP-encoded body for the given block number.
+func (e *Era) GetRawBodyByNumber(num uint64) ([]byte, error) {
if e.m.start > num || e.m.start+e.m.count <= num {
- return nil, errors.New("out-of-bounds")
+ return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count)
+ }
+ off, err := e.readOffset(num)
+ if err != nil {
+ return nil, err
+ }
+ off, err = e.s.SkipN(off, 1)
+ if err != nil {
+ return nil, err
+ }
+ r, _, err := newSnappyReader(e.s, TypeCompressedBody, off)
+ if err != nil {
+ return nil, err
+ }
+ return io.ReadAll(r)
+}
+
+// GetRawReceiptsByNumber returns the RLP-encoded receipts for the given block number.
+func (e *Era) GetRawReceiptsByNumber(num uint64) ([]byte, error) {
+ if e.m.start > num || e.m.start+e.m.count <= num {
+ return nil, fmt.Errorf("out-of-bounds: %d not in [%d, %d)", num, e.m.start, e.m.start+e.m.count)
}
off, err := e.readOffset(num)
if err != nil {
@@ -193,16 +190,11 @@ func (e *Era) GetReceiptsByNumber(num uint64) (types.Receipts, error) {
return nil, err
}
- // Read and decompress receipts.
r, _, err := newSnappyReader(e.s, TypeCompressedReceipts, off)
if err != nil {
return nil, err
}
- var receipts types.Receipts
- if err := rlp.Decode(r, &receipts); err != nil {
- return nil, err
- }
- return receipts, nil
+ return io.ReadAll(r)
}
// Accumulator reads the accumulator entry in the Era1 file.
diff --git a/internal/era/era_test.go b/internal/era/era_test.go
index 46fc2e91f3..31fa0076a6 100644
--- a/internal/era/era_test.go
+++ b/internal/era/era_test.go
@@ -101,17 +101,6 @@ func TestEra1Builder(t *testing.T) {
if !bytes.Equal(rawHeader, chain.headers[i]) {
t.Fatalf("mismatched header: want %s, got %s", chain.headers[i], rawHeader)
}
- header, err := e.GetHeaderByNumber(i)
- if err != nil {
- t.Fatalf("error reading header: %v", err)
- }
- encHeader, err := rlp.EncodeToBytes(header)
- if err != nil {
- t.Fatalf("error encoding header: %v", err)
- }
- if !bytes.Equal(encHeader, chain.headers[i]) {
- t.Fatalf("mismatched header: want %s, got %s", chain.headers[i], encHeader)
- }
// Check bodies.
body, err := io.ReadAll(it.Body)
@@ -130,7 +119,7 @@ func TestEra1Builder(t *testing.T) {
if !bytes.Equal(rawReceipts, chain.receipts[i]) {
t.Fatalf("mismatched receipts: want %s, got %s", chain.receipts[i], rawReceipts)
}
- receipts, err := e.GetReceiptsByNumber(i)
+ receipts, err := getReceiptsByNumber(e, i)
if err != nil {
t.Fatalf("error reading receipts: %v", err)
}
@@ -179,3 +168,15 @@ func mustEncode(obj any) []byte {
}
return b
}
+
+func getReceiptsByNumber(e *Era, number uint64) (types.Receipts, error) {
+ r, err := e.GetRawReceiptsByNumber(number)
+ if err != nil {
+ return nil, err
+ }
+ var receipts types.Receipts
+ if err := rlp.DecodeBytes(r, &receipts); err != nil {
+ return nil, err
+ }
+ return receipts, nil
+}
diff --git a/node/database.go b/node/database.go
index e3ccb91066..44bea10307 100644
--- a/node/database.go
+++ b/node/database.go
@@ -26,16 +26,25 @@ import (
"github.com/ethereum/go-ethereum/log"
)
-// openOptions contains the options to apply when opening a database.
-// OBS: If AncientsDirectory is empty, it indicates that no freezer is to be used.
-type openOptions struct {
- Type string // "leveldb" | "pebble"
- Directory string // the datadir
- AncientsDirectory string // the ancients-dir
- Namespace string // the namespace for database relevant metrics
- Cache int // the capacity(in megabytes) of the data caching
- Handles int // number of files to be open simultaneously
- ReadOnly bool
+// DatabaseOptions contains the options to apply when opening a database.
+type DatabaseOptions struct {
+ // Directory for storing chain history ("freezer").
+ AncientsDirectory string
+
+ // The optional Era folder, which can be either a subfolder under
+ // ancient/chain or a directory specified via an absolute path.
+ EraDirectory string
+
+ MetricsNamespace string // the namespace for database relevant metrics
+ Cache int // the capacity(in megabytes) of the data caching
+ Handles int // number of files to be open simultaneously
+ ReadOnly bool // if true, no writes can be performed
+}
+
+type internalOpenOptions struct {
+ directory string
+ dbEngine string // "leveldb" | "pebble"
+ DatabaseOptions
}
// openDatabase opens both a disk-based key-value database such as leveldb or pebble, but also
@@ -43,15 +52,18 @@ type openOptions struct {
// set on the provided OpenOptions.
// The passed o.AncientDir indicates the path of root ancient directory where
// the chain freezer can be opened.
-func openDatabase(o openOptions) (ethdb.Database, error) {
+func openDatabase(o internalOpenOptions) (ethdb.Database, error) {
kvdb, err := openKeyValueDatabase(o)
if err != nil {
return nil, err
}
- if len(o.AncientsDirectory) == 0 {
- return kvdb, nil
+ opts := rawdb.OpenOptions{
+ Ancient: o.AncientsDirectory,
+ Era: o.EraDirectory,
+ MetricsNamespace: o.MetricsNamespace,
+ ReadOnly: o.ReadOnly,
}
- frdb, err := rawdb.NewDatabaseWithFreezer(kvdb, o.AncientsDirectory, o.Namespace, o.ReadOnly)
+ frdb, err := rawdb.Open(kvdb, opts)
if err != nil {
kvdb.Close()
return nil, err
@@ -61,37 +73,37 @@ func openDatabase(o openOptions) (ethdb.Database, error) {
// openKeyValueDatabase opens a disk-based key-value database, e.g. leveldb or pebble.
//
-// type == null type != null
-// +----------------------------------------
+// type == null type != null
+// +----------------------------------------
// db is non-existent | pebble default | specified type
// db is existent | from db | specified type (if compatible)
-func openKeyValueDatabase(o openOptions) (ethdb.Database, error) {
+func openKeyValueDatabase(o internalOpenOptions) (ethdb.KeyValueStore, error) {
// Reject any unsupported database type
- if len(o.Type) != 0 && o.Type != rawdb.DBLeveldb && o.Type != rawdb.DBPebble {
- return nil, fmt.Errorf("unknown db.engine %v", o.Type)
+ if len(o.dbEngine) != 0 && o.dbEngine != rawdb.DBLeveldb && o.dbEngine != rawdb.DBPebble {
+ return nil, fmt.Errorf("unknown db.engine %v", o.dbEngine)
}
// Retrieve any pre-existing database's type and use that or the requested one
// as long as there's no conflict between the two types
- existingDb := rawdb.PreexistingDatabase(o.Directory)
- if len(existingDb) != 0 && len(o.Type) != 0 && o.Type != existingDb {
- return nil, fmt.Errorf("db.engine choice was %v but found pre-existing %v database in specified data directory", o.Type, existingDb)
+ existingDb := rawdb.PreexistingDatabase(o.directory)
+ if len(existingDb) != 0 && len(o.dbEngine) != 0 && o.dbEngine != existingDb {
+ return nil, fmt.Errorf("db.engine choice was %v but found pre-existing %v database in specified data directory", o.dbEngine, existingDb)
}
- if o.Type == rawdb.DBPebble || existingDb == rawdb.DBPebble {
+ if o.dbEngine == rawdb.DBPebble || existingDb == rawdb.DBPebble {
log.Info("Using pebble as the backing database")
- return newPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
+ return newPebbleDBDatabase(o.directory, o.Cache, o.Handles, o.MetricsNamespace, o.ReadOnly)
}
- if o.Type == rawdb.DBLeveldb || existingDb == rawdb.DBLeveldb {
+ if o.dbEngine == rawdb.DBLeveldb || existingDb == rawdb.DBLeveldb {
log.Info("Using leveldb as the backing database")
- return newLevelDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
+ return newLevelDBDatabase(o.directory, o.Cache, o.Handles, o.MetricsNamespace, o.ReadOnly)
}
// No pre-existing database, no user-requested one either. Default to Pebble.
log.Info("Defaulting to pebble as the backing database")
- return newPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly)
+ return newPebbleDBDatabase(o.directory, o.Cache, o.Handles, o.MetricsNamespace, o.ReadOnly)
}
// newLevelDBDatabase creates a persistent key-value database without a freezer
// moving immutable chain segments into cold storage.
-func newLevelDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) {
+func newLevelDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.KeyValueStore, error) {
db, err := leveldb.New(file, cache, handles, namespace, readonly)
if err != nil {
return nil, err
@@ -102,7 +114,7 @@ func newLevelDBDatabase(file string, cache int, handles int, namespace string, r
// newPebbleDBDatabase creates a persistent key-value database without a freezer
// moving immutable chain segments into cold storage.
-func newPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) {
+func newPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.KeyValueStore, error) {
db, err := pebble.New(file, cache, handles, namespace, readonly)
if err != nil {
return nil, err
diff --git a/node/node.go b/node/node.go
index ec7382e725..62b5abc34b 100644
--- a/node/node.go
+++ b/node/node.go
@@ -697,27 +697,27 @@ func (n *Node) EventMux() *event.TypeMux {
}
// OpenDatabase opens an existing database with the given name (or creates one if no
-// previous can be found) from within the node's instance directory. If the node is
-// ephemeral, a memory database is returned.
-func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, readonly bool) (ethdb.Database, error) {
+// previous can be found) from within the node's instance directory. If the node has no
+// data directory, an in-memory database is returned.
+func (n *Node) OpenDatabaseWithOptions(name string, opt DatabaseOptions) (ethdb.Database, error) {
n.lock.Lock()
defer n.lock.Unlock()
if n.state == closedState {
return nil, ErrNodeStopped
}
-
var db ethdb.Database
var err error
if n.config.DataDir == "" {
- db = rawdb.NewMemoryDatabase()
+ db, _ = rawdb.Open(memorydb.New(), rawdb.OpenOptions{
+ MetricsNamespace: opt.MetricsNamespace,
+ ReadOnly: opt.ReadOnly,
+ })
} else {
- db, err = openDatabase(openOptions{
- Type: n.config.DBEngine,
- Directory: n.ResolvePath(name),
- Namespace: namespace,
- Cache: cache,
- Handles: handles,
- ReadOnly: readonly,
+ opt.AncientsDirectory = n.ResolveAncient(name, opt.AncientsDirectory)
+ db, err = openDatabase(internalOpenOptions{
+ directory: n.ResolvePath(name),
+ dbEngine: n.config.DBEngine,
+ DatabaseOptions: opt,
})
}
if err == nil {
@@ -726,36 +726,31 @@ func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, r
return db, err
}
+// OpenDatabase opens an existing database with the given name (or creates one if no
+// previous can be found) from within the node's instance directory.
+// If the node has no data directory, an in-memory database is returned.
+// Deprecated: use OpenDatabaseWithOptions instead.
+func (n *Node) OpenDatabase(name string, cache, handles int, namespace string, readonly bool) (ethdb.Database, error) {
+ return n.OpenDatabaseWithOptions(name, DatabaseOptions{
+ MetricsNamespace: namespace,
+ Cache: cache,
+ Handles: handles,
+ ReadOnly: readonly,
+ })
+}
+
// OpenDatabaseWithFreezer opens an existing database with the given name (or
-// creates one if no previous can be found) from within the node's data directory,
-// also attaching a chain freezer to it that moves ancient chain data from the
-// database to immutable append-only files. If the node is an ephemeral one, a
-// memory database is returned.
+// creates one if no previous can be found) from within the node's data directory.
+// If the node has no data directory, an in-memory database is returned.
+// Deprecated: use OpenDatabaseWithOptions instead.
func (n *Node) OpenDatabaseWithFreezer(name string, cache, handles int, ancient string, namespace string, readonly bool) (ethdb.Database, error) {
- n.lock.Lock()
- defer n.lock.Unlock()
- if n.state == closedState {
- return nil, ErrNodeStopped
- }
- var db ethdb.Database
- var err error
- if n.config.DataDir == "" {
- db, err = rawdb.NewDatabaseWithFreezer(memorydb.New(), "", namespace, readonly)
- } else {
- db, err = openDatabase(openOptions{
- Type: n.config.DBEngine,
- Directory: n.ResolvePath(name),
- AncientsDirectory: n.ResolveAncient(name, ancient),
- Namespace: namespace,
- Cache: cache,
- Handles: handles,
- ReadOnly: readonly,
- })
- }
- if err == nil {
- db = n.wrapDatabase(db)
- }
- return db, err
+ return n.OpenDatabaseWithOptions(name, DatabaseOptions{
+ AncientsDirectory: n.ResolveAncient(name, ancient),
+ MetricsNamespace: namespace,
+ Cache: cache,
+ Handles: handles,
+ ReadOnly: readonly,
+ })
}
// ResolvePath returns the absolute path of a resource in the instance directory.
diff --git a/triedb/pathdb/database_test.go b/triedb/pathdb/database_test.go
index 3b780c975d..ca106cc27c 100644
--- a/triedb/pathdb/database_test.go
+++ b/triedb/pathdb/database_test.go
@@ -123,7 +123,7 @@ type tester struct {
func newTester(t *testing.T, historyLimit uint64, isVerkle bool, layers int) *tester {
var (
- disk, _ = rawdb.NewDatabaseWithFreezer(rawdb.NewMemoryDatabase(), t.TempDir(), "", false)
+ disk, _ = rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{Ancient: t.TempDir()})
db = New(disk, &Config{
StateHistory: historyLimit,
TrieCleanSize: 256 * 1024,