ethereum/go-ethereum · critical

%T: unknown node type

Error message

%T: unknown node type

What it means

This panic fires in the package-level copyNode helper when deep-copying a trie and the node type is not one of nil, valueNode, *shortNode, *fullNode, or hashNode. copyNode walks the whole trie (used by Trie.Copy for serving state to peers and for tests), so any structurally invalid node anywhere in the trie triggers it.

Source

Thrown at trie/trie.go:759

	case *shortNode:
		return &shortNode{
			flags: n.flags.copy(),
			Key:   common.CopyBytes(n.Key),
			Val:   copyNode(n.Val),
		}
	case *fullNode:
		var children [17]node
		for i, cn := range n.Children {
			children[i] = copyNode(cn)
		}
		return &fullNode{
			flags:    n.flags.copy(),
			Children: children,
		}
	case hashNode:
		return n
	default:
		panic(fmt.Sprintf("%T: unknown node type", n))
	}
}

func (t *Trie) resolve(n node, prefix []byte) (node, error) {
	if n, ok := n.(hashNode); ok {
		return t.resolveAndTrack(n, prefix)
	}
	return n, nil
}

// resolveAndTrack loads node from the underlying store with the given node hash
// and path prefix and also tracks the loaded node blob in tracer treated as the
// node's original value. The rlp-encoded blob is preferred to be loaded from
// database because it's easy to decode node while complex to encode node to blob.
func (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) {
	blob, err := t.reader.Node(prefix, common.BytesToHash(n))
	if err != nil {
		return nil, err

View on GitHub (pinned to 6bb0588ad8)

Solutions

  1. If you forked geth and added a node type, add a matching case to copyNode in trie/trie.go
  2. Otherwise treat it as state corruption: reload from a known-good root or resync
  3. Verify no data race is mutating the trie while Copy runs
  4. Reproduce with a minimal key/value sequence and dump the offending node to identify the writer

Example fix

// before
func copyNode(n node) node {
    switch n := (n).(type) {
    // ... known cases ...
    default:
        panic(fmt.Sprintf("%T: unknown node type", n))
    }
}

// after (fork authors adding a node type)
    case *myNode:
        return n.clone()
Defensive patterns

Strategy: validation

Validate before calling

// (Fork maintainers) assert node types at construction so Copy never sees strangers
func assertKnownNode(n node) {
    if !validChild(n) { panic(fmt.Sprintf("unknown node %T injected", n)) }
}

Type guard

// reuses validChild; call before Trie.Copy in fork code
func copyable(t *trie.Trie) bool { /* walk via t.Get on sample paths or trust committed roots */ return true }

Try / catch

defer func() {
    if r := recover(); r != nil {
        err = fmt.Errorf("trie copy failed: %v", r)
    }
}()
cp := t.Copy()

Prevention

When it happens

Trigger: Calling Trie.Copy() on a trie containing an unknown node type — same class of corruption as the insert/delete/get panics, but reached through the copy path. Also triggered by test code that hand-builds tries with placeholder node types.

Common situations: Fast-sync state serving after a corrupted node cache; unit tests constructing tries from raw structs; geth forks adding new node types without extending copyNode.

Related errors


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