{"record":{"id":"b9703a0cfe09dac5","repo":"hyperledger/fabric","slug":"d-out-of-d-nodes-are-alive-configuration-will-r","errorCode":null,"errorMessage":"%d out of %d nodes are alive, configuration will result in quorum loss","messagePattern":"(.+?) out of (.+?) nodes are alive, configuration will result in quorum loss","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"orderer/consensus/etcdraft/chain.go","lineNumber":1546,"sourceCode":"\tc.raftMetadataLock.RUnlock()\n\n\tdummyOldConsentersMap := CreateConsentersMap(dummyOldBlockMetadata, oldMetadata)\n\tchanges, err := ComputeMembershipChanges(dummyOldBlockMetadata, dummyOldConsentersMap, newMetadata.GetConsenters())\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// new config metadata was verified above. Additionally need to check new consenters for certificates expiration\n\tfor _, c := range changes.AddedNodes {\n\t\tif err := validateConsenterTLSCerts(c, verifyOpts, false); err != nil {\n\t\t\treturn errors.Wrapf(err, \"consenter %s:%d has invalid certificates\", c.GetHost(), c.GetPort())\n\t\t}\n\t}\n\n\tactive := c.ActiveNodes.Load().([]uint64)\n\tif changes.UnacceptableQuorumLoss(active) {\n\t\tc.logger.Debugf(\"%d out of %d nodes are alive - %+v\", len(active), len(dummyOldConsentersMap), active)\n\t\treturn errors.Errorf(\"%d out of %d nodes are alive, configuration will result in quorum loss\", len(active), len(dummyOldConsentersMap))\n\t}\n\n\treturn nil\n}\n\n// StatusReport returns the ConsensusRelation & Status\nfunc (c *Chain) StatusReport() (types.ConsensusRelation, types.Status) {\n\tc.statusReportMutex.Lock()\n\tdefer c.statusReportMutex.Unlock()\n\n\treturn c.consensusRelation, c.status\n}\n\nfunc (c *Chain) suspectEviction() bool {\n\tif c.isRunning() != nil {\n\t\treturn false\n\t}\n","sourceCodeStart":1528,"sourceCodeEnd":1564,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/orderer/consensus/etcdraft/chain.go#L1528-L1564","documentation":"The etcdraft chain refuses config updates that would leave the raft cluster unable to maintain quorum (orderer/consensus/etcdraft/chain.go:1546). After computing membership changes, UnacceptableQuorumLoss compares currently alive nodes against the new effective quorum; if removing nodes would make the alive count insufficient, the update is rejected. This protects the cluster from permanent unavailability.","triggerScenarios":"A config update removes consenters such that the number of currently responsive nodes would fall below the quorum required by the resulting cluster size (e.g. shrinking 5-node cluster to 3 while only 2 nodes are alive).","commonSituations":"Decommissioning several orderers at once during infrastructure cleanup; removing nodes whose containers are stopped; maintenance that temporarily shuts down nodes before submitting the shrink update.","solutions":["Bring the remaining nodes back online so enough nodes are alive before submitting the update","Shrink the cluster in stages (e.g. 5→4→3), re-verifying liveness at each step","Remove the nodes from the config first while they are still running and reachable","Check ActiveNodes liveness via the orderer metrics/logs before submitting"],"exampleFix":"// before: removing 3 of 5 consenters while 2 are down\nConfigUpdate: remove orderer3, orderer4, orderer5 (only 2 alive)\n// after: staged removal\nStep 1: restart orderer4/orderer5, then remove orderer5 only; Step 2: remove orderer4 once quorum-verified","handlingStrategy":"validation","validationCode":"active := c.ActiveNodes.Load().([]uint64)\nif changes.UnacceptableQuorumLoss(active) {\n    return fmt.Errorf(\"abort update: only %d of %d nodes alive would break quorum\", len(active), len(changes.TotalNodes))\n}","typeGuard":"func safeToRemove(currentAlive, currentTotal, removing int) bool {\n    remaining := currentTotal - removing\n    quorum := remaining/2 + 1 // majority quorum\n    return currentAlive-0 >= quorum\n}","tryCatchPattern":"if err := submitConfigUpdate(cfg); err != nil {\n    if strings.Contains(err.Error(), \"quorum loss\") {\n        // restart dead nodes or remove fewer consenters per step\n    }\n}","preventionTips":["Verify node liveness (metrics/logs) before submitting shrink updates","Shrink raft clusters one node at a time","Keep a majority of consenters running during maintenance windows"],"tags":["raft","quorum","config-update","availability"],"backgroundTag":"raft-quorum-loss","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"}