{"record":{"id":"0b2b29b953586b70","repo":"hyperledger/fabric","slug":"invalid-new-config-bft-consenters-are-missing","errorCode":null,"errorMessage":"Invalid new config: bft consenters are missing","messagePattern":"Invalid new config: bft consenters are missing","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"orderer/common/msgprocessor/maintenancefilter.go","lineNumber":222,"sourceCode":"\t\t}\n\t} else {\n\t\treturn errors.Errorf(\"update does not contain the %s group\", channelconfig.OrdererGroupKey)\n\t}\n\n\treturn nil\n}\n\nfunc validateBFTConsenterMapping(currentOrdererConfig channelconfig.Orderer, nextOrdererConfig channelconfig.Orderer) error {\n\t// extract raft consenters from consensusTypeValue.metadata\n\traftMetadata := &etcdraft.ConfigMetadata{}\n\tproto.Unmarshal(currentOrdererConfig.ConsensusMetadata(), raftMetadata)\n\traftConsenters := raftMetadata.GetConsenters()\n\n\t// extract bft consenters\n\tbftConsenters := nextOrdererConfig.Consenters()\n\n\tif len(bftConsenters) == 0 {\n\t\treturn errors.Errorf(\"Invalid new config: bft consenters are missing\")\n\t}\n\n\tif len(raftConsenters) != len(bftConsenters) {\n\t\treturn errors.Errorf(\"Invalid new config: the number of bft consenters: %d is not equal to the number of raft consenters: %d\", len(bftConsenters), len(raftConsenters))\n\t}\n\n\tfor _, raftConsenter := range raftConsenters {\n\t\tflag := false\n\t\tfor _, bftConsenter := range bftConsenters {\n\t\t\tif raftConsenter.Port == bftConsenter.Port && raftConsenter.Host == bftConsenter.Host &&\n\t\t\t\tbytes.Equal(raftConsenter.ServerTlsCert, bftConsenter.ServerTlsCert) &&\n\t\t\t\tbytes.Equal(raftConsenter.ClientTlsCert, bftConsenter.ClientTlsCert) {\n\t\t\t\tflag = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !flag {\n\t\t\treturn errors.Errorf(\"No suitable BFT consenter for Raft consenter: %v\", raftConsenter)","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/orderer/common/msgprocessor/maintenancefilter.go#L204-L240","documentation":"validateBFTConsenterMapping checks the next (proposed) orderer config when migrating from Raft (etcdraft) to BFT consensus. The new config must list at least one BFT consenter; an empty consenter set means the resulting channel would have no orderers and cannot function, so it is rejected outright.","triggerScenarios":"A migration config update whose Orderer group results in nextOrdererConfig.Consenters() returning an empty slice — e.g. the ConsensusType value's new metadata omits the consenters list entirely.","commonSituations":"Hand-editing the consensus_type metadata via configtxlator and forgetting to carry the consenters array into the new BFT metadata; generating metadata with only type/state but no nodes.","solutions":["Populate the consenters array (host, port, client/server TLS certs) in the new ConsensusType metadata.","Copy the existing Raft consenters as the starting point for the BFT consenter list, then adjust as needed.","Validate the marshaled metadata structure with configtxlator before submitting the update."],"exampleFix":"// before\nmetadata := {\"State\": \"MAINTENANCE\", \"ConsensusType\": \"BFT\"} // no consenters\n// after\nmetadata := {\"ConsensusType\": \"BFT\", \"Consenters\": [{\"Host\": \"n1\", \"Port\": 7050, ...}]}","handlingStrategy":"validation","validationCode":"var meta etcdraft.Metadata\nprotojson.Unmarshal(nextConsensusTypeValue, &meta)\nif len(meta.GetConsenters()) == 0 {\n    return errors.New(\"new consensus metadata must include consenters\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Seed the BFT metadata consenters from the existing Raft consenters list.","Validate marshaled metadata with configtxlator before submission.","Never hand-write consensus metadata from scratch."],"tags":["hyperledger-fabric","raft","bft","consensus-migration"],"backgroundTag":"consenter-config-invalid","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"}