From 0281e226bf2950f041276daf1e8f95e40ef6ac72 Mon Sep 17 00:00:00 2001 From: jsvisa Date: Mon, 7 Jul 2025 11:27:36 +0000 Subject: [PATCH] doc Signed-off-by: jsvisa --- triedb/pathdb/layertree.go | 2 + triedb/pathdb/lookup.go | 32 +++++-- triedb/pathdb/lookup_test.go | 158 +++++------------------------------ 3 files changed, 47 insertions(+), 145 deletions(-) diff --git a/triedb/pathdb/layertree.go b/triedb/pathdb/layertree.go index df0f84b3ab..221fdda844 100644 --- a/triedb/pathdb/layertree.go +++ b/triedb/pathdb/layertree.go @@ -339,6 +339,8 @@ func (tree *layerTree) lookupStorage(accountHash common.Hash, slotHash common.Ha return l, nil } +// lookupNode returns the layer that is guaranteed to contain the trie node +// data corresponding to the specified state root being queried. func (tree *layerTree) lookupNode(accountHash common.Hash, path string, state common.Hash) (layer, error) { // Hold the read lock to prevent the unexpected layer changes tree.lock.RLock() diff --git a/triedb/pathdb/lookup.go b/triedb/pathdb/lookup.go index 80771cd7b8..576fa81e39 100644 --- a/triedb/pathdb/lookup.go +++ b/triedb/pathdb/lookup.go @@ -27,6 +27,7 @@ import ( "golang.org/x/sync/errgroup" ) +// storageNodesShardCount is the number of shards used for storage nodes. const storageNodesShardCount = 16 // storageKey returns a key for uniquely identifying the storage slot. @@ -52,10 +53,17 @@ type lookup struct { // where the slot was modified, with the order from oldest to newest. storages map[[64]byte][]common.Hash + // accountNodes represents the mutation history for specific account + // trie nodes. The key is the trie path of the node, and the value is a slice + // of **diff layer** IDs indicating where the account was modified, + // with the order from oldest to newest. accountNodes map[string][]common.Hash - // Key is accountHash.Hex() + path, distributed across 16 shards - // This eliminates the need for two-level map lookups + // storageNodes represents the mutation history for specific storage + // slot trie nodes, distributed across 16 shards for efficiency. + // The key is the account address hash and the trie path of the node, + // the value is a slice of **diff layer** IDs indicating where the + // slot was modified, with the order from oldest to newest. storageNodes [storageNodesShardCount]map[string][]common.Hash // descendant is the callback indicating whether the layer with @@ -186,6 +194,17 @@ func (l *lookup) storageTip(accountHash common.Hash, slotHash common.Hash, state return common.Hash{} } +// nodeTip traverses the layer list associated with the given account and path +// in reverse order to locate the first entry that either matches +// the specified stateID or is a descendant of it. +// +// If found, the trie node data corresponding to the supplied stateID resides +// in that layer. Otherwise, two scenarios are possible: +// +// (a) the trie node remains unmodified from the current disk layer up to +// the state layer specified by the stateID: fallback to the disk layer for +// data retrieval, (b) or the layer specified by the stateID is stale: reject +// the data retrieval. func (l *lookup) nodeTip(accountHash common.Hash, path string, stateID common.Hash, base common.Hash) common.Hash { var list []common.Hash if accountHash == (common.Hash{}) { @@ -448,17 +467,16 @@ func (l *lookup) removeStorageNodes(state common.Hash, nodes map[common.Hash]map for accountHash, slots := range nodes { accountHex := accountHash.Hex() for path := range slots { - // Construct the combined key and find the correct shard + shard := l.storageNodes[getStorageShardIndex(path)] key := accountHex + path - shardIndex := getStorageShardIndex(path) - found, list := removeFromList(l.storageNodes[shardIndex][key], state) + found, list := removeFromList(shard[key], state) if !found { return fmt.Errorf("storage lookup is not found, %x %x, state: %x", accountHash, path, state) } if len(list) != 0 { - l.storageNodes[shardIndex][key] = list + shard[key] = list } else { - delete(l.storageNodes[shardIndex], key) + delete(shard, key) } } } diff --git a/triedb/pathdb/lookup_test.go b/triedb/pathdb/lookup_test.go index 271ad8d8d6..aac7df95b1 100644 --- a/triedb/pathdb/lookup_test.go +++ b/triedb/pathdb/lookup_test.go @@ -2,7 +2,6 @@ package pathdb import ( "crypto/rand" - "fmt" "testing" "github.com/ethereum/go-ethereum/common" @@ -43,173 +42,56 @@ func generateRandomStorageNodes(accountCount, nodesPerAccount int) map[common.Ha return storageNodes } -// addNodes is a helper method for testing that adds nodes to the lookup structure -func (l *lookup) addNodes(stateHash common.Hash, accountNodes map[string]*trienode.Node, storageNodes map[common.Hash]map[string]*trienode.Node) { - // Add account nodes - for path := range accountNodes { - list, exists := l.accountNodes[path] - if !exists { - list = make([]common.Hash, 0, 16) - } - list = append(list, stateHash) - l.accountNodes[path] = list - } - - // Add storage nodes using single-level sharded structure - for accountHash, slots := range storageNodes { - accountHex := accountHash.Hex() - - for path := range slots { - // Construct the combined key but use only path for shard calculation - key := accountHex + path - shardIndex := getStorageShardIndex(path) // Use only path for sharding - shardMap := l.storageNodes[shardIndex] - - list, exists := shardMap[key] - if !exists { - list = make([]common.Hash, 0, 16) - } - list = append(list, stateHash) - shardMap[key] = list - } - } -} - func BenchmarkAddNodes(b *testing.B) { tests := []struct { - name string - accountNodeCount int - storageAccountCount int - nodesPerAccount int + name string + accountNodeCount int + nodesPerAccount int }{ { - name: "Small-100-accounts-10-nodes", - accountNodeCount: 100, - storageAccountCount: 100, - nodesPerAccount: 10, + name: "Small-100-accounts-10-nodes", + accountNodeCount: 100, + nodesPerAccount: 10, }, { - name: "Medium-500-accounts-20-nodes", - accountNodeCount: 500, - storageAccountCount: 500, - nodesPerAccount: 20, + name: "Medium-500-accounts-20-nodes", + accountNodeCount: 500, + nodesPerAccount: 20, }, { - name: "Large-2000-accounts-40-nodes", - accountNodeCount: 2000, - storageAccountCount: 2000, - nodesPerAccount: 40, + name: "Large-2000-accounts-40-nodes", + accountNodeCount: 2000, + nodesPerAccount: 40, }, } for _, tc := range tests { b.Run(tc.name, func(b *testing.B) { - accountNodes := generateRandomAccountNodes(tc.accountNodeCount) - storageNodes := generateRandomStorageNodes(tc.storageAccountCount, tc.nodesPerAccount) + storageNodes := generateRandomStorageNodes(tc.accountNodeCount, tc.nodesPerAccount) lookup := &lookup{ accountNodes: make(map[string][]common.Hash), } + // Initialize all 16 storage node shards - for i := 0; i < 16; i++ { + for i := 0; i < storageNodesShardCount; i++ { lookup.storageNodes[i] = make(map[string][]common.Hash) } - var stateHash common.Hash - rand.Read(stateHash[:]) + var state common.Hash + rand.Read(state[:]) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { - // Clear the nodes maps for each iteration - lookup.accountNodes = make(map[string][]common.Hash) - // Reinitialize all 16 storage node shards - for j := 0; j < 16; j++ { + // Reset the lookup instance for each benchmark iteration + for j := 0; j < storageNodesShardCount; j++ { lookup.storageNodes[j] = make(map[string][]common.Hash) } - lookup.addNodes(stateHash, accountNodes, storageNodes) + lookup.addStorageNodes(state, storageNodes) } }) } } - -func TestConcurrentStorageNodesUpdate(b *testing.T) { - // Create a lookup instance - lookup := &lookup{ - accountNodes: make(map[string][]common.Hash), - } - // Initialize all storage node shards - for i := 0; i < storageNodesShardCount; i++ { - lookup.storageNodes[i] = make(map[string][]common.Hash) - } - - // Create test data with known shard distribution - testData := map[common.Hash]map[string]*trienode.Node{} - - // Create accounts that will distribute across different shards - for i := 0; i < 100; i++ { - var accountHash common.Hash - accountHash[0] = byte(i) - - testData[accountHash] = make(map[string]*trienode.Node) - - // Create paths that will hash to different shards - for j := 0; j < 10; j++ { - path := fmt.Sprintf("path_%d_%d", i, j) - var nodeHash common.Hash - nodeHash[0] = byte(j) - - testData[accountHash][path] = &trienode.Node{Hash: nodeHash} - } - } - - // Add nodes using the concurrent method - var stateHash common.Hash - stateHash[0] = 0x42 - lookup.addNodes(stateHash, nil, testData) - - // Verify that all nodes were added correctly - totalNodes := 0 - for accountHash, slots := range testData { - accountHex := accountHash.Hex() - for path := range slots { - key := accountHex + path - shardIndex := getStorageShardIndex(path) - - list, exists := lookup.storageNodes[shardIndex][key] - if !exists { - b.Errorf("Node not found: account=%x, path=%s, shard=%d", accountHash, path, shardIndex) - continue - } - - if len(list) != 1 { - b.Errorf("Expected 1 state hash, got %d: account=%x, path=%s", len(list), accountHash, path) - continue - } - - if list[0] != stateHash { - b.Errorf("Expected state hash %x, got %x: account=%x, path=%s", stateHash, list[0], accountHash, path) - continue - } - - totalNodes++ - } - } - - expectedTotal := 100 * 10 // 100 accounts * 10 nodes each - if totalNodes != expectedTotal { - b.Errorf("Expected %d total nodes, got %d", expectedTotal, totalNodes) - } - - // Verify shard distribution - for i := 0; i < storageNodesShardCount; i++ { - shardSize := len(lookup.storageNodes[i]) - if shardSize == 0 { - b.Logf("Shard %d is empty", i) - } else { - b.Logf("Shard %d has %d entries", i, shardSize) - } - } -}