From 07015878c709294ced30238c03b7ed50f3f9bb65 Mon Sep 17 00:00:00 2001 From: lightclient Date: Wed, 18 Feb 2026 21:46:28 +0000 Subject: [PATCH] trie: add combined depth summary --- trie/inspect.go | 41 +++++++++++++++++++-- trie/inspect_test.go | 86 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 123 insertions(+), 4 deletions(-) diff --git a/trie/inspect.go b/trie/inspect.go index 6014b0e47a..281e4d2687 100644 --- a/trie/inspect.go +++ b/trie/inspect.go @@ -328,9 +328,11 @@ func Summarize(dumpPath string, config *InspectConfig) error { } summary.StorageCount++ summary.DepthHistogram[snapshot.MaxDepth]++ - summary.StorageTotals.Short += snapshot.Summary.Short - summary.StorageTotals.Full += snapshot.Summary.Full - summary.StorageTotals.Value += snapshot.Summary.Value + for i := 0; i < trieStatLevels; i++ { + summary.StorageLevels[i].Short += record.Levels[i].Short + summary.StorageLevels[i].Full += record.Levels[i].Full + summary.StorageLevels[i].Value += record.Levels[i].Value + } depthTop.TryInsert(snapshot) totalTop.TryInsert(snapshot) @@ -339,6 +341,11 @@ func Summarize(dumpPath string, config *InspectConfig) error { if summary.Account == nil { return fmt.Errorf("dump file %s does not contain the account trie sentinel record", dumpPath) } + for i := 0; i < trieStatLevels; i++ { + summary.StorageTotals.Short += summary.StorageLevels[i].Short + summary.StorageTotals.Full += summary.StorageLevels[i].Full + summary.StorageTotals.Value += summary.StorageLevels[i].Value + } summary.TopByDepth = depthTop.Sorted() summary.TopByTotalNodes = totalTop.Sorted() summary.TopByValueNodes = valueTop.Sorted() @@ -522,6 +529,7 @@ type inspectSummary struct { Account *storageStats StorageCount uint64 StorageTotals jsonLevel + StorageLevels [trieStatLevels]jsonLevel DepthHistogram [trieStatLevels]uint64 TopByDepth []*storageStats TopByTotalNodes []*storageStats @@ -529,6 +537,7 @@ type inspectSummary struct { } func (s *inspectSummary) display() { + s.displayCombinedDepthTable() s.Account.toLevelStats().display("Accounts trie") fmt.Println("Storage trie aggregate summary") fmt.Printf("Total storage tries: %d\n", s.StorageCount) @@ -553,6 +562,30 @@ func (s *inspectSummary) display() { s.displayTop("Top storage tries by value (slot) count", s.TopByValueNodes) } +func (s *inspectSummary) displayCombinedDepthTable() { + accountTotal := s.Account.Summary.Short + s.Account.Summary.Full + s.Account.Summary.Value + storageTotal := s.StorageTotals.Short + s.StorageTotals.Full + s.StorageTotals.Value + + fmt.Println("Trie Depth Distribution") + fmt.Printf("Account Trie: %d nodes\n", accountTotal) + fmt.Printf("Storage Tries: %d nodes across %d tries\n", storageTotal, s.StorageCount) + + b := new(strings.Builder) + table := tablewriter.NewWriter(b) + table.SetHeader([]string{"Depth", "Account Nodes", "Storage Nodes"}) + for i := 0; i < trieStatLevels; i++ { + accountNodes := s.Account.Levels[i].Short + s.Account.Levels[i].Full + s.Account.Levels[i].Value + storageNodes := s.StorageLevels[i].Short + s.StorageLevels[i].Full + s.StorageLevels[i].Value + if accountNodes == 0 && storageNodes == 0 { + continue + } + table.AppendBulk([][]string{{fmt.Sprint(i), fmt.Sprint(accountNodes), fmt.Sprint(storageNodes)}}) + } + table.Render() + fmt.Print(b.String()) + fmt.Println() +} + func (s *inspectSummary) displayTop(title string, list []*storageStats) { fmt.Println(title) if len(list) == 0 { @@ -575,6 +608,7 @@ func (s *inspectSummary) MarshalJSON() ([]byte, error) { type jsonStorageSummary struct { TotalStorageTries uint64 `json:"TotalStorageTries"` Totals jsonLevel `json:"Totals"` + Levels []jsonLevel `json:"Levels"` DepthHistogram [trieStatLevels]uint64 `json:"DepthHistogram"` } type jsonInspectSummary struct { @@ -593,6 +627,7 @@ func (s *inspectSummary) MarshalJSON() ([]byte, error) { StorageSummary: jsonStorageSummary{ TotalStorageTries: s.StorageCount, Totals: s.StorageTotals, + Levels: trimLevels(s.StorageLevels), DepthHistogram: s.DepthHistogram, }, TopByDepth: s.TopByDepth, diff --git a/trie/inspect_test.go b/trie/inspect_test.go index c3145cb964..8cce7affb0 100644 --- a/trie/inspect_test.go +++ b/trie/inspect_test.go @@ -17,8 +17,11 @@ package trie import ( + "encoding/json" "math/rand" + "os" "path/filepath" + "reflect" "testing" "github.com/ethereum/go-ethereum/core/rawdb" @@ -56,12 +59,93 @@ func TestInspect(t *testing.T) { }); err != nil { t.Fatalf("inspect failed: %v", err) } + reanalysisPath := filepath.Join(tempDir, "trie-summary-reanalysis.json") if err := Summarize(dumpPath, &InspectConfig{ TopN: 1, - Path: filepath.Join(tempDir, "trie-summary-reanalysis.json"), + Path: reanalysisPath, }); err != nil { t.Fatalf("summarize failed: %v", err) } + + inspectSummaryPath := filepath.Join(tempDir, "trie-summary.json") + inspectOut := loadInspectJSON(t, inspectSummaryPath) + reanalysisOut := loadInspectJSON(t, reanalysisPath) + + if len(inspectOut.StorageSummary.Levels) == 0 { + t.Fatal("expected StorageSummary.Levels to be populated") + } + if !reflect.DeepEqual(inspectOut.AccountTrie, reanalysisOut.AccountTrie) { + t.Fatal("account trie summary mismatch between inspect and summarize") + } + if !reflect.DeepEqual(inspectOut.StorageSummary, reanalysisOut.StorageSummary) { + t.Fatal("storage summary mismatch between inspect and summarize") + } + + assertStorageTotalsMatchLevels(t, inspectOut) + assertStorageTotalsMatchLevels(t, reanalysisOut) + assertAccountTotalsMatchLevels(t, inspectOut.AccountTrie) + assertAccountTotalsMatchLevels(t, reanalysisOut.AccountTrie) + + var histogramTotal uint64 + for _, count := range inspectOut.StorageSummary.DepthHistogram { + histogramTotal += count + } + if histogramTotal != inspectOut.StorageSummary.TotalStorageTries { + t.Fatalf("depth histogram total %d does not match total storage tries %d", histogramTotal, inspectOut.StorageSummary.TotalStorageTries) + } +} + +type inspectJSONOutput struct { + // Reuse storageStats for AccountTrie JSON to avoid introducing a parallel + // account summary test type. AccountTrie JSON includes Levels+Summary, + // which map directly; other storageStats fields remain zero-values. + AccountTrie storageStats `json:"AccountTrie"` + + StorageSummary struct { + TotalStorageTries uint64 `json:"TotalStorageTries"` + Totals jsonLevel `json:"Totals"` + Levels []jsonLevel `json:"Levels"` + DepthHistogram [trieStatLevels]uint64 `json:"DepthHistogram"` + } `json:"StorageSummary"` +} + +func loadInspectJSON(t *testing.T, path string) inspectJSONOutput { + t.Helper() + raw, err := os.ReadFile(path) + if err != nil { + t.Fatalf("failed to read %s: %v", path, err) + } + var out inspectJSONOutput + if err := json.Unmarshal(raw, &out); err != nil { + t.Fatalf("failed to decode %s: %v", path, err) + } + return out +} + +func assertStorageTotalsMatchLevels(t *testing.T, out inspectJSONOutput) { + t.Helper() + var fromLevels jsonLevel + for _, level := range out.StorageSummary.Levels { + fromLevels.Short += level.Short + fromLevels.Full += level.Full + fromLevels.Value += level.Value + } + if fromLevels.Short != out.StorageSummary.Totals.Short || fromLevels.Full != out.StorageSummary.Totals.Full || fromLevels.Value != out.StorageSummary.Totals.Value { + t.Fatalf("storage totals mismatch: levels=%+v totals=%+v", fromLevels, out.StorageSummary.Totals) + } +} + +func assertAccountTotalsMatchLevels(t *testing.T, account storageStats) { + t.Helper() + var fromLevels jsonLevel + for _, level := range account.Levels { + fromLevels.Short += level.Short + fromLevels.Full += level.Full + fromLevels.Value += level.Value + } + if fromLevels.Short != account.Summary.Short || fromLevels.Full != account.Summary.Full || fromLevels.Value != account.Summary.Value { + t.Fatalf("account totals mismatch: levels=%+v totals=%+v", fromLevels, account.Summary) + } } func makeAccountsWithStorage(db *testDb, size int, storage bool) (addresses [][20]byte, accounts [][]byte) {