{"record":{"id":"5505f997262cfa0a","repo":"hyperledger/fabric","slug":"empty-consenter-set","errorCode":null,"errorMessage":"empty consenter set","messagePattern":"empty consenter set","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"orderer/consensus/etcdraft/util.go","lineNumber":238,"sourceCode":"\t\t// if SnapshotIntervalSize is zero, DefaultSnapshotIntervalSize is used\n\t\treturn errors.Errorf(\"none of HeartbeatTick (%d), ElectionTick (%d) and MaxInflightBlocks (%d) can be zero\",\n\t\t\tmetadata.GetOptions().GetHeartbeatTick(), metadata.GetOptions().GetElectionTick(), metadata.GetOptions().GetMaxInflightBlocks())\n\t}\n\n\t// check Raft options\n\tif metadata.GetOptions().GetElectionTick() <= metadata.GetOptions().GetHeartbeatTick() {\n\t\treturn errors.Errorf(\"ElectionTick (%d) must be greater than HeartbeatTick (%d)\",\n\t\t\tmetadata.GetOptions().GetElectionTick(), metadata.GetOptions().GetHeartbeatTick())\n\t}\n\n\tif d, err := time.ParseDuration(metadata.GetOptions().GetTickInterval()); err != nil {\n\t\treturn errors.Errorf(\"failed to parse TickInterval (%s) to time duration: %s\", metadata.GetOptions().GetTickInterval(), err)\n\t} else if d == 0 {\n\t\treturn errors.Errorf(\"TickInterval cannot be zero\")\n\t}\n\n\tif len(metadata.GetConsenters()) == 0 {\n\t\treturn errors.Errorf(\"empty consenter set\")\n\t}\n\n\t// verifying certificates for being signed by CA, expiration is ignored\n\tfor _, consenter := range metadata.GetConsenters() {\n\t\tif consenter == nil {\n\t\t\treturn errors.Errorf(\"metadata has nil consenter\")\n\t\t}\n\t\tif err := validateConsenterTLSCerts(consenter, verifyOpts, true); err != nil {\n\t\t\treturn errors.WithMessagef(err, \"consenter %s:%d has invalid certificate\", consenter.GetHost(), consenter.GetPort())\n\t\t}\n\t}\n\n\tif err := MetadataHasDuplication(metadata); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/orderer/consensus/etcdraft/util.go#L220-L256","documentation":"A Raft channel must have at least one consenter (ordering node). VerifyConfigMetadata returns this error when metadata.GetConsenters() is empty, since a cluster with zero nodes cannot elect a leader or replicate the ledger.","triggerScenarios":"A config update removes all consenters, or a ConfigMetadata is constructed with only Options but no Consenter entries.","commonSituations":"Attempting to shrink an ordering service to zero nodes; hand-built metadata for tests; consenters list dropped during channel-create config generation.","solutions":["Include at least one Consenter (host, port, client/server TLS certs) in ConfigMetadata.","If removing nodes, keep the remaining set non-empty and follow orderly consenter-exit procedures.","Regenerate config via configtxgen with the Raft orderer profiles listing the orderers."],"exampleFix":"// before\nConsenters: []*Consenter{}\n// after\nConsenters: []*Consenter{{Host: \"orderer.example.com\", Port: 7050, ClientTlsCert: c, ServerTlsCert: s}}","handlingStrategy":"validation","validationCode":"if len(metadata.GetConsenters()) == 0 {\n\treturn errors.New(\"channel config must define at least one raft consenter\")\n}","typeGuard":null,"tryCatchPattern":"if err := VerifyConfigMetadata(meta, opts); err != nil {\n\tif strings.Contains(err.Error(), \"empty consenter set\") {\n\t\treturn errors.New(\"cannot remove all ordering nodes from a raft channel\")\n\t}\n\treturn err\n}","preventionTips":["When removing orderers, always leave at least one (preferably a majority quorum) running.","Keep consenters lists in configtx.yaml profiles in sync with the live orderer set.","Dry-run config updates through VerifyConfigMetadata before submission."],"tags":["hyperledger-fabric","raft","config","consenters"],"backgroundTag":"empty-consenter-set","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"}