From b530d8e4552a583eb89538799b31aa92e281b4ed Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Tue, 16 Jul 2024 18:52:19 +0800 Subject: [PATCH 1/4] trie, triedb: remove unnecessary child resolver interface (#30167) --- trie/committer.go | 8 ++------ triedb/database.go | 10 +--------- triedb/hashdb/database.go | 30 +++++++++++------------------- 3 files changed, 14 insertions(+), 34 deletions(-) diff --git a/trie/committer.go b/trie/committer.go index 4e2f7b8bd6a3..863e7bafdc4b 100644 --- a/trie/committer.go +++ b/trie/committer.go @@ -154,12 +154,8 @@ func (c *committer) store(path []byte, n node) node { return hash } -// MerkleResolver the children resolver in merkle-patricia-tree. -type MerkleResolver struct{} - -// ForEach implements childResolver, decodes the provided node and -// traverses the children inside. -func (resolver MerkleResolver) ForEach(node []byte, onChild func(common.Hash)) { +// ForGatherChildren decodes the provided node and traverses the children inside. +func ForGatherChildren(node []byte, onChild func(common.Hash)) { forGatherChildren(mustDecodeNodeUnsafe(nil, node), onChild) } diff --git a/triedb/database.go b/triedb/database.go index 91386a9dbcf5..1f9f38388b4d 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "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/triestate" "github.com/ethereum/go-ethereum/triedb/database" @@ -112,14 +111,7 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database { if config.PathDB != nil { db.backend = pathdb.New(diskdb, config.PathDB, config.IsVerkle) } else { - var resolver hashdb.ChildResolver - if config.IsVerkle { - // TODO define verkle resolver - log.Crit("verkle does not use a hash db") - } else { - resolver = trie.MerkleResolver{} - } - db.backend = hashdb.New(diskdb, config.HashDB, resolver) + db.backend = hashdb.New(diskdb, config.HashDB) } return db } diff --git a/triedb/hashdb/database.go b/triedb/hashdb/database.go index bb0deca9a713..4def10e338b1 100644 --- a/triedb/hashdb/database.go +++ b/triedb/hashdb/database.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/triestate" "github.com/ethereum/go-ethereum/triedb/database" @@ -60,12 +61,6 @@ var ( memcacheCommitBytesMeter = metrics.NewRegisteredMeter("hashdb/memcache/commit/bytes", nil) ) -// ChildResolver defines the required method to decode the provided -// trie node and iterate the children on top. -type ChildResolver interface { - ForEach(node []byte, onChild func(common.Hash)) -} - // Config contains the settings for database. type Config struct { CleanCacheSize int // Maximum memory allowance (in bytes) for caching clean nodes @@ -84,9 +79,7 @@ var Defaults = &Config{ // the disk database. The aim is to accumulate trie writes in-memory and only // periodically flush a couple tries to disk, garbage collecting the remainder. type Database struct { - diskdb ethdb.Database // Persistent storage for matured trie nodes - resolver ChildResolver // The handler to resolve children of nodes - + diskdb ethdb.Database // Persistent storage for matured trie nodes cleans *fastcache.Cache // GC friendly memory cache of clean node RLPs dirties map[common.Hash]*cachedNode // Data and references relationships of dirty trie nodes oldest common.Hash // Oldest tracked node, flush-list head @@ -124,15 +117,15 @@ var cachedNodeSize = int(reflect.TypeOf(cachedNode{}).Size()) // forChildren invokes the callback for all the tracked children of this node, // both the implicit ones from inside the node as well as the explicit ones // from outside the node. -func (n *cachedNode) forChildren(resolver ChildResolver, onChild func(hash common.Hash)) { +func (n *cachedNode) forChildren(onChild func(hash common.Hash)) { for child := range n.external { onChild(child) } - resolver.ForEach(n.node, onChild) + trie.ForGatherChildren(n.node, onChild) } // New initializes the hash-based node database. -func New(diskdb ethdb.Database, config *Config, resolver ChildResolver) *Database { +func New(diskdb ethdb.Database, config *Config) *Database { if config == nil { config = Defaults } @@ -141,10 +134,9 @@ func New(diskdb ethdb.Database, config *Config, resolver ChildResolver) *Databas cleans = fastcache.New(config.CleanCacheSize) } return &Database{ - diskdb: diskdb, - resolver: resolver, - cleans: cleans, - dirties: make(map[common.Hash]*cachedNode), + diskdb: diskdb, + cleans: cleans, + dirties: make(map[common.Hash]*cachedNode), } } @@ -163,7 +155,7 @@ func (db *Database) insert(hash common.Hash, node []byte) { node: node, flushPrev: db.newest, } - entry.forChildren(db.resolver, func(child common.Hash) { + entry.forChildren(func(child common.Hash) { if c := db.dirties[child]; c != nil { c.parents++ } @@ -316,7 +308,7 @@ func (db *Database) dereference(hash common.Hash) { db.dirties[node.flushNext].flushPrev = node.flushPrev } // Dereference all children and delete the node - node.forChildren(db.resolver, func(child common.Hash) { + node.forChildren(func(child common.Hash) { db.dereference(child) }) delete(db.dirties, hash) @@ -465,7 +457,7 @@ func (db *Database) commit(hash common.Hash, batch ethdb.Batch, uncacher *cleane var err error // Dereference all children and delete the node - node.forChildren(db.resolver, func(child common.Hash) { + node.forChildren(func(child common.Hash) { if err == nil { err = db.commit(child, batch, uncacher) } From 15936c64a26d624026ae2df953cc3d5369a8131f Mon Sep 17 00:00:00 2001 From: maskpp Date: Tue, 16 Jul 2024 19:42:30 +0800 Subject: [PATCH 2/4] core/txpool/legacypool: use maps.Keys and maps.Copy (#30091) --- core/txpool/legacypool/legacypool.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 4e1d26acf405..5506ecc31fc3 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -38,6 +38,7 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" + "golang.org/x/exp/maps" ) const ( @@ -1717,7 +1718,7 @@ func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] } type accountSet struct { accounts map[common.Address]struct{} signer types.Signer - cache *[]common.Address + cache []common.Address } // newAccountSet creates a new address set with an associated signer for sender @@ -1765,20 +1766,14 @@ func (as *accountSet) addTx(tx *types.Transaction) { // reuse. The returned slice should not be changed! func (as *accountSet) flatten() []common.Address { if as.cache == nil { - accounts := make([]common.Address, 0, len(as.accounts)) - for account := range as.accounts { - accounts = append(accounts, account) - } - as.cache = &accounts + as.cache = maps.Keys(as.accounts) } - return *as.cache + return as.cache } // merge adds all addresses from the 'other' set into 'as'. func (as *accountSet) merge(other *accountSet) { - for addr := range other.accounts { - as.accounts[addr] = struct{}{} - } + maps.Copy(as.accounts, other.accounts) as.cache = nil } From c54294bd41fdea30f715d9f0a4f3f9e44dcc9101 Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 16 Jul 2024 15:06:22 +0200 Subject: [PATCH 3/4] core/state: don't compute verkle storage tree roots (#30130) --- core/state/statedb.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 1664a446f4e2..641775b0bdfc 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -860,12 +860,16 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { } obj := s.stateObjects[addr] // closure for the task runner below workers.Go(func() error { - obj.updateRoot() + if s.db.TrieDB().IsVerkle() { + obj.updateTrie() + } else { + obj.updateRoot() - // If witness building is enabled and the state object has a trie, - // gather the witnesses for its specific storage trie - if s.witness != nil && obj.trie != nil { - s.witness.AddState(obj.trie.Witness()) + // If witness building is enabled and the state object has a trie, + // gather the witnesses for its specific storage trie + if s.witness != nil && obj.trie != nil { + s.witness.AddState(obj.trie.Witness()) + } } return nil }) From f59d013e4064adbb092b323781104c07550a8bff Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Tue, 16 Jul 2024 21:17:58 +0800 Subject: [PATCH 4/4] core/rawdb, triedb, cmd: create an isolated disk namespace for verkle (#30105) * core, triedb/pathdb, cmd: define verkle state ancient store * core/rawdb, triedb: add verkle namespace in pathdb --- cmd/geth/dbcmd.go | 3 ++- core/genesis_test.go | 6 +++--- core/rawdb/accessors_trie.go | 12 +++++++++++- core/rawdb/ancient_scheme.go | 17 ++++++++++++----- core/rawdb/ancient_utils.go | 6 +++--- core/rawdb/database.go | 24 ++++++++++++++++++++++++ core/rawdb/schema.go | 7 +++++++ triedb/database.go | 11 +++++++++-- triedb/pathdb/database.go | 10 +++++++++- triedb/pathdb/history_test.go | 8 ++++---- 10 files changed, 84 insertions(+), 20 deletions(-) diff --git a/cmd/geth/dbcmd.go b/cmd/geth/dbcmd.go index 4b683569a45e..052ae0eab2d5 100644 --- a/cmd/geth/dbcmd.go +++ b/cmd/geth/dbcmd.go @@ -248,7 +248,8 @@ func removeDB(ctx *cli.Context) error { // Delete state data statePaths := []string{ rootDir, - filepath.Join(ancientDir, rawdb.StateFreezerName), + filepath.Join(ancientDir, rawdb.MerkleStateFreezerName), + filepath.Join(ancientDir, rawdb.VerkleStateFreezerName), } confirmAndRemoveDB(statePaths, "state data", ctx, removeStateDataFlag.Name) diff --git a/core/genesis_test.go b/core/genesis_test.go index ab408327d4e6..002e58a961b9 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -311,7 +311,7 @@ func TestVerkleGenesisCommit(t *testing.T) { } db := rawdb.NewMemoryDatabase() - triedb := triedb.NewDatabase(db, &triedb.Config{IsVerkle: true, PathDB: pathdb.Defaults}) + triedb := triedb.NewDatabase(db, triedb.VerkleDefaults) block := genesis.MustCommit(db, triedb) if !bytes.Equal(block.Root().Bytes(), expected) { t.Fatalf("invalid genesis state root, expected %x, got %x", expected, block.Root()) @@ -321,8 +321,8 @@ func TestVerkleGenesisCommit(t *testing.T) { if !triedb.IsVerkle() { t.Fatalf("expected trie to be verkle") } - - if !rawdb.HasAccountTrieNode(db, nil) { + vdb := rawdb.NewTable(db, string(rawdb.VerklePrefix)) + if !rawdb.HasAccountTrieNode(vdb, nil) { t.Fatal("could not find node") } } diff --git a/core/rawdb/accessors_trie.go b/core/rawdb/accessors_trie.go index 44eb715d04e2..0f856d180457 100644 --- a/core/rawdb/accessors_trie.go +++ b/core/rawdb/accessors_trie.go @@ -245,7 +245,7 @@ func DeleteTrieNode(db ethdb.KeyValueWriter, owner common.Hash, path []byte, has // ReadStateScheme reads the state scheme of persistent state, or none // if the state is not present in database. -func ReadStateScheme(db ethdb.Reader) string { +func ReadStateScheme(db ethdb.Database) string { // Check if state in path-based scheme is present. if HasAccountTrieNode(db, nil) { return PathScheme @@ -255,6 +255,16 @@ func ReadStateScheme(db ethdb.Reader) string { if id := ReadPersistentStateID(db); id != 0 { return PathScheme } + // Check if verkle state in path-based scheme is present. + vdb := NewTable(db, string(VerklePrefix)) + if HasAccountTrieNode(vdb, nil) { + return PathScheme + } + // The root node of verkle might be deleted during the initial snap sync, + // check the persistent state id then. + if id := ReadPersistentStateID(vdb); id != 0 { + return PathScheme + } // In a hash-based scheme, the genesis state is consistently stored // on the disk. To assess the scheme of the persistent state, it // suffices to inspect the scheme of the genesis state. diff --git a/core/rawdb/ancient_scheme.go b/core/rawdb/ancient_scheme.go index 44867ded04ab..371fd384ada7 100644 --- a/core/rawdb/ancient_scheme.go +++ b/core/rawdb/ancient_scheme.go @@ -72,12 +72,13 @@ var stateFreezerNoSnappy = map[string]bool{ // The list of identifiers of ancient stores. var ( - ChainFreezerName = "chain" // the folder name of chain segment ancient store. - StateFreezerName = "state" // the folder name of reverse diff ancient store. + ChainFreezerName = "chain" // the folder name of chain segment ancient store. + MerkleStateFreezerName = "state" // the folder name of state history ancient store. + VerkleStateFreezerName = "state_verkle" // the folder name of state history ancient store. ) // freezers the collections of all builtin freezers. -var freezers = []string{ChainFreezerName, StateFreezerName} +var freezers = []string{ChainFreezerName, MerkleStateFreezerName, VerkleStateFreezerName} // NewStateFreezer initializes the ancient store for state history. // @@ -85,9 +86,15 @@ var freezers = []string{ChainFreezerName, StateFreezerName} // state freezer (e.g. dev mode). // - if non-empty directory is given, initializes the regular file-based // state freezer. -func NewStateFreezer(ancientDir string, readOnly bool) (ethdb.ResettableAncientStore, error) { +func NewStateFreezer(ancientDir string, verkle bool, readOnly bool) (ethdb.ResettableAncientStore, error) { if ancientDir == "" { return NewMemoryFreezer(readOnly, stateFreezerNoSnappy), nil } - return newResettableFreezer(filepath.Join(ancientDir, StateFreezerName), "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy) + var name string + if verkle { + name = filepath.Join(ancientDir, VerkleStateFreezerName) + } else { + name = filepath.Join(ancientDir, MerkleStateFreezerName) + } + return newResettableFreezer(name, "eth/db/state", readOnly, stateHistoryTableSize, stateFreezerNoSnappy) } diff --git a/core/rawdb/ancient_utils.go b/core/rawdb/ancient_utils.go index 1c69639c9d04..6804d7a91a2a 100644 --- a/core/rawdb/ancient_utils.go +++ b/core/rawdb/ancient_utils.go @@ -88,12 +88,12 @@ func inspectFreezers(db ethdb.Database) ([]freezerInfo, error) { } infos = append(infos, info) - case StateFreezerName: + case MerkleStateFreezerName, VerkleStateFreezerName: datadir, err := db.AncientDatadir() if err != nil { return nil, err } - f, err := NewStateFreezer(datadir, true) + f, err := NewStateFreezer(datadir, freezer == VerkleStateFreezerName, true) if err != nil { continue // might be possible the state freezer is not existent } @@ -124,7 +124,7 @@ func InspectFreezerTable(ancient string, freezerName string, tableName string, s switch freezerName { case ChainFreezerName: path, tables = resolveChainFreezerDir(ancient), chainFreezerNoSnappy - case StateFreezerName: + case MerkleStateFreezerName, VerkleStateFreezerName: path, tables = filepath.Join(ancient, freezerName), stateFreezerNoSnappy default: return fmt.Errorf("unknown freezer, supported ones: %v", freezers) diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 3436958de735..831016da15df 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -481,6 +481,10 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { beaconHeaders stat cliqueSnaps stat + // Verkle statistics + verkleTries stat + verkleStateLookups stat + // Les statistic chtTrieNodes stat bloomTrieNodes stat @@ -550,6 +554,24 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { bytes.HasPrefix(key, BloomTrieIndexPrefix) || bytes.HasPrefix(key, BloomTriePrefix): // Bloomtrie sub bloomTrieNodes.Add(size) + + // Verkle trie data is detected, determine the sub-category + case bytes.HasPrefix(key, VerklePrefix): + remain := key[len(VerklePrefix):] + switch { + case IsAccountTrieNode(remain): + verkleTries.Add(size) + case bytes.HasPrefix(remain, stateIDPrefix) && len(remain) == len(stateIDPrefix)+common.HashLength: + verkleStateLookups.Add(size) + case bytes.Equal(remain, persistentStateIDKey): + metadata.Add(size) + case bytes.Equal(remain, trieJournalKey): + metadata.Add(size) + case bytes.Equal(remain, snapSyncStatusFlagKey): + metadata.Add(size) + default: + unaccounted.Add(size) + } default: var accounted bool for _, meta := range [][]byte{ @@ -590,6 +612,8 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { {"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()}, {"Key-Value store", "Path trie account nodes", accountTries.Size(), accountTries.Count()}, {"Key-Value store", "Path trie storage nodes", storageTries.Size(), storageTries.Count()}, + {"Key-Value store", "Verkle trie nodes", verkleTries.Size(), verkleTries.Count()}, + {"Key-Value store", "Verkle trie state lookups", verkleStateLookups.Size(), verkleStateLookups.Count()}, {"Key-Value store", "Trie preimages", preimages.Size(), preimages.Count()}, {"Key-Value store", "Account snapshot", accountSnaps.Size(), accountSnaps.Count()}, {"Key-Value store", "Storage snapshot", storageSnaps.Size(), storageSnaps.Count()}, diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index dbf010be0ca8..04b5d0d6d2c8 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -117,6 +117,13 @@ var ( TrieNodeStoragePrefix = []byte("O") // TrieNodeStoragePrefix + accountHash + hexPath -> trie node stateIDPrefix = []byte("L") // stateIDPrefix + state root -> state id + // VerklePrefix is the database prefix for Verkle trie data, which includes: + // (a) Trie nodes + // (b) In-memory trie node journal + // (c) Persistent state ID + // (d) State ID lookups, etc. + VerklePrefix = []byte("v") + PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db genesisPrefix = []byte("ethereum-genesis-") // genesis state prefix for the db diff --git a/triedb/database.go b/triedb/database.go index 1f9f38388b4d..aecb900f31d3 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -42,9 +42,18 @@ type Config struct { // default settings. var HashDefaults = &Config{ Preimages: false, + IsVerkle: false, HashDB: hashdb.Defaults, } +// VerkleDefaults represents a config for holding verkle trie data +// using path-based scheme with default settings. +var VerkleDefaults = &Config{ + Preimages: false, + IsVerkle: true, + PathDB: pathdb.Defaults, +} + // backend defines the methods needed to access/update trie nodes in different // state scheme. type backend interface { @@ -84,7 +93,6 @@ type backend interface { // relevant with trie nodes and node preimages. type Database struct { config *Config // Configuration for trie database - diskdb ethdb.Database // Persistent database to store the snapshot preimages *preimageStore // The store for caching preimages backend backend // The backend for managing trie nodes } @@ -102,7 +110,6 @@ func NewDatabase(diskdb ethdb.Database, config *Config) *Database { } db := &Database{ config: config, - diskdb: diskdb, preimages: preimages, } if config.HashDB != nil && config.PathDB != nil { diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index 450c3a8f4f38..31e478117cd5 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -152,6 +152,14 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database { } config = config.sanitize() + // Establish a dedicated database namespace tailored for verkle-specific + // data, ensuring the isolation of both verkle and merkle tree data. It's + // important to note that the introduction of a prefix won't lead to + // substantial storage overhead, as the underlying database will efficiently + // compress the shared key prefix. + if isVerkle { + diskdb = rawdb.NewTable(diskdb, string(rawdb.VerklePrefix)) + } db := &Database{ readOnly: config.ReadOnly, isVerkle: isVerkle, @@ -190,7 +198,7 @@ func (db *Database) repairHistory() error { // all of them. Fix the tests first. return nil } - freezer, err := rawdb.NewStateFreezer(ancient, db.readOnly) + freezer, err := rawdb.NewStateFreezer(ancient, db.isVerkle, db.readOnly) if err != nil { log.Crit("Failed to open state history freezer", "err", err) } diff --git a/triedb/pathdb/history_test.go b/triedb/pathdb/history_test.go index 4114aa118532..586f907fe4e0 100644 --- a/triedb/pathdb/history_test.go +++ b/triedb/pathdb/history_test.go @@ -129,7 +129,7 @@ func TestTruncateHeadHistory(t *testing.T) { roots []common.Hash hs = makeHistories(10) db = rawdb.NewMemoryDatabase() - freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false) + freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false, false) ) defer freezer.Close() @@ -157,7 +157,7 @@ func TestTruncateTailHistory(t *testing.T) { roots []common.Hash hs = makeHistories(10) db = rawdb.NewMemoryDatabase() - freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false) + freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false, false) ) defer freezer.Close() @@ -200,7 +200,7 @@ func TestTruncateTailHistories(t *testing.T) { roots []common.Hash hs = makeHistories(10) db = rawdb.NewMemoryDatabase() - freezer, _ = rawdb.NewStateFreezer(t.TempDir()+fmt.Sprintf("%d", i), false) + freezer, _ = rawdb.NewStateFreezer(t.TempDir()+fmt.Sprintf("%d", i), false, false) ) defer freezer.Close() @@ -228,7 +228,7 @@ func TestTruncateOutOfRange(t *testing.T) { var ( hs = makeHistories(10) db = rawdb.NewMemoryDatabase() - freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false) + freezer, _ = rawdb.NewStateFreezer(t.TempDir(), false, false) ) defer freezer.Close()