{"record":{"id":"dfbcb0b42fb1f2cc","repo":"hyperledger/fabric","slug":"without-a-system-channel-a-follower-should-have-b","errorCode":null,"errorMessage":"without a system channel, a follower should have been created","messagePattern":"without a system channel, a follower should have been created","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"orderer/consensus/etcdraft/consenter.go","lineNumber":149,"sourceCode":"\t\tc.Logger.Debugf(\"Block metadata is nil at block height=%d, it is consensus-type migration\", support.Height())\n\t}\n\n\t// determine raft replica set mapping for each node to its id\n\t// for newly started chain we need to read and initialize raft\n\t// metadata by creating mapping between conseter and its id.\n\t// In case chain has been restarted we restore raft metadata\n\t// information from the recently committed block meta data\n\t// field.\n\tblockMetadata, err := ReadBlockMetadata(metadata, m)\n\tif err != nil {\n\t\treturn nil, errors.Wrapf(err, \"failed to read Raft metadata\")\n\t}\n\n\tconsenters := CreateConsentersMap(blockMetadata, m)\n\n\tid, err := c.detectSelfID(consenters)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"without a system channel, a follower should have been created\")\n\t}\n\n\tvar evictionSuspicion time.Duration\n\tif c.EtcdRaftConfig.EvictionSuspicion == \"\" {\n\t\tc.Logger.Infof(\"EvictionSuspicion not set, defaulting to %v\", DefaultEvictionSuspicion)\n\t\tevictionSuspicion = DefaultEvictionSuspicion\n\t} else {\n\t\tevictionSuspicion, err = time.ParseDuration(c.EtcdRaftConfig.EvictionSuspicion)\n\t\tif err != nil {\n\t\t\tc.Logger.Panicf(\"Failed parsing Consensus.EvictionSuspicion: %s: %v\", c.EtcdRaftConfig.EvictionSuspicion, err)\n\t\t}\n\t}\n\n\tvar tickInterval time.Duration\n\tif c.EtcdRaftConfig.TickIntervalOverride == \"\" {\n\t\ttickInterval, err = time.ParseDuration(m.GetOptions().GetTickInterval())\n\t\tif err != nil {\n\t\t\treturn nil, errors.Errorf(\"failed to parse TickInterval (%s) to time duration\", m.GetOptions().GetTickInterval())","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/orderer/consensus/etcdraft/consenter.go#L131-L167","documentation":"After building the consenter map, HandleChain (orderer/consensus/etcdraft/consenter.go:149) calls detectSelfID to find this orderer's TLS certificate among the channel consenters. If it is not found (and no system-channel-based follower/fetcher path applies), this error wraps the cause, indicating this node is not a consenter of the channel and, without a system channel to fall back on, it cannot serve or follow the channel.","triggerScenarios":"An orderer whose local TLS certificate does not match any consenter cert in the channel's raft metadata attempts HandleChain; detectSelfID returns 'failed to detect own cluster membership' or 'was not found in consenter set'.","commonSituations":"Orderer joined the channel before its consenter entry was added via config update; TLS certificates regenerated so the running cert differs from the one in channel config; typo/mismatch between Orderer.TLS settings and the consenter cert in channel config; running without a system channel where membership eviction means the node must not serve the channel.","solutions":["Add this orderer as a consenter to the channel config (with its exact current TLS server/client certs) via config update","If the node was intentionally removed, stop it and remove the channel from its local configuration (or delete its channel ledger) so it does not retry","Ensure the orderer's local TLS cert files match the certs registered in the channel consenter list","Compare certificate PEMs byte-for-byte between the node's tls/server.crt and channel config metadata"],"exampleFix":"// before: node starts channel it isn't a member of\norderer3 not listed in channel Consenters\n// after\nconfigtxlator update: add {Host: orderer3.example.com, Port: 7050, server/client certs of orderer3} to channel ConfigMetadata, then restart","handlingStrategy":"validation","validationCode":"consenters := CreateConsentersMap(blockMetadata, configMetadata)\nif _, err := detectSelfID(consenters); err != nil {\n    // this node is not a consenter: either add it via config update or remove the channel locally\n    return fmt.Errorf(\"node not in consenter set: %w\", err)\n}","typeGuard":"func nodeIsConsenter(selfTLSCert []byte, consenters map[uint64]*common.Consenter) bool {\n    for _, c := range consenters {\n        if bytes.Equal(c.GetServerTlsCert(), selfTLSCert) { return true }\n    }\n    return false\n}","tryCatchPattern":"chain, err := consenter.HandleChain(support, metadata)\nif err != nil {\n    if strings.Contains(err.Error(), \"without a system channel, a follower should have been created\") {\n        // check membership: add node to channel config or remove channel from this orderer\n    }\n}","preventionTips":["Only join orderers to channels after their consenter entry is committed","Keep local TLS certs in sync with the certs registered in channel config","Remove evicted nodes from the channel locally so they stop retrying"],"tags":["raft","membership","tls","consenter"],"backgroundTag":"consenter-self-id-not-found","analyzedSha":"2736b63f8fd5932511d56fe68b7039d15977f7f6","analyzedAt":"2026-09-04T08:52:36.465Z","contentChangedAt":"2026-09-04T08:52:36.465Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}