hyperledger/fabric · error

none of HeartbeatTick

Error message

none of HeartbeatTick (%d), ElectionTick (%d) and MaxInflightBlocks (%d) can be zero

What it means

Raft timing relies on HeartbeatTick, ElectionTick and MaxInflightBlocks all being non-zero; a zero value would break leader election or flow control. VerifyConfigMetadata reports the current values of all three whenever any of them is zero.

Solutions

  1. Set all three fields to positive values (e.g. HeartbeatTick: 1, ElectionTick: 10, MaxInflightBlocks: 256) in the config metadata.
  2. Validate the metadata locally with VerifyConfigMetadata before submitting the config update.
  3. Re-run configtxgen with correct Raft profile settings and inspect the produced configtx.yaml.

Example fix

// before
Options{HeartbeatTick: 0, ElectionTick: 10, MaxInflightBlocks: 256}
// after
Options{HeartbeatTick: 1, ElectionTick: 10, MaxInflightBlocks: 256}
Defensive patterns

Strategy: validation

Validate before calling

o := metadata.GetOptions()
if o.GetHeartbeatTick() == 0 || o.GetElectionTick() == 0 || o.GetMaxInflightBlocks() == 0 {
	return errors.New("raft tick/inflight options must be positive")
}

Try / catch

if err := VerifyConfigMetadata(meta, opts); err != nil {
	if strings.Contains(err.Error(), "can be zero") {
		// parse reported values from message and surface them to the operator
		return fmt.Errorf("fix raft options in channel config: %v", err)
	}
	return err
}

Prevention

When it happens

Trigger: A channel config update sets ConsensusType metadata with any of Options.HeartbeatTick, Options.ElectionTick or Options.MaxInflightBlocks equal to 0.

Common situations: Typo in configtx.yaml consensus options; copying a template and deleting a field; constructing Options in Go code leaving fields at their zero value; partial migration of config between channels.

Related errors


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

Appendix: source

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

// 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)
	} else if d == 0 {
		return errors.Errorf("TickInterval cannot be zero")
	}

	if len(metadata.GetConsenters()) == 0 {
		return errors.Errorf("empty consenter set")
	}

View on GitHub (pinned to 2736b63f8f)