Skip to content
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

all: distinguish empty state root in merkle and verkle #30896

Open
wants to merge 3 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
2 changes: 1 addition & 1 deletion cmd/devp2p/internal/ethtest/snap.go
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ func (s *Suite) TestSnapGetByteCodes(t *utesting.T) {
{
desc: `Here we request the empty state root (which is not an existing code hash). The server should deliver an empty response with no items.`,
nBytes: 10000,
hashes: []common.Hash{types.EmptyRootHash},
hashes: []common.Hash{types.EmptyRootHash}, // TODO add verkle tests
expHashes: 0,
},
{
Expand Down
2 changes: 1 addition & 1 deletion cmd/evm/internal/t8ntool/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
func MakePreState(db ethdb.Database, accounts types.GenesisAlloc) *state.StateDB {
tdb := triedb.NewDatabase(db, &triedb.Config{Preimages: true})
sdb := state.NewDatabase(tdb, nil)
statedb, _ := state.New(types.EmptyRootHash, sdb)
statedb, _ := state.New(types.EmptyRootHash, sdb) // TODO support verkle node in t8n
for addr, a := range accounts {
statedb.SetCode(addr, a.Code)
statedb.SetNonce(addr, a.Nonce)
Expand Down
5 changes: 5 additions & 0 deletions cmd/geth/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ func checkDanglingStorage(ctx *cli.Context) error {
// traverseState is a helper function used for pruning verification.
// Basically it just iterates the trie, ensure all nodes and associated
// contract codes are present.
//
// TODO(rjl493456442) this command is not compatible with verkle.
func traverseState(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
Expand Down Expand Up @@ -374,6 +376,8 @@ func traverseState(ctx *cli.Context) error {
// Basically it just iterates the trie, ensure all nodes and associated
// contract codes are present. It's basically identical to traverseState
// but it will check each trie node.
//
// TODO(rjl493456442) this command is not compatible with verkle.
func traverseRawState(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
Expand Down Expand Up @@ -537,6 +541,7 @@ func parseRoot(input string) (common.Hash, error) {
return h, nil
}

// dumpState is not compatible with verkle mode.
func dumpState(ctx *cli.Context) error {
stack, _ := makeConfigNode(ctx)
defer stack.Close()
Expand Down
2 changes: 2 additions & 0 deletions cmd/utils/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,8 @@ func ExportPreimages(db ethdb.Database, fn string) error {

// ExportSnapshotPreimages exports the preimages corresponding to the enumeration of
// the snapshot for a given root.
//
// TODO(rjl493456442) this command is not compatible with verkle.
func ExportSnapshotPreimages(chaindb ethdb.Database, snaptree *snapshot.Tree, fn string, root common.Hash) error {
log.Info("Exporting preimages", "file", fn)

Expand Down
11 changes: 7 additions & 4 deletions core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
// Create an ephemeral in-memory database for computing hash,
// all the derived states will be discarded to not pollute disk.
db := rawdb.NewMemoryDatabase()
statedb, err := state.New(types.EmptyRootHash, state.NewDatabase(triedb.NewDatabase(db, config), nil))
statedb, err := state.New(types.EmptyTreeRootHash(isVerkle), state.NewDatabase(triedb.NewDatabase(db, config), nil))
if err != nil {
return common.Hash{}, err
}
Expand All @@ -148,7 +148,7 @@ func hashAlloc(ga *types.GenesisAlloc, isVerkle bool) (common.Hash, error) {
// flushAlloc is very similar with hash, but the main difference is all the
// generated states will be persisted into the given database.
func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, error) {
statedb, err := state.New(types.EmptyRootHash, state.NewDatabase(triedb, nil))
statedb, err := state.New(types.EmptyTreeRootHash(triedb.IsVerkle()), state.NewDatabase(triedb, nil))
if err != nil {
return common.Hash{}, err
}
Expand All @@ -169,7 +169,7 @@ func flushAlloc(ga *types.GenesisAlloc, triedb *triedb.Database) (common.Hash, e
return common.Hash{}, err
}
// Commit newly generated states into disk if it's not empty.
if root != types.EmptyRootHash {
if root != types.EmptyTreeRootHash(triedb.IsVerkle()) {
if err := triedb.Commit(root, true); err != nil {
return common.Hash{}, err
}
Expand Down Expand Up @@ -292,7 +292,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, triedb *triedb.Database, g
// is initialized with an external ancient store. Commit genesis state
// in this case.
header := rawdb.ReadHeader(db, stored, 0)
if header.Root != types.EmptyRootHash && !triedb.Initialized(header.Root) {
if header.Root != types.EmptyTreeRootHash(triedb.IsVerkle()) && !triedb.Initialized(header.Root) {
if genesis == nil {
genesis = DefaultGenesisBlock()
}
Expand Down Expand Up @@ -494,6 +494,9 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *triedb.Database) (*types.Blo
if config.Clique != nil && len(g.ExtraData) < 32+crypto.SignatureLength {
return nil, errors.New("can't start clique chain without signers")
}
if g.IsVerkle() != triedb.IsVerkle() {
return nil, fmt.Errorf("supplied triedb is in wrong mode, verkle genesis: %v, verkle triedb: %v", g.IsVerkle(), triedb.IsVerkle())
}
// flush the data to disk and compute the state root
root, err := flushAlloc(&g.Alloc, triedb)
if err != nil {
Expand Down
9 changes: 8 additions & 1 deletion core/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,18 @@ func TestGenesisHashes(t *testing.T) {
want common.Hash
}{
{DefaultGenesisBlock(), params.MainnetGenesisHash},
{DefaultHoleskyGenesisBlock(), params.HoleskyGenesisHash},
{DefaultSepoliaGenesisBlock(), params.SepoliaGenesisHash},

// TODO (rjl493456442, gballet) add verkle genesis tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can help you with that, but if all these changes are meant for verkle, we should include verkle tests as well. Otherwise we get in the same situation, when the base branch drifted and broke a ton of verkle use cases.

} {
// Test via MustCommit
db := rawdb.NewMemoryDatabase()
if have := c.genesis.MustCommit(db, triedb.NewDatabase(db, triedb.HashDefaults)).Hash(); have != c.want {
config := triedb.HashDefaults
if c.genesis.IsVerkle() {
config = triedb.VerkleDefaults
}
if have := c.genesis.MustCommit(db, triedb.NewDatabase(db, config)).Hash(); have != c.want {
t.Errorf("case: %d a), want: %s, got: %s", i, c.want.Hex(), have.Hex())
}
// Test via ToBlock
Expand Down
4 changes: 2 additions & 2 deletions core/state/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,14 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
// then construct the legacy snap reader.
snap := db.snap.Snapshot(stateRoot)
if snap != nil {
readers = append(readers, newFlatReader(snap))
readers = append(readers, newFlatReader(snap, db.triedb.IsVerkle()))
}
} else {
// If standalone state snapshot is not available, try to construct
// the state reader with database.
reader, err := db.triedb.StateReader(stateRoot)
if err == nil {
readers = append(readers, newFlatReader(reader)) // state reader is optional
readers = append(readers, newFlatReader(reader, db.triedb.IsVerkle())) // state reader is optional
}
}
// Set up the trie reader, which is expected to always be available
Expand Down
20 changes: 13 additions & 7 deletions core/state/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,17 @@ func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash)

// flatReader wraps a database state reader.
type flatReader struct {
reader database.StateReader
buff crypto.KeccakState
reader database.StateReader
buff crypto.KeccakState
isVerkle bool
}

// newFlatReader constructs a state reader with on the given state root.
func newFlatReader(reader database.StateReader) *flatReader {
func newFlatReader(reader database.StateReader, isVerkle bool) *flatReader {
return &flatReader{
reader: reader,
buff: crypto.NewKeccakState(),
reader: reader,
buff: crypto.NewKeccakState(),
isVerkle: isVerkle,
}
}

Expand All @@ -160,8 +162,9 @@ func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
if len(acct.CodeHash) == 0 {
acct.CodeHash = types.EmptyCodeHash.Bytes()
}
// Annotate the empty root hash, especially for merkle tree.
if acct.Root == (common.Hash{}) {
acct.Root = types.EmptyRootHash
acct.Root = types.EmptyTreeRootHash(r.isVerkle)
}
return acct, nil
}
Expand Down Expand Up @@ -240,8 +243,11 @@ func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
return nil, err
}
if account == nil {
r.subRoots[addr] = types.EmptyRootHash
r.subRoots[addr] = types.EmptyTreeRootHash(r.db.IsVerkle())
} else {
// The root hash will be these values if the storage is empty:
// - merkle: types.EmptyRootHash
// - verkle: types.EmptyVerkleHash
r.subRoots[addr] = account.Root
}
return account, nil
Expand Down
33 changes: 25 additions & 8 deletions core/state/state_object.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (s *stateObject) empty() bool {
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject {
origin := acct
if acct == nil {
acct = types.NewEmptyStateAccount()
acct = types.NewEmptyStateAccount(db.db.TrieDB().IsVerkle())
}
return &stateObject{
db: db,
Expand Down Expand Up @@ -142,7 +142,8 @@ func (s *stateObject) getTrie() (Trie, error) {
func (s *stateObject) getPrefetchedTrie() Trie {
// If there's nothing to meaningfully return, let the user figure it out by
// pulling the trie from disk.
if (s.data.Root == types.EmptyRootHash && !s.db.db.TrieDB().IsVerkle()) || s.db.prefetcher == nil {
isVerkle := s.db.db.TrieDB().IsVerkle()
if (s.data.Root == types.EmptyTreeRootHash(isVerkle) && !isVerkle) || s.db.prefetcher == nil {
return nil
}
// Attempt to retrieve the trie from the prefetcher
Expand Down Expand Up @@ -197,9 +198,17 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash {
s.db.StorageReads += time.Since(start)

// Schedule the resolved storage slots for prefetching if it's enabled.
if s.db.prefetcher != nil && s.data.Root != types.EmptyRootHash {
if err = s.db.prefetcher.prefetch(s.addrHash, s.origin.Root, s.address, nil, []common.Hash{key}, true); err != nil {
log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err)
// Verkle tree is always available for prefetching, or the storage trie is
// existent in merkle.
if s.db.prefetcher != nil {
if s.db.db.TrieDB().IsVerkle() {
if err = s.db.prefetcher.prefetch(s.addrHash, common.Hash{}, s.address, nil, []common.Hash{key}, true); err != nil {
log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err)
}
} else if s.data.Root != types.EmptyRootHash {
if err = s.db.prefetcher.prefetch(s.addrHash, s.origin.Root, s.address, nil, []common.Hash{key}, true); err != nil {
log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err)
}
}
}
s.originStorage[key] = value
Expand Down Expand Up @@ -258,9 +267,17 @@ func (s *stateObject) finalise() {
// byzantium fork) and entry is necessary to modify the value back.
s.pendingStorage[key] = value
}
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, nil, slotsToPrefetch, false); err != nil {
log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err)
// Verkle tree is always available for prefetching, or the storage trie is
// existent in merkle.
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 {
if s.db.db.TrieDB().IsVerkle() {
if err := s.db.prefetcher.prefetch(s.addrHash, common.Hash{}, s.address, nil, slotsToPrefetch, false); err != nil {
log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err)
}
} else if s.data.Root != types.EmptyRootHash {
if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, nil, slotsToPrefetch, false); err != nil {
log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err)
}
}
}
if len(s.dirtyStorage) > 0 {
Expand Down
6 changes: 4 additions & 2 deletions core/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -1075,8 +1075,10 @@ func (s *StateDB) handleDestruction() (map[common.Hash]*accountDelete, []*trieno
}
deletes[addrHash] = op

// Short circuit if the origin storage was empty.
if prev.Root == types.EmptyRootHash || s.db.TrieDB().IsVerkle() {
// Short circuit if the origin storage was empty. Notably, this
// condition is always true for verkle and storage deletion is
// not supported.
if prev.Root == types.EmptyTreeRootHash(s.db.TrieDB().IsVerkle()) {
continue
}
// Remove storage slots belonging to the account.
Expand Down
5 changes: 2 additions & 3 deletions core/state/stateupdate.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"maps"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb"
)
Expand Down Expand Up @@ -133,8 +132,8 @@ func newStateUpdate(originRoot common.Hash, root common.Hash, deletes map[common
}
}
return &stateUpdate{
originRoot: types.TrieRootHash(originRoot),
root: types.TrieRootHash(root),
originRoot: originRoot,
root: root,
accounts: accounts,
accountsOrigin: accountsOrigin,
storages: storages,
Expand Down
2 changes: 1 addition & 1 deletion core/state/trie_prefetcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func TestVerklePrefetcher(t *testing.T) {
db := triedb.NewDatabase(disk, triedb.VerkleDefaults)
sdb := NewDatabase(db, nil)

state, err := New(types.EmptyRootHash, sdb)
state, err := New(types.EmptyVerkleHash, sdb)
if err != nil {
t.Fatalf("failed to initialize state: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion core/txpool/blobpool/blobpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ func (p *BlobPool) Init(gasTip uint64, head *types.Header, reserve txpool.Addres
// fully synced).
state, err := p.chain.StateAt(head.Root)
if err != nil {
state, err = p.chain.StateAt(types.EmptyRootHash)
state, err = p.chain.StateAt(types.EmptyRootHash) // TODO (rjl493456442) support verkle
}
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion core/txpool/legacypool/legacypool.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ func (pool *LegacyPool) Init(gasTip uint64, head *types.Header, reserve txpool.A
// fully synced).
statedb, err := pool.chain.StateAt(head.Root)
if err != nil {
statedb, err = pool.chain.StateAt(types.EmptyRootHash)
statedb, err = pool.chain.StateAt(types.EmptyRootHash) // TODO (rjl493456442) support verkle
}
if err != nil {
return err
Expand Down
19 changes: 9 additions & 10 deletions core/types/hashes.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,9 @@ package types
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
)

var (
// EmptyRootHash is the known root hash of an empty merkle trie.
EmptyRootHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")

// EmptyUncleHash is the known hash of the empty uncle set.
EmptyUncleHash = rlpHash([]*Header(nil)) // 1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347

Expand All @@ -46,14 +42,17 @@ var (

// EmptyVerkleHash is the known hash of an empty verkle trie.
EmptyVerkleHash = common.Hash{}

// EmptyRootHash is the known root hash of an empty merkle trie.
//
// TODO(rjl493456442) rename it to EmptyMerkleHash.
EmptyRootHash = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
)

// TrieRootHash returns the hash itself if it's non-empty or the predefined
// emptyHash one instead.
func TrieRootHash(hash common.Hash) common.Hash {
if hash == (common.Hash{}) {
log.Error("Zero trie root hash!")
// EmptyTreeRootHash returns the empty root tree hash of the specific tree type.
func EmptyTreeRootHash(isVerkle bool) common.Hash {
if !isVerkle {
return EmptyRootHash
}
return hash
return EmptyVerkleHash
}
14 changes: 11 additions & 3 deletions core/types/state_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,14 @@ type StateAccount struct {
}

// NewEmptyStateAccount constructs an empty state account.
func NewEmptyStateAccount() *StateAccount {
func NewEmptyStateAccount(isVerkle bool) *StateAccount {
emptyRoot := EmptyRootHash
if isVerkle {
emptyRoot = EmptyVerkleHash
}
return &StateAccount{
Balance: new(uint256.Int),
Root: EmptyRootHash,
Root: emptyRoot,
CodeHash: EmptyCodeHash.Bytes(),
}
}
Expand Down Expand Up @@ -74,7 +78,11 @@ func SlimAccountRLP(account StateAccount) []byte {
Nonce: account.Nonce,
Balance: account.Balance,
}
if account.Root != EmptyRootHash {
// It is highly unlikely for a valid hash (value = [32]byte{}) to appear
// in a Merkle tree, or for a valid hash (value = EmptyRootHash) to appear
// in a Verkle tree. Therefore, in both cases, any other value is considered
// a non-empty root hash.
if account.Root != EmptyRootHash && account.Root != EmptyVerkleHash {
slim.Root = account.Root[:]
}
if !bytes.Equal(account.CodeHash, EmptyCodeHash[:]) {
Expand Down
2 changes: 1 addition & 1 deletion core/vm/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
storageRoot := evm.StateDB.GetStorageRoot(address)
if evm.StateDB.GetNonce(address) != 0 ||
(contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code
(storageRoot != (common.Hash{}) && storageRoot != types.EmptyRootHash) { // non-empty storage
(storageRoot != (common.Hash{}) && storageRoot != types.EmptyRootHash) { // non-empty storage TODO (rjl493456442) support verkle
if evm.Config.Tracer != nil && evm.Config.Tracer.OnGasChange != nil {
evm.Config.Tracer.OnGasChange(gas, 0, tracing.GasChangeCallFailedExecution)
}
Expand Down
4 changes: 4 additions & 0 deletions core/vm/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ func setDefaults(cfg *Config) {
//
// Execute sets up an in-memory, temporary, environment for the execution of
// the given code. It makes sure that it's restored to its original state afterwards.
//
// TODO (rjl493456442) support verkle mode.
func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
if cfg == nil {
cfg = new(Config)
Expand Down Expand Up @@ -156,6 +158,8 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
}

// Create executes the code using the EVM create method
//
// TODO (rjl493456442) support verkle mode.
func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
if cfg == nil {
cfg = new(Config)
Expand Down
Loading
Loading