mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 05:06:43 +00:00
doc
Signed-off-by: jsvisa <delweng@gmail.com>
This commit is contained in:
parent
f5f0129f85
commit
0281e226bf
3 changed files with 47 additions and 145 deletions
|
|
@ -339,6 +339,8 @@ func (tree *layerTree) lookupStorage(accountHash common.Hash, slotHash common.Ha
|
||||||
return l, nil
|
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) {
|
func (tree *layerTree) lookupNode(accountHash common.Hash, path string, state common.Hash) (layer, error) {
|
||||||
// Hold the read lock to prevent the unexpected layer changes
|
// Hold the read lock to prevent the unexpected layer changes
|
||||||
tree.lock.RLock()
|
tree.lock.RLock()
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// storageNodesShardCount is the number of shards used for storage nodes.
|
||||||
const storageNodesShardCount = 16
|
const storageNodesShardCount = 16
|
||||||
|
|
||||||
// storageKey returns a key for uniquely identifying the storage slot.
|
// 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.
|
// where the slot was modified, with the order from oldest to newest.
|
||||||
storages map[[64]byte][]common.Hash
|
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
|
accountNodes map[string][]common.Hash
|
||||||
|
|
||||||
// Key is accountHash.Hex() + path, distributed across 16 shards
|
// storageNodes represents the mutation history for specific storage
|
||||||
// This eliminates the need for two-level map lookups
|
// 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
|
storageNodes [storageNodesShardCount]map[string][]common.Hash
|
||||||
|
|
||||||
// descendant is the callback indicating whether the layer with
|
// 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{}
|
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 {
|
func (l *lookup) nodeTip(accountHash common.Hash, path string, stateID common.Hash, base common.Hash) common.Hash {
|
||||||
var list []common.Hash
|
var list []common.Hash
|
||||||
if accountHash == (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 {
|
for accountHash, slots := range nodes {
|
||||||
accountHex := accountHash.Hex()
|
accountHex := accountHash.Hex()
|
||||||
for path := range slots {
|
for path := range slots {
|
||||||
// Construct the combined key and find the correct shard
|
shard := l.storageNodes[getStorageShardIndex(path)]
|
||||||
key := accountHex + path
|
key := accountHex + path
|
||||||
shardIndex := getStorageShardIndex(path)
|
found, list := removeFromList(shard[key], state)
|
||||||
found, list := removeFromList(l.storageNodes[shardIndex][key], state)
|
|
||||||
if !found {
|
if !found {
|
||||||
return fmt.Errorf("storage lookup is not found, %x %x, state: %x", accountHash, path, state)
|
return fmt.Errorf("storage lookup is not found, %x %x, state: %x", accountHash, path, state)
|
||||||
}
|
}
|
||||||
if len(list) != 0 {
|
if len(list) != 0 {
|
||||||
l.storageNodes[shardIndex][key] = list
|
shard[key] = list
|
||||||
} else {
|
} else {
|
||||||
delete(l.storageNodes[shardIndex], key)
|
delete(shard, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package pathdb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -43,173 +42,56 @@ func generateRandomStorageNodes(accountCount, nodesPerAccount int) map[common.Ha
|
||||||
return storageNodes
|
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) {
|
func BenchmarkAddNodes(b *testing.B) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
accountNodeCount int
|
accountNodeCount int
|
||||||
storageAccountCount int
|
nodesPerAccount int
|
||||||
nodesPerAccount int
|
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Small-100-accounts-10-nodes",
|
name: "Small-100-accounts-10-nodes",
|
||||||
accountNodeCount: 100,
|
accountNodeCount: 100,
|
||||||
storageAccountCount: 100,
|
nodesPerAccount: 10,
|
||||||
nodesPerAccount: 10,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Medium-500-accounts-20-nodes",
|
name: "Medium-500-accounts-20-nodes",
|
||||||
accountNodeCount: 500,
|
accountNodeCount: 500,
|
||||||
storageAccountCount: 500,
|
nodesPerAccount: 20,
|
||||||
nodesPerAccount: 20,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Large-2000-accounts-40-nodes",
|
name: "Large-2000-accounts-40-nodes",
|
||||||
accountNodeCount: 2000,
|
accountNodeCount: 2000,
|
||||||
storageAccountCount: 2000,
|
nodesPerAccount: 40,
|
||||||
nodesPerAccount: 40,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
b.Run(tc.name, func(b *testing.B) {
|
b.Run(tc.name, func(b *testing.B) {
|
||||||
accountNodes := generateRandomAccountNodes(tc.accountNodeCount)
|
storageNodes := generateRandomStorageNodes(tc.accountNodeCount, tc.nodesPerAccount)
|
||||||
storageNodes := generateRandomStorageNodes(tc.storageAccountCount, tc.nodesPerAccount)
|
|
||||||
|
|
||||||
lookup := &lookup{
|
lookup := &lookup{
|
||||||
accountNodes: make(map[string][]common.Hash),
|
accountNodes: make(map[string][]common.Hash),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize all 16 storage node shards
|
// 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)
|
lookup.storageNodes[i] = make(map[string][]common.Hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
var stateHash common.Hash
|
var state common.Hash
|
||||||
rand.Read(stateHash[:])
|
rand.Read(state[:])
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
// Clear the nodes maps for each iteration
|
// Reset the lookup instance for each benchmark iteration
|
||||||
lookup.accountNodes = make(map[string][]common.Hash)
|
for j := 0; j < storageNodesShardCount; j++ {
|
||||||
// Reinitialize all 16 storage node shards
|
|
||||||
for j := 0; j < 16; j++ {
|
|
||||||
lookup.storageNodes[j] = make(map[string][]common.Hash)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue