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