From 8e8d10ac6ce422ce51435f868cd25eaea30ca48b Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Mon, 31 Mar 2025 09:41:55 +0200 Subject: [PATCH] core/state, trie: implement stateless transition Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com> --- core/overlay/conversion.go | 21 +++++++++------------ core/rawdb/accessors_overlay.go | 30 ++++++++++++++++++++++++++++++ core/rawdb/schema.go | 8 ++++++++ core/state/database.go | 30 ++++++++++++------------------ core/state/reader.go | 17 ++++++++++++++--- trie/transition.go | 10 ++++------ 6 files changed, 77 insertions(+), 39 deletions(-) create mode 100644 core/rawdb/accessors_overlay.go diff --git a/core/overlay/conversion.go b/core/overlay/conversion.go index deb2e344f3..c8ff51247e 100644 --- a/core/overlay/conversion.go +++ b/core/overlay/conversion.go @@ -146,7 +146,7 @@ func (kvm *keyValueMigrator) getOrInitLeafNodeData(bk branchKey) *verkle.BatchNe return &kvm.leafData[bk].leafNodeData } -func (kvm *keyValueMigrator) prepare() { +func (kvm *keyValueMigrator) prepare(pointCache *utils.PointCache) { // We fire a background routine to process the leafData and save the result in newLeaves. // The background routine signals that it is done by closing processingReady. go func() { @@ -175,7 +175,7 @@ func (kvm *keyValueMigrator) prepare() { for i := range batch { if batch[i].branchKey.addr != currAddr || currAddr == (common.Address{}) { currAddr = batch[i].branchKey.addr - currPoint = utils.EvaluateAddressPoint(currAddr[:]) + currPoint = pointCache.Get(currAddr[:]) } stem := utils.GetTreeKeyWithEvaluatedAddress(currPoint, &batch[i].branchKey.treeIndex, 0) stem = stem[:verkle.StemSize] @@ -227,7 +227,6 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC now = time.Now() tt = statedb.GetTrie().(*trie.TransitionTrie) mpt = tt.Base() - vkt = tt.Overlay() hasPreimagesBin = false preimageSeek = migrdb.GetCurrentPreimageOffset() fpreimages *bufio.Reader @@ -247,7 +246,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC hasPreimagesBin = true } - accIt, err := statedb.Snaps().AccountIterator(mpt.Hash(), migrdb.GetCurrentAccountHash()) + accIt, err := statedb.Database().Snapshot().AccountIterator(mpt.Hash(), migrdb.GetCurrentAccountHash()) if err != nil { return err } @@ -262,7 +261,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC return fmt.Errorf("reading preimage file: %s", err) } } else { - addr = common.BytesToAddress(rawdb.ReadPreimage(migrdb.DiskDB(), accIt.Hash())) + addr = common.BytesToAddress(rawdb.ReadPreimage(migrdb.TrieDB().Disk(), accIt.Hash())) if len(addr) != 20 { return fmt.Errorf("addr len is zero is not 32: %d", len(addr)) } @@ -289,7 +288,6 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC log.Error("Invalid account encountered during traversal", "error", err) return err } - vkt.SetStorageRootConversion(*migrdb.GetCurrentAccountAddress(), acc.Root) // Start with processing the storage, because once the account is // converted, the `stateRoot` field loses its meaning. Which means @@ -301,7 +299,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC // to during normal block execution. A mitigation strategy has been // introduced with the `*StorageRootConversion` fields in VerkleDB. if len(acc.Root) == 32 && acc.Root != types.EmptyRootHash { - stIt, err := statedb.Snaps().StorageIterator(mpt.Hash(), accIt.Hash(), migrdb.GetCurrentSlotHash()) + stIt, err := statedb.Database().Snapshot().StorageIterator(mpt.Hash(), accIt.Hash(), migrdb.GetCurrentSlotHash()) if err != nil { return err } @@ -337,7 +335,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC return fmt.Errorf("reading preimage file: %s", err) } } else { - slotnr = rawdb.ReadPreimage(migrdb.DiskDB(), stIt.Hash()) + slotnr = rawdb.ReadPreimage(migrdb.TrieDB().Disk(), stIt.Hash()) if len(slotnr) != 32 { return fmt.Errorf("slotnr len is zero is not 32: %d", len(slotnr)) } @@ -367,11 +365,10 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC count++ // count increase for the account itself mkv.addAccount(migrdb.GetCurrentAccountAddress().Bytes(), acc) - vkt.ClearStrorageRootConversion(*migrdb.GetCurrentAccountAddress()) // Store the account code if present if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash[:]) { - code := rawdb.ReadCode(statedb.Database().DiskDB(), common.BytesToHash(acc.CodeHash)) + code := rawdb.ReadCode(statedb.Database().TrieDB().Disk(), common.BytesToHash(acc.CodeHash)) chunks := trie.ChunkifyCode(code) mkv.addAccountCode(migrdb.GetCurrentAccountAddress().Bytes(), uint64(len(code)), chunks) @@ -391,7 +388,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC return fmt.Errorf("reading preimage file: %s", err) } } else { - addr = common.BytesToAddress(rawdb.ReadPreimage(migrdb.DiskDB(), accIt.Hash())) + addr = common.BytesToAddress(rawdb.ReadPreimage(migrdb.TrieDB().Disk(), accIt.Hash())) if len(addr) != 20 { return fmt.Errorf("account address len is zero is not 20: %d", len(addr)) } @@ -422,7 +419,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC // after we fix an existing bug, we can call prepare() before the block execution and // let it do the work in the background. After the block execution and finalization // finish, we can call migrateCollectedKeyValues() which should already find everything ready. - mkv.prepare() + mkv.prepare(statedb.PointCache()) now = time.Now() if err := mkv.migrateCollectedKeyValues(tt.Overlay()); err != nil { return fmt.Errorf("could not migrate key values: %w", err) diff --git a/core/rawdb/accessors_overlay.go b/core/rawdb/accessors_overlay.go new file mode 100644 index 0000000000..5a371b9d30 --- /dev/null +++ b/core/rawdb/accessors_overlay.go @@ -0,0 +1,30 @@ +// Copyright 2024 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 ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" +) + +func ReadVerkleTransitionState(db ethdb.KeyValueReader, hash common.Hash) ([]byte, error) { + return db.Get(transitionStateKey(hash)) +} + +func WriteVerkleTransitionState(db ethdb.KeyValueWriter, hash common.Hash, state []byte) error { + return db.Put(transitionStateKey(hash), state) +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index fa125cecc0..a4af6557d9 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -147,6 +147,9 @@ var ( preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil) preimageHitsCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil) preimageMissCounter = metrics.NewRegisteredCounter("db/preimage/miss", nil) + + // Verkle transition information + VerkleTransitionStatePrefix = []byte("verkle-transition-state-") ) // LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary @@ -362,3 +365,8 @@ func filterMapBlockLVKey(number uint64) []byte { binary.BigEndian.PutUint64(key[l:], number) return key } + +// transitionStateKey = transitionStatusKey + hash +func transitionStateKey(hash common.Hash) []byte { + return append(VerkleTransitionStatePrefix, hash.Bytes()...) +} diff --git a/core/state/database.go b/core/state/database.go index 4c009ad67b..42e16f4067 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -69,18 +69,24 @@ type Database interface { // Snapshot returns the underlying state snapshot. Snapshot() *snapshot.Tree + // StartVerkleTransition marks the start of the verkle transition StartVerkleTransition(originalRoot, translatedRoot common.Hash, chainConfig *params.ChainConfig, verkleTime *uint64, root common.Hash) + // EndVerkleTransition marks the end of the verkle transition EndVerkleTransition() + // InTransition returns true if the verkle transition is currently ongoing InTransition() bool + // Transitioned returns true if the verkle transition has ended Transitioned() bool InitTransitionStatus(bool, bool, common.Hash) + // SetCurrentSlotHash provides the next slot to be translated SetCurrentSlotHash(common.Hash) + // GetCurrentAccountAddress returns the address of the account that is currently being translated GetCurrentAccountAddress() *common.Address SetCurrentAccountAddress(common.Address) @@ -226,6 +232,7 @@ func (db *CachingDB) Transitioned() bool { return db.CurrentTransitionState != nil && db.CurrentTransitionState.Ended } +// StartVerkleTransition marks the start of the verkle transition func (db *CachingDB) StartVerkleTransition(originalRoot, translatedRoot common.Hash, chainConfig *params.ChainConfig, verkleTime *uint64, root common.Hash) { db.CurrentTransitionState = &TransitionState{ Started: true, @@ -317,7 +324,7 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) { } // Set up the trie reader, which is expected to always be available // as the gatekeeper unless the state is corrupted. - tr, err := newTrieReader(stateRoot, db.triedb, db.pointCache) + tr, err := newTrieReader(stateRoot, db.triedb, db.pointCache, db.InTransition(), db.Transitioned()) if err != nil { return nil, err } @@ -338,24 +345,11 @@ func (db *CachingDB) openMPTTrie(root common.Hash) (Trie, error) { return tr, nil } -func (db *CachingDB) openVKTrie(_ common.Hash) (Trie, error) { - payload, err := db.DiskDB().Get(trie.FlatDBVerkleNodeKeyPrefix) - if err != nil { - return trie.NewVerkleTrie(verkle.New(), db.triedb, db.addrToPoint, db.CurrentTransitionState.Ended) - } - - r, err := verkle.ParseNode(payload, 0) - if err != nil { - panic(err) - } - return trie.NewVerkleTrie(r, db.triedb, db.addrToPoint, db.CurrentTransitionState.Ended) -} - // OpenTrie opens the main account trie at a specific root hash. func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) { if db.InTransition() || db.Transitioned() { // NOTE this is a kaustinen-only change, it will break replay - vkt, err := db.openVKTrie(root) + vkt, err := trie.NewVerkleTrie(root, db.triedb, db.addrToPoint) if err != nil { log.Error("failed to open the vkt", "err", err) return nil, err @@ -376,7 +370,7 @@ func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) { return nil, err } - return trie.NewTransitionTree(mpt.(*trie.SecureTrie), vkt.(*trie.VerkleTrie), false), nil + return trie.NewTransitionTree(mpt.(*trie.SecureTrie), vkt, false), nil } log.Info("not in transition, opening mpt alone", "root", root) @@ -509,7 +503,7 @@ func (db *CachingDB) SaveTransitionState(root common.Hash) { // it has been saved. db.TransitionStatePerRoot.Add(root, db.CurrentTransitionState.Copy()) - rawdb.WriteVerkleTransitionState(db.DiskDB(), root, buf.Bytes()) + rawdb.WriteVerkleTransitionState(db.TrieDB().Disk(), root, buf.Bytes()) } log.Debug("saving transition state", "storage processed", db.CurrentTransitionState.StorageProcessed, "addr", db.CurrentTransitionState.CurrentAccountAddress, "slot hash", db.CurrentTransitionState.CurrentSlotHash, "root", root, "ended", db.CurrentTransitionState.Ended, "started", db.CurrentTransitionState.Started) @@ -524,7 +518,7 @@ func (db *CachingDB) LoadTransitionState(root common.Hash) { ts, ok := db.TransitionStatePerRoot.Get(root) if !ok { // Not in the cache, try getting it from the DB - data, err := rawdb.ReadVerkleTransitionState(db.DiskDB(), root) + data, err := rawdb.ReadVerkleTransitionState(db.TrieDB().Disk(), root) if err != nil { log.Error("failed to read transition state", "err", err) return diff --git a/core/state/reader.go b/core/state/reader.go index a0f15dfcc8..8f15d12e9b 100644 --- a/core/state/reader.go +++ b/core/state/reader.go @@ -207,15 +207,26 @@ type trieReader struct { // trieReader constructs a trie reader of the specific state. An error will be // returned if the associated trie specified by root is not existent. -func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCache) (*trieReader, error) { +func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCache, intransition, transitioned bool) (*trieReader, error) { var ( tr Trie err error ) - if !db.IsVerkle() { + if !intransition && !transitioned { tr, err = trie.NewStateTrie(trie.StateTrieID(root), db) } else { - tr, err = trie.NewVerkleTrie(root, db, cache) + vktr, err := trie.NewVerkleTrie(root, db, cache) + if err != nil { + return nil, err + } + tr = vktr + if intransition { + mptr, err := trie.NewStateTrie(trie.StateTrieID(root), db) + if err != nil { + return nil, err + } + tr = trie.NewTransitionTree(mptr, vktr, false) + } } if err != nil { return nil, err diff --git a/trie/transition.go b/trie/transition.go index 4b328b8f30..60fbe2d946 100644 --- a/trie/transition.go +++ b/trie/transition.go @@ -82,9 +82,6 @@ func (t *TransitionTrie) GetAccount(address common.Address) (*types.StateAccount return nil, err } if data != nil { - if t.overlay.db.HasStorageRootConversion(address) { - data.Root = t.overlay.db.StorageRootConversion(address) - } return data, nil } // TODO also insert value into overlay @@ -109,9 +106,6 @@ func (t *TransitionTrie) UpdateStorage(address common.Address, key []byte, value // UpdateAccount abstract an account write to the trie. func (t *TransitionTrie) UpdateAccount(addr common.Address, account *types.StateAccount, codeLen int) error { - if account.Root != (common.Hash{}) && account.Root != types.EmptyRootHash { - t.overlay.db.SetStorageRootConversion(addr, account.Root) - } return t.overlay.UpdateAccount(addr, account, codeLen) } @@ -192,3 +186,7 @@ func (t *TransitionTrie) Copy() *TransitionTrie { func (t *TransitionTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error { return t.overlay.UpdateContractCode(addr, codeHash, code) } + +func (t *TransitionTrie) Witness() map[string]struct{} { + return t.overlay.Witness() +}