Skip to content

core/state: introduce the TransitionState object (verkle transition part 1) #31634

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/overlay"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -354,9 +355,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 {
Expand All @@ -370,30 +371,32 @@ 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))

// 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()
// TODO(gballet) move this to the end of the overlay conversion function in a subsequent PR
statedb.Database().(*state.CachingDB).SaveTransitionState(header.Root, &overlay.TransitionState{Ended: true})
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.
Expand Down
7 changes: 1 addition & 6 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -575,6 +577,7 @@ func GenerateVerkleChain(config *params.ChainConfig, parent *types.Block, engine

func GenerateVerkleChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, gen func(int, *BlockGen)) (common.Hash, ethdb.Database, []*types.Block, []types.Receipts, []*verkle.VerkleProof, []verkle.StateDiff) {
db := rawdb.NewMemoryDatabase()
saveVerkleTransitionStatusAtVerlkeGenesis(db)
cacheConfig := DefaultCacheConfigWithScheme(rawdb.PathScheme)
cacheConfig.SnapshotLimit = 0
triedb := triedb.NewDatabase(db, cacheConfig.triedbConfig(true))
Expand Down
53 changes: 29 additions & 24 deletions core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -145,6 +146,9 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
emptyRoot = types.EmptyVerkleHash
}
db := rawdb.NewMemoryDatabase()
if isVerkle {
saveVerkleTransitionStatusAtVerlkeGenesis(db)
}
statedb, err := state.New(emptyRoot, state.NewDatabase(triedb.NewDatabase(db, config), nil))
if err != nil {
return common.Hash{}, err
Expand Down Expand Up @@ -276,6 +280,22 @@ func (o *ChainOverrides) apply(cfg *params.ChainConfig) error {
return cfg.CheckConfigForkOrder()
}

// saveVerkleTransitionStatusAtVerlkeGenesis saves a conversion marker
// representing a converted state, which is used in devnets that activate
// verkle at genesis.
func saveVerkleTransitionStatusAtVerlkeGenesis(db ethdb.Database) {
saveVerkleTransitionStatus(db, common.Hash{}, &overlay.TransitionState{Ended: true})
}

func saveVerkleTransitionStatus(db ethdb.Database, root common.Hash, ts *overlay.TransitionState) {
enc, err := rlp.EncodeToBytes(ts)
if err != nil {
log.Error("failed to encode transition state", "err", err)
return
}
rawdb.WriteVerkleTransitionState(db, root, enc)
}

// SetupGenesisBlock writes or updates the genesis block in db.
// The block that will be used is:
//
Expand All @@ -299,6 +319,11 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g
if genesis != nil && genesis.Config == nil {
return nil, common.Hash{}, nil, errGenesisNoConfig
}
// In case of verkle-at-genesis, we need to ensure that the conversion
// markers are indicating that the conversion has completed.
if genesis != nil && genesis.Config.VerkleTime != nil && *genesis.Config.VerkleTime == genesis.Timestamp {
saveVerkleTransitionStatusAtVerlkeGenesis(db)
}
// Commit the genesis if the database is empty
ghash := rawdb.ReadCanonicalHash(db, 0)
if (ghash == common.Hash{}) {
Expand Down Expand Up @@ -443,7 +468,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.
Expand Down Expand Up @@ -547,6 +572,9 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Blo
if err != nil {
return nil, err
}
if g.IsVerkle() {
saveVerkleTransitionStatus(db, block.Root(), &overlay.TransitionState{Ended: true})
}
batch := db.NewBatch()
rawdb.WriteGenesisStateSpec(batch, block.Hash(), blob)
rawdb.WriteBlock(batch, block)
Expand All @@ -569,29 +597,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{
Expand Down
2 changes: 1 addition & 1 deletion core/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,6 @@ func TestVerkleGenesisCommit(t *testing.T) {
OsakaTime: &verkleTime,
VerkleTime: &verkleTime,
TerminalTotalDifficulty: big.NewInt(0),
EnableVerkleAtGenesis: true,
Ethash: nil,
Clique: nil,
BlobScheduleConfig: &params.BlobScheduleConfig{
Expand Down Expand Up @@ -314,6 +313,7 @@ func TestVerkleGenesisCommit(t *testing.T) {
}

db := rawdb.NewMemoryDatabase()
saveVerkleTransitionStatusAtVerlkeGenesis(db)
triedb := triedb.NewDatabase(db, triedb.VerkleDefaults)
block := genesis.MustCommit(db, triedb)
if !bytes.Equal(block.Root().Bytes(), expected) {
Expand Down
63 changes: 63 additions & 0 deletions core/overlay/state_transition.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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 <http://www.gnu.org/licenses/>.

package overlay

import "github.com/ethereum/go-ethereum/common"

// TransitionState is a structure that holds the progress markers of the
// translation process.
type TransitionState struct {
CurrentAccountAddress *common.Address `rlp:"nil"` // addresss of the last translated account
CurrentSlotHash common.Hash // hash of the last translated storage slot
CurrentPreimageOffset uint64 // 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
}
30 changes: 30 additions & 0 deletions core/rawdb/accessors_overlay.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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 <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)
}
8 changes: 8 additions & 0 deletions core/rawdb/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()...)
}
Loading