ethereum/go-ethereum · critical

%T: invalid node: %v

Error message

%T: invalid node: %v

What it means

LevelStats.add panics when asked to account for a node that is not a *shortNode, *fullNode, or valueNode. The per-level statistics structure has buckets only for those three node kinds; any other node type (or a typed nil in an unexpected interface shape) violates the trie's node model.

Source

Thrown at trie/levelstats.go:83

	var total uint64
	for i := range s.level {
		total += s.level[i].short.Load() + s.level[i].full.Load() + s.level[i].value.Load()
	}
	return total
}

// add increases the node count by one for the specified node type and depth.
func (s *LevelStats) add(n node, depth uint32) {
	d := int(depth)
	switch (n).(type) {
	case *shortNode:
		s.level[d].short.Add(1)
	case *fullNode:
		s.level[d].full.Add(1)
	case valueNode:
		s.level[d].value.Add(1)
	default:
		panic(fmt.Sprintf("%T: invalid node: %v", n, n))
	}
}

// addSize increases the raw byte-size tally at the specified depth.
func (s *LevelStats) addSize(depth uint32, size uint64) {
	s.level[depth].size.Add(size)
}

// AddLeaf records a leaf depth. Witness collection reuses the value-node bucket
// for leaf accounting. It panics if the depth is outside [0, 15].
func (s *LevelStats) AddLeaf(depth int) {
	s.level[depth].value.Add(1)
}

// LeafDepths returns leaf counts grouped by depth.
func (s *LevelStats) LeafDepths() [trieStatLevels]int64 {
	var leaves [trieStatLevels]int64
	for i := range s.level {

View on GitHub (pinned to 6bb0588ad8)

Solutions

  1. If you extended node types, add a case (or explicit skip) for them in LevelStats.add.
  2. Ensure the trie is not being mutated while statistics are collected.
  3. Validate/recover the trie data source before running statistics.
  4. Report with the %T value from the panic message to identify the offending type.
Defensive patterns

Strategy: try-catch

Try / catch

defer func() {
    if r := recover(); r != nil {
        err = fmt.Errorf("stats collection hit invalid node: %v", r)
    }
}()
stat := trie.NewLevelStats() // ...
_ = stat

Prevention

When it happens

Trigger: Feeding hashNode or custom node implementations into LevelStats.add; statistics collection running over corrupted trie memory; forks adding node types without extending the stats switch.

Common situations: Trie dump/size-analysis tooling over damaged data; concurrent mutation racing the statistics pass; version drift where a new node type exists but levelstats.go was not updated.

Related errors


AI-assisted analysis of ethereum/go-ethereum@6bb0588ad8 (2026-08-15). Data as JSON: /api/errors/d72a5ff9d17bdb16. Report an issue: GitHub.