mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 11:20:45 +00:00
trie: add optional json output
This commit is contained in:
parent
f93b0307b7
commit
889feb2e08
3 changed files with 79 additions and 7 deletions
|
|
@ -38,6 +38,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/tablewriter"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
|
@ -98,7 +99,7 @@ Remove blockchain and state databases`,
|
||||||
Action: inspectTrie,
|
Action: inspectTrie,
|
||||||
Name: "inspect-trie",
|
Name: "inspect-trie",
|
||||||
ArgsUsage: "<blocknum>",
|
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.",
|
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,
|
Description: `This commands iterates the entrie trie-backed state. If the 'blocknum' is not specified,
|
||||||
the latest block number will be used by default.`,
|
the latest block number will be used by default.`,
|
||||||
|
|
@ -449,6 +450,7 @@ func inspectTrie(ctx *cli.Context) error {
|
||||||
config := &trie.InspectConfig{
|
config := &trie.InspectConfig{
|
||||||
NoStorage: ctx.Bool(utils.ExcludeStorageFlag.Name),
|
NoStorage: ctx.Bool(utils.ExcludeStorageFlag.Name),
|
||||||
TopN: ctx.Int(utils.TopFlag.Name),
|
TopN: ctx.Int(utils.TopFlag.Name),
|
||||||
|
Path: ctx.String(utils.OutputFileFlag.Name),
|
||||||
}
|
}
|
||||||
err := trie.Inspect(triedb, trieRoot, config)
|
err := trie.Inspect(triedb, trieRoot, config)
|
||||||
if err != nil {
|
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.Root", fmt.Sprintf("%v", h.Root)})
|
||||||
data = append(data, []string{"headHeader.Number", fmt.Sprintf("%d (%#x)", h.Number, h.Number)})
|
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.SetHeader([]string{"Field", "Value"})
|
||||||
table.AppendBulk(data)
|
table.AppendBulk(data)
|
||||||
table.Render()
|
table.Render()
|
||||||
|
|
|
||||||
|
|
@ -223,6 +223,11 @@ var (
|
||||||
Usage: "Print the top N results",
|
Usage: "Print the top N results",
|
||||||
Value: 5,
|
Value: 5,
|
||||||
}
|
}
|
||||||
|
OutputFileFlag = &cli.StringFlag{
|
||||||
|
Name: "output",
|
||||||
|
Usage: "Writes the result in json to the output",
|
||||||
|
Value: "",
|
||||||
|
}
|
||||||
|
|
||||||
SnapshotFlag = &cli.BoolFlag{
|
SnapshotFlag = &cli.BoolFlag{
|
||||||
Name: "snapshot",
|
Name: "snapshot",
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ package trie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -48,9 +50,11 @@ type inspector struct {
|
||||||
|
|
||||||
// InspectConfig is a set of options to control inspection and format the
|
// InspectConfig is a set of options to control inspection and format the
|
||||||
// output. TopN will print the deepest min(len(results), N) storage tries.
|
// 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 {
|
type InspectConfig struct {
|
||||||
NoStorage bool
|
NoStorage bool
|
||||||
TopN int
|
TopN int
|
||||||
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inspect walks the trie with the given root and records the number and type of
|
// 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.inspect(trie, trie.root, 0, []byte{}, in.stats[root])
|
||||||
in.wg.Wait()
|
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
|
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.
|
// Display results prints out the inspect results.
|
||||||
func (in *inspector) DisplayResult() {
|
func (in *inspector) displayResult() {
|
||||||
fmt.Println("Results for trie", in.root)
|
fmt.Println("Results for trie", in.root)
|
||||||
in.stats[in.root].display("Accounts trie")
|
in.stats[in.root].display("Accounts trie")
|
||||||
fmt.Println("===")
|
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.
|
// 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
|
// 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) {
|
func (s *triestat) display(title string) {
|
||||||
// Shorten title if too long.
|
// Shorten title if too long.
|
||||||
if len(title) > 32 {
|
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)
|
b := new(strings.Builder)
|
||||||
table := tablewriter.NewWriter(b)
|
table := tablewriter.NewWriter(b)
|
||||||
table.SetHeader([]string{title, "Level", "Short Nodes", "Full Node", "Value Node"})
|
table.SetHeader([]string{title, "Level", "Short Nodes", "Full Node", "Value Node"})
|
||||||
table.SetAlignment(1)
|
|
||||||
|
|
||||||
stat := &stat{}
|
stat := &stat{}
|
||||||
for i := range s.level {
|
for i := range s.level {
|
||||||
|
|
@ -282,7 +315,7 @@ func (s *triestat) display(title string) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
short, full, value := s.level[i].load()
|
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])
|
stat.add(&s.level[i])
|
||||||
}
|
}
|
||||||
short, full, value := stat.load()
|
short, full, value := stat.load()
|
||||||
|
|
@ -292,3 +325,35 @@ func (s *triestat) display(title string) {
|
||||||
fmt.Println("Max depth", s.maxDepth())
|
fmt.Println("Max depth", s.maxDepth())
|
||||||
fmt.Println()
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue