From 9d2db926b9455df0555f674ec343db3011359932 Mon Sep 17 00:00:00 2001
From: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
Date: Thu, 10 Apr 2025 19:16:27 +0200
Subject: [PATCH 1/3] core/state: introduce the TransitionState object
---
consensus/beacon/consensus.go | 16 ++--
core/blockchain.go | 7 +-
core/chain_makers.go | 5 +-
core/genesis.go | 55 ++++++-----
core/genesis_test.go | 2 +-
core/rawdb/accessors_overlay.go | 30 ++++++
core/rawdb/schema.go | 8 ++
core/state/database.go | 165 ++++++++++++++++++++++++++++++--
core/state/reader.go | 4 +-
core/state/statedb.go | 3 +-
core/verkle_witness_test.go | 6 +-
params/config.go | 27 ------
12 files changed, 248 insertions(+), 80 deletions(-)
create mode 100644 core/rawdb/accessors_overlay.go
diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go
index cc9f44e460a..5c943405790 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,21 @@ 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()
+ // TODO(gballet) move this to the end of the overlay conversion function in a subsequent PR
+ statedb.Database().SaveTransitionState(header.Root, &state.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.
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..a3825137c6f 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)
}
@@ -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))
diff --git a/core/genesis.go b/core/genesis.go
index 95782a827a4..ab80f3ededf 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -18,6 +18,7 @@ package core
import (
"bytes"
+ "encoding/gob"
"encoding/json"
"errors"
"fmt"
@@ -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
@@ -276,6 +280,24 @@ 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{}, &state.TransitionState{Ended: true})
+}
+
+func saveVerkleTransitionStatus(db ethdb.Database, root common.Hash, ts *state.TransitionState) {
+ 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
+ }
+ rawdb.WriteVerkleTransitionState(db, root, buf.Bytes())
+}
+
// SetupGenesisBlock writes or updates the genesis block in db.
// The block that will be used is:
//
@@ -299,6 +321,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{}) {
@@ -443,7 +470,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.Config.VerkleTime != nil && *g.Config.VerkleTime == g.Timestamp
}
// ToBlock returns the genesis block according to genesis specification.
@@ -547,6 +574,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(), &state.TransitionState{Ended: true})
+ }
batch := db.NewBatch()
rawdb.WriteGenesisStateSpec(batch, block.Hash(), blob)
rawdb.WriteBlock(batch, block)
@@ -569,29 +599,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..837ec7cdd83 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{
@@ -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) {
diff --git a/core/rawdb/accessors_overlay.go b/core/rawdb/accessors_overlay.go
new file mode 100644
index 00000000000..364cc889d1e
--- /dev/null
+++ b/core/rawdb/accessors_overlay.go
@@ -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 .
+
+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 fa125cecc05..a4af6557d9a 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 faf4954650b..082dbd82575 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -17,7 +17,10 @@
package state
import (
+ "bytes"
+ "encoding/gob"
"fmt"
+ "reflect"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
@@ -26,6 +29,7 @@ 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"
@@ -62,6 +66,12 @@ type Database interface {
// Snapshot returns the underlying state snapshot.
Snapshot() *snapshot.Tree
+
+ // SaveTransitionState saves the tree transition progress markers to the database.
+ SaveTransitionState(common.Hash, *TransitionState)
+
+ // LoadTransitionState loads the tree transition progress markers from the database.
+ LoadTransitionState(common.Hash) *TransitionState
}
// Trie is a Ethereum Merkle Patricia trie.
@@ -141,6 +151,50 @@ type Trie interface {
IsVerkle() bool
}
+// 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
+}
+
// CachingDB is an implementation of Database interface. It leverages both trie and
// state snapshot to provide functionalities for state access. It's meant to be a
// long-live object and has a few caches inside for sharing between blocks.
@@ -151,17 +205,21 @@ type CachingDB struct {
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
codeSizeCache *lru.Cache[common.Hash, int]
pointCache *utils.PointCache
+
+ // Transition-specific fields
+ TransitionStatePerRoot lru.BasicLRU[common.Hash, *TransitionState]
}
// NewDatabase creates a state database with the provided data sources.
func NewDatabase(triedb *triedb.Database, snap *snapshot.Tree) *CachingDB {
return &CachingDB{
- disk: triedb.Disk(),
- triedb: triedb,
- snap: snap,
- codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
- codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
- pointCache: utils.NewPointCache(pointCacheSize),
+ disk: triedb.Disk(),
+ triedb: triedb,
+ snap: snap,
+ codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
+ codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
+ pointCache: utils.NewPointCache(pointCacheSize),
+ TransitionStatePerRoot: lru.NewBasicLRU[common.Hash, *TransitionState](1000),
}
}
@@ -193,9 +251,10 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
readers = append(readers, newFlatReader(reader)) // state reader is optional
}
}
+ ts := db.LoadTransitionState(stateRoot)
// 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, ts)
if err != nil {
return nil, err
}
@@ -210,7 +269,11 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
// 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)
+ if ts.InTransition() {
+ panic("transition isn't supported yet")
+ }
+ if ts.Transitioned() {
return trie.NewVerkleTrie(root, db.triedb, db.pointCache)
}
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
@@ -222,10 +285,11 @@ func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) {
// OpenStorageTrie opens the storage trie of an account.
func (db *CachingDB) OpenStorageTrie(stateRoot common.Hash, address common.Address, root common.Hash, self Trie) (Trie, error) {
+ ts := db.LoadTransitionState(stateRoot)
// In the verkle case, there is only one tree. But the two-tree structure
// is hardcoded in the codebase. So we need to return the same trie in this
// case.
- if db.triedb.IsVerkle() {
+ if ts.InTransition() || ts.Transitioned() {
return self, nil
}
tr, err := trie.NewStateTrie(trie.StorageTrieID(stateRoot, crypto.Keccak256Hash(address.Bytes()), root), db.triedb)
@@ -277,3 +341,86 @@ 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 *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) *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 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 = &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/reader.go b/core/state/reader.go
index a0f15dfcc8b..ae2bf0945d1 100644
--- a/core/state/reader.go
+++ b/core/state/reader.go
@@ -207,12 +207,12 @@ 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, ts *TransitionState) (*trieReader, error) {
var (
tr Trie
err error
)
- if !db.IsVerkle() {
+ if !ts.Transitioned() && !ts.InTransition() {
tr, err = trie.NewStateTrie(trie.StateTrieID(root), db)
} else {
tr, err = trie.NewVerkleTrie(root, db, cache)
diff --git a/core/state/statedb.go b/core/state/statedb.go
index e3f5b9e1a0a..175f67155d3 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -181,7 +181,7 @@ func New(root common.Hash, db Database) (*StateDB, error) {
accessList: newAccessList(),
transientStorage: newTransientStorage(),
}
- if db.TrieDB().IsVerkle() {
+ if tr.IsVerkle() {
sdb.accessEvents = NewAccessEvents(db.PointCache())
}
return sdb, nil
@@ -1275,6 +1275,7 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorag
return nil, err
}
}
+ s.db.SaveTransitionState(ret.root, &TransitionState{Ended: true})
if !ret.empty() {
// If snapshotting is enabled, update the snapshot tree with this new version
if snap := s.db.Snapshot(); snap != nil && snap.Snapshot(ret.originRoot) != 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)
From bb038c64a51ffb62df6e465041b3355d95929596 Mon Sep 17 00:00:00 2001
From: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
Date: Mon, 14 Apr 2025 12:16:30 +0200
Subject: [PATCH 2/3] fix failing tests
---
core/genesis.go | 2 +-
core/state/statedb.go | 1 -
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/core/genesis.go b/core/genesis.go
index ab80f3ededf..da01a420c4a 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -470,7 +470,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.VerkleTime != nil && *g.Config.VerkleTime == g.Timestamp
+ return g != nil && g.Config != nil && g.Config.VerkleTime != nil && *g.Config.VerkleTime == g.Timestamp
}
// ToBlock returns the genesis block according to genesis specification.
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 175f67155d3..6d1ef45a7bd 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -1275,7 +1275,6 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorag
return nil, err
}
}
- s.db.SaveTransitionState(ret.root, &TransitionState{Ended: true})
if !ret.empty() {
// If snapshotting is enabled, update the snapshot tree with this new version
if snap := s.db.Snapshot(); snap != nil && snap.Snapshot(ret.originRoot) != nil {
From 2196aa11588916629c901ded249eaa7079800639 Mon Sep 17 00:00:00 2001
From: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
Date: Tue, 22 Apr 2025 18:17:18 +0200
Subject: [PATCH 3/3] cmd/evm/internal/t8ntool: support for verkle-at-genesis
testnets
Co-authored-by: Ignacio Hagopian
---
.github/workflows/stable-spec-tests.yml | 77 +++++++++++++++++++++
cmd/evm/blockrunner.go | 2 +-
cmd/evm/internal/t8ntool/execution.go | 90 ++++++++++++++++++++-----
cmd/evm/internal/t8ntool/flags.go | 20 ++++++
cmd/evm/internal/t8ntool/gen_stenv.go | 6 ++
cmd/evm/internal/t8ntool/transition.go | 48 +++++++++----
cmd/evm/main.go | 3 +
cmd/geth/snapshot.go | 80 ++++++++++++++++++++++
core/state/dump.go | 12 ++++
tests/block_test_util.go | 7 +-
tests/init.go | 39 +++++++++++
11 files changed, 349 insertions(+), 35 deletions(-)
create mode 100644 .github/workflows/stable-spec-tests.yml
diff --git a/.github/workflows/stable-spec-tests.yml b/.github/workflows/stable-spec-tests.yml
new file mode 100644
index 00000000000..3c5477150eb
--- /dev/null
+++ b/.github/workflows/stable-spec-tests.yml
@@ -0,0 +1,77 @@
+name: Execution Spec Tests - Consume (stable)
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master, kaustinen-with-shapella]
+ workflow_dispatch:
+
+env:
+ FIXTURES_TAG: "verkle@v0.0.9-alpha-1"
+
+jobs:
+ setup:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout go-ethereum
+ uses: actions/checkout@v4
+
+ - name: Setup Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.12.4"
+
+ - name: Set up Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: 1.22.4
+
+ - name: Build geth evm
+ run: |
+ go build -v ./cmd/evm
+ mkdir -p ${{ github.workspace }}/bin
+ mv evm ${{ github.workspace }}/bin/evm
+ chmod +x ${{ github.workspace }}/bin/evm
+
+ - name: Archive built evm
+ uses: actions/upload-artifact@v4
+ with:
+ name: evm
+ path: ${{ github.workspace }}/bin/evm
+
+ consume:
+ runs-on: ubuntu-latest
+ needs: setup
+ strategy:
+ matrix:
+ filename:
+ [
+ fixtures_verkle-genesis.tar.gz,
+ ]
+ steps:
+ - name: Download geth evm
+ uses: actions/download-artifact@v4
+ with:
+ name: evm
+ path: ./bin
+
+ - name: Make evm binary executable and add to PATH
+ run: |
+ chmod +x ./bin/evm
+ echo "${{ github.workspace }}/bin" >> $GITHUB_PATH
+
+ - name: Download fixtures
+ uses: robinraju/release-downloader@v1
+ with:
+ repository: "ethereum/execution-spec-tests"
+ tag: "${{ env.FIXTURES_TAG }}"
+ fileName: "${{ matrix.filename }}"
+ extract: true
+ - name: Clone execution-spec-tests and consume tests
+ run: |
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ git clone https://github.com/ethereum/execution-spec-tests -b ${{ env.FIXTURES_TAG }} --depth 1
+ cd execution-spec-tests
+ uv run consume direct --evm-bin="${{ github.workspace }}/bin/evm" --input=../fixtures -n auto
+ shell: bash
diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go
index 31d1ba5ba1c..f6538b13567 100644
--- a/cmd/evm/blockrunner.go
+++ b/cmd/evm/blockrunner.go
@@ -89,7 +89,7 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
continue
}
result := &testResult{Name: name, Pass: true}
- if err := tests[name].Run(false, rawdb.HashScheme, ctx.Bool(WitnessCrossCheckFlag.Name), tracer, func(res error, chain *core.BlockChain) {
+ if err := tests[name].Run(false, rawdb.PathScheme, ctx.Bool(WitnessCrossCheckFlag.Name), tracer, func(res error, chain *core.BlockChain) {
if ctx.Bool(DumpFlag.Name) {
if s, _ := chain.State(); s != nil {
result.State = dump(s)
diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go
index 7de1eb6949d..35993759e32 100644
--- a/cmd/evm/internal/t8ntool/execution.go
+++ b/cmd/evm/internal/t8ntool/execution.go
@@ -39,13 +39,15 @@ import (
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb"
+ "github.com/ethereum/go-verkle"
"github.com/holiman/uint256"
"golang.org/x/crypto/sha3"
)
type Prestate struct {
- Env stEnv `json:"env"`
- Pre types.GenesisAlloc `json:"pre"`
+ Env stEnv `json:"env"`
+ Pre types.GenesisAlloc `json:"pre"`
+ VKT map[common.Hash]hexutil.Bytes `json:"vkt,omitempty"`
}
//go:generate go run github.com/fjl/gencodec -type ExecutionResult -field-override executionResultMarshaling -out gen_execresult.go
@@ -68,6 +70,11 @@ type ExecutionResult struct {
CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"`
RequestsHash *common.Hash `json:"requestsHash,omitempty"`
Requests [][]byte `json:"requests"`
+
+ // Verkle witness
+ VerkleProof *verkle.VerkleProof `json:"verkleProof,omitempty"`
+ StateDiff verkle.StateDiff `json:"stateDiff,omitempty"`
+ ParentRoot common.Hash `json:"parentStateRoot,omitempty"`
}
type executionResultMarshaling struct {
@@ -101,6 +108,7 @@ type stEnv struct {
ParentExcessBlobGas *uint64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *uint64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
+ ParentHash *common.Hash `json:"parentHash,omitempty"`
}
type stEnvMarshaling struct {
@@ -143,16 +151,17 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
return h
}
var (
- statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre)
- signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp)
- gaspool = new(core.GasPool)
- blockHash = common.Hash{0x13, 0x37}
- rejectedTxs []*rejectedTx
- includedTxs types.Transactions
- gasUsed = uint64(0)
- blobGasUsed = uint64(0)
- receipts = make(types.Receipts, 0)
- txIndex = 0
+ parentStateRoot, statedb = MakePreState(rawdb.NewMemoryDatabase(), chainConfig, pre, chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp))
+ signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp)
+ gaspool = new(core.GasPool)
+ blockHash = common.Hash{0x13, 0x37}
+ rejectedTxs []*rejectedTx
+ includedTxs types.Transactions
+ gasUsed = uint64(0)
+ blobGasUsed = uint64(0)
+ receipts = make(types.Receipts, 0)
+ txIndex = 0
+ vtrpre *trie.VerkleTrie
)
gaspool.AddGas(pre.Env.GasLimit)
vmContext := vm.BlockContext{
@@ -165,6 +174,14 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
GasLimit: pre.Env.GasLimit,
GetHash: getHash,
}
+
+ // We save the current state of the Verkle Tree before applying the transactions.
+ // Note that if the Verkle fork isn't active, this will be a noop.
+ switch tr := statedb.GetTrie().(type) {
+ case *trie.VerkleTrie:
+ vtrpre = tr.Copy()
+ }
+
// If currentBaseFee is defined, add it to the vmContext.
if pre.Env.BaseFee != nil {
vmContext.BaseFee = new(big.Int).Set(pre.Env.BaseFee)
@@ -251,6 +268,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
}
}
statedb.SetTxContext(tx.Hash(), txIndex)
+ evm.AccessEvents = state.NewAccessEvents(evm.StateDB.PointCache())
var (
snapshot = statedb.Snapshot()
prevGas = gaspool.Gas()
@@ -315,7 +333,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
evm.Config.Tracer.OnTxEnd(receipt, nil)
}
}
-
+ statedb.AccessEvents().Merge(evm.AccessEvents)
txIndex++
}
statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber))
@@ -348,6 +366,8 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
// Amount is in gwei, turn into wei
amount := new(big.Int).Mul(new(big.Int).SetUint64(w.Amount), big.NewInt(params.GWei))
statedb.AddBalance(w.Address, uint256.MustFromBig(amount), tracing.BalanceIncreaseWithdrawal)
+
+ statedb.AccessEvents().AddAccount(w.Address, true)
}
// Gather the execution-layer triggered requests.
@@ -373,6 +393,25 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
if err != nil {
return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err))
}
+ // Add the witness to the execution result
+ var vktProof *verkle.VerkleProof
+ var vktStateDiff verkle.StateDiff
+ if chainConfig.IsVerkle(big.NewInt(int64(pre.Env.Number)), pre.Env.Timestamp) {
+ keys := statedb.AccessEvents().Keys()
+ if len(keys) > 0 && vtrpre != nil {
+ var proofTrie *trie.VerkleTrie
+ switch tr := statedb.GetTrie().(type) {
+ case *trie.VerkleTrie:
+ proofTrie = tr
+ default:
+ return nil, nil, nil, fmt.Errorf("invalid tree type in proof generation: %v", tr)
+ }
+ vktProof, vktStateDiff, err = vtrpre.Proof(proofTrie, keys)
+ if err != nil {
+ return nil, nil, nil, fmt.Errorf("error generating verkle proof for block %d: %w", pre.Env.Number, err)
+ }
+ }
+ }
execRs := &ExecutionResult{
StateRoot: root,
TxRoot: types.DeriveSha(includedTxs, trie.NewStackTrie(nil)),
@@ -384,6 +423,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
Difficulty: (*math.HexOrDecimal256)(vmContext.Difficulty),
GasUsed: (math.HexOrDecimal64)(gasUsed),
BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee),
+ VerkleProof: vktProof,
+ StateDiff: vktStateDiff,
+ ParentRoot: parentStateRoot,
}
if pre.Env.Withdrawals != nil {
h := types.DeriveSha(types.Withdrawals(pre.Env.Withdrawals), trie.NewStackTrie(nil))
@@ -414,11 +456,13 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
return statedb, execRs, body, nil
}
-func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB {
+// XXX peut-etre pas besoin de changer les parametres tant qu'on n'a pas la conversion
+func MakePreState(db ethdb.Database, chainConfig *params.ChainConfig, pre *Prestate, verkle bool) (common.Hash, *state.StateDB) {
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true})
sdb := state.NewDatabase(tdb, nil)
+
statedb, _ := state.New(types.EmptyRootHash, sdb)
- for addr, a := range accounts {
+ for addr, a := range pre.Pre {
statedb.SetCode(addr, a.Code)
statedb.SetNonce(addr, a.Nonce, tracing.NonceChangeGenesis)
statedb.SetBalance(addr, uint256.MustFromBig(a.Balance), tracing.BalanceIncreaseGenesisBalance)
@@ -427,9 +471,19 @@ func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB
}
}
// Commit and re-open to start with a clean state.
- root, _ := statedb.Commit(0, false, false)
- statedb, _ = state.New(root, sdb)
- return statedb
+ mptRoot, err := statedb.Commit(0, false, false)
+ if err != nil {
+ panic(err)
+ }
+ parentRoot := mptRoot
+ // If verkle mode started, establish the conversion
+ if verkle {
+ if _, ok := statedb.GetTrie().(*trie.VerkleTrie); ok {
+ return parentRoot, statedb
+ }
+ }
+ statedb, _ = state.New(mptRoot, sdb)
+ return parentRoot, statedb
}
func rlpHash(x interface{}) (h common.Hash) {
diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go
index f2606c86d18..52f7b05a20f 100644
--- a/cmd/evm/internal/t8ntool/flags.go
+++ b/cmd/evm/internal/t8ntool/flags.go
@@ -88,6 +88,22 @@ var (
"\t - into the file ",
Value: "block.json",
}
+ OutputVKTFlag = &cli.StringFlag{
+ Name: "output.vkt",
+ Usage: "Determines where to put the `VKT` of the post-state.\n" +
+ "\t`stdout` - into the stdout output\n" +
+ "\t`stderr` - into the stderr output\n" +
+ "\t - into the file ",
+ Value: "vkt.json",
+ }
+ OutputWitnessFlag = &cli.StringFlag{
+ Name: "output.witness",
+ Usage: "Determines where to put the `witness` of the post-state.\n" +
+ "\t`stdout` - into the stdout output\n" +
+ "\t`stderr` - into the stderr output\n" +
+ "\t - into the file ",
+ Value: "witness.json",
+ }
InputAllocFlag = &cli.StringFlag{
Name: "input.alloc",
Usage: "`stdin` or file name of where to find the prestate alloc to use.",
@@ -123,6 +139,10 @@ var (
Usage: "`stdin` or file name of where to find the transactions list in RLP form.",
Value: "txs.rlp",
}
+ InputVKTFlag = &cli.StringFlag{
+ Name: "input.vkt",
+ Usage: "`stdin` or file name of where to find the prestate VKT.",
+ }
SealCliqueFlag = &cli.StringFlag{
Name: "seal.clique",
Usage: "Seal block with Clique. `stdin` or file name of where to find the Clique sealing data.",
diff --git a/cmd/evm/internal/t8ntool/gen_stenv.go b/cmd/evm/internal/t8ntool/gen_stenv.go
index d47db4a8765..0c296395e2e 100644
--- a/cmd/evm/internal/t8ntool/gen_stenv.go
+++ b/cmd/evm/internal/t8ntool/gen_stenv.go
@@ -37,6 +37,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
+ ParentHash *common.Hash `json:"parentHash,omitempty"`
}
var enc stEnv
enc.Coinbase = common.UnprefixedAddress(s.Coinbase)
@@ -59,6 +60,7 @@ func (s stEnv) MarshalJSON() ([]byte, error) {
enc.ParentExcessBlobGas = (*math.HexOrDecimal64)(s.ParentExcessBlobGas)
enc.ParentBlobGasUsed = (*math.HexOrDecimal64)(s.ParentBlobGasUsed)
enc.ParentBeaconBlockRoot = s.ParentBeaconBlockRoot
+ enc.ParentHash = s.ParentHash
return json.Marshal(&enc)
}
@@ -85,6 +87,7 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"`
ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"`
ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"`
+ ParentHash *common.Hash `json:"parentHash,omitempty"`
}
var dec stEnv
if err := json.Unmarshal(input, &dec); err != nil {
@@ -154,5 +157,8 @@ func (s *stEnv) UnmarshalJSON(input []byte) error {
if dec.ParentBeaconBlockRoot != nil {
s.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot
}
+ if dec.ParentHash != nil {
+ s.ParentHash = dec.ParentHash
+ }
return nil
}
diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go
index e946ccddd56..6036f868b2a 100644
--- a/cmd/evm/internal/t8ntool/transition.go
+++ b/cmd/evm/internal/t8ntool/transition.go
@@ -75,10 +75,11 @@ var (
)
type input struct {
- Alloc types.GenesisAlloc `json:"alloc,omitempty"`
- Env *stEnv `json:"env,omitempty"`
- Txs []*txWithKey `json:"txs,omitempty"`
- TxRlp string `json:"txsRlp,omitempty"`
+ Alloc types.GenesisAlloc `json:"alloc,omitempty"`
+ Env *stEnv `json:"env,omitempty"`
+ VKT map[common.Hash]hexutil.Bytes `json:"vkt,omitempty"`
+ Txs []*txWithKey `json:"txs,omitempty"`
+ TxRlp string `json:"txsRlp,omitempty"`
}
func Transition(ctx *cli.Context) error {
@@ -90,16 +91,16 @@ func Transition(ctx *cli.Context) error {
// stdin input or in files.
// Check if anything needs to be read from stdin
var (
- prestate Prestate
- txIt txIterator // txs to apply
- allocStr = ctx.String(InputAllocFlag.Name)
-
+ prestate Prestate
+ txIt txIterator // txs to apply
+ allocStr = ctx.String(InputAllocFlag.Name)
+ vktStr = ctx.String(InputVKTFlag.Name)
envStr = ctx.String(InputEnvFlag.Name)
txStr = ctx.String(InputTxsFlag.Name)
inputData = &input{}
)
// Figure out the prestate alloc
- if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector {
+ if allocStr == stdinSelector || vktStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector {
decoder := json.NewDecoder(os.Stdin)
if err := decoder.Decode(inputData); err != nil {
return NewError(ErrorJson, fmt.Errorf("failed unmarshalling stdin: %v", err))
@@ -110,8 +111,18 @@ func Transition(ctx *cli.Context) error {
return err
}
}
+ if vktStr != stdinSelector {
+ if err := readFile(vktStr, "VKT", &inputData.Alloc); err != nil {
+ return err
+ }
+ }
prestate.Pre = inputData.Alloc
-
+ if vktStr != stdinSelector && vktStr != "" {
+ if err := readFile(vktStr, "VKT", &inputData.VKT); err != nil {
+ return err
+ }
+ }
+ prestate.VKT = inputData.VKT
// Set the block environment
if envStr != stdinSelector {
var env stEnv
@@ -183,8 +194,14 @@ func Transition(ctx *cli.Context) error {
}
// Dump the execution result
collector := make(Alloc)
- s.DumpToCollector(collector, nil)
- return dispatchOutput(ctx, baseDir, result, collector, body)
+ var vktleaves map[common.Hash]hexutil.Bytes
+ if !chainConfig.IsVerkle(big.NewInt(int64(prestate.Env.Number)), prestate.Env.Timestamp) {
+ s.DumpToCollector(collector, nil)
+ } else {
+ vktleaves = make(map[common.Hash]hexutil.Bytes)
+ s.DumpVKTLeaves(vktleaves)
+ }
+ return dispatchOutput(ctx, baseDir, result, collector, body, vktleaves)
}
func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error {
@@ -306,7 +323,7 @@ func saveFile(baseDir, filename string, data interface{}) error {
// dispatchOutput writes the output data to either stderr or stdout, or to the specified
// files
-func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes) error {
+func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes, vkt map[common.Hash]hexutil.Bytes) error {
stdOutObject := make(map[string]interface{})
stdErrObject := make(map[string]interface{})
dispatch := func(baseDir, fName, name string, obj interface{}) error {
@@ -333,6 +350,11 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a
if err := dispatch(baseDir, ctx.String(OutputBodyFlag.Name), "body", body); err != nil {
return err
}
+ if vkt != nil {
+ if err := dispatch(baseDir, ctx.String(OutputVKTFlag.Name), "vkt", vkt); err != nil {
+ return err
+ }
+ }
if len(stdOutObject) > 0 {
b, err := json.MarshalIndent(stdOutObject, "", " ")
if err != nil {
diff --git a/cmd/evm/main.go b/cmd/evm/main.go
index 61e46aa50e1..7f405a7df16 100644
--- a/cmd/evm/main.go
+++ b/cmd/evm/main.go
@@ -146,10 +146,13 @@ var (
t8ntool.TraceEnableCallFramesFlag,
t8ntool.OutputBasedir,
t8ntool.OutputAllocFlag,
+ t8ntool.OutputVKTFlag,
+ t8ntool.OutputWitnessFlag,
t8ntool.OutputResultFlag,
t8ntool.OutputBodyFlag,
t8ntool.InputAllocFlag,
t8ntool.InputEnvFlag,
+ t8ntool.InputVKTFlag,
t8ntool.InputTxsFlag,
t8ntool.ForknameFlag,
t8ntool.ChainIDFlag,
diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go
index f0be52a0dff..cfe0a5e6890 100644
--- a/cmd/geth/snapshot.go
+++ b/cmd/geth/snapshot.go
@@ -98,6 +98,17 @@ data, and verifies that all snapshot storage data has a corresponding account.
Description: `
geth snapshot inspect-account checks all snapshot layers and prints out
information about the specified address.
+`,
+ },
+ {
+ Name: "inspect-garys-account",
+ Usage: "Check all snapshot layers for the specific account",
+ ArgsUsage: "",
+ Action: checkGarysAccounts,
+ Flags: slices.Concat(utils.NetworkFlags, utils.DatabaseFlags),
+ Description: `
+geth snapshot inspect-account checks all snapshot layers and prints out
+information about the specified address.
`,
},
{
@@ -692,3 +703,72 @@ func checkAccount(ctx *cli.Context) error {
log.Info("Checked the snapshot journalled storage", "time", common.PrettyDuration(time.Since(start)))
return nil
}
+
+func checkGarysAccounts(ctx *cli.Context) error {
+ stack, _ := makeConfigNode(ctx)
+ defer stack.Close()
+
+ chaindb := utils.MakeChainDatabase(ctx, stack, true)
+ defer chaindb.Close()
+
+ triedb := utils.MakeTrieDatabase(ctx, chaindb, false, true, false)
+ defer triedb.Close()
+
+ var root common.Hash
+ if ctx.NArg() == 1 {
+ rootBytes := common.FromHex(ctx.Args().Get(1))
+ if len(rootBytes) != common.HashLength {
+ return fmt.Errorf("invalid hash: %s", ctx.Args().Get(1))
+ }
+ root = common.BytesToHash(rootBytes)
+ } else {
+ headBlock := rawdb.ReadHeadBlock(chaindb)
+ if headBlock == nil {
+ log.Error("Failed to load head block")
+ return errors.New("no head block")
+ }
+ root = headBlock.Root()
+ }
+ snapConfig := snapshot.Config{
+ CacheSize: 256,
+ Recovery: false,
+ NoBuild: true,
+ AsyncBuild: false,
+ }
+ snaptree, err := snapshot.New(snapConfig, chaindb, triedb, root)
+ if err != nil {
+ return err
+ }
+ accIt, err := snaptree.AccountIterator(root, common.Hash{})
+ if err != nil {
+ log.Error("Failed to create account iterator", "error", err)
+ return err
+ }
+ defer accIt.Release()
+
+ // Open the file in append mode, create it if it doesn't exist
+ file, err := os.OpenFile("weird_contracts.csv", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ file.WriteString("account hash,nonce,code hash,root hash,balance\n")
+
+ var weird int
+ for accIt.Next() {
+ acc, err := types.FullAccount(accIt.Account())
+ if err != nil {
+ log.Error("Failed to get full account", "error", err)
+ return err
+ }
+
+ if acc.Nonce == 0 && bytes.Equal(acc.CodeHash, types.EmptyCodeHash[:]) && acc.Root != types.EmptyRootHash {
+ weird++
+ if _, err = file.WriteString(fmt.Sprintf("%x,%d,%x,%x,%s\n", accIt.Hash(), acc.Nonce, acc.CodeHash, acc.Root, acc.Balance.String())); err != nil {
+ return err
+ }
+ }
+ }
+ log.Info("sweep completed", "count", weird)
+ return nil
+}
diff --git a/core/state/dump.go b/core/state/dump.go
index c9aad4f8e23..3005c0810b3 100644
--- a/core/state/dump.go
+++ b/core/state/dump.go
@@ -208,6 +208,18 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
return nextKey
}
+func (s *StateDB) DumpVKTLeaves(collector map[common.Hash]hexutil.Bytes) {
+ it, err := s.trie.(*trie.VerkleTrie).NodeIterator(nil)
+ if err != nil {
+ panic(err)
+ }
+ for it.Next(true) {
+ if it.Leaf() {
+ collector[common.BytesToHash(it.LeafKey())] = it.LeafBlob()
+ }
+ }
+}
+
// RawDump returns the state. If the processing is aborted e.g. due to options
// reaching Max, the `Next` key is set on the returned Dump.
func (s *StateDB) RawDump(opts *DumpConfig) Dump {
diff --git a/tests/block_test_util.go b/tests/block_test_util.go
index 77bf945e40e..e42e69ad725 100644
--- a/tests/block_test_util.go
+++ b/tests/block_test_util.go
@@ -117,19 +117,20 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t
return UnsupportedForkError{t.json.Network}
}
// import pre accounts & construct test genesis block & state root
+ // Commit genesis state
+ gspec := t.genesis(config)
var (
db = rawdb.NewMemoryDatabase()
tconf = &triedb.Config{
Preimages: true,
+ IsVerkle: gspec.Config.VerkleTime != nil && *gspec.Config.VerkleTime <= gspec.Timestamp,
}
)
- if scheme == rawdb.PathScheme {
+ if scheme == rawdb.PathScheme || tconf.IsVerkle {
tconf.PathDB = pathdb.Defaults
} else {
tconf.HashDB = hashdb.Defaults
}
- // Commit genesis state
- gspec := t.genesis(config)
// if ttd is not specified, set an arbitrary huge value
if gspec.Config.TerminalTotalDifficulty == nil {
diff --git a/tests/init.go b/tests/init.go
index a8bc424fa2c..2db7ca1b75c 100644
--- a/tests/init.go
+++ b/tests/init.go
@@ -464,6 +464,45 @@ var Forks = map[string]*params.ChainConfig{
Osaka: params.DefaultOsakaBlobConfig,
},
},
+
+ "Verkle": {
+ ChainID: big.NewInt(1),
+ HomesteadBlock: big.NewInt(0),
+ EIP150Block: big.NewInt(0),
+ EIP155Block: big.NewInt(0),
+ EIP158Block: big.NewInt(0),
+ ByzantiumBlock: big.NewInt(0),
+ ConstantinopleBlock: big.NewInt(0),
+ PetersburgBlock: big.NewInt(0),
+ IstanbulBlock: big.NewInt(0),
+ MuirGlacierBlock: big.NewInt(0),
+ BerlinBlock: big.NewInt(0),
+ LondonBlock: big.NewInt(0),
+ ArrowGlacierBlock: big.NewInt(0),
+ MergeNetsplitBlock: big.NewInt(0),
+ TerminalTotalDifficulty: big.NewInt(0),
+ ShanghaiTime: u64(0),
+ VerkleTime: u64(0),
+ },
+ "ShanghaiToVerkleAtTime32": {
+ ChainID: big.NewInt(1),
+ HomesteadBlock: big.NewInt(0),
+ EIP150Block: big.NewInt(0),
+ EIP155Block: big.NewInt(0),
+ EIP158Block: big.NewInt(0),
+ ByzantiumBlock: big.NewInt(0),
+ ConstantinopleBlock: big.NewInt(0),
+ PetersburgBlock: big.NewInt(0),
+ IstanbulBlock: big.NewInt(0),
+ MuirGlacierBlock: big.NewInt(0),
+ BerlinBlock: big.NewInt(0),
+ LondonBlock: big.NewInt(0),
+ ArrowGlacierBlock: big.NewInt(0),
+ MergeNetsplitBlock: big.NewInt(0),
+ TerminalTotalDifficulty: big.NewInt(0),
+ ShanghaiTime: u64(0),
+ VerkleTime: u64(32),
+ },
}
// AvailableForks returns the set of defined fork names