diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index cc9f44e460a..cdddb014285 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -354,9 +354,9 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types. // FinalizeAndAssemble implements consensus.Engine, setting the final state and // assembling the block. -func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { +func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, statedb *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { if !beacon.IsPoSHeader(header) { - return beacon.ethone.FinalizeAndAssemble(chain, header, state, body, receipts) + return beacon.ethone.FinalizeAndAssemble(chain, header, statedb, body, receipts) } shanghai := chain.Config().IsShanghai(header.Number, header.Time) if shanghai { @@ -370,10 +370,10 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea } } // Finalize and assemble the block. - beacon.Finalize(chain, header, state, body) + beacon.Finalize(chain, header, statedb, body) // Assign the final state root to header. - header.Root = state.IntermediateRoot(true) + header.Root = statedb.IntermediateRoot(true) // Assemble the final block. block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)) @@ -381,19 +381,19 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea // Create the block witness and attach to block. // This step needs to happen as late as possible to catch all access events. if chain.Config().IsVerkle(header.Number, header.Time) { - keys := state.AccessEvents().Keys() + keys := statedb.AccessEvents().Keys() // Open the pre-tree to prove the pre-state against parent := chain.GetHeaderByNumber(header.Number.Uint64() - 1) if parent == nil { return nil, fmt.Errorf("nil parent header for block %d", header.Number) } - preTrie, err := state.Database().OpenTrie(parent.Root) + preTrie, err := statedb.Database().OpenTrie(parent.Root) if err != nil { return nil, fmt.Errorf("error opening pre-state tree root: %w", err) } vktPreTrie, okpre := preTrie.(*trie.VerkleTrie) - vktPostTrie, okpost := state.GetTrie().(*trie.VerkleTrie) + vktPostTrie, okpost := statedb.GetTrie().(*trie.VerkleTrie) // The witness is only attached iff both parent and current block are // using verkle tree. diff --git a/core/blockchain.go b/core/blockchain.go index d56996dadbe..c08b05e7f47 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -283,12 +283,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis if cacheConfig == nil { cacheConfig = defaultCacheConfig } - // Open trie database with provided config - enableVerkle, err := EnableVerkleAtGenesis(db, genesis) - if err != nil { - return nil, err - } - triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(enableVerkle)) + triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(genesis.IsVerkle())) // Write the supplied genesis to the database if it has not been initialized // yet. The corresponding chain config will be returned, either from the diff --git a/core/chain_makers.go b/core/chain_makers.go index 7a258dc05f8..e7106f82e9a 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -536,8 +536,10 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine return block, b.receipts } + sdb := state.NewDatabase(trdb, nil) + for i := 0; i < n; i++ { - statedb, err := state.New(parent.Root(), state.NewDatabase(trdb, nil)) + statedb, err := state.New(parent.Root(), sdb) if err != nil { panic(err) } diff --git a/core/genesis.go b/core/genesis.go index 95782a827a4..cdd810dd082 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/overlay" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" @@ -443,7 +444,7 @@ func (g *Genesis) chainConfigOrDefault(ghash common.Hash, stored *params.ChainCo // IsVerkle indicates whether the state is already stored in a verkle // tree at genesis time. func (g *Genesis) IsVerkle() bool { - return g.Config.IsVerkleGenesis() + return g != nil && g.Config != nil && g.Config.VerkleTime != nil && *g.Config.VerkleTime == g.Timestamp } // ToBlock returns the genesis block according to genesis specification. @@ -556,6 +557,12 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Blo rawdb.WriteHeadFastBlockHash(batch, block.Hash()) rawdb.WriteHeadHeaderHash(batch, block.Hash()) rawdb.WriteChainConfig(batch, block.Hash(), config) + + // In case of verkle-at-genesis, we need to ensure that the conversion + // markers are indicating that the conversion has completed. + if g.IsVerkle() { + overlay.StoreTransitionState(batch, block.Root(), &overlay.TransitionState{Ended: true}) + } return block, batch.Write() } @@ -569,29 +576,6 @@ func (g *Genesis) MustCommit(db ethdb.Database, triedb *triedb.Database) *types. return block } -// EnableVerkleAtGenesis indicates whether the verkle fork should be activated -// at genesis. This is a temporary solution only for verkle devnet testing, where -// verkle fork is activated at genesis, and the configured activation date has -// already passed. -// -// In production networks (mainnet and public testnets), verkle activation always -// occurs after the genesis block, making this function irrelevant in those cases. -func EnableVerkleAtGenesis(db ethdb.Database, genesis *Genesis) (bool, error) { - if genesis != nil { - if genesis.Config == nil { - return false, errGenesisNoConfig - } - return genesis.Config.EnableVerkleAtGenesis, nil - } - if ghash := rawdb.ReadCanonicalHash(db, 0); ghash != (common.Hash{}) { - chainCfg := rawdb.ReadChainConfig(db, ghash) - if chainCfg != nil { - return chainCfg.EnableVerkleAtGenesis, nil - } - } - return false, nil -} - // DefaultGenesisBlock returns the Ethereum main net genesis block. func DefaultGenesisBlock() *Genesis { return &Genesis{ diff --git a/core/genesis_test.go b/core/genesis_test.go index 726bda86bb0..1204279bbae 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -286,7 +286,6 @@ func TestVerkleGenesisCommit(t *testing.T) { OsakaTime: &verkleTime, VerkleTime: &verkleTime, TerminalTotalDifficulty: big.NewInt(0), - EnableVerkleAtGenesis: true, Ethash: nil, Clique: nil, BlobScheduleConfig: ¶ms.BlobScheduleConfig{ diff --git a/core/overlay/state_transition.go b/core/overlay/state_transition.go new file mode 100644 index 00000000000..ad33324fa1b --- /dev/null +++ b/core/overlay/state_transition.go @@ -0,0 +1,123 @@ +// 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 ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rlp" +) + +// TransitionState represents the progress of the Verkle transition process, +// tracking which parts of the legacy MPT-based state have already been +// migrated to the Verkle-based state. +type TransitionState struct { + // CurrentAccountHash is the hash of the next account to be migrated + // from the MPT state to the Verkle state. + CurrentAccountHash common.Hash + + // CurrentSlotHash is the hash of the next storage slot (within the + // current account) to be migrated from the MPT state to the Verkle state. + // This field is irrelevant if StorageProcessed is true, indicating that + // all storage slots for the current account have already been migrated. + CurrentSlotHash common.Hash + + // StorageProcessed indicates whether all storage slots for the current + // account have been fully migrated. This is useful in cases where the + // transition was interrupted before all state entries were processed + // (e.g., due to a configured migration step limit). + StorageProcessed bool + + // CurrentPreimageOffset is the byte offset in the preimage file from + // which the migration should resume. + CurrentPreimageOffset uint64 + + // Started is true if the transition process has begun. Note that the + // transition is considered started only after the MPT state referenced + // by BaseRoot has been finalized. + Started bool + + // Ended is true if the transition process has completed, meaning the entire + // MPT-based state has been fully migrated. When true, the complete Ethereum + // state is available in the Verkle state, and constructing a mixed state + // view is no longer necessary. + Ended bool + + // BaseRoot is the MPT root hash of the read-only base state, the original + // state prior to Verkle activation. + BaseRoot common.Hash +} + +// 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 +} + +// LogAttrs returns a list of attributes for logging purpose. +func (ts *TransitionState) LogAttrs(root common.Hash) []any { + var attrs []any + if !ts.Started { + attrs = append(attrs, "started", false) + return attrs + } + if ts.Ended { + attrs = append(attrs, "ended", true) + return attrs + } + attrs = append(attrs, []interface{}{"root", root, "base", ts.BaseRoot}...) + + if ts.StorageProcessed { + attrs = append(attrs, []interface{}{"at", ts.CurrentAccountHash}...) + } else { + attrs = append(attrs, []interface{}{ + "in", ts.CurrentAccountHash, + "at", ts.CurrentSlotHash, + }...) + } + return attrs +} + +// LoadTransitionState retrieves the Verkle transition state associated with +// the given state root hash from the database. +func LoadTransitionState(db ethdb.KeyValueReader, root common.Hash) (*TransitionState, error) { + data := rawdb.ReadVerkleTransitionState(db, root) + if len(data) == 0 { + return nil, nil + } + var ts TransitionState + if err := rlp.DecodeBytes(data, &ts); err != nil { + return nil, err + } + return &ts, nil +} + +// StoreTransitionState serializes and writes the provided Verkle transition state +// to the database with the given state root hash. +func StoreTransitionState(db ethdb.KeyValueWriter, root common.Hash, ts *TransitionState) error { + data, err := rlp.EncodeToBytes(ts) + if err != nil { + return err + } + rawdb.WriteVerkleTransitionState(db, root, data) + return nil +} diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go index 859566f722f..6996031be2c 100644 --- a/core/rawdb/accessors_metadata.go +++ b/core/rawdb/accessors_metadata.go @@ -174,16 +174,3 @@ func UpdateUncleanShutdownMarker(db ethdb.KeyValueStore) { log.Warn("Failed to write unclean-shutdown marker", "err", err) } } - -// ReadTransitionStatus retrieves the eth2 transition status from the database -func ReadTransitionStatus(db ethdb.KeyValueReader) []byte { - data, _ := db.Get(transitionStatusKey) - return data -} - -// WriteTransitionStatus stores the eth2 transition status to the database -func WriteTransitionStatus(db ethdb.KeyValueWriter, data []byte) { - if err := db.Put(transitionStatusKey, data); err != nil { - log.Crit("Failed to store the eth2 transition status", "err", err) - } -} diff --git a/core/rawdb/accessors_overlay.go b/core/rawdb/accessors_overlay.go new file mode 100644 index 00000000000..18853bc781a --- /dev/null +++ b/core/rawdb/accessors_overlay.go @@ -0,0 +1,36 @@ +// 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 ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// ReadVerkleTransitionState reads the verkle transition data from the database. +func ReadVerkleTransitionState(db ethdb.KeyValueReader, hash common.Hash) []byte { + data, _ := db.Get(transitionStateKey(hash)) + return data +} + +// WriteVerkleTransitionState writes the verkle transition data into the database. +func WriteVerkleTransitionState(db ethdb.KeyValueWriter, hash common.Hash, state []byte) { + if err := db.Put(transitionStateKey(hash), state); err != nil { + log.Crit("Failed to write the verkle transition state", "hash", hash, "err", err) + } +} diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 2a50e3f9eef..7e0ee228461 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -566,7 +566,7 @@ var knownMetadataKeys = [][]byte{ snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey, persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey, - filterMapsRangeKey, + filterMapsRangeKey, verkleTransitionStatePrefix, } // printChainMetadata prints out chain metadata to stderr. diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index fa125cecc05..4cc79b266dd 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -91,7 +91,7 @@ var ( uncleanShutdownKey = []byte("unclean-shutdown") // config prefix for the db // transitionStatusKey tracks the eth2 transition status. - transitionStatusKey = []byte("eth2-transition") + transitionStatusKey = []byte("eth2-transition") // deprecated! // snapSyncStatusFlagKey flags that status of snap sync. snapSyncStatusFlagKey = []byte("SnapSyncStatus") @@ -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 = verkleTransitionStatePrefix + 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 faf4954650b..ea2b3f77d65 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -21,11 +21,13 @@ 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/rawdb" "github.com/ethereum/go-ethereum/core/state/snapshot" "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" @@ -151,6 +153,9 @@ type CachingDB struct { codeCache *lru.SizeConstrainedCache[common.Hash, []byte] codeSizeCache *lru.Cache[common.Hash, int] pointCache *utils.PointCache + + // Transition-specific fields + transitions *lru.Cache[common.Hash, *overlay.TransitionState] } // NewDatabase creates a state database with the provided data sources. @@ -162,6 +167,7 @@ func NewDatabase(triedb *triedb.Database, snap *snapshot.Tree) *CachingDB { codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize), codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize), pointCache: utils.NewPointCache(pointCacheSize), + transitions: lru.NewCache[common.Hash, *overlay.TransitionState](1000), } } @@ -277,3 +283,38 @@ func mustCopyTrie(t Trie) Trie { panic(fmt.Errorf("unknown trie type %T", t)) } } + +// SaveTransitionState stores the given transition state associated with the +// specified root. It writes the state to persistent storage and also updates +// the in-memory cache. +func (db *CachingDB) SaveTransitionState(root common.Hash, ts *overlay.TransitionState) error { + if err := overlay.StoreTransitionState(db.disk, root, ts); err != nil { + return err + } + db.transitions.Add(root, ts) + log.Debug("Saved transition state", ts.LogAttrs(root)...) + return nil +} + +// LoadTransitionState loads the transition state associated with the specified +// root. The transition state may be unavailable in the following cases: +// +// - Verkle has not been activated yet (the legacy MPT state should be used) +// - The first block immediately since Verkle activation +// - The block after the Verkle transition has completed +func (db *CachingDB) LoadTransitionState(root common.Hash) (*overlay.TransitionState, error) { + ts, ok := db.transitions.Get(root) + if ok { + return ts, nil + } + ts, err := overlay.LoadTransitionState(db.disk, root) + if err != nil { + return nil, err + } + if ts == nil { + log.Debug("Transition state is not found", "root", root) + return nil, nil + } + log.Debug("Loaded transition state", ts.LogAttrs(root)...) + return ts, nil +} diff --git a/core/verkle_witness_test.go b/core/verkle_witness_test.go index de2280ced1c..353c4310150 100644 --- a/core/verkle_witness_test.go +++ b/core/verkle_witness_test.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/binary" "encoding/hex" + "fmt" "math/big" "slices" "testing" @@ -58,7 +59,6 @@ var ( ShanghaiTime: u64(0), VerkleTime: u64(0), TerminalTotalDifficulty: common.Big0, - EnableVerkleAtGenesis: true, BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Verkle: params.DefaultPragueBlobConfig, }, @@ -82,7 +82,6 @@ var ( ShanghaiTime: u64(0), VerkleTime: u64(0), TerminalTotalDifficulty: common.Big0, - EnableVerkleAtGenesis: true, BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Verkle: params.DefaultPragueBlobConfig, }, @@ -202,6 +201,9 @@ func TestProcessVerkle(t *testing.T) { t.Log("verified verkle proof, inserting blocks into the chain") + for i, b := range chain { + fmt.Printf("%d %x\n", i, b.Root()) + } endnum, err := blockchain.InsertChain(chain) if err != nil { t.Fatalf("block %d imported with error: %v", endnum, err) diff --git a/params/config.go b/params/config.go index 8f9e02583bf..b7e839ff8af 100644 --- a/params/config.go +++ b/params/config.go @@ -415,19 +415,6 @@ type ChainConfig struct { DepositContractAddress common.Address `json:"depositContractAddress,omitempty"` - // EnableVerkleAtGenesis is a flag that specifies whether the network uses - // the Verkle tree starting from the genesis block. If set to true, the - // genesis state will be committed using the Verkle tree, eliminating the - // need for any Verkle transition later. - // - // This is a temporary flag only for verkle devnet testing, where verkle is - // activated at genesis, and the configured activation date has already passed. - // - // In production networks (mainnet and public testnets), verkle activation - // always occurs after the genesis block, making this flag irrelevant in - // those cases. - EnableVerkleAtGenesis bool `json:"enableVerkleAtGenesis,omitempty"` - // Various consensus engines Ethash *EthashConfig `json:"ethash,omitempty"` Clique *CliqueConfig `json:"clique,omitempty"` @@ -651,20 +638,6 @@ func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time) } -// IsVerkleGenesis checks whether the verkle fork is activated at the genesis block. -// -// Verkle mode is considered enabled if the verkle fork time is configured, -// regardless of whether the local time has surpassed the fork activation time. -// This is a temporary workaround for verkle devnet testing, where verkle is -// activated at genesis, and the configured activation date has already passed. -// -// In production networks (mainnet and public testnets), verkle activation -// always occurs after the genesis block, making this function irrelevant in -// those cases. -func (c *ChainConfig) IsVerkleGenesis() bool { - return c.EnableVerkleAtGenesis -} - // IsEIP4762 returns whether eip 4762 has been activated at given block. func (c *ChainConfig) IsEIP4762(num *big.Int, time uint64) bool { return c.IsVerkle(num, time)