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
- Set ElectionTick strictly greater than HeartbeatTick (Fabric default: HeartbeatTick 1, ElectionTick 10).
- Submit a normal channel config update with corrected Options; note ElectionTick changes require careful liveness consideration.
- 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
- Keep ElectionTick at least 5-10x HeartbeatTick for stable elections.
- Review any manual tuning of tick values before config updates.
- Document tick semantics for operators editing configtx.yaml.
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
- nil Raft config metadata options
- none of HeartbeatTick (%d), ElectionTick (%d) and MaxInfligh
- TickInterval cannot be zero
- cannot marshal metadata for orderer type %s: %s
- nil metadata
AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04).
Data as JSON: /api/errors/a0e4d35db37a45be.
Report an issue: GitHub.