trie: add optional json output

This commit is contained in:
MariusVanDerWijden 2026-01-07 11:58:55 +01:00
parent f93b0307b7
commit 889feb2e08
3 changed files with 79 additions and 7 deletions

View file

@ -38,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/internal/tablewriter"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
@ -98,7 +99,7 @@ Remove blockchain and state databases`,
Action: inspectTrie,
Name: "inspect-trie",
ArgsUsage: "<blocknum>",
Flags: slices.Concat([]cli.Flag{utils.ExcludeStorageFlag, utils.TopFlag}, utils.NetworkFlags, utils.DatabaseFlags),
Flags: slices.Concat([]cli.Flag{utils.ExcludeStorageFlag, utils.TopFlag, utils.OutputFileFlag}, utils.NetworkFlags, utils.DatabaseFlags),
Usage: "Print detailed trie information about the structure of account trie and storage tries.",
Description: `This commands iterates the entrie trie-backed state. If the 'blocknum' is not specified,
the latest block number will be used by default.`,
@ -449,6 +450,7 @@ func inspectTrie(ctx *cli.Context) error {
config := &trie.InspectConfig{
NoStorage: ctx.Bool(utils.ExcludeStorageFlag.Name),
TopN: ctx.Int(utils.TopFlag.Name),
Path: ctx.String(utils.OutputFileFlag.Name),
}
err := trie.Inspect(triedb, trieRoot, config)
if err != nil {
@ -831,7 +833,7 @@ func showMetaData(ctx *cli.Context) error {
data = append(data, []string{"headHeader.Root", fmt.Sprintf("%v", h.Root)})
data = append(data, []string{"headHeader.Number", fmt.Sprintf("%d (%#x)", h.Number, h.Number)})
}
table := rawdb.NewTableWriter(os.Stdout)
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Field", "Value"})
table.AppendBulk(data)
table.Render()

View file

@ -223,6 +223,11 @@ var (
Usage: "Print the top N results",
Value: 5,
}
OutputFileFlag = &cli.StringFlag{
Name: "output",
Usage: "Writes the result in json to the output",
Value: "",
}
SnapshotFlag = &cli.BoolFlag{
Name: "snapshot",

View file

@ -18,7 +18,9 @@ package trie
import (
"bytes"
"encoding/json"
"fmt"
"os"
"sort"
"strings"
"sync"
@ -48,9 +50,11 @@ type inspector struct {
// InspectConfig is a set of options to control inspection and format the
// output. TopN will print the deepest min(len(results), N) storage tries.
// If path is set, a JSON version of the data will be written to a file at this path.
type InspectConfig struct {
NoStorage bool
TopN int
Path string
}
// Inspect walks the trie with the given root and records the number and type of
@ -75,7 +79,13 @@ func Inspect(triedb database.NodeDatabase, root common.Hash, config *InspectConf
in.inspect(trie, trie.root, 0, []byte{}, in.stats[root])
in.wg.Wait()
in.DisplayResult()
if len(config.Path) > 0 {
if err := in.writeJSON(); err != nil {
log.Crit("Error during json encodeing", "error", err)
}
} else {
in.displayResult()
}
return nil
}
@ -163,7 +173,7 @@ func (in *inspector) inspect(trie *Trie, n node, height uint32, path []byte, sta
}
// Display results prints out the inspect results.
func (in *inspector) DisplayResult() {
func (in *inspector) displayResult() {
fmt.Println("Results for trie", in.root)
in.stats[in.root].display("Accounts trie")
fmt.Println("===")
@ -181,6 +191,30 @@ func (in *inspector) DisplayResult() {
}
}
func (in *inspector) writeJSON() error {
file, err := os.OpenFile(in.config.Path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777)
if err != nil {
return err
}
enc := json.NewEncoder(file)
accountTrie := newJsonStat(in.stats[in.root], "account trie")
if err := enc.Encode(accountTrie); err != nil {
return err
}
if !in.config.NoStorage {
// Sort stats by max node depth.
keys, stats := sortedTriestat(in.stats).sort()
for i := range keys[0:min(in.config.TopN, len(keys))] {
storageTrie := newJsonStat(stats[i], fmt.Sprintf("%x", keys[i]))
if err := enc.Encode(storageTrie); err != nil {
return err
}
}
}
return nil
}
// triestat tracks the type and count of trie nodes at each level in the trie.
//
// Note: theoretically it is possible to have up to 64 trie level. Since it is
@ -268,13 +302,12 @@ func (s *stat) add(other *stat) *stat {
func (s *triestat) display(title string) {
// Shorten title if too long.
if len(title) > 32 {
title = title[0:8] + "..." + title[len(title)-8:len(title)]
title = title[0:8] + "..." + title[len(title)-8:]
}
b := new(strings.Builder)
table := tablewriter.NewWriter(b)
table.SetHeader([]string{title, "Level", "Short Nodes", "Full Node", "Value Node"})
table.SetAlignment(1)
stat := &stat{}
for i := range s.level {
@ -282,7 +315,7 @@ func (s *triestat) display(title string) {
break
}
short, full, value := s.level[i].load()
table.Append([]string{"-", fmt.Sprint(i), fmt.Sprint(short), fmt.Sprint(full), fmt.Sprint(value)})
table.AppendBulk([][]string{{"-", fmt.Sprint(i), fmt.Sprint(short), fmt.Sprint(full), fmt.Sprint(value)}})
stat.add(&s.level[i])
}
short, full, value := stat.load()
@ -292,3 +325,35 @@ func (s *triestat) display(title string) {
fmt.Println("Max depth", s.maxDepth())
fmt.Println()
}
type jsonLevel struct {
Short uint64
Full uint64
Value uint64
}
type jsonStat struct {
Name string
Levels []jsonLevel
Summary jsonLevel
}
func newJsonStat(s *triestat, name string) *jsonStat {
ret := jsonStat{Name: name, Summary: jsonLevel{}}
for i := 0; i < len(s.level); i++ {
// only count non-empty levels
if s.level[i].empty() {
continue
}
level := jsonLevel{
Short: s.level[i].short.Load(),
Full: s.level[i].full.Load(),
Value: s.level[i].value.Load(),
}
ret.Summary.Full += level.Full
ret.Summary.Short += level.Short
ret.Summary.Value += level.Value
ret.Levels = append(ret.Levels, level)
}
return &ret
}