mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-20 11:46:44 +00:00
opt
This commit is contained in:
parent
b4083957d7
commit
a471810a70
1 changed files with 178 additions and 143 deletions
|
|
@ -799,125 +799,155 @@ func SafeDeleteRange(db ethdb.KeyValueStore, start, end []byte, hashScheme bool,
|
||||||
return batch.Write()
|
return batch.Write()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// depthTracker computes the depth distribution of trie nodes using a
|
||||||
|
// stack-based streaming algorithm. Nodes must be fed in lexicographic
|
||||||
|
// path order. Not safe for concurrent use.
|
||||||
|
type depthTracker struct {
|
||||||
|
depths [65]stat
|
||||||
|
stack []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// newDepthTracker creates a depth tracker pre-allocated for the maximum
|
||||||
|
// trie depth (65 nibbles).
|
||||||
|
func newDepthTracker() *depthTracker {
|
||||||
|
return &depthTracker{stack: make([]string, 0, 65)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add records a trie node at the given path with the given on-disk size.
|
||||||
|
// Paths must be provided in lexicographic order.
|
||||||
|
func (d *depthTracker) add(path string, size common.StorageSize) {
|
||||||
|
// Pop until the stack top is a strict prefix of the current path.
|
||||||
|
for len(d.stack) > 0 {
|
||||||
|
top := d.stack[len(d.stack)-1]
|
||||||
|
if len(top) < len(path) && path[:len(top)] == top {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d.stack = d.stack[:len(d.stack)-1]
|
||||||
|
}
|
||||||
|
d.depths[len(d.stack)].add(size)
|
||||||
|
d.stack = append(d.stack, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset clears the stack for reuse (e.g. when switching between tries)
|
||||||
|
// without reallocating. The accumulated depth stats are preserved.
|
||||||
|
func (d *depthTracker) reset() {
|
||||||
|
d.stack = d.stack[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// total returns the aggregate count and size across all depths.
|
||||||
|
func (d *depthTracker) total() (count uint64, size uint64) {
|
||||||
|
for i := range d.depths {
|
||||||
|
count += d.depths[i].count
|
||||||
|
size += d.depths[i].size
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// InspectTrieDepth traverses all path-based trie nodes in the database
|
// InspectTrieDepth traverses all path-based trie nodes in the database
|
||||||
// and calculates the depth distribution of nodes in the account trie
|
// and calculates the depth distribution of nodes in the account trie
|
||||||
// and storage tries. Uses a streaming algorithm that processes nodes
|
// and storage tries. Account and storage iterations run in parallel.
|
||||||
// in lexicographic order without loading all paths into memory.
|
|
||||||
func InspectTrieDepth(db ethdb.Database) error {
|
func InspectTrieDepth(db ethdb.Database) error {
|
||||||
var (
|
var (
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
|
|
||||||
accountDepths [65]stat
|
accountTracker = newDepthTracker()
|
||||||
storageDepths [65]stat
|
storageTracker = newDepthTracker()
|
||||||
storageTriesCnt uint64
|
storageTriesCnt uint64
|
||||||
nodeCount uint64
|
nodeCount atomic.Uint64
|
||||||
)
|
)
|
||||||
|
|
||||||
log.Info("Calculating trie depth distribution...")
|
log.Info("Calculating trie depth distribution...")
|
||||||
|
|
||||||
// Process account trie nodes (prefix "A")
|
// Progress reporter
|
||||||
// Keys are already in lexicographic order from the database iterator
|
done := make(chan struct{})
|
||||||
accountStack := make([]string, 0, 65)
|
go func() {
|
||||||
|
ticker := time.NewTicker(8 * time.Second)
|
||||||
it := db.NewIterator(TrieNodeAccountPrefix, nil)
|
defer ticker.Stop()
|
||||||
for it.Next() {
|
for {
|
||||||
ok, path := ResolveAccountTrieNodeKey(it.Key())
|
select {
|
||||||
if !ok {
|
case <-ticker.C:
|
||||||
continue
|
log.Info("Processing nodes", "count", nodeCount.Load(),
|
||||||
}
|
"elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
pathStr := string(path)
|
case <-done:
|
||||||
|
return
|
||||||
// Pop until stack top is a strict prefix of current path
|
|
||||||
for len(accountStack) > 0 {
|
|
||||||
top := accountStack[len(accountStack)-1]
|
|
||||||
if len(top) < len(pathStr) && pathStr[:len(top)] == top {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
accountStack = accountStack[:len(accountStack)-1]
|
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
depth := len(accountStack)
|
eg, ctx := errgroup.WithContext(context.Background())
|
||||||
accountDepths[depth].add(common.StorageSize(len(it.Key()) + len(it.Value())))
|
|
||||||
nodeCount++
|
|
||||||
|
|
||||||
accountStack = append(accountStack, pathStr)
|
// Process account trie nodes
|
||||||
|
eg.Go(func() error {
|
||||||
|
it := db.NewIterator(TrieNodeAccountPrefix, nil)
|
||||||
|
defer it.Release()
|
||||||
|
|
||||||
if nodeCount%10000000 == 0 {
|
for it.Next() {
|
||||||
log.Info("Processing nodes", "count", nodeCount, "elapsed", common.PrettyDuration(time.Since(start)))
|
ok, path := ResolveAccountTrieNodeKey(it.Key())
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
accountTracker.add(string(path),
|
||||||
|
common.StorageSize(len(it.Key())+len(it.Value())))
|
||||||
|
nodeCount.Add(1)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return it.Error()
|
||||||
it.Release()
|
})
|
||||||
if err := it.Error(); err != nil {
|
|
||||||
|
// Process storage trie nodes
|
||||||
|
eg.Go(func() error {
|
||||||
|
var (
|
||||||
|
currentAccountHash common.Hash
|
||||||
|
firstStorageTrie = true
|
||||||
|
)
|
||||||
|
|
||||||
|
it := db.NewIterator(TrieNodeStoragePrefix, nil)
|
||||||
|
defer it.Release()
|
||||||
|
|
||||||
|
for it.Next() {
|
||||||
|
ok, accountHash, path := ResolveStorageTrieNode(it.Key())
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// When the account hash changes we're entering a new trie.
|
||||||
|
if accountHash != currentAccountHash {
|
||||||
|
if !firstStorageTrie {
|
||||||
|
storageTriesCnt++
|
||||||
|
}
|
||||||
|
firstStorageTrie = false
|
||||||
|
currentAccountHash = accountHash
|
||||||
|
storageTracker.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
storageTracker.add(string(path),
|
||||||
|
common.StorageSize(len(it.Key())+len(it.Value())))
|
||||||
|
nodeCount.Add(1)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !firstStorageTrie {
|
||||||
|
storageTriesCnt++
|
||||||
|
}
|
||||||
|
return it.Error()
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
close(done)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
close(done)
|
||||||
|
|
||||||
var accountCount uint64
|
accountCount, _ := accountTracker.total()
|
||||||
for i := range accountDepths {
|
storageCount, _ := storageTracker.total()
|
||||||
accountCount += atomic.LoadUint64(&accountDepths[i].count)
|
|
||||||
}
|
|
||||||
log.Info("Finished account trie", "nodes", accountCount, "elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
|
|
||||||
// Process storage trie nodes (prefix "O")
|
|
||||||
// Keys are ordered by (accountHash, path), so when accountHash changes,
|
|
||||||
// we're starting a new trie and need to reset the stack
|
|
||||||
var (
|
|
||||||
storageStack = make([]string, 0, 65)
|
|
||||||
currentAccountHash common.Hash
|
|
||||||
firstStorageTrie = true
|
|
||||||
)
|
|
||||||
|
|
||||||
it = db.NewIterator(TrieNodeStoragePrefix, nil)
|
|
||||||
for it.Next() {
|
|
||||||
ok, accountHash, path := ResolveStorageTrieNode(it.Key())
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we're starting a new storage trie
|
|
||||||
if accountHash != currentAccountHash {
|
|
||||||
if !firstStorageTrie {
|
|
||||||
storageTriesCnt++
|
|
||||||
}
|
|
||||||
firstStorageTrie = false
|
|
||||||
currentAccountHash = accountHash
|
|
||||||
storageStack = storageStack[:0] // Reset stack for new trie
|
|
||||||
}
|
|
||||||
|
|
||||||
pathStr := string(path)
|
|
||||||
|
|
||||||
// Pop until stack top is a strict prefix of current path
|
|
||||||
for len(storageStack) > 0 {
|
|
||||||
top := storageStack[len(storageStack)-1]
|
|
||||||
if len(top) < len(pathStr) && pathStr[:len(top)] == top {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
storageStack = storageStack[:len(storageStack)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
depth := len(storageStack)
|
|
||||||
storageDepths[depth].add(common.StorageSize(len(it.Key()) + len(it.Value())))
|
|
||||||
nodeCount++
|
|
||||||
|
|
||||||
storageStack = append(storageStack, pathStr)
|
|
||||||
|
|
||||||
if nodeCount%10000000 == 0 {
|
|
||||||
log.Info("Processing nodes", "count", nodeCount, "elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
it.Release()
|
|
||||||
if err := it.Error(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count the last storage trie if we processed any
|
|
||||||
if !firstStorageTrie {
|
|
||||||
storageTriesCnt++
|
|
||||||
}
|
|
||||||
|
|
||||||
var storageCount uint64
|
|
||||||
for i := range storageDepths {
|
|
||||||
storageCount += atomic.LoadUint64(&storageDepths[i].count)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Depth calculation complete",
|
log.Info("Depth calculation complete",
|
||||||
"accountNodes", accountCount,
|
"accountNodes", accountCount,
|
||||||
|
|
@ -933,10 +963,10 @@ func InspectTrieDepth(db ethdb.Database) error {
|
||||||
fmt.Println("Depth | Account Nodes | Account Size | Storage Nodes | Storage Size")
|
fmt.Println("Depth | Account Nodes | Account Size | Storage Nodes | Storage Size")
|
||||||
fmt.Println("------|---------------|---------------|---------------|---------------")
|
fmt.Println("------|---------------|---------------|---------------|---------------")
|
||||||
for i := 0; i < 65; i++ {
|
for i := 0; i < 65; i++ {
|
||||||
if !accountDepths[i].empty() || !storageDepths[i].empty() {
|
if !accountTracker.depths[i].empty() || !storageTracker.depths[i].empty() {
|
||||||
fmt.Printf("%5d | %13s | %13s | %13s | %13s\n",
|
fmt.Printf("%5d | %13s | %13s | %13s | %13s\n",
|
||||||
i, accountDepths[i].countString(), accountDepths[i].sizeString(),
|
i, accountTracker.depths[i].countString(), accountTracker.depths[i].sizeString(),
|
||||||
storageDepths[i].countString(), storageDepths[i].sizeString())
|
storageTracker.depths[i].countString(), storageTracker.depths[i].sizeString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
@ -946,65 +976,70 @@ func InspectTrieDepth(db ethdb.Database) error {
|
||||||
|
|
||||||
// InspectContract inspects the on-disk footprint of a single contract:
|
// InspectContract inspects the on-disk footprint of a single contract:
|
||||||
// snapshot account, snapshot storage slots, and path-based storage trie
|
// snapshot account, snapshot storage slots, and path-based storage trie
|
||||||
// nodes including depth distribution.
|
// nodes including depth distribution. Storage snapshot and trie
|
||||||
|
// iterations run in parallel.
|
||||||
func InspectContract(db ethdb.Database, address common.Address) error {
|
func InspectContract(db ethdb.Database, address common.Address) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
accountHash := crypto.Keccak256Hash(address.Bytes())
|
accountHash := crypto.Keccak256Hash(address.Bytes())
|
||||||
|
|
||||||
log.Info("Inspecting contract", "address", address, "hash", accountHash)
|
log.Info("Inspecting contract", "address", address, "hash", accountHash)
|
||||||
|
|
||||||
// 1. Account snapshot
|
// 1. Account snapshot (fast single-key lookup, done inline)
|
||||||
accountData := ReadAccountSnapshot(db, accountHash)
|
accountData := ReadAccountSnapshot(db, accountHash)
|
||||||
accountSnapshotSize := len(accountSnapshotKey(accountHash)) + len(accountData)
|
accountSnapshotSize := len(accountSnapshotKey(accountHash)) + len(accountData)
|
||||||
|
|
||||||
// 2. Storage snapshot: iterate all storage slots for this account
|
|
||||||
var storageStat stat
|
|
||||||
storagePrefix := storageSnapshotsKey(accountHash)
|
|
||||||
|
|
||||||
it := db.NewIterator(storagePrefix, nil)
|
|
||||||
for it.Next() {
|
|
||||||
storageStat.add(common.StorageSize(len(it.Key()) + len(it.Value())))
|
|
||||||
}
|
|
||||||
it.Release()
|
|
||||||
if err := it.Error(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Storage trie nodes: iterate and compute depth distribution
|
|
||||||
var (
|
var (
|
||||||
trieStat stat
|
storageStat stat
|
||||||
trieDepths [65]stat
|
trieTracker = newDepthTracker()
|
||||||
trieStack = make([]string, 0, 65)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
it = db.NewIterator(storageTrieNodeKey(accountHash, nil), nil)
|
eg, ctx := errgroup.WithContext(context.Background())
|
||||||
for it.Next() {
|
|
||||||
ok, hash, path := ResolveStorageTrieNode(it.Key())
|
// 2. Storage snapshot: iterate all storage slots for this account
|
||||||
if !ok || hash != accountHash {
|
eg.Go(func() error {
|
||||||
break
|
it := db.NewIterator(storageSnapshotsKey(accountHash), nil)
|
||||||
|
defer it.Release()
|
||||||
|
|
||||||
|
for it.Next() {
|
||||||
|
storageStat.add(common.StorageSize(len(it.Key()) + len(it.Value())))
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return it.Error()
|
||||||
|
})
|
||||||
|
|
||||||
size := common.StorageSize(len(it.Key()) + len(it.Value()))
|
// 3. Storage trie nodes: iterate and compute depth distribution
|
||||||
trieStat.add(size)
|
eg.Go(func() error {
|
||||||
|
it := db.NewIterator(storageTrieNodeKey(accountHash, nil), nil)
|
||||||
|
defer it.Release()
|
||||||
|
|
||||||
pathStr := string(path)
|
for it.Next() {
|
||||||
|
ok, hash, path := ResolveStorageTrieNode(it.Key())
|
||||||
// Stack-based depth: pop until top is strict prefix of current
|
if !ok || hash != accountHash {
|
||||||
for len(trieStack) > 0 {
|
|
||||||
top := trieStack[len(trieStack)-1]
|
|
||||||
if len(top) < len(pathStr) && pathStr[:len(top)] == top {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
trieStack = trieStack[:len(trieStack)-1]
|
trieTracker.add(string(path),
|
||||||
|
common.StorageSize(len(it.Key())+len(it.Value())))
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
trieDepths[len(trieStack)].add(size)
|
return it.Error()
|
||||||
trieStack = append(trieStack, pathStr)
|
})
|
||||||
}
|
|
||||||
it.Release()
|
if err := eg.Wait(); err != nil {
|
||||||
if err := it.Error(); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trieCount, trieSize := trieTracker.total()
|
||||||
|
|
||||||
log.Info("Inspection complete", "elapsed", common.PrettyDuration(time.Since(start)))
|
log.Info("Inspection complete", "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
|
||||||
// Print results
|
// Print results
|
||||||
|
|
@ -1018,15 +1053,15 @@ func InspectContract(db ethdb.Database, address common.Address) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Snapshot storage: %s slots (%s)\n", storageStat.countString(), storageStat.sizeString())
|
fmt.Printf("Snapshot storage: %s slots (%s)\n", storageStat.countString(), storageStat.sizeString())
|
||||||
fmt.Printf("Storage trie: %s nodes (%s)\n", trieStat.countString(), trieStat.sizeString())
|
fmt.Printf("Storage trie: %d nodes (%s)\n", trieCount, common.StorageSize(trieSize))
|
||||||
|
|
||||||
fmt.Println("\nStorage Trie Depth Distribution:")
|
fmt.Println("\nStorage Trie Depth Distribution:")
|
||||||
fmt.Println("Depth | Nodes | Size")
|
fmt.Println("Depth | Nodes | Size")
|
||||||
fmt.Println("------|---------------|---------------")
|
fmt.Println("------|---------------|---------------")
|
||||||
for i := 0; i < 65; i++ {
|
for i := 0; i < 65; i++ {
|
||||||
if !trieDepths[i].empty() {
|
if !trieTracker.depths[i].empty() {
|
||||||
fmt.Printf("%5d | %13s | %13s\n",
|
fmt.Printf("%5d | %13s | %13s\n",
|
||||||
i, trieDepths[i].countString(), trieDepths[i].sizeString())
|
i, trieTracker.depths[i].countString(), trieTracker.depths[i].sizeString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue