From d65c46526de851db6e6e6b04d545d2f0fc4404eb Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Fri, 18 Apr 2025 16:05:18 +0800 Subject: [PATCH] core/rawdb: move erdb integration into chain freezer --- core/rawdb/chain_freezer.go | 70 ++++++++++++++++++++++++-------- core/rawdb/eradb/eradb.go | 7 +++- core/rawdb/freezer.go | 27 +----------- core/rawdb/freezer_resettable.go | 2 +- core/rawdb/freezer_test.go | 12 +++--- 5 files changed, 68 insertions(+), 50 deletions(-) diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go index d63003047d..58f8f5afb7 100644 --- a/core/rawdb/chain_freezer.go +++ b/core/rawdb/chain_freezer.go @@ -46,6 +46,9 @@ const ( type chainFreezer struct { ethdb.AncientStore // Ancient store for storing cold chain segment + // Optional Era database used as a backup for the pruned chain. + eradb *eradb.EraDatabase + quit chan struct{} wg sync.WaitGroup trigger chan chan struct{} // Manual blocking freeze trigger, test determinism @@ -58,29 +61,27 @@ type chainFreezer struct { // - if non-empty directory is given, initializes the regular file-based // state freezer. func newChainFreezer(datadir string, namespace string, readonly bool, eraDir string) (*chainFreezer, error) { - var ( - err error - freezer ethdb.AncientStore - ) if datadir == "" { - freezer = NewMemoryFreezer(readonly, chainFreezerTableConfigs) - } else { - // Instantiate eradb outside of freezer to avoid - // creating an instance for the state freezer. - var edb *eradb.EraDatabase - if eraDir != "" { - edb, err = eradb.New(eraDir) - if err != nil { - return nil, err - } - } - freezer, err = NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerTableConfigs, edb) + return &chainFreezer{ + AncientStore: 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 } + var edb *eradb.EraDatabase + if eraDir != "" { + edb, err = eradb.New(eraDir) + if err != nil { + return nil, err + } + } return &chainFreezer{ AncientStore: freezer, + eradb: edb, quit: make(chan struct{}), trigger: make(chan chan struct{}), }, nil @@ -94,7 +95,14 @@ func (f *chainFreezer) Close() error { close(f.quit) } f.wg.Wait() - return f.AncientStore.Close() + + var errs []error + if f.eradb != nil { + errs = append(errs, f.eradb.Close()) + } + errs = append(errs, f.AncientStore.Close()) + + return errors.Join(errs...) } // readHeadNumber returns the number of chain head block. 0 is returned if the @@ -344,3 +352,31 @@ 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.AncientStore.Ancient(kind, number) + } + tail, err := f.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.AncientStore.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 +} diff --git a/core/rawdb/eradb/eradb.go b/core/rawdb/eradb/eradb.go index 21b9973390..537492edeb 100644 --- a/core/rawdb/eradb/eradb.go +++ b/core/rawdb/eradb/eradb.go @@ -54,7 +54,10 @@ func New(datadir string) (*EraDatabase, error) { if err := os.MkdirAll(datadir, 0755); err != nil { return nil, err } - db := &EraDatabase{datadir: datadir, cache: lru.NewCache[uint64, *era.Era](openFileLimit)} + db := &EraDatabase{ + datadir: datadir, + cache: lru.NewCache[uint64, *era.Era](openFileLimit), + } closeEra := func(epoch uint64, e *era.Era) { if e == nil { log.Warn("Era1 cache contained nil value", "epoch", epoch) @@ -66,9 +69,11 @@ func New(datadir string) (*EraDatabase, error) { } // Take care to close era1 files when they are evicted from cache. db.cache.OnEvicted(closeEra) + // Concurrently calling GetRaw* methods can cause the same era1 file to be // opened multiple times. db.cache.OnReplaced(closeEra) + log.Info("Opened erastore", "datadir", datadir) return db, nil } diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index d545005295..d84700c0f2 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -25,7 +25,6 @@ import ( "sync" "sync/atomic" - "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/metrics" @@ -72,8 +71,6 @@ type Freezer struct { tables map[string]*freezerTable // Data tables for storing everything instanceLock *flock.Flock // File-system lock to prevent double opens closeOnce sync.Once - - eradb *eradb.EraDatabase } // NewFreezer creates a freezer instance for maintaining immutable ordered @@ -81,7 +78,7 @@ type Freezer struct { // // The 'tables' argument defines the data tables. If the value of a map // entry is true, snappy compression is disabled for the table. -func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]freezerTableConfig, eradb *eradb.EraDatabase) (*Freezer, error) { +func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize uint32, tables map[string]freezerTableConfig) (*Freezer, error) { // Create the initial freezer object var ( readMeter = metrics.NewRegisteredMeter(namespace+"ancient/read", nil) @@ -121,7 +118,6 @@ func NewFreezer(datadir string, namespace string, readonly bool, maxTableSize ui readonly: readonly, tables: make(map[string]*freezerTable), instanceLock: lock, - eradb: eradb, } // Create the tables. @@ -175,16 +171,8 @@ func (f *Freezer) Close() error { if err := f.instanceLock.Unlock(); err != nil { errs = append(errs, err) } - if f.eradb != nil { - if err := f.eradb.Close(); err != nil { - 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. @@ -204,17 +192,6 @@ func (f *Freezer) HasAncient(kind string, number uint64) (bool, error) { // 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 { - if f.eradb != nil && table.config.prunable && number < f.tail.Load() { - // The requested item has been pruned. Attempt fetching from era1 file. - switch kind { - case ChainFreezerBodiesTable: - return f.eradb.GetRawBody(number) - case ChainFreezerReceiptTable: - return f.eradb.GetRawReceipts(number) - default: - return nil, errOutOfBounds - } - } return table.Retrieve(number) } return nil, errUnknownTable diff --git a/core/rawdb/freezer_resettable.go b/core/rawdb/freezer_resettable.go index 2be5cee0d4..01df2877d9 100644 --- a/core/rawdb/freezer_resettable.go +++ b/core/rawdb/freezer_resettable.go @@ -54,7 +54,7 @@ func newResettableFreezer(datadir string, namespace string, readonly bool, maxTa return nil, err } opener := func() (*Freezer, error) { - return NewFreezer(datadir, namespace, readonly, maxTableSize, tables, nil) + return NewFreezer(datadir, namespace, readonly, maxTableSize, tables) } freezer, err := opener() if err != nil { diff --git a/core/rawdb/freezer_test.go b/core/rawdb/freezer_test.go index 2500121ed8..a7a3559ec4 100644 --- a/core/rawdb/freezer_test.go +++ b/core/rawdb/freezer_test.go @@ -112,7 +112,7 @@ func TestFreezerModifyRollback(t *testing.T) { // Reopen and check that the rolled-back data doesn't reappear. tables := map[string]freezerTableConfig{"test": {noSnappy: true}} - f2, err := NewFreezer(dir, "", false, 2049, tables, nil) + f2, err := NewFreezer(dir, "", false, 2049, tables) if err != nil { t.Fatalf("can't reopen freezer after failed ModifyAncients: %v", err) } @@ -253,7 +253,7 @@ func TestFreezerReadonlyValidate(t *testing.T) { dir := t.TempDir() // Open non-readonly freezer and fill individual tables // with different amount of data. - f, err := NewFreezer(dir, "", false, 2049, tables, nil) + f, err := NewFreezer(dir, "", false, 2049, tables) if err != nil { t.Fatal("can't open freezer", err) } @@ -276,7 +276,7 @@ func TestFreezerReadonlyValidate(t *testing.T) { // Re-opening as readonly should fail when validating // table lengths. - _, err = NewFreezer(dir, "", true, 2049, tables, nil) + _, err = NewFreezer(dir, "", true, 2049, tables) if err == nil { t.Fatal("readonly freezer should fail with differing table lengths") } @@ -288,7 +288,7 @@ func TestFreezerConcurrentReadonly(t *testing.T) { tables := map[string]freezerTableConfig{"a": {noSnappy: true}} dir := t.TempDir() - f, err := NewFreezer(dir, "", false, 2049, tables, nil) + f, err := NewFreezer(dir, "", false, 2049, tables) if err != nil { t.Fatal("can't open freezer", err) } @@ -314,7 +314,7 @@ func TestFreezerConcurrentReadonly(t *testing.T) { go func(i int) { defer wg.Done() - f, err := NewFreezer(dir, "", true, 2049, tables, nil) + f, err := NewFreezer(dir, "", true, 2049, tables) if err == nil { fs[i] = f } else { @@ -339,7 +339,7 @@ func newFreezerForTesting(t *testing.T, tables map[string]freezerTableConfig) (* dir := t.TempDir() // note: using low max table size here to ensure the tests actually // switch between multiple files. - f, err := NewFreezer(dir, "", false, 2049, tables, nil) + f, err := NewFreezer(dir, "", false, 2049, tables) if err != nil { t.Fatal("can't open freezer", err) }