canopy-network/canopy · warning
target round %d must be greater than current round %d
Error message
target round %d must be greater than current round %d
What it means
ScheduleForceRound refuses to schedule a forced round that is not strictly ahead of the controller's current round. It returns this error when round <= b.Round because forcing backwards or into the current round is an invalid view change.
Source
Thrown at bft/bft.go:635
if totalVotedPower > b.ValidatorSet.TotalPower/3 {
pacemakerRound = vote.Qc.Header.Round // set the highest round where +1/3rds have been
break
}
}
// if +1/3rd Round is larger than local Round - advance to the +1/3rd Round to better join the Majority
if pacemakerRound > b.Round {
b.log.Infof("Pacemaker peers set round: %d", pacemakerRound)
b.Round = pacemakerRound
b.round.Store(b.Round)
}
return true
}
// ScheduleForceRound enters the exact target round at the first pacemaker
// boundary at or after at. The caller must hold the Controller lock.
func (b *BFT) ScheduleForceRound(round uint64, at time.Time, timeoutRound *uint64) error {
if round <= b.Round {
return fmt.Errorf("target round %d must be greater than current round %d", round, b.Round)
}
b.forcedRound = round
b.forcedRoundAt = at
b.forcedTimeoutRound = timeoutRound
b.log.Warnf("Scheduled forced consensus round %d at %s", round, at.Format(time.RFC3339Nano))
if b.Phase == Pacemaker {
b.SetWaitTimers(time.Until(at), 0)
}
return nil
}
// PacemakerMessages is a collection of 'View' messages keyed by each Replica's public key
// These messages help Replicas synchronize their Rounds more effectively during periods of instability or failure
type PacemakerMessages map[string]*Message // [ public_key_string ] -> View message
// AddPacemakerMessage() adds the 'View' message to the list (keyed by public key string)
func (b *BFT) AddPacemakerMessage(msg *Message) (err lib.ErrorI) {
b.Controller.Lock()View on GitHub (pinned to ee8197d91d)
Solutions
- Re-read the current round immediately before scheduling and use max(current+1, desired)
- Hold the Controller lock while reading b.Round and scheduling so the check cannot race
- Skip the call if round <= current round (it is a no-op by definition, not an error to fix)
- If repeated, investigate why the node's round keeps advancing past the planned target
Example fix
// before
err := bft.ScheduleForceRound(42, time.Now().Add(time.Minute), nil)
// after
if round := bft.Round; targetRound <= round {
targetRound = round + 1
}
err := bft.ScheduleForceRound(targetRound, time.Now().Add(time.Minute), nil) Defensive patterns
Strategy: validation
Validate before calling
if targetRound <= bft.Round {
return fmt.Errorf("refusing to force: target %d <= current %d", targetRound, bft.Round)
} Type guard
func canForceRound(target, current uint64) bool { return target > current } Try / catch
if err := bft.ScheduleForceRound(round, at, nil); err != nil {
if strings.Contains(err.Error(), "must be greater than current round") {
// already at/past target — treat as success
return nil
}
return err
} Prevention
- Read b.Round under the Controller lock right before scheduling
- Derive target round from a fresh state read, not a cached snapshot
- Treat <= current round as a benign no-op
When it happens
Trigger: Calling BFT.ScheduleForceRound(round, at, timeoutRound) while holding the Controller lock with a target round equal to or below the node's current b.Round.
Common situations: An admin/operator tool computes the target round from a stale snapshot of the chain state while the node already advanced, or two force requests race so the second one targets a round that has since been reached.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Item too long: {len(item)} bytes (max 255)
- Invalid uint64 value: {value}
- Invalid chain_id: {self.chain_id}
- account-auth multisig requires threshold > 0
- invalid public key
AI-assisted analysis of canopy-network/canopy@ee8197d91d (2026-09-06).
Data as JSON: /api/errors/6ae40c16c956c09c.
Report an issue: GitHub.