hyperledger/fabric · error

nil Raft config metadata options

Error message

nil Raft config metadata options

What it means

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.

Solutions

  1. Regenerate channel config including a full etcdraft.Options block (TickInterval, ElectionTick, HeartbeatTick, MaxInflightBlocks, SnapshotIntervalSize).
  2. Use configtxgen/configtxlator to diff your config against a known-good Raft config and restore Options.
  3. Guard in code: verify meta.GetOptions() != nil before deeper validation.

Example fix

// before: metadata with only consenters
Conf metadata = Metadata{Consenters: consenterList}
// after
Conf metadata = Metadata{Consenters: consenterList, Options: &Options{TickInterval: "500ms", HeartbeatTick: 1, ElectionTick: 10, MaxInflightBlocks: 256}}
Defensive patterns

Strategy: validation

Validate before calling

if metadata.GetOptions() == nil {
	return errors.New("raft Options block missing from channel config metadata")
}

Type guard

func optionsPresent(m *etcdraft.ConfigMetadata) bool { return m != nil && m.GetOptions() != nil }

Try / catch

if err := VerifyConfigMetadata(meta, opts); err != nil {
	if strings.Contains(err.Error(), "metadata options") {
		return errors.New("channel config lacks raft options; regenerate config with configtxgen")
	}
	return err
}

Prevention

When it happens

Trigger: A ConfigMetadata was built without Options, or the serialized config metadata in the channel's ConsensusType had an empty/omitted options field.

Common situations: 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.

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/c1cd257419a3a606. Report an issue: GitHub.

Appendix: source

Thrown at orderer/consensus/etcdraft/util.go:214

	configUpdate, err := configtx.UnmarshalConfigUpdateFromPayload(payload)
	if err != nil {
		return nil, nil, errors.Wrap(err, "could not read config update")
	}

	return MetadataFromConfigUpdate(configUpdate)
}

// VerifyConfigMetadata validates Raft config metadata.
// Note: ignores certificates expiration.
func VerifyConfigMetadata(metadata *etcdraft.ConfigMetadata, verifyOpts x509.VerifyOptions) error {
	if metadata == nil {
		// defensive check. this should not happen as CheckConfigMetadata
		// should always be called with non-nil config metadata
		return errors.Errorf("nil Raft config metadata")
	}

	if metadata.GetOptions() == nil {
		return errors.Errorf("nil Raft config metadata options")
	}

	if metadata.GetOptions().GetHeartbeatTick() == 0 ||
		metadata.GetOptions().GetElectionTick() == 0 ||
		metadata.GetOptions().GetMaxInflightBlocks() == 0 {
		// if SnapshotIntervalSize is zero, DefaultSnapshotIntervalSize is used
		return errors.Errorf("none of HeartbeatTick (%d), ElectionTick (%d) and MaxInflightBlocks (%d) can be zero",
			metadata.GetOptions().GetHeartbeatTick(), metadata.GetOptions().GetElectionTick(), metadata.GetOptions().GetMaxInflightBlocks())
	}

	// check Raft options
	if metadata.GetOptions().GetElectionTick() <= metadata.GetOptions().GetHeartbeatTick() {
		return errors.Errorf("ElectionTick (%d) must be greater than HeartbeatTick (%d)",
			metadata.GetOptions().GetElectionTick(), metadata.GetOptions().GetHeartbeatTick())
	}

	if d, err := time.ParseDuration(metadata.GetOptions().GetTickInterval()); err != nil {
		return errors.Errorf("failed to parse TickInterval (%s) to time duration: %s", metadata.GetOptions().GetTickInterval(), err)

View on GitHub (pinned to 2736b63f8f)