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.
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
AI-assisted analysis of ethereum/go-ethereum@6bb0588ad8 (2026-08-15).
Data as JSON: /api/errors/03ae5c3bc7e830fb.
Report an issue: GitHub.
Appendix: 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)