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, errView on GitHub (pinned to 6bb0588ad8)
Solutions
- If you forked geth and added a node type, add a matching case to copyNode in trie/trie.go
- Otherwise treat it as state corruption: reload from a known-good root or resync
- Verify no data race is mutating the trie while Copy runs
- 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
- Only Copy tries built from committed roots
- When vendoring upstream trie changes, keep copyNode in sync with the node type set
- Add a unit test that builds every node type and runs Copy to catch drift
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
- %T: invalid node: %v (%v)
- invalid group depth size
- not implemented
- unexpected node type, %T
- %T: invalid node: %v
AI-assisted analysis of ethereum/go-ethereum@6bb0588ad8 (2026-08-15).
Data as JSON: /api/errors/03ae5c3bc7e830fb.
Report an issue: GitHub.