hyperledger/fabric · error

ElectionTick (%d) must be greater than HeartbeatTick (%d)

Error message

ElectionTick (%d) must be greater than HeartbeatTick (%d)

What it means

In etcd/raft, ElectionTick must be strictly greater than HeartbeatTick; otherwise heartbeats could trigger elections or elections would never time out properly. VerifyConfigMetadata enforces this ordering and echoes both values in the message.

Source

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

		// 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")
	}

	// verifying certificates for being signed by CA, expiration is ignored
	for _, consenter := range metadata.GetConsenters() {
		if consenter == nil {
			return errors.Errorf("metadata has nil consenter")
		}

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Set ElectionTick strictly greater than HeartbeatTick (Fabric default: HeartbeatTick 1, ElectionTick 10).
  2. Submit a normal channel config update with corrected Options; note ElectionTick changes require careful liveness consideration.
  3. Pre-validate with VerifyConfigMetadata before submitting the update.

Example fix

// before
Options{HeartbeatTick: 10, ElectionTick: 5}
// after
Options{HeartbeatTick: 1, ElectionTick: 10}
Defensive patterns

Strategy: validation

Validate before calling

o := metadata.GetOptions()
if o.GetElectionTick() <= o.GetHeartbeatTick() {
	return fmt.Errorf("ElectionTick(%d) must exceed HeartbeatTick(%d)", o.GetElectionTick(), o.GetHeartbeatTick())
}

Try / catch

if err := VerifyConfigMetadata(meta, opts); err != nil {
	if strings.Contains(err.Error(), "must be greater than HeartbeatTick") {
		return fmt.Errorf("invalid tick ratio in config update: %v", err)
	}
	return err
}

Prevention

When it happens

Trigger: A config update sets Options.ElectionTick <= Options.HeartbeatTick (e.g. both 1, or ElectionTick 2 with HeartbeatTick 5).

Common situations: Hand-edited channel config where someone tuned ticks and inverted the relationship; copying tick values between profiles; confusion about tick semantics when migrating from Kafka.

Related errors


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