{"record":{"id":"a92d80a4c3c14ae7","repo":"hyperledger/fabric","slug":"pending-config-does-not-match-calculated-expected","errorCode":null,"errorMessage":"pending config does not match calculated expected config","messagePattern":"pending config does not match calculated expected config","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"orderer/consensus/smartbft/configverifier.go","lineNumber":244,"sourceCode":"\t} else {\n\t\texpectedConfigEnv, err = cbv.ConfigUpdateProposer.ProposeConfigUpdate(chdr.ChannelId, confEnv.LastUpdate)\n\t\tif err != nil {\n\t\t\tcbv.Logger.Errorf(\"Rejecting config proposal due to %v\", err)\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif err := cbv.checkConsentersMatchPolicy(confEnv.Config); err != nil {\n\t\treturn err\n\t}\n\n\t// Extract the Config from the result of ProposeConfigUpdate, and compare it\n\t// with the pending config.\n\tif proto.Equal(confEnv.Config, expectedConfigEnv.Config) {\n\t\treturn nil\n\t}\n\tcbv.Logger.Errorf(\"Pending Config is %v, but it should be %v\", confEnv.Config, expectedConfigEnv.Config)\n\treturn errors.Errorf(\"pending config does not match calculated expected config\")\n}\n","sourceCodeStart":226,"sourceCodeEnd":246,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/orderer/consensus/smartbft/configverifier.go#L226-L246","documentation":"During a SmartBFT config update, the orderer re-applies the proposed config envelope through ProposeConfigUpdate to compute what the resulting config SHOULD be, then compares it byte-for-byte (proto.Equal) with the config the proposal actually carried. This error means the two diverged: the config in the submitted CONFIG transaction is not the one the current config would produce when the update is applied. It is thrown from verifyConfigUpdateMsg, reached via ValidateConfig, so the update is rejected and the channel config stays unchanged.","triggerScenarios":"A CONFIG envelope is proposed whose embedded Config message differs from the recomputed Config produced by ProposeConfigUpdate on the current committed config — e.g. a peer/orderer tampered with or hand-crafted the Config fields instead of deriving them from a ConfigUpdateEnvelope, or the proposal raced with another config change so the expected result shifted.","commonSituations":"A fabric config update (adding an org, changing batch size, certificate rotation) is submitted against a stale ConfigUpdate so the recomputed result differs; a malicious or buggy client signs a modified Config directly; two config updates race so the second one's expected config no longer matches; tooling that edits channel config blocks manually re-submits an inconsistent Config.","solutions":["Regenerate the CONFIG envelope from a fresh, current config block using configtxlator/transport: fetch latest config, apply the ConfigUpdate, recompute, re-sign — do not reuse an old CONFIG envelope","Verify the ConfigUpdate was computed against the latest committed config sequence; refetch the newest config block and retry the update if another update changed the channel in between","Check for concurrent config submitters on the channel and serialize updates","Inspect the logger output 'Pending Config is %v, but it should be %v' to diff the submitted vs expected Config and find the mismatched field"],"exampleFix":"// before: reusing a stale CONFIG envelope fetched earlier\noldBlock := fetchConfigBlock(channel)\nenv := buildConfigEnvelopeFrom(oldBlock) // may mismatch current config\nsubmit(env)\n\n// after: always recompute from the latest committed config\nlatestBlock := fetchLatestConfigBlock(channel)\nupdate := computeConfigUpdate(latestBlock, desiredChanges)\nenv, _ := protoutil.CreateSignedEnvelope(..., update, ...)\nsubmit(env)","handlingStrategy":"validation","validationCode":"// Recompute the expected config exactly like the orderer does, before submitting\nexpected, err := endorser.ProposeConfigUpdate(configUpdateEnv, channelID)\nif err != nil { return err }\nsubmitted, _ := extractConfig(confEnv)\nif !proto.Equal(submitted, expected) {\n    return errors.New(\"config envelope is stale; recompute against latest config block\")\n}\nsubmit(confEnv)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always derive the CONFIG envelope from the latest fetched config block, never reuse cached ones","Serialize config updates — one submitter at a time per channel","Use configtxlator to compute updates rather than hand-editing configs","On failure, refetch the newest config block and retry with a recomputed update"],"tags":["config","consensus","fabric","smartbft","config-update"],"backgroundTag":"config-update-mismatch","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"}