From f692f252c0b5d71ac13ef61b30455f331ce21bcc Mon Sep 17 00:00:00 2001
From: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
Date: Tue, 5 Aug 2025 22:12:22 +0200
Subject: [PATCH 1/5] save state
Signed-off-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
---
core/state/reader.go | 14 ++-
trie/transition.go | 197 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 210 insertions(+), 1 deletion(-)
create mode 100644 trie/transition.go
diff --git a/core/state/reader.go b/core/state/reader.go
index f56a1bfae1e..718a5322e46 100644
--- a/core/state/reader.go
+++ b/core/state/reader.go
@@ -23,6 +23,7 @@ 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/types"
"github.com/ethereum/go-ethereum/crypto"
@@ -241,8 +242,19 @@ func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCach
if !db.IsVerkle() {
tr, err = trie.NewStateTrie(trie.StateTrieID(root), db)
} else {
- // TODO @gballet determine the trie type (verkle or overlay) by transition state
tr, err = trie.NewVerkleTrie(root, db, cache)
+
+ // Based on the transition status, determine if the overlay
+ // tree needs to be created, or if a single, target tree is
+ // to be picked.
+ ts := overlay.LoadTransitionState(db.Disk(), root, true)
+ if ts.InTransition() {
+ mpt, err := trie.NewStateTrie(trie.StateTrieID(ts.BaseRoot), db)
+ if err != nil {
+ return nil, err
+ }
+ tr = trie.NewTransitionTree(mpt.(*trie.SecureTrie), tr.(*trie.VerkleTrie), false), nil
+ }
}
if err != nil {
return nil, err
diff --git a/trie/transition.go b/trie/transition.go
new file mode 100644
index 00000000000..6bf6a632889
--- /dev/null
+++ b/trie/transition.go
@@ -0,0 +1,197 @@
+// Copyright 2021 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 trie
+
+import (
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/ethdb"
+ "github.com/ethereum/go-ethereum/trie/trienode"
+ "github.com/ethereum/go-verkle"
+)
+
+type TransitionTrie struct {
+ overlay *VerkleTrie
+ base *SecureTrie
+ storage bool
+}
+
+func NewTransitionTree(base *SecureTrie, overlay *VerkleTrie, st bool) *TransitionTrie {
+ return &TransitionTrie{
+ overlay: overlay,
+ base: base,
+ storage: st,
+ }
+}
+
+func (t *TransitionTrie) Base() *SecureTrie {
+ return t.base
+}
+
+// TODO(gballet/jsign): consider removing this API.
+func (t *TransitionTrie) Overlay() *VerkleTrie {
+ return t.overlay
+}
+
+// GetKey returns the sha3 preimage of a hashed key that was previously used
+// to store a value.
+//
+// TODO(fjl): remove this when StateTrie is removed
+func (t *TransitionTrie) GetKey(key []byte) []byte {
+ if key := t.overlay.GetKey(key); key != nil {
+ return key
+ }
+ return t.base.GetKey(key)
+}
+
+// Get returns the value for key stored in the trie. The value bytes must
+// not be modified by the caller. If a node was not found in the database, a
+// trie.MissingNodeError is returned.
+func (t *TransitionTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
+ val, err := t.overlay.GetStorage(addr, key)
+ if err != nil {
+ return nil, fmt.Errorf("get storage from overlay: %s", err)
+ }
+ if len(val) != 0 {
+ return val, nil
+ }
+ // TODO also insert value into overlay
+ return t.base.GetStorage(addr, key)
+}
+
+// GetAccount abstract an account read from the trie.
+func (t *TransitionTrie) GetAccount(address common.Address) (*types.StateAccount, error) {
+ data, err := t.overlay.GetAccount(address)
+ if err != nil {
+ // Post cancun, no indicator needs to be used to indicate that
+ // an account was deleted in the overlay tree. If an error is
+ // returned, then it's a genuine error, and not an indicator
+ // that a tombstone was found.
+ return nil, err
+ }
+ if data != nil {
+ if t.overlay.db.HasStorageRootConversion(address) {
+ data.Root = t.overlay.db.StorageRootConversion(address)
+ }
+ return data, nil
+ }
+ return t.base.GetAccount(address)
+}
+
+// Update associates key with value in the trie. If value has length zero, any
+// existing value is deleted from the trie. The value bytes must not be modified
+// by the caller while they are stored in the trie. If a node was not found in the
+// database, a trie.MissingNodeError is returned.
+func (t *TransitionTrie) UpdateStorage(address common.Address, key []byte, value []byte) error {
+ var v []byte
+ if len(value) >= 32 {
+ v = value[:32]
+ } else {
+ var val [32]byte
+ copy(val[32-len(value):], value[:])
+ v = val[:]
+ }
+ return t.overlay.UpdateStorage(address, key, v)
+}
+
+// UpdateAccount abstract an account write to the trie.
+func (t *TransitionTrie) UpdateAccount(addr common.Address, account *types.StateAccount, codeLen int) error {
+ if account.Root != (common.Hash{}) && account.Root != types.EmptyRootHash {
+ t.overlay.db.SetStorageRootConversion(addr, account.Root)
+ }
+ return t.overlay.UpdateAccount(addr, account, codeLen)
+}
+
+// Delete removes any existing value for key from the trie. If a node was not
+// found in the database, a trie.MissingNodeError is returned.
+func (t *TransitionTrie) DeleteStorage(addr common.Address, key []byte) error {
+ return t.overlay.DeleteStorage(addr, key)
+}
+
+// DeleteAccount abstracts an account deletion from the trie.
+func (t *TransitionTrie) DeleteAccount(key common.Address) error {
+ return t.overlay.DeleteAccount(key)
+}
+
+// Hash returns the root hash of the trie. It does not write to the database and
+// can be used even if the trie doesn't have one.
+func (t *TransitionTrie) Hash() common.Hash {
+ return t.overlay.Hash()
+}
+
+// Commit collects all dirty nodes in the trie and replace them with the
+// corresponding node hash. All collected nodes(including dirty leaves if
+// collectLeaf is true) will be encapsulated into a nodeset for return.
+// The returned nodeset can be nil if the trie is clean(nothing to commit).
+// Once the trie is committed, it's not usable anymore. A new trie must
+// be created with new root and updated trie database for following usage
+func (t *TransitionTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
+ // Just return if the trie is a storage trie: otherwise,
+ // the overlay trie will be committed as many times as
+ // there are storage tries. This would kill performance.
+ if t.storage {
+ return common.Hash{}, nil
+ }
+ return t.overlay.Commit(collectLeaf)
+}
+
+// NodeIterator returns an iterator that returns nodes of the trie. Iteration
+// starts at the key after the given start key.
+func (t *TransitionTrie) NodeIterator(startKey []byte) (NodeIterator, error) {
+ panic("not implemented") // TODO: Implement
+}
+
+// Prove constructs a Merkle proof for key. The result contains all encoded nodes
+// on the path to the value at key. The value itself is also included in the last
+// node and can be retrieved by verifying the proof.
+//
+// If the trie does not contain a value for key, the returned proof contains all
+// nodes of the longest existing prefix of the key (at least the root), ending
+// with the node that proves the absence of the key.
+func (t *TransitionTrie) Prove(key []byte, proofDb ethdb.KeyValueWriter) error {
+ panic("not implemented") // TODO: Implement
+}
+
+// IsVerkle returns true if the trie is verkle-tree based
+func (t *TransitionTrie) IsVerkle() bool {
+ // For all intents and purposes, the calling code should treat this as a verkle trie
+ return true
+}
+
+func (t *TransitionTrie) UpdateStem(key []byte, values [][]byte) error {
+ trie := t.overlay
+ switch root := trie.root.(type) {
+ case *verkle.InternalNode:
+ return root.InsertValuesAtStem(key, values, t.overlay.FlatdbNodeResolver)
+ default:
+ panic("invalid root type")
+ }
+}
+
+func (t *TransitionTrie) Copy() *TransitionTrie {
+ return &TransitionTrie{
+ overlay: t.overlay.Copy(),
+ base: t.base.Copy(),
+ storage: t.storage,
+ }
+}
+
+func (t *TransitionTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
+ return t.overlay.UpdateContractCode(addr, codeHash, code)
+}
From 7b378bab390e36c0379cbcfcb72bc2a82a3eb849 Mon Sep 17 00:00:00 2001
From: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
Date: Thu, 7 Aug 2025 17:31:40 +0200
Subject: [PATCH 2/5] trie, core/state: add the transition tree
---
core/state/reader.go | 2 +-
trie/transition.go | 14 ++++++++------
2 files changed, 9 insertions(+), 7 deletions(-)
diff --git a/core/state/reader.go b/core/state/reader.go
index 718a5322e46..4fc67ebd601 100644
--- a/core/state/reader.go
+++ b/core/state/reader.go
@@ -253,7 +253,7 @@ func newTrieReader(root common.Hash, db *triedb.Database, cache *utils.PointCach
if err != nil {
return nil, err
}
- tr = trie.NewTransitionTree(mpt.(*trie.SecureTrie), tr.(*trie.VerkleTrie), false), nil
+ tr = trie.NewTransitionTree(mpt, tr.(*trie.VerkleTrie), false)
}
}
if err != nil {
diff --git a/trie/transition.go b/trie/transition.go
index 6bf6a632889..484b1933e8a 100644
--- a/trie/transition.go
+++ b/trie/transition.go
@@ -86,9 +86,6 @@ func (t *TransitionTrie) GetAccount(address common.Address) (*types.StateAccount
return nil, err
}
if data != nil {
- if t.overlay.db.HasStorageRootConversion(address) {
- data.Root = t.overlay.db.StorageRootConversion(address)
- }
return data, nil
}
return t.base.GetAccount(address)
@@ -112,9 +109,9 @@ func (t *TransitionTrie) UpdateStorage(address common.Address, key []byte, value
// UpdateAccount abstract an account write to the trie.
func (t *TransitionTrie) UpdateAccount(addr common.Address, account *types.StateAccount, codeLen int) error {
- if account.Root != (common.Hash{}) && account.Root != types.EmptyRootHash {
- t.overlay.db.SetStorageRootConversion(addr, account.Root)
- }
+ // NOTE: before the rebase, this was saving the state root, so that OpenStorageTrie
+ // could still work during a replay. This is no longer needed, as OpenStorageTrie
+ // only needs to know what the account trie does now.
return t.overlay.UpdateAccount(addr, account, codeLen)
}
@@ -195,3 +192,8 @@ func (t *TransitionTrie) Copy() *TransitionTrie {
func (t *TransitionTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
return t.overlay.UpdateContractCode(addr, codeHash, code)
}
+
+// Witness returns a set containing all trie nodes that have been accessed.
+func (t *TransitionTrie) Witness() map[string]struct{} {
+ panic("not implemented")
+}
From b87aa75936fc44e6d1a63d8f40a6cfc35399e614 Mon Sep 17 00:00:00 2001
From: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
Date: Tue, 12 Aug 2025 22:34:43 +0200
Subject: [PATCH 3/5] give comment headers to all TransitionTrie methods
---
trie/transition.go | 26 ++++++++++++++++----------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/trie/transition.go b/trie/transition.go
index 484b1933e8a..d7499d8a993 100644
--- a/trie/transition.go
+++ b/trie/transition.go
@@ -26,12 +26,17 @@ import (
"github.com/ethereum/go-verkle"
)
+// TransitionTrie is a trie that implements a façade design pattern, presenting
+// a single interface to the old MPT trie and the new verkle/binary trie. Reads
+// first from the overlay trie, and falls back to the base trie if the key isn't
+// found. All writes go to the overlay trie.
type TransitionTrie struct {
overlay *VerkleTrie
base *SecureTrie
storage bool
}
+// NewTransitionTrie creates a new TransitionTrie.
func NewTransitionTree(base *SecureTrie, overlay *VerkleTrie, st bool) *TransitionTrie {
return &TransitionTrie{
overlay: overlay,
@@ -40,19 +45,18 @@ func NewTransitionTree(base *SecureTrie, overlay *VerkleTrie, st bool) *Transiti
}
}
+// Base returns the base trie.
func (t *TransitionTrie) Base() *SecureTrie {
return t.base
}
-// TODO(gballet/jsign): consider removing this API.
+// Overlay returns the overlay trie.
func (t *TransitionTrie) Overlay() *VerkleTrie {
return t.overlay
}
// GetKey returns the sha3 preimage of a hashed key that was previously used
// to store a value.
-//
-// TODO(fjl): remove this when StateTrie is removed
func (t *TransitionTrie) GetKey(key []byte) []byte {
if key := t.overlay.GetKey(key); key != nil {
return key
@@ -60,9 +64,8 @@ func (t *TransitionTrie) GetKey(key []byte) []byte {
return t.base.GetKey(key)
}
-// Get returns the value for key stored in the trie. The value bytes must
-// not be modified by the caller. If a node was not found in the database, a
-// trie.MissingNodeError is returned.
+// GetStorage returns the value for key stored in the trie. The value bytes must
+// not be modified by the caller.
func (t *TransitionTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) {
val, err := t.overlay.GetStorage(addr, key)
if err != nil {
@@ -91,10 +94,9 @@ func (t *TransitionTrie) GetAccount(address common.Address) (*types.StateAccount
return t.base.GetAccount(address)
}
-// Update associates key with value in the trie. If value has length zero, any
+// UpdateStorage associates key with value in the trie. If value has length zero, any
// existing value is deleted from the trie. The value bytes must not be modified
-// by the caller while they are stored in the trie. If a node was not found in the
-// database, a trie.MissingNodeError is returned.
+// by the caller while they are stored in the trie.
func (t *TransitionTrie) UpdateStorage(address common.Address, key []byte, value []byte) error {
var v []byte
if len(value) >= 32 {
@@ -115,7 +117,7 @@ func (t *TransitionTrie) UpdateAccount(addr common.Address, account *types.State
return t.overlay.UpdateAccount(addr, account, codeLen)
}
-// Delete removes any existing value for key from the trie. If a node was not
+// DeleteStorage removes any existing value for key from the trie. If a node was not
// found in the database, a trie.MissingNodeError is returned.
func (t *TransitionTrie) DeleteStorage(addr common.Address, key []byte) error {
return t.overlay.DeleteStorage(addr, key)
@@ -171,6 +173,8 @@ func (t *TransitionTrie) IsVerkle() bool {
return true
}
+// UpdateStems updates a group of values, given the stem they are using. If
+// a value already exists, it is overwritten.
func (t *TransitionTrie) UpdateStem(key []byte, values [][]byte) error {
trie := t.overlay
switch root := trie.root.(type) {
@@ -181,6 +185,7 @@ func (t *TransitionTrie) UpdateStem(key []byte, values [][]byte) error {
}
}
+// Copy creates a deep copy of the transition trie.
func (t *TransitionTrie) Copy() *TransitionTrie {
return &TransitionTrie{
overlay: t.overlay.Copy(),
@@ -189,6 +194,7 @@ func (t *TransitionTrie) Copy() *TransitionTrie {
}
}
+// UpdateContractCode updates the contract code for the given address.
func (t *TransitionTrie) UpdateContractCode(addr common.Address, codeHash common.Hash, code []byte) error {
return t.overlay.UpdateContractCode(addr, codeHash, code)
}
From 496ed4943fc64b66c50a11e69d516da715b6c809 Mon Sep 17 00:00:00 2001
From: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
Date: Wed, 13 Aug 2025 22:56:15 +0200
Subject: [PATCH 4/5] fix: update nodeResolver reference in UpdateStem method
(rebase issue)
---
trie/transition.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/trie/transition.go b/trie/transition.go
index d7499d8a993..c509eedb596 100644
--- a/trie/transition.go
+++ b/trie/transition.go
@@ -179,7 +179,7 @@ func (t *TransitionTrie) UpdateStem(key []byte, values [][]byte) error {
trie := t.overlay
switch root := trie.root.(type) {
case *verkle.InternalNode:
- return root.InsertValuesAtStem(key, values, t.overlay.FlatdbNodeResolver)
+ return root.InsertValuesAtStem(key, values, t.overlay.nodeResolver)
default:
panic("invalid root type")
}
From 929b6c21cec9a5c3b806c5eb200799193c1ae78a Mon Sep 17 00:00:00 2001
From: rjl493456442
Date: Fri, 15 Aug 2025 14:33:19 +0800
Subject: [PATCH 5/5] Update transition.go
---
trie/transition.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/trie/transition.go b/trie/transition.go
index c509eedb596..ad3f782b750 100644
--- a/trie/transition.go
+++ b/trie/transition.go
@@ -1,4 +1,4 @@
-// Copyright 2021 go-ethereum Authors
+// Copyright 2025 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