core/state, trie: implement stateless transition

Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
This commit is contained in:
Guillaume Ballet 2025-03-31 09:41:55 +02:00
parent f50b2541a4
commit 8e8d10ac6c
6 changed files with 77 additions and 39 deletions

View file

@ -146,7 +146,7 @@ func (kvm *keyValueMigrator) getOrInitLeafNodeData(bk branchKey) *verkle.BatchNe
return &kvm.leafData[bk].leafNodeData 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. // 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. // The background routine signals that it is done by closing processingReady.
go func() { go func() {
@ -175,7 +175,7 @@ func (kvm *keyValueMigrator) prepare() {
for i := range batch { for i := range batch {
if batch[i].branchKey.addr != currAddr || currAddr == (common.Address{}) { if batch[i].branchKey.addr != currAddr || currAddr == (common.Address{}) {
currAddr = batch[i].branchKey.addr currAddr = batch[i].branchKey.addr
currPoint = utils.EvaluateAddressPoint(currAddr[:]) currPoint = pointCache.Get(currAddr[:])
} }
stem := utils.GetTreeKeyWithEvaluatedAddress(currPoint, &batch[i].branchKey.treeIndex, 0) stem := utils.GetTreeKeyWithEvaluatedAddress(currPoint, &batch[i].branchKey.treeIndex, 0)
stem = stem[:verkle.StemSize] stem = stem[:verkle.StemSize]
@ -227,7 +227,6 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC
now = time.Now() now = time.Now()
tt = statedb.GetTrie().(*trie.TransitionTrie) tt = statedb.GetTrie().(*trie.TransitionTrie)
mpt = tt.Base() mpt = tt.Base()
vkt = tt.Overlay()
hasPreimagesBin = false hasPreimagesBin = false
preimageSeek = migrdb.GetCurrentPreimageOffset() preimageSeek = migrdb.GetCurrentPreimageOffset()
fpreimages *bufio.Reader fpreimages *bufio.Reader
@ -247,7 +246,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC
hasPreimagesBin = true hasPreimagesBin = true
} }
accIt, err := statedb.Snaps().AccountIterator(mpt.Hash(), migrdb.GetCurrentAccountHash()) accIt, err := statedb.Database().Snapshot().AccountIterator(mpt.Hash(), migrdb.GetCurrentAccountHash())
if err != nil { if err != nil {
return err return err
} }
@ -262,7 +261,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC
return fmt.Errorf("reading preimage file: %s", err) return fmt.Errorf("reading preimage file: %s", err)
} }
} else { } else {
addr = common.BytesToAddress(rawdb.ReadPreimage(migrdb.DiskDB(), accIt.Hash())) addr = common.BytesToAddress(rawdb.ReadPreimage(migrdb.TrieDB().Disk(), accIt.Hash()))
if len(addr) != 20 { if len(addr) != 20 {
return fmt.Errorf("addr len is zero is not 32: %d", len(addr)) 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) log.Error("Invalid account encountered during traversal", "error", err)
return err return err
} }
vkt.SetStorageRootConversion(*migrdb.GetCurrentAccountAddress(), acc.Root)
// Start with processing the storage, because once the account is // Start with processing the storage, because once the account is
// converted, the `stateRoot` field loses its meaning. Which means // 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 // to during normal block execution. A mitigation strategy has been
// introduced with the `*StorageRootConversion` fields in VerkleDB. // introduced with the `*StorageRootConversion` fields in VerkleDB.
if len(acc.Root) == 32 && acc.Root != types.EmptyRootHash { 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 { if err != nil {
return err return err
} }
@ -337,7 +335,7 @@ func OverlayVerkleTransition(statedb *state.StateDB, root common.Hash, maxMovedC
return fmt.Errorf("reading preimage file: %s", err) return fmt.Errorf("reading preimage file: %s", err)
} }
} else { } else {
slotnr = rawdb.ReadPreimage(migrdb.DiskDB(), stIt.Hash()) slotnr = rawdb.ReadPreimage(migrdb.TrieDB().Disk(), stIt.Hash())
if len(slotnr) != 32 { if len(slotnr) != 32 {
return fmt.Errorf("slotnr len is zero is not 32: %d", len(slotnr)) 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 count++ // count increase for the account itself
mkv.addAccount(migrdb.GetCurrentAccountAddress().Bytes(), acc) mkv.addAccount(migrdb.GetCurrentAccountAddress().Bytes(), acc)
vkt.ClearStrorageRootConversion(*migrdb.GetCurrentAccountAddress())
// Store the account code if present // Store the account code if present
if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash[:]) { 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) chunks := trie.ChunkifyCode(code)
mkv.addAccountCode(migrdb.GetCurrentAccountAddress().Bytes(), uint64(len(code)), chunks) 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) return fmt.Errorf("reading preimage file: %s", err)
} }
} else { } else {
addr = common.BytesToAddress(rawdb.ReadPreimage(migrdb.DiskDB(), accIt.Hash())) addr = common.BytesToAddress(rawdb.ReadPreimage(migrdb.TrieDB().Disk(), accIt.Hash()))
if len(addr) != 20 { if len(addr) != 20 {
return fmt.Errorf("account address len is zero is not 20: %d", len(addr)) 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 // 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 // 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. // finish, we can call migrateCollectedKeyValues() which should already find everything ready.
mkv.prepare() mkv.prepare(statedb.PointCache())
now = time.Now() now = time.Now()
if err := mkv.migrateCollectedKeyValues(tt.Overlay()); err != nil { if err := mkv.migrateCollectedKeyValues(tt.Overlay()); err != nil {
return fmt.Errorf("could not migrate key values: %w", err) return fmt.Errorf("could not migrate key values: %w", err)

View file

@ -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 <http://www.gnu.org/licenses/>.
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)
}

View file

@ -147,6 +147,9 @@ var (
preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil) preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil)
preimageHitsCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil) preimageHitsCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil)
preimageMissCounter = metrics.NewRegisteredCounter("db/preimage/miss", 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 // LegacyTxLookupEntry is the legacy TxLookupEntry definition with some unnecessary
@ -362,3 +365,8 @@ func filterMapBlockLVKey(number uint64) []byte {
binary.BigEndian.PutUint64(key[l:], number) binary.BigEndian.PutUint64(key[l:], number)
return key return key
} }
// transitionStateKey = transitionStatusKey + hash
func transitionStateKey(hash common.Hash) []byte {
return append(VerkleTransitionStatePrefix, hash.Bytes()...)
}

