From 7fe474b24c42e7636fa6bc40b0fb71216df82b84 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Wed, 23 Apr 2025 09:23:49 +0800 Subject: [PATCH 1/7] core/rawdb, ethdb, triedb/pathdb: rename to SyncAncient --- core/blockchain.go | 4 ++-- core/rawdb/chain_freezer.go | 2 +- core/rawdb/database.go | 4 ++-- core/rawdb/freezer.go | 4 ++-- core/rawdb/freezer_memory.go | 4 ++-- core/rawdb/freezer_resettable.go | 6 +++--- core/rawdb/freezer_test.go | 2 +- core/rawdb/table.go | 6 +++--- ethdb/database.go | 6 +++--- ethdb/remotedb/remotedb.go | 2 +- triedb/pathdb/buffer.go | 2 +- 11 files changed, 21 insertions(+), 21 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 6667f649110c..06f0baf102e7 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1361,7 +1361,7 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ size += writeSize // Sync the ancient store explicitly to ensure all data has been flushed to disk. - if err := bc.db.Sync(); err != nil { + if err := bc.db.SyncAncient(); err != nil { return 0, err } // Write hash to number mappings @@ -2627,7 +2627,7 @@ func (bc *BlockChain) InsertHeadersBeforeCutoff(headers []*types.Header) (int, e if err != nil { return 0, err } - if err := bc.db.Sync(); err != nil { + if err := bc.db.SyncAncient(); err != nil { return 0, err } // Write hash to number mappings diff --git a/core/rawdb/chain_freezer.go b/core/rawdb/chain_freezer.go index f3c671f45a88..cc7a62df329f 100644 --- a/core/rawdb/chain_freezer.go +++ b/core/rawdb/chain_freezer.go @@ -205,7 +205,7 @@ func (f *chainFreezer) freeze(db ethdb.KeyValueStore) { continue } // Batch of blocks have been frozen, flush them before wiping from key-value store - if err := f.Sync(); err != nil { + if err := f.SyncAncient(); err != nil { log.Crit("Failed to flush frozen tables", "err", err) } // Wipe out all data from the active database diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 2a50e3f9eef5..a03dbafb1f4f 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -131,8 +131,8 @@ func (db *nofreezedb) TruncateTail(items uint64) (uint64, error) { return 0, errNotSupported } -// Sync returns an error as we don't have a backing chain freezer. -func (db *nofreezedb) Sync() error { +// SyncAncient returns an error as we don't have a backing chain freezer. +func (db *nofreezedb) SyncAncient() error { return errNotSupported } diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index 105d3af93443..1e5b98c7fe0e 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -325,8 +325,8 @@ func (f *Freezer) TruncateTail(tail uint64) (uint64, error) { return old, nil } -// Sync flushes all data tables to disk. -func (f *Freezer) Sync() error { +// SyncAncient flushes all data tables to disk. +func (f *Freezer) SyncAncient() error { var errs []error for _, table := range f.tables { if err := table.Sync(); err != nil { diff --git a/core/rawdb/freezer_memory.go b/core/rawdb/freezer_memory.go index 4274546de51b..bd286f45f571 100644 --- a/core/rawdb/freezer_memory.go +++ b/core/rawdb/freezer_memory.go @@ -395,8 +395,8 @@ func (f *MemoryFreezer) TruncateTail(tail uint64) (uint64, error) { return old, nil } -// Sync flushes all data tables to disk. -func (f *MemoryFreezer) Sync() error { +// SyncAncient flushes all data tables to disk. +func (f *MemoryFreezer) SyncAncient() error { return nil } diff --git a/core/rawdb/freezer_resettable.go b/core/rawdb/freezer_resettable.go index 2e64e6074c50..01df2877d916 100644 --- a/core/rawdb/freezer_resettable.go +++ b/core/rawdb/freezer_resettable.go @@ -194,12 +194,12 @@ func (f *resettableFreezer) TruncateTail(tail uint64) (uint64, error) { return f.freezer.TruncateTail(tail) } -// Sync flushes all data tables to disk. -func (f *resettableFreezer) Sync() error { +// SyncAncient flushes all data tables to disk. +func (f *resettableFreezer) SyncAncient() error { f.lock.RLock() defer f.lock.RUnlock() - return f.freezer.Sync() + return f.freezer.SyncAncient() } // AncientDatadir returns the path of the ancient store. diff --git a/core/rawdb/freezer_test.go b/core/rawdb/freezer_test.go index 150734d3ac92..a7a3559ec433 100644 --- a/core/rawdb/freezer_test.go +++ b/core/rawdb/freezer_test.go @@ -392,7 +392,7 @@ func TestFreezerCloseSync(t *testing.T) { if err := f.Close(); err != nil { t.Fatal(err) } - if err := f.Sync(); err == nil { + if err := f.SyncAncient(); err == nil { t.Fatalf("want error, have nil") } else if have, want := err.Error(), "[closed closed]"; have != want { t.Fatalf("want %v, have %v", have, want) diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 1a9060b6365b..fc99ff086675 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -107,10 +107,10 @@ func (t *table) TruncateTail(items uint64) (uint64, error) { return t.db.TruncateTail(items) } -// Sync is a noop passthrough that just forwards the request to the underlying +// SyncAncient is a noop passthrough that just forwards the request to the underlying // database. -func (t *table) Sync() error { - return t.db.Sync() +func (t *table) SyncAncient() error { + return t.db.SyncAncient() } // AncientDatadir returns the ancient datadir of the underlying database. diff --git a/ethdb/database.go b/ethdb/database.go index f2d458b85f3c..39a565f6ac41 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -126,6 +126,9 @@ type AncientWriter interface { // The integer return value is the total size of the written data. ModifyAncients(func(AncientWriteOp) error) (int64, error) + // SyncAncient flushes all in-memory ancient store data to disk. + SyncAncient() error + // TruncateHead discards all but the first n ancient data from the ancient store. // After the truncation, the latest item can be accessed it item_n-1(start from 0). TruncateHead(n uint64) (uint64, error) @@ -138,9 +141,6 @@ type AncientWriter interface { // // Note that data marked as non-prunable will still be retained and remain accessible. TruncateTail(n uint64) (uint64, error) - - // Sync flushes all in-memory ancient store data to disk. - Sync() error } // AncientWriteOp is given to the function argument of ModifyAncients. diff --git a/ethdb/remotedb/remotedb.go b/ethdb/remotedb/remotedb.go index 8a91fdbcf266..9de0bdc0d577 100644 --- a/ethdb/remotedb/remotedb.go +++ b/ethdb/remotedb/remotedb.go @@ -110,7 +110,7 @@ func (db *Database) TruncateTail(n uint64) (uint64, error) { panic("not supported") } -func (db *Database) Sync() error { +func (db *Database) SyncAncient() error { return nil } diff --git a/triedb/pathdb/buffer.go b/triedb/pathdb/buffer.go index dea8875bda5d..12c98f24f017 100644 --- a/triedb/pathdb/buffer.go +++ b/triedb/pathdb/buffer.go @@ -138,7 +138,7 @@ func (b *buffer) flush(db ethdb.KeyValueStore, freezer ethdb.AncientWriter, node // Explicitly sync the state freezer, ensuring that all written // data is transferred to disk before updating the key-value store. if freezer != nil { - if err := freezer.Sync(); err != nil { + if err := freezer.SyncAncient(); err != nil { return err } } From 1665dfecba09d8d8fc5f61832fccaf60299fab72 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Wed, 23 Apr 2025 10:37:09 +0800 Subject: [PATCH 2/7] core/rawdb, ethdb, trie: implement db.Sync --- core/rawdb/table.go | 6 ++++++ ethdb/database.go | 8 ++++++++ ethdb/leveldb/leveldb.go | 6 ++++++ ethdb/memorydb/memorydb.go | 6 ++++++ ethdb/pebble/pebble.go | 8 ++++++++ ethdb/remotedb/remotedb.go | 4 ++++ trie/trie_test.go | 1 + 7 files changed, 39 insertions(+) diff --git a/core/rawdb/table.go b/core/rawdb/table.go index fc99ff086675..d481fbd3ec21 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -188,6 +188,12 @@ func (t *table) Compact(start []byte, limit []byte) error { return t.db.Compact(start, limit) } +// Sync ensures that all pending writes are flushed to disk, guaranteeing +// data durability up to the point. +func (t *table) Sync() error { + return t.db.Sync() +} + // NewBatch creates a write-only database that buffers changes to its host db // until a final write is called, each operation prefixing all keys with the // pre-configured string. diff --git a/ethdb/database.go b/ethdb/database.go index 39a565f6ac41..f277acb550e7 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -57,6 +57,13 @@ type KeyValueStater interface { Stat() (string, error) } +// KeyValueSyncer wraps the Sync method of a backing data store. +type KeyValueSyncer interface { + // Sync ensures that all pending writes are flushed to disk, guaranteeing + // data durability up to the point. + Sync() error +} + // Compacter wraps the Compact method of a backing data store. type Compacter interface { // Compact flattens the underlying data store for the given key range. In essence, @@ -75,6 +82,7 @@ type KeyValueStore interface { KeyValueReader KeyValueWriter KeyValueStater + KeyValueSyncer KeyValueRangeDeleter Batcher Iteratee diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index ef02e91822e2..f29a0f064d4a 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -324,6 +324,12 @@ func (db *Database) Path() string { return db.fn } +// Sync flushes all pending writes in the write-ahead-log to disk, ensuring +// data durability up to that point. +func (db *Database) Sync() error { + return nil +} + // meter periodically retrieves internal leveldb counters and reports them to // the metrics subsystem. func (db *Database) meter(refresh time.Duration, namespace string) { diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go index a797275e92aa..c8196f71379c 100644 --- a/ethdb/memorydb/memorydb.go +++ b/ethdb/memorydb/memorydb.go @@ -199,6 +199,12 @@ func (db *Database) Compact(start []byte, limit []byte) error { return nil } +// Sync ensures that all pending writes are flushed to disk, guaranteeing +// data durability up to the point. +func (db *Database) Sync() error { + return nil +} + // Len returns the number of entries currently present in the memory database. // // Note, this method is only used for testing (i.e. not public in general) and diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go index 969e67af5a3f..da6d68f673b2 100644 --- a/ethdb/pebble/pebble.go +++ b/ethdb/pebble/pebble.go @@ -414,6 +414,14 @@ func (d *Database) Path() string { return d.fn } +// Sync flushes all pending writes in the write-ahead-log to disk, ensuring +// data durability up to that point. +func (d *Database) Sync() error { + b := d.db.NewBatch() + b.LogData(nil, nil) + return d.db.Apply(b, pebble.Sync) +} + // meter periodically retrieves internal pebble counters and reports them to // the metrics subsystem. func (d *Database) meter(refresh time.Duration, namespace string) { diff --git a/ethdb/remotedb/remotedb.go b/ethdb/remotedb/remotedb.go index 9de0bdc0d577..13b19facea7f 100644 --- a/ethdb/remotedb/remotedb.go +++ b/ethdb/remotedb/remotedb.go @@ -138,6 +138,10 @@ func (db *Database) Compact(start []byte, limit []byte) error { return nil } +func (db *Database) Sync() error { + return nil +} + func (db *Database) Close() error { db.remote.Close() return nil diff --git a/trie/trie_test.go b/trie/trie_test.go index 54d1b083d8e9..0a297b317d7a 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -830,6 +830,7 @@ func (s *spongeDb) NewBatch() ethdb.Batch { return &spongeBat func (s *spongeDb) NewBatchWithSize(size int) ethdb.Batch { return &spongeBatch{s} } func (s *spongeDb) Stat() (string, error) { panic("implement me") } func (s *spongeDb) Compact(start []byte, limit []byte) error { panic("implement me") } +func (s *spongeDb) Sync() error { return nil } func (s *spongeDb) Close() error { return nil } func (s *spongeDb) Put(key []byte, value []byte) error { var ( From b3d0362fa9cc5dc168a4260ac788c4d91c91b114 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Wed, 23 Apr 2025 10:47:58 +0800 Subject: [PATCH 3/7] core, ethdb/pebble: enable async write mode in pebble --- core/bench_test.go | 8 ++++---- core/blockchain_repair_test.go | 8 ++++---- core/blockchain_sethead_test.go | 2 +- core/blockchain_snapshot_test.go | 4 ++-- core/blockchain_test.go | 2 +- ethdb/pebble/pebble.go | 7 ++++--- node/database.go | 13 ++++--------- 7 files changed, 20 insertions(+), 24 deletions(-) diff --git a/core/bench_test.go b/core/bench_test.go index 00f924076ad5..155fa6c3b547 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -183,7 +183,7 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { if !disk { db = rawdb.NewMemoryDatabase() } else { - pdb, err := pebble.New(b.TempDir(), 128, 128, "", false, true) + pdb, err := pebble.New(b.TempDir(), 128, 128, "", false) if err != nil { b.Fatalf("cannot create temporary database: %v", err) } @@ -303,7 +303,7 @@ func makeChainForBench(db ethdb.Database, genesis *Genesis, full bool, count uin func benchWriteChain(b *testing.B, full bool, count uint64) { genesis := &Genesis{Config: params.AllEthashProtocolChanges} for i := 0; i < b.N; i++ { - pdb, err := pebble.New(b.TempDir(), 1024, 128, "", false, true) + pdb, err := pebble.New(b.TempDir(), 1024, 128, "", false) if err != nil { b.Fatalf("error opening database: %v", err) } @@ -316,7 +316,7 @@ func benchWriteChain(b *testing.B, full bool, count uint64) { func benchReadChain(b *testing.B, full bool, count uint64) { dir := b.TempDir() - pdb, err := pebble.New(dir, 1024, 128, "", false, true) + pdb, err := pebble.New(dir, 1024, 128, "", false) if err != nil { b.Fatalf("error opening database: %v", err) } @@ -332,7 +332,7 @@ func benchReadChain(b *testing.B, full bool, count uint64) { b.ResetTimer() for i := 0; i < b.N; i++ { - pdb, err = pebble.New(dir, 1024, 128, "", false, true) + pdb, err = pebble.New(dir, 1024, 128, "", false) if err != nil { b.Fatalf("error opening database: %v", err) } diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index 3ff1d77fc835..6c52d057adf8 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -1765,7 +1765,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s datadir := t.TempDir() ancient := filepath.Join(datadir, "ancient") - pdb, err := pebble.New(datadir, 0, 0, "", false, true) + pdb, err := pebble.New(datadir, 0, 0, "", false) if err != nil { t.Fatalf("Failed to create persistent key-value database: %v", err) } @@ -1850,7 +1850,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s chain.stopWithoutSaving() // Start a new blockchain back up and see where the repair leads us - pdb, err = pebble.New(datadir, 0, 0, "", false, true) + pdb, err = pebble.New(datadir, 0, 0, "", false) if err != nil { t.Fatalf("Failed to reopen persistent key-value database: %v", err) } @@ -1915,7 +1915,7 @@ func testIssue23496(t *testing.T, scheme string) { datadir := t.TempDir() ancient := filepath.Join(datadir, "ancient") - pdb, err := pebble.New(datadir, 0, 0, "", false, true) + pdb, err := pebble.New(datadir, 0, 0, "", false) if err != nil { t.Fatalf("Failed to create persistent key-value database: %v", err) } @@ -1973,7 +1973,7 @@ func testIssue23496(t *testing.T, scheme string) { chain.stopWithoutSaving() // Start a new blockchain back up and see where the repair leads us - pdb, err = pebble.New(datadir, 0, 0, "", false, true) + pdb, err = pebble.New(datadir, 0, 0, "", false) if err != nil { t.Fatalf("Failed to reopen persistent key-value database: %v", err) } diff --git a/core/blockchain_sethead_test.go b/core/blockchain_sethead_test.go index e998b510df94..424854b2bf81 100644 --- a/core/blockchain_sethead_test.go +++ b/core/blockchain_sethead_test.go @@ -1969,7 +1969,7 @@ func testSetHeadWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme datadir := t.TempDir() ancient := filepath.Join(datadir, "ancient") - pdb, err := pebble.New(datadir, 0, 0, "", false, true) + pdb, err := pebble.New(datadir, 0, 0, "", false) if err != nil { t.Fatalf("Failed to create persistent key-value database: %v", err) } diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index 23effea15e21..1a6fe38af6d7 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -66,7 +66,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo datadir := t.TempDir() ancient := filepath.Join(datadir, "ancient") - pdb, err := pebble.New(datadir, 0, 0, "", false, true) + pdb, err := pebble.New(datadir, 0, 0, "", false) if err != nil { t.Fatalf("Failed to create persistent key-value database: %v", err) } @@ -257,7 +257,7 @@ func (snaptest *crashSnapshotTest) test(t *testing.T) { chain.triedb.Close() // Start a new blockchain back up and see where the repair leads us - pdb, err := pebble.New(snaptest.datadir, 0, 0, "", false, true) + pdb, err := pebble.New(snaptest.datadir, 0, 0, "", false) if err != nil { t.Fatalf("Failed to create persistent key-value database: %v", err) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 134deee237dc..b981c33f21e4 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -2492,7 +2492,7 @@ func testSideImportPrunedBlocks(t *testing.T, scheme string) { datadir := t.TempDir() ancient := path.Join(datadir, "ancient") - pdb, err := pebble.New(datadir, 0, 0, "", false, true) + pdb, err := pebble.New(datadir, 0, 0, "", false) if err != nil { t.Fatalf("Failed to create persistent key-value database: %v", err) } diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go index da6d68f673b2..4055be30cee8 100644 --- a/ethdb/pebble/pebble.go +++ b/ethdb/pebble/pebble.go @@ -144,7 +144,7 @@ func (l panicLogger) Fatalf(format string, args ...interface{}) { // New returns a wrapped pebble DB object. The namespace is the prefix that the // metrics reporting should use for surfacing internal stats. -func New(file string, cache int, handles int, namespace string, readonly bool, ephemeral bool) (*Database, error) { +func New(file string, cache int, handles int, namespace string, readonly bool) (*Database, error) { // Ensure we have some minimal caching and file guarantees if cache < minCache { cache = minCache @@ -185,7 +185,7 @@ func New(file string, cache int, handles int, namespace string, readonly bool, e fn: file, log: logger, quitChan: make(chan chan error), - writeOptions: &pebble.WriteOptions{Sync: !ephemeral}, + writeOptions: &pebble.WriteOptions{Sync: false}, } opt := &pebble.Options{ // Pebble has a single combined cache area and the write @@ -227,7 +227,8 @@ func New(file string, cache int, handles int, namespace string, readonly bool, e WriteStallBegin: db.onWriteStallBegin, WriteStallEnd: db.onWriteStallEnd, }, - Logger: panicLogger{}, // TODO(karalabe): Delete when this is upstreamed in Pebble + WALBytesPerSync: 5 * ethdb.IdealBatchSize, + Logger: panicLogger{}, // TODO(karalabe): Delete when this is upstreamed in Pebble } // Disable seek compaction explicitly. Check https://github.com/ethereum/go-ethereum/pull/20130 // for more details. diff --git a/node/database.go b/node/database.go index b7d0d856cbd6..e3ccb9106678 100644 --- a/node/database.go +++ b/node/database.go @@ -36,11 +36,6 @@ type openOptions struct { Cache int // the capacity(in megabytes) of the data caching Handles int // number of files to be open simultaneously ReadOnly bool - - // Ephemeral means that filesystem sync operations should be avoided: - // data integrity in the face of a crash is not important. This option - // should typically be used in tests. - Ephemeral bool } // openDatabase opens both a disk-based key-value database such as leveldb or pebble, but also @@ -83,7 +78,7 @@ func openKeyValueDatabase(o openOptions) (ethdb.Database, error) { } if o.Type == 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, o.Ephemeral) + return newPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly) } if o.Type == rawdb.DBLeveldb || existingDb == rawdb.DBLeveldb { log.Info("Using leveldb as the backing database") @@ -91,7 +86,7 @@ func openKeyValueDatabase(o openOptions) (ethdb.Database, error) { } // 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, o.Ephemeral) + return newPebbleDBDatabase(o.Directory, o.Cache, o.Handles, o.Namespace, o.ReadOnly) } // newLevelDBDatabase creates a persistent key-value database without a freezer @@ -107,8 +102,8 @@ 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, ephemeral bool) (ethdb.Database, error) { - db, err := pebble.New(file, cache, handles, namespace, readonly, ephemeral) +func newPebbleDBDatabase(file string, cache int, handles int, namespace string, readonly bool) (ethdb.Database, error) { + db, err := pebble.New(file, cache, handles, namespace, readonly) if err != nil { return nil, err } From a64a85cf9a6f864e6569814386bc04fc97278679 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Thu, 24 Apr 2025 21:20:42 +0800 Subject: [PATCH 4/7] core, ethdb, triedb: use db.sync --- core/blockchain.go | 20 ++++++++++---------- core/headerchain.go | 30 +++++++++++++++++++++++++++++- ethdb/leveldb/leveldb.go | 9 +++++++++ ethdb/pebble/pebble.go | 15 ++++++++++++++- ethdb/pebble/pebble_test.go | 24 ++++++++++++++++++++++++ triedb/pathdb/database.go | 9 +++++++++ 6 files changed, 95 insertions(+), 12 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 06f0baf102e7..b0c1b119fc56 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -979,17 +979,16 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha // Ignore the error here since light client won't hit this path frozen, _ := bc.db.Ancients() if num+1 <= frozen { - // Truncate all relative data(header, total difficulty, body, receipt - // and canonical hash) from ancient store. - if _, err := bc.db.TruncateHead(num); err != nil { - log.Crit("Failed to truncate ancient data", "number", num, "err", err) - } - // Remove the hash <-> number mapping from the active store. - rawdb.DeleteHeaderNumber(db, hash) + // The chain segment, such as the block header, canonical hash, + // body, and receipt, will be removed from the ancient store + // in one go. + // + // The hash-to-number mapping in the key-value store will be + // removed by the hc.SetHead function. } else { - // Remove relative body and receipts from the active store. - // The header, total difficulty and canonical hash will be - // removed in the hc.SetHead function. + // Remove the associated body and receipts from the key-value store. + // The header, hash-to-number mapping, and canonical hash will be + // removed by the hc.SetHead function. rawdb.DeleteBody(db, hash, num) rawdb.DeleteReceipts(db, hash, num) } @@ -2627,6 +2626,7 @@ func (bc *BlockChain) InsertHeadersBeforeCutoff(headers []*types.Header) (int, e if err != nil { return 0, err } + // Sync the ancient store explicitly to ensure all data has been flushed to disk. if err := bc.db.SyncAncient(); err != nil { return 0, err } diff --git a/core/headerchain.go b/core/headerchain.go index f7acc49bef4f..29cb55a2122f 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -591,17 +591,45 @@ func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn Updat hashes = append(hashes, hdr.Hash()) } for _, hash := range hashes { + // Remove the associated block body and receipts if required. + // + // If the block is in the chain freezer, then this delete operation + // is actually ineffective. if delFn != nil { delFn(batch, hash, num) } + // Remove the hash->number mapping along with the header itself rawdb.DeleteHeader(batch, hash, num) } + // Remove the number->hash mapping rawdb.DeleteCanonicalHash(batch, num) } } // Flush all accumulated deletions. if err := batch.Write(); err != nil { - log.Crit("Failed to rewind block", "error", err) + log.Crit("Failed to commit batch in setHead", "err", err) + } + // Explicitly flush the pending writes in the key-value store to disk, ensuring + // data durability of the previous deletions. + if err := hc.chainDb.Sync(); err != nil { + log.Crit("Failed to sync the key-value store in setHead", "err", err) + } + // Truncate the excessive chain segments in the ancient store. + // These are actually deferred deletions from the loop above. + // + // This step must be performed after synchronizing the key-value store; + // otherwise, in the event of a panic, it's theoretically possible to + // lose recent key-value store writes while the ancient store deletions + // remain, leading to data inconsistency. + if delFn != nil { + // Ignore the error here since light client won't hit this path + frozen, _ := hc.chainDb.Ancients() + if headBlock+1 < frozen { + _, err := hc.chainDb.TruncateHead(headBlock + 1) + if err != nil { + log.Crit("Failed to truncate head block", "err", err) + } + } } // Clear out any stale content from the caches hc.headerCache.Purge() diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index f29a0f064d4a..e8f15753ece9 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -327,6 +327,15 @@ func (db *Database) Path() string { // Sync flushes all pending writes in the write-ahead-log to disk, ensuring // data durability up to that point. func (db *Database) Sync() error { + // In theory, the WAL (Write-Ahead Log) can be explicitly synchronized using + // a write operation with SYNC=true. However, there is no dedicated key reserved + // for this purpose, and even a nil key (key=nil) is considered a valid database entry. + // + // In LevelDB, writes are blocked until the data is written to the WAL, meaning + // recent writes won't be lost unless a power failure or system crash occurs. + // Additionally, LevelDB is no longer the default database engine and is likely + // only used by hash-mode archive nodes. Given this, the durability guarantees + // without explicit sync are acceptable in the context of LevelDB. return nil } diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go index 4055be30cee8..dc6ab92db00d 100644 --- a/ethdb/pebble/pebble.go +++ b/ethdb/pebble/pebble.go @@ -227,8 +227,16 @@ func New(file string, cache int, handles int, namespace string, readonly bool) ( WriteStallBegin: db.onWriteStallBegin, WriteStallEnd: db.onWriteStallEnd, }, + Logger: panicLogger{}, // TODO(karalabe): Delete when this is upstreamed in Pebble + + // Pebble is configured to use asynchronous write mode, meaning write operations + // return as soon as the data is cached in memory, without waiting for the WAL + // to be written. This mode offers better write performance but risks losing + // recent writes if the application crashes or a power failure/system crash occurs. + // + // By setting the WALBytesPerSync, the cached WAL writes will be periodically + // flushed at the background if the accumulated size exceeds this threshold. WALBytesPerSync: 5 * ethdb.IdealBatchSize, - Logger: panicLogger{}, // TODO(karalabe): Delete when this is upstreamed in Pebble } // Disable seek compaction explicitly. Check https://github.com/ethereum/go-ethereum/pull/20130 // for more details. @@ -419,6 +427,11 @@ func (d *Database) Path() string { // data durability up to that point. func (d *Database) Sync() error { b := d.db.NewBatch() + + // The entry (value=nil) is not written to the database; it is only + // added to the WAL. Writing this special log entry in sync mode + // automatically flushes all previous writes, ensuring database + // durability up to this point. b.LogData(nil, nil) return d.db.Apply(b, pebble.Sync) } diff --git a/ethdb/pebble/pebble_test.go b/ethdb/pebble/pebble_test.go index 3265491d4a38..e703a8d0ced2 100644 --- a/ethdb/pebble/pebble_test.go +++ b/ethdb/pebble/pebble_test.go @@ -17,6 +17,7 @@ package pebble import ( + "errors" "testing" "github.com/cockroachdb/pebble" @@ -54,3 +55,26 @@ func BenchmarkPebbleDB(b *testing.B) { } }) } + +func TestPebbleLogData(t *testing.T) { + db, err := pebble.Open("", &pebble.Options{ + FS: vfs.NewMem(), + }) + if err != nil { + t.Fatal(err) + } + + _, _, err = db.Get(nil) + if !errors.Is(err, pebble.ErrNotFound) { + t.Fatal("Unknown database entry") + } + + b := db.NewBatch() + b.LogData(nil, nil) + db.Apply(b, pebble.Sync) + + _, _, err = db.Get(nil) + if !errors.Is(err, pebble.ErrNotFound) { + t.Fatal("Unknown database entry") + } +} diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index d48850c102fe..85103063b57c 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -454,6 +454,15 @@ func (db *Database) Recover(root common.Hash) error { db.tree.reset(dl) } rawdb.DeleteTrieJournal(db.diskdb) + + // Explicitly sync the key-value store to ensure all recent writes are + // flushed to disk. This step is crucial to prevent a scenario where + // recent key-value writes are lost due to an application panic, while + // the associated state histories have already been removed, resulting + // in the inability to perform a state rollback. + if err := db.diskdb.Sync(); err != nil { + return err + } _, err := truncateFromHead(db.diskdb, db.freezer, dl.stateID()) if err != nil { return err From 892113345ff8aa25cd6cb27f7547860c89f17b09 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Fri, 25 Apr 2025 17:01:06 +0800 Subject: [PATCH 5/7] core, ethdb: polish code --- core/headerchain.go | 3 ++- ethdb/leveldb/leveldb.go | 3 ++- ethdb/pebble/pebble.go | 19 +++++++++++++------ triedb/pathdb/buffer.go | 7 +++++-- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/core/headerchain.go b/core/headerchain.go index 29cb55a2122f..477c629ddabf 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -620,7 +620,8 @@ func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn Updat // This step must be performed after synchronizing the key-value store; // otherwise, in the event of a panic, it's theoretically possible to // lose recent key-value store writes while the ancient store deletions - // remain, leading to data inconsistency. + // remain, leading to data inconsistency, e.g., the gap between the key + // value store and ancient can be created due to unclean shutdown. if delFn != nil { // Ignore the error here since light client won't hit this path frozen, _ := hc.chainDb.Ancients() diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index e8f15753ece9..b583365ac2d9 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -329,7 +329,8 @@ func (db *Database) Path() string { func (db *Database) Sync() error { // In theory, the WAL (Write-Ahead Log) can be explicitly synchronized using // a write operation with SYNC=true. However, there is no dedicated key reserved - // for this purpose, and even a nil key (key=nil) is considered a valid database entry. + // for this purpose, and even a nil key (key=nil) is considered a valid + // database entry. // // In LevelDB, writes are blocked until the data is written to the WAL, meaning // recent writes won't be lost unless a power failure or system crash occurs. diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go index dc6ab92db00d..5b2918aa890a 100644 --- a/ethdb/pebble/pebble.go +++ b/ethdb/pebble/pebble.go @@ -182,10 +182,18 @@ func New(file string, cache int, handles int, namespace string, readonly bool) ( memTableSize = maxMemTableSize - 1 } db := &Database{ - fn: file, - log: logger, - quitChan: make(chan chan error), - writeOptions: &pebble.WriteOptions{Sync: false}, + fn: file, + log: logger, + quitChan: make(chan chan error), + + // Use asynchronous write mode by default. Otherwise, the overhead of frequent fsync + // operations can be significant, especially on platforms with slow fsync performance + // (e.g., macOS) or less capable SSDs. + // + // Note that enabling async writes means recent data may be lost in the event of an + // application-level panic (writes will also be lost on a machine-level failure, + // of course). Geth is expected to handle recovery from an unclean shutdown. + writeOptions: pebble.NoSync, } opt := &pebble.Options{ // Pebble has a single combined cache area and the write @@ -426,12 +434,11 @@ func (d *Database) Path() string { // Sync flushes all pending writes in the write-ahead-log to disk, ensuring // data durability up to that point. func (d *Database) Sync() error { - b := d.db.NewBatch() - // The entry (value=nil) is not written to the database; it is only // added to the WAL. Writing this special log entry in sync mode // automatically flushes all previous writes, ensuring database // durability up to this point. + b := d.db.NewBatch() b.LogData(nil, nil) return d.db.Apply(b, pebble.Sync) } diff --git a/triedb/pathdb/buffer.go b/triedb/pathdb/buffer.go index 12c98f24f017..c4e081b9737a 100644 --- a/triedb/pathdb/buffer.go +++ b/triedb/pathdb/buffer.go @@ -135,8 +135,11 @@ func (b *buffer) flush(db ethdb.KeyValueStore, freezer ethdb.AncientWriter, node start = time.Now() batch = db.NewBatchWithSize(b.nodes.dbsize() * 11 / 10) // extra 10% for potential pebble internal stuff ) - // Explicitly sync the state freezer, ensuring that all written - // data is transferred to disk before updating the key-value store. + // Explicitly sync the state freezer to ensure all written data is persisted to disk + // before updating the key-value store. + // + // This step is crucial to guarantee that the corresponding state history remains + // available for state rollback. if freezer != nil { if err := freezer.SyncAncient(); err != nil { return err From 546af1adc1fae11683891248a8ac983f2fc53fea Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Wed, 30 Apr 2025 08:47:48 +0800 Subject: [PATCH 6/7] core, ethdb, trie: rename Sync to SyncKeyValue --- core/headerchain.go | 2 +- core/rawdb/table.go | 8 ++++---- ethdb/database.go | 6 +++--- ethdb/leveldb/leveldb.go | 6 +++--- ethdb/memorydb/memorydb.go | 6 +++--- ethdb/pebble/pebble.go | 6 +++--- ethdb/remotedb/remotedb.go | 2 +- trie/trie_test.go | 2 +- triedb/pathdb/database.go | 2 +- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/core/headerchain.go b/core/headerchain.go index 477c629ddabf..abe8086cf873 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -611,7 +611,7 @@ func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn Updat } // Explicitly flush the pending writes in the key-value store to disk, ensuring // data durability of the previous deletions. - if err := hc.chainDb.Sync(); err != nil { + if err := hc.chainDb.SyncKeyValue(); err != nil { log.Crit("Failed to sync the key-value store in setHead", "err", err) } // Truncate the excessive chain segments in the ancient store. diff --git a/core/rawdb/table.go b/core/rawdb/table.go index d481fbd3ec21..9a342a8217b2 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -188,10 +188,10 @@ func (t *table) Compact(start []byte, limit []byte) error { return t.db.Compact(start, limit) } -// Sync ensures that all pending writes are flushed to disk, guaranteeing -// data durability up to the point. -func (t *table) Sync() error { - return t.db.Sync() +// SyncKeyValue ensures that all pending writes are flushed to disk, +// guaranteeing data durability up to the point. +func (t *table) SyncKeyValue() error { + return t.db.SyncKeyValue() } // NewBatch creates a write-only database that buffers changes to its host db diff --git a/ethdb/database.go b/ethdb/database.go index f277acb550e7..fbf142e5546b 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -59,9 +59,9 @@ type KeyValueStater interface { // KeyValueSyncer wraps the Sync method of a backing data store. type KeyValueSyncer interface { - // Sync ensures that all pending writes are flushed to disk, guaranteeing - // data durability up to the point. - Sync() error + // SyncKeyValue ensures that all pending writes are flushed to disk, + // guaranteeing data durability up to the point. + SyncKeyValue() error } // Compacter wraps the Compact method of a backing data store. diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index b583365ac2d9..223d01aff6e1 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -324,9 +324,9 @@ func (db *Database) Path() string { return db.fn } -// Sync flushes all pending writes in the write-ahead-log to disk, ensuring -// data durability up to that point. -func (db *Database) Sync() error { +// SyncKeyValue flushes all pending writes in the write-ahead-log to disk, +// ensuring data durability up to that point. +func (db *Database) SyncKeyValue() error { // In theory, the WAL (Write-Ahead Log) can be explicitly synchronized using // a write operation with SYNC=true. However, there is no dedicated key reserved // for this purpose, and even a nil key (key=nil) is considered a valid diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go index c8196f71379c..f56727cf4a63 100644 --- a/ethdb/memorydb/memorydb.go +++ b/ethdb/memorydb/memorydb.go @@ -199,9 +199,9 @@ func (db *Database) Compact(start []byte, limit []byte) error { return nil } -// Sync ensures that all pending writes are flushed to disk, guaranteeing -// data durability up to the point. -func (db *Database) Sync() error { +// SyncKeyValue ensures that all pending writes are flushed to disk, +// guaranteeing data durability up to the point. +func (db *Database) SyncKeyValue() error { return nil } diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go index 5b2918aa890a..9ece99565526 100644 --- a/ethdb/pebble/pebble.go +++ b/ethdb/pebble/pebble.go @@ -431,9 +431,9 @@ func (d *Database) Path() string { return d.fn } -// Sync flushes all pending writes in the write-ahead-log to disk, ensuring -// data durability up to that point. -func (d *Database) Sync() error { +// SyncKeyValue flushes all pending writes in the write-ahead-log to disk, +// ensuring data durability up to that point. +func (d *Database) SyncKeyValue() error { // The entry (value=nil) is not written to the database; it is only // added to the WAL. Writing this special log entry in sync mode // automatically flushes all previous writes, ensuring database diff --git a/ethdb/remotedb/remotedb.go b/ethdb/remotedb/remotedb.go index 13b19facea7f..a417a25854fd 100644 --- a/ethdb/remotedb/remotedb.go +++ b/ethdb/remotedb/remotedb.go @@ -138,7 +138,7 @@ func (db *Database) Compact(start []byte, limit []byte) error { return nil } -func (db *Database) Sync() error { +func (db *Database) SyncKeyValue() error { return nil } diff --git a/trie/trie_test.go b/trie/trie_test.go index 0a297b317d7a..91fde6dbf260 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -830,7 +830,7 @@ func (s *spongeDb) NewBatch() ethdb.Batch { return &spongeBat func (s *spongeDb) NewBatchWithSize(size int) ethdb.Batch { return &spongeBatch{s} } func (s *spongeDb) Stat() (string, error) { panic("implement me") } func (s *spongeDb) Compact(start []byte, limit []byte) error { panic("implement me") } -func (s *spongeDb) Sync() error { return nil } +func (s *spongeDb) SyncKeyValue() error { return nil } func (s *spongeDb) Close() error { return nil } func (s *spongeDb) Put(key []byte, value []byte) error { var ( diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index 85103063b57c..155e28543dba 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -460,7 +460,7 @@ func (db *Database) Recover(root common.Hash) error { // recent key-value writes are lost due to an application panic, while // the associated state histories have already been removed, resulting // in the inability to perform a state rollback. - if err := db.diskdb.Sync(); err != nil { + if err := db.diskdb.SyncKeyValue(); err != nil { return err } _, err := truncateFromHead(db.diskdb, db.freezer, dl.stateID()) From cbd8ffcc3ad0cb0e8a1ea9cdce103ba4e745f448 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Wed, 30 Apr 2025 08:59:41 +0800 Subject: [PATCH 7/7] core, ethdb: fix a flaw in SetHeadWithTimestamp --- core/headerchain.go | 8 ++++++-- ethdb/database.go | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/headerchain.go b/core/headerchain.go index abe8086cf873..6e70dfa86595 100644 --- a/core/headerchain.go +++ b/core/headerchain.go @@ -625,8 +625,12 @@ func (hc *HeaderChain) setHead(headBlock uint64, headTime uint64, updateFn Updat if delFn != nil { // Ignore the error here since light client won't hit this path frozen, _ := hc.chainDb.Ancients() - if headBlock+1 < frozen { - _, err := hc.chainDb.TruncateHead(headBlock + 1) + header := hc.CurrentHeader() + + // Truncate the excessive chain segment above the current chain head + // in the ancient store. + if header.Number.Uint64()+1 < frozen { + _, err := hc.chainDb.TruncateHead(header.Number.Uint64() + 1) if err != nil { log.Crit("Failed to truncate head block", "err", err) } diff --git a/ethdb/database.go b/ethdb/database.go index fbf142e5546b..7f421752c475 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -57,7 +57,7 @@ type KeyValueStater interface { Stat() (string, error) } -// KeyValueSyncer wraps the Sync method of a backing data store. +// KeyValueSyncer wraps the SyncKeyValue method of a backing data store. type KeyValueSyncer interface { // SyncKeyValue ensures that all pending writes are flushed to disk, // guaranteeing data durability up to the point.