{"record":{"id":"a5aff3dc7756a2c7","repo":"hyperledger/fabric","slug":"writeset-contained-key-s-which-did-not-appear-in","errorCode":null,"errorMessage":"writeset contained key %s which did not appear in proposed config","messagePattern":"writeset contained key (.+?) which did not appear in proposed config","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/configtx/update.go","lineNumber":108,"sourceCode":"\n\t\tpolicy, ok := vi.policyForItem(existing)\n\t\tif !ok {\n\t\t\treturn errors.Errorf(\"unexpected missing policy %s for item %s\", existing.modPolicy(), key)\n\t\t}\n\n\t\t// Ensure the policy is satisfied\n\t\tif err := policy.EvaluateSignedData(signedData); err != nil {\n\t\t\tlogger.Warnw(\"policy not satisfied for channel configuration update\", \"key\", key, \"policy\", policy, \"signingIdenties\", protoutil.LogMessageForSerializedIdentities(signedData))\n\t\t\treturn errors.Wrapf(err, \"policy for %s not satisfied\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc verifyFullProposedConfig(writeSet, fullProposedConfig map[string]comparable) error {\n\tfor key := range writeSet {\n\t\tif _, ok := fullProposedConfig[key]; !ok {\n\t\t\treturn errors.Errorf(\"writeset contained key %s which did not appear in proposed config\", key)\n\t\t}\n\t}\n\treturn nil\n}\n\n// authorizeUpdate validates that all modified config has the corresponding modification policies satisfied by the signature set\n// it returns a map of the modified config\nfunc (vi *ValidatorImpl) authorizeUpdate(configUpdateEnv *cb.ConfigUpdateEnvelope) (map[string]comparable, error) {\n\tif configUpdateEnv == nil {\n\t\treturn nil, errors.Errorf(\"cannot process nil ConfigUpdateEnvelope\")\n\t}\n\n\tconfigUpdate, err := UnmarshalConfigUpdate(configUpdateEnv.ConfigUpdate)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif configUpdate.ChannelId != vi.channelID {","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/common/configtx/update.go#L90-L126","documentation":"verifyFullProposedConfig found a key present in the update's writeSet that is absent from the fully computed proposed config. The delta application (computeUpdateResult) should produce a config containing every written key; if one is missing, the writeSet references a key that mapConfig/the update machinery cannot place in the resulting config, indicating a malformed or inconsistent update.","triggerScenarios":"Calling proposeConfigUpdate/Validate with a writeSet key that computeUpdateResult dropped — e.g. a key whose namespace/group mapping fails, or a delta applied to a path that does not exist in the full proposed config.","commonSituations":"Constructing a ConfigUpdate programmatically with keys not matching the config tree structure; using an update file generated against a different channel or config version; corrupted/partially serialized write-set after manual proto editing.","solutions":["Regenerate the update from the actual current config using configtxlator (fetch config, decode, diff, encode) instead of hand-crafting the writeSet.","Compare the writeSet keys against the current channel config tree to find the orphan key.","Verify the update targets the same channel and was derived from that channel's latest config block.","Remove the stray key from the writeSet if it is not a legitimate change."],"exampleFix":"// before: hand-built writeSet with key \"Groups/Orderer/Org1/OldKey\" absent in proposed tree\n// after: diff current vs desired config via configtxlator so every writeSet key\n// appears in the recomputed full config\nconfig, _ := fetchAndDecodeChannelConfig(channelID)\nupdate := computeDiff(config, desiredConfig)","handlingStrategy":"validation","validationCode":"// Every writeSet key must be resolvable in the config tree before proposing\nfunc writeSetKeysResolvable(update *cb.ConfigUpdate) error {\n    for key := range update.GetWriteSet().GetValues() {\n        if key == \"\" { return fmt.Errorf(\"empty value key\") }\n    }\n    for g := range update.GetWriteSet().GetGroups() {\n        if g == \"\" { return fmt.Errorf(\"empty group key\") }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if _, err := validator.ProposeConfigUpdate(env, seq); err != nil {\n    if strings.Contains(err.Error(), \"did not appear in proposed config\") {\n        // rebuild update via configtxlator diff from current config\n    }\n    return err\n}","preventionTips":["Always generate the writeSet as a diff of the fetched current config vs desired config.","Never hand-craft writeSet keys; mirror the group/value/policy hierarchy exactly.","Tag update files with the channel and config sequence they were derived from."],"tags":["hyperledger-fabric","configtx","writeset"],"backgroundTag":"writeset-key-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"}