{"record":{"id":"c1cd257419a3a606","repo":"hyperledger/fabric","slug":"nil-raft-config-metadata-options","errorCode":null,"errorMessage":"nil Raft config metadata options","messagePattern":"nil Raft config metadata options","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"orderer/consensus/etcdraft/util.go","lineNumber":214,"sourceCode":"\tconfigUpdate, err := configtx.UnmarshalConfigUpdateFromPayload(payload)\n\tif err != nil {\n\t\treturn nil, nil, errors.Wrap(err, \"could not read config update\")\n\t}\n\n\treturn MetadataFromConfigUpdate(configUpdate)\n}\n\n// VerifyConfigMetadata validates Raft config metadata.\n// Note: ignores certificates expiration.\nfunc VerifyConfigMetadata(metadata *etcdraft.ConfigMetadata, verifyOpts x509.VerifyOptions) error {\n\tif metadata == nil {\n\t\t// defensive check. this should not happen as CheckConfigMetadata\n\t\t// should always be called with non-nil config metadata\n\t\treturn errors.Errorf(\"nil Raft config metadata\")\n\t}\n\n\tif metadata.GetOptions() == nil {\n\t\treturn errors.Errorf(\"nil Raft config metadata options\")\n\t}\n\n\tif metadata.GetOptions().GetHeartbeatTick() == 0 ||\n\t\tmetadata.GetOptions().GetElectionTick() == 0 ||\n\t\tmetadata.GetOptions().GetMaxInflightBlocks() == 0 {\n\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)","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/hyperledger/fabric/blob/2736b63f8fd5932511d56fe68b7039d15977f7f6/orderer/consensus/etcdraft/util.go#L196-L232","documentation":"VerifyConfigMetadata requires the Options sub-message (HeartbeatTick, ElectionTick, MaxInflightBlocks, TickInterval, etc.) to be present. This error means metadata.GetOptions() returned nil, i.e. the Raft options section is entirely missing from the ConfigMetadata.","triggerScenarios":"A ConfigMetadata was built without Options, or the serialized config metadata in the channel's ConsensusType had an empty/omitted options field.","commonSituations":"Hand-crafted or migrated channel config dropping the options block; config generated by tooling that only sets consenters; protobuf default-empty message unmarshalled from empty metadata bytes.","solutions":["Regenerate channel config including a full etcdraft.Options block (TickInterval, ElectionTick, HeartbeatTick, MaxInflightBlocks, SnapshotIntervalSize).","Use configtxgen/configtxlator to diff your config against a known-good Raft config and restore Options.","Guard in code: verify meta.GetOptions() != nil before deeper validation."],"exampleFix":"// before: metadata with only consenters\nConf metadata = Metadata{Consenters: consenterList}\n// after\nConf metadata = Metadata{Consenters: consenterList, Options: &Options{TickInterval: \"500ms\", HeartbeatTick: 1, ElectionTick: 10, MaxInflightBlocks: 256}}","handlingStrategy":"validation","validationCode":"if metadata.GetOptions() == nil {\n\treturn errors.New(\"raft Options block missing from channel config metadata\")\n}","typeGuard":"func optionsPresent(m *etcdraft.ConfigMetadata) bool { return m != nil && m.GetOptions() != nil }","tryCatchPattern":"if err := VerifyConfigMetadata(meta, opts); err != nil {\n\tif strings.Contains(err.Error(), \"metadata options\") {\n\t\treturn errors.New(\"channel config lacks raft options; regenerate config with configtxgen\")\n\t}\n\treturn err\n}","preventionTips":["Always generate raft channel config from full profiles, never hand-trim Options.","Diff configs with configtxlator before submission.","Add Options checks to any config-generation tooling tests."],"tags":["hyperledger-fabric","raft","config","validation"],"backgroundTag":"nil-raft-config-metadata-options","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"}