From 338d754ed008c425162af244fb8aff04e36b4986 Mon Sep 17 00:00:00 2001 From: Csaba Kiraly Date: Sun, 22 Jun 2025 14:28:29 +0200 Subject: [PATCH 01/11] core/state: fix prefetch on single core CPU (#32075) We need at least one prefetch goroutine. SetLimit(0) would block prefetch. Signed-off-by: Csaba Kiraly --- core/state_prefetcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 2f40e3dc97..c0ce705c77 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -57,7 +57,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c workers errgroup.Group reader = statedb.Reader() ) - workers.SetLimit(4 * runtime.NumCPU() / 5) // Aggressively run the prefetching + workers.SetLimit(max(1, 4*runtime.NumCPU()/5)) // Aggressively run the prefetching // Iterate over and process the individual transactions for i, tx := range block.Transactions() { From 21920207e45225a004f899d475aa996f621973c5 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Sun, 22 Jun 2025 20:40:54 +0800 Subject: [PATCH 02/11] triedb/pathdb, eth: use double-buffer mechanism in pathdb (#30464) Previously, PathDB used a single buffer to aggregate database writes, which needed to be flushed atomically. However, flushing large amounts of data (e.g., 256MB) caused significant overhead, often blocking the system for around 3 seconds during the flush. To mitigate this overhead and reduce performance spikes, a double-buffer mechanism is introduced. When the active buffer fills up, it is marked as frozen and a background flushing process is triggered. Meanwhile, a new buffer is allocated for incoming writes, allowing operations to continue uninterrupted. This approach reduces system blocking times and provides flexibility in adjusting buffer parameters for improved performance. --- core/blockchain.go | 16 +- core/blockchain_snapshot_test.go | 4 +- core/genesis_test.go | 13 +- core/state/snapshot/generate_test.go | 1 + core/state/statedb_test.go | 20 ++- core/state/sync_test.go | 4 +- triedb/pathdb/buffer.go | 119 +++++++++----- triedb/pathdb/database.go | 42 ++--- triedb/pathdb/database_test.go | 1 + triedb/pathdb/disklayer.go | 227 ++++++++++++++++++++------- triedb/pathdb/generate.go | 2 +- triedb/pathdb/generate_test.go | 1 + triedb/pathdb/iterator_binary.go | 8 + triedb/pathdb/iterator_fast.go | 10 ++ triedb/pathdb/iterator_test.go | 77 ++++----- triedb/pathdb/journal.go | 11 +- triedb/pathdb/layertree_test.go | 2 +- 17 files changed, 382 insertions(+), 176 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index ec979a6b17..0f2c065ced 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -162,9 +162,10 @@ const ( // BlockChainConfig contains the configuration of the BlockChain object. type BlockChainConfig struct { // Trie database related options - TrieCleanLimit int // Memory allowance (MB) to use for caching trie nodes in memory - TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk - TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk + TrieCleanLimit int // Memory allowance (MB) to use for caching trie nodes in memory + TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk + TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk + TrieNoAsyncFlush bool // Whether the asynchronous buffer flushing is disallowed Preimages bool // Whether to store preimage of trie key to the disk StateHistory uint64 // Number of blocks from head whose state histories are reserved. @@ -210,7 +211,7 @@ func DefaultConfig() *BlockChainConfig { } } -// WithArchive enabled/disables archive mode on the config. +// WithArchive enables/disables archive mode on the config. func (cfg BlockChainConfig) WithArchive(on bool) *BlockChainConfig { cfg.ArchiveMode = on return &cfg @@ -222,6 +223,12 @@ func (cfg BlockChainConfig) WithStateScheme(scheme string) *BlockChainConfig { return &cfg } +// WithNoAsyncFlush enables/disables asynchronous buffer flushing mode on the config. +func (cfg BlockChainConfig) WithNoAsyncFlush(on bool) *BlockChainConfig { + cfg.TrieNoAsyncFlush = on + return &cfg +} + // triedbConfig derives the configures for trie database. func (cfg *BlockChainConfig) triedbConfig(isVerkle bool) *triedb.Config { config := &triedb.Config{ @@ -243,6 +250,7 @@ func (cfg *BlockChainConfig) triedbConfig(isVerkle bool) *triedb.Config { // for flushing both trie data and state data to disk. The config name // should be updated to eliminate the confusion. WriteBufferSize: cfg.TrieDirtyLimit * 1024 * 1024, + NoAsyncFlush: cfg.TrieNoAsyncFlush, } } return config diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index 5550907b0d..ae9398b97d 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -81,7 +81,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo } engine = ethash.NewFullFaker() ) - chain, err := NewBlockChain(db, gspec, engine, DefaultConfig().WithStateScheme(basic.scheme)) + chain, err := NewBlockChain(db, gspec, engine, DefaultConfig().WithStateScheme(basic.scheme).WithNoAsyncFlush(true)) if err != nil { t.Fatalf("Failed to create chain: %v", err) } @@ -572,7 +572,7 @@ func TestHighCommitCrashWithNewSnapshot(t *testing.T) { // // Expected head header : C8 // Expected head fast block: C8 - // Expected head block : G (Hash mode), C6 (Hash mode) + // Expected head block : G (Hash mode), C6 (Path mode) // Expected snapshot disk : C4 (Hash mode) for _, scheme := range []string{rawdb.HashScheme, rawdb.PathScheme} { expHead := uint64(0) diff --git a/core/genesis_test.go b/core/genesis_test.go index 9dc4bfb3df..a41dfce578 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -256,7 +256,9 @@ func newDbConfig(scheme string) *triedb.Config { if scheme == rawdb.HashScheme { return triedb.HashDefaults } - return &triedb.Config{PathDB: pathdb.Defaults} + config := *pathdb.Defaults + config.NoAsyncFlush = true + return &triedb.Config{PathDB: &config} } func TestVerkleGenesisCommit(t *testing.T) { @@ -313,7 +315,14 @@ func TestVerkleGenesisCommit(t *testing.T) { } db := rawdb.NewMemoryDatabase() - triedb := triedb.NewDatabase(db, triedb.VerkleDefaults) + + config := *pathdb.Defaults + config.NoAsyncFlush = true + + triedb := triedb.NewDatabase(db, &triedb.Config{ + IsVerkle: true, + PathDB: &config, + }) block := genesis.MustCommit(db, triedb) if !bytes.Equal(block.Root().Bytes(), expected) { t.Fatalf("invalid genesis state root, expected %x, got %x", expected, block.Root()) diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go index 3f83cb1a00..4488630095 100644 --- a/core/state/snapshot/generate_test.go +++ b/core/state/snapshot/generate_test.go @@ -168,6 +168,7 @@ func newHelper(scheme string) *testHelper { if scheme == rawdb.PathScheme { config.PathDB = &pathdb.Config{ SnapshotNoBuild: true, + NoAsyncFlush: true, } // disable caching } else { config.HashDB = &hashdb.Config{} // disable caching diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 2c87d81d40..061ac59752 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -982,6 +982,7 @@ func testMissingTrieNodes(t *testing.T, scheme string) { TrieCleanSize: 0, StateCleanSize: 0, WriteBufferSize: 0, + NoAsyncFlush: true, }}) // disable caching } else { tdb = triedb.NewDatabase(memDb, &triedb.Config{HashDB: &hashdb.Config{ @@ -1004,18 +1005,25 @@ func testMissingTrieNodes(t *testing.T, scheme string) { // force-flush tdb.Commit(root, false) } - // Create a new state on the old root - state, _ = New(root, db) // Now we clear out the memdb it := memDb.NewIterator(nil, nil) for it.Next() { k := it.Key() - // Leave the root intact - if !bytes.Equal(k, root[:]) { - t.Logf("key: %x", k) - memDb.Delete(k) + if scheme == rawdb.HashScheme { + if !bytes.Equal(k, root[:]) { + t.Logf("key: %x", k) + memDb.Delete(k) + } + } + if scheme == rawdb.PathScheme { + rk := k[len(rawdb.TrieNodeAccountPrefix):] + if len(rk) != 0 { + t.Logf("key: %x", k) + memDb.Delete(k) + } } } + state, _ = New(root, db) balance := state.GetBalance(addr) // The removed elem should lead to it returning zero balance if exp, got := uint64(0), balance.Uint64(); got != exp { diff --git a/core/state/sync_test.go b/core/state/sync_test.go index 5c8b5a90f7..cae0e0a936 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -46,7 +46,9 @@ func makeTestState(scheme string) (ethdb.Database, Database, *triedb.Database, c // Create an empty state config := &triedb.Config{Preimages: true} if scheme == rawdb.PathScheme { - config.PathDB = pathdb.Defaults + pconfig := *pathdb.Defaults + pconfig.NoAsyncFlush = true + config.PathDB = &pconfig } else { config.HashDB = hashdb.Defaults } diff --git a/triedb/pathdb/buffer.go b/triedb/pathdb/buffer.go index e639a43835..138962110f 100644 --- a/triedb/pathdb/buffer.go +++ b/triedb/pathdb/buffer.go @@ -17,6 +17,7 @@ package pathdb import ( + "errors" "fmt" "time" @@ -37,6 +38,13 @@ type buffer struct { limit uint64 // The maximum memory allowance in bytes nodes *nodeSet // Aggregated trie node set states *stateSet // Aggregated state set + + // done is the notifier whether the content in buffer has been flushed or not. + // This channel is nil if the buffer is not frozen. + done chan struct{} + + // flushErr memorizes the error if any exception occurs during flushing + flushErr error } // newBuffer initializes the buffer with the provided states and trie nodes. @@ -61,7 +69,7 @@ func (b *buffer) account(hash common.Hash) ([]byte, bool) { return b.states.account(hash) } -// storage retrieves the storage slot with account address hash and slot key. +// storage retrieves the storage slot with account address hash and slot key hash. func (b *buffer) storage(addrHash common.Hash, storageHash common.Hash) ([]byte, bool) { return b.states.storage(addrHash, storageHash) } @@ -124,43 +132,78 @@ func (b *buffer) size() uint64 { // flush persists the in-memory dirty trie node into the disk if the configured // memory threshold is reached. Note, all data must be written atomically. -func (b *buffer) flush(root common.Hash, db ethdb.KeyValueStore, freezer ethdb.AncientWriter, progress []byte, nodesCache, statesCache *fastcache.Cache, id uint64) error { - // Ensure the target state id is aligned with the internal counter. - head := rawdb.ReadPersistentStateID(db) - if head+b.layers != id { - return fmt.Errorf("buffer layers (%d) cannot be applied on top of persisted state id (%d) to reach requested state id (%d)", b.layers, head, id) +func (b *buffer) flush(root common.Hash, db ethdb.KeyValueStore, freezer ethdb.AncientWriter, progress []byte, nodesCache, statesCache *fastcache.Cache, id uint64, postFlush func()) { + if b.done != nil { + panic("duplicated flush operation") } - // Terminate the state snapshot generation if it's active - var ( - start = time.Now() - batch = db.NewBatchWithSize((b.nodes.dbsize() + b.states.dbsize()) * 11 / 10) // extra 10% for potential pebble internal stuff - ) - // 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 - } - } - nodes := b.nodes.write(batch, nodesCache) - accounts, slots := b.states.write(batch, progress, statesCache) - rawdb.WritePersistentStateID(batch, id) - rawdb.WriteSnapshotRoot(batch, root) + b.done = make(chan struct{}) // allocate the channel for notification - // Flush all mutations in a single batch - size := batch.ValueSize() - if err := batch.Write(); err != nil { - return err - } - commitBytesMeter.Mark(int64(size)) - commitNodesMeter.Mark(int64(nodes)) - commitAccountsMeter.Mark(int64(accounts)) - commitStoragesMeter.Mark(int64(slots)) - commitTimeTimer.UpdateSince(start) - b.reset() - log.Debug("Persisted buffer content", "nodes", nodes, "accounts", accounts, "slots", slots, "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start))) - return nil + // Schedule the background thread to construct the batch, which usually + // take a few seconds. + go func() { + defer func() { + if postFlush != nil { + postFlush() + } + close(b.done) + }() + + // Ensure the target state id is aligned with the internal counter. + head := rawdb.ReadPersistentStateID(db) + if head+b.layers != id { + b.flushErr = fmt.Errorf("buffer layers (%d) cannot be applied on top of persisted state id (%d) to reach requested state id (%d)", b.layers, head, id) + return + } + + // Terminate the state snapshot generation if it's active + var ( + start = time.Now() + batch = db.NewBatchWithSize((b.nodes.dbsize() + b.states.dbsize()) * 11 / 10) // extra 10% for potential pebble internal stuff + ) + // 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 { + b.flushErr = err + return + } + } + nodes := b.nodes.write(batch, nodesCache) + accounts, slots := b.states.write(batch, progress, statesCache) + rawdb.WritePersistentStateID(batch, id) + rawdb.WriteSnapshotRoot(batch, root) + + // Flush all mutations in a single batch + size := batch.ValueSize() + if err := batch.Write(); err != nil { + b.flushErr = err + return + } + commitBytesMeter.Mark(int64(size)) + commitNodesMeter.Mark(int64(nodes)) + commitAccountsMeter.Mark(int64(accounts)) + commitStoragesMeter.Mark(int64(slots)) + commitTimeTimer.UpdateSince(start) + + // The content in the frozen buffer is kept for consequent state access, + // TODO (rjl493456442) measure the gc overhead for holding this struct. + // TODO (rjl493456442) can we somehow get rid of it after flushing?? + // TODO (rjl493456442) buffer itself is not thread-safe, add the lock + // protection if try to reset the buffer here. + // b.reset() + log.Debug("Persisted buffer content", "nodes", nodes, "accounts", accounts, "slots", slots, "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start))) + }() +} + +// waitFlush blocks until the buffer has been fully flushed and returns any +// stored errors that occurred during the process. +func (b *buffer) waitFlush() error { + if b.done == nil { + return errors.New("the buffer is not frozen") + } + <-b.done + return b.flushErr } diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index 3174a7c964..9795d81e63 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -119,7 +119,11 @@ type Config struct { StateCleanSize int // Maximum memory allowance (in bytes) for caching clean state data WriteBufferSize int // Maximum memory allowance (in bytes) for write buffer ReadOnly bool // Flag whether the database is opened in read only mode - SnapshotNoBuild bool // Flag Whether the background generation is allowed + + // Testing configurations + SnapshotNoBuild bool // Flag Whether the state generation is allowed + NoAsyncFlush bool // Flag whether the background buffer flushing is allowed + NoAsyncGeneration bool // Flag whether the background generation is allowed } // sanitize checks the provided user configurations and changes anything that's @@ -366,6 +370,12 @@ func (db *Database) setStateGenerator() error { } stats.log("Starting snapshot generation", root, generator.Marker) dl.generator.run(root) + + // Block until the generation completes. It's the feature used in + // unit tests. + if db.config.NoAsyncGeneration { + <-dl.generator.done + } return nil } @@ -434,8 +444,8 @@ func (db *Database) Disable() error { // Terminate the state generator if it's active and mark the disk layer // as stale to prevent access to persistent state. disk := db.tree.bottom() - if disk.generator != nil { - disk.generator.stop() + if err := disk.terminate(); err != nil { + return err } disk.markStale() @@ -592,12 +602,14 @@ func (db *Database) Close() error { // following mutations. db.readOnly = true - // Terminate the background generation if it's active - disk := db.tree.bottom() - if disk.generator != nil { - disk.generator.stop() + // Block until the background flushing is finished. It must + // be done before terminating the potential background snapshot + // generator. + dl := db.tree.bottom() + if err := dl.terminate(); err != nil { + return err } - disk.resetCache() // release the memory held by clean cache + dl.resetCache() // release the memory held by clean cache // Close the attached state history freezer. if db.freezer == nil { @@ -662,16 +674,6 @@ func (db *Database) HistoryRange() (uint64, uint64, error) { return historyRange(db.freezer) } -// waitGeneration waits until the background generation is finished. It assumes -// that the generation is permitted; otherwise, it will block indefinitely. -func (db *Database) waitGeneration() { - gen := db.tree.bottom().generator - if gen == nil || gen.completed() { - return - } - <-gen.done -} - // AccountIterator creates a new account iterator for the specified root hash and // seeks to a starting account hash. func (db *Database) AccountIterator(root common.Hash, seek common.Hash) (AccountIterator, error) { @@ -681,7 +683,7 @@ func (db *Database) AccountIterator(root common.Hash, seek common.Hash) (Account if wait { return nil, errDatabaseWaitSync } - if gen := db.tree.bottom().generator; gen != nil && !gen.completed() { + if !db.tree.bottom().genComplete() { return nil, errNotConstructed } return newFastAccountIterator(db, root, seek) @@ -696,7 +698,7 @@ func (db *Database) StorageIterator(root common.Hash, account common.Hash, seek if wait { return nil, errDatabaseWaitSync } - if gen := db.tree.bottom().generator; gen != nil && !gen.completed() { + if !db.tree.bottom().genComplete() { return nil, errNotConstructed } return newFastStorageIterator(db, root, account, seek) diff --git a/triedb/pathdb/database_test.go b/triedb/pathdb/database_test.go index ca106cc27c..6fc099015c 100644 --- a/triedb/pathdb/database_test.go +++ b/triedb/pathdb/database_test.go @@ -129,6 +129,7 @@ func newTester(t *testing.T, historyLimit uint64, isVerkle bool, layers int) *te TrieCleanSize: 256 * 1024, StateCleanSize: 256 * 1024, WriteBufferSize: 256 * 1024, + NoAsyncFlush: true, }, isVerkle) obj = &tester{ diff --git a/triedb/pathdb/disklayer.go b/triedb/pathdb/disklayer.go index 1c9efb024b..494164987f 100644 --- a/triedb/pathdb/disklayer.go +++ b/triedb/pathdb/disklayer.go @@ -41,17 +41,20 @@ type diskLayer struct { nodes *fastcache.Cache // GC friendly memory cache of clean nodes states *fastcache.Cache // GC friendly memory cache of clean states - buffer *buffer // Dirty buffer to aggregate writes of nodes and states - stale bool // Signals that the layer became stale (state progressed) - lock sync.RWMutex // Lock used to protect stale flag and genMarker + buffer *buffer // Live buffer to aggregate writes + frozen *buffer // Frozen node buffer waiting for flushing + + stale bool // Signals that the layer became stale (state progressed) + lock sync.RWMutex // Lock used to protect stale flag and genMarker // The generator is set if the state snapshot was not fully completed, // regardless of whether the background generation is running or not. + // It should only be unset if the generation completes. generator *generator } // newDiskLayer creates a new disk layer based on the passing arguments. -func newDiskLayer(root common.Hash, id uint64, db *Database, nodes *fastcache.Cache, states *fastcache.Cache, buffer *buffer) *diskLayer { +func newDiskLayer(root common.Hash, id uint64, db *Database, nodes *fastcache.Cache, states *fastcache.Cache, buffer *buffer, frozen *buffer) *diskLayer { // Initialize the clean caches if the memory allowance is not zero // or reuse the provided caches if they are not nil (inherited from // the original disk layer). @@ -68,6 +71,7 @@ func newDiskLayer(root common.Hash, id uint64, db *Database, nodes *fastcache.Ca nodes: nodes, states: states, buffer: buffer, + frozen: frozen, } } @@ -114,16 +118,19 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co if dl.stale { return nil, common.Hash{}, nil, errSnapshotStale } - // Try to retrieve the trie node from the not-yet-written - // node buffer first. Note the buffer is lock free since - // it's impossible to mutate the buffer before tagging the - // layer as stale. - n, found := dl.buffer.node(owner, path) - if found { - dirtyNodeHitMeter.Mark(1) - dirtyNodeReadMeter.Mark(int64(len(n.Blob))) - dirtyNodeHitDepthHist.Update(int64(depth)) - return n.Blob, n.Hash, &nodeLoc{loc: locDirtyCache, depth: depth}, nil + // Try to retrieve the trie node from the not-yet-written node buffer first + // (both the live one and the frozen one). Note the buffer is lock free since + // it's impossible to mutate the buffer before tagging the layer as stale. + for _, buffer := range []*buffer{dl.buffer, dl.frozen} { + if buffer != nil { + n, found := buffer.node(owner, path) + if found { + dirtyNodeHitMeter.Mark(1) + dirtyNodeReadMeter.Mark(int64(len(n.Blob))) + dirtyNodeHitDepthHist.Update(int64(depth)) + return n.Blob, n.Hash, &nodeLoc{loc: locDirtyCache, depth: depth}, nil + } + } } dirtyNodeMissMeter.Mark(1) @@ -144,6 +151,11 @@ func (dl *diskLayer) node(owner common.Hash, path []byte, depth int) ([]byte, co } else { blob = rawdb.ReadStorageTrieNode(dl.db.diskdb, owner, path) } + // Store the resolved data in the clean cache. The background buffer flusher + // may also write to the clean cache concurrently, but two writers cannot + // write the same item with different content. If the item already exists, + // it will be found in the frozen buffer, eliminating the need to check the + // database. if dl.nodes != nil && len(blob) > 0 { dl.nodes.Set(key, blob) cleanNodeWriteMeter.Mark(int64(len(blob))) @@ -162,22 +174,25 @@ func (dl *diskLayer) account(hash common.Hash, depth int) ([]byte, error) { if dl.stale { return nil, errSnapshotStale } - // Try to retrieve the account from the not-yet-written - // node buffer first. Note the buffer is lock free since - // it's impossible to mutate the buffer before tagging the - // layer as stale. - blob, found := dl.buffer.account(hash) - if found { - dirtyStateHitMeter.Mark(1) - dirtyStateReadMeter.Mark(int64(len(blob))) - dirtyStateHitDepthHist.Update(int64(depth)) + // Try to retrieve the trie node from the not-yet-written node buffer first + // (both the live one and the frozen one). Note the buffer is lock free since + // it's impossible to mutate the buffer before tagging the layer as stale. + for _, buffer := range []*buffer{dl.buffer, dl.frozen} { + if buffer != nil { + blob, found := buffer.account(hash) + if found { + dirtyStateHitMeter.Mark(1) + dirtyStateReadMeter.Mark(int64(len(blob))) + dirtyStateHitDepthHist.Update(int64(depth)) - if len(blob) == 0 { - stateAccountInexMeter.Mark(1) - } else { - stateAccountExistMeter.Mark(1) + if len(blob) == 0 { + stateAccountInexMeter.Mark(1) + } else { + stateAccountExistMeter.Mark(1) + } + return blob, nil + } } - return blob, nil } dirtyStateMissMeter.Mark(1) @@ -203,7 +218,13 @@ func (dl *diskLayer) account(hash common.Hash, depth int) ([]byte, error) { cleanStateMissMeter.Mark(1) } // Try to retrieve the account from the disk. - blob = rawdb.ReadAccountSnapshot(dl.db.diskdb, hash) + blob := rawdb.ReadAccountSnapshot(dl.db.diskdb, hash) + + // Store the resolved data in the clean cache. The background buffer flusher + // may also write to the clean cache concurrently, but two writers cannot + // write the same item with different content. If the item already exists, + // it will be found in the frozen buffer, eliminating the need to check the + // database. if dl.states != nil { dl.states.Set(hash[:], blob) cleanStateWriteMeter.Mark(int64(len(blob))) @@ -231,21 +252,24 @@ func (dl *diskLayer) storage(accountHash, storageHash common.Hash, depth int) ([ if dl.stale { return nil, errSnapshotStale } - // Try to retrieve the storage slot from the not-yet-written - // node buffer first. Note the buffer is lock free since - // it's impossible to mutate the buffer before tagging the - // layer as stale. - if blob, found := dl.buffer.storage(accountHash, storageHash); found { - dirtyStateHitMeter.Mark(1) - dirtyStateReadMeter.Mark(int64(len(blob))) - dirtyStateHitDepthHist.Update(int64(depth)) + // Try to retrieve the trie node from the not-yet-written node buffer first + // (both the live one and the frozen one). Note the buffer is lock free since + // it's impossible to mutate the buffer before tagging the layer as stale. + for _, buffer := range []*buffer{dl.buffer, dl.frozen} { + if buffer != nil { + if blob, found := buffer.storage(accountHash, storageHash); found { + dirtyStateHitMeter.Mark(1) + dirtyStateReadMeter.Mark(int64(len(blob))) + dirtyStateHitDepthHist.Update(int64(depth)) - if len(blob) == 0 { - stateStorageInexMeter.Mark(1) - } else { - stateStorageExistMeter.Mark(1) + if len(blob) == 0 { + stateStorageInexMeter.Mark(1) + } else { + stateStorageExistMeter.Mark(1) + } + return blob, nil + } } - return blob, nil } dirtyStateMissMeter.Mark(1) @@ -273,6 +297,12 @@ func (dl *diskLayer) storage(accountHash, storageHash common.Hash, depth int) ([ } // Try to retrieve the account from the disk blob := rawdb.ReadStorageSnapshot(dl.db.diskdb, accountHash, storageHash) + + // Store the resolved data in the clean cache. The background buffer flusher + // may also write to the clean cache concurrently, but two writers cannot + // write the same item with different content. If the item already exists, + // it will be found in the frozen buffer, eliminating the need to check the + // database. if dl.states != nil { dl.states.Set(key, blob) cleanStateWriteMeter.Mark(int64(len(blob))) @@ -341,7 +371,8 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) { // truncation) surpasses the persisted state ID, we take the necessary action // of forcibly committing the cached dirty states to ensure that the persisted // state ID remains higher. - if !force && rawdb.ReadPersistentStateID(dl.db.diskdb) < oldest { + persistedID := rawdb.ReadPersistentStateID(dl.db.diskdb) + if !force && persistedID < oldest { force = true } // Merge the trie nodes and flat states of the bottom-most diff layer into the @@ -351,12 +382,25 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) { // Terminate the background state snapshot generation before mutating the // persistent state. if combined.full() || force { + // Wait until the previous frozen buffer is fully flushed + if dl.frozen != nil { + if err := dl.frozen.waitFlush(); err != nil { + return nil, err + } + } + // Release the frozen buffer and the internally referenced maps will + // be reclaimed by GC. + dl.frozen = nil + // Terminate the background state snapshot generator before flushing // to prevent data race. - var progress []byte - if dl.generator != nil { - dl.generator.stop() - progress = dl.generator.progressMarker() + var ( + progress []byte + gen = dl.generator + ) + if gen != nil { + gen.stop() + progress = gen.progressMarker() // If the snapshot has been fully generated, unset the generator if progress == nil { @@ -365,18 +409,33 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) { log.Info("Paused snapshot generation") } } - // Flush the content in combined buffer. Any state data after the progress - // marker will be ignored, as the generator will pick it up later. - if err := combined.flush(bottom.root, dl.db.diskdb, dl.db.freezer, progress, dl.nodes, dl.states, bottom.stateID()); err != nil { - return nil, err - } - // Resume the background generation if it's not completed yet - if progress != nil { - dl.generator.run(bottom.root) + + // Freeze the live buffer and schedule background flushing + dl.frozen = combined + dl.frozen.flush(bottom.root, dl.db.diskdb, dl.db.freezer, progress, dl.nodes, dl.states, bottom.stateID(), func() { + // Resume the background generation if it's not completed yet. + // The generator is assumed to be available if the progress is + // not nil. + // + // Notably, the generator will be shared and linked by all the + // disk layer instances, regardless of the generation is terminated + // or not. + if progress != nil { + gen.run(bottom.root) + } + }) + // Block until the frozen buffer is fully flushed out if the async flushing + // is not allowed, or if the oldest history surpasses the persisted state ID. + if dl.db.config.NoAsyncFlush || persistedID < oldest { + if err := dl.frozen.waitFlush(); err != nil { + return nil, err + } + dl.frozen = nil } + combined = newBuffer(dl.db.config.WriteBufferSize, nil, nil, 0) } // Link the generator if snapshot is not yet completed - ndl := newDiskLayer(bottom.root, bottom.stateID(), dl.db, dl.nodes, dl.states, combined) + ndl := newDiskLayer(bottom.root, bottom.stateID(), dl.db, dl.nodes, dl.states, combined, dl.frozen) if dl.generator != nil { ndl.setGenerator(dl.generator) } @@ -428,7 +487,7 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) { if err != nil { return nil, err } - ndl := newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.states, dl.buffer) + ndl := newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.states, dl.buffer, dl.frozen) // Link the generator if it exists if dl.generator != nil { @@ -437,7 +496,19 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) { log.Debug("Reverted data in write buffer", "oldroot", h.meta.root, "newroot", h.meta.parent, "elapsed", common.PrettyDuration(time.Since(start))) return ndl, nil } - // Terminate the generation before writing any data into database + // Block until the frozen buffer is fully flushed + if dl.frozen != nil { + if err := dl.frozen.waitFlush(); err != nil { + return nil, err + } + // Unset the frozen buffer if it exists, otherwise these "reverted" + // states will still be accessible after revert in frozen buffer. + dl.frozen = nil + } + + // Terminate the generator before writing any data to the database. + // This must be done after flushing the frozen buffer, as the generator + // may be restarted at the end of the flush process. var progress []byte if dl.generator != nil { dl.generator.stop() @@ -455,7 +526,7 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) { } // Link the generator and resume generation if the snapshot is not yet // fully completed. - ndl := newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.states, dl.buffer) + ndl := newDiskLayer(h.meta.parent, dl.id-1, dl.db, dl.nodes, dl.states, dl.buffer, dl.frozen) if dl.generator != nil && !dl.generator.completed() { ndl.generator = dl.generator ndl.generator.run(h.meta.parent) @@ -500,3 +571,41 @@ func (dl *diskLayer) genMarker() []byte { } return dl.generator.progressMarker() } + +// genComplete returns a flag indicating whether the state snapshot has been +// fully generated. +func (dl *diskLayer) genComplete() bool { + dl.lock.RLock() + defer dl.lock.RUnlock() + + return dl.genMarker() == nil +} + +// waitFlush blocks until the background buffer flush is completed. +func (dl *diskLayer) waitFlush() error { + dl.lock.RLock() + defer dl.lock.RUnlock() + + if dl.frozen == nil { + return nil + } + return dl.frozen.waitFlush() +} + +// terminate releases the frozen buffer if it's not nil and terminates the +// background state generator. +func (dl *diskLayer) terminate() error { + dl.lock.Lock() + defer dl.lock.Unlock() + + if dl.frozen != nil { + if err := dl.frozen.waitFlush(); err != nil { + return err + } + dl.frozen = nil + } + if dl.generator != nil { + dl.generator.stop() + } + return nil +} diff --git a/triedb/pathdb/generate.go b/triedb/pathdb/generate.go index f4f98c9d19..2efbbbb4e1 100644 --- a/triedb/pathdb/generate.go +++ b/triedb/pathdb/generate.go @@ -186,7 +186,7 @@ func generateSnapshot(triedb *Database, root common.Hash, noBuild bool) *diskLay stats = &generatorStats{start: time.Now()} genMarker = []byte{} // Initialized but empty! ) - dl := newDiskLayer(root, 0, triedb, nil, nil, newBuffer(triedb.config.WriteBufferSize, nil, nil, 0)) + dl := newDiskLayer(root, 0, triedb, nil, nil, newBuffer(triedb.config.WriteBufferSize, nil, nil, 0), nil) dl.setGenerator(newGenerator(triedb.diskdb, noBuild, genMarker, stats)) if !noBuild { diff --git a/triedb/pathdb/generate_test.go b/triedb/pathdb/generate_test.go index 23efb0e3c5..f38a1ed7c4 100644 --- a/triedb/pathdb/generate_test.go +++ b/triedb/pathdb/generate_test.go @@ -49,6 +49,7 @@ func newGenTester() *genTester { disk := rawdb.NewMemoryDatabase() config := *Defaults config.SnapshotNoBuild = true // no background generation + config.NoAsyncFlush = true // no async flush db := New(disk, &config, false) tr, _ := trie.New(trie.StateTrieID(types.EmptyRootHash), db) return &genTester{ diff --git a/triedb/pathdb/iterator_binary.go b/triedb/pathdb/iterator_binary.go index 0620081d0c..97a2918989 100644 --- a/triedb/pathdb/iterator_binary.go +++ b/triedb/pathdb/iterator_binary.go @@ -45,6 +45,10 @@ type binaryIterator struct { // accounts in a slow, but easily verifiable way. Note this function is used // for initialization, use `newBinaryAccountIterator` as the API. func (dl *diskLayer) initBinaryAccountIterator(seek common.Hash) *binaryIterator { + // Block until the frozen buffer flushing is completed. + if err := dl.waitFlush(); err != nil { + panic(err) + } // The state set in the disk layer is mutable, hold the lock before obtaining // the account list to prevent concurrent map iteration and write. dl.lock.RLock() @@ -113,6 +117,10 @@ func (dl *diffLayer) initBinaryAccountIterator(seek common.Hash) *binaryIterator // storage slots in a slow, but easily verifiable way. Note this function is used // for initialization, use `newBinaryStorageIterator` as the API. func (dl *diskLayer) initBinaryStorageIterator(account common.Hash, seek common.Hash) *binaryIterator { + // Block until the frozen buffer flushing is completed. + if err := dl.waitFlush(); err != nil { + panic(err) + } // The state set in the disk layer is mutable, hold the lock before obtaining // the storage list to prevent concurrent map iteration and write. dl.lock.RLock() diff --git a/triedb/pathdb/iterator_fast.go b/triedb/pathdb/iterator_fast.go index 87b2dd567a..04bf89e874 100644 --- a/triedb/pathdb/iterator_fast.go +++ b/triedb/pathdb/iterator_fast.go @@ -76,6 +76,11 @@ func newFastIterator(db *Database, root common.Hash, account common.Hash, seek c if accountIterator { switch dl := current.(type) { case *diskLayer: + // Ensure no active background buffer flush is in progress, otherwise, + // part of the state data may become invisible. + if err := dl.waitFlush(); err != nil { + return nil, err + } // The state set in the disk layer is mutable, hold the lock before obtaining // the account list to prevent concurrent map iteration and write. dl.lock.RLock() @@ -113,6 +118,11 @@ func newFastIterator(db *Database, root common.Hash, account common.Hash, seek c } else { switch dl := current.(type) { case *diskLayer: + // Ensure no active background buffer flush is in progress, otherwise, + // part of the state data may become invisible. + if err := dl.waitFlush(); err != nil { + return nil, err + } // The state set in the disk layer is mutable, hold the lock before obtaining // the storage list to prevent concurrent map iteration and write. dl.lock.RLock() diff --git a/triedb/pathdb/iterator_test.go b/triedb/pathdb/iterator_test.go index 6517735d7e..b24575cb47 100644 --- a/triedb/pathdb/iterator_test.go +++ b/triedb/pathdb/iterator_test.go @@ -254,10 +254,9 @@ func TestFastIteratorBasics(t *testing.T) { // TestAccountIteratorTraversal tests some simple multi-layer iteration. func TestAccountIteratorTraversal(t *testing.T) { config := &Config{ - WriteBufferSize: 0, + NoAsyncGeneration: true, } db := New(rawdb.NewMemoryDatabase(), config, false) - db.waitGeneration() // Stack three diff layers on top with various overlaps db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 0, trienode.NewMergedNodeSet(), @@ -298,10 +297,9 @@ func TestAccountIteratorTraversal(t *testing.T) { func TestStorageIteratorTraversal(t *testing.T) { config := &Config{ - WriteBufferSize: 0, + NoAsyncGeneration: true, } db := New(rawdb.NewMemoryDatabase(), config, false) - db.waitGeneration() // Stack three diff layers on top with various overlaps db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 0, trienode.NewMergedNodeSet(), @@ -311,14 +309,14 @@ func TestStorageIteratorTraversal(t *testing.T) { NewStateSetWithOrigin(randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x04", "0x05", "0x06"}}, nil), nil, nil, false)) db.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), 0, trienode.NewMergedNodeSet(), - NewStateSetWithOrigin(randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x02", "0x03"}}, nil), nil, nil, false)) + NewStateSetWithOrigin(randomAccountSet("0xaa"), randomStorageSet([]string{"0xaa"}, [][]string{{"0x01", "0x02"}}, nil), nil, nil, false)) // Verify the single and multi-layer iterators head := db.tree.get(common.HexToHash("0x04")) // singleLayer: 0x1, 0x2, 0x3 diffIter := newDiffStorageIterator(common.HexToHash("0xaa"), common.Hash{}, head.(*diffLayer).states.stateSet.storageList(common.HexToHash("0xaa")), nil) - verifyIterator(t, 3, diffIter, verifyNothing) + verifyIterator(t, 2, diffIter, verifyNothing) // binaryIterator: 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 verifyIterator(t, 6, head.(*diffLayer).newBinaryStorageIterator(common.HexToHash("0xaa"), common.Hash{}), verifyStorage) @@ -342,10 +340,9 @@ func TestStorageIteratorTraversal(t *testing.T) { // also expect the correct values to show up. func TestAccountIteratorTraversalValues(t *testing.T) { config := &Config{ - WriteBufferSize: 0, + NoAsyncGeneration: true, } db := New(rawdb.NewMemoryDatabase(), config, false) - db.waitGeneration() // Create a batch of account sets to seed subsequent layers with var ( @@ -458,10 +455,9 @@ func TestAccountIteratorTraversalValues(t *testing.T) { func TestStorageIteratorTraversalValues(t *testing.T) { config := &Config{ - WriteBufferSize: 0, + NoAsyncGeneration: true, } db := New(rawdb.NewMemoryDatabase(), config, false) - db.waitGeneration() wrapStorage := func(storage map[common.Hash][]byte) map[common.Hash]map[common.Hash][]byte { return map[common.Hash]map[common.Hash][]byte{ @@ -591,10 +587,9 @@ func TestAccountIteratorLargeTraversal(t *testing.T) { } // Build up a large stack of snapshots config := &Config{ - WriteBufferSize: 0, + NoAsyncGeneration: true, } db := New(rawdb.NewMemoryDatabase(), config, false) - db.waitGeneration() for i := 1; i < 128; i++ { parent := types.EmptyRootHash @@ -630,10 +625,10 @@ func TestAccountIteratorLargeTraversal(t *testing.T) { // - continues iterating func TestAccountIteratorFlattening(t *testing.T) { config := &Config{ - WriteBufferSize: 10 * 1024, + WriteBufferSize: 10 * 1024, + NoAsyncGeneration: true, } db := New(rawdb.NewMemoryDatabase(), config, false) - db.waitGeneration() // Create a stack of diffs on top db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 1, trienode.NewMergedNodeSet(), @@ -662,11 +657,24 @@ func TestAccountIteratorFlattening(t *testing.T) { } func TestAccountIteratorSeek(t *testing.T) { + t.Run("fast", func(t *testing.T) { + testAccountIteratorSeek(t, func(db *Database, root, seek common.Hash) AccountIterator { + it, _ := db.AccountIterator(root, seek) + return it + }) + }) + t.Run("binary", func(t *testing.T) { + testAccountIteratorSeek(t, func(db *Database, root, seek common.Hash) AccountIterator { + return db.tree.get(root).(*diffLayer).newBinaryAccountIterator(seek) + }) + }) +} + +func testAccountIteratorSeek(t *testing.T, newIterator func(db *Database, root, seek common.Hash) AccountIterator) { config := &Config{ - WriteBufferSize: 0, + NoAsyncGeneration: true, } db := New(rawdb.NewMemoryDatabase(), config, false) - db.waitGeneration() db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 1, trienode.NewMergedNodeSet(), NewStateSetWithOrigin(randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil, nil, nil, false)) @@ -682,39 +690,39 @@ func TestAccountIteratorSeek(t *testing.T) { // 03: aa, bb, dd, ee, f0 (, f0), ff // 04: aa, bb, cc, dd, ee, f0 (, f0), ff (, ff) // Construct various iterators and ensure their traversal is correct - it, _ := db.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xdd")) + it := newIterator(db, common.HexToHash("0x02"), common.HexToHash("0xdd")) defer it.Release() verifyIterator(t, 3, it, verifyAccount) // expected: ee, f0, ff - it, _ = db.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xaa")) + it = newIterator(db, common.HexToHash("0x02"), common.HexToHash("0xaa")) defer it.Release() verifyIterator(t, 4, it, verifyAccount) // expected: aa, ee, f0, ff - it, _ = db.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xff")) + it = newIterator(db, common.HexToHash("0x02"), common.HexToHash("0xff")) defer it.Release() verifyIterator(t, 1, it, verifyAccount) // expected: ff - it, _ = db.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xff1")) + it = newIterator(db, common.HexToHash("0x02"), common.HexToHash("0xff1")) defer it.Release() verifyIterator(t, 0, it, verifyAccount) // expected: nothing - it, _ = db.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xbb")) + it = newIterator(db, common.HexToHash("0x04"), common.HexToHash("0xbb")) defer it.Release() verifyIterator(t, 6, it, verifyAccount) // expected: bb, cc, dd, ee, f0, ff - it, _ = db.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xef")) + it = newIterator(db, common.HexToHash("0x04"), common.HexToHash("0xef")) defer it.Release() verifyIterator(t, 2, it, verifyAccount) // expected: f0, ff - it, _ = db.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xf0")) + it = newIterator(db, common.HexToHash("0x04"), common.HexToHash("0xf0")) defer it.Release() verifyIterator(t, 2, it, verifyAccount) // expected: f0, ff - it, _ = db.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xff")) + it = newIterator(db, common.HexToHash("0x04"), common.HexToHash("0xff")) defer it.Release() verifyIterator(t, 1, it, verifyAccount) // expected: ff - it, _ = db.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xff1")) + it = newIterator(db, common.HexToHash("0x04"), common.HexToHash("0xff1")) defer it.Release() verifyIterator(t, 0, it, verifyAccount) // expected: nothing } @@ -735,10 +743,9 @@ func TestStorageIteratorSeek(t *testing.T) { func testStorageIteratorSeek(t *testing.T, newIterator func(db *Database, root, account, seek common.Hash) StorageIterator) { config := &Config{ - WriteBufferSize: 0, + NoAsyncGeneration: true, } db := New(rawdb.NewMemoryDatabase(), config, false) - db.waitGeneration() // Stack three diff layers on top with various overlaps db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 1, trienode.NewMergedNodeSet(), @@ -807,10 +814,9 @@ func TestAccountIteratorDeletions(t *testing.T) { func testAccountIteratorDeletions(t *testing.T, newIterator func(db *Database, root, seek common.Hash) AccountIterator) { config := &Config{ - WriteBufferSize: 0, + NoAsyncGeneration: true, } db := New(rawdb.NewMemoryDatabase(), config, false) - db.waitGeneration() // Stack three diff layers on top with various overlaps db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 1, trienode.NewMergedNodeSet(), @@ -847,10 +853,9 @@ func testAccountIteratorDeletions(t *testing.T, newIterator func(db *Database, r func TestStorageIteratorDeletions(t *testing.T) { config := &Config{ - WriteBufferSize: 0, + NoAsyncGeneration: true, } db := New(rawdb.NewMemoryDatabase(), config, false) - db.waitGeneration() // Stack three diff layers on top with various overlaps db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 1, trienode.NewMergedNodeSet(), @@ -915,10 +920,10 @@ func TestStaleIterator(t *testing.T) { func testStaleIterator(t *testing.T, newIter func(db *Database, hash common.Hash) Iterator) { config := &Config{ - WriteBufferSize: 16 * 1024 * 1024, + WriteBufferSize: 16 * 1024 * 1024, + NoAsyncGeneration: true, } db := New(rawdb.NewMemoryDatabase(), config, false) - db.waitGeneration() // [02 (disk), 03] db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 1, trienode.NewMergedNodeSet(), @@ -970,10 +975,9 @@ func BenchmarkAccountIteratorTraversal(b *testing.B) { return accounts } config := &Config{ - WriteBufferSize: 0, + NoAsyncGeneration: true, } db := New(rawdb.NewMemoryDatabase(), config, false) - db.waitGeneration() for i := 1; i <= 100; i++ { parent := types.EmptyRootHash @@ -1065,10 +1069,9 @@ func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { return accounts } config := &Config{ - WriteBufferSize: 0, + NoAsyncGeneration: true, } db := New(rawdb.NewMemoryDatabase(), config, false) - db.waitGeneration() db.Update(common.HexToHash("0x02"), types.EmptyRootHash, 1, trienode.NewMergedNodeSet(), NewStateSetWithOrigin(makeAccounts(2000), nil, nil, nil, false)) for i := 2; i <= 100; i++ { diff --git a/triedb/pathdb/journal.go b/triedb/pathdb/journal.go index 5dc6da92b8..e88b3e062f 100644 --- a/triedb/pathdb/journal.go +++ b/triedb/pathdb/journal.go @@ -160,7 +160,7 @@ func (db *Database) loadLayers() layer { log.Info("Failed to load journal, discard it", "err", err) } // Return single layer with persistent state. - return newDiskLayer(root, rawdb.ReadPersistentStateID(db.diskdb), db, nil, nil, newBuffer(db.config.WriteBufferSize, nil, nil, 0)) + return newDiskLayer(root, rawdb.ReadPersistentStateID(db.diskdb), db, nil, nil, newBuffer(db.config.WriteBufferSize, nil, nil, 0), nil) } // loadDiskLayer reads the binary blob from the layer journal, reconstructing @@ -192,7 +192,7 @@ func (db *Database) loadDiskLayer(r *rlp.Stream) (layer, error) { if err := states.decode(r); err != nil { return nil, err } - return newDiskLayer(root, id, db, nil, nil, newBuffer(db.config.WriteBufferSize, &nodes, &states, id-stored)), nil + return newDiskLayer(root, id, db, nil, nil, newBuffer(db.config.WriteBufferSize, &nodes, &states, id-stored), nil), nil } // loadDiffLayer reads the next sections of a layer journal, reconstructing a new @@ -301,9 +301,10 @@ func (db *Database) Journal(root common.Hash) error { } else { // disk layer only on noop runs (likely) or deep reorgs (unlikely) log.Info("Persisting dirty state to disk", "root", root, "layers", disk.buffer.layers) } - // Terminate the background state generation if it's active - if disk.generator != nil { - disk.generator.stop() + // Block until the background flushing is finished and terminate + // the potential active state generator. + if err := disk.terminate(); err != nil { + return err } start := time.Now() diff --git a/triedb/pathdb/layertree_test.go b/triedb/pathdb/layertree_test.go index a72ff5899e..a76d60ba5b 100644 --- a/triedb/pathdb/layertree_test.go +++ b/triedb/pathdb/layertree_test.go @@ -27,7 +27,7 @@ import ( func newTestLayerTree() *layerTree { db := New(rawdb.NewMemoryDatabase(), nil, false) - l := newDiskLayer(common.Hash{0x1}, 0, db, nil, nil, newBuffer(0, nil, nil, 0)) + l := newDiskLayer(common.Hash{0x1}, 0, db, nil, nil, newBuffer(0, nil, nil, 0), nil) t := newLayerTree(l) return t } From 78b6059341bdc2b58ab956aac1bd2ddd1d1c1534 Mon Sep 17 00:00:00 2001 From: Delweng Date: Mon, 23 Jun 2025 14:04:21 +0800 Subject: [PATCH 03/11] eth: quick canceling block inserting when debug_setHead is invoked (#32067) If Geth is engaged in a long-run block synchronization, such as a full syncing over a large number of blocks, invoking `debug_setHead` will cause `downloader.Cancel` to wait for all fetchers to stop first. This can be time-consuming, particularly for the block processing thread. To address this, we manually call `blockchain.StopInsert` to interrupt the blocking processing thread and allow it to exit immediately, and after that call `blockchain.ResumeInsert` to resume the block downloading process. Additionally, we add a sanity check for the input block number of `debug_setHead` to ensure its validity. --------- Signed-off-by: jsvisa Co-authored-by: Gary Rong --- core/blockchain.go | 16 ++++++++++------ eth/downloader/downloader.go | 9 ++++----- internal/ethapi/api.go | 10 +++++++++- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 0f2c065ced..3bb681d7c3 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1244,7 +1244,7 @@ func (bc *BlockChain) stopWithoutSaving() { bc.scope.Close() // Signal shutdown to all goroutines. - bc.StopInsert() + bc.InterruptInsert(true) // Now wait for all chain modifications to end and persistent goroutines to exit. // @@ -1318,11 +1318,15 @@ func (bc *BlockChain) Stop() { log.Info("Blockchain stopped") } -// StopInsert interrupts all insertion methods, causing them to return -// errInsertionInterrupted as soon as possible. Insertion is permanently disabled after -// calling this method. -func (bc *BlockChain) StopInsert() { - bc.procInterrupt.Store(true) +// InterruptInsert interrupts all insertion methods, causing them to return +// errInsertionInterrupted as soon as possible, or resume the chain insertion +// if required. +func (bc *BlockChain) InterruptInsert(on bool) { + if on { + bc.procInterrupt.Store(true) + } else { + bc.procInterrupt.Store(false) + } } // insertStopped returns true after StopInsert has been called. diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index 8f8f219597..539aaef40e 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -199,8 +199,8 @@ type BlockChain interface { // InsertChain inserts a batch of blocks into the local chain. InsertChain(types.Blocks) (int, error) - // StopInsert interrupts the inserting process. - StopInsert() + // InterruptInsert whether disables the chain insertion. + InterruptInsert(on bool) // InsertReceiptChain inserts a batch of blocks along with their receipts // into the local chain. Blocks older than the specified `ancientLimit` @@ -630,16 +630,15 @@ func (d *Downloader) cancel() { // Cancel aborts all of the operations and waits for all download goroutines to // finish before returning. func (d *Downloader) Cancel() { + d.blockchain.InterruptInsert(true) d.cancel() d.cancelWg.Wait() + d.blockchain.InterruptInsert(false) } // Terminate interrupts the downloader, canceling all pending operations. // The downloader cannot be reused after calling Terminate. func (d *Downloader) Terminate() { - // Signal to stop inserting in-flight blocks - d.blockchain.StopInsert() - // Close the termination channel (make sure double close is allowed) d.quitLock.Lock() select { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 8f736226c7..db0297ae5a 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1831,8 +1831,16 @@ func (api *DebugAPI) ChaindbCompact() error { } // SetHead rewinds the head of the blockchain to a previous block. -func (api *DebugAPI) SetHead(number hexutil.Uint64) { +func (api *DebugAPI) SetHead(number hexutil.Uint64) error { + header := api.b.CurrentHeader() + if header == nil { + return errors.New("current header is not available") + } + if header.Number.Uint64() <= uint64(number) { + return errors.New("not allowed to rewind to a future block") + } api.b.SetHead(uint64(number)) + return nil } // NetAPI offers network related RPC methods From b62c0c67fa83c8dce314b849d8235f16667feafd Mon Sep 17 00:00:00 2001 From: GarmashAlex Date: Mon, 23 Jun 2025 10:13:48 +0300 Subject: [PATCH 04/11] cmd/clef: update readme (#32070) Replaced the outdated and broken link to the Web3 Secret Storage Definition with the current official URL from ethereum.org in the Clef README. This ensures users have access to up-to-date and accurate documentation for the keystore file format. --- cmd/clef/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/clef/README.md b/cmd/clef/README.md index d23e70a3d4..a92dcb1d77 100644 --- a/cmd/clef/README.md +++ b/cmd/clef/README.md @@ -150,7 +150,7 @@ All hex encoded values must be prefixed with `0x`. #### Create new password protected account -The signer will generate a new private key, encrypt it according to [web3 keystore spec](https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition) and store it in the keystore directory. +The signer will generate a new private key, encrypt it according to [web3 keystore spec](https://ethereum.org/en/developers/docs/data-structures-and-encoding/web3-secret-storage/) and store it in the keystore directory. The client is responsible for creating a backup of the keystore. If the keystore is lost there is no method of retrieving lost accounts. #### Arguments From 0b21c4a63300ec883fb1a56c39adca1291794253 Mon Sep 17 00:00:00 2001 From: Delweng Date: Tue, 24 Jun 2025 13:52:18 +0800 Subject: [PATCH 05/11] eth/tracers: prestate lookup EIP7702 delegation account (#32080) Implement https://github.com/ethereum/go-ethereum/issues/32078 Parse and lookup the delegation account if EIP7702 is enabled. --------- Signed-off-by: jsvisa --- .../prestate_tracer/7702_delegate.json | 135 ++++++++++++++++++ .../testdata/prestate_tracer/setcode_tx.json | 4 + eth/tracers/native/prestate.go | 44 ++++-- 3 files changed, 169 insertions(+), 14 deletions(-) create mode 100644 eth/tracers/internal/tracetest/testdata/prestate_tracer/7702_delegate.json diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer/7702_delegate.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer/7702_delegate.json new file mode 100644 index 0000000000..14874dcc07 --- /dev/null +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer/7702_delegate.json @@ -0,0 +1,135 @@ +{ + "genesis": { + "baseFeePerGas": "0x389ef14a", + "blobGasUsed": "0x120000", + "difficulty": "0x0", + "excessBlobGas": "0x20000", + "extraData": "0x6265617665726275696c642e6f7267", + "gasLimit": "0x225da53", + "hash": "0x9c1d4eb19d30fa830e02493f5108ddfd49f2736983cecb6b3748b79e78f98d14", + "miner": "0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5", + "mixHash": "0x82bbbb55d5e4edf221aadaefe697f265210cc4afd8a0fa977769da5be8c100c0", + "nonce": "0x0000000000000000", + "number": "0x15b589d", + "parentBeaconBlockRoot": "0x64c714ee5b2d66ea6fd1f6633e41bf2863955c0b7a9e925a241f5e4e3c19f81e", + "requestsHash": "0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "stateRoot": "0x5c700c05128ae491d83b5602fd96f0faa487562441bef96ec9474c144820ed10", + "timestamp": "0x6858a55f", + "alloc": { + "0x17816e9a858b161c3e37016d139cf618056cacd4": { + "balance": "0x0", + "code": "0xef0100b684710e6d5914ad6e64493de2a3c424cc43e970", + "nonce": 15809 + }, + "0x236501327e701692a281934230af0b6be8df3353": { + "balance": "0x0", + "code": "0x6080604052600a600c565b005b60186014601a565b605e565b565b600060597f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5473ffffffffffffffffffffffffffffffffffffffff1690565b905090565b3660008037600080366000845af43d6000803e808015607c573d6000f35b3d6000fdfea26469706673582212200b737106e31d6abde738d261a4c4f12fcdfac5141ebc6ab5ffe4cf6e1630aaed64736f6c63430008140033", + "nonce": 1, + "storage": { + "0x078d9cc432fb3eab476f678ef9a73d8ca570f23897c68eb99b2721ebf46e5a9e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000bdb50eff425fb2b1b67fea21b8420eeb6d99ccc0", + "0x5555c0547520ec9521cc3134a71677625cdeb6accbb330321dcaf2cbc22c1fe9": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x84fdd52031be5dc8bcfa0ffd090a0bf85ef922e1fa9d026be0cf5716edafb4db": "0x0000000000000000000000000000000000000000007b74591c97f086c1057bee", + "0x8c854b3845c254f768d5435bc89fa04fb52bd2f72a1cf4370b962cf104ecd5fc": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xc45aef11733ee3a84cf02368a8b99ca24b1e3bfc2f5f532a1a2439aa077d2843": "0x000000000000000000000000000000000000000000000738cda8f7729a2a8a1e", + "0xda699a88dd51ba5e1d66c40fd985a4ad1511875941c3dd2936300679d596ab7b": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97": { + "balance": "0x8c2e6837fe7fb165", + "nonce": 1874580 + }, + "0xb684710e6d5914ad6e64493de2a3c424cc43e970": { + "balance": "0x0", + "code": "0x60806040525f4711156100b6575f3273ffffffffffffffffffffffffffffffffffffffff16476040516100319061048b565b5f6040518083038185875af1925050503d805f811461006b576040519150601f19603f3d011682016040523d82523d5f602084013e610070565b606091505b50509050806100b4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100ab906104f9565b60405180910390fd5b505b73ffffffffffffffffffffffffffffffffffffffff80166001336100da9190610563565b73ffffffffffffffffffffffffffffffffffffffff161115610131576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610128906105f4565b60405180910390fd5b73b9df4a9ba45917e71d664d51462d46926e4798e873ffffffffffffffffffffffffffffffffffffffff166001336101699190610563565b73ffffffffffffffffffffffffffffffffffffffff160361045c575f8036906101929190610631565b5f1c90505f73cda6461f1a30c618373f5790a83e1569fb685cba73ffffffffffffffffffffffffffffffffffffffff16631f3a71ba306040518263ffffffff1660e01b81526004016101e491906106af565b602060405180830381865afa1580156101ff573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061022391906106ff565b90508181106104595773cda6461f1a30c618373f5790a83e1569fb685cba73ffffffffffffffffffffffffffffffffffffffff1663a9059cbb5f836040518363ffffffff1660e01b815260040161027b929190610739565b6020604051808303815f875af1158015610297573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906102bb9190610795565b6102fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102f1906104f9565b60405180910390fd5b5f73236501327e701692a281934230af0b6be8df335373ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b815260040161034891906106af565b602060405180830381865afa158015610363573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061038791906106ff565b905073236501327e701692a281934230af0b6be8df335373ffffffffffffffffffffffffffffffffffffffff1663a9059cbb32836040518363ffffffff1660e01b81526004016103d8929190610739565b6020604051808303815f875af11580156103f4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104189190610795565b610457576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161044e9061080a565b60405180910390fd5b505b50505b005b5f81905092915050565b50565b5f6104765f8361045e565b915061048182610468565b5f82019050919050565b5f6104958261046b565b9150819050919050565b5f82825260208201905092915050565b7f5472616e73666572206661696c656400000000000000000000000000000000005f82015250565b5f6104e3600f8361049f565b91506104ee826104af565b602082019050919050565b5f6020820190508181035f830152610510816104d7565b9050919050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61056d82610517565b915061057883610517565b9250828201905073ffffffffffffffffffffffffffffffffffffffff8111156105a4576105a3610536565b5b92915050565b7f50616e69632831372900000000000000000000000000000000000000000000005f82015250565b5f6105de60098361049f565b91506105e9826105aa565b602082019050919050565b5f6020820190508181035f83015261060b816105d2565b9050919050565b5f82905092915050565b5f819050919050565b5f82821b905092915050565b5f61063c8383610612565b82610647813561061c565b92506020821015610687576106827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83602003600802610625565b831692505b505092915050565b5f61069982610517565b9050919050565b6106a98161068f565b82525050565b5f6020820190506106c25f8301846106a0565b92915050565b5f80fd5b5f819050919050565b6106de816106cc565b81146106e8575f80fd5b50565b5f815190506106f9816106d5565b92915050565b5f60208284031215610714576107136106c8565b5b5f610721848285016106eb565b91505092915050565b610733816106cc565b82525050565b5f60408201905061074c5f8301856106a0565b610759602083018461072a565b9392505050565b5f8115159050919050565b61077481610760565b811461077e575f80fd5b50565b5f8151905061078f8161076b565b92915050565b5f602082840312156107aa576107a96106c8565b5b5f6107b784828501610781565b91505092915050565b7f546f6b656e207472616e73666572206661696c656400000000000000000000005f82015250565b5f6107f460158361049f565b91506107ff826107c0565b602082019050919050565b5f6020820190508181035f830152610821816107e8565b905091905056fea2646970667358221220b6a06cc7b930dc4e34352a145f3548d57ec5a60d0097c1979ef363376bf9a69164736f6c63430008140033", + "nonce": 1 + }, + "0xb9df4a9ba45917e71d664d51462d46926e4798e7": { + "balance": "0x597af049b190a724", + "code": "0xef0100000000009b1d0af20d8c6d0a44e162d11f9b8f00", + "nonce": 1887 + }, + "0xbdb50eff425fb2b1b67fea21b8420eeb6d99ccc0": { + "balance": "0x0", + "code": "0x6080604052600436106101cd5760003560e01c80637ecebe00116100f7578063a9059cbb11610095578063d505accf11610064578063d505accf146105b2578063dd62ed3e146105d2578063f1127ed814610637578063f2fde38b1461068357600080fd5b8063a9059cbb14610509578063ad3cb1cc14610529578063b119490e14610572578063c3cda5201461059257600080fd5b80638e539e8c116100d15780638e539e8c1461048857806391ddadf4146104a857806395d89b41146104d45780639ab24eb0146104e957600080fd5b80637ecebe001461040357806384b0196e146104235780638da5cb5b1461044b57600080fd5b80634bf5d7e91161016f5780635c19a95c1161013e5780635c19a95c146103795780636fcfff451461039957806370a08231146103ce578063715018a6146103ee57600080fd5b80634bf5d7e9146102dc5780634f1ef286146102f157806352d1902d14610306578063587cde1e1461031b57600080fd5b806323b872dd116101ab57806323b872dd1461026b578063313ce5671461028b5780633644e515146102a75780633a46b1a8146102bc57600080fd5b806306fdde03146101d2578063095ea7b3146101fd57806318160ddd1461022d575b600080fd5b3480156101de57600080fd5b506101e76106a3565b6040516101f49190612a81565b60405180910390f35b34801561020957600080fd5b5061021d610218366004612ab0565b61075e565b60405190151581526020016101f4565b34801561023957600080fd5b507f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace02545b6040519081526020016101f4565b34801561027757600080fd5b5061021d610286366004612ada565b610778565b34801561029757600080fd5b50604051601281526020016101f4565b3480156102b357600080fd5b5061025d61079e565b3480156102c857600080fd5b5061025d6102d7366004612ab0565b6107ad565b3480156102e857600080fd5b506101e7610845565b6103046102ff366004612ba2565b6108d6565b005b34801561031257600080fd5b5061025d6108f5565b34801561032757600080fd5b50610361610336366004612c04565b6001600160a01b03908116600090815260008051602061312283398151915260205260409020541690565b6040516001600160a01b0390911681526020016101f4565b34801561038557600080fd5b50610304610394366004612c04565b610924565b3480156103a557600080fd5b506103b96103b4366004612c04565b61092f565b60405163ffffffff90911681526020016101f4565b3480156103da57600080fd5b5061025d6103e9366004612c04565b61093a565b3480156103fa57600080fd5b5061030461097f565b34801561040f57600080fd5b5061025d61041e366004612c04565b610993565b34801561042f57600080fd5b5061043861099e565b6040516101f49796959493929190612c1f565b34801561045757600080fd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b0316610361565b34801561049457600080fd5b5061025d6104a3366004612cd1565b610a9a565b3480156104b457600080fd5b506104bd610b16565b60405165ffffffffffff90911681526020016101f4565b3480156104e057600080fd5b506101e7610b20565b3480156104f557600080fd5b5061025d610504366004612c04565b610b71565b34801561051557600080fd5b5061021d610524366004612ab0565b610bd1565b34801561053557600080fd5b506101e76040518060400160405280600581526020017f352e302e3000000000000000000000000000000000000000000000000000000081525081565b34801561057e57600080fd5b5061030461058d366004612d0a565b610bdf565b34801561059e57600080fd5b506103046105ad366004612d88565b610d4b565b3480156105be57600080fd5b506103046105cd366004612de0565b610e21565b3480156105de57600080fd5b5061025d6105ed366004612e4a565b6001600160a01b0391821660009081527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace016020908152604080832093909416825291909152205490565b34801561064357600080fd5b50610657610652366004612e7d565b610fac565b60408051825165ffffffffffff1681526020928301516001600160d01b031692810192909252016101f4565b34801561068f57600080fd5b5061030461069e366004612c04565b610fca565b606060007f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace005b90508060030180546106da90612ebd565b80601f016020809104026020016040519081016040528092919081815260200182805461070690612ebd565b80156107535780601f1061072857610100808354040283529160200191610753565b820191906000526020600020905b81548152906001019060200180831161073657829003601f168201915b505050505091505090565b60003361076c818585611021565b60019150505b92915050565b600033610786858285611033565b6107918585856110e9565b60019150505b9392505050565b60006107a8611161565b905090565b6000600080516020613122833981519152816107c7610b16565b90508065ffffffffffff16841061080757604051637669fc0f60e11b81526004810185905265ffffffffffff821660248201526044015b60405180910390fd5b6108336108138561116b565b6001600160a01b03871660009081526001850160205260409020906111a2565b6001600160d01b031695945050505050565b606061084f61125b565b65ffffffffffff1661085f610b16565b65ffffffffffff161461089e576040517f6ff0714000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5060408051808201909152601d81527f6d6f64653d626c6f636b6e756d6265722666726f6d3d64656661756c74000000602082015290565b6108de611266565b6108e78261131d565b6108f18282611325565b5050565b60006108ff61140d565b507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc90565b336108f18183611456565b600061077282611513565b6000807f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace005b6001600160a01b0390931660009081526020939093525050604090205490565b610987611564565b61099160006115d8565b565b600061077282611656565b600060608082808083817fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10080549091501580156109dd57506001810154155b610a43576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f4549503731323a20556e696e697469616c697a6564000000000000000000000060448201526064016107fe565b610a4b611661565b610a536116b2565b604080516000808252602082019092527f0f000000000000000000000000000000000000000000000000000000000000009c939b5091995046985030975095509350915050565b600060008051602061312283398151915281610ab4610b16565b90508065ffffffffffff168410610aef57604051637669fc0f60e11b81526004810185905265ffffffffffff821660248201526044016107fe565b610b05610afb8561116b565b60028401906111a2565b6001600160d01b0316949350505050565b60006107a861125b565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace0480546060917f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00916106da90612ebd565b6001600160a01b03811660009081527fe8b26c30fad74198956032a3533d903385d56dd795af560196f9c78d4af40d016020526040812060008051602061312283398151915290610bc1906116dc565b6001600160d01b03169392505050565b60003361076c8185856110e9565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000810460ff16159067ffffffffffffffff16600081158015610c2a5750825b905060008267ffffffffffffffff166001148015610c475750303b155b905081158015610c55575080155b15610c8c576040517ff92ee8a900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b845467ffffffffffffffff191660011785558315610cc057845468ff00000000000000001916680100000000000000001785555b610cca8888611718565b610cd38861172a565b610cdb611771565b610ce433611779565b610cec611771565b610cf6338761178a565b8315610d4157845468ff000000000000000019168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b5050505050505050565b83421115610d88576040517f4683af0e000000000000000000000000000000000000000000000000000000008152600481018590526024016107fe565b604080517fe48329057bfd03d55e49b547132e39cffd9c1820ad7b9d4c5307691425d15adf60208201526001600160a01b038816918101919091526060810186905260808101859052600090610e0290610dfa9060a001604051602081830303815290604052805190602001206117c0565b858585611808565b9050610e0e8187611836565b610e188188611456565b50505050505050565b83421115610e5e576040517f62791302000000000000000000000000000000000000000000000000000000008152600481018590526024016107fe565b60007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9888888610eca8c6001600160a01b031660009081527f5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb006020526040902080546001810190915590565b6040805160208101969096526001600160a01b0394851690860152929091166060840152608083015260a082015260c0810186905260e0016040516020818303038152906040528051906020012090506000610f25826117c0565b90506000610f3582878787611808565b9050896001600160a01b0316816001600160a01b031614610f95576040517f4b800e460000000000000000000000000000000000000000000000000000000081526001600160a01b0380831660048301528b1660248201526044016107fe565b610fa08a8a8a611021565b50505050505050505050565b604080518082019091526000808252602082015261079783836118c1565b610fd2611564565b6001600160a01b038116611015576040517f1e4fbdf7000000000000000000000000000000000000000000000000000000008152600060048201526024016107fe565b61101e816115d8565b50565b61102e838383600161192c565b505050565b6001600160a01b0383811660009081527f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace01602090815260408083209386168352929052205460001981146110e357818110156110d4576040517ffb8f41b20000000000000000000000000000000000000000000000000000000081526001600160a01b038416600482015260248101829052604481018390526064016107fe565b6110e38484848403600061192c565b50505050565b6001600160a01b03831661112c576040517f96c6fd1e000000000000000000000000000000000000000000000000000000008152600060048201526024016107fe565b6001600160a01b0382166111565760405163ec442f0560e01b8152600060048201526024016107fe565b61102e838383611a58565b60006107a8611a63565b600065ffffffffffff82111561119e576040516306dfcc6560e41b815260306004820152602481018390526044016107fe565b5090565b8154600090818160058111156112015760006111bd84611ad7565b6111c79085612f0d565b60008881526020902090915081015465ffffffffffff90811690871610156111f1578091506111ff565b6111fc816001612f20565b92505b505b600061120f87878585611bbf565b9050801561124d5761123487611226600184612f0d565b600091825260209091200190565b54660100000000000090046001600160d01b0316611250565b60005b979650505050505050565b60006107a84361116b565b306001600160a01b037f000000000000000000000000bdb50eff425fb2b1b67fea21b8420eeb6d99ccc01614806112ff57507f000000000000000000000000bdb50eff425fb2b1b67fea21b8420eeb6d99ccc06001600160a01b03166112f37f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b6001600160a01b031614155b156109915760405163703e46dd60e11b815260040160405180910390fd5b61101e611564565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa92505050801561137f575060408051601f3d908101601f1916820190925261137c91810190612f33565b60015b6113a757604051634c9c8ce360e01b81526001600160a01b03831660048201526024016107fe565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc8114611403576040517faa1d49a4000000000000000000000000000000000000000000000000000000008152600481018290526024016107fe565b61102e8383611c21565b306001600160a01b037f000000000000000000000000bdb50eff425fb2b1b67fea21b8420eeb6d99ccc016146109915760405163703e46dd60e11b815260040160405180910390fd5b6000805160206131228339815191526000611496846001600160a01b03908116600090815260008051602061312283398151915260205260409020541690565b6001600160a01b03858116600081815260208690526040808220805473ffffffffffffffffffffffffffffffffffffffff1916898616908117909155905194955093928516927f3134e8a2e6d97e929a7e54011ea5485d7d196dd5f0ba4d4ef95803e8e3fc257f9190a46110e3818461150e87611c77565b611c82565b6001600160a01b03811660009081527fe8b26c30fad74198956032a3533d903385d56dd795af560196f9c78d4af40d0160205260408120546000805160206131228339815191529061079790611dfc565b336115967f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b031614610991576040517f118cdaa70000000000000000000000000000000000000000000000000000000081523360048201526024016107fe565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300805473ffffffffffffffffffffffffffffffffffffffff1981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a3505050565b600061077282611e2d565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d10280546060917fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100916106da90612ebd565b606060007fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1006106c9565b8054600090801561170f576116f683611226600184612f0d565b54660100000000000090046001600160d01b0316610797565b60009392505050565b611720611e56565b6108f18282611ebd565b611732611e56565b61101e816040518060400160405280600181526020017f3100000000000000000000000000000000000000000000000000000000000000815250611f20565b610991611e56565b611781611e56565b61101e81611f93565b6001600160a01b0382166117b45760405163ec442f0560e01b8152600060048201526024016107fe565b6108f160008383611a58565b60006107726117cd611161565b836040517f19010000000000000000000000000000000000000000000000000000000000008152600281019290925260228201526042902090565b60008060008061181a88888888611f9b565b92509250925061182a828261206a565b50909695505050505050565b6001600160a01b03821660009081527f5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb006020526040902080546001810190915581811461102e576040517f752d88c00000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602481018290526044016107fe565b604080518082018252600080825260208083018290526001600160a01b03861682527fe8b26c30fad74198956032a3533d903385d56dd795af560196f9c78d4af40d0190529190912060008051602061312283398151915290611924908461216e565b949350505050565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace006001600160a01b038516611990576040517fe602df05000000000000000000000000000000000000000000000000000000008152600060048201526024016107fe565b6001600160a01b0384166119d3576040517f94280d62000000000000000000000000000000000000000000000000000000008152600060048201526024016107fe565b6001600160a01b03808616600090815260018301602090815260408083209388168352929052208390558115611a5157836001600160a01b0316856001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92585604051611a4891815260200190565b60405180910390a35b5050505050565b61102e8383836121e1565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f611a8e612280565b611a966122fc565b60408051602081019490945283019190915260608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b600081600003611ae957506000919050565b60006001611af684612352565b901c6001901b90506001818481611b0f57611b0f612f4c565b048201901c90506001818481611b2757611b27612f4c565b048201901c90506001818481611b3f57611b3f612f4c565b048201901c90506001818481611b5757611b57612f4c565b048201901c90506001818481611b6f57611b6f612f4c565b048201901c90506001818481611b8757611b87612f4c565b048201901c90506001818481611b9f57611b9f612f4c565b048201901c905061079781828581611bb957611bb9612f4c565b046123e6565b60005b81831015611c19576000611bd684846123fc565b60008781526020902090915065ffffffffffff86169082015465ffffffffffff161115611c0557809250611c13565b611c10816001612f20565b93505b50611bc2565b509392505050565b611c2a82612417565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a2805115611c6f5761102e828261249b565b6108f1612511565b60006107728261093a565b6000805160206131228339815191526001600160a01b0384811690841614801590611cad5750600082115b156110e3576001600160a01b03841615611d57576001600160a01b038416600090815260018201602052604081208190611cf290612549611ced87612555565b612589565b6001600160d01b031691506001600160d01b03169150856001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7248383604051611d4c929190918252602082015260400190565b60405180910390a250505b6001600160a01b038316156110e3576001600160a01b038316600090815260018201602052604081208190611d92906125c2611ced87612555565b6001600160d01b031691506001600160d01b03169150846001600160a01b03167fdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a7248383604051611dec929190918252602082015260400190565b60405180910390a2505050505050565b600063ffffffff82111561119e576040516306dfcc6560e41b815260206004820152602481018390526044016107fe565b6000807f5ab42ced628888259c08ac98db1eb0cf702fc1501344311d8b100cd1bfe4bb0061095f565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a005468010000000000000000900460ff16610991576040517fd7e6bcf800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611ec5611e56565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace007f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace03611f118482612fb0565b50600481016110e38382612fb0565b611f28611e56565b7fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d1007fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d102611f748482612fb0565b5060038101611f838382612fb0565b5060008082556001909101555050565b610fd2611e56565b600080807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115611fd65750600091506003905082612060565b604080516000808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa15801561202a573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811661205657506000925060019150829050612060565b9250600091508190505b9450945094915050565b600082600381111561207e5761207e613070565b03612087575050565b600182600381111561209b5761209b613070565b036120d2576040517ff645eedf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028260038111156120e6576120e6613070565b03612120576040517ffce698f7000000000000000000000000000000000000000000000000000000008152600481018290526024016107fe565b600382600381111561213457612134613070565b036108f1576040517fd78bce0c000000000000000000000000000000000000000000000000000000008152600481018290526024016107fe565b6040805180820190915260008082526020820152826000018263ffffffff168154811061219d5761219d613086565b60009182526020918290206040805180820190915291015465ffffffffffff81168252660100000000000090046001600160d01b0316918101919091529392505050565b6121ec8383836125ce565b6001600160a01b0383166122755760006122247f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace025490565b90506001600160d01b0380821115612272576040517f1cb15d2600000000000000000000000000000000000000000000000000000000815260048101839052602481018290526044016107fe565b50505b61102e838383612737565b60007fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100816122ac611661565b8051909150156122c457805160209091012092915050565b815480156122d3579392505050565b7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470935050505090565b60007fa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100816123286116b2565b80519091501561234057805160209091012092915050565b600182015480156122d3579392505050565b600080608083901c1561236757608092831c92015b604083901c1561237957604092831c92015b602083901c1561238b57602092831c92015b601083901c1561239d57601092831c92015b600883901c156123af57600892831c92015b600483901c156123c157600492831c92015b600283901c156123d357600292831c92015b600183901c156107725760010192915050565b60008183106123f55781610797565b5090919050565b600061240b600284841861309c565b61079790848416612f20565b806001600160a01b03163b60000361244d57604051634c9c8ce360e01b81526001600160a01b03821660048201526024016107fe565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0392909216919091179055565b6060600080846001600160a01b0316846040516124b891906130be565b600060405180830381855af49150503d80600081146124f3576040519150601f19603f3d011682016040523d82523d6000602084013e6124f8565b606091505b50915091506125088583836127cd565b95945050505050565b3415610991576040517fb398979f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061079782846130da565b60006001600160d01b0382111561119e576040516306dfcc6560e41b815260d06004820152602481018390526044016107fe565b6000806125b5612597610b16565b6125ad6125a3886116dc565b868863ffffffff16565b879190612842565b915091505b935093915050565b60006107978284613101565b7f52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace006001600160a01b03841661261c57818160020160008282546126119190612f20565b909155506126a79050565b6001600160a01b03841660009081526020829052604090205482811015612688576040517fe450d38c0000000000000000000000000000000000000000000000000000000081526001600160a01b038616600482015260248101829052604481018490526064016107fe565b6001600160a01b03851660009081526020839052604090209083900390555b6001600160a01b0383166126c55760028101805483900390556126e4565b6001600160a01b03831660009081526020829052604090208054830190555b826001600160a01b0316846001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161272991815260200190565b60405180910390a350505050565b6000805160206131228339815191526001600160a01b03841661276a57612767816002016125c2611ced85612555565b50505b6001600160a01b03831661278e5761278b81600201612549611ced85612555565b50505b6001600160a01b03848116600090815260008051602061312283398151915260205260408082205486841683529120546110e392918216911684611c82565b6060826127e2576127dd82612850565b610797565b81511580156127f957506001600160a01b0384163b155b1561283b576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016107fe565b5080610797565b6000806125b5858585612892565b8051156128605780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8254600090819080156129d35760006128b087611226600185612f0d565b60408051808201909152905465ffffffffffff80821680845266010000000000009092046001600160d01b031660208401529192509087161015612920576040517f2520601d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805165ffffffffffff80881691160361296f578461294388611226600186612f0d565b80546001600160d01b039290921666010000000000000265ffffffffffff9092169190911790556129c3565b6040805180820190915265ffffffffffff80881682526001600160d01b0380881660208085019182528b54600181018d5560008d815291909120945191519092166601000000000000029216919091179101555b6020015192508391506125ba9050565b50506040805180820190915265ffffffffffff80851682526001600160d01b0380851660208085019182528854600181018a5560008a81529182209551925190931666010000000000000291909316179201919091559050816125ba565b60005b83811015612a4c578181015183820152602001612a34565b50506000910152565b60008151808452612a6d816020860160208601612a31565b601f01601f19169290920160200192915050565b6020815260006107976020830184612a55565b80356001600160a01b0381168114612aab57600080fd5b919050565b60008060408385031215612ac357600080fd5b612acc83612a94565b946020939093013593505050565b600080600060608486031215612aef57600080fd5b612af884612a94565b9250612b0660208501612a94565b9150604084013590509250925092565b634e487b7160e01b600052604160045260246000fd5b600067ffffffffffffffff80841115612b4757612b47612b16565b604051601f8501601f19908116603f01168101908282118183101715612b6f57612b6f612b16565b81604052809350858152868686011115612b8857600080fd5b858560208301376000602087830101525050509392505050565b60008060408385031215612bb557600080fd5b612bbe83612a94565b9150602083013567ffffffffffffffff811115612bda57600080fd5b8301601f81018513612beb57600080fd5b612bfa85823560208401612b2c565b9150509250929050565b600060208284031215612c1657600080fd5b61079782612a94565b7fff00000000000000000000000000000000000000000000000000000000000000881681526000602060e081840152612c5b60e084018a612a55565b8381036040850152612c6d818a612a55565b606085018990526001600160a01b038816608086015260a0850187905284810360c0860152855180825283870192509083019060005b81811015612cbf57835183529284019291840191600101612ca3565b50909c9b505050505050505050505050565b600060208284031215612ce357600080fd5b5035919050565b600082601f830112612cfb57600080fd5b61079783833560208501612b2c565b600080600060608486031215612d1f57600080fd5b833567ffffffffffffffff80821115612d3757600080fd5b612d4387838801612cea565b94506020860135915080821115612d5957600080fd5b50612d6686828701612cea565b925050604084013590509250925092565b803560ff81168114612aab57600080fd5b60008060008060008060c08789031215612da157600080fd5b612daa87612a94565b95506020870135945060408701359350612dc660608801612d77565b92506080870135915060a087013590509295509295509295565b600080600080600080600060e0888a031215612dfb57600080fd5b612e0488612a94565b9650612e1260208901612a94565b95506040880135945060608801359350612e2e60808901612d77565b925060a0880135915060c0880135905092959891949750929550565b60008060408385031215612e5d57600080fd5b612e6683612a94565b9150612e7460208401612a94565b90509250929050565b60008060408385031215612e9057600080fd5b612e9983612a94565b9150602083013563ffffffff81168114612eb257600080fd5b809150509250929050565b600181811c90821680612ed157607f821691505b602082108103612ef157634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561077257610772612ef7565b8082018082111561077257610772612ef7565b600060208284031215612f4557600080fd5b5051919050565b634e487b7160e01b600052601260045260246000fd5b601f82111561102e57600081815260208120601f850160051c81016020861015612f895750805b601f850160051c820191505b81811015612fa857828155600101612f95565b505050505050565b815167ffffffffffffffff811115612fca57612fca612b16565b612fde81612fd88454612ebd565b84612f62565b602080601f8311600181146130135760008415612ffb5750858301515b600019600386901b1c1916600185901b178555612fa8565b600085815260208120601f198616915b8281101561304257888601518255948401946001909101908401613023565b50858210156130605787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b6000826130b957634e487b7160e01b600052601260045260246000fd5b500490565b600082516130d0818460208701612a31565b9190910192915050565b6001600160d01b038281168282160390808211156130fa576130fa612ef7565b5092915050565b6001600160d01b038181168382160190808211156130fa576130fa612ef756fee8b26c30fad74198956032a3533d903385d56dd795af560196f9c78d4af40d00a2646970667358221220c2a4c7c504a36ab9781f5fb312d81d27f781047ab9f97621c7f031a185ecb78864736f6c63430008140033", + "nonce": 1 + }, + "0xcda6461f1a30c618373f5790a83e1569fb685cba": { + "balance": "0x0", + "code": "0x608060405234801561001057600080fd5b50600436106100ea5760003560e01c8063313ce5671161008c578063a9059cbb11610066578063a9059cbb146102ab578063dd62ed3e146102be578063e6fd48bc146102d4578063fc0c546a146102fb57600080fd5b8063313ce567146101f857806370a082311461023157806395d89b411461025157600080fd5b80631514617e116100c85780631514617e146101a857806318160ddd146101cf5780631f3a71ba146101d757806323b872dd146101ea57600080fd5b80630483a7f6146100ef57806306fdde0314610122578063095ea7b314610185575b600080fd5b61010f6100fd366004610926565b60006020819052908152604090205481565b6040519081526020015b60405180910390f35b604080517f466c75656e636520546f6b656e20284c6f636b65642900000000000000000000602082015281519082019091527f000000000000000000000000000000000000000000000000000000000000001681525b6040516101199190610965565b610198610193366004610998565b61033a565b6040519015158152602001610119565b61010f7f0000000000000000000000000000000000000000000000000000000001e1338081565b60025461010f565b61010f6101e5366004610926565b61038a565b6101986101933660046109c2565b61021f7f000000000000000000000000000000000000000000000000000000000000001281565b60405160ff9091168152602001610119565b61010f61023f366004610926565b60016020526000908152604090205481565b604080517f464c542d4c000000000000000000000000000000000000000000000000000000602082015281519082019091527f00000000000000000000000000000000000000000000000000000000000000058152610178565b6101986102b9366004610998565b610485565b61010f6102cc3660046109fe565b600092915050565b61010f7f0000000000000000000000000000000000000000000000000000000067afabe881565b6103227f000000000000000000000000236501327e701692a281934230af0b6be8df335381565b6040516001600160a01b039091168152602001610119565b60405162461bcd60e51b815260206004820152601560248201527f556e737570706f72746564206f7065726174696f6e000000000000000000000060448201526000906064015b60405180910390fd5b60007f0000000000000000000000000000000000000000000000000000000067afabe842116103bb57506000919050565b6001600160a01b0382166000908152602081815260408083205460019092528220547f0000000000000000000000000000000000000000000000000000000001e13380929061040a9083610a47565b905060006104387f0000000000000000000000000000000000000000000000000000000067afabe842610a47565b905060008482106104545761044d8385610a47565b905061047b565b60006104608686610a5a565b90508361046d8285610a7c565b6104779190610a47565b9150505b9695505050505050565b60006001600160a01b038316156105045760405162461bcd60e51b815260206004820152602960248201527f5472616e7366657220616c6c6f776564206f6e6c7920746f20746865207a657260448201527f6f206164647265737300000000000000000000000000000000000000000000006064820152608401610381565b3361050f8184610568565b836001600160a01b0316816001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8560405161055491815260200190565b60405180910390a360019150505b92915050565b60006105738361038a565b9050600081116105c55760405162461bcd60e51b815260206004820152601d60248201527f4e6f7420656e6f756768207468652072656c6561736520616d6f756e740000006044820152606401610381565b8115610620578082111561061b5760405162461bcd60e51b815260206004820152601d60248201527f4e6f7420656e6f756768207468652072656c6561736520616d6f756e740000006044820152606401610381565b610624565b8091505b6001600160a01b0383166000908152600160205260408120805484929061064c908490610a47565b9250508190555081600260008282546106659190610a47565b9091555061069f90506001600160a01b037f000000000000000000000000236501327e701692a281934230af0b6be8df33531684846106a4565b505050565b604080516001600160a01b03848116602483015260448083018590528351808403909101815260649092019092526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb0000000000000000000000000000000000000000000000000000000017905261069f9185919060009061073090841683610797565b905080516000141580156107555750808060200190518101906107539190610a93565b155b1561069f576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b0384166004820152602401610381565b60606107a5838360006107ac565b9392505050565b6060814710156107ea576040517fcd786059000000000000000000000000000000000000000000000000000000008152306004820152602401610381565b600080856001600160a01b031684866040516108069190610ab5565b60006040518083038185875af1925050503d8060008114610843576040519150601f19603f3d011682016040523d82523d6000602084013e610848565b606091505b509150915061047b86838360608261086857610863826108c8565b6107a5565b815115801561087f57506001600160a01b0384163b155b156108c1576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b0385166004820152602401610381565b50806107a5565b8051156108d85780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80356001600160a01b038116811461092157600080fd5b919050565b60006020828403121561093857600080fd5b6107a58261090a565b60005b8381101561095c578181015183820152602001610944565b50506000910152565b6020815260008251806020840152610984816040850160208701610941565b601f01601f19169190910160400192915050565b600080604083850312156109ab57600080fd5b6109b48361090a565b946020939093013593505050565b6000806000606084860312156109d757600080fd5b6109e08461090a565b92506109ee6020850161090a565b9150604084013590509250925092565b60008060408385031215610a1157600080fd5b610a1a8361090a565b9150610a286020840161090a565b90509250929050565b634e487b7160e01b600052601160045260246000fd5b8181038181111561056257610562610a31565b600082610a7757634e487b7160e01b600052601260045260246000fd5b500490565b808202811582820484141761056257610562610a31565b600060208284031215610aa557600080fd5b815180151581146107a557600080fd5b60008251610ac7818460208701610941565b919091019291505056fea2646970667358221220aa9a251bde32306273cb5f6045040ac4b74b767bd02205c60c6003c5346ac34c64736f6c63430008140033", + "nonce": 1, + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000007b74591c97f086c1057bee", + "0x4f2aab765280a617b8913308bffbaed810827576241edbcd290b48d2b699bf92": "0x0000000000000000000000000000000000000000000580926bcba6406ba40000", + "0xd057d56b4d1539d5c08615edc01a9792908fefc021b63dbdc5db20bf522e882e": "0x00000000000000000000000000000000000000000003920c271ee5a29be97bee" + } + } + }, + "config": { + "chainId": 1, + "homesteadBlock": 1150000, + "daoForkBlock": 1920000, + "daoForkSupport": true, + "eip150Block": 2463000, + "eip155Block": 2675000, + "eip158Block": 2675000, + "byzantiumBlock": 4370000, + "constantinopleBlock": 7280000, + "petersburgBlock": 7280000, + "istanbulBlock": 9069000, + "muirGlacierBlock": 9200000, + "berlinBlock": 12244000, + "londonBlock": 12965000, + "arrowGlacierBlock": 13773000, + "grayGlacierBlock": 15050000, + "shanghaiTime": 1681338455, + "cancunTime": 1710338135, + "pragueTime": 1746612311, + "terminalTotalDifficulty": 58750000000000000000000, + "depositContractAddress": "0x00000000219ab540356cbb839cbe05303d7705fa", + "ethash": {}, + "blobSchedule": { + "cancun": { + "target": 3, + "max": 6, + "baseFeeUpdateFraction": 3338477 + }, + "prague": { + "target": 6, + "max": 9, + "baseFeeUpdateFraction": 5007716 + } + } + } + }, + "context": { + "number": "0x15b589e", + "timestamp": "0x6858a56b", + "difficulty": "0x0", + "gasLimit": "0x22550de", + "miner": "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97", + "baseFeePerGas": "0x38e42046" + }, + "input": "0x04f8ec0182075f830f424084714d24d7830493e09417816e9a858b161c3e37016d139cf618056cacd480a000000000000000000000000000000000000000000000000316580c3ab7e66cc4c0f85ef85c0194b684710e6d5914ad6e64493de2a3c424cc43e970823dc101a02f15ba55009fcd3682cd0f9c9645dd94e616f9a969ba3f1a5a2d871f9fe0f2b4a053c332a83312d0b17dd4c16eeb15b1ff5223398b14e0a55c70762e8f3972b7a580a02aceec9737d2a211c79aff3dbd4bf44a5cdabbdd6bbe19ff346a89d94d61914aa062e92842bfe7d2f3ff785c594c70fafafcb180fb32a774de1b92c588be8cd87b", + "result": { + "0x17816e9a858b161c3e37016d139cf618056cacd4": { + "balance": "0x0", + "code": "0xef0100b684710e6d5914ad6e64493de2a3c424cc43e970", + "nonce": 15809 + }, + "0x4838b106fce9647bdf1e7877bf73ce8b0bad5f97": { + "balance": "0x8c2e6837fe7fb165", + "nonce": 1874580 + }, + "0xb684710e6d5914ad6e64493de2a3c424cc43e970": { + "balance": "0x0", + "code": "0x60806040525f4711156100b6575f3273ffffffffffffffffffffffffffffffffffffffff16476040516100319061048b565b5f6040518083038185875af1925050503d805f811461006b576040519150601f19603f3d011682016040523d82523d5f602084013e610070565b606091505b50509050806100b4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100ab906104f9565b60405180910390fd5b505b73ffffffffffffffffffffffffffffffffffffffff80166001336100da9190610563565b73ffffffffffffffffffffffffffffffffffffffff161115610131576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610128906105f4565b60405180910390fd5b73b9df4a9ba45917e71d664d51462d46926e4798e873ffffffffffffffffffffffffffffffffffffffff166001336101699190610563565b73ffffffffffffffffffffffffffffffffffffffff160361045c575f8036906101929190610631565b5f1c90505f73cda6461f1a30c618373f5790a83e1569fb685cba73ffffffffffffffffffffffffffffffffffffffff16631f3a71ba306040518263ffffffff1660e01b81526004016101e491906106af565b602060405180830381865afa1580156101ff573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061022391906106ff565b90508181106104595773cda6461f1a30c618373f5790a83e1569fb685cba73ffffffffffffffffffffffffffffffffffffffff1663a9059cbb5f836040518363ffffffff1660e01b815260040161027b929190610739565b6020604051808303815f875af1158015610297573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906102bb9190610795565b6102fa576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102f1906104f9565b60405180910390fd5b5f73236501327e701692a281934230af0b6be8df335373ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b815260040161034891906106af565b602060405180830381865afa158015610363573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061038791906106ff565b905073236501327e701692a281934230af0b6be8df335373ffffffffffffffffffffffffffffffffffffffff1663a9059cbb32836040518363ffffffff1660e01b81526004016103d8929190610739565b6020604051808303815f875af11580156103f4573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906104189190610795565b610457576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161044e9061080a565b60405180910390fd5b505b50505b005b5f81905092915050565b50565b5f6104765f8361045e565b915061048182610468565b5f82019050919050565b5f6104958261046b565b9150819050919050565b5f82825260208201905092915050565b7f5472616e73666572206661696c656400000000000000000000000000000000005f82015250565b5f6104e3600f8361049f565b91506104ee826104af565b602082019050919050565b5f6020820190508181035f830152610510816104d7565b9050919050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61056d82610517565b915061057883610517565b9250828201905073ffffffffffffffffffffffffffffffffffffffff8111156105a4576105a3610536565b5b92915050565b7f50616e69632831372900000000000000000000000000000000000000000000005f82015250565b5f6105de60098361049f565b91506105e9826105aa565b602082019050919050565b5f6020820190508181035f83015261060b816105d2565b9050919050565b5f82905092915050565b5f819050919050565b5f82821b905092915050565b5f61063c8383610612565b82610647813561061c565b92506020821015610687576106827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff83602003600802610625565b831692505b505092915050565b5f61069982610517565b9050919050565b6106a98161068f565b82525050565b5f6020820190506106c25f8301846106a0565b92915050565b5f80fd5b5f819050919050565b6106de816106cc565b81146106e8575f80fd5b50565b5f815190506106f9816106d5565b92915050565b5f60208284031215610714576107136106c8565b5b5f610721848285016106eb565b91505092915050565b610733816106cc565b82525050565b5f60408201905061074c5f8301856106a0565b610759602083018461072a565b9392505050565b5f8115159050919050565b61077481610760565b811461077e575f80fd5b50565b5f8151905061078f8161076b565b92915050565b5f602082840312156107aa576107a96106c8565b5b5f6107b784828501610781565b91505092915050565b7f546f6b656e207472616e73666572206661696c656400000000000000000000005f82015250565b5f6107f460158361049f565b91506107ff826107c0565b602082019050919050565b5f6020820190508181035f830152610821816107e8565b905091905056fea2646970667358221220b6a06cc7b930dc4e34352a145f3548d57ec5a60d0097c1979ef363376bf9a69164736f6c63430008140033", + "nonce": 1 + }, + "0xb9df4a9ba45917e71d664d51462d46926e4798e7": { + "balance": "0x597af049b190a724", + "code": "0xef0100000000009b1d0af20d8c6d0a44e162d11f9b8f00", + "nonce": 1887 + } + } +} diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer/setcode_tx.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer/setcode_tx.json index 043130a072..121509f132 100644 --- a/eth/tracers/internal/tracetest/testdata/prestate_tracer/setcode_tx.json +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer/setcode_tx.json @@ -81,6 +81,10 @@ "0x0000000000000000000000000000000000000000": { "balance": "0x272e0528" }, + "0x000000000000000000000000000000000000bbbb": { + "balance": "0x0", + "code": "0x6042604255" + }, "0x703c4b2bd70c169f5717101caee543299fc946c7": { "balance": "0xde0b6b3a7640000", "storage": { diff --git a/eth/tracers/native/prestate.go b/eth/tracers/native/prestate.go index a2d598adaa..57c66ae327 100644 --- a/eth/tracers/native/prestate.go +++ b/eth/tracers/native/prestate.go @@ -61,15 +61,16 @@ type accountMarshaling struct { } type prestateTracer struct { - env *tracing.VMContext - pre stateMap - post stateMap - to common.Address - config prestateTracerConfig - interrupt atomic.Bool // Atomic flag to signal execution interruption - reason error // Textual reason for the interruption - created map[common.Address]bool - deleted map[common.Address]bool + env *tracing.VMContext + pre stateMap + post stateMap + to common.Address + config prestateTracerConfig + chainConfig *params.ChainConfig + interrupt atomic.Bool // Atomic flag to signal execution interruption + reason error // Textual reason for the interruption + created map[common.Address]bool + deleted map[common.Address]bool } type prestateTracerConfig struct { @@ -90,11 +91,12 @@ func newPrestateTracer(ctx *tracers.Context, cfg json.RawMessage, chainConfig *p return nil, errors.New("cannot use diffMode with includeEmpty") } t := &prestateTracer{ - pre: stateMap{}, - post: stateMap{}, - config: config, - created: make(map[common.Address]bool), - deleted: make(map[common.Address]bool), + pre: stateMap{}, + post: stateMap{}, + config: config, + chainConfig: chainConfig, + created: make(map[common.Address]bool), + deleted: make(map[common.Address]bool), } return &tracers.Tracer{ Hooks: &tracing.Hooks{ @@ -133,6 +135,13 @@ func (t *prestateTracer) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scop case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE): addr := common.Address(stackData[stackLen-2].Bytes20()) t.lookupAccount(addr) + // Lookup the delegation target + if t.chainConfig.IsPrague(t.env.BlockNumber, t.env.Time) { + code := t.env.StateDB.GetCode(addr) + if target, ok := types.ParseDelegation(code); ok { + t.lookupAccount(target) + } + } case op == vm.CREATE: nonce := t.env.StateDB.GetNonce(caller) addr := crypto.CreateAddress(caller, nonce) @@ -161,6 +170,13 @@ func (t *prestateTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction t.created[t.to] = true } else { t.to = *tx.To() + // Lookup the delegation target + if t.chainConfig.IsPrague(t.env.BlockNumber, t.env.Time) { + code := t.env.StateDB.GetCode(t.to) + if target, ok := types.ParseDelegation(code); ok { + t.lookupAccount(target) + } + } } t.lookupAccount(from) From c80e7f42806781469412bc4885d74754c08892d1 Mon Sep 17 00:00:00 2001 From: shazam8253 <54690736+shazam8253@users.noreply.github.com> Date: Tue, 24 Jun 2025 09:12:11 +0200 Subject: [PATCH 06/11] cmd/geth: era-download logic fix (#32081) Downloading from a range was failing because it would return and error early with an error misinterpreting "start-end". --------- Co-authored-by: shantichanal <158101918+shantichanal@users.noreply.github.com> Co-authored-by: Gary Rong --- cmd/geth/chaincmd.go | 24 ++++++---- cmd/geth/chaincmd_test.go | 98 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 cmd/geth/chaincmd_test.go diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index e0606f0d0b..fafdfe2f07 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -765,15 +765,8 @@ func downloadEra(ctx *cli.Context) error { } func parseRange(s string) (start uint64, end uint64, ok bool) { - if m, _ := regexp.MatchString("[0-9]+", s); m { - start, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return 0, 0, false - } - end = start - return start, end, true - } - if m, _ := regexp.MatchString("[0-9]+-[0-9]+", s); m { + log.Info("Parsing block range", "input", s) + if m, _ := regexp.MatchString("^[0-9]+-[0-9]+$", s); m { s1, s2, _ := strings.Cut(s, "-") start, err := strconv.ParseUint(s1, 10, 64) if err != nil { @@ -783,6 +776,19 @@ func parseRange(s string) (start uint64, end uint64, ok bool) { if err != nil { return 0, 0, false } + if start > end { + return 0, 0, false + } + log.Info("Parsing block range", "start", start, "end", end) + return start, end, true + } + if m, _ := regexp.MatchString("^[0-9]+$", s); m { + start, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return 0, 0, false + } + end = start + log.Info("Parsing single block range", "block", start) return start, end, true } return 0, 0, false diff --git a/cmd/geth/chaincmd_test.go b/cmd/geth/chaincmd_test.go new file mode 100644 index 0000000000..131f5c4501 --- /dev/null +++ b/cmd/geth/chaincmd_test.go @@ -0,0 +1,98 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import "testing" + +func TestParseRange(t *testing.T) { + var cases = []struct { + input string + valid bool + expStart uint64 + expEnd uint64 + }{ + { + input: "0", + valid: true, + expStart: 0, + expEnd: 0, + }, + { + input: "500", + valid: true, + expStart: 500, + expEnd: 500, + }, + { + input: "-1", + valid: false, + expStart: 0, + expEnd: 0, + }, + { + input: "1-1", + valid: true, + expStart: 1, + expEnd: 1, + }, + { + input: "0-1", + valid: true, + expStart: 0, + expEnd: 1, + }, + { + input: "1-0", + valid: false, + expStart: 0, + expEnd: 0, + }, + { + input: "1-1000", + valid: true, + expStart: 1, + expEnd: 1000, + }, + { + input: "1-1-", + valid: false, + expStart: 0, + expEnd: 0, + }, + { + input: "-1-1", + valid: false, + expStart: 0, + expEnd: 0, + }, + } + for _, c := range cases { + start, end, valid := parseRange(c.input) + if valid != c.valid { + t.Errorf("Unexpected result, want: %t, got: %t", c.valid, valid) + continue + } + if valid { + if c.expStart != start { + t.Errorf("Unexpected start, want: %d, got: %d", c.expStart, start) + } + if c.expEnd != end { + t.Errorf("Unexpected end, want: %d, got: %d", c.expEnd, end) + } + } + } +} From ebff350a2efede7bc207258a85456556a1b68c78 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Tue, 24 Jun 2025 13:17:41 +0200 Subject: [PATCH 07/11] miner: default gaslimit 45M (#32087) We believe it is safe to raise the gaslimit to 45M before the Fusaka fork. So this PR changes the default to 45M. --- miner/miner.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miner/miner.go b/miner/miner.go index 3cec054d19..ddfe0dcf26 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -52,7 +52,7 @@ type Config struct { // DefaultConfig contains default settings for miner. var DefaultConfig = Config{ - GasCeil: 36_000_000, + GasCeil: 45_000_000, GasPrice: big.NewInt(params.GWei / 1000), // The default recommit time is chosen as two seconds since From 9c5c0e37bf5542b6b60300bf035b43052adcc0ed Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Tue, 24 Jun 2025 20:36:12 +0800 Subject: [PATCH 08/11] core/rawdb, triedb/pathdb: implement history indexer (#31156) This pull request is part-1 for shipping the core part of archive node in PBSS mode. --- cmd/utils/flags.go | 5 - core/blockchain.go | 7 +- core/rawdb/accessors_history.go | 179 +++++++ core/rawdb/accessors_state.go | 31 ++ core/rawdb/database.go | 10 +- core/rawdb/schema.go | 35 ++ ethdb/pebble/pebble.go | 3 + triedb/pathdb/database.go | 38 +- triedb/pathdb/database_test.go | 41 +- triedb/pathdb/disklayer.go | 12 + triedb/pathdb/history.go | 42 +- triedb/pathdb/history_index.go | 436 +++++++++++++++ triedb/pathdb/history_index_block.go | 405 ++++++++++++++ triedb/pathdb/history_index_block_test.go | 216 ++++++++ triedb/pathdb/history_index_test.go | 292 +++++++++++ triedb/pathdb/history_indexer.go | 613 ++++++++++++++++++++++ triedb/pathdb/history_reader.go | 375 +++++++++++++ triedb/pathdb/history_reader_test.go | 159 ++++++ triedb/pathdb/metrics.go | 6 + triedb/pathdb/reader.go | 122 +++++ 20 files changed, 2986 insertions(+), 41 deletions(-) create mode 100644 core/rawdb/accessors_history.go create mode 100644 triedb/pathdb/history_index.go create mode 100644 triedb/pathdb/history_index_block.go create mode 100644 triedb/pathdb/history_index_block_test.go create mode 100644 triedb/pathdb/history_index_test.go create mode 100644 triedb/pathdb/history_indexer.go create mode 100644 triedb/pathdb/history_reader.go create mode 100644 triedb/pathdb/history_reader_test.go diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2412964a3a..ba98ec4169 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1661,11 +1661,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.TransactionHistory = 0 log.Warn("Disabled transaction unindexing for archive node") } - - if cfg.StateScheme != rawdb.HashScheme { - cfg.StateScheme = rawdb.HashScheme - log.Warn("Forcing hash state-scheme for archive mode") - } } if ctx.IsSet(LogHistoryFlag.Name) { cfg.LogHistory = ctx.Uint64(LogHistoryFlag.Name) diff --git a/core/blockchain.go b/core/blockchain.go index 3bb681d7c3..d8b101f616 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -242,9 +242,10 @@ func (cfg *BlockChainConfig) triedbConfig(isVerkle bool) *triedb.Config { } if cfg.StateScheme == rawdb.PathScheme { config.PathDB = &pathdb.Config{ - StateHistory: cfg.StateHistory, - TrieCleanSize: cfg.TrieCleanLimit * 1024 * 1024, - StateCleanSize: cfg.SnapshotLimit * 1024 * 1024, + StateHistory: cfg.StateHistory, + EnableStateIndexing: cfg.ArchiveMode, + TrieCleanSize: cfg.TrieCleanLimit * 1024 * 1024, + StateCleanSize: cfg.SnapshotLimit * 1024 * 1024, // TODO(rjl493456442): The write buffer represents the memory limit used // for flushing both trie data and state data to disk. The config name diff --git a/core/rawdb/accessors_history.go b/core/rawdb/accessors_history.go new file mode 100644 index 0000000000..cf1073f387 --- /dev/null +++ b/core/rawdb/accessors_history.go @@ -0,0 +1,179 @@ +// 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 rawdb + +import ( + "bytes" + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// ReadStateHistoryIndexMetadata retrieves the metadata of state history index. +func ReadStateHistoryIndexMetadata(db ethdb.KeyValueReader) []byte { + data, _ := db.Get(headStateHistoryIndexKey) + return data +} + +// WriteStateHistoryIndexMetadata stores the metadata of state history index +// into database. +func WriteStateHistoryIndexMetadata(db ethdb.KeyValueWriter, blob []byte) { + if err := db.Put(headStateHistoryIndexKey, blob); err != nil { + log.Crit("Failed to store the metadata of state history index", "err", err) + } +} + +// DeleteStateHistoryIndexMetadata removes the metadata of state history index. +func DeleteStateHistoryIndexMetadata(db ethdb.KeyValueWriter) { + if err := db.Delete(headStateHistoryIndexKey); err != nil { + log.Crit("Failed to delete the metadata of state history index", "err", err) + } +} + +// ReadAccountHistoryIndex retrieves the account history index with the provided +// account address. +func ReadAccountHistoryIndex(db ethdb.KeyValueReader, addressHash common.Hash) []byte { + data, err := db.Get(accountHistoryIndexKey(addressHash)) + if err != nil || len(data) == 0 { + return nil + } + return data +} + +// WriteAccountHistoryIndex writes the provided account history index into database. +func WriteAccountHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, data []byte) { + if err := db.Put(accountHistoryIndexKey(addressHash), data); err != nil { + log.Crit("Failed to store account history index", "err", err) + } +} + +// DeleteAccountHistoryIndex deletes the specified account history index from +// the database. +func DeleteAccountHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash) { + if err := db.Delete(accountHistoryIndexKey(addressHash)); err != nil { + log.Crit("Failed to delete account history index", "err", err) + } +} + +// ReadStorageHistoryIndex retrieves the storage history index with the provided +// account address and storage key hash. +func ReadStorageHistoryIndex(db ethdb.KeyValueReader, addressHash common.Hash, storageHash common.Hash) []byte { + data, err := db.Get(storageHistoryIndexKey(addressHash, storageHash)) + if err != nil || len(data) == 0 { + return nil + } + return data +} + +// WriteStorageHistoryIndex writes the provided storage history index into database. +func WriteStorageHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, storageHash common.Hash, data []byte) { + if err := db.Put(storageHistoryIndexKey(addressHash, storageHash), data); err != nil { + log.Crit("Failed to store storage history index", "err", err) + } +} + +// DeleteStorageHistoryIndex deletes the specified state index from the database. +func DeleteStorageHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, storageHash common.Hash) { + if err := db.Delete(storageHistoryIndexKey(addressHash, storageHash)); err != nil { + log.Crit("Failed to delete storage history index", "err", err) + } +} + +// ReadAccountHistoryIndexBlock retrieves the index block with the provided +// account address along with the block id. +func ReadAccountHistoryIndexBlock(db ethdb.KeyValueReader, addressHash common.Hash, blockID uint32) []byte { + data, err := db.Get(accountHistoryIndexBlockKey(addressHash, blockID)) + if err != nil || len(data) == 0 { + return nil + } + return data +} + +// WriteAccountHistoryIndexBlock writes the provided index block into database. +func WriteAccountHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, blockID uint32, data []byte) { + if err := db.Put(accountHistoryIndexBlockKey(addressHash, blockID), data); err != nil { + log.Crit("Failed to store account index block", "err", err) + } +} + +// DeleteAccountHistoryIndexBlock deletes the specified index block from the database. +func DeleteAccountHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, blockID uint32) { + if err := db.Delete(accountHistoryIndexBlockKey(addressHash, blockID)); err != nil { + log.Crit("Failed to delete account index block", "err", err) + } +} + +// ReadStorageHistoryIndexBlock retrieves the index block with the provided state +// identifier along with the block id. +func ReadStorageHistoryIndexBlock(db ethdb.KeyValueReader, addressHash common.Hash, storageHash common.Hash, blockID uint32) []byte { + data, err := db.Get(storageHistoryIndexBlockKey(addressHash, storageHash, blockID)) + if err != nil || len(data) == 0 { + return nil + } + return data +} + +// WriteStorageHistoryIndexBlock writes the provided index block into database. +func WriteStorageHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, storageHash common.Hash, id uint32, data []byte) { + if err := db.Put(storageHistoryIndexBlockKey(addressHash, storageHash, id), data); err != nil { + log.Crit("Failed to store storage index block", "err", err) + } +} + +// DeleteStorageHistoryIndexBlock deletes the specified index block from the database. +func DeleteStorageHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, storageHash common.Hash, id uint32) { + if err := db.Delete(storageHistoryIndexBlockKey(addressHash, storageHash, id)); err != nil { + log.Crit("Failed to delete storage index block", "err", err) + } +} + +// increaseKey increase the input key by one bit. Return nil if the entire +// addition operation overflows. +func increaseKey(key []byte) []byte { + for i := len(key) - 1; i >= 0; i-- { + key[i]++ + if key[i] != 0x0 { + return key + } + } + return nil +} + +// DeleteStateHistoryIndex completely removes all history indexing data, including +// indexes for accounts and storages. +// +// Note, this method assumes the storage space with prefix `StateHistoryIndexPrefix` +// is exclusively occupied by the history indexing data! +func DeleteStateHistoryIndex(db ethdb.KeyValueRangeDeleter) { + start := StateHistoryIndexPrefix + limit := increaseKey(bytes.Clone(StateHistoryIndexPrefix)) + + // Try to remove the data in the range by a loop, as the leveldb + // doesn't support the native range deletion. + for { + err := db.DeleteRange(start, limit) + if err == nil { + return + } + if errors.Is(err, ethdb.ErrTooManyKeys) { + continue + } + log.Crit("Failed to delete history index range", "err", err) + } +} diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 41e15debe9..7d7b37641b 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -18,6 +18,7 @@ package rawdb import ( "encoding/binary" + "errors" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" @@ -255,6 +256,36 @@ func ReadStateHistory(db ethdb.AncientReaderOp, id uint64) ([]byte, []byte, []by return meta, accountIndex, storageIndex, accountData, storageData, nil } +// ReadStateHistoryList retrieves a list of state histories from database with +// specific range. Compute the position of state history in freezer by minus one +// since the id of first state history starts from one(zero for initial state). +func ReadStateHistoryList(db ethdb.AncientReaderOp, start uint64, count uint64) ([][]byte, [][]byte, [][]byte, [][]byte, [][]byte, error) { + metaList, err := db.AncientRange(stateHistoryMeta, start-1, count, 0) + if err != nil { + return nil, nil, nil, nil, nil, err + } + aIndexList, err := db.AncientRange(stateHistoryAccountIndex, start-1, count, 0) + if err != nil { + return nil, nil, nil, nil, nil, err + } + sIndexList, err := db.AncientRange(stateHistoryStorageIndex, start-1, count, 0) + if err != nil { + return nil, nil, nil, nil, nil, err + } + aDataList, err := db.AncientRange(stateHistoryAccountData, start-1, count, 0) + if err != nil { + return nil, nil, nil, nil, nil, err + } + sDataList, err := db.AncientRange(stateHistoryStorageData, start-1, count, 0) + if err != nil { + return nil, nil, nil, nil, nil, err + } + if len(metaList) != len(aIndexList) || len(metaList) != len(sIndexList) || len(metaList) != len(aDataList) || len(metaList) != len(sDataList) { + return nil, nil, nil, nil, nil, errors.New("state history is corrupted") + } + return metaList, aIndexList, sIndexList, aDataList, sDataList, nil +} + // WriteStateHistory writes the provided state history to database. Compute the // position of state history in freezer by minus one since the id of first state // history starts from one(zero for initial state). diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 8854336327..9681c39c58 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -412,6 +412,9 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { filterMapLastBlock stat filterMapBlockLV stat + // Path-mode archive data + stateIndex stat + // Verkle statistics verkleTries stat verkleStateLookups stat @@ -489,6 +492,10 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { case bytes.HasPrefix(key, bloomBitsMetaPrefix) && len(key) < len(bloomBitsMetaPrefix)+8: bloomBits.Add(size) + // Path-based historic state indexes + case bytes.HasPrefix(key, StateHistoryIndexPrefix) && len(key) >= len(StateHistoryIndexPrefix)+common.HashLength: + stateIndex.Add(size) + // Verkle trie data is detected, determine the sub-category case bytes.HasPrefix(key, VerklePrefix): remain := key[len(VerklePrefix):] @@ -544,6 +551,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { {"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()}, {"Key-Value store", "Path trie account nodes", accountTries.Size(), accountTries.Count()}, {"Key-Value store", "Path trie storage nodes", storageTries.Size(), storageTries.Count()}, + {"Key-Value store", "Path state history indexes", stateIndex.Size(), stateIndex.Count()}, {"Key-Value store", "Verkle trie nodes", verkleTries.Size(), verkleTries.Count()}, {"Key-Value store", "Verkle trie state lookups", verkleStateLookups.Size(), verkleStateLookups.Count()}, {"Key-Value store", "Trie preimages", preimages.Size(), preimages.Count()}, @@ -591,7 +599,7 @@ var knownMetadataKeys = [][]byte{ snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey, persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey, - filterMapsRangeKey, + filterMapsRangeKey, headStateHistoryIndexKey, } // printChainMetadata prints out chain metadata to stderr. diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index fa125cecc0..eec25a8042 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -76,6 +76,10 @@ var ( // trieJournalKey tracks the in-memory trie node layers across restarts. trieJournalKey = []byte("TrieJournal") + // headStateHistoryIndexKey tracks the ID of the latest state history that has + // been indexed. + headStateHistoryIndexKey = []byte("LastStateHistoryIndex") + // txIndexTailKey tracks the oldest block whose transactions have been indexed. txIndexTailKey = []byte("TransactionIndexTail") @@ -117,6 +121,13 @@ var ( TrieNodeStoragePrefix = []byte("O") // TrieNodeStoragePrefix + accountHash + hexPath -> trie node stateIDPrefix = []byte("L") // stateIDPrefix + state root -> state id + // State history indexing within path-based storage scheme + StateHistoryIndexPrefix = []byte("m") // The global prefix of state history index data + StateHistoryAccountMetadataPrefix = []byte("ma") // StateHistoryAccountMetadataPrefix + account address hash => account metadata + StateHistoryStorageMetadataPrefix = []byte("ms") // StateHistoryStorageMetadataPrefix + account address hash + storage slot hash => slot metadata + StateHistoryAccountBlockPrefix = []byte("mba") // StateHistoryAccountBlockPrefix + account address hash + block_number => account block + StateHistoryStorageBlockPrefix = []byte("mbs") // StateHistoryStorageBlockPrefix + account address hash + storage slot hash + block_number => slot block + // VerklePrefix is the database prefix for Verkle trie data, which includes: // (a) Trie nodes // (b) In-memory trie node journal @@ -362,3 +373,27 @@ func filterMapBlockLVKey(number uint64) []byte { binary.BigEndian.PutUint64(key[l:], number) return key } + +// accountHistoryIndexKey = StateHistoryAccountMetadataPrefix + addressHash +func accountHistoryIndexKey(addressHash common.Hash) []byte { + return append(StateHistoryAccountMetadataPrefix, addressHash.Bytes()...) +} + +// storageHistoryIndexKey = StateHistoryStorageMetadataPrefix + addressHash + storageHash +func storageHistoryIndexKey(addressHash common.Hash, storageHash common.Hash) []byte { + return append(append(StateHistoryStorageMetadataPrefix, addressHash.Bytes()...), storageHash.Bytes()...) +} + +// accountHistoryIndexBlockKey = StateHistoryAccountBlockPrefix + addressHash + blockID +func accountHistoryIndexBlockKey(addressHash common.Hash, blockID uint32) []byte { + var buf [4]byte + binary.BigEndian.PutUint32(buf[:], blockID) + return append(append(StateHistoryAccountBlockPrefix, addressHash.Bytes()...), buf[:]...) +} + +// storageHistoryIndexBlockKey = StateHistoryStorageBlockPrefix + addressHash + storageHash + blockID +func storageHistoryIndexBlockKey(addressHash common.Hash, storageHash common.Hash, blockID uint32) []byte { + var buf [4]byte + binary.BigEndian.PutUint32(buf[:], blockID) + return append(append(append(StateHistoryStorageBlockPrefix, addressHash.Bytes()...), storageHash.Bytes()...), buf[:]...) +} diff --git a/ethdb/pebble/pebble.go b/ethdb/pebble/pebble.go index ac9eb9bdba..58a521f6fb 100644 --- a/ethdb/pebble/pebble.go +++ b/ethdb/pebble/pebble.go @@ -83,6 +83,7 @@ type Database struct { estimatedCompDebtGauge *metrics.Gauge // Gauge for tracking the number of bytes that need to be compacted liveCompGauge *metrics.Gauge // Gauge for tracking the number of in-progress compactions liveCompSizeGauge *metrics.Gauge // Gauge for tracking the size of in-progress compactions + liveIterGauge *metrics.Gauge // Gauge for tracking the number of live database iterators levelsGauge []*metrics.Gauge // Gauge for tracking the number of tables in levels quitLock sync.RWMutex // Mutex protecting the quit channel and the closed flag @@ -326,6 +327,7 @@ func New(file string, cache int, handles int, namespace string, readonly bool) ( db.estimatedCompDebtGauge = metrics.GetOrRegisterGauge(namespace+"compact/estimateDebt", nil) db.liveCompGauge = metrics.GetOrRegisterGauge(namespace+"compact/live/count", nil) db.liveCompSizeGauge = metrics.GetOrRegisterGauge(namespace+"compact/live/size", nil) + db.liveIterGauge = metrics.GetOrRegisterGauge(namespace+"iter/count", nil) // Start up the metrics gathering and return go db.meter(metricsGatheringInterval, namespace) @@ -582,6 +584,7 @@ func (d *Database) meter(refresh time.Duration, namespace string) { d.seekCompGauge.Update(stats.Compact.ReadCount) d.liveCompGauge.Update(stats.Compact.NumInProgress) d.liveCompSizeGauge.Update(stats.Compact.InProgressBytes) + d.liveIterGauge.Update(stats.TableIters) d.liveMemTablesGauge.Update(stats.MemTable.Count) d.zombieMemTablesGauge.Update(stats.MemTable.ZombieCount) diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index 9795d81e63..8932f3f7f8 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -114,11 +114,12 @@ type layer interface { // Config contains the settings for database. type Config struct { - StateHistory uint64 // Number of recent blocks to maintain state history for - TrieCleanSize int // Maximum memory allowance (in bytes) for caching clean trie nodes - StateCleanSize int // Maximum memory allowance (in bytes) for caching clean state data - WriteBufferSize int // Maximum memory allowance (in bytes) for write buffer - ReadOnly bool // Flag whether the database is opened in read only mode + StateHistory uint64 // Number of recent blocks to maintain state history for + EnableStateIndexing bool // Whether to enable state history indexing for external state access + TrieCleanSize int // Maximum memory allowance (in bytes) for caching clean trie nodes + StateCleanSize int // Maximum memory allowance (in bytes) for caching clean state data + WriteBufferSize int // Maximum memory allowance (in bytes) for write buffer + ReadOnly bool // Flag whether the database is opened in read only mode // Testing configurations SnapshotNoBuild bool // Flag Whether the state generation is allowed @@ -149,7 +150,12 @@ func (c *Config) fields() []interface{} { list = append(list, "triecache", common.StorageSize(c.TrieCleanSize)) list = append(list, "statecache", common.StorageSize(c.StateCleanSize)) list = append(list, "buffer", common.StorageSize(c.WriteBufferSize)) - list = append(list, "history", c.StateHistory) + + if c.StateHistory == 0 { + list = append(list, "history", "entire chain") + } else { + list = append(list, "history", fmt.Sprintf("last %d blocks", c.StateHistory)) + } return list } @@ -213,6 +219,7 @@ type Database struct { tree *layerTree // The group for all known layers freezer ethdb.ResettableAncientStore // Freezer for storing trie histories, nil possible in tests lock sync.RWMutex // Lock to prevent mutations from happening at the same time + indexer *historyIndexer // History indexer } // New attempts to load an already existing layer from a persistent key-value @@ -262,6 +269,11 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database { if err := db.setStateGenerator(); err != nil { log.Crit("Failed to setup the generator", "err", err) } + // TODO (rjl493456442) disable the background indexing in read-only mode + if db.freezer != nil && db.config.EnableStateIndexing { + db.indexer = newHistoryIndexer(db.diskdb, db.freezer, db.tree.bottom().stateID()) + log.Info("Enabled state history indexing") + } fields := config.fields() if db.isVerkle { fields = append(fields, "verkle", true) @@ -299,6 +311,11 @@ func (db *Database) repairHistory() error { log.Crit("Failed to retrieve head of state history", "err", err) } if frozen != 0 { + // TODO(rjl493456442) would be better to group them into a batch. + // + // Purge all state history indexing data first + rawdb.DeleteStateHistoryIndexMetadata(db.diskdb) + rawdb.DeleteStateHistoryIndex(db.diskdb) err := db.freezer.Reset() if err != nil { log.Crit("Failed to reset state histories", "err", err) @@ -487,6 +504,11 @@ func (db *Database) Enable(root common.Hash) error { // mappings can be huge and might take a while to clear // them, just leave them in disk and wait for overwriting. if db.freezer != nil { + // TODO(rjl493456442) would be better to group them into a batch. + // + // Purge all state history indexing data first + rawdb.DeleteStateHistoryIndexMetadata(db.diskdb) + rawdb.DeleteStateHistoryIndex(db.diskdb) if err := db.freezer.Reset(); err != nil { return err } @@ -611,6 +633,10 @@ func (db *Database) Close() error { } dl.resetCache() // release the memory held by clean cache + // Terminate the background state history indexer + if db.indexer != nil { + db.indexer.close() + } // Close the attached state history freezer. if db.freezer == nil { return nil diff --git a/triedb/pathdb/database_test.go b/triedb/pathdb/database_test.go index 6fc099015c..2982202009 100644 --- a/triedb/pathdb/database_test.go +++ b/triedb/pathdb/database_test.go @@ -121,15 +121,16 @@ type tester struct { snapStorages map[common.Hash]map[common.Hash]map[common.Hash][]byte // Keyed by the hash of account address and the hash of storage key } -func newTester(t *testing.T, historyLimit uint64, isVerkle bool, layers int) *tester { +func newTester(t *testing.T, historyLimit uint64, isVerkle bool, layers int, enableIndex bool) *tester { var ( disk, _ = rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{Ancient: t.TempDir()}) db = New(disk, &Config{ - StateHistory: historyLimit, - TrieCleanSize: 256 * 1024, - StateCleanSize: 256 * 1024, - WriteBufferSize: 256 * 1024, - NoAsyncFlush: true, + StateHistory: historyLimit, + EnableStateIndexing: enableIndex, + TrieCleanSize: 256 * 1024, + StateCleanSize: 256 * 1024, + WriteBufferSize: 256 * 1024, + NoAsyncFlush: true, }, isVerkle) obj = &tester{ @@ -164,6 +165,20 @@ func (t *tester) hashPreimage(hash common.Hash) common.Hash { return common.BytesToHash(t.preimages[hash]) } +func (t *tester) extend(layers int) { + for i := 0; i < layers; i++ { + var parent = types.EmptyRootHash + if len(t.roots) != 0 { + parent = t.roots[len(t.roots)-1] + } + root, nodes, states := t.generate(parent, true) + if err := t.db.Update(root, parent, uint64(i), nodes, states); err != nil { + panic(fmt.Errorf("failed to update state changes, err: %w", err)) + } + t.roots = append(t.roots, root) + } +} + func (t *tester) release() { t.db.Close() t.db.diskdb.Close() @@ -451,7 +466,7 @@ func TestDatabaseRollback(t *testing.T) { }() // Verify state histories - tester := newTester(t, 0, false, 32) + tester := newTester(t, 0, false, 32, false) defer tester.release() if err := tester.verifyHistory(); err != nil { @@ -485,7 +500,7 @@ func TestDatabaseRecoverable(t *testing.T) { }() var ( - tester = newTester(t, 0, false, 12) + tester = newTester(t, 0, false, 12, false) index = tester.bottomIndex() ) defer tester.release() @@ -529,7 +544,7 @@ func TestDisable(t *testing.T) { maxDiffLayers = 128 }() - tester := newTester(t, 0, false, 32) + tester := newTester(t, 0, false, 32, false) defer tester.release() stored := crypto.Keccak256Hash(rawdb.ReadAccountTrieNode(tester.db.diskdb, nil)) @@ -571,7 +586,7 @@ func TestCommit(t *testing.T) { maxDiffLayers = 128 }() - tester := newTester(t, 0, false, 12) + tester := newTester(t, 0, false, 12, false) defer tester.release() if err := tester.db.Commit(tester.lastHash(), false); err != nil { @@ -601,7 +616,7 @@ func TestJournal(t *testing.T) { maxDiffLayers = 128 }() - tester := newTester(t, 0, false, 12) + tester := newTester(t, 0, false, 12, false) defer tester.release() if err := tester.db.Journal(tester.lastHash()); err != nil { @@ -631,7 +646,7 @@ func TestCorruptedJournal(t *testing.T) { maxDiffLayers = 128 }() - tester := newTester(t, 0, false, 12) + tester := newTester(t, 0, false, 12, false) defer tester.release() if err := tester.db.Journal(tester.lastHash()); err != nil { @@ -679,7 +694,7 @@ func TestTailTruncateHistory(t *testing.T) { maxDiffLayers = 128 }() - tester := newTester(t, 10, false, 12) + tester := newTester(t, 10, false, 12, false) defer tester.release() tester.db.Close() diff --git a/triedb/pathdb/disklayer.go b/triedb/pathdb/disklayer.go index 494164987f..06f0a7285f 100644 --- a/triedb/pathdb/disklayer.go +++ b/triedb/pathdb/disklayer.go @@ -355,6 +355,12 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) { overflow = true oldest = bottom.stateID() - limit + 1 // track the id of history **after truncation** } + // Notify the state history indexer for newly created history + if dl.db.indexer != nil { + if err := dl.db.indexer.extend(bottom.stateID()); err != nil { + return nil, err + } + } } // Mark the diskLayer as stale before applying any mutations on top. dl.stale = true @@ -477,6 +483,12 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) { dl.stale = true + // Unindex the corresponding state history + if dl.db.indexer != nil { + if err := dl.db.indexer.shorten(dl.id); err != nil { + return nil, err + } + } // State change may be applied to node buffer, or the persistent // state, depends on if node buffer is empty or not. If the node // buffer is not empty, it means that the state transition that diff --git a/triedb/pathdb/history.go b/triedb/pathdb/history.go index 63c9152bf7..47f224170d 100644 --- a/triedb/pathdb/history.go +++ b/triedb/pathdb/history.go @@ -505,25 +505,41 @@ func (h *history) decode(accountData, storageData, accountIndexes, storageIndexe // readHistory reads and decodes the state history object by the given id. func readHistory(reader ethdb.AncientReader, id uint64) (*history, error) { - blob := rawdb.ReadStateHistoryMeta(reader, id) - if len(blob) == 0 { - return nil, fmt.Errorf("state history not found %d", id) + mData, accountIndexes, storageIndexes, accountData, storageData, err := rawdb.ReadStateHistory(reader, id) + if err != nil { + return nil, err } var m meta - if err := m.decode(blob); err != nil { + if err := m.decode(mData); err != nil { return nil, err } - var ( - dec = history{meta: &m} - accountData = rawdb.ReadStateAccountHistory(reader, id) - storageData = rawdb.ReadStateStorageHistory(reader, id) - accountIndexes = rawdb.ReadStateAccountIndex(reader, id) - storageIndexes = rawdb.ReadStateStorageIndex(reader, id) - ) - if err := dec.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil { + h := history{meta: &m} + if err := h.decode(accountData, storageData, accountIndexes, storageIndexes); err != nil { return nil, err } - return &dec, nil + return &h, nil +} + +// readHistories reads and decodes a list of state histories with the specific +// history range. +func readHistories(freezer ethdb.AncientReader, start uint64, count uint64) ([]*history, error) { + var histories []*history + metaList, aIndexList, sIndexList, aDataList, sDataList, err := rawdb.ReadStateHistoryList(freezer, start, count) + if err != nil { + return nil, err + } + for i := 0; i < len(metaList); i++ { + var m meta + if err := m.decode(metaList[i]); err != nil { + return nil, err + } + h := history{meta: &m} + if err := h.decode(aDataList[i], sDataList[i], aIndexList[i], sIndexList[i]); err != nil { + return nil, err + } + histories = append(histories, &h) + } + return histories, nil } // writeHistory persists the state history with the provided state set. diff --git a/triedb/pathdb/history_index.go b/triedb/pathdb/history_index.go new file mode 100644 index 0000000000..f79581b38b --- /dev/null +++ b/triedb/pathdb/history_index.go @@ -0,0 +1,436 @@ +// 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 = indexBlockEntriesCap +} + +// encode packs index block descriptor into byte stream. +func (d *indexBlockDesc) encode() []byte { + var buf [indexBlockDescSize]byte + binary.BigEndian.PutUint64(buf[0:8], d.max) + binary.BigEndian.PutUint16(buf[8:10], d.entries) + binary.BigEndian.PutUint32(buf[10:14], d.id) + return buf[:] +} + +// decode unpacks index block descriptor from byte stream. +func (d *indexBlockDesc) decode(blob []byte) { + d.max = binary.BigEndian.Uint64(blob[:8]) + d.entries = binary.BigEndian.Uint16(blob[8:10]) + d.id = binary.BigEndian.Uint32(blob[10:14]) +} + +// parseIndexBlock parses the index block with the supplied byte stream. +// The index block format can be illustrated as below: +// +// +---->+------------------+ +// | | Chunk1 | +// | +------------------+ +// | | ...... | +// | +-->+------------------+ +// | | | ChunkN | +// | | +------------------+ +// +-|---| Restart1 | +// | | Restart... | 2N bytes +// +---| RestartN | +// +------------------+ +// | Restart count | 1 byte +// +------------------+ +// +// - Chunk list: A list of data chunks +// - Restart list: A list of 2-byte pointers, each pointing to the start position of a chunk +// - Restart count: The number of restarts in the block, stored at the end of the block (1 byte) +// +// Note: the pointer is encoded as a uint16, which is sufficient within a chunk. +// A uint16 can cover offsets in the range [0, 65536), which is more than enough +// to store 4096 integers. +// +// Each chunk begins with the full value of the first integer, followed by +// subsequent integers representing the differences between the current value +// and the preceding one. Integers are encoded with variable-size for best +// storage efficiency. Each chunk can be illustrated as below. +// +// Restart ---> +----------------+ +// | Full integer | +// +----------------+ +// | Diff with prev | +// +----------------+ +// | ... | +// +----------------+ +// | Diff with prev | +// +----------------+ +// +// Empty index block is regarded as invalid. +func parseIndexBlock(blob []byte) ([]uint16, []byte, error) { + if len(blob) < 1 { + return nil, nil, fmt.Errorf("corrupted index block, len: %d", len(blob)) + } + restartLen := blob[len(blob)-1] + if restartLen == 0 { + return nil, nil, errors.New("corrupted index block, no restart") + } + tailLen := int(restartLen)*2 + 1 + if len(blob) < tailLen { + return nil, nil, fmt.Errorf("truncated restarts, size: %d, restarts: %d", len(blob), restartLen) + } + restarts := make([]uint16, 0, restartLen) + for i := int(restartLen); i > 0; i-- { + restart := binary.BigEndian.Uint16(blob[len(blob)-1-2*i:]) + restarts = append(restarts, restart) + } + // Validate that restart points are strictly ordered and within the valid + // data range. + var prev uint16 + for i := 0; i < len(restarts); i++ { + if i != 0 { + if restarts[i] <= prev { + return nil, nil, fmt.Errorf("restart out of order, prev: %d, next: %d", prev, restarts[i]) + } + } + if int(restarts[i]) >= len(blob)-tailLen { + return nil, nil, fmt.Errorf("invalid restart position, restart: %d, size: %d", restarts[i], len(blob)-tailLen) + } + prev = restarts[i] + } + return restarts, blob[:len(blob)-tailLen], nil +} + +// blockReader is the reader to access the element within a block. +type blockReader struct { + restarts []uint16 + data []byte +} + +// newBlockReader constructs the block reader with the supplied block data. +func newBlockReader(blob []byte) (*blockReader, error) { + restarts, data, err := parseIndexBlock(blob) + if err != nil { + return nil, err + } + return &blockReader{ + restarts: restarts, + data: data, // safe to own the slice + }, nil +} + +// readGreaterThan locates the first element in the block that is greater than +// the specified value. If no such element is found, MaxUint64 is returned. +func (br *blockReader) readGreaterThan(id uint64) (uint64, error) { + var err error + index := sort.Search(len(br.restarts), func(i int) bool { + item, n := binary.Uvarint(br.data[br.restarts[i]:]) + if n <= 0 { + err = fmt.Errorf("failed to decode item at restart %d", br.restarts[i]) + } + return item > id + }) + if err != nil { + return 0, err + } + if index == 0 { + item, _ := binary.Uvarint(br.data[br.restarts[0]:]) + return item, nil + } + var ( + start int + limit int + result uint64 + ) + if index == len(br.restarts) { + // The element being searched falls within the last restart section, + // there is no guarantee such element can be found. + start = int(br.restarts[len(br.restarts)-1]) + limit = len(br.data) + } else { + // The element being searched falls within the non-last restart section, + // such element can be found for sure. + start = int(br.restarts[index-1]) + limit = int(br.restarts[index]) + } + pos := start + for pos < limit { + x, n := binary.Uvarint(br.data[pos:]) + if pos == start { + result = x + } else { + result += x + } + if result > id { + return result, nil + } + pos += n + } + // The element which is greater than specified id is not found. + if index == len(br.restarts) { + return math.MaxUint64, nil + } + // The element which is the first one greater than the specified id + // is exactly the one located at the restart point. + item, _ := binary.Uvarint(br.data[br.restarts[index]:]) + return item, nil +} + +type blockWriter struct { + desc *indexBlockDesc // Descriptor of the block + restarts []uint16 // Offsets into the data slice, marking the start of each section + scratch []byte // Buffer used for encoding full integers or value differences + data []byte // Aggregated encoded data slice +} + +func newBlockWriter(blob []byte, desc *indexBlockDesc) (*blockWriter, error) { + scratch := make([]byte, binary.MaxVarintLen64) + if len(blob) == 0 { + return &blockWriter{ + desc: desc, + scratch: scratch, + data: make([]byte, 0, 1024), + }, nil + } + restarts, data, err := parseIndexBlock(blob) + if err != nil { + return nil, err + } + return &blockWriter{ + desc: desc, + restarts: restarts, + scratch: scratch, + data: data, // safe to own the slice + }, nil +} + +// append adds a new element to the block. The new element must be greater than +// the previous one. The provided ID is assumed to always be greater than 0. +func (b *blockWriter) append(id uint64) error { + if id == 0 { + return errors.New("invalid zero id") + } + if id <= b.desc.max { + return fmt.Errorf("append element out of order, last: %d, this: %d", b.desc.max, id) + } + // Rotate the current restart section if it's full + if b.desc.entries%indexBlockRestartLen == 0 { + // Save the offset within the data slice as the restart point + // for the next section. + b.restarts = append(b.restarts, uint16(len(b.data))) + + // The restart point item can either be encoded in variable + // size or fixed size. Although variable-size encoding is + // slightly slower (2ns per operation), it is still relatively + // fast, therefore, it's picked for better space efficiency. + // + // The first element in a restart range is encoded using its + // full value. + n := binary.PutUvarint(b.scratch[0:], id) + b.data = append(b.data, b.scratch[:n]...) + } else { + // The current section is not full, append the element. + // The element which is not the first one in the section + // is encoded using the value difference from the preceding + // element. + n := binary.PutUvarint(b.scratch[0:], id-b.desc.max) + b.data = append(b.data, b.scratch[:n]...) + } + b.desc.entries++ + + // The state history ID must be greater than 0. + //if b.desc.min == 0 { + // b.desc.min = id + //} + b.desc.max = id + return nil +} + +// scanSection traverses the specified section and terminates if fn returns true. +func (b *blockWriter) scanSection(section int, fn func(uint64, int) bool) { + var ( + value uint64 + start = int(b.restarts[section]) + pos = start + limit int + ) + if section == len(b.restarts)-1 { + limit = len(b.data) + } else { + limit = int(b.restarts[section+1]) + } + for pos < limit { + x, n := binary.Uvarint(b.data[pos:]) + if pos == start { + value = x + } else { + value += x + } + if fn(value, pos) { + return + } + pos += n + } +} + +// sectionLast returns the last element in the specified section. +func (b *blockWriter) sectionLast(section int) uint64 { + var n uint64 + b.scanSection(section, func(v uint64, _ int) bool { + n = v + return false + }) + return n +} + +// sectionSearch looks up the specified value in the given section, +// the position and the preceding value will be returned if found. +func (b *blockWriter) sectionSearch(section int, n uint64) (found bool, prev uint64, pos int) { + b.scanSection(section, func(v uint64, p int) bool { + if n == v { + pos = p + found = true + return true // terminate iteration + } + prev = v + return false // continue iteration + }) + return found, prev, pos +} + +// pop removes the last element from the block. The assumption is held that block +// writer must be non-empty. +func (b *blockWriter) pop(id uint64) error { + if id == 0 { + return errors.New("invalid zero id") + } + if id != b.desc.max { + return fmt.Errorf("pop element out of order, last: %d, this: %d", b.desc.max, id) + } + // If there is only one entry left, the entire block should be reset + if b.desc.entries == 1 { + //b.desc.min = 0 + b.desc.max = 0 + b.desc.entries = 0 + b.restarts = nil + b.data = b.data[:0] + return nil + } + // Pop the last restart section if the section becomes empty after removing + // one element. + if b.desc.entries%indexBlockRestartLen == 1 { + b.data = b.data[:b.restarts[len(b.restarts)-1]] + b.restarts = b.restarts[:len(b.restarts)-1] + b.desc.max = b.sectionLast(len(b.restarts) - 1) + b.desc.entries -= 1 + return nil + } + // Look up the element preceding the one to be popped, in order to update + // the maximum element in the block. + found, prev, pos := b.sectionSearch(len(b.restarts)-1, id) + if !found { + return fmt.Errorf("pop element is not found, last: %d, this: %d", b.desc.max, id) + } + b.desc.max = prev + b.data = b.data[:pos] + b.desc.entries -= 1 + return nil +} + +func (b *blockWriter) empty() bool { + return b.desc.empty() +} + +func (b *blockWriter) full() bool { + return b.desc.full() +} + +// finish finalizes the index block encoding by appending the encoded restart points +// and the restart counter to the end of the block. +// +// This function is safe to be called multiple times. +func (b *blockWriter) finish() []byte { + var buf []byte + for _, number := range b.restarts { + binary.BigEndian.PutUint16(b.scratch[:2], number) + buf = append(buf, b.scratch[:2]...) + } + buf = append(buf, byte(len(b.restarts))) + return append(b.data, buf...) +} diff --git a/triedb/pathdb/history_index_block_test.go b/triedb/pathdb/history_index_block_test.go new file mode 100644 index 0000000000..173387b447 --- /dev/null +++ b/triedb/pathdb/history_index_block_test.go @@ -0,0 +1,216 @@ +// 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 value + }) + got, err := br.readGreaterThan(value) + if err != nil { + t.Fatalf("Unexpected error, got %v", err) + } + if pos == len(elements) { + if got != math.MaxUint64 { + t.Fatalf("Unexpected result, got %d, wanted math.MaxUint64", got) + } + } else if got != elements[pos] { + t.Fatalf("Unexpected result, got %d, wanted %d", got, elements[pos]) + } + } +} + +func TestBlockWriterBasic(t *testing.T) { + bw, _ := newBlockWriter(nil, newIndexBlockDesc(0)) + if !bw.empty() { + t.Fatal("expected empty block") + } + bw.append(2) + if err := bw.append(1); err == nil { + t.Fatal("out-of-order insertion is not expected") + } + for i := 0; i < 10; i++ { + bw.append(uint64(i + 3)) + } + + bw, err := newBlockWriter(bw.finish(), newIndexBlockDesc(0)) + if err != nil { + t.Fatalf("Failed to construct the block writer, %v", err) + } + for i := 0; i < 10; i++ { + if err := bw.append(uint64(i + 100)); err != nil { + t.Fatalf("Failed to append value %d: %v", i, err) + } + } + bw.finish() +} + +func TestBlockWriterDelete(t *testing.T) { + bw, _ := newBlockWriter(nil, newIndexBlockDesc(0)) + for i := 0; i < 10; i++ { + bw.append(uint64(i + 1)) + } + // Pop unknown id, the request should be rejected + if err := bw.pop(100); err == nil { + t.Fatal("Expect error to occur for unknown id") + } + for i := 10; i >= 1; i-- { + if err := bw.pop(uint64(i)); err != nil { + t.Fatalf("Unexpected error for element popping, %v", err) + } + empty := i == 1 + if empty != bw.empty() { + t.Fatalf("Emptiness is not matched, want: %T, got: %T", empty, bw.empty()) + } + newMax := uint64(i - 1) + if bw.desc.max != newMax { + t.Fatalf("Maxmium element is not matched, want: %d, got: %d", newMax, bw.desc.max) + } + } +} + +func TestBlcokWriterDeleteWithData(t *testing.T) { + elements := []uint64{ + 1, 5, 10, 11, 20, + } + bw, _ := newBlockWriter(nil, newIndexBlockDesc(0)) + for i := 0; i < len(elements); i++ { + bw.append(elements[i]) + } + + // Re-construct the block writer with data + desc := &indexBlockDesc{ + id: 0, + max: 20, + entries: 5, + } + bw, err := newBlockWriter(bw.finish(), desc) + if err != nil { + t.Fatalf("Failed to construct block writer %v", err) + } + for i := len(elements) - 1; i > 0; i-- { + if err := bw.pop(elements[i]); err != nil { + t.Fatalf("Failed to pop element, %v", err) + } + newTail := elements[i-1] + + // Ensure the element can still be queried with no issue + br, err := newBlockReader(bw.finish()) + if err != nil { + t.Fatalf("Failed to construct the block reader, %v", err) + } + cases := []struct { + value uint64 + result uint64 + }{ + {0, 1}, + {1, 5}, + {10, 11}, + {19, 20}, + {20, math.MaxUint64}, + {21, math.MaxUint64}, + } + for _, c := range cases { + want := c.result + if c.value >= newTail { + want = math.MaxUint64 + } + got, err := br.readGreaterThan(c.value) + if err != nil { + t.Fatalf("Unexpected error, got %v", err) + } + if got != want { + t.Fatalf("Unexpected result, got %v, wanted %v", got, want) + } + } + } +} + +func TestCorruptedIndexBlock(t *testing.T) { + bw, _ := newBlockWriter(nil, newIndexBlockDesc(0)) + for i := 0; i < 10; i++ { + bw.append(uint64(i + 1)) + } + buf := bw.finish() + + // Mutate the buffer manually + buf[len(buf)-1]++ + _, err := newBlockWriter(buf, newIndexBlockDesc(0)) + if err == nil { + t.Fatal("Corrupted index block data is not detected") + } +} diff --git a/triedb/pathdb/history_index_test.go b/triedb/pathdb/history_index_test.go new file mode 100644 index 0000000000..7b24b86fd6 --- /dev/null +++ b/triedb/pathdb/history_index_test.go @@ -0,0 +1,292 @@ +// 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 value + }) + got, err := br.readGreaterThan(value) + if err != nil { + t.Fatalf("Unexpected error, got %v", err) + } + if pos == len(elements) { + if got != math.MaxUint64 { + t.Fatalf("Unexpected result, got %d, wanted math.MaxUint64", got) + } + } else if got != elements[pos] { + t.Fatalf("Unexpected result, got %d, wanted %d", got, elements[pos]) + } + } +} + +func TestEmptyIndexReader(t *testing.T) { + br, err := newIndexReader(rawdb.NewMemoryDatabase(), newAccountIdent(common.Hash{0xa})) + if err != nil { + t.Fatalf("Failed to construct the index reader, %v", err) + } + res, err := br.readGreaterThan(100) + if err != nil { + t.Fatalf("Failed to query, %v", err) + } + if res != math.MaxUint64 { + t.Fatalf("Unexpected result, got %d, wanted math.MaxUint64", res) + } +} + +func TestIndexWriterBasic(t *testing.T) { + db := rawdb.NewMemoryDatabase() + iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa})) + iw.append(2) + if err := iw.append(1); err == nil { + t.Fatal("out-of-order insertion is not expected") + } + for i := 0; i < 10; i++ { + iw.append(uint64(i + 3)) + } + batch := db.NewBatch() + iw.finish(batch) + batch.Write() + + iw, err := newIndexWriter(db, newAccountIdent(common.Hash{0xa})) + if err != nil { + t.Fatalf("Failed to construct the block writer, %v", err) + } + for i := 0; i < 10; i++ { + if err := iw.append(uint64(i + 100)); err != nil { + t.Fatalf("Failed to append item, %v", err) + } + } + iw.finish(db.NewBatch()) +} + +func TestIndexWriterDelete(t *testing.T) { + db := rawdb.NewMemoryDatabase() + iw, _ := newIndexWriter(db, newAccountIdent(common.Hash{0xa})) + for i := 0; i < indexBlockEntriesCap*4; i++ { + iw.append(uint64(i + 1)) + } + batch := db.NewBatch() + iw.finish(batch) + batch.Write() + + // Delete unknown id, the request should be rejected + id, _ := newIndexDeleter(db, newAccountIdent(common.Hash{0xa})) + if err := id.pop(indexBlockEntriesCap * 5); err == nil { + t.Fatal("Expect error to occur for unknown id") + } + for i := indexBlockEntriesCap * 4; i >= 1; i-- { + if err := id.pop(uint64(i)); err != nil { + t.Fatalf("Unexpected error for element popping, %v", err) + } + if id.lastID != uint64(i-1) { + t.Fatalf("Unexpected lastID, want: %d, got: %d", uint64(i-1), iw.lastID) + } + if rand.Intn(10) == 0 { + batch := db.NewBatch() + id.finish(batch) + batch.Write() + } + } +} + +func TestBatchIndexerWrite(t *testing.T) { + var ( + db = rawdb.NewMemoryDatabase() + batch = newBatchIndexer(db, false) + histories = makeHistories(10) + ) + for i, h := range histories { + if err := batch.process(h, uint64(i+1)); err != nil { + t.Fatalf("Failed to process history, %v", err) + } + } + if err := batch.finish(true); err != nil { + t.Fatalf("Failed to finish batch indexer, %v", err) + } + metadata := loadIndexMetadata(db) + if metadata == nil || metadata.Last != uint64(10) { + t.Fatal("Unexpected index position") + } + var ( + accounts = make(map[common.Hash][]uint64) + storages = make(map[common.Hash]map[common.Hash][]uint64) + ) + for i, h := range histories { + for _, addr := range h.accountList { + addrHash := crypto.Keccak256Hash(addr.Bytes()) + accounts[addrHash] = append(accounts[addrHash], uint64(i+1)) + + if _, ok := storages[addrHash]; !ok { + storages[addrHash] = make(map[common.Hash][]uint64) + } + for _, slot := range h.storageList[addr] { + storages[addrHash][slot] = append(storages[addrHash][slot], uint64(i+1)) + } + } + } + for addrHash, indexes := range accounts { + ir, _ := newIndexReader(db, newAccountIdent(addrHash)) + for i := 0; i < len(indexes)-1; i++ { + n, err := ir.readGreaterThan(indexes[i]) + if err != nil { + t.Fatalf("Failed to read index, %v", err) + } + if n != indexes[i+1] { + t.Fatalf("Unexpected result, want %d, got %d", indexes[i+1], n) + } + } + n, err := ir.readGreaterThan(indexes[len(indexes)-1]) + if err != nil { + t.Fatalf("Failed to read index, %v", err) + } + if n != math.MaxUint64 { + t.Fatalf("Unexpected result, want math.MaxUint64, got %d", n) + } + } + for addrHash, slots := range storages { + for slotHash, indexes := range slots { + ir, _ := newIndexReader(db, newStorageIdent(addrHash, slotHash)) + for i := 0; i < len(indexes)-1; i++ { + n, err := ir.readGreaterThan(indexes[i]) + if err != nil { + t.Fatalf("Failed to read index, %v", err) + } + if n != indexes[i+1] { + t.Fatalf("Unexpected result, want %d, got %d", indexes[i+1], n) + } + } + n, err := ir.readGreaterThan(indexes[len(indexes)-1]) + if err != nil { + t.Fatalf("Failed to read index, %v", err) + } + if n != math.MaxUint64 { + t.Fatalf("Unexpected result, want math.MaxUint64, got %d", n) + } + } + } +} + +func TestBatchIndexerDelete(t *testing.T) { + var ( + db = rawdb.NewMemoryDatabase() + bw = newBatchIndexer(db, false) + histories = makeHistories(10) + ) + // Index histories + for i, h := range histories { + if err := bw.process(h, uint64(i+1)); err != nil { + t.Fatalf("Failed to process history, %v", err) + } + } + if err := bw.finish(true); err != nil { + t.Fatalf("Failed to finish batch indexer, %v", err) + } + + // Unindex histories + bd := newBatchIndexer(db, true) + for i := len(histories) - 1; i >= 0; i-- { + if err := bd.process(histories[i], uint64(i+1)); err != nil { + t.Fatalf("Failed to process history, %v", err) + } + } + if err := bd.finish(true); err != nil { + t.Fatalf("Failed to finish batch indexer, %v", err) + } + + metadata := loadIndexMetadata(db) + if metadata != nil { + t.Fatal("Unexpected index position") + } + it := db.NewIterator(rawdb.StateHistoryIndexPrefix, nil) + for it.Next() { + t.Fatal("Leftover history index data") + } + it.Release() +} diff --git a/triedb/pathdb/history_indexer.go b/triedb/pathdb/history_indexer.go new file mode 100644 index 0000000000..b09804ce9d --- /dev/null +++ b/triedb/pathdb/history_indexer.go @@ -0,0 +1,613 @@ +// 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 = tailID { + log.Debug("Resume state history indexing", "id", metadata.Last+1, "tail", tailID) + return metadata.Last + 1, nil + } + // History has been shortened without indexing. Discard the gapped segment + // in the history and shift to the first available element. + // + // The missing indexes corresponding to the gapped histories won't be visible. + // It's fine to leave them unindexed. + log.Info("History gap detected, discard old segment", "oldHead", metadata.Last, "newHead", tailID) + return tailID, nil +} + +func (i *indexIniter) index(done chan struct{}, interrupt *atomic.Int32, lastID uint64) { + defer close(done) + + beginID, err := i.next() + if err != nil { + log.Error("Failed to find next state history for indexing", "err", err) + return + } + // All available state histories have been indexed, and the last indexed one + // exceeds the most recent available state history. This situation may occur + // when the state is reverted manually (chain.SetHead) or the deep reorg is + // encountered. In such cases, no indexing should be scheduled. + if beginID > lastID { + log.Debug("State history is fully indexed", "last", lastID) + return + } + log.Info("Start history indexing", "beginID", beginID, "lastID", lastID) + + var ( + current = beginID + start = time.Now() + logged = time.Now() + batch = newBatchIndexer(i.disk, false) + ) + for current <= lastID { + count := lastID - current + 1 + if count > historyReadBatch { + count = historyReadBatch + } + histories, err := readHistories(i.freezer, current, count) + if err != nil { + // The history read might fall if the history is truncated from + // head due to revert operation. + log.Error("Failed to read history for indexing", "current", current, "count", count, "err", err) + return + } + for _, h := range histories { + if err := batch.process(h, current); err != nil { + log.Error("Failed to index history", "err", err) + return + } + current += 1 + + // Occasionally report the indexing progress + if time.Since(logged) > time.Second*8 { + logged = time.Now() + + var ( + left = lastID - current + 1 + done = current - beginID + speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + ) + // Override the ETA if larger than the largest until now + eta := time.Duration(left/speed) * time.Millisecond + log.Info("Indexing state history", "processed", done, "left", left, "eta", common.PrettyDuration(eta)) + } + } + // Check interruption signal and abort process if it's fired + if interrupt != nil { + if signal := interrupt.Load(); signal != 0 { + if err := batch.finish(true); err != nil { + log.Error("Failed to flush index", "err", err) + } + log.Info("State indexing interrupted") + return + } + } + } + if err := batch.finish(true); err != nil { + log.Error("Failed to flush index", "err", err) + } + log.Info("Indexed state history", "from", beginID, "to", lastID, "elapsed", common.PrettyDuration(time.Since(start))) +} + +// historyIndexer manages the indexing and unindexing of state histories, +// providing access to historical states. +// +// Upon initialization, historyIndexer starts a one-time background process +// to complete the indexing of any remaining state histories. Once this +// process is finished, all state histories are marked as fully indexed, +// enabling handling of requests for historical states. Thereafter, any new +// state histories must be indexed or unindexed synchronously, ensuring that +// the history index is created or removed along with the corresponding +// state history. +type historyIndexer struct { + initer *indexIniter + disk ethdb.KeyValueStore + freezer ethdb.AncientStore +} + +// checkVersion checks whether the index data in the database matches the version. +func checkVersion(disk ethdb.KeyValueStore) { + blob := rawdb.ReadStateHistoryIndexMetadata(disk) + if len(blob) == 0 { + return + } + var m indexMetadata + err := rlp.DecodeBytes(blob, &m) + if err == nil && m.Version == stateIndexVersion { + return + } + // TODO(rjl493456442) would be better to group them into a batch. + rawdb.DeleteStateHistoryIndexMetadata(disk) + rawdb.DeleteStateHistoryIndex(disk) + + version := "unknown" + if err == nil { + version = fmt.Sprintf("%d", m.Version) + } + log.Info("Cleaned up obsolete state history index", "version", version, "want", stateIndexVersion) +} + +// newHistoryIndexer constructs the history indexer and launches the background +// initer to complete the indexing of any remaining state histories. +func newHistoryIndexer(disk ethdb.KeyValueStore, freezer ethdb.AncientStore, lastHistoryID uint64) *historyIndexer { + checkVersion(disk) + return &historyIndexer{ + initer: newIndexIniter(disk, freezer, lastHistoryID), + disk: disk, + freezer: freezer, + } +} + +func (i *historyIndexer) close() { + i.initer.close() +} + +// inited returns a flag indicating whether the existing state histories +// have been fully indexed, in other words, whether they are available +// for external access. +func (i *historyIndexer) inited() bool { + return i.initer.inited() +} + +// extend sends the notification that new state history with specified ID +// has been written into the database and is ready for indexing. +func (i *historyIndexer) extend(historyID uint64) error { + signal := &interruptSignal{ + newLastID: historyID, + result: make(chan error, 1), + } + select { + case <-i.initer.closed: + return errors.New("indexer is closed") + case <-i.initer.done: + return indexSingle(historyID, i.disk, i.freezer) + case i.initer.interrupt <- signal: + return <-signal.result + } +} + +// shorten sends the notification that state history with specified ID +// is about to be deleted from the database and should be unindexed. +func (i *historyIndexer) shorten(historyID uint64) error { + signal := &interruptSignal{ + newLastID: historyID - 1, + result: make(chan error, 1), + } + select { + case <-i.initer.closed: + return errors.New("indexer is closed") + case <-i.initer.done: + return unindexSingle(historyID, i.disk, i.freezer) + case i.initer.interrupt <- signal: + return <-signal.result + } +} diff --git a/triedb/pathdb/history_reader.go b/triedb/pathdb/history_reader.go new file mode 100644 index 0000000000..9471ab423d --- /dev/null +++ b/triedb/pathdb/history_reader.go @@ -0,0 +1,375 @@ +// 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 lastID { + return 0, fmt.Errorf("index reader is stale, limit: %d, last-state-id: %d", r.limit, lastID) + } + // Try to find the element which is greater than the specified target + res, err := r.reader.readGreaterThan(id) + if err != nil { + return 0, err + } + // Short circuit if the element is found within the current index + if res != math.MaxUint64 { + return res, nil + } + // The element was not found, and no additional histories have been indexed. + // Return a not-found result. + if r.limit == lastID { + return res, nil + } + // Refresh the index reader and attempt again. If the latest indexed position + // is even below the ID of the disk layer, it indicates that state histories + // are being removed. In this case, it would theoretically be better to block + // the state rollback operation synchronously until all readers are released. + // Given that it's very unlikely to occur and users try to perform historical + // state queries while reverting the states at the same time. Simply returning + // an error should be sufficient for now. + metadata := loadIndexMetadata(r.db) + if metadata == nil || metadata.Last < lastID { + return 0, errors.New("state history hasn't been indexed yet") + } + if err := r.reader.refresh(); err != nil { + return 0, err + } + r.limit = metadata.Last + + return r.reader.readGreaterThan(id) +} + +// historyReader is the structure to access historic state data. +type historyReader struct { + disk ethdb.KeyValueReader + freezer ethdb.AncientReader + readers map[string]*indexReaderWithLimitTag +} + +// newHistoryReader constructs the history reader with the supplied db. +func newHistoryReader(disk ethdb.KeyValueReader, freezer ethdb.AncientReader) *historyReader { + return &historyReader{ + disk: disk, + freezer: freezer, + readers: make(map[string]*indexReaderWithLimitTag), + } +} + +// readAccountMetadata resolves the account metadata within the specified +// state history. +func (r *historyReader) readAccountMetadata(address common.Address, historyID uint64) ([]byte, error) { + blob := rawdb.ReadStateAccountIndex(r.freezer, historyID) + if len(blob) == 0 { + return nil, fmt.Errorf("account index is truncated, historyID: %d", historyID) + } + if len(blob)%accountIndexSize != 0 { + return nil, fmt.Errorf("account index is corrupted, historyID: %d, size: %d", historyID, len(blob)) + } + n := len(blob) / accountIndexSize + + pos := sort.Search(n, func(i int) bool { + h := blob[accountIndexSize*i : accountIndexSize*i+common.HashLength] + return bytes.Compare(h, address.Bytes()) >= 0 + }) + if pos == n { + return nil, fmt.Errorf("account %#x is not found", address) + } + offset := accountIndexSize * pos + if address != common.BytesToAddress(blob[offset:offset+common.AddressLength]) { + return nil, fmt.Errorf("account %#x is not found", address) + } + return blob[offset : accountIndexSize*(pos+1)], nil +} + +// readStorageMetadata resolves the storage slot metadata within the specified +// state history. +func (r *historyReader) readStorageMetadata(storageKey common.Hash, storageHash common.Hash, historyID uint64, slotOffset, slotNumber int) ([]byte, error) { + // TODO(rj493456442) optimize it with partial read + blob := rawdb.ReadStateStorageIndex(r.freezer, historyID) + if len(blob) == 0 { + return nil, fmt.Errorf("storage index is truncated, historyID: %d", historyID) + } + if len(blob)%slotIndexSize != 0 { + return nil, fmt.Errorf("storage indices is corrupted, historyID: %d, size: %d", historyID, len(blob)) + } + if slotIndexSize*(slotOffset+slotNumber) > len(blob) { + return nil, fmt.Errorf("storage indices is truncated, historyID: %d, size: %d, offset: %d, length: %d", historyID, len(blob), slotOffset, slotNumber) + } + subSlice := blob[slotIndexSize*slotOffset : slotIndexSize*(slotOffset+slotNumber)] + + // TODO(rj493456442) get rid of the metadata resolution + var ( + m meta + target common.Hash + ) + blob = rawdb.ReadStateHistoryMeta(r.freezer, historyID) + if err := m.decode(blob); err != nil { + return nil, err + } + if m.version == stateHistoryV0 { + target = storageHash + } else { + target = storageKey + } + pos := sort.Search(slotNumber, func(i int) bool { + slotID := subSlice[slotIndexSize*i : slotIndexSize*i+common.HashLength] + return bytes.Compare(slotID, target.Bytes()) >= 0 + }) + if pos == slotNumber { + return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID) + } + offset := slotIndexSize * pos + if target != common.BytesToHash(subSlice[offset:offset+common.HashLength]) { + return nil, fmt.Errorf("storage metadata is not found, slot key: %#x, historyID: %d", storageKey, historyID) + } + return subSlice[offset : slotIndexSize*(pos+1)], nil +} + +// readAccount retrieves the account data from the specified state history. +func (r *historyReader) readAccount(address common.Address, historyID uint64) ([]byte, error) { + metadata, err := r.readAccountMetadata(address, historyID) + if err != nil { + return nil, err + } + length := int(metadata[common.AddressLength]) // one byte for account data length + offset := int(binary.BigEndian.Uint32(metadata[common.AddressLength+1 : common.AddressLength+5])) // four bytes for the account data offset + + // TODO(rj493456442) optimize it with partial read + data := rawdb.ReadStateAccountHistory(r.freezer, historyID) + if len(data) < length+offset { + return nil, fmt.Errorf("account data is truncated, address: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, historyID, len(data), offset, length) + } + return data[offset : offset+length], nil +} + +// readStorage retrieves the storage slot data from the specified state history. +func (r *historyReader) readStorage(address common.Address, storageKey common.Hash, storageHash common.Hash, historyID uint64) ([]byte, error) { + metadata, err := r.readAccountMetadata(address, historyID) + if err != nil { + return nil, err + } + // slotIndexOffset: + // The offset of storage indices associated with the specified account. + // slotIndexNumber: + // The number of storage indices associated with the specified account. + slotIndexOffset := int(binary.BigEndian.Uint32(metadata[common.AddressLength+5 : common.AddressLength+9])) + slotIndexNumber := int(binary.BigEndian.Uint32(metadata[common.AddressLength+9 : common.AddressLength+13])) + + slotMetadata, err := r.readStorageMetadata(storageKey, storageHash, historyID, slotIndexOffset, slotIndexNumber) + if err != nil { + return nil, err + } + length := int(slotMetadata[common.HashLength]) // one byte for slot data length + offset := int(binary.BigEndian.Uint32(slotMetadata[common.HashLength+1 : common.HashLength+5])) // four bytes for slot data offset + + // TODO(rj493456442) optimize it with partial read + data := rawdb.ReadStateStorageHistory(r.freezer, historyID) + if len(data) < offset+length { + return nil, fmt.Errorf("storage data is truncated, address: %#x, key: %#x, historyID: %d, size: %d, offset: %d, len: %d", address, storageKey, historyID, len(data), offset, length) + } + return data[offset : offset+length], nil +} + +// read retrieves the state element data associated with the stateID. +// stateID: represents the ID of the state of the specified version; +// lastID: represents the ID of the latest/newest state history; +// latestValue: represents the state value at the current disk layer with ID == lastID; +func (r *historyReader) read(state stateIdentQuery, stateID uint64, lastID uint64, latestValue []byte) ([]byte, error) { + tail, err := r.freezer.Tail() + if err != nil { + return nil, err + } + // stateID == tail is allowed, as the first history object preserved + // is tail+1 + if stateID < tail { + return nil, errors.New("historical state has been pruned") + } + + // To serve the request, all state histories from stateID+1 to lastID + // must be indexed. It's not supposed to happen unless system is very + // wrong. + metadata := loadIndexMetadata(r.disk) + if metadata == nil || metadata.Last < lastID { + indexed := "null" + if metadata != nil { + indexed = fmt.Sprintf("%d", metadata.Last) + } + return nil, fmt.Errorf("state history is not fully indexed, requested: %d, indexed: %s", stateID, indexed) + } + + // Construct the index reader to locate the corresponding history for + // state retrieval + ir, ok := r.readers[state.String()] + if !ok { + ir, err = newIndexReaderWithLimitTag(r.disk, state.stateIdent) + if err != nil { + return nil, err + } + r.readers[state.String()] = ir + } + historyID, err := ir.readGreaterThan(stateID, lastID) + if err != nil { + return nil, err + } + // The state was not found in the state histories, as it has not been modified + // since stateID. Use the data from the associated disk layer instead. + if historyID == math.MaxUint64 { + return latestValue, nil + } + // Resolve data from the specified state history object. Notably, since the history + // reader operates completely asynchronously with the indexer/unindexer, it's possible + // that the associated state histories are no longer available due to a rollback. + // Such truncation should be captured by the state resolver below, rather than returning + // invalid data. + if state.account { + return r.readAccount(state.address, historyID) + } + return r.readStorage(state.address, state.storageKey, state.storageHash, historyID) +} diff --git a/triedb/pathdb/history_reader_test.go b/triedb/pathdb/history_reader_test.go new file mode 100644 index 0000000000..4356490f23 --- /dev/null +++ b/triedb/pathdb/history_reader_test.go @@ -0,0 +1,159 @@ +// 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 = db.tree.bottom().stateID() { + return + } + time.Sleep(100 * time.Millisecond) + } +} + +func checkHistoricState(env *tester, root common.Hash, hr *historyReader) error { + // Short circuit if the historical state is no longer available + if rawdb.ReadStateID(env.db.diskdb, root) == nil { + return nil + } + var ( + dl = env.db.tree.bottom() + stateID = rawdb.ReadStateID(env.db.diskdb, root) + accounts = env.snapAccounts[root] + storages = env.snapStorages[root] + ) + for addrHash, accountData := range accounts { + latest, _ := dl.account(addrHash, 0) + blob, err := hr.read(newAccountIdentQuery(env.accountPreimage(addrHash), addrHash), *stateID, dl.stateID(), latest) + if err != nil { + return err + } + if !bytes.Equal(accountData, blob) { + return fmt.Errorf("wrong account data, expected %x, got %x", accountData, blob) + } + } + for i := 0; i < len(env.roots); i++ { + if env.roots[i] == root { + break + } + // Find all accounts deleted in the past, ensure the associated data is null + for addrHash := range env.snapAccounts[env.roots[i]] { + if _, ok := accounts[addrHash]; !ok { + latest, _ := dl.account(addrHash, 0) + blob, err := hr.read(newAccountIdentQuery(env.accountPreimage(addrHash), addrHash), *stateID, dl.stateID(), latest) + if err != nil { + return err + } + if len(blob) != 0 { + return fmt.Errorf("wrong account data, expected null, got %x", blob) + } + } + } + } + for addrHash, slots := range storages { + for slotHash, slotData := range slots { + latest, _ := dl.storage(addrHash, slotHash, 0) + blob, err := hr.read(newStorageIdentQuery(env.accountPreimage(addrHash), addrHash, env.hashPreimage(slotHash), slotHash), *stateID, dl.stateID(), latest) + if err != nil { + return err + } + if !bytes.Equal(slotData, blob) { + return fmt.Errorf("wrong storage data, expected %x, got %x", slotData, blob) + } + } + } + for i := 0; i < len(env.roots); i++ { + if env.roots[i] == root { + break + } + // Find all storage slots deleted in the past, ensure the associated data is null + for addrHash, slots := range env.snapStorages[env.roots[i]] { + for slotHash := range slots { + _, ok := storages[addrHash] + if ok { + _, ok = storages[addrHash][slotHash] + } + if !ok { + latest, _ := dl.storage(addrHash, slotHash, 0) + blob, err := hr.read(newStorageIdentQuery(env.accountPreimage(addrHash), addrHash, env.hashPreimage(slotHash), slotHash), *stateID, dl.stateID(), latest) + if err != nil { + return err + } + if len(blob) != 0 { + return fmt.Errorf("wrong storage data, expected null, got %x", blob) + } + } + } + } + } + return nil +} + +func TestHistoryReader(t *testing.T) { + testHistoryReader(t, 0) // with all histories reserved + testHistoryReader(t, 10) // with latest 10 histories reserved +} + +func testHistoryReader(t *testing.T, historyLimit uint64) { + maxDiffLayers = 4 + defer func() { + maxDiffLayers = 128 + }() + //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelDebug, true))) + + env := newTester(t, historyLimit, false, 64, true) + defer env.release() + waitIndexing(env.db) + + var ( + roots = env.roots + dRoot = env.db.tree.bottom().rootHash() + hr = newHistoryReader(env.db.diskdb, env.db.freezer) + ) + for _, root := range roots { + if root == dRoot { + break + } + if err := checkHistoricState(env, root, hr); err != nil { + t.Fatal(err) + } + } + + // Pile up more histories on top, ensuring the historic reader is not affected + env.extend(4) + waitIndexing(env.db) + + for _, root := range roots { + if root == dRoot { + break + } + if err := checkHistoricState(env, root, hr); err != nil { + t.Fatal(err) + } + } +} diff --git a/triedb/pathdb/metrics.go b/triedb/pathdb/metrics.go index 6d40c5713b..779f9d813f 100644 --- a/triedb/pathdb/metrics.go +++ b/triedb/pathdb/metrics.go @@ -73,8 +73,14 @@ var ( historyDataBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/data", nil) historyIndexBytesMeter = metrics.NewRegisteredMeter("pathdb/history/bytes/index", nil) + indexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/index/time", nil) + unindexHistoryTimer = metrics.NewRegisteredResettingTimer("pathdb/history/unindex/time", nil) + lookupAddLayerTimer = metrics.NewRegisteredResettingTimer("pathdb/lookup/add/time", nil) lookupRemoveLayerTimer = metrics.NewRegisteredResettingTimer("pathdb/lookup/remove/time", nil) + + historicalAccountReadTimer = metrics.NewRegisteredResettingTimer("pathdb/history/account/reads", nil) + historicalStorageReadTimer = metrics.NewRegisteredResettingTimer("pathdb/history/storage/reads", nil) ) // Metrics in generation diff --git a/triedb/pathdb/reader.go b/triedb/pathdb/reader.go index bc72db34e3..b7b18f13f9 100644 --- a/triedb/pathdb/reader.go +++ b/triedb/pathdb/reader.go @@ -19,10 +19,13 @@ package pathdb import ( "errors" "fmt" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/triedb/database" @@ -192,3 +195,122 @@ func (db *Database) StateReader(root common.Hash) (database.StateReader, error) layer: layer, }, nil } + +// HistoricalStateReader is a wrapper over history reader, providing access to +// historical state. +type HistoricalStateReader struct { + db *Database + reader *historyReader + id uint64 +} + +// HistoricReader constructs a reader for accessing the requested historic state. +func (db *Database) HistoricReader(root common.Hash) (*HistoricalStateReader, error) { + // Bail out if the state history hasn't been fully indexed + if db.indexer == nil || !db.indexer.inited() { + return nil, errors.New("state histories haven't been fully indexed yet") + } + if db.freezer == nil { + return nil, errors.New("state histories are not available") + } + // States at the current disk layer or above are directly accessible via + // db.StateReader. + // + // States older than the current disk layer (including the disk layer + // itself) are available through historic state access. + // + // Note: the requested state may refer to a stale historic state that has + // already been pruned. This function does not validate availability, as + // underlying states may be pruned dynamically. Validity is checked during + // each actual state retrieval. + id := rawdb.ReadStateID(db.diskdb, root) + if id == nil { + return nil, fmt.Errorf("state %#x is not available", root) + } + return &HistoricalStateReader{ + id: *id, + db: db, + reader: newHistoryReader(db.diskdb, db.freezer), + }, nil +} + +// AccountRLP directly retrieves the account RLP associated with a particular +// address in the slim data format. An error will be returned if the read +// operation exits abnormally. Specifically, if the layer is already stale. +// +// Note: +// - the returned account is not a copy, please don't modify it. +// - no error will be returned if the requested account is not found in database. +func (r *HistoricalStateReader) AccountRLP(address common.Address) ([]byte, error) { + defer func(start time.Time) { + historicalAccountReadTimer.UpdateSince(start) + }(time.Now()) + + // TODO(rjl493456442): Theoretically, the obtained disk layer could become stale + // within a very short time window. + // + // While reading the account data while holding `db.tree.lock` can resolve + // this issue, but it will introduce a heavy contention over the lock. + // + // Let's optimistically assume the situation is very unlikely to happen, + // and try to define a low granularity lock if the current approach doesn't + // work later. + dl := r.db.tree.bottom() + hash := crypto.Keccak256Hash(address.Bytes()) + latest, err := dl.account(hash, 0) + if err != nil { + return nil, err + } + return r.reader.read(newAccountIdentQuery(address, hash), r.id, dl.stateID(), latest) +} + +// Account directly retrieves the account associated with a particular address in +// the slim data format. An error will be returned if the read operation exits +// abnormally. Specifically, if the layer is already stale. +// +// No error will be returned if the requested account is not found in database +func (r *HistoricalStateReader) Account(address common.Address) (*types.SlimAccount, error) { + blob, err := r.AccountRLP(address) + if err != nil { + return nil, err + } + if len(blob) == 0 { + return nil, nil + } + account := new(types.SlimAccount) + if err := rlp.DecodeBytes(blob, account); err != nil { + panic(err) + } + return account, nil +} + +// Storage directly retrieves the storage data associated with a particular key, +// within a particular account. An error will be returned if the read operation +// exits abnormally. Specifically, if the layer is already stale. +// +// Note: +// - the returned storage data is not a copy, please don't modify it. +// - no error will be returned if the requested slot is not found in database. +func (r *HistoricalStateReader) Storage(address common.Address, key common.Hash) ([]byte, error) { + defer func(start time.Time) { + historicalStorageReadTimer.UpdateSince(start) + }(time.Now()) + + // TODO(rjl493456442): Theoretically, the obtained disk layer could become stale + // within a very short time window. + // + // While reading the account data while holding `db.tree.lock` can resolve + // this issue, but it will introduce a heavy contention over the lock. + // + // Let's optimistically assume the situation is very unlikely to happen, + // and try to define a low granularity lock if the current approach doesn't + // work later. + dl := r.db.tree.bottom() + addrHash := crypto.Keccak256Hash(address.Bytes()) + keyHash := crypto.Keccak256Hash(key.Bytes()) + latest, err := dl.storage(addrHash, keyHash, 0) + if err != nil { + return nil, err + } + return r.reader.read(newStorageIdentQuery(address, addrHash, key, keyHash), r.id, dl.stateID(), latest) +} From 6dd38d239d5b7582ea8f5551f55602d74d8f8f7f Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Tue, 24 Jun 2025 21:06:20 +0800 Subject: [PATCH 09/11] cmd/geth, triedb: add pathdb state verification (#32086) This pull request ports the snapshot iteration logic from the legacy implementation. --- cmd/geth/snapshot.go | 52 +++--- triedb/database.go | 10 ++ triedb/pathdb/verifier.go | 355 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 396 insertions(+), 21 deletions(-) create mode 100644 triedb/pathdb/verifier.go diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index f0be52a0df..aa9ae7087f 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -220,22 +220,10 @@ func verifyState(ctx *cli.Context) error { triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false) defer triedb.Close() - snapConfig := snapshot.Config{ - CacheSize: 256, - Recovery: false, - NoBuild: true, - AsyncBuild: false, - } - snaptree, err := snapshot.New(snapConfig, chaindb, triedb, headBlock.Root()) - if err != nil { - log.Error("Failed to open snapshot tree", "err", err) - return err - } - if ctx.NArg() > 1 { - log.Error("Too many arguments given") - return errors.New("too many arguments") - } - var root = headBlock.Root() + var ( + err error + root = headBlock.Root() + ) if ctx.NArg() == 1 { root, err = parseRoot(ctx.Args().First()) if err != nil { @@ -243,12 +231,34 @@ func verifyState(ctx *cli.Context) error { return err } } - if err := snaptree.Verify(root); err != nil { - log.Error("Failed to verify state", "root", root, "err", err) - return err + if triedb.Scheme() == rawdb.PathScheme { + if err := triedb.VerifyState(root); err != nil { + log.Error("Failed to verify state", "root", root, "err", err) + return err + } + log.Info("Verified the state", "root", root) + + // TODO(rjl493456442) implement dangling checks in pathdb. + return nil + } else { + snapConfig := snapshot.Config{ + CacheSize: 256, + Recovery: false, + NoBuild: true, + AsyncBuild: false, + } + snaptree, err := snapshot.New(snapConfig, chaindb, triedb, headBlock.Root()) + if err != nil { + log.Error("Failed to open snapshot tree", "err", err) + return err + } + if err := snaptree.Verify(root); err != nil { + log.Error("Failed to verify state", "root", root, "err", err) + return err + } + log.Info("Verified the state", "root", root) + return snapshot.CheckDanglingStorage(chaindb) } - log.Info("Verified the state", "root", root) - return snapshot.CheckDanglingStorage(chaindb) } // checkDanglingStorage iterates the snap storage data, and verifies that all diff --git a/triedb/database.go b/triedb/database.go index 1c9d49b245..03d07c1c1b 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -317,6 +317,16 @@ func (db *Database) Journal(root common.Hash) error { return pdb.Journal(root) } +// VerifyState traverses the flat states specified by the given state root and +// ensures they are matched with each other. +func (db *Database) VerifyState(root common.Hash) error { + pdb, ok := db.backend.(*pathdb.Database) + if !ok { + return errors.New("not supported") + } + return pdb.VerifyState(root) +} + // AccountIterator creates a new account iterator for the specified root hash and // seeks to a starting account hash. func (db *Database) AccountIterator(root common.Hash, seek common.Hash) (pathdb.AccountIterator, error) { diff --git a/triedb/pathdb/verifier.go b/triedb/pathdb/verifier.go new file mode 100644 index 0000000000..2d6f72925b --- /dev/null +++ b/triedb/pathdb/verifier.go @@ -0,0 +1,355 @@ +// Copyright 2020 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 pathdb + +import ( + "encoding/binary" + "errors" + "fmt" + "math" + "runtime" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +// trieKV represents a trie key-value pair +type trieKV struct { + key common.Hash + value []byte +} + +type ( + // trieHasherFn is the interface of trie hasher which can be implemented + // by different trie algorithm. + trieHasherFn func(in chan trieKV, out chan common.Hash) + + // leafCallbackFn is the callback invoked at the leaves of the trie, + // returns the subtrie root with the specified subtrie identifier. + leafCallbackFn func(accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) +) + +// VerifyState traverses the flat states specified by the given state root and +// ensures they are matched with each other. +func (db *Database) VerifyState(root common.Hash) error { + acctIt, err := db.AccountIterator(root, common.Hash{}) + if err != nil { + return err // The required snapshot might not exist. + } + defer acctIt.Release() + + got, err := generateTrieRoot(acctIt, common.Hash{}, stackTrieHasher, func(accountHash, codeHash common.Hash, stat *generateStats) (common.Hash, error) { + // Migrate the code first, commit the contract code into the tmp db. + if codeHash != types.EmptyCodeHash { + code := rawdb.ReadCode(db.diskdb, codeHash) + if len(code) == 0 { + return common.Hash{}, errors.New("failed to read contract code") + } + } + // Then migrate all storage trie nodes into the tmp db. + storageIt, err := db.StorageIterator(root, accountHash, common.Hash{}) + if err != nil { + return common.Hash{}, err + } + defer storageIt.Release() + + hash, err := generateTrieRoot(storageIt, accountHash, stackTrieHasher, nil, stat, false) + if err != nil { + return common.Hash{}, err + } + return hash, nil + }, newGenerateStats(), true) + + if err != nil { + return err + } + if got != root { + return fmt.Errorf("state root hash mismatch: got %x, want %x", got, root) + } + return nil +} + +// generateStats is a collection of statistics gathered by the trie generator +// for logging purposes. +type generateStats struct { + head common.Hash + start time.Time + + accounts uint64 // Number of accounts done (including those being crawled) + slots uint64 // Number of storage slots done (including those being crawled) + + slotsStart map[common.Hash]time.Time // Start time for account slot crawling + slotsHead map[common.Hash]common.Hash // Slot head for accounts being crawled + + lock sync.RWMutex +} + +// newGenerateStats creates a new generator stats. +func newGenerateStats() *generateStats { + return &generateStats{ + slotsStart: make(map[common.Hash]time.Time), + slotsHead: make(map[common.Hash]common.Hash), + start: time.Now(), + } +} + +// progressAccounts updates the generator stats for the account range. +func (stat *generateStats) progressAccounts(account common.Hash, done uint64) { + stat.lock.Lock() + defer stat.lock.Unlock() + + stat.accounts += done + stat.head = account +} + +// finishAccounts updates the generator stats for the finished account range. +func (stat *generateStats) finishAccounts(done uint64) { + stat.lock.Lock() + defer stat.lock.Unlock() + + stat.accounts += done +} + +// progressContract updates the generator stats for a specific in-progress contract. +func (stat *generateStats) progressContract(account common.Hash, slot common.Hash, done uint64) { + stat.lock.Lock() + defer stat.lock.Unlock() + + stat.slots += done + stat.slotsHead[account] = slot + if _, ok := stat.slotsStart[account]; !ok { + stat.slotsStart[account] = time.Now() + } +} + +// finishContract updates the generator stats for a specific just-finished contract. +func (stat *generateStats) finishContract(account common.Hash, done uint64) { + stat.lock.Lock() + defer stat.lock.Unlock() + + stat.slots += done + delete(stat.slotsHead, account) + delete(stat.slotsStart, account) +} + +// report prints the cumulative progress statistic smartly. +func (stat *generateStats) report() { + stat.lock.RLock() + defer stat.lock.RUnlock() + + ctx := []interface{}{ + "accounts", stat.accounts, + "slots", stat.slots, + "elapsed", common.PrettyDuration(time.Since(stat.start)), + } + if stat.accounts > 0 { + // If there's progress on the account trie, estimate the time to finish crawling it + if done := binary.BigEndian.Uint64(stat.head[:8]) / stat.accounts; done > 0 { + var ( + left = (math.MaxUint64 - binary.BigEndian.Uint64(stat.head[:8])) / stat.accounts + speed = done/uint64(time.Since(stat.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + eta = time.Duration(left/speed) * time.Millisecond + ) + // If there are large contract crawls in progress, estimate their finish time + for acc, head := range stat.slotsHead { + start := stat.slotsStart[acc] + if done := binary.BigEndian.Uint64(head[:8]); done > 0 { + var ( + left = math.MaxUint64 - binary.BigEndian.Uint64(head[:8]) + speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + ) + // Override the ETA if larger than the largest until now + if slotETA := time.Duration(left/speed) * time.Millisecond; eta < slotETA { + eta = slotETA + } + } + } + ctx = append(ctx, []interface{}{ + "eta", common.PrettyDuration(eta), + }...) + } + } + log.Info("Iterating state snapshot", ctx...) +} + +// reportDone prints the last log when the whole generation is finished. +func (stat *generateStats) reportDone() { + stat.lock.RLock() + defer stat.lock.RUnlock() + + var ctx []interface{} + ctx = append(ctx, []interface{}{"accounts", stat.accounts}...) + if stat.slots != 0 { + ctx = append(ctx, []interface{}{"slots", stat.slots}...) + } + ctx = append(ctx, []interface{}{"elapsed", common.PrettyDuration(time.Since(stat.start))}...) + log.Info("Iterated snapshot", ctx...) +} + +// runReport periodically prints the progress information. +func runReport(stats *generateStats, stop chan bool) { + timer := time.NewTimer(0) + defer timer.Stop() + + for { + select { + case <-timer.C: + stats.report() + timer.Reset(time.Second * 8) + case success := <-stop: + if success { + stats.reportDone() + } + return + } + } +} + +// generateTrieRoot generates the trie hash based on the snapshot iterator. +// It can be used for generating account trie, storage trie or even the +// whole state which connects the accounts and the corresponding storages. +func generateTrieRoot(it Iterator, account common.Hash, generatorFn trieHasherFn, leafCallback leafCallbackFn, stats *generateStats, report bool) (common.Hash, error) { + var ( + in = make(chan trieKV) // chan to pass leaves + out = make(chan common.Hash, 1) // chan to collect result + stoplog = make(chan bool, 1) // 1-size buffer, works when logging is not enabled + wg sync.WaitGroup + ) + // Spin up a go-routine for trie hash re-generation + wg.Add(1) + go func() { + defer wg.Done() + generatorFn(in, out) + }() + // Spin up a go-routine for progress logging + if report && stats != nil { + wg.Add(1) + go func() { + defer wg.Done() + runReport(stats, stoplog) + }() + } + // Create a semaphore to assign tasks and collect results through. We'll pre- + // fill it with nils, thus using the same channel for both limiting concurrent + // processing and gathering results. + threads := runtime.NumCPU() + results := make(chan error, threads) + for i := 0; i < threads; i++ { + results <- nil // fill the semaphore + } + // stop is a helper function to shutdown the background threads + // and return the re-generated trie hash. + stop := func(fail error) (common.Hash, error) { + close(in) + result := <-out + for i := 0; i < threads; i++ { + if err := <-results; err != nil && fail == nil { + fail = err + } + } + stoplog <- fail == nil + + wg.Wait() + return result, fail + } + var ( + logged = time.Now() + processed = uint64(0) + leaf trieKV + ) + // Start to feed leaves + for it.Next() { + if account == (common.Hash{}) { + var ( + err error + fullData []byte + ) + if leafCallback == nil { + fullData, err = types.FullAccountRLP(it.(AccountIterator).Account()) + if err != nil { + return stop(err) + } + } else { + // Wait until the semaphore allows us to continue, aborting if + // a sub-task failed + if err := <-results; err != nil { + results <- nil // stop will drain the results, add a noop back for this error we just consumed + return stop(err) + } + // Fetch the next account and process it concurrently + account, err := types.FullAccount(it.(AccountIterator).Account()) + if err != nil { + return stop(err) + } + go func(hash common.Hash) { + subroot, err := leafCallback(hash, common.BytesToHash(account.CodeHash), stats) + if err != nil { + results <- err + return + } + if account.Root != subroot { + results <- fmt.Errorf("invalid subroot(path %x), want %x, have %x", hash, account.Root, subroot) + return + } + results <- nil + }(it.Hash()) + fullData, err = rlp.EncodeToBytes(account) + if err != nil { + return stop(err) + } + } + leaf = trieKV{it.Hash(), fullData} + } else { + leaf = trieKV{it.Hash(), common.CopyBytes(it.(StorageIterator).Slot())} + } + in <- leaf + + // Accumulate the generation statistic if it's required. + processed++ + if time.Since(logged) > 3*time.Second && stats != nil { + if account == (common.Hash{}) { + stats.progressAccounts(it.Hash(), processed) + } else { + stats.progressContract(account, it.Hash(), processed) + } + logged, processed = time.Now(), 0 + } + } + // Commit the last part statistic. + if processed > 0 && stats != nil { + if account == (common.Hash{}) { + stats.finishAccounts(processed) + } else { + stats.finishContract(account, processed) + } + } + return stop(nil) +} + +func stackTrieHasher(in chan trieKV, out chan common.Hash) { + t := trie.NewStackTrie(nil) + for leaf := range in { + t.Update(leaf.key[:], leaf.value) + } + out <- t.Hash() +} From cafa5e6c1263d63b6b13040c0fa100acd97364d7 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Wed, 25 Jun 2025 09:42:11 +0800 Subject: [PATCH 10/11] core, consensus/beacon: defer trie resolution (#31725) Previously, the account trie for a given state root was resolved immediately when the stateDB was created, implying that the trie was always required by the stateDB. However, this assumption no longer holds, especially for path archive nodes, where historical states can be accessed even if the corresponding trie data does not exist. --- consensus/beacon/consensus.go | 6 +++++- core/chain_makers.go | 2 +- core/state/dump.go | 23 ++++++++++++++++------- core/state/iterator.go | 20 ++++++++++++++------ core/state/state_object.go | 1 + core/state/state_test.go | 4 ---- core/state/statedb.go | 25 ++++++++++++++++++------- core/state/statedb_test.go | 11 +---------- core/state_processor.go | 3 +-- 9 files changed, 57 insertions(+), 38 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index f9a5a3233b..196cbc857c 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -391,8 +391,12 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea if err != nil { return nil, fmt.Errorf("error opening pre-state tree root: %w", err) } + postTrie := state.GetTrie() + if postTrie == nil { + return nil, errors.New("post-state tree is not available") + } vktPreTrie, okpre := preTrie.(*trie.VerkleTrie) - vktPostTrie, okpost := state.GetTrie().(*trie.VerkleTrie) + vktPostTrie, okpost := postTrie.(*trie.VerkleTrie) // The witness is only attached iff both parent and current block are // using verkle tree. diff --git a/core/chain_makers.go b/core/chain_makers.go index fe2e25d2d7..b2559495a1 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -124,7 +124,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti } // Merge the tx-local access event into the "block-local" one, in order to collect // all values, so that the witness can be built. - if b.statedb.GetTrie().IsVerkle() { + if b.statedb.Database().TrieDB().IsVerkle() { b.statedb.AccessEvents().Merge(evm.AccessEvents) } b.txs = append(b.txs, tx) diff --git a/core/state/dump.go b/core/state/dump.go index 3af9133f5b..a4abc33733 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -112,6 +112,9 @@ func (d iterativeDump) OnRoot(root common.Hash) { // DumpToCollector iterates the state according to the given options and inserts // the items into a collector for aggregation or serialization. +// +// The state iterator is still trie-based and can be converted to snapshot-based +// once the state snapshot is fully integrated into database. TODO(rjl493456442). func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) { // Sanitize the input to allow nil configs if conf == nil { @@ -123,15 +126,20 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] start = time.Now() logged = time.Now() ) - log.Info("Trie dumping started", "root", s.trie.Hash()) - c.OnRoot(s.trie.Hash()) + log.Info("Trie dumping started", "root", s.originalRoot) + c.OnRoot(s.originalRoot) - trieIt, err := s.trie.NodeIterator(conf.Start) + tr, err := s.db.OpenTrie(s.originalRoot) + if err != nil { + return nil + } + trieIt, err := tr.NodeIterator(conf.Start) if err != nil { log.Error("Trie dumping error", "err", err) return nil } it := trie.NewIterator(trieIt) + for it.Next() { var data types.StateAccount if err := rlp.DecodeBytes(it.Value, &data); err != nil { @@ -147,7 +155,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] } address *common.Address addr common.Address - addrBytes = s.trie.GetKey(it.Key) + addrBytes = tr.GetKey(it.Key) ) if addrBytes == nil { missingPreimages++ @@ -165,12 +173,13 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] } if !conf.SkipStorage { account.Storage = make(map[common.Hash]string) - tr, err := obj.getTrie() + + storageTr, err := s.db.OpenStorageTrie(s.originalRoot, addr, obj.Root(), tr) if err != nil { log.Error("Failed to load storage trie", "err", err) continue } - trieIt, err := tr.NodeIterator(nil) + trieIt, err := storageTr.NodeIterator(nil) if err != nil { log.Error("Failed to create trie iterator", "err", err) continue @@ -182,7 +191,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] log.Error("Failed to decode the value returned by iterator", "error", err) continue } - key := s.trie.GetKey(storageIt.Key) + key := storageTr.GetKey(storageIt.Key) if key == nil { continue } diff --git a/core/state/iterator.go b/core/state/iterator.go index 5ea52c6183..0abae091d9 100644 --- a/core/state/iterator.go +++ b/core/state/iterator.go @@ -32,6 +32,7 @@ import ( // required in order to resolve the contract address. type nodeIterator struct { state *StateDB // State being iterated + tr Trie // Primary account trie for traversal stateIt trie.NodeIterator // Primary iterator for the global state trie dataIt trie.NodeIterator // Secondary iterator for the data trie of a contract @@ -75,13 +76,20 @@ func (it *nodeIterator) step() error { if it.state == nil { return nil } - // Initialize the iterator if we've just started - var err error - if it.stateIt == nil { - it.stateIt, err = it.state.trie.NodeIterator(nil) + if it.tr == nil { + tr, err := it.state.db.OpenTrie(it.state.originalRoot) if err != nil { return err } + it.tr = tr + } + // Initialize the iterator if we've just started + if it.stateIt == nil { + stateIt, err := it.tr.NodeIterator(nil) + if err != nil { + return err + } + it.stateIt = stateIt } // If we had data nodes previously, we surely have at least state nodes if it.dataIt != nil { @@ -116,14 +124,14 @@ func (it *nodeIterator) step() error { return err } // Lookup the preimage of account hash - preimage := it.state.trie.GetKey(it.stateIt.LeafKey()) + preimage := it.tr.GetKey(it.stateIt.LeafKey()) if preimage == nil { return errors.New("account address is not available") } address := common.BytesToAddress(preimage) // Traverse the storage slots belong to the account - dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, address, account.Root, it.state.trie) + dataTrie, err := it.state.db.OpenStorageTrie(it.state.originalRoot, address, account.Root, it.tr) if err != nil { return err } diff --git a/core/state/state_object.go b/core/state/state_object.go index 73a3b5146c..767f469bfd 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -124,6 +124,7 @@ func (s *stateObject) touch() { // subsequent reads to expand the same trie instead of reloading from disk. func (s *stateObject) getTrie() (Trie, error) { if s.trie == nil { + // Assumes the primary account trie is already loaded tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie) if err != nil { return nil, err diff --git a/core/state/state_test.go b/core/state/state_test.go index b443411f1b..eeeb7fa2df 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -54,8 +54,6 @@ func TestDump(t *testing.T) { obj3.SetBalance(uint256.NewInt(44)) // write some of them to the trie - s.state.updateStateObject(obj1) - s.state.updateStateObject(obj2) root, _ := s.state.Commit(0, false, false) // check that DumpToCollector contains the state objects that are in trie @@ -114,8 +112,6 @@ func TestIterativeDump(t *testing.T) { obj4.AddBalance(uint256.NewInt(1337)) // write some of them to the trie - s.state.updateStateObject(obj1) - s.state.updateStateObject(obj2) root, _ := s.state.Commit(0, false, false) s.state, _ = New(root, tdb) diff --git a/core/state/statedb.go b/core/state/statedb.go index 6b474ea0a4..0d76e8c61c 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -79,8 +79,8 @@ func (m *mutation) isDelete() bool { type StateDB struct { db Database prefetcher *triePrefetcher - trie Trie reader Reader + trie Trie // it's resolved on first access // originalRoot is the pre-state root, before any changes were made. // It will be updated when the Commit is called. @@ -169,13 +169,8 @@ func New(root common.Hash, db Database) (*StateDB, error) { // NewWithReader creates a new state for the specified state root. Unlike New, // this function accepts an additional Reader which is bound to the given root. func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, error) { - tr, err := db.OpenTrie(root) - if err != nil { - return nil, err - } sdb := &StateDB{ db: db, - trie: tr, originalRoot: root, reader: reader, stateObjects: make(map[common.Address]*stateObject), @@ -664,7 +659,6 @@ func (s *StateDB) Copy() *StateDB { // Copy all the basic fields, initialize the memory ones state := &StateDB{ db: s.db, - trie: mustCopyTrie(s.trie), reader: s.reader, originalRoot: s.originalRoot, stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)), @@ -688,6 +682,9 @@ func (s *StateDB) Copy() *StateDB { transientStorage: s.transientStorage.Copy(), journal: s.journal.copy(), } + if s.trie != nil { + state.trie = mustCopyTrie(s.trie) + } if s.witness != nil { state.witness = s.witness.Copy() } @@ -783,6 +780,20 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // Finalise all the dirty storage states and write them into the tries s.Finalise(deleteEmptyObjects) + // Initialize the trie if it's not constructed yet. If the prefetch + // is enabled, the trie constructed below will be replaced by the + // prefetched one. + // + // This operation must be done before state object storage hashing, + // as it assumes the main trie is already loaded. + if s.trie == nil { + tr, err := s.db.OpenTrie(s.originalRoot) + if err != nil { + s.setError(err) + return common.Hash{} + } + s.trie = tr + } // If there was a trie prefetcher operating, terminate it async so that the // individual storage tries can be updated as soon as the disk load finishes. if s.prefetcher != nil { diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 061ac59752..67e27cc832 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -171,7 +171,6 @@ func TestCopy(t *testing.T) { for i := byte(0); i < 255; i++ { obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) obj.AddBalance(uint256.NewInt(uint64(i))) - orig.updateStateObject(obj) } orig.Finalise(false) @@ -190,10 +189,6 @@ func TestCopy(t *testing.T) { origObj.AddBalance(uint256.NewInt(2 * uint64(i))) copyObj.AddBalance(uint256.NewInt(3 * uint64(i))) ccopyObj.AddBalance(uint256.NewInt(4 * uint64(i))) - - orig.updateStateObject(origObj) - copy.updateStateObject(copyObj) - ccopy.updateStateObject(copyObj) } // Finalise the changes on all concurrently @@ -238,7 +233,6 @@ func TestCopyWithDirtyJournal(t *testing.T) { obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) obj.AddBalance(uint256.NewInt(uint64(i))) obj.data.Root = common.HexToHash("0xdeadbeef") - orig.updateStateObject(obj) } root, _ := orig.Commit(0, true, false) orig, _ = New(root, db) @@ -248,8 +242,6 @@ func TestCopyWithDirtyJournal(t *testing.T) { obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) amount := uint256.NewInt(uint64(i)) obj.SetBalance(new(uint256.Int).Sub(obj.Balance(), amount)) - - orig.updateStateObject(obj) } cpy := orig.Copy() @@ -284,7 +276,6 @@ func TestCopyObjectState(t *testing.T) { obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) obj.AddBalance(uint256.NewInt(uint64(i))) obj.data.Root = common.HexToHash("0xdeadbeef") - orig.updateStateObject(obj) } orig.Finalise(true) cpy := orig.Copy() @@ -573,7 +564,7 @@ func forEachStorage(s *StateDB, addr common.Address, cb func(key, value common.H ) for it.Next() { - key := common.BytesToHash(s.trie.GetKey(it.Key)) + key := common.BytesToHash(tr.GetKey(it.Key)) visited[key] = true if value, dirty := so.dirtyStorage[key]; dirty { if !cb(key, value) { diff --git a/core/state_processor.go b/core/state_processor.go index b778befd77..ee98326467 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -161,10 +161,9 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, // Merge the tx-local access event into the "block-local" one, in order to collect // all values, so that the witness can be built. - if statedb.GetTrie().IsVerkle() { + if statedb.Database().TrieDB().IsVerkle() { statedb.AccessEvents().Merge(evm.AccessEvents) } - return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, *usedGas, root), nil } From 429e821fa2cbd094ee81e18bc43228693e4384c2 Mon Sep 17 00:00:00 2001 From: lightclient <14004106+lightclient@users.noreply.github.com> Date: Wed, 25 Jun 2025 04:42:45 +0200 Subject: [PATCH 11/11] cmd/utils: fix formatting for beacon flag errors to fit Fatalf form (#32090) Noticed that the errors for the blsync flags were not formatted correctly for `Fatalf(..)`. --- cmd/utils/flags.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index ba98ec4169..3747e2c70b 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1891,11 +1891,11 @@ func MakeBeaconLightConfig(ctx *cli.Context) bparams.ClientConfig { if c, err := hexutil.Decode(ctx.String(BeaconGenesisRootFlag.Name)); err == nil && len(c) <= 32 { copy(config.GenesisValidatorsRoot[:len(c)], c) } else { - Fatalf("Invalid hex string", "beacon.genesis.gvroot", ctx.String(BeaconGenesisRootFlag.Name), "error", err) + Fatalf("Could not parse --%s: %v", BeaconGenesisRootFlag.Name, err) } configFile := ctx.String(BeaconConfigFlag.Name) if err := config.ChainConfig.LoadForks(configFile); err != nil { - Fatalf("Could not load beacon chain config", "file", configFile, "error", err) + Fatalf("Could not load beacon chain config '%s': %v", configFile, err) } log.Info("Using custom beacon chain config", "file", configFile) } else { @@ -1912,17 +1912,17 @@ func MakeBeaconLightConfig(ctx *cli.Context) bparams.ClientConfig { // are saved to the specified file. if ctx.IsSet(BeaconCheckpointFileFlag.Name) { if _, err := config.SetCheckpointFile(ctx.String(BeaconCheckpointFileFlag.Name)); err != nil { - Fatalf("Could not load beacon checkpoint file", "beacon.checkpoint.file", ctx.String(BeaconCheckpointFileFlag.Name), "error", err) + Fatalf("Could not load beacon checkpoint file '%s': %v", ctx.String(BeaconCheckpointFileFlag.Name), err) } } if ctx.IsSet(BeaconCheckpointFlag.Name) { hex := ctx.String(BeaconCheckpointFlag.Name) c, err := hexutil.Decode(hex) if err != nil { - Fatalf("Invalid hex string", "beacon.checkpoint", hex, "error", err) + Fatalf("Could not parse --%s: %v", BeaconCheckpointFlag.Name, err) } if len(c) != 32 { - Fatalf("Invalid hex string length", "beacon.checkpoint", hex, "length", len(c)) + Fatalf("Could not parse --%s: invalid length %d, want 32", BeaconCheckpointFlag.Name, len(c)) } copy(config.Checkpoint[:len(c)], c) }