View file

@ -69,18 +69,24 @@ type Database interface {
// Snapshot returns the underlying state snapshot. // Snapshot returns the underlying state snapshot.
Snapshot() *snapshot.Tree Snapshot() *snapshot.Tree
// StartVerkleTransition marks the start of the verkle transition
StartVerkleTransition(originalRoot, translatedRoot common.Hash, chainConfig *params.ChainConfig, verkleTime *uint64, root common.Hash) StartVerkleTransition(originalRoot, translatedRoot common.Hash, chainConfig *params.ChainConfig, verkleTime *uint64, root common.Hash)
// EndVerkleTransition marks the end of the verkle transition
EndVerkleTransition() EndVerkleTransition()
// InTransition returns true if the verkle transition is currently ongoing
InTransition() bool InTransition() bool
// Transitioned returns true if the verkle transition has ended
Transitioned() bool Transitioned() bool
InitTransitionStatus(bool, bool, common.Hash) InitTransitionStatus(bool, bool, common.Hash)
// SetCurrentSlotHash provides the next slot to be translated
SetCurrentSlotHash(common.Hash) SetCurrentSlotHash(common.Hash)
// GetCurrentAccountAddress returns the address of the account that is currently being translated
GetCurrentAccountAddress() *common.Address GetCurrentAccountAddress() *common.Address
SetCurrentAccountAddress(common.Address) SetCurrentAccountAddress(common.Address)
@ -226,6 +232,7 @@ func (db *CachingDB) Transitioned() bool {
return db.CurrentTransitionState != nil && db.CurrentTransitionState.Ended 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) { func (db *CachingDB) StartVerkleTransition(originalRoot, translatedRoot common.Hash, chainConfig *params.ChainConfig, verkleTime *uint64, root common.Hash) {
db.CurrentTransitionState = &TransitionState{ db.CurrentTransitionState = &TransitionState{
Started: true, 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 // Set up the trie reader, which is expected to always be available
// as the gatekeeper unless the state is corrupted. // 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 { if err != nil {
return nil, err return nil, err
} }
@ -338,24 +345,11 @@ func (db *CachingDB) openMPTTrie(root common.Hash) (Trie, error) {
return tr, nil 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. // OpenTrie opens the main account trie at a specific root hash.
func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) { func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
if db.InTransition() || db.Transitioned() { if db.InTransition() || db.Transitioned() {
// NOTE this is a kaustinen-only change, it will break replay // 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 { if err != nil {
log.Error("failed to open the vkt", "err", err) log.Error("failed to open the vkt", "err", err)
return nil, err return nil, err
@ -376,7 +370,7 @@ func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
return nil, err 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) 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. // it has been saved.
db.TransitionStatePerRoot.Add(root, db.CurrentTransitionState.Copy()) 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) 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) ts, ok := db.TransitionStatePerRoot.Get(root)
if !ok { if !ok {
// Not in the cache, try getting it from the DB // 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 { if err != nil {
log.Error("failed to read transition state", "err", err) log.Error("failed to read transition state", "err", err)
return return

View file

@ -207,15 +207,26 @@ type trieReader struct {
// trieReader constructs a trie reader of the specific state. An error will be // trieReader constructs a trie reader of the specific state. An error will be
// returned if the associated trie specified by root is not existent. // 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 ( var (
tr Trie tr Trie
err error err error
) )
if !db.IsVerkle() { if !intransition && !transitioned {
tr, err = trie.NewStateTrie(trie.StateTrieID(root), db) tr, err = trie.NewStateTrie(trie.StateTrieID(root), db)
} else { } 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 { if err != nil {
return nil, err return nil, err

View file

@ -82,9 +82,6 @@ func (t *TransitionTrie) GetAccount(address common.Address) (*types.StateAccount
return nil, err return nil, err
} }
if data != nil { if data != nil {
if t.overlay.db.HasStorageRootConversion(address) {
data.Root = t.overlay.db.StorageRootConversion(address)
}
return data, nil return data, nil
} }
// TODO also insert value into overlay // 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. // UpdateAccount abstract an account write to the trie.
func (t *TransitionTrie) UpdateAccount(addr common.Address, account *types.StateAccount, codeLen int) error { 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) 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 { func (t *TransitionTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
return t.overlay.UpdateContractCode(addr, codeHash, code) return t.overlay.UpdateContractCode(addr, codeHash, code)
} }
func (t *TransitionTrie) Witness() map[string]struct{} {
return t.overlay.Witness()
}