From 0f37c99d3457791f97995b08c2f6d53f4a5b37c6 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 4 Aug 2025 15:38:51 +0200 Subject: [PATCH] make LoadTransitionState cacheless and move it to the overlay pkg Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Co-authored-by: Gary Rong --- core/overlay/state_transition.go | 107 +++++++++++++++++++++++++++++++ core/state/database.go | 91 +------------------------- core/state/database_history.go | 9 --- 3 files changed, 109 insertions(+), 98 deletions(-) create mode 100644 core/overlay/state_transition.go diff --git a/core/overlay/state_transition.go b/core/overlay/state_transition.go new file mode 100644 index 0000000000..8cc1b4baaf --- /dev/null +++ b/core/overlay/state_transition.go @@ -0,0 +1,107 @@ +// 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 overlay + +import ( + "bytes" + "encoding/gob" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// TransitionState is a structure that holds the progress markers of the +// translation process. +type TransitionState struct { + CurrentAccountAddress *common.Address // addresss of the last translated account + CurrentSlotHash common.Hash // hash of the last translated storage slot + CurrentPreimageOffset int64 // next byte to read from the preimage file + Started, Ended bool + + // Mark whether the storage for an account has been processed. This is useful if the + // maximum number of leaves of the conversion is reached before the whole storage is + // processed. + StorageProcessed bool + + BaseRoot common.Hash // hash of the last read-only MPT base tree +} + +// InTransition returns true if the translation process is in progress. +func (ts *TransitionState) InTransition() bool { + return ts != nil && ts.Started && !ts.Ended +} + +// Transitioned returns true if the translation process has been completed. +func (ts *TransitionState) Transitioned() bool { + return ts != nil && ts.Ended +} + +// Copy returns a deep copy of the TransitionState object. +func (ts *TransitionState) Copy() *TransitionState { + ret := &TransitionState{ + Started: ts.Started, + Ended: ts.Ended, + CurrentSlotHash: ts.CurrentSlotHash, + CurrentPreimageOffset: ts.CurrentPreimageOffset, + StorageProcessed: ts.StorageProcessed, + } + + if ts.CurrentAccountAddress != nil { + ret.CurrentAccountAddress = &common.Address{} + copy(ret.CurrentAccountAddress[:], ts.CurrentAccountAddress[:]) + } + + return ret +} + +// LoadTransitionState retrieves the Verkle transition state associated with +// the given state root hash from the database. +func LoadTransitionState(db ethdb.KeyValueReader, root common.Hash, isVerkle bool) *TransitionState { + var ts *TransitionState + + data, _ := rawdb.ReadVerkleTransitionState(db, root) + + // if a state could be read from the db, attempt to decode it + if len(data) > 0 { + var ( + newts TransitionState + buf = bytes.NewBuffer(data[:]) + dec = gob.NewDecoder(buf) + ) + // Decode transition state + err := dec.Decode(&newts) + if err != nil { + log.Error("failed to decode transition state", "err", err) + return nil + } + ts = &newts + } + + // Fallback that should only happen before the transition + if ts == nil { + // Initialize the first transition state, with the "ended" + // field set to true if the database was created + // as a verkle database. + log.Debug("no transition state found, starting fresh", "is verkle", db) + // Start with a fresh state + ts = &TransitionState{Ended: isVerkle} + } + + return ts +} diff --git a/core/state/database.go b/core/state/database.go index 187dc79bce..a12209bea3 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -17,10 +17,7 @@ package state import ( - "bytes" - "encoding/gob" "fmt" - "reflect" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" @@ -30,7 +27,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/utils" @@ -193,7 +189,7 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) { readers = append(readers, newFlatReader(snap)) } } - ts := db.LoadTransitionState(stateRoot) + ts := overlay.LoadTransitionState(db.TrieDB().Disk(), stateRoot, db.triedb.IsVerkle()) // Configure the state reader using the path database in path mode. // This reader offers improved performance but is optional and only // partially useful if the snapshot data in path database is not @@ -234,7 +230,7 @@ func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithSta // OpenTrie opens the main account trie at a specific root hash. func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) { if db.triedb.IsVerkle() { - ts := db.LoadTransitionState(root) + ts := overlay.LoadTransitionState(db.TrieDB().Disk(), root, db.triedb.IsVerkle()) if ts.InTransition() { panic("transition isn't supported yet") } @@ -303,86 +299,3 @@ func mustCopyTrie(t Trie) Trie { panic(fmt.Errorf("unknown trie type %T", t)) } } - -// SaveTransitionState saves the transition state to the cache and commits -// it to the database if it's not already in the cache. -func (db *CachingDB) SaveTransitionState(root common.Hash, ts *overlay.TransitionState) { - if ts == nil { - panic("nil transition state") - } - - var buf bytes.Buffer - enc := gob.NewEncoder(&buf) - err := enc.Encode(ts) - if err != nil { - log.Error("failed to encode transition state", "err", err) - return - } - - if !db.TransitionStatePerRoot.Contains(root) { - // Copy so that the address pointer isn't updated after - // it has been saved. - db.TransitionStatePerRoot.Add(root, ts.Copy()) - rawdb.WriteVerkleTransitionState(db.TrieDB().Disk(), root, buf.Bytes()) - } else { - // Check that the state is consistent with what is in the cache, - // which is not strictly necessary but a good sanity check. Can - // be removed when the transition is stable. - cachedState, _ := db.TransitionStatePerRoot.Get(root) - if !reflect.DeepEqual(cachedState, ts) { - fmt.Println("transition state mismatch", "cached state", cachedState, "new state", ts) - panic("transition state mismatch") - } - } - - log.Debug("saving transition state", "storage processed", ts.StorageProcessed, "addr", ts.CurrentAccountAddress, "slot hash", ts.CurrentSlotHash, "root", root, "ended", ts.Ended, "started", ts.Started) -} - -func (db *CachingDB) LoadTransitionState(root common.Hash) *overlay.TransitionState { - // Try to get the transition state from the cache and - // the DB if it's not there. - ts, ok := db.TransitionStatePerRoot.Get(root) - if !ok { - // Not in the cache, try getting it from the DB - data, _ := rawdb.ReadVerkleTransitionState(db.TrieDB().Disk(), root) - // if err != nil && errors.Is(err, triedb.ErrNotFound) { - // log.Error("failed to read transition state", "err", err) - // return nil - // } - - // if a state could be read from the db, attempt to decode it - if len(data) > 0 { - var ( - newts overlay.TransitionState - buf = bytes.NewBuffer(data[:]) - dec = gob.NewDecoder(buf) - ) - // Decode transition state - err := dec.Decode(&newts) - if err != nil { - log.Error("failed to decode transition state", "err", err) - return nil - } - ts = &newts - } - - // Fallback that should only happen before the transition - if ts == nil { - // Initialize the first transition state, with the "ended" - // field set to true if the database was created - // as a verkle database. - log.Debug("no transition state found, starting fresh", "is verkle", db.triedb.IsVerkle()) - // Start with a fresh state - ts = &overlay.TransitionState{Ended: db.triedb.IsVerkle()} - } - - db.TransitionStatePerRoot.Add(root, ts) - } - - // Copy so that the CurrentAddress pointer in the map - // doesn't get overwritten. - // db.CurrentTransitionState = ts.Copy() - - log.Debug("loaded transition state", "storage processed", ts.StorageProcessed, "addr", ts.CurrentAccountAddress, "slot hash", ts.CurrentSlotHash, "root", root, "ended", ts.Ended, "started", ts.Started) - return ts -} diff --git a/core/state/database_history.go b/core/state/database_history.go index 88b893695e..314c56c470 100644 --- a/core/state/database_history.go +++ b/core/state/database_history.go @@ -21,7 +21,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" - "github.com/ethereum/go-ethereum/core/overlay" "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" @@ -154,11 +153,3 @@ func (db *HistoricDB) TrieDB() *triedb.Database { func (db *HistoricDB) Snapshot() *snapshot.Tree { return nil } - -func (db *HistoricDB) LoadTransitionState(common.Hash) *overlay.TransitionState { - panic("should not be called") -} - -func (db *HistoricDB) SaveTransitionState(common.Hash, *overlay.TransitionState) { - panic("should not be called") -}