{"record":{"id":"03ae5c3bc7e830fb","repo":"ethereum/go-ethereum","slug":"t-unknown-node-type","errorCode":null,"errorMessage":"%T: unknown node type","messagePattern":"%T: unknown node type","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"trie/trie.go","lineNumber":759,"sourceCode":"\tcase *shortNode:\n\t\treturn &shortNode{\n\t\t\tflags: n.flags.copy(),\n\t\t\tKey:   common.CopyBytes(n.Key),\n\t\t\tVal:   copyNode(n.Val),\n\t\t}\n\tcase *fullNode:\n\t\tvar children [17]node\n\t\tfor i, cn := range n.Children {\n\t\t\tchildren[i] = copyNode(cn)\n\t\t}\n\t\treturn &fullNode{\n\t\t\tflags:    n.flags.copy(),\n\t\t\tChildren: children,\n\t\t}\n\tcase hashNode:\n\t\treturn n\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"%T: unknown node type\", n))\n\t}\n}\n\nfunc (t *Trie) resolve(n node, prefix []byte) (node, error) {\n\tif n, ok := n.(hashNode); ok {\n\t\treturn t.resolveAndTrack(n, prefix)\n\t}\n\treturn n, nil\n}\n\n// resolveAndTrack loads node from the underlying store with the given node hash\n// and path prefix and also tracks the loaded node blob in tracer treated as the\n// node's original value. The rlp-encoded blob is preferred to be loaded from\n// database because it's easy to decode node while complex to encode node to blob.\nfunc (t *Trie) resolveAndTrack(n hashNode, prefix []byte) (node, error) {\n\tblob, err := t.reader.Node(prefix, common.BytesToHash(n))\n\tif err != nil {\n\t\treturn nil, err","sourceCodeStart":741,"sourceCodeEnd":777,"githubUrl":"https://github.com/ethereum/go-ethereum/blob/6bb0588ad8e7f922e4ad5580f51265a4097af08f/trie/trie.go#L741-L777","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nfunc copyNode(n node) node {\n    switch n := (n).(type) {\n    // ... known cases ...\n    default:\n        panic(fmt.Sprintf(\"%T: unknown node type\", n))\n    }\n}\n\n// after (fork authors adding a node type)\n    case *myNode:\n        return n.clone()","handlingStrategy":"validation","validationCode":"// (Fork maintainers) assert node types at construction so Copy never sees strangers\nfunc assertKnownNode(n node) {\n    if !validChild(n) { panic(fmt.Sprintf(\"unknown node %T injected\", n)) }\n}","typeGuard":"// reuses validChild; call before Trie.Copy in fork code\nfunc copyable(t *trie.Trie) bool { /* walk via t.Get on sample paths or trust committed roots */ return true }","tryCatchPattern":"defer func() {\n    if r := recover(); r != nil {\n        err = fmt.Errorf(\"trie copy failed: %v\", r)\n    }\n}()\ncp := t.Copy()","preventionTips":["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"],"tags":["trie","mpt","copy","fork-maintenance","panic"],"backgroundTag":null,"analyzedSha":"6bb0588ad8e7f922e4ad5580f51265a4097af08f","analyzedAt":"2026-08-15T10:06:53.996Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